aboutsummaryrefslogtreecommitdiff
path: root/devel/gamin/files/patch-server_gam_kqueue.c
diff options
context:
space:
mode:
authorJean-Yves Lefort <jylefort@FreeBSD.org>2005-05-09 15:46:39 +0000
committerJean-Yves Lefort <jylefort@FreeBSD.org>2005-05-09 15:46:39 +0000
commit8c8500aaef431618d740029a73ef987ad40bc18a (patch)
treea41e8d33e826aee41e7dd041bed6bc727df2f9d8 /devel/gamin/files/patch-server_gam_kqueue.c
parent3a29824ba26c83704560fd9fe8bae618ab8ec4ea (diff)
downloadports-8c8500aaef431618d740029a73ef987ad40bc18a.tar.gz
ports-8c8500aaef431618d740029a73ef987ad40bc18a.zip
Improve the kqueue backend:
- also use kqueue to monitor files within a monitored directory - tremendously improve the scalability of the backend when monitoring very large directories - periodically attempt to switch polled files to kqueue notification - do not perform an extra useless lstat() when a missing file is created - code cleanups Approved by: marcus
Notes
Notes: svn path=/head/; revision=134932
Diffstat (limited to 'devel/gamin/files/patch-server_gam_kqueue.c')
-rw-r--r--devel/gamin/files/patch-server_gam_kqueue.c1176
1 files changed, 854 insertions, 322 deletions
diff --git a/devel/gamin/files/patch-server_gam_kqueue.c b/devel/gamin/files/patch-server_gam_kqueue.c
index 53708f66fae5..9edf541d131b 100644
--- a/devel/gamin/files/patch-server_gam_kqueue.c
+++ b/devel/gamin/files/patch-server_gam_kqueue.c
@@ -1,6 +1,6 @@
---- server/gam_kqueue.c.orig Sun Apr 10 13:21:46 2005
-+++ server/gam_kqueue.c Sun Apr 10 13:29:17 2005
-@@ -0,0 +1,730 @@
+--- server/gam_kqueue.c.orig Sat Apr 30 12:26:31 2005
++++ server/gam_kqueue.c Sun May 1 08:18:47 2005
+@@ -0,0 +1,1262 @@
+/*
+ * gam_kqueue.c - a kqueue(2) Gamin backend
+ *
@@ -21,13 +21,8 @@
+ * resides on from being unmounted, because kqueue can only
+ * monitor open files.
+ *
-+ * * To monitor changes made to files within a monitored
-+ * directory, we periodically poll the files. If we wanted to
-+ * use kqueue, we would have to open every file in the
-+ * directory, which is not acceptable.
-+ *
+ * * The creation of missing monitored files is detected by
-+ * performing a stat() every second. Although using kqueue to
++ * performing a lstat() every second. Although using kqueue to
+ * monitor the parent directory is technically feasible, it
+ * would introduce a lot of complexity in the code.
+ *
@@ -39,7 +34,7 @@
+ * - kqueue needs to be moved out the UFS code.
+ *
+ * Copyright (C) 2005 Joe Marcus Clarke <marcus@FreeBSD.org>
-+ * Copyright (C) 2005 Jean-Yves Lefort <jylefort@brutele.be>
++ * Copyright (C) 2005 Jean-Yves Lefort <jylefort@FreeBSD.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
@@ -61,6 +56,7 @@
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/types.h>
++#include <sys/sysctl.h>
+#include <sys/stat.h>
+#include <sys/event.h>
+#include <sys/time.h>
@@ -70,12 +66,41 @@
+#include "gam_event.h"
+#include "gam_server.h"
+
-+#define VN_NOTE_ALL (NOTE_DELETE | NOTE_WRITE | NOTE_EXTEND | \
-+ NOTE_ATTRIB | NOTE_LINK | NOTE_RENAME | \
-+ NOTE_REVOKE)
++/*** tunable constants, modify to tweak the backend aggressivity *************/
++
++/*
++ * The backend will use at most n file descriptors, where n is the
++ * minimum value between (kern.maxfiles * CFG_GLOBAL_FILE_RESERVE_RATIO)
++ * and (kern.maxfilesperproc - CFG_SELF_FILE_RESERVE).
++ */
++#define CFG_GLOBAL_FILE_RESERVE_RATIO 0.7
++#define CFG_SELF_FILE_RESERVE 200
++
++/*
++ * If a SubMonitor or FileMonitor is not supported by kqueue and has
++ * to be polled, the backend will re-attempt to enable kqueue
++ * notification every n polls.
++ */
++#define CFG_UNSUPPORTED_SMON_KQUEUE_RETRY_FREQUENCY 10
++#define CFG_UNSUPPORTED_FMON_KQUEUE_RETRY_FREQUENCY 10
++
++/*
++ * The various poll intervals, in milliseconds. The default interval
++ * for each poller is based on the poller's expected usage.
++ */
++#define CFG_MISSING_SMON_POLL_INTERVAL 1000
++#define CFG_UNSUPPORTED_SMON_POLL_INTERVAL 3000
++#define CFG_UNSUPPORTED_FMON_POLL_INTERVAL 3000
++
++/*** end of tunable constants ************************************************/
++
+#define VN_NOTE_CHANGED (NOTE_WRITE | NOTE_EXTEND | NOTE_ATTRIB | NOTE_LINK)
+#define VN_NOTE_DELETED (NOTE_DELETE | NOTE_REVOKE)
++#define VN_NOTE_ALL (VN_NOTE_CHANGED | VN_NOTE_DELETED | NOTE_RENAME)
+
++/*
++ * A barebone stat structure, only containing the fields we need.
++ */
+typedef struct
+{
+ gboolean exists;
@@ -88,66 +113,176 @@
+ off_t size;
+} MiniStat;
+
++typedef void (*HashTableAddFunc) (GHashTable *table,
++ gpointer item,
++ gpointer user_data);
++typedef void (*HashTablePostAddFunc) (GHashTable *table,
++ gpointer user_data);
++typedef void (*HashTableRemoveFunc) (GHashTable *table,
++ gpointer item,
++ gpointer user_data);
++typedef void (*HashTablePostRemoveFunc) (GHashTable *table,
++ gpointer user_data);
++
+typedef struct
+{
-+ const char *path; /* belongs to the first sub in subs */
-+ GList *subs;
-+ GSList *files; /* list of files in directory */
-+ MiniStat sb; /* for poll */
++ HashTableAddFunc add;
++ HashTablePostAddFunc post_add;
++ HashTableRemoveFunc remove;
++ HashTablePostRemoveFunc post_remove;
++} HashTableMethods;
++
++/*
++ * A hash table which can be modified while iterating over it.
++ */
++typedef struct
++{
++ GHashTable *main;
++ gboolean iterating;
++ GSList *pending_additions;
++ GSList *pending_removals;
++ HashTableMethods *methods;
++ gpointer user_data;
++} HashTable;
++
++/* the base monitor class */
++typedef struct _Monitor Monitor;
++struct _Monitor
++{
++ void (*handle_kevent) (Monitor *monitor, struct kevent *event);
++ char *pathname;
+ int fd; /* for kqueue */
-+ gboolean isdir; /* is a directory monitor? */
-+} Monitor;
++ MiniStat sb; /* for poll */
++ unsigned int poll_count;
++};
++#define MONITOR(ptr) ((Monitor *) ptr)
++
++typedef enum
++{
++ MONITOR_ISDIR = 1 << 0,
++ MONITOR_ISNOTDIR = 1 << 1
++} MonitorFlags;
++#define MONITOR_FLAGS_SHIFT 2
+
++/* monitor for Gamin subscriptions */
+typedef struct
+{
-+ char *pathname;
-+ char *filename; /* pointer into pathname */
-+ MiniStat sb;
-+} FileEntry;
++ Monitor base;
++ GList *subs;
++ HashTable *fmons; /* FileMonitor objects */
++ HashTable *unsupported_fmons; /* subset of fmons to poll */
++ gboolean isdir; /* is a directory subscription? */
++} SubMonitor;
++#define SUB_MONITOR(ptr) ((SubMonitor *) ptr)
+
++typedef enum
++{
++ SUB_MONITOR_WAS_MISSING = 1 << MONITOR_FLAGS_SHIFT
++} SubMonitorFlags;
++
++/* monitor for files within directory subscriptions */
+typedef struct
+{
-+ GSourceFunc func;
++ Monitor base;
++ SubMonitor *smon; /* the SubMonitor this fmon belongs to */
++ char *filename; /* pointer into base.pathname */
++} FileMonitor;
++#define FILE_MONITOR(ptr) ((FileMonitor *) ptr)
++
++typedef enum
++{
++ FILE_MONITOR_POSSIBLY_RECREATED = 1 << MONITOR_FLAGS_SHIFT
++} FileMonitorFlags;
++
++typedef void (*PollerFunc) (SubMonitor *smon);
++typedef struct
++{
++ PollerFunc func;
+ unsigned int interval;
+ unsigned int timeout_id;
-+ GSList *monitors;
++ HashTable *smons; /* SubMonitor objects */
+} Poller;
+
+static int kq = -1;
+
++static unsigned int open_files = 0;
++static unsigned int max_open_files = 0;
++
+static GHashTable *dir_hash = NULL;
+static GHashTable *file_hash = NULL;
+
-+/*
-+ * The poller monitoring file creations. Low usage is expected,
-+ * therefore we set a small interval (one second).
-+ */
-+static gboolean gam_kqueue_missing_poll (gpointer user_data);
-+static Poller missing_poller = { gam_kqueue_missing_poll, 1000, -1, NULL };
-+
-+/*
-+ * The poller monitoring files not supported by kqueue (remote files,
-+ * etc). Very low usage is expected, but since this poller is likely
-+ * going to access the network, we set a medium interval (3 seconds).
-+ */
-+static gboolean gam_kqueue_unsupported_poll (gpointer user_data);
-+static Poller unsupported_poller = { gam_kqueue_unsupported_poll, 3000, -1, NULL };
-+
-+/*
-+ * The poller monitoring files inside monitored directories. Very high
-+ * usage is expected (a mail checker monitoring a couple of large MH
-+ * folders will lead to thousands of lstat() calls for each check),
-+ * therefore we set a large interval (6 seconds, same as FAM's
-+ * default).
-+ */
-+static gboolean gam_kqueue_dirs_poll (gpointer user_data);
-+static Poller dirs_poller = { gam_kqueue_dirs_poll, 6000, -1, NULL };
++static Poller missing_smon_poller;
++static Poller unsupported_smon_poller;
++static Poller unsupported_fmon_poller;
++
++static void gam_kqueue_hash_table_default_add_cb (GHashTable *table,
++ gpointer item,
++ gpointer user_data);
++static void gam_kqueue_hash_table_default_remove_cb (GHashTable *table,
++ gpointer item,
++ gpointer user_data);
++
++static void gam_kqueue_poller_post_add_cb (GHashTable *table, Poller *poller);
++static void gam_kqueue_poller_post_remove_cb (GHashTable *table, Poller *poller);
++
++static HashTableMethods poller_hash_table_methods =
++ {
++ gam_kqueue_hash_table_default_add_cb,
++ (HashTablePostAddFunc) gam_kqueue_poller_post_add_cb,
++ gam_kqueue_hash_table_default_remove_cb,
++ (HashTablePostRemoveFunc) gam_kqueue_poller_post_remove_cb
++ };
++
++static void gam_kqueue_sub_monitor_add_fmon_cb (GHashTable *table,
++ FileMonitor *fmon,
++ SubMonitor *smon);
++static void gam_kqueue_sub_monitor_remove_fmon_cb (GHashTable *table,
++ FileMonitor *fmon,
++ SubMonitor *smon);
++
++static HashTableMethods sub_monitor_fmons_hash_table_methods =
++ {
++ (HashTableAddFunc) gam_kqueue_sub_monitor_add_fmon_cb,
++ (HashTablePostAddFunc) NULL,
++ (HashTableRemoveFunc) gam_kqueue_sub_monitor_remove_fmon_cb,
++ (HashTablePostRemoveFunc) NULL
++ };
++
++static void gam_kqueue_sub_monitor_post_add_unsupported_fmon_cb (GHashTable *table,
++ SubMonitor *smon);
++static void gam_kqueue_sub_monitor_post_remove_unsupported_fmon_cb (GHashTable *table,
++ SubMonitor *smon);
++
++static HashTableMethods sub_monitor_unsupported_fmons_hash_table_methods =
++ {
++ gam_kqueue_hash_table_default_add_cb,
++ (HashTablePostAddFunc) gam_kqueue_sub_monitor_post_add_unsupported_fmon_cb,
++ gam_kqueue_hash_table_default_remove_cb,
++ (HashTablePostRemoveFunc) gam_kqueue_sub_monitor_post_remove_unsupported_fmon_cb
++ };
++
++static void gam_kqueue_sub_monitor_emit_event (SubMonitor *smon,
++ GaminEventType event,
++ SubMonitorFlags flags);
++static void gam_kqueue_sub_monitor_handle_kevent (Monitor *mon,
++ struct kevent *event);
++
++static FileMonitor *gam_kqueue_file_monitor_new (SubMonitor *smon,
++ const char *filename,
++ FileMonitorFlags *flags);
++static void gam_kqueue_file_monitor_free (FileMonitor *fmon);
++static void gam_kqueue_file_monitor_emit_event (FileMonitor *fmon,
++ GaminEventType event,
++ FileMonitorFlags flags);
++static void gam_kqueue_file_monitor_handle_kevent (Monitor *mon, struct kevent *event);
++
++/*** helpers *****************************************************************/
+
+static void
+gam_kqueue_mini_lstat (const char *pathname, MiniStat *mini_sb)
+{
+ struct stat sb;
-+
++
+ if (lstat(pathname, &sb) < 0)
+ memset(mini_sb, 0, sizeof(*mini_sb));
+ else
@@ -163,27 +298,6 @@
+ }
+}
+
-+static FileEntry *
-+gam_kqueue_file_entry_new (const char *path, const char *filename)
-+{
-+ FileEntry *entry;
-+
-+ entry = g_new(FileEntry, 1);
-+ entry->pathname = g_build_filename(path, filename, NULL);
-+ entry->filename = strrchr(entry->pathname, G_DIR_SEPARATOR);
-+ entry->filename = entry->filename ? entry->filename + 1 : entry->pathname;
-+ gam_kqueue_mini_lstat(entry->pathname, &entry->sb);
-+
-+ return entry;
-+}
-+
-+static void
-+gam_kqueue_file_entry_free (FileEntry *entry)
-+{
-+ g_free(entry->pathname);
-+ g_free(entry);
-+}
-+
+static gboolean
+gam_kqueue_differs (const MiniStat *sb1, const MiniStat *sb2)
+{
@@ -196,283 +310,691 @@
+ || sb1->ino != sb2->ino;
+}
+
-+static int
-+gam_kqueue_files_find (const FileEntry *entry, const char *filename)
++static gboolean
++gam_kqueue_isdir (const char *pathname, MonitorFlags flags)
+{
-+ return strcmp(entry->filename, filename);
++ if ((flags & MONITOR_ISDIR) != 0)
++ return TRUE;
++ else if ((flags & MONITOR_ISNOTDIR) != 0)
++ return FALSE;
++ else
++ {
++ struct stat sb;
++ return lstat(pathname, &sb) >= 0 && (sb.st_mode & S_IFDIR) != 0;
++ }
++}
++
++static gboolean
++gam_kqueue_get_uint_sysctl (const char *name, unsigned int *value)
++{
++ unsigned int value_len = sizeof(*value);
++
++ if (sysctlbyname(name, value, &value_len, NULL, 0) < 0)
++ {
++ gam_error(DEBUG_INFO, "unable to retrieve %s: %s\n", name, g_strerror(errno));
++ return FALSE;
++ }
++ else
++ return TRUE;
++}
++
++/*** HashTable ***************************************************************/
++
++static HashTable *
++gam_kqueue_hash_table_new (GHashTable *main,
++ HashTableMethods *methods,
++ gpointer user_data)
++{
++ HashTable *table;
++
++ table = g_new0(HashTable, 1);
++ table->main = main;
++ table->methods = methods;
++ table->user_data = user_data;
++
++ return table;
+}
+
+static void
-+gam_kqueue_poller_add_monitor (Poller *poller, Monitor *mon)
++gam_kqueue_hash_table_default_add_cb (GHashTable *table,
++ gpointer item,
++ gpointer user_data)
+{
-+ if (! g_slist_find(poller->monitors, mon))
++ g_hash_table_insert(table, item, GINT_TO_POINTER(TRUE));
++}
++
++static void
++gam_kqueue_hash_table_default_remove_cb (GHashTable *table,
++ gpointer item,
++ gpointer user_data)
++{
++ g_hash_table_remove(table, item);
++}
++
++static void
++gam_kqueue_hash_table_add (HashTable *table, gpointer item)
++{
++ if (table->iterating)
++ table->pending_additions = g_slist_prepend(table->pending_additions, item);
++ else
++ {
++ table->methods->add(table->main, item, table->user_data);
++ if (table->methods->post_add)
++ table->methods->post_add(table->main, table->user_data);
++ }
++}
++
++static void
++gam_kqueue_hash_table_remove (HashTable *table, gpointer item)
++{
++ if (table->iterating)
++ table->pending_removals = g_slist_prepend(table->pending_removals, item);
++ else
+ {
-+ poller->monitors = g_slist_prepend(poller->monitors, mon);
-+ if (poller->timeout_id == -1)
-+ poller->timeout_id = g_timeout_add(poller->interval, poller->func, NULL);
++ table->methods->remove(table->main, item, table->user_data);
++ if (table->methods->post_remove)
++ table->methods->post_remove(table->main, table->user_data);
+ }
+}
+
+static void
-+gam_kqueue_poller_remove_monitor (Poller *poller, Monitor *mon)
++gam_kqueue_hash_table_foreach (HashTable *table,
++ GHFunc func,
++ gpointer user_data)
+{
-+ poller->monitors = g_slist_remove(poller->monitors, mon);
-+ if (! poller->monitors && poller->timeout_id != -1)
++ g_assert(table->iterating == FALSE);
++
++ table->iterating = TRUE;
++ g_hash_table_foreach(table->main, func, user_data);
++ table->iterating = FALSE;
++
++ if (table->pending_additions)
++ {
++ GSList *l;
++
++ for (l = table->pending_additions; l != NULL; l = l->next)
++ table->methods->add(table->main, l->data, table->user_data);
++
++ g_slist_free(table->pending_additions);
++ table->pending_additions = NULL;
++
++ if (table->methods->post_add)
++ table->methods->post_add(table->main, table->user_data);
++ }
++
++ if (table->pending_removals)
++ {
++ GSList *l;
++
++ for (l = table->pending_removals; l != NULL; l = l->next)
++ table->methods->remove(table->main, l->data, table->user_data);
++
++ g_slist_free(table->pending_removals);
++ table->pending_removals = NULL;
++
++ if (table->methods->post_remove)
++ table->methods->post_remove(table->main, table->user_data);
++ }
++}
++
++static void
++gam_kqueue_hash_table_destroy (HashTable *table)
++{
++ g_assert(table->iterating == FALSE);
++
++ g_hash_table_destroy(table->main);
++ g_free(table);
++}
++
++/*** Poller ******************************************************************/
++
++static void
++gam_kqueue_poller_init (Poller *poller, PollerFunc func, unsigned int interval)
++{
++ poller->func = func;
++ poller->interval = interval;
++ poller->timeout_id = 0;
++ poller->smons = gam_kqueue_hash_table_new(g_hash_table_new(g_direct_hash, g_direct_equal),
++ &poller_hash_table_methods,
++ poller);
++}
++
++static void
++gam_kqueue_poller_foreach_cb (SubMonitor *smon,
++ gpointer unused,
++ Poller *poller)
++{
++ poller->func(smon);
++}
++
++static gboolean
++gam_kqueue_poller_timeout_cb (Poller *poller)
++{
++ gam_kqueue_hash_table_foreach(poller->smons, (GHFunc) gam_kqueue_poller_foreach_cb, poller);
++
++ return TRUE; /* keep source */
++}
++
++static void
++gam_kqueue_poller_post_add_cb (GHashTable *table, Poller *poller)
++{
++ if (! poller->timeout_id)
++ poller->timeout_id = g_timeout_add(poller->interval, (GSourceFunc) gam_kqueue_poller_timeout_cb, poller);
++}
++
++static void
++gam_kqueue_poller_post_remove_cb (GHashTable *table, Poller *poller)
++{
++ if (g_hash_table_size(table) == 0 && poller->timeout_id)
+ {
+ g_source_remove(poller->timeout_id);
-+ poller->timeout_id = -1;
++ poller->timeout_id = 0;
+ }
+}
+
+static void
-+gam_kqueue_monitor_clear_files (Monitor *mon)
++gam_kqueue_poller_add_sub_monitor (Poller *poller, SubMonitor *smon)
++{
++ gam_kqueue_hash_table_add(poller->smons, smon);
++}
++
++static void
++gam_kqueue_poller_remove_sub_monitor (Poller *poller, SubMonitor *smon)
++{
++ gam_kqueue_hash_table_remove(poller->smons, smon);
++}
++
++/*** Monitor *****************************************************************/
++
++static gboolean
++gam_kqueue_monitor_enable_kqueue (Monitor *mon)
+{
-+ gam_kqueue_poller_remove_monitor(&dirs_poller, mon);
-+ g_slist_foreach(mon->files, (GFunc) gam_kqueue_file_entry_free, NULL);
-+ g_slist_free(mon->files);
-+ mon->files = NULL;
++ struct kevent ev[1];
++
++ if (open_files == max_open_files)
++ {
++ GAM_DEBUG(DEBUG_INFO, "cannot open %s (max_open_files limit reached), falling back to poll\n", mon->pathname);
++ return FALSE;
++ }
++
++ mon->fd = open(mon->pathname, O_RDONLY | O_NOFOLLOW);
++ if (mon->fd < 0)
++ {
++ GAM_DEBUG(DEBUG_INFO, "cannot open %s (%s), falling back to poll\n", mon->pathname, g_strerror(errno));
++ return FALSE;
++ }
++
++ EV_SET(ev, mon->fd, EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_CLEAR, VN_NOTE_ALL, 0, mon);
++ if (kevent(kq, ev, G_N_ELEMENTS(ev), NULL, 0, NULL) < 0)
++ {
++ GAM_DEBUG(DEBUG_INFO, "cannot enable kqueue notification for %s (%s), falling back to poll\n", mon->pathname, g_strerror(errno));
++
++ close(mon->fd);
++ mon->fd = -1;
++
++ return FALSE;
++ }
++
++ open_files++;
++ return TRUE;
+}
+
+static void
-+gam_kqueue_monitor_set_missing (Monitor *mon)
++gam_kqueue_monitor_set_unsupported (Monitor *mon)
+{
+ if (mon->fd >= 0)
+ {
+ close(mon->fd);
+ mon->fd = -1;
++
++ open_files--;
+ }
+
-+ /*
-+ * This shouldn't normally happen, since a directory cannot be
-+ * deleted unless all its files are deleted first.
-+ */
-+ if (mon->files)
-+ gam_kqueue_monitor_clear_files(mon);
++ gam_kqueue_mini_lstat(mon->pathname, &mon->sb);
++}
+
-+ gam_kqueue_poller_remove_monitor(&unsupported_poller, mon);
-+ gam_kqueue_poller_add_monitor(&missing_poller, mon);
++static void
++gam_kqueue_monitor_free (Monitor *mon)
++{
++ g_free(mon->pathname);
++ if (mon->fd >= 0)
++ {
++ close(mon->fd);
++ open_files--;
++ }
++ g_free(mon);
+}
+
++/*** SubMonitor **************************************************************/
++
+static void
-+gam_kqueue_monitor_set_unsupported (Monitor *mon)
++gam_kqueue_sub_monitor_init_fmons (SubMonitor *smon)
++{
++ smon->fmons = gam_kqueue_hash_table_new(g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify) gam_kqueue_file_monitor_free),
++ &sub_monitor_fmons_hash_table_methods,
++ smon);
++ smon->unsupported_fmons = gam_kqueue_hash_table_new(g_hash_table_new(g_direct_hash, g_direct_equal),
++ &sub_monitor_unsupported_fmons_hash_table_methods,
++ smon);
++}
++
++static SubMonitor *
++gam_kqueue_sub_monitor_new (GamSubscription *sub)
++{
++ SubMonitor *smon;
++ Monitor *mon;
++
++ smon = g_new0(SubMonitor, 1);
++
++ mon = MONITOR(smon);
++ mon->handle_kevent = gam_kqueue_sub_monitor_handle_kevent;
++ mon->pathname = g_strdup(gam_subscription_get_path(sub));
++ mon->fd = -1;
++
++ smon->isdir = gam_subscription_is_dir(sub);
++ gam_kqueue_sub_monitor_init_fmons(smon);
++
++ return smon;
++}
++
++static void
++gam_kqueue_sub_monitor_clear_fmons (SubMonitor *smon)
++{
++ if (g_hash_table_size(smon->unsupported_fmons->main) > 0)
++ gam_kqueue_poller_remove_sub_monitor(&unsupported_fmon_poller, smon);
++
++ gam_kqueue_hash_table_destroy(smon->unsupported_fmons);
++ gam_kqueue_hash_table_destroy(smon->fmons);
++}
++
++static void
++gam_kqueue_sub_monitor_free (SubMonitor *smon)
++{
++ gam_kqueue_poller_remove_sub_monitor(&missing_smon_poller, smon);
++ gam_kqueue_poller_remove_sub_monitor(&unsupported_smon_poller, smon);
++ /* unsupported_dirs_poller is handled by _clear_fmons() below */
++
++ gam_kqueue_sub_monitor_clear_fmons(smon);
++ gam_kqueue_monitor_free(MONITOR(smon));
++}
++
++static void
++gam_kqueue_sub_monitor_add_fmon_cb (GHashTable *table,
++ FileMonitor *fmon,
++ SubMonitor *smon)
++{
++ g_hash_table_replace(table, fmon->filename, fmon);
++}
++
++static void
++gam_kqueue_sub_monitor_remove_fmon_cb (GHashTable *table,
++ FileMonitor *fmon,
++ SubMonitor *smon)
++{
++ g_hash_table_remove(table, fmon->filename);
++}
++
++static void
++gam_kqueue_sub_monitor_post_add_unsupported_fmon_cb (GHashTable *table,
++ SubMonitor *smon)
+{
++ gam_kqueue_poller_add_sub_monitor(&unsupported_fmon_poller, smon);
++}
++
++static void
++gam_kqueue_sub_monitor_post_remove_unsupported_fmon_cb (GHashTable *table,
++ SubMonitor *smon)
++{
++ if (g_hash_table_size(table) == 0)
++ gam_kqueue_poller_remove_sub_monitor(&unsupported_fmon_poller, smon);
++}
++
++static void
++gam_kqueue_sub_monitor_set_missing (SubMonitor *smon)
++{
++ Monitor *mon = MONITOR(smon);
++
+ if (mon->fd >= 0)
+ {
+ close(mon->fd);
+ mon->fd = -1;
++
++ open_files--;
++ }
++
++ /*
++ * A removed directory will normally not contain files, but we must
++ * not assume it (we might receive events out of order, etc). We
++ * therefore check if files are remaining, and if yes, clear them.
++ */
++ if (g_hash_table_size(smon->fmons->main) > 0)
++ {
++ gam_kqueue_sub_monitor_clear_fmons(smon);
++ gam_kqueue_sub_monitor_init_fmons(smon);
+ }
+
-+ gam_kqueue_mini_lstat(mon->path, &mon->sb);
-+ gam_kqueue_poller_add_monitor(&unsupported_poller, mon);
++ gam_kqueue_poller_remove_sub_monitor(&unsupported_smon_poller, smon);
++ gam_kqueue_poller_add_sub_monitor(&missing_smon_poller, smon);
+}
+
+static void
-+gam_kqueue_monitor_enable_notification (Monitor *mon, gboolean was_missing)
++gam_kqueue_sub_monitor_set_unsupported (SubMonitor *smon)
+{
-+ struct stat sb;
++ Monitor *mon = MONITOR(smon);
++
++ gam_kqueue_monitor_set_unsupported(mon);
++ gam_kqueue_poller_add_sub_monitor(&unsupported_smon_poller, smon);
++}
++
++static void
++gam_kqueue_sub_monitor_enable_notification (SubMonitor *smon,
++ SubMonitorFlags flags)
++{
++ Monitor *mon = MONITOR(smon);
+ gboolean exists;
-+ gboolean isdir;
-+ struct kevent ev[1];
+
+ /* we first send CREATED or EXISTS/DELETED+ENDEXISTS events */
+
-+ exists = lstat(mon->path, &sb) >= 0;
-+ isdir = exists && (sb.st_mode & S_IFDIR) != 0;
++ if ((flags & SUB_MONITOR_WAS_MISSING) != 0)
++ exists = TRUE;
++ else
++ {
++ struct stat sb;
++
++ exists = lstat(mon->pathname, &sb) >= 0;
++ flags |= (exists && (sb.st_mode & S_IFDIR) != 0) ? MONITOR_ISDIR : MONITOR_ISNOTDIR;
++ }
+
+ if (exists)
+ {
+ GaminEventType gevent;
+
-+ gevent = was_missing ? GAMIN_EVENT_CREATED : GAMIN_EVENT_EXISTS;
-+ gam_server_emit_event(mon->path, isdir, gevent, mon->subs, 1);
++ gevent = (flags & SUB_MONITOR_WAS_MISSING) != 0 ? GAMIN_EVENT_CREATED : GAMIN_EVENT_EXISTS;
++ gam_kqueue_sub_monitor_emit_event(smon, gevent, flags);
+
-+ if (mon->isdir && isdir)
++ if (smon->isdir && (flags & MONITOR_ISDIR) != 0)
+ {
+ GDir *dir;
+ GError *err = NULL;
-+
-+ if (mon->files)
-+ {
-+ /* be robust, handle the bug gracefully */
-+ GAM_DEBUG(DEBUG_INFO, "there is a bug in gam_kqueue: monitor had files\n");
-+ gam_kqueue_monitor_clear_files(mon);
-+ }
-+
-+ dir = g_dir_open(mon->path, 0, &err);
++
++ dir = g_dir_open(mon->pathname, 0, &err);
+ if (dir)
+ {
+ const char *filename;
-+
++
+ while ((filename = g_dir_read_name(dir)))
+ {
-+ FileEntry *entry;
-+
-+ entry = gam_kqueue_file_entry_new(mon->path, filename);
-+ mon->files = g_slist_prepend(mon->files, entry);
-+
-+ gam_server_emit_event(entry->pathname,
-+ (entry->sb.mode & S_IFDIR) != 0,
-+ gevent, mon->subs, 1);
++ FileMonitor *fmon;
++ FileMonitorFlags fmon_flags;
++
++ fmon = gam_kqueue_file_monitor_new(smon, filename, &fmon_flags);
++ gam_kqueue_file_monitor_emit_event(fmon, gevent, fmon_flags);
+ }
-+
++
+ g_dir_close(dir);
+ }
+ else
+ {
-+ GAM_DEBUG(DEBUG_INFO, "unable to open directory %s: %s\n", mon->path, err->message);
++ GAM_DEBUG(DEBUG_INFO, "unable to open directory %s: %s\n", mon->pathname, err->message);
+ g_error_free(err);
+ }
-+
-+ if (mon->files)
-+ gam_kqueue_poller_add_monitor(&dirs_poller, mon);
+ }
+
-+ if (! was_missing)
-+ gam_server_emit_event(mon->path, isdir, GAMIN_EVENT_ENDEXISTS, mon->subs, 1);
++ if ((flags & SUB_MONITOR_WAS_MISSING) == 0)
++ gam_kqueue_sub_monitor_emit_event(smon, GAMIN_EVENT_ENDEXISTS, flags);
+ }
+ else
+ {
-+ if (! was_missing)
-+ {
-+ gam_server_emit_event(mon->path, isdir, GAMIN_EVENT_DELETED, mon->subs, 1);
-+ gam_server_emit_event(mon->path, isdir, GAMIN_EVENT_ENDEXISTS, mon->subs, 1);
-+ }
++ gam_kqueue_sub_monitor_emit_event(smon, GAMIN_EVENT_DELETED, flags);
++ gam_kqueue_sub_monitor_emit_event(smon, GAMIN_EVENT_ENDEXISTS, flags);
+
-+ gam_kqueue_monitor_set_missing(mon);
+ return;
+ }
-+
++
+ /* then we enable kqueue notification, falling back to poll if necessary */
+
-+ mon->fd = open(mon->path, O_RDONLY | O_NOFOLLOW);
-+ if (mon->fd < 0)
-+ {
-+ GAM_DEBUG(DEBUG_INFO, "cannot open %s (%s), falling back to poll\n", mon->path, g_strerror(errno));
-+ gam_kqueue_monitor_set_unsupported(mon);
-+ return;
-+ }
++ if (! gam_kqueue_monitor_enable_kqueue(mon))
++ gam_kqueue_sub_monitor_set_unsupported(smon);
++}
+
-+ EV_SET(ev, mon->fd, EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_CLEAR, VN_NOTE_ALL, 0, mon);
-+ if (kevent(kq, ev, G_N_ELEMENTS(ev), NULL, 0, NULL) < 0)
-+ {
-+ GAM_DEBUG(DEBUG_INFO, "cannot enable kqueue notification for %s (%s), falling back to poll\n", mon->path, g_strerror(errno));
-+ gam_kqueue_monitor_set_unsupported(mon);
-+ }
++static void
++gam_kqueue_sub_monitor_handle_directory_change_removal_cb (const char *filename,
++ FileMonitor *fmon,
++ GHashTable *filenames)
++{
++ if (! g_hash_table_lookup(filenames, filename))
++ gam_kqueue_file_monitor_emit_event(fmon, GAMIN_EVENT_DELETED, MONITOR_ISNOTDIR);
+}
+
+static void
-+gam_kqueue_monitor_handle_directory_change (Monitor *mon, gboolean isdir)
++gam_kqueue_sub_monitor_handle_directory_change (SubMonitor *smon,
++ gboolean isdir)
+{
-+ GSList *filenames = NULL;
-+ GSList *l;
-+ GSList *tmp_files;
++ Monitor *mon = MONITOR(smon);
++ GHashTable *filenames;
++
++ filenames = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
+
+ if (isdir) /* do not follow symbolic links */
+ {
+ GDir *dir;
+ GError *err = NULL;
+
-+ dir = g_dir_open(mon->path, 0, &err);
++ dir = g_dir_open(mon->pathname, 0, &err);
+ if (dir)
+ {
+ const char *filename;
-+
++
+ while ((filename = g_dir_read_name(dir)))
-+ filenames = g_slist_prepend(filenames, g_strdup(filename));
++ {
++ g_hash_table_insert(filenames, g_strdup(filename), GINT_TO_POINTER(TRUE));
+
++ /* handle file creation */
++ if (! g_hash_table_lookup(smon->fmons->main, filename))
++ {
++ FileMonitor *fmon;
++ FileMonitorFlags fmon_flags;
++
++ fmon = gam_kqueue_file_monitor_new(smon, filename, &fmon_flags);
++ gam_kqueue_file_monitor_emit_event(fmon, GAMIN_EVENT_CREATED, fmon_flags);
++ }
++ }
++
+ g_dir_close(dir);
+ }
+ else
+ {
-+ GAM_DEBUG(DEBUG_INFO, "unable to open directory %s: %s\n", mon->path, err->message);
++ GAM_DEBUG(DEBUG_INFO, "unable to open directory %s: %s\n", mon->pathname, err->message);
+ g_error_free(err);
+ }
+ }
+
-+ /* handle created files */
-+ for (l = filenames; l; l = l->next)
-+ if (! g_slist_find_custom(mon->files, l->data, (GCompareFunc) gam_kqueue_files_find))
-+ {
-+ FileEntry *entry;
++ /*
++ * Handle deleted files (they are also handled at the FileMonitor
++ * level, but we must use whichever event comes first).
++ */
++ gam_kqueue_hash_table_foreach(smon->fmons, (GHFunc) gam_kqueue_sub_monitor_handle_directory_change_removal_cb, filenames);
++ g_hash_table_destroy(filenames);
++}
+
-+ entry = gam_kqueue_file_entry_new(mon->path, l->data);
-+ mon->files = g_slist_prepend(mon->files, entry);
++static void
++gam_kqueue_sub_monitor_emit_event (SubMonitor *smon,
++ GaminEventType event,
++ SubMonitorFlags flags)
++{
++ Monitor *mon = MONITOR(smon);
++ gboolean isdir;
+
-+ gam_server_emit_event(entry->pathname,
-+ (entry->sb.mode & S_IFDIR) != 0,
-+ GAMIN_EVENT_CREATED, mon->subs, 1);
-+ }
++ isdir = gam_kqueue_isdir(mon->pathname, flags);
+
-+ /* handle deleted files */
-+ tmp_files = g_slist_copy(mon->files);
-+ for (l = tmp_files; l; l = l->next)
++ switch (event)
+ {
-+ FileEntry *entry = l->data;
-+
-+ if (! g_slist_find_custom(filenames, entry->filename, (GCompareFunc) strcmp))
++ case GAMIN_EVENT_CHANGED:
++ if (smon->isdir)
+ {
-+ mon->files = g_slist_remove(mon->files, entry);
-+
-+ gam_server_emit_event(entry->pathname,
-+ (entry->sb.mode & S_IFDIR) != 0,
-+ GAMIN_EVENT_DELETED, mon->subs, 1);
-+
-+ gam_kqueue_file_entry_free(entry);
++ gam_kqueue_sub_monitor_handle_directory_change(smon, isdir);
++ return;
+ }
++ break;
++
++ case GAMIN_EVENT_DELETED:
++ case GAMIN_EVENT_MOVED:
++ gam_kqueue_sub_monitor_set_missing(smon);
++ break;
+ }
-+ g_slist_free(tmp_files);
+
-+ if (filenames)
++ gam_server_emit_event(mon->pathname, isdir, event, smon->subs, 1);
++}
++
++static void
++gam_kqueue_sub_monitor_handle_kevent (Monitor *mon, struct kevent *event)
++{
++ SubMonitor *smon = SUB_MONITOR(mon);
++
++ if ((event->flags & EV_ERROR) != 0)
+ {
-+ g_slist_foreach(filenames, (GFunc) g_free, NULL);
-+ g_slist_free(filenames);
++ /* kqueue failed, fallback to poll */
++ GAM_DEBUG(DEBUG_INFO, "kqueue failed for %s, falling back to poll\n", mon->pathname);
++ gam_kqueue_sub_monitor_set_unsupported(smon);
++ return;
+ }
+
-+ if (mon->files)
-+ gam_kqueue_poller_add_monitor(&dirs_poller, mon);
++ /*
++ * kevent aggregates events, so we must handle a fflags with
++ * multiple event bits set.
++ */
++ if ((event->fflags & VN_NOTE_CHANGED) != 0)
++ gam_kqueue_sub_monitor_emit_event(smon, GAMIN_EVENT_CHANGED, 0);
++
++ /* emitting the following events will add the smon to the missing poller */
++ if ((event->fflags & VN_NOTE_DELETED) != 0)
++ gam_kqueue_sub_monitor_emit_event(smon, GAMIN_EVENT_DELETED, MONITOR_ISNOTDIR);
++ else if ((event->fflags & NOTE_RENAME) != 0)
++ gam_kqueue_sub_monitor_emit_event(smon, GAMIN_EVENT_MOVED, MONITOR_ISNOTDIR);
++}
++
++/*** FileMonitor *************************************************************/
++
++static void
++gam_kqueue_file_monitor_set_unsupported (FileMonitor *fmon)
++{
++ Monitor *mon = MONITOR(fmon);
++
++ gam_kqueue_monitor_set_unsupported(mon);
++ gam_kqueue_hash_table_add(fmon->smon->unsupported_fmons, fmon);
++}
++
++static FileMonitor *
++gam_kqueue_file_monitor_new (SubMonitor *smon,
++ const char *filename,
++ FileMonitorFlags *flags)
++{
++ FileMonitor *fmon;
++ Monitor *mon;
++
++ fmon = g_new0(FileMonitor, 1);
++
++ mon = MONITOR(fmon);
++ mon->handle_kevent = gam_kqueue_file_monitor_handle_kevent;
++ mon->pathname = g_build_filename(MONITOR(smon)->pathname, filename, NULL);
++ mon->fd = -1;
++
++ fmon->smon = smon;
++ fmon->filename = strrchr(mon->pathname, G_DIR_SEPARATOR);
++ fmon->filename = fmon->filename ? fmon->filename + 1 : mon->pathname;
++
++ gam_kqueue_hash_table_add(fmon->smon->fmons, fmon);
++
++ if (gam_kqueue_monitor_enable_kqueue(mon))
++ *flags = 0;
+ else
-+ gam_kqueue_poller_remove_monitor(&dirs_poller, mon);
++ {
++ gam_kqueue_file_monitor_set_unsupported(fmon);
++ *flags = (mon->sb.mode & S_IFDIR) != 0 ? MONITOR_ISDIR : MONITOR_ISNOTDIR;
++ }
++
++ return fmon;
++}
++
++static void
++gam_kqueue_file_monitor_free (FileMonitor *fmon)
++{
++ gam_kqueue_monitor_free(MONITOR(fmon));
+}
+
+static void
-+gam_kqueue_monitor_emit_event (Monitor *mon,
-+ GaminEventType event,
-+ gboolean has_isdir,
-+ gboolean isdir)
++gam_kqueue_file_monitor_emit_event (FileMonitor *fmon,
++ GaminEventType event,
++ FileMonitorFlags flags)
+{
-+ if (! has_isdir)
++ Monitor *mon = MONITOR(fmon);
++ struct stat sb;
++ gboolean isdir;
++ gboolean stat_done;
++ gboolean stat_succeeded;
++
++ if ((flags & MONITOR_ISDIR) == 0 && (flags & MONITOR_ISNOTDIR) == 0)
+ {
-+ struct stat sb;
-+ isdir = lstat(mon->path, &sb) >= 0 && (sb.st_mode & S_IFDIR) != 0;
++ stat_done = TRUE;
++ stat_succeeded = lstat(mon->pathname, &sb) >= 0;
++ isdir = stat_succeeded && (sb.st_mode & S_IFDIR) != 0;
+ }
++ else
++ {
++ stat_done = FALSE;
++ isdir = (flags & MONITOR_ISDIR) != 0;
++ }
++
++ gam_server_emit_event(mon->pathname, isdir, event, fmon->smon->subs, 1);
+
+ switch (event)
+ {
-+ case GAMIN_EVENT_CHANGED:
-+ if (mon->isdir)
-+ {
-+ gam_kqueue_monitor_handle_directory_change(mon, isdir);
-+ return;
-+ }
-+ break;
-+
+ case GAMIN_EVENT_DELETED:
+ case GAMIN_EVENT_MOVED:
-+ gam_kqueue_monitor_set_missing(mon);
++ if (mon->fd < 0)
++ gam_kqueue_hash_table_remove(fmon->smon->unsupported_fmons, fmon);
++
++ if ((flags & FILE_MONITOR_POSSIBLY_RECREATED) != 0)
++ {
++ if (! stat_done)
++ stat_succeeded = lstat(mon->pathname, &sb) >= 0;
++
++ if (stat_succeeded)
++ {
++ FileMonitor *new_fmon;
++ FileMonitorFlags new_fmon_flags;
++
++ /*
++ * The file exists again. It means that kqueue has
++ * aggregated a removal+creation into a single event. We
++ * must therefore create a new fmon and emit a
++ * GAMIN_EVENT_CREATED event, because
++ * gam_kqueue_sub_monitor_handle_directory_change() did
++ * not detect the removal+creation.
++ */
++
++ new_fmon = gam_kqueue_file_monitor_new(fmon->smon, fmon->filename, &new_fmon_flags);
++ gam_kqueue_file_monitor_emit_event(new_fmon, GAMIN_EVENT_CREATED, new_fmon_flags);
++
++ break; /* do not remove the fmon we've just created */
++ }
++ }
++
++ gam_kqueue_hash_table_remove(fmon->smon->fmons, fmon);
+ break;
+ }
-+
-+ gam_server_emit_event(mon->path, isdir, event, mon->subs, 1);
+}
+
+static void
-+gam_kqueue_monitor_handle_kevent (Monitor *mon, struct kevent *event)
++gam_kqueue_file_monitor_handle_kevent (Monitor *mon, struct kevent *event)
+{
++ FileMonitor *fmon = FILE_MONITOR(mon);
++
+ if ((event->flags & EV_ERROR) != 0)
+ {
+ /* kqueue failed, fallback to poll */
-+ GAM_DEBUG(DEBUG_INFO, "kqueue failed for %s, falling back to poll\n", mon->path);
-+ gam_kqueue_monitor_set_unsupported(mon);
++ GAM_DEBUG(DEBUG_INFO, "kqueue failed for %s, falling back to poll\n", mon->pathname);
++ gam_kqueue_file_monitor_set_unsupported(fmon);
+ return;
+ }
+
@@ -480,16 +1002,23 @@
+ * kevent aggregates events, so we must handle a fflags with
+ * multiple event bits set.
+ */
++
+ if ((event->fflags & VN_NOTE_CHANGED) != 0)
-+ gam_kqueue_monitor_emit_event(mon, GAMIN_EVENT_CHANGED, FALSE, FALSE);
++ gam_kqueue_file_monitor_emit_event(fmon, GAMIN_EVENT_CHANGED, 0);
++
++ /* emitting the following events will free the fmon */
+ if ((event->fflags & VN_NOTE_DELETED) != 0)
-+ gam_kqueue_monitor_emit_event(mon, GAMIN_EVENT_DELETED, FALSE, FALSE);
-+ if ((event->fflags & NOTE_RENAME) != 0)
-+ gam_kqueue_monitor_emit_event(mon, GAMIN_EVENT_MOVED, FALSE, FALSE);
++ gam_kqueue_file_monitor_emit_event(fmon, GAMIN_EVENT_DELETED, MONITOR_ISNOTDIR | FILE_MONITOR_POSSIBLY_RECREATED);
++ else if ((event->fflags & NOTE_RENAME) != 0)
++ gam_kqueue_file_monitor_emit_event(fmon, GAMIN_EVENT_MOVED, MONITOR_ISNOTDIR | FILE_MONITOR_POSSIBLY_RECREATED);
+}
+
++/*** kevent/poll callbacks ***************************************************/
++
+static gboolean
-+gam_kqueue_kevent_cb (GIOChannel *source, GIOCondition condition, gpointer user_data)
++gam_kqueue_kevent_cb (GIOChannel *source,
++ GIOCondition condition,
++ gpointer user_data)
+{
+ int nevents;
+ struct kevent ev[1];
@@ -504,100 +1033,87 @@
+ }
+
+ for (i = 0; i < nevents; i++)
-+ gam_kqueue_monitor_handle_kevent(ev[i].udata, &ev[i]);
-+
++ MONITOR(ev[i].udata)->handle_kevent(ev[i].udata, &ev[i]);
++
+ return TRUE; /* keep source */
+}
+
-+static gboolean
-+gam_kqueue_missing_poll (gpointer user_data)
++static void
++gam_kqueue_missing_smon_poll (SubMonitor *smon)
+{
-+ GSList *tmp_list;
-+ GSList *l;
-+
-+ /* the list may be modified while we're iterating, so we use a copy */
++ struct stat sb;
+
-+ tmp_list = g_slist_copy(missing_poller.monitors);
-+ for (l = tmp_list; l; l = l->next)
++ if (lstat(MONITOR(smon)->pathname, &sb) >= 0)
+ {
-+ Monitor *mon = l->data;
-+ struct stat sb;
-+
-+ if (lstat(mon->path, &sb) >= 0)
-+ {
-+ gam_kqueue_poller_remove_monitor(&missing_poller, mon);
-+ gam_kqueue_monitor_enable_notification(mon, TRUE);
-+ }
++ gam_kqueue_poller_remove_sub_monitor(&missing_smon_poller, smon);
++ gam_kqueue_sub_monitor_enable_notification(smon, SUB_MONITOR_WAS_MISSING | ((sb.st_mode & S_IFDIR) != 0 ? MONITOR_ISDIR : MONITOR_ISNOTDIR));
+ }
-+ g_slist_free(tmp_list);
-+
-+ return TRUE;
+}
+
-+static gboolean
-+gam_kqueue_unsupported_poll (gpointer user_data)
++static void
++gam_kqueue_unsupported_smon_poll (SubMonitor *smon)
+{
-+ GSList *tmp_list;
-+ GSList *l;
++ Monitor *mon = MONITOR(smon);
++ MiniStat sb;
++ GaminEventType event;
+
-+ /* the list may be modified while we're iterating, so we use a copy */
-+
-+ tmp_list = g_slist_copy(unsupported_poller.monitors);
-+ for (l = tmp_list; l; l = l->next)
++ if (++mon->poll_count == CFG_UNSUPPORTED_SMON_KQUEUE_RETRY_FREQUENCY)
+ {
-+ Monitor *mon = l->data;
-+ MiniStat sb;
-+ GaminEventType event;
-+
-+ gam_kqueue_mini_lstat(mon->path, &sb);
-+
-+ if (! sb.exists && mon->sb.exists)
-+ event = GAMIN_EVENT_DELETED;
-+ else if (gam_kqueue_differs(&sb, &mon->sb))
-+ event = GAMIN_EVENT_CHANGED;
-+ else
-+ continue;
-+
-+ memcpy(&mon->sb, &sb, sizeof(sb));
-+ gam_kqueue_monitor_emit_event(mon, event, TRUE, (sb.mode & S_IFDIR) != 0);
++ mon->poll_count = 0;
++ if (gam_kqueue_monitor_enable_kqueue(mon))
++ gam_kqueue_poller_remove_sub_monitor(&missing_smon_poller, smon);
+ }
-+ g_slist_free(tmp_list);
-+
-+ return TRUE;
++
++ gam_kqueue_mini_lstat(mon->pathname, &sb);
++
++ if (! sb.exists && mon->sb.exists)
++ event = GAMIN_EVENT_DELETED;
++ else if (gam_kqueue_differs(&sb, &mon->sb))
++ event = GAMIN_EVENT_CHANGED;
++ else
++ return;
++
++ memcpy(&mon->sb, &sb, sizeof(sb));
++ gam_kqueue_sub_monitor_emit_event(smon, event, (sb.mode & S_IFDIR) != 0 ? MONITOR_ISDIR : MONITOR_ISNOTDIR);
+}
+
-+static gboolean
-+gam_kqueue_dirs_poll (gpointer user_data)
++static void
++gam_kqueue_unsupported_fmon_poll_foreach_cb (FileMonitor *fmon,
++ gpointer unused,
++ gpointer user_data)
+{
-+ GSList *l;
-+
-+ /* the list cannot be modified while we're iterating */
++ Monitor *mon = MONITOR(fmon);
++ MiniStat sb;
++ GaminEventType event;
+
-+ for (l = dirs_poller.monitors; l; l = l->next)
++ if (++mon->poll_count == CFG_UNSUPPORTED_FMON_KQUEUE_RETRY_FREQUENCY)
+ {
-+ Monitor *mon = l->data;
-+ GSList *f;
++ mon->poll_count = 0;
++ if (gam_kqueue_monitor_enable_kqueue(mon))
++ gam_kqueue_hash_table_remove(fmon->smon->unsupported_fmons, fmon);
++ }
+
-+ for (f = mon->files; f; f = f->next)
-+ {
-+ FileEntry *entry = f->data;
-+ MiniStat sb;
++ gam_kqueue_mini_lstat(mon->pathname, &sb);
+
-+ gam_kqueue_mini_lstat(entry->pathname, &sb);
-+ if (gam_kqueue_differs(&sb, &entry->sb))
-+ {
-+ memcpy(&entry->sb, &sb, sizeof(sb));
-+ gam_server_emit_event(entry->pathname,
-+ (sb.mode & S_IFDIR) != 0,
-+ GAMIN_EVENT_CHANGED,
-+ mon->subs, 1);
-+ }
-+ }
-+ }
++ if (! sb.exists && mon->sb.exists)
++ event = GAMIN_EVENT_DELETED;
++ else if (gam_kqueue_differs(&sb, &mon->sb))
++ event = GAMIN_EVENT_CHANGED;
++ else
++ return;
+
-+ return TRUE;
++ memcpy(&mon->sb, &sb, sizeof(sb));
++ gam_kqueue_file_monitor_emit_event(fmon, event, (sb.mode & S_IFDIR) != 0 ? MONITOR_ISDIR : MONITOR_ISNOTDIR);
++}
++
++static void
++gam_kqueue_unsupported_fmon_poll (SubMonitor *smon)
++{
++ gam_kqueue_hash_table_foreach(smon->unsupported_fmons, (GHFunc) gam_kqueue_unsupported_fmon_poll_foreach_cb, NULL);
+}
+
++/*** Gamin backend implementation ********************************************/
++
+/**
+ * Initializes the kqueue system. This must be called before
+ * any other functions in this module.
@@ -608,6 +1124,8 @@
+gam_kqueue_init (void)
+{
+ GIOChannel *channel;
++ unsigned int maxfiles;
++ unsigned int maxfilesperproc;
+
+ kq = kqueue();
+ if (kq < 0)
@@ -616,9 +1134,37 @@
+ return FALSE;
+ }
+
++ if (! gam_kqueue_get_uint_sysctl("kern.maxfiles", &maxfiles))
++ return FALSE;
++ if (! gam_kqueue_get_uint_sysctl("kern.maxfilesperproc", &maxfilesperproc))
++ return FALSE;
++
++ /*
++ * We make sure to:
++ * - never paralyze the system (CFG_GLOBAL_FILE_RESERVE_RATIO)
++ * - never paralyze our own process (CFG_SELF_FILE_RESERVE)
++ */
++
++ maxfiles *= CFG_GLOBAL_FILE_RESERVE_RATIO;
++ maxfilesperproc = maxfilesperproc > CFG_SELF_FILE_RESERVE
++ ? maxfilesperproc - CFG_SELF_FILE_RESERVE
++ : 0;
++
++ max_open_files = MIN(maxfiles, maxfilesperproc);
++
+ dir_hash = g_hash_table_new(g_str_hash, g_str_equal);
+ file_hash = g_hash_table_new(g_str_hash, g_str_equal);
+
++ gam_kqueue_poller_init(&missing_smon_poller,
++ gam_kqueue_missing_smon_poll,
++ CFG_MISSING_SMON_POLL_INTERVAL);
++ gam_kqueue_poller_init(&unsupported_smon_poller,
++ gam_kqueue_unsupported_smon_poll,
++ CFG_UNSUPPORTED_SMON_POLL_INTERVAL);
++ gam_kqueue_poller_init(&unsupported_fmon_poller,
++ gam_kqueue_unsupported_fmon_poll,
++ CFG_UNSUPPORTED_FMON_POLL_INTERVAL);
++
+ channel = g_io_channel_unix_new(kq);
+ g_io_add_watch(channel, G_IO_IN, gam_kqueue_kevent_cb, NULL);
+
@@ -640,28 +1186,25 @@
+{
+ const char *path;
+ GHashTable *hash;
-+ Monitor *mon;
++ SubMonitor *smon;
+
+ gam_listener_add_subscription(gam_subscription_get_listener(sub), sub);
+
+ path = gam_subscription_get_path(sub);
+ hash = gam_subscription_is_dir(sub) ? dir_hash : file_hash;
-+ mon = g_hash_table_lookup(hash, path);
++ smon = g_hash_table_lookup(hash, path);
+
-+ if (mon)
++ if (smon)
+ {
-+ mon->subs = g_list_append(mon->subs, sub);
++ smon->subs = g_list_append(smon->subs, sub);
+ return TRUE;
+ }
++
++ smon = gam_kqueue_sub_monitor_new(sub);
++ smon->subs = g_list_append(smon->subs, sub);
+
-+ mon = g_new0(Monitor, 1);
-+ mon->path = path;
-+ mon->subs = g_list_append(mon->subs, sub);
-+ mon->fd = -1;
-+ mon->isdir = hash == dir_hash;
-+
-+ g_hash_table_insert(hash, (gpointer) mon->path, mon);
-+ gam_kqueue_monitor_enable_notification(mon, FALSE);
++ g_hash_table_insert(hash, MONITOR(smon)->pathname, smon);
++ gam_kqueue_sub_monitor_enable_notification(smon, 0);
+
+ return TRUE;
+}
@@ -676,30 +1219,19 @@
+gam_kqueue_remove_subscription (GamSubscription *sub)
+{
+ GHashTable *hash;
-+ Monitor *mon;
++ SubMonitor *smon;
+
+ hash = gam_subscription_is_dir(sub) ? dir_hash : file_hash;
-+ mon = g_hash_table_lookup(hash, gam_subscription_get_path(sub));
++ smon = g_hash_table_lookup(hash, gam_subscription_get_path(sub));
+
-+ if (! mon)
++ if (! smon)
+ return FALSE;
+
-+ mon->subs = g_list_remove_all(mon->subs, sub);
-+ if (! mon->subs)
++ smon->subs = g_list_remove_all(smon->subs, sub);
++ if (! smon->subs)
+ {
-+ g_hash_table_remove(hash, mon->path);
-+
-+ /* might have been in any of these two */
-+ gam_kqueue_poller_remove_monitor(&missing_poller, mon);
-+ gam_kqueue_poller_remove_monitor(&unsupported_poller, mon);
-+
-+ if (mon->fd >= 0)
-+ close(mon->fd);
-+
-+ if (mon->files)
-+ gam_kqueue_monitor_clear_files(mon);
-+
-+ g_free(mon);
++ g_hash_table_remove(hash, MONITOR(smon)->pathname);
++ gam_kqueue_sub_monitor_free(smon);
+ }
+
+ gam_subscription_cancel(sub);
@@ -723,11 +1255,11 @@
+
+ subs = gam_listener_get_subscriptions(listener);
+
-+ for (l = subs; l; l = l->next)
++ for (l = subs; l != NULL; l = l->next)
+ if (! gam_kqueue_remove_subscription(l->data))
+ success = FALSE;
+
+ g_list_free(subs);
-+
++
+ return success;
+}