Bug 79680 - Update port: devel/gamin (rewritten kqueue backend)
Summary: Update port: devel/gamin (rewritten kqueue backend)
Status: Closed FIXED
Alias: None
Product: Ports & Packages
Classification: Unclassified
Component: Individual Port(s) (show other bugs)
Version: Latest
Hardware: Any Any
: Normal Affects Only Me
Assignee: freebsd-gnome (Nobody)
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2005-04-08 15:00 UTC by Jean-Yves Lefort
Modified: 2005-04-10 18:36 UTC (History)
1 user (show)

See Also:


Attachments
file.diff (37.56 KB, patch)
2005-04-08 15:00 UTC, Jean-Yves Lefort
no flags Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Jean-Yves Lefort 2005-04-08 15:00:25 UTC
I have rewritten the kqueue backend from scratch.

Hilights:
  * Now fallbacks to polling for files non supported by kqueue. I have
    therefore removed the KQUEUE option, it's now always enabled.
  * A number of bugs have been fixed, the backend now behaves as it
    should and passes the tests (cd ${WRKSRC}/tests && gmake tests):
      * do not send GAMIN_EVENT_ENDEXISTS on NOTE_REVOKE (ENDEXISTS is
        the terminator of an EXISTS/DELETED list, not a "file has
        stopped existing" event)
      * handles kevent aggregation (multiple bits set in fflags)
      * mimic FAM's behaviour of never following symbolic link (so
        g_file_test() is not used anymore, since it can lead to an
        access() or stat() call)
  * Exhaustive error handling (previously, an open() or kevent()
    failure caused the file to be silently ignored; EV_ERROR was not
    handled)
  * Clean and simple implementation, without all the useless
    thread-safe design (it was a remnant from old versions of gamin,
    which were multi-threaded)

I have also modified pkg-descr, to emphasize the fact that Gamin uses
kqueue whereas FAM does not. Perhaps the user should also be hinted
about Gamin in devel/gnomevfs2, either through a pkg-message or a
FAM=[fam|gamin] option.
Comment 1 Pav Lucistnik freebsd_committer freebsd_triage 2005-04-08 19:10:54 UTC
Responsible Changed
From-To: freebsd-ports-bugs->gnome

Over to maintainers 

http://www.freebsd.org/cgi/query-pr.cgi?pr=79680 

Adding to audit trail from misfiled PR 79682:

Date: Fri, 8 Apr 2005 16:14:36 +0200
Comment 2 Jean-Yves Lefort 2005-04-09 16:18:04 UTC
More gam_kqueue.c symlink fixes (set O_NOFOLLOW on open(); make sure
g_dir_open() is not called on a symlink), and added notes on top of
the file.

diff -ruN /usr/ports/devel/gamin/Makefile gamin/Makefile
--- /usr/ports/devel/gamin/Makefile	Thu Apr  7 06:02:38 2005
+++ gamin/Makefile	Sat Apr  9 15:04:36 2005
@@ -7,7 +7,7 @@
 
 PORTNAME=	gamin
 PORTVERSION=	0.0.26
-PORTREVISION?=	9
+PORTREVISION?=	10
 CATEGORIES?=	devel
 MASTER_SITES=	http://www.gnome.org/~veillard/gamin/sources/
 
@@ -25,21 +25,7 @@
 
 CONFLICTS=	fam-[0-9]*
 
-.if !defined(GAMIN_SLAVE)
-OPTIONS=	KQUEUE "Enable the KQueue backend (UFS only)" on
-.endif
-
-.include <bsd.port.pre.mk>
-
-.if !defined(GAMIN_SLAVE)
-.if defined(WITHOUT_KQUEUE)
-CONFIGURE_ARGS+=	--disable-kqueue
-.else
-CONFIGURE_ARGS+=	--enable-kqueue
-.endif
-.endif
-
 post-patch:
 	@${FIND} ${WRKSRC} -type f | ${XARGS} ${TOUCH} -f
 
-.include <bsd.port.post.mk>
+.include <bsd.port.mk>
diff -ruN /usr/ports/devel/gamin/files/patch-server_gam_kqueue.c gamin/files/patch-server_gam_kqueue.c
--- /usr/ports/devel/gamin/files/patch-server_gam_kqueue.c	Thu Apr  7 06:02:38 2005
+++ gamin/files/patch-server_gam_kqueue.c	Sat Apr  9 16:57:51 2005
@@ -1,7 +1,43 @@
---- server/gam_kqueue.c.orig	Wed Apr  6 22:46:40 2005
-+++ server/gam_kqueue.c	Wed Apr  6 22:47:16 2005
-@@ -0,0 +1,720 @@
+--- server/gam_kqueue.c.orig	Sat Apr  9 16:09:04 2005
++++ server/gam_kqueue.c	Sat Apr  9 16:54:45 2005
+@@ -0,0 +1,730 @@
 +/*
++ * gam_kqueue.c - a kqueue(2) Gamin backend
++ *
++ * Notes:
++ *
++ *     * http://techpubs.sgi.com/library/tpl/cgi-bin/getdoc.cgi?coll=0650&db=bks&fname=/SGI_Developer/books/IIDsktp_IG/sgi_html/ch08.html
++ *       states that FAM does not follow monitored symbolic links: we
++ *       do the same (note that regarding
++ *       http://oss.sgi.com/bugzilla/show_bug.cgi?id=405, we do what
++ *       FAM should do: we do not call g_dir_open() if the file is a
++ *       symbolic link).
++ *
++ *     * kqueue cannot monitor files residing on anything but a UFS
++ *       file system. If kqueue cannot monitor a file, this backend
++ *       will poll it periodically.
++ *
++ *     * Monitoring a file with kqueue prevents the file system it
++ *       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
++ *       monitor the parent directory is technically feasible, it
++ *       would introduce a lot of complexity in the code.
++ *
++ *     * In light of the previous points, it appears that:
++ *
++ *           - kqueue needs to be augmented with a filename-based
++ *             monitoring facility;
++ *
++ *           - 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>
 + *
@@ -20,621 +56,577 @@
 + * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 + */
 +
-+
-+#include <config.h>
++#include "config.h"
++#include <string.h>
++#include <fcntl.h>
++#include <unistd.h>
 +#include <sys/types.h>
 +#include <sys/stat.h>
 +#include <sys/event.h>
 +#include <sys/time.h>
-+#include <fcntl.h>
-+#include <sys/ioctl.h>
-+#include <signal.h>
-+#include <unistd.h>
-+#include <stdio.h>
-+#include <string.h>
-+#include <glib.h>
++#include <errno.h>
 +#include "gam_error.h"
 +#include "gam_kqueue.h"
-+#include "gam_tree.h"
 +#include "gam_event.h"
 +#include "gam_server.h"
-+#include "gam_event.h"
 +
-+typedef struct {
-+    char *path;
-+    int fd;
-+    int refcount;
-+    gboolean isdir;
-+    GList *subs;
-+    GSList *dirlist;
-+} KQueueData;
++#define VN_NOTE_ALL	(NOTE_DELETE | NOTE_WRITE | NOTE_EXTEND | \
++			 NOTE_ATTRIB | NOTE_LINK | NOTE_RENAME | \
++			 NOTE_REVOKE)
++#define VN_NOTE_CHANGED	(NOTE_WRITE | NOTE_EXTEND | NOTE_ATTRIB | NOTE_LINK)
++#define VN_NOTE_DELETED (NOTE_DELETE | NOTE_REVOKE)
 +
 +typedef struct
 +{
-+    ino_t ino;
-+    mode_t mode;
-+    uid_t uid;
-+    gid_t gid;
-+    time_t mtime;
-+    time_t ctime;
-+    off_t size;
++  gboolean	exists;
++  ino_t		ino;
++  mode_t	mode;
++  uid_t		uid;
++  gid_t		gid;
++  time_t	mtime;
++  time_t	ctime;
++  off_t		size;
 +} MiniStat;
 +
-+typedef struct {
-+    char *pathname;
-+    char *filename;		/* pointer into pathname */
-+    MiniStat sb;
-+} FileData;
-+  
-+static GHashTable *dir_path_hash = NULL;
-+static GHashTable *file_path_hash = NULL;
-+static GHashTable *fd_hash = NULL;
-+
-+static GSList *exist_list = NULL;
++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 */
++  int		fd;		/* for kqueue */
++  gboolean	isdir;		/* is a directory monitor? */
++} Monitor;
 +
-+static GList *new_subs = NULL;
-+G_LOCK_DEFINE_STATIC(new_subs);
-+static GList *removed_subs = NULL;
-+G_LOCK_DEFINE_STATIC(removed_subs);
++typedef struct
++{
++  char		*pathname;
++  char		*filename;	/* pointer into pathname */
++  MiniStat	sb;
++} FileEntry;
 +
-+G_LOCK_DEFINE_STATIC(kqueue);
++typedef struct
++{
++  GSourceFunc	func;
++  unsigned int	interval;
++  unsigned int	timeout_id;
++  GSList	*monitors;
++} Poller;
 +
-+static gboolean have_consume_idler = FALSE;
++static int kq = -1;
 +
-+int kq = -1;
++static GHashTable *dir_hash = NULL;
++static GHashTable *file_hash = NULL;
 +
-+static KQueueData *
-+gam_kqueue_data_new(const char *path, int fd)
-+{
-+    KQueueData *data;
++/*
++ * 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 };
 +
-+    data = g_new0(KQueueData, 1);
-+    data->path = g_strdup(path);
-+    data->fd = fd;
-+    data->refcount = 1;
-+    data->isdir = FALSE;
-+    data->subs = NULL;
-+    data->dirlist = 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 };
 +
-+    return data;
-+}
++/*
++ * 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 void
-+gam_kqueue_mini_stat (const char *pathname, MiniStat *mini_sb)
++gam_kqueue_mini_lstat (const char *pathname, MiniStat *mini_sb)
 +{
-+    struct stat sb;
-+
-+    if (lstat(pathname, &sb) == 0) {
-+        mini_sb->ino = sb.st_ino;
-+	mini_sb->mode = sb.st_mode;
-+	mini_sb->uid = sb.st_uid;
-+	mini_sb->gid = sb.st_gid;
-+        mini_sb->mtime = sb.st_mtime;
-+	mini_sb->ctime = sb.st_ctime;
-+	mini_sb->size = sb.st_size;
-+    } else {
-+        memset(mini_sb, 0, sizeof(*mini_sb));
++  struct stat sb;
++  
++  if (lstat(pathname, &sb) < 0)
++    memset(mini_sb, 0, sizeof(*mini_sb));
++  else
++    {
++      mini_sb->exists = TRUE;
++      mini_sb->ino = sb.st_ino;
++      mini_sb->mode = sb.st_mode;
++      mini_sb->uid = sb.st_uid;
++      mini_sb->gid = sb.st_gid;
++      mini_sb->mtime = sb.st_mtime;
++      mini_sb->ctime = sb.st_ctime;
++      mini_sb->size = sb.st_size;
 +    }
 +}
 +
-+static FileData *
-+gam_kqueue_file_data_new (const char *path, const char *filename)
++static FileEntry *
++gam_kqueue_file_entry_new (const char *path, const char *filename)
 +{
-+    FileData *fdata;
-+
-+    fdata = g_new(FileData, 1);
-+    fdata->pathname = g_build_filename(path, filename, NULL);
-+    fdata->filename = strrchr(fdata->pathname, G_DIR_SEPARATOR);
-+    fdata->filename = fdata->filename ? fdata->filename + 1 : fdata->pathname;
-+    gam_kqueue_mini_stat(fdata->pathname, &fdata->sb);
++  FileEntry *entry;
 +
-+    return fdata;
++  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_data_free (FileData *fdata)
++gam_kqueue_file_entry_free (FileEntry *entry)
 +{
-+    g_free(fdata->pathname);
-+    g_free(fdata);
++  g_free(entry->pathname);
++  g_free(entry);
 +}
 +
-+static void
-+gam_kqueue_data_free(KQueueData * data)
++static gboolean
++gam_kqueue_differs (const MiniStat *sb1, const MiniStat *sb2)
 +{
-+    g_free(data->path);
-+    if (data->dirlist) {
-+        g_slist_foreach(data->dirlist, (GFunc)gam_kqueue_file_data_free, NULL);
-+        g_slist_free(data->dirlist);
-+    }
-+    if (data->subs) {
-+	g_list_free(data->subs);
-+    }
-+    g_free(data);
++  return sb1->mtime != sb2->mtime
++    || sb1->ctime != sb2->ctime
++    || sb1->size != sb2->size
++    || sb1->mode != sb2->mode
++    || sb1->uid != sb2->uid
++    || sb1->gid != sb2->gid
++    || sb1->ino != sb2->ino;
 +}
 +
 +static int
-+gam_kqueue_dirlist_find (FileData *fdata, const char *filename)
++gam_kqueue_files_find (const FileEntry *entry, const char *filename)
 +{
-+    return strcmp(fdata->filename, filename);
++    return strcmp(entry->filename, filename);
 +}
 +
 +static void
-+gam_kqueue_add_rm_handler(const char *path, GamSubscription *sub, gboolean added, gboolean was_missing)
++gam_kqueue_poller_add_monitor (Poller *poller, Monitor *mon)
 +{
-+    KQueueData *data;
-+    struct kevent ev[1];
-+    int isdir = 0;
-+    int fd;
-+
-+    G_LOCK(kqueue);
-+
-+    isdir = g_file_test(path, G_FILE_TEST_IS_DIR);
-+    if (gam_subscription_is_dir(sub)) {
-+        data = g_hash_table_lookup(dir_path_hash, path);
-+    }
-+    else {
-+        data = g_hash_table_lookup(file_path_hash, path);
++  if (! g_slist_find(poller->monitors, mon))
++    {
++      poller->monitors = g_slist_prepend(poller->monitors, mon);
++      if (poller->timeout_id == -1)
++	poller->timeout_id = g_timeout_add(poller->interval, poller->func, NULL);
 +    }
++}
 +
-+    if (added) {
-+	GList *subs;
-+
-+	subs = NULL;
-+	subs = g_list_append(subs, sub);
-+
-+        if (data != NULL) {
-+            data->refcount++;
-+	    data->subs = g_list_prepend(data->subs, sub);
-+            G_UNLOCK(kqueue);
-+	    GAM_DEBUG(DEBUG_INFO, "kqueue updated refcount\n");
-+	    if (!was_missing) {
-+	        gam_server_emit_event (path, isdir, GAMIN_EVENT_EXISTS, subs, 1);
-+                gam_server_emit_event (path, isdir, GAMIN_EVENT_ENDEXISTS, subs, 1);
-+	    }
-+	    else {
-+                gam_server_emit_event (path, isdir, GAMIN_EVENT_CREATED, subs, 1);
-+	    }
-+            return;
-+        }
-+
-+	if (!g_file_test(path, G_FILE_TEST_EXISTS)) {
-+	    data = gam_kqueue_data_new(path, -1);
-+	    data->subs = g_list_prepend(data->subs, sub);
-+            exist_list = g_slist_append(exist_list, data);
-+	    gam_server_emit_event (path, isdir, GAMIN_EVENT_DELETED, subs, 1);
-+	    gam_server_emit_event (path, isdir, GAMIN_EVENT_ENDEXISTS, subs, 1);
-+	    G_UNLOCK(kqueue);
-+	    return;
-+	}
-+
-+	fd = open(path, O_RDONLY);
-+
-+	if (fd < 0) {
-+            G_UNLOCK(kqueue);
-+	    return;
-+	}
-+
-+        EV_SET(ev, fd, EVFILT_VNODE,
-+            EV_ADD | EV_ENABLE | EV_CLEAR, VN_NOTE_ALL, 0, 0);
-+        kevent(kq, ev, 1, NULL, 0, NULL);
-+
-+        data = gam_kqueue_data_new(path, fd);
-+        data->subs = g_list_prepend(data->subs, sub);
-+
-+	if (!was_missing) {
-+            gam_server_emit_event (path, isdir, GAMIN_EVENT_EXISTS, subs, 1);
-+	}
-+	else {
-+            gam_server_emit_event (path, isdir, GAMIN_EVENT_CREATED, subs, 1);
-+	}
-+        if (gam_subscription_is_dir(sub) && isdir) {
-+	    GDir *dir;
-+
-+            data->isdir = TRUE;
-+	    data->dirlist = NULL;
-+
-+	    dir = g_dir_open(path, 0, NULL);
-+	    if (dir) {
-+	        const char *entry;
-+
-+		while ((entry = g_dir_read_name(dir))) {
-+		    FileData *fdata;
-+
-+		    fdata = gam_kqueue_file_data_new(path, entry);
-+		    data->dirlist = g_slist_prepend(data->dirlist, fdata);
-+
-+		    if (!was_missing) {
-+		        gam_server_emit_event(fdata->pathname,
-+					      g_file_test(fdata->pathname, G_FILE_TEST_IS_DIR),
-+					      GAMIN_EVENT_EXISTS, subs, 1);
-+		    }
-+		}
-+
-+		g_dir_close(dir);
-+	    }
-+	}
-+
-+	if (!was_missing) {
-+	    gam_server_emit_event (path, isdir, GAMIN_EVENT_ENDEXISTS, subs, 1);
-+	}
-+
-+        g_hash_table_insert(fd_hash, GINT_TO_POINTER(data->fd), data);
-+	if (data->isdir) {
-+            g_hash_table_insert(dir_path_hash, data->path, data);
-+	}
-+	else {
-+            g_hash_table_insert(file_path_hash, data->path, data);
-+	}
-+
-+	if (subs)
-+            g_list_free(subs);
-+
-+        GAM_DEBUG(DEBUG_INFO, "added kqueue watch for %s\n", path);
-+    } else {
-+
-+        if (!data) {
-+            G_UNLOCK(kqueue);
-+            return;
-+        }
-+
-+	if (g_list_find (data->subs, sub)) {
-+		data->subs = g_list_remove_all (data->subs, sub);
-+	}
-+        data->refcount--;
-+	    GAM_DEBUG(DEBUG_INFO, "kqueue decremeneted refcount for %s\n", path);
-+
-+        if (data->refcount == 0) {
-+            GList *l;
-+
-+	    close(data->fd);
-+            l = data->subs;
-+            for (l = l; l; l = l->next) {
-+                GamSubscription *sub = l->data;
-+                gam_kqueue_remove_subscription (sub);
-+            }
-+            GAM_DEBUG(DEBUG_INFO, "removed kqueue watch for %s\n", data->path);
-+	    if (data->isdir) {
-+                g_hash_table_remove(dir_path_hash, data->path);
-+	    }
-+	    else {
-+		g_hash_table_remove(file_path_hash, data->path);
-+	    }
-+            g_hash_table_remove(fd_hash, GINT_TO_POINTER(data->fd));
-+            gam_kqueue_data_free(data);
-+        }
++static void
++gam_kqueue_poller_remove_monitor (Poller *poller, Monitor *mon)
++{
++  poller->monitors = g_slist_remove(poller->monitors, mon);
++  if (! poller->monitors && poller->timeout_id != -1)
++    {
++      g_source_remove(poller->timeout_id);
++      poller->timeout_id = -1;
 +    }
-+    G_UNLOCK(kqueue);
 +}
 +
-+static GaminEventType kqueue_event_to_gamin_event (int mask)
++static void
++gam_kqueue_monitor_clear_files (Monitor *mon)
 +{
-+	if ((mask & VN_NOTE_CHANGED) != 0)
-+		return GAMIN_EVENT_CHANGED;
-+	else if ((mask & NOTE_DELETE) != 0)
-+		return GAMIN_EVENT_DELETED;
-+	else if ((mask & NOTE_REVOKE) != 0)
-+		return GAMIN_EVENT_ENDEXISTS;
-+	else if ((mask & NOTE_RENAME) != 0)
-+		return GAMIN_EVENT_MOVED;
-+	else
-+		return GAMIN_EVENT_UNKNOWN;
++  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;
 +}
 +
-+static void gam_kqueue_emit_event (KQueueData *data, struct kevent *event)
++static void
++gam_kqueue_monitor_set_missing (Monitor *mon)
 +{
-+	GaminEventType gevent;
-+	int isdir = 0;
-+	char *event_path;
++  if (mon->fd >= 0)
++    {
++      close(mon->fd);
++      mon->fd = -1;
++    }
 +
-+	if (!data||!event)
-+		return;
++  /*
++   * 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);
 +
-+	gevent = kqueue_event_to_gamin_event (event->fflags);
++  gam_kqueue_poller_remove_monitor(&unsupported_poller, mon);
++  gam_kqueue_poller_add_monitor(&missing_poller, mon);
++}
 +
-+	if (gevent == GAMIN_EVENT_UNKNOWN) {
-+		return;
-+	}
++static void
++gam_kqueue_monitor_set_unsupported (Monitor *mon)
++{
++  if (mon->fd >= 0)
++    {
++      close(mon->fd);
++      mon->fd = -1;
++    }
 +
-+	isdir = g_file_test(data->path, G_FILE_TEST_IS_DIR);
++  gam_kqueue_mini_lstat(mon->path, &mon->sb);
++  gam_kqueue_poller_add_monitor(&unsupported_poller, mon);
++}
 +
-+	if (gevent == GAMIN_EVENT_CHANGED && data->isdir) {
-+	    GSList *dirlist = NULL;
-+	    GSList *l;
-+	    GDir *dir;
-+
-+	    dir = g_dir_open(data->path, 0, NULL);
-+	    if (dir) {
-+	        const char *entry;
++static void
++gam_kqueue_monitor_enable_notification (Monitor *mon, gboolean was_missing)
++{
++  struct stat sb;
++  gboolean exists;
++  gboolean isdir;
++  struct kevent ev[1];
 +
-+		while ((entry = g_dir_read_name(dir))) {
-+		    dirlist = g_slist_prepend(dirlist, g_strdup(entry));
-+		}
++  /* we first send CREATED or EXISTS/DELETED+ENDEXISTS events */
 +
-+		g_dir_close(dir);
++  exists = lstat(mon->path, &sb) >= 0;
++  isdir = exists && (sb.st_mode & S_IFDIR) != 0;
++  
++  if (exists)
++    {
++      GaminEventType gevent;
++
++      gevent = was_missing ? GAMIN_EVENT_CREATED : GAMIN_EVENT_EXISTS;
++      gam_server_emit_event(mon->path, isdir, gevent, mon->subs, 1);
++
++      if (mon->isdir && isdir)
++	{
++	  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);
 +	    }
-+
-+	    for (l = dirlist; l; l = l->next) {
-+	        if (! g_slist_find_custom(data->dirlist, l->data, (GCompareFunc) gam_kqueue_dirlist_find)) {
-+		    FileData *fdata;
-+
-+		    fdata = gam_kqueue_file_data_new(data->path, l->data);
-+		    data->dirlist = g_slist_prepend(data->dirlist, fdata);
-+
-+		    GAM_DEBUG(DEBUG_INFO, "kqueue emitting event %s for %s\n", gam_event_to_string(GAMIN_EVENT_CREATED), fdata->pathname);
-+		    gam_server_emit_event(fdata->pathname,
-+					  g_file_test(fdata->pathname, G_FILE_TEST_IS_DIR),
-+					  GAMIN_EVENT_CREATED, data->subs, 1);
++	  
++	  dir = g_dir_open(mon->path, 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);
 +		}
++	      
++	      g_dir_close(dir);
 +	    }
-+
-+	iterate:
-+	    for (l = data->dirlist; l; l = l->next) {
-+	        FileData *fdata = l->data;
-+
-+	        if (! g_slist_find_custom(dirlist, fdata->filename, (GCompareFunc) strcmp)) {
-+		    data->dirlist = g_slist_remove(data->dirlist, fdata);
-+
-+                    GAM_DEBUG(DEBUG_INFO, "kqueue emitting event %s for %s\n", gam_event_to_string(GAMIN_EVENT_DELETED), fdata->pathname);
-+		    gam_server_emit_event(fdata->pathname,
-+					  g_file_test(fdata->pathname, G_FILE_TEST_IS_DIR),
-+					  GAMIN_EVENT_DELETED, data->subs, 1);
-+
-+		    gam_kqueue_file_data_free(fdata);
-+		    goto iterate; /* list changed, start again */
-+		}
++	  else
++	    {
++	      GAM_DEBUG(DEBUG_INFO, "unable to open directory %s: %s\n", mon->path, err->message);
++	      g_error_free(err);
 +	    }
 +
-+	    if (dirlist) {
-+	        g_slist_foreach(dirlist, (GFunc)g_free, NULL);
-+	        g_slist_free(dirlist);
-+	    }
-+            return;
++	  if (mon->files)
++	    gam_kqueue_poller_add_monitor(&dirs_poller, mon);
 +	}
-+	else {
-+	    event_path = g_strdup (data->path);
 +
-+	    if (gevent == GAMIN_EVENT_DELETED
-+		|| gevent == GAMIN_EVENT_ENDEXISTS
-+		|| gevent == GAMIN_EVENT_MOVED) {
-+	      /* close and move to exist_list, to catch next creation */
-+	      close(data->fd);
-+	      if (data->isdir) {
-+		  g_hash_table_remove(dir_path_hash, data->path);
-+	      }
-+	      else {
-+		  g_hash_table_remove(file_path_hash, data->path);
-+	      }
-+	      g_hash_table_remove(fd_hash, GINT_TO_POINTER(data->fd));
-+
-+	      exist_list = g_slist_append(exist_list, data);
-+	    }
++      if (! was_missing)
++	gam_server_emit_event(mon->path, isdir, GAMIN_EVENT_ENDEXISTS, mon->subs, 1);
++    }
++  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);
 +	}
 +
-+        isdir = g_file_test(event_path, G_FILE_TEST_IS_DIR);
-+
-+	GAM_DEBUG(DEBUG_INFO, "kqueue emitting event %s for %s\n", gam_event_to_string(gevent) , event_path);
++      gam_kqueue_monitor_set_missing(mon);
++      return;
++    }
++    
++  /* then we enable kqueue notification, falling back to poll if necessary */
 +
-+	gam_server_emit_event (event_path, isdir, gevent, data->subs, 1);
++  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;
++    }
 +
-+	g_free (event_path);
++  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 gboolean
-+gam_kqueue_exist_check (gpointer user_data)
++static void
++gam_kqueue_monitor_handle_directory_change (Monitor *mon, gboolean isdir)
 +{
-+    GSList *l, *tmplst;
-+    KQueueData *data;
-+
-+    tmplst = g_slist_copy(exist_list);
-+
-+    for (l = tmplst; l; l = l->next) {
-+        data = l->data;
++  GSList *filenames = NULL;
++  GSList *l;
++  GSList *tmp_files;
++
++  if (isdir)			/* do not follow symbolic links */
++    {
++      GDir *dir;
++      GError *err = NULL;
++
++      dir = g_dir_open(mon->path, 0, &err);
++      if (dir)
++	{
++	  const char *filename;
++	  
++	  while ((filename = g_dir_read_name(dir)))
++	    filenames = g_slist_prepend(filenames, g_strdup(filename));
++	  
++	  g_dir_close(dir);
++	}
++      else
++	{
++	  GAM_DEBUG(DEBUG_INFO, "unable to open directory %s: %s\n", mon->path, err->message);
++	  g_error_free(err);
++	}
++    }
 +
-+	if (g_file_test(data->path, G_FILE_TEST_EXISTS)) {
-+            /* The subs list is guaranteed to have only one entry. */
-+            GamSubscription *sub = data->subs->data;
++  /* 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;
++      
++	entry = gam_kqueue_file_entry_new(mon->path, l->data);
++	mon->files = g_slist_prepend(mon->files, entry);
++      
++	gam_server_emit_event(entry->pathname,
++			      (entry->sb.mode & S_IFDIR) != 0,
++			      GAMIN_EVENT_CREATED, mon->subs, 1);
++      }
++
++  /* handle deleted files */
++  tmp_files = g_slist_copy(mon->files);
++  for (l = tmp_files; l; l = l->next)
++    {
++      FileEntry *entry = l->data;
++
++      if (! g_slist_find_custom(filenames, entry->filename, (GCompareFunc) strcmp))
++	{
++	  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);
 +
-+            exist_list = g_slist_remove(exist_list, data);
-+	    gam_kqueue_add_rm_handler(data->path, sub, TRUE, TRUE);
-+	    gam_kqueue_data_free(data);
++	  gam_kqueue_file_entry_free(entry);
 +	}
 +    }
++  g_slist_free(tmp_files);
 +
-+    if (tmplst)
-+        g_slist_free(tmplst);
++  if (filenames)
++    {
++      g_slist_foreach(filenames, (GFunc) g_free, NULL);
++      g_slist_free(filenames);
++    }
 +
-+    return TRUE;
++  if (mon->files)
++    gam_kqueue_poller_add_monitor(&dirs_poller, mon);
++  else
++    gam_kqueue_poller_remove_monitor(&dirs_poller, mon);
 +}
 +
 +static void
-+gam_kqueue_dirlist_check_cb (const char *path, KQueueData *data, gpointer user_data)
-+{
-+    GSList *l;
-+
-+    for (l = data->dirlist; l; l = l->next) {
-+        FileData *fdata = l->data;
-+	MiniStat sb;
++gam_kqueue_monitor_emit_event (Monitor *mon,
++			       GaminEventType event,
++			       gboolean has_isdir,
++			       gboolean isdir)
++{
++  if (! has_isdir)
++    {
++      struct stat sb;
++      isdir = lstat(mon->path, &sb) >= 0 && (sb.st_mode & S_IFDIR) != 0;
++    }
 +
-+	gam_kqueue_mini_stat(fdata->pathname, &sb);
++  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);
++      break;
++    }
 +
-+	if (sb.mtime != fdata->sb.mtime
-+	    || sb.ctime != fdata->sb.ctime
-+	    || sb.size != fdata->sb.size
-+	    || sb.mode != fdata->sb.mode
-+	    || sb.uid != fdata->sb.uid
-+	    || sb.gid != fdata->sb.gid
-+	    || sb.ino != fdata->sb.ino)
-+	  {
-+	      memcpy(&fdata->sb, &sb, sizeof(sb));
++  gam_server_emit_event(mon->path, isdir, event, mon->subs, 1);
++}
 +
-+	      GAM_DEBUG(DEBUG_INFO, "kqueue emitting event %s for %s\n", gam_event_to_string(GAMIN_EVENT_CHANGED), fdata->pathname);
-+	      gam_server_emit_event(fdata->pathname,
-+				    g_file_test(fdata->pathname, G_FILE_TEST_IS_DIR),
-+				    GAMIN_EVENT_CHANGED, data->subs, 1);
-+	  }
++static void
++gam_kqueue_monitor_handle_kevent (Monitor *mon, struct kevent *event)
++{
++  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);
++      return;
 +    }
++
++  /*
++   * 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);
++  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);
 +}
 +
 +static gboolean
-+gam_kqueue_dirlist_check (gpointer user_data)
++gam_kqueue_kevent_cb (GIOChannel *source, GIOCondition condition, gpointer user_data)
 +{
-+    G_LOCK(kqueue);
-+
-+    GAM_DEBUG(DEBUG_INFO, "gam_kqueue_dirlist_check()\n");
-+
-+    g_hash_table_foreach(dir_path_hash, (GHFunc) gam_kqueue_dirlist_check_cb, NULL);
++  int nevents;
++  struct kevent ev[1];
++  struct timespec timeout = { 0, 0 };
++  int i;
++
++  nevents = kevent(kq, NULL, 0, ev, G_N_ELEMENTS(ev), &timeout);
++  if (nevents < 0)
++    {
++      GAM_DEBUG(DEBUG_INFO, "kevent() failure: %s\n", g_strerror(errno));
++      return TRUE;		/* keep source */
++    }
 +
-+    G_UNLOCK(kqueue);
++  for (i = 0; i < nevents; i++)
++    gam_kqueue_monitor_handle_kevent(ev[i].udata, &ev[i]);
 +
-+    return TRUE;
++  return TRUE;			/* keep source */
 +}
 +
 +static gboolean
-+gam_kqueue_event_handler (GIOChannel *source, GIOCondition condition, gpointer user_data)
++gam_kqueue_missing_poll (gpointer user_data)
 +{
-+    KQueueData *data;
-+    struct kevent ev[1];
-+    struct timespec timeout = { 0, 0 };
-+    int fd, i, nevents;
-+
-+    G_LOCK(kqueue);
-+
-+    GAM_DEBUG(DEBUG_INFO, "gam_kqueue_event_handler()\n");
-+
-+    nevents = kevent(kq, NULL, 0, ev, 1, &timeout);
-+    if (nevents == -1)
-+        return FALSE;
-+    for (i = 0; i < nevents; i++) {
-+        fd = ev[i].ident;
-+
-+	data = g_hash_table_lookup (fd_hash, GINT_TO_POINTER(fd));
-+	if (!data) {
-+	    GAM_DEBUG(DEBUG_INFO, "kqueue can't find fd %d\n", fd);
-+	    GAM_DEBUG(DEBUG_INFO, "weird things have happened to kqueue.\n");
-+	} else {
-+	    gam_kqueue_emit_event (data, &ev[i]);
-+	}
++  GSList *tmp_list;
++  GSList *l;
 +
-+    }
++  /* the list may be modified while we're iterating, so we use a copy */
 +
-+    G_UNLOCK(kqueue);
++  tmp_list = g_slist_copy(missing_poller.monitors);
++  for (l = tmp_list; l; l = l->next)
++    {
++      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);
++	}
++    }
++  g_slist_free(tmp_list);
 +
-+    return TRUE;
++  return TRUE;
 +}
 +
 +static gboolean
-+gam_kqueue_consume_subscriptions_real(gpointer data)
++gam_kqueue_unsupported_poll (gpointer user_data)
 +{
-+	GList *subs, *l;
++  GSList *tmp_list;
++  GSList *l;
 +
-+	G_LOCK(new_subs);
-+	if (new_subs) {
-+		subs = new_subs;
-+		new_subs = NULL;
-+		G_UNLOCK(new_subs);
-+
-+		for (l = subs; l; l = l->next) {
-+			GamSubscription *sub = l->data;
-+			GAM_DEBUG(DEBUG_INFO, "called gam_kqueue_add_handler()\n");
-+			gam_kqueue_add_rm_handler (gam_subscription_get_path (sub), sub, TRUE, FALSE);
-+		}
-+
-+	} else {
-+		G_UNLOCK(new_subs);
-+	}
++  /* the list may be modified while we're iterating, so we use a copy */
 +
-+	G_LOCK(removed_subs);
-+	if (removed_subs) {
-+		subs = removed_subs;
-+		removed_subs = NULL;
-+		G_UNLOCK(removed_subs);
-+
-+		for (l = subs; l; l = l->next) {
-+			GamSubscription *sub = l->data;
-+			GAM_DEBUG(DEBUG_INFO, "called gam_kqueue_rm_handler()\n");
-+			gam_kqueue_add_rm_handler (gam_subscription_get_path (sub), sub, FALSE, FALSE);
-+		}
-+	} else {
-+		G_UNLOCK(removed_subs);
-+	}
-+
-+	GAM_DEBUG(DEBUG_INFO, "gam_kqueue_consume_subscriptions()\n");
++  tmp_list = g_slist_copy(unsupported_poller.monitors);
++  for (l = tmp_list; l; l = l->next)
++    {
++      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);
++    }
++  g_slist_free(tmp_list);
 +
-+	have_consume_idler = FALSE;
-+	return FALSE;
++  return TRUE;
 +}
 +
-+static void
-+gam_kqueue_consume_subscriptions(void)
++static gboolean
++gam_kqueue_dirs_poll (gpointer user_data)
 +{
-+	GSource *source;
-+
-+	if (have_consume_idler)
-+		return;
++  GSList *l;
 +
-+	have_consume_idler = TRUE;
++  /* the list cannot be modified while we're iterating */
 +
-+	source = g_idle_source_new ();
-+	g_source_set_callback (source, gam_kqueue_consume_subscriptions_real, NULL, NULL);
++  for (l = dirs_poller.monitors; l; l = l->next)
++    {
++      Monitor *mon = l->data;
++      GSList *f;
++
++      for (f = mon->files; f; f = f->next)
++	{
++	  FileEntry *entry = f->data;
++	  MiniStat 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);
++	    }
++	}
++    }
 +
-+	g_source_attach (source, NULL);
++  return TRUE;
 +}
 +
 +/**
-+ * @defgroup kqueue kqueue backend
-+ * @ingroup Backends
-+ * @brief kqueue backend API
-+ *
-+ * Since 4.1, FreeBSD kernels have included the kernel event notification
-+ * machanism (kqueue).  This backend uses kqueue to know when
-+ * files are changed/created/deleted.
-+ *
-+ * @{
-+ */
-+
-+
-+/**
 + * Initializes the kqueue system.  This must be called before
 + * any other functions in this module.
 + *
 + * @returns TRUE if initialization succeeded, FALSE otherwise
 + */
 +gboolean
-+gam_kqueue_init(void)
++gam_kqueue_init (void)
 +{
-+    GIOChannel *channel;
++  GIOChannel *channel;
 +
-+    kq = kqueue();
-+    if (kq == -1) {
-+	GAM_DEBUG(DEBUG_INFO, "Could not initialize a new kqueue\n");
-+	return FALSE;
++  kq = kqueue();
++  if (kq < 0)
++    {
++      gam_error(DEBUG_INFO, "kqueue initialization failure: %s\n", g_strerror(errno));
++      return FALSE;
 +    }
 +
-+    g_timeout_add(1000, gam_kqueue_exist_check, NULL);
++  dir_hash = g_hash_table_new(g_str_hash, g_str_equal);
++  file_hash = g_hash_table_new(g_str_hash, g_str_equal);
 +
-+    channel = g_io_channel_unix_new(kq);
-+    g_io_add_watch(channel, G_IO_IN, gam_kqueue_event_handler, NULL);
++  channel = g_io_channel_unix_new(kq);
++  g_io_add_watch(channel, G_IO_IN, gam_kqueue_kevent_cb, NULL);
 +
-+    dir_path_hash = g_hash_table_new(g_str_hash, g_str_equal);
-+    file_path_hash = g_hash_table_new(g_str_hash, g_str_equal);
-+    fd_hash = g_hash_table_new(g_direct_hash, g_direct_equal);
++  gam_backend_add_subscription = gam_kqueue_add_subscription;
++  gam_backend_remove_subscription = gam_kqueue_remove_subscription;
++  gam_backend_remove_all_for = gam_kqueue_remove_all_for;
 +
-+    /*
-+     * gam_kqueue_dirlist_check() has to lstat() every file in every
-+     * monitored directory. This can easily become an intensive task
-+     * if a few large directories are monitored (for instance a mail
-+     * checker monitoring a couple of MH folders), therefore we use a
-+     * reasonable poll interval (6 seconds, same as FAM's default).
-+     */
-+    g_timeout_add(6000, gam_kqueue_dirlist_check, NULL);
-+
-+    GAM_DEBUG(DEBUG_INFO, "kqueue initialized\n");
-+
-+    gam_backend_add_subscription = gam_kqueue_add_subscription;
-+    gam_backend_remove_subscription = gam_kqueue_remove_subscription;
-+    gam_backend_remove_all_for = gam_kqueue_remove_all_for;
-+
-+    return TRUE;
++  return TRUE;
 +}
 +
 +/**
@@ -644,18 +636,34 @@
 + * @returns TRUE if adding the subscription succeeded, FALSE otherwise
 + */
 +gboolean
-+gam_kqueue_add_subscription(GamSubscription * sub)
++gam_kqueue_add_subscription (GamSubscription *sub)
 +{
-+	gam_listener_add_subscription(gam_subscription_get_listener(sub), sub);
-+
-+	G_LOCK(new_subs);
-+	new_subs = g_list_prepend(new_subs, sub);
-+	G_UNLOCK(new_subs);
++  const char *path;
++  GHashTable *hash;
++  Monitor *mon;
++
++  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);
++
++  if (mon)
++    {
++      mon->subs = g_list_append(mon->subs, sub);
++      return TRUE;
++    }
++  
++  mon = g_new0(Monitor, 1);
++  mon->path = path;
++  mon->subs = g_list_append(mon->subs, sub);
++  mon->fd = -1;
++  mon->isdir = hash == dir_hash;
 +
-+	GAM_DEBUG(DEBUG_INFO, "kqueue_add_sub\n");
++  g_hash_table_insert(hash, (gpointer) mon->path, mon);
++  gam_kqueue_monitor_enable_notification(mon, FALSE);
 +
-+	gam_kqueue_consume_subscriptions();
-+    return TRUE;
++  return TRUE;
 +}
 +
 +/**
@@ -665,28 +673,39 @@
 + * @returns TRUE if removing the subscription succeeded, FALSE otherwise
 + */
 +gboolean
-+gam_kqueue_remove_subscription(GamSubscription * sub)
++gam_kqueue_remove_subscription (GamSubscription *sub)
 +{
-+	G_LOCK(new_subs);
-+	if (g_list_find(new_subs, sub)) {
-+		GAM_DEBUG(DEBUG_INFO, "removed sub found on new_subs\n");
-+		new_subs = g_list_remove_all (new_subs, sub);
-+		G_UNLOCK(new_subs);
-+		return TRUE;
-+	}
-+	G_UNLOCK(new_subs);
++  GHashTable *hash;
++  Monitor *mon;
++
++  hash = gam_subscription_is_dir(sub) ? dir_hash : file_hash;
++  mon = g_hash_table_lookup(hash, gam_subscription_get_path(sub));
++
++  if (! mon)
++    return FALSE;
++
++  mon->subs = g_list_remove_all(mon->subs, sub);
++  if (! mon->subs)
++    {
++      g_hash_table_remove(hash, mon->path);
 +
-+	gam_subscription_cancel (sub);
-+	gam_listener_remove_subscription(gam_subscription_get_listener(sub), sub);
++      /* might have been in any of these two */
++      gam_kqueue_poller_remove_monitor(&missing_poller, mon);
++      gam_kqueue_poller_remove_monitor(&unsupported_poller, mon);
 +
-+	G_LOCK(removed_subs);
-+	removed_subs = g_list_prepend (removed_subs, sub);
-+	G_UNLOCK(removed_subs);
++      if (mon->fd >= 0)
++	close(mon->fd);
 +
-+	GAM_DEBUG(DEBUG_INFO, "kqueue_remove_sub\n");
-+	gam_kqueue_consume_subscriptions();
++      if (mon->files)
++	gam_kqueue_monitor_clear_files(mon);
 +
-+    return TRUE;
++      g_free(mon);
++    }
++
++  gam_subscription_cancel(sub);
++  gam_listener_remove_subscription(gam_subscription_get_listener(sub), sub);
++
++  return TRUE;
 +}
 +
 +/**
@@ -696,28 +715,19 @@
 + * @returns TRUE if removing the subscriptions succeeded, FALSE otherwise
 + */
 +gboolean
-+gam_kqueue_remove_all_for(GamListener * listener)
++gam_kqueue_remove_all_for (GamListener *listener)
 +{
-+	GList *subs, *l = NULL;
-+
-+	subs = gam_listener_get_subscriptions (listener);
-+
-+	for (l = subs; l; l = l->next) {
-+		GamSubscription *sub = l->data;
++  GList *subs;
++  GList *l;
++  gboolean success = TRUE;
 +
-+		g_assert (sub != NULL);
++  subs = gam_listener_get_subscriptions(listener);
 +
-+		gam_kqueue_remove_subscription (sub);
++  for (l = subs; l; l = l->next)
++    if (! gam_kqueue_remove_subscription(l->data))
++      success = FALSE;
 +
-+	}
-+
-+	if (subs) {
-+		g_list_free (subs);
-+		gam_kqueue_consume_subscriptions();
-+		return TRUE;
-+	} else {
-+		return FALSE;
-+	}
++  g_list_free(subs);
++  
++  return success;
 +}
-+
-+/** @} */
diff -ruN /usr/ports/devel/gamin/files/patch-server_gam_kqueue.h gamin/files/patch-server_gam_kqueue.h
--- /usr/ports/devel/gamin/files/patch-server_gam_kqueue.h	Sat Apr  2 11:08:47 2005
+++ gamin/files/patch-server_gam_kqueue.h	Fri Apr  8 14:51:25 2005
@@ -1,19 +1,11 @@
---- server/gam_kqueue.h.orig	Sat Mar 26 18:32:19 2005
-+++ server/gam_kqueue.h	Sat Mar 26 18:36:11 2005
-@@ -0,0 +1,24 @@
-+
+--- server/gam_kqueue.h.orig	Fri Apr  8 14:50:41 2005
++++ server/gam_kqueue.h	Fri Apr  8 14:51:01 2005
+@@ -0,0 +1,16 @@
 +#ifndef __MD_KQUEUE_H__
 +#define __MD_KQUEUE_H__
 +
 +#include <glib.h>
-+#include "gam_poll.h"
 +#include "gam_subscription.h"
-+
-+#define VN_NOTE_MOST	(NOTE_DELETE | NOTE_WRITE | NOTE_EXTEND | \
-+			 NOTE_ATTRIB | NOTE_LINK | NOTE_RENAME | \
-+			 NOTE_REVOKE)
-+#define VN_NOTE_ALL	VN_NOTE_MOST
-+#define VN_NOTE_CHANGED	(NOTE_WRITE | NOTE_EXTEND | NOTE_ATTRIB | NOTE_LINK)
 +
 +G_BEGIN_DECLS
 +
diff -ruN /usr/ports/devel/gamin/files/patch-tests_testing.c gamin/files/patch-tests_testing.c
--- /usr/ports/devel/gamin/files/patch-tests_testing.c	Thu Jan  1 01:00:00 1970
+++ gamin/files/patch-tests_testing.c	Fri Apr  8 15:11:03 2005
@@ -0,0 +1,14 @@
+--- tests/testing.c.orig	Fri Apr  8 15:09:45 2005
++++ tests/testing.c	Fri Apr  8 15:09:57 2005
+@@ -424,9 +424,9 @@
+             return (-1);
+         }
+         /*
+-         * wait at most 3 secs before declaring failure
++         * wait at most 7 secs before declaring failure
+          */
+-        while ((delay < 30) && (testState.nb_events < nb_events + count)) {
++        while ((delay < 70) && (testState.nb_events < nb_events + count)) {
+             debugLoop(100);
+ 
+ /*	    printf("+"); fflush(stdout); */
diff -ruN /usr/ports/devel/gamin/pkg-descr gamin/pkg-descr
--- /usr/ports/devel/gamin/pkg-descr	Sat Apr  2 11:08:46 2005
+++ gamin/pkg-descr	Fri Apr  8 15:08:51 2005
@@ -2,4 +2,7 @@
 FAM (File Alteration Monitor) system. This is a service provided by a library
 which allows to detect when a file or a directory has been modified.
 
+Whereas the FreeBSD port of FAM polls files every few seconds, this port
+includes a kqueue(2) backend for immediate notification of most alterations.
+
 WWW: http://www.gnome.org/~veillard/gamin/index.html
Comment 3 Joe Marcus Clarke freebsd_committer freebsd_triage 2005-04-10 18:36:34 UTC
State Changed
From-To: open->closed

Committed, thanks!