summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'app-admin/gamin/files/gamin-new-inotify-backend.patch')
-rw-r--r--app-admin/gamin/files/gamin-new-inotify-backend.patch3643
1 files changed, 3643 insertions, 0 deletions
diff --git a/app-admin/gamin/files/gamin-new-inotify-backend.patch b/app-admin/gamin/files/gamin-new-inotify-backend.patch
new file mode 100644
index 0000000..5917559
--- /dev/null
+++ b/app-admin/gamin/files/gamin-new-inotify-backend.patch
@@ -0,0 +1,3643 @@
+--- /dev/null 2006-08-28 15:22:40.902752500 +0200
++++ gamin-0.1.7/server/inotify-missing.h 2006-09-05 11:01:21.000000000 +0200
+@@ -0,0 +1,34 @@
++/* inotify-helper.h - GNOME VFS Monitor using inotify
++
++ Copyright (C) 2006 John McCutchan <john@johnmccutchan.com>
++
++ The Gnome Library is free software; you can redistribute it and/or
++ modify it under the terms of the GNU Library General Public License as
++ published by the Free Software Foundation; either version 2 of the
++ License, or (at your option) any later version.
++
++ The Gnome Library is distributed in the hope that it will be useful,
++ but WITHOUT ANY WARRANTY; without even the implied warranty of
++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
++ Library General Public License for more details.
++
++ You should have received a copy of the GNU Library General Public
++ License along with the Gnome Library; see the file COPYING.LIB. If not,
++ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
++ Boston, MA 02111-1307, USA.
++
++ Author: John McCutchan <ttb@tentacle.dhs.org>
++*/
++
++
++#ifndef __INOTIFY_MISSING_H
++#define __INOTIFY_MISSING_H
++
++#include "inotify-sub.h"
++
++void im_startup (void (*missing_cb)(ih_sub_t *sub));
++void im_add (ih_sub_t *sub);
++void im_rm (ih_sub_t *sub);
++void im_diag_dump (GIOChannel *ioc);
++
++#endif /* __INOTIFY_MISSING_H */
+--- /dev/null 2006-08-28 15:22:40.902752500 +0200
++++ gamin-0.1.7/server/inotify-kernel.h 2006-09-05 11:01:21.000000000 +0200
+@@ -0,0 +1,40 @@
++/*
++ Copyright (C) 2006 John McCutchan <john@johnmccutchan.com>
++
++ This program is free software; you can redistribute it and/or modify
++ it under the terms of the GNU General Public License as published by
++ the Free Software Foundation; version 2.
++
++ This program is distributed in the hope that it will be useful,
++ but WITHOUT ANY WARRANTY; without even the implied warranty of
++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ GNU General Public License version 2 for more details.
++ You should have received a copy of the GNU General Public License
++ along with this program; if not, write to the Free Software Foundation,
++ Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
++*/
++
++#ifndef __INOTIFY_KERNEL_H
++#define __INOTIFY_KERNEL_H
++
++typedef struct ik_event_s {
++ gint32 wd;
++ guint32 mask;
++ guint32 cookie;
++ guint32 len;
++ char * name;
++ struct ik_event_s *pair;
++} ik_event_t;
++
++gboolean ik_startup (void (*cb)(ik_event_t *event));
++ik_event_t *ik_event_new_dummy (const char *name, gint32 wd, guint32 mask);
++void ik_event_free (ik_event_t *event);
++
++gint32 ik_watch(const char *path, guint32 mask, int *err);
++int ik_ignore(const char *path, gint32 wd);
++
++/* The miss count will probably be enflated */
++void ik_move_stats (guint32 *matches, guint32 *misses);
++const char *ik_mask_to_string (guint32 mask);
++
++#endif
+--- /dev/null 2006-08-28 15:22:40.902752500 +0200
++++ gamin-0.1.7/server/inotify-diag.h 2006-09-05 11:01:21.000000000 +0200
+@@ -0,0 +1,30 @@
++/* inotify-helper.h - GNOME VFS Monitor using inotify
++
++ Copyright (C) 2006 John McCutchan <john@johnmccutchan.com>
++
++ The Gnome Library is free software; you can redistribute it and/or
++ modify it under the terms of the GNU Library General Public License as
++ published by the Free Software Foundation; either version 2 of the
++ License, or (at your option) any later version.
++
++ The Gnome Library is distributed in the hope that it will be useful,
++ but WITHOUT ANY WARRANTY; without even the implied warranty of
++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
++ Library General Public License for more details.
++
++ You should have received a copy of the GNU Library General Public
++ License along with the Gnome Library; see the file COPYING.LIB. If not,
++ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
++ Boston, MA 02111-1307, USA.
++
++ Author: John McCutchan <ttb@tentacle.dhs.org>
++*/
++
++
++#ifndef __INOTIFY_DIAG_H
++#define __INOTIFY_DIAG_H
++
++void id_startup (void);
++gboolean id_dump (gpointer userdata);
++
++#endif /* __INOTIFY_DIAG_H */
+--- gamin-0.1.7/server/Makefile.am.new-inotify-backend 2005-08-26 13:52:19.000000000 +0200
++++ gamin-0.1.7/server/Makefile.am 2006-09-05 11:01:21.000000000 +0200
+@@ -51,6 +51,12 @@
+
+ if ENABLE_INOTIFY
+ gam_server_SOURCES += gam_inotify.c gam_inotify.h \
++ inotify-helper.c inotify-helper.h \
++ inotify-kernel.c inotify-kernel.h \
++ inotify-missing.c inotify-missing.h \
++ inotify-path.c inotify-path.h \
++ inotify-sub.c inotify-sub.h \
++ inotify-diag.c inotify-diag.h \
+ local_inotify.h local_inotify_syscalls.h
+ endif
+
+--- gamin-0.1.7/server/local_inotify_syscalls.h.new-inotify-backend 2005-08-17 15:50:04.000000000 +0200
++++ gamin-0.1.7/server/local_inotify_syscalls.h 2006-09-05 11:01:21.000000000 +0200
+@@ -1,7 +1,9 @@
+ #ifndef _LINUX_INOTIFY_SYSCALLS_H
+ #define _LINUX_INOTIFY_SYSCALLS_H
+
++#include <asm/types.h>
+ #include <sys/syscall.h>
++#include <unistd.h>
+
+ #if defined(__i386__)
+ # define __NR_inotify_init 291
+--- /dev/null 2006-08-28 15:22:40.902752500 +0200
++++ gamin-0.1.7/server/inotify-kernel.c 2006-09-05 11:01:21.000000000 +0200
+@@ -0,0 +1,685 @@
++/*
++ Copyright (C) 2006 John McCutchan <john@johnmccutchan.com>
++
++ This program is free software; you can redistribute it and/or modify
++ it under the terms of the GNU General Public License as published by
++ the Free Software Foundation; version 2.
++
++ This program is distributed in the hope that it will be useful,
++ but WITHOUT ANY WARRANTY; without even the implied warranty of
++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ GNU General Public License version 2 for more details.
++ You should have received a copy of the GNU General Public License
++ along with this program; if not, write to the Free Software Foundation,
++ Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
++*/
++
++#include "config.h"
++
++#include <stdio.h>
++#include <sys/ioctl.h>
++#include <unistd.h>
++#include <errno.h>
++#include <string.h>
++#include <glib.h>
++#include "inotify-kernel.h"
++
++/* Just include the local headers to stop all the pain */
++#include "local_inotify.h"
++#include "local_inotify_syscalls.h"
++#if 0
++#ifdef HAVE_SYS_INOTIFY_H
++/* We don't actually include the libc header, because there has been
++ * problems with libc versions that was built without inotify support.
++ * Instead we use the local version.
++ */
++#include "local_inotify.h"
++#include "local_inotify_syscalls.h"
++#elif defined (HAVE_LINUX_INOTIFY_H)
++#include <linux/inotify.h>
++#include "local_inotify_syscalls.h"
++#endif
++#endif
++
++/* Timings for pairing MOVED_TO / MOVED_FROM events */
++#define PROCESS_EVENTS_TIME 1000 /* milliseconds (1 hz) */
++#define DEFAULT_HOLD_UNTIL_TIME 0 /* 0 millisecond */
++#define MOVE_HOLD_UNTIL_TIME 0 /* 0 milliseconds */
++
++static int inotify_instance_fd = -1;
++static GQueue *events_to_process = NULL;
++static GQueue *event_queue = NULL;
++static GHashTable * cookie_hash = NULL;
++static GIOChannel *inotify_read_ioc;
++static GPollFD ik_poll_fd;
++static gboolean ik_poll_fd_enabled = TRUE;
++static void (*user_cb)(ik_event_t *event);
++
++static gboolean ik_read_callback (gpointer user_data);
++static gboolean ik_process_eq_callback (gpointer user_data);
++
++static guint32 ik_move_matches = 0;
++static guint32 ik_move_misses = 0;
++
++static gboolean process_eq_running = FALSE;
++
++/* We use the lock from inotify-helper.c
++ *
++ * There are two places that we take this lock
++ *
++ * 1) In ik_read_callback
++ *
++ * 2) ik_process_eq_callback.
++ *
++ *
++ * The rest of locking is taken care of in inotify-helper.c
++ */
++G_LOCK_EXTERN (inotify_lock);
++
++typedef struct ik_event_internal {
++ ik_event_t *event;
++ gboolean seen;
++ gboolean sent;
++ GTimeVal hold_until;
++ struct ik_event_internal *pair;
++} ik_event_internal_t;
++
++/* In order to perform non-sleeping inotify event chunking we need
++ * a custom GSource
++ */
++static gboolean
++ik_source_prepare (GSource *source,
++ gint *timeout)
++{
++ return FALSE;
++}
++
++static gboolean
++ik_source_timeout (gpointer data)
++{
++ GSource *source = (GSource *)data;
++
++ /* Re-active the PollFD */
++ g_source_add_poll (source, &ik_poll_fd);
++ g_source_unref (source);
++ ik_poll_fd_enabled = TRUE;
++
++ return FALSE;
++}
++
++#define MAX_PENDING_COUNT 2
++#define PENDING_THRESHOLD(qsize) ((qsize) >> 1)
++#define PENDING_MARGINAL_COST(p) ((unsigned int)(1 << (p)))
++#define MAX_QUEUED_EVENTS 2048
++#define AVERAGE_EVENT_SIZE sizeof (struct inotify_event) + 16
++#define TIMEOUT_MILLISECONDS 10
++static gboolean
++ik_source_check (GSource *source)
++{
++ static int prev_pending = 0, pending_count = 0;
++
++ /* We already disabled the PollFD or
++ * nothing to be read from inotify */
++ if (!ik_poll_fd_enabled || !(ik_poll_fd.revents & G_IO_IN))
++ {
++ return FALSE;
++ }
++
++ if (pending_count < MAX_PENDING_COUNT) {
++ unsigned int pending;
++
++ if (ioctl (inotify_instance_fd, FIONREAD, &pending) == -1)
++ goto do_read;
++
++ pending /= AVERAGE_EVENT_SIZE;
++
++ /* Don't wait if the number of pending events is too close
++ * to the maximum queue size.
++ */
++ if (pending > PENDING_THRESHOLD (MAX_QUEUED_EVENTS))
++ goto do_read;
++
++ /* With each successive iteration, the minimum rate for
++ * further sleep doubles. */
++ if (pending-prev_pending < PENDING_MARGINAL_COST(pending_count))
++ goto do_read;
++
++ prev_pending = pending;
++ pending_count++;
++
++ /* We are going to wait to read the events: */
++
++ /* Remove the PollFD from the source */
++ g_source_remove_poll (source, &ik_poll_fd);
++ /* To avoid threading issues we need to flag that we've done that */
++ ik_poll_fd_enabled = FALSE;
++ /* Set a timeout to re-add the PollFD to the source */
++ g_source_ref (source);
++ g_timeout_add (TIMEOUT_MILLISECONDS, ik_source_timeout, source);
++
++ return FALSE;
++ }
++
++do_read:
++ /* We are ready to read events from inotify */
++
++ prev_pending = 0;
++ pending_count = 0;
++
++ return TRUE;
++}
++
++static gboolean
++ik_source_dispatch (GSource *source,
++ GSourceFunc callback,
++ gpointer user_data)
++{
++ if (callback)
++ {
++ return callback(user_data);
++ }
++ return TRUE;
++}
++
++GSourceFuncs ik_source_funcs =
++{
++ ik_source_prepare,
++ ik_source_check,
++ ik_source_dispatch,
++ NULL
++};
++
++gboolean ik_startup (void (*cb)(ik_event_t *event))
++{
++ static gboolean initialized = FALSE;
++ GSource *source;
++
++ user_cb = cb;
++ /* Ignore multi-calls */
++ if (initialized) {
++ return inotify_instance_fd >= 0;
++ }
++
++ initialized = TRUE;
++ inotify_instance_fd = inotify_init ();
++
++ if (inotify_instance_fd < 0) {
++ return FALSE;
++ }
++
++ inotify_read_ioc = g_io_channel_unix_new(inotify_instance_fd);
++ ik_poll_fd.fd = inotify_instance_fd;
++ ik_poll_fd.events = G_IO_IN | G_IO_HUP | G_IO_ERR;
++ g_io_channel_set_encoding(inotify_read_ioc, NULL, NULL);
++ g_io_channel_set_flags(inotify_read_ioc, G_IO_FLAG_NONBLOCK, NULL);
++
++ source = g_source_new (&ik_source_funcs, sizeof(GSource));
++ g_source_add_poll (source, &ik_poll_fd);
++ g_source_set_callback(source, ik_read_callback, NULL, NULL);
++ g_source_attach(source, NULL);
++ g_source_unref (source);
++
++ cookie_hash = g_hash_table_new(g_direct_hash, g_direct_equal);
++ event_queue = g_queue_new ();
++ events_to_process = g_queue_new ();
++
++ return TRUE;
++}
++
++static ik_event_internal_t *ik_event_internal_new (ik_event_t *event)
++{
++ ik_event_internal_t *internal_event = g_new0(ik_event_internal_t, 1);
++ GTimeVal tv;
++
++ g_assert (event);
++
++ g_get_current_time (&tv);
++ g_time_val_add (&tv, DEFAULT_HOLD_UNTIL_TIME);
++ internal_event->event = event;
++ internal_event->hold_until = tv;
++
++ return internal_event;
++}
++
++static ik_event_t *ik_event_new (char *buffer)
++{
++ struct inotify_event *kevent = (struct inotify_event *)buffer;
++ g_assert (buffer);
++ ik_event_t *event = g_new0(ik_event_t,1);
++ event->wd = kevent->wd;
++ event->mask = kevent->mask;
++ event->cookie = kevent->cookie;
++ event->len = kevent->len;
++ if (event->len)
++ event->name = g_strdup(kevent->name);
++ else
++ event->name = g_strdup("");
++
++ return event;
++}
++
++ik_event_t *ik_event_new_dummy (const char *name, gint32 wd, guint32 mask)
++{
++ ik_event_t *event = g_new0(ik_event_t,1);
++ event->wd = wd;
++ event->mask = mask;
++ event->cookie = 0;
++ if (name)
++ event->name = g_strdup(name);
++ else
++ event->name = g_strdup("");
++
++ event->len = strlen (event->name);
++
++ return event;
++}
++
++void ik_event_free (ik_event_t *event)
++{
++ if (event->pair)
++ ik_event_free (event->pair);
++ g_free(event->name);
++ g_free(event);
++}
++
++gint32 ik_watch (const char *path, guint32 mask, int *err)
++{
++ gint32 wd = -1;
++
++ g_assert (path != NULL);
++ g_assert (inotify_instance_fd >= 0);
++
++ wd = inotify_add_watch (inotify_instance_fd, path, mask);
++
++ if (wd < 0)
++ {
++ int e = errno;
++ // FIXME: debug msg failed to add watch
++ if (err)
++ *err = e;
++ return wd;
++ }
++
++ g_assert (wd >= 0);
++ return wd;
++}
++
++int ik_ignore(const char *path, gint32 wd)
++{
++ g_assert (wd >= 0);
++ g_assert (inotify_instance_fd >= 0);
++
++ if (inotify_rm_watch (inotify_instance_fd, wd) < 0)
++ {
++ //int e = errno;
++ // failed to rm watch
++ return -1;
++ }
++
++ return 0;
++}
++
++void ik_move_stats (guint32 *matches, guint32 *misses)
++{
++ if (matches)
++ *matches = ik_move_matches;
++
++ if (misses)
++ *misses = ik_move_misses;
++}
++
++const char *ik_mask_to_string (guint32 mask)
++{
++ gboolean is_dir = mask & IN_ISDIR;
++ mask &= ~IN_ISDIR;
++
++ if (is_dir)
++ {
++ switch (mask)
++ {
++ case IN_ACCESS:
++ return "ACCESS (dir)";
++ break;
++ case IN_MODIFY:
++ return "MODIFY (dir)";
++ break;
++ case IN_ATTRIB:
++ return "ATTRIB (dir)";
++ break;
++ case IN_CLOSE_WRITE:
++ return "CLOSE_WRITE (dir)";
++ break;
++ case IN_CLOSE_NOWRITE:
++ return "CLOSE_NOWRITE (dir)";
++ break;
++ case IN_OPEN:
++ return "OPEN (dir)";
++ break;
++ case IN_MOVED_FROM:
++ return "MOVED_FROM (dir)";
++ break;
++ case IN_MOVED_TO:
++ return "MOVED_TO (dir)";
++ break;
++ case IN_DELETE:
++ return "DELETE (dir)";
++ break;
++ case IN_CREATE:
++ return "CREATE (dir)";
++ break;
++ case IN_DELETE_SELF:
++ return "DELETE_SELF (dir)";
++ break;
++ case IN_UNMOUNT:
++ return "UNMOUNT (dir)";
++ break;
++ case IN_Q_OVERFLOW:
++ return "Q_OVERFLOW (dir)";
++ break;
++ case IN_IGNORED:
++ return "IGNORED (dir)";
++ break;
++ default:
++ return "UNKNOWN_EVENT (dir)";
++ break;
++
++ }
++ } else {
++ switch (mask)
++ {
++ case IN_ACCESS:
++ return "ACCESS";
++ break;
++ case IN_MODIFY:
++ return "MODIFY";
++ break;
++ case IN_ATTRIB:
++ return "ATTRIB";
++ break;
++ case IN_CLOSE_WRITE:
++ return "CLOSE_WRITE";
++ break;
++ case IN_CLOSE_NOWRITE:
++ return "CLOSE_NOWRITE";
++ break;
++ case IN_OPEN:
++ return "OPEN";
++ break;
++ case IN_MOVED_FROM:
++ return "MOVED_FROM";
++ break;
++ case IN_MOVED_TO:
++ return "MOVED_TO";
++ break;
++ case IN_DELETE:
++ return "DELETE";
++ break;
++ case IN_CREATE:
++ return "CREATE";
++ break;
++ case IN_DELETE_SELF:
++ return "DELETE_SELF";
++ break;
++ case IN_UNMOUNT:
++ return "UNMOUNT";
++ break;
++ case IN_Q_OVERFLOW:
++ return "Q_OVERFLOW";
++ break;
++ case IN_IGNORED:
++ return "IGNORED";
++ break;
++ default:
++ return "UNKNOWN_EVENT";
++ break;
++
++ }
++ }
++}
++
++
++static void ik_read_events (gsize *buffer_size_out, gchar **buffer_out)
++{
++ static gchar *buffer = NULL;
++ static gsize buffer_size;
++
++ /* Initialize the buffer on our first call */
++ if (buffer == NULL)
++ {
++ buffer_size = AVERAGE_EVENT_SIZE;
++ buffer_size *= MAX_QUEUED_EVENTS;
++ buffer = g_malloc (buffer_size);
++
++ if (!buffer) {
++ *buffer_size_out = 0;
++ *buffer_out = NULL;
++ return;
++ }
++ }
++
++ *buffer_size_out = 0;
++ *buffer_out = NULL;
++
++ memset(buffer, 0, buffer_size);
++
++ if (g_io_channel_read_chars (inotify_read_ioc, (char *)buffer, buffer_size, buffer_size_out, NULL) != G_IO_STATUS_NORMAL) {
++ // error reading
++ }
++ *buffer_out = buffer;
++}
++
++static gboolean ik_read_callback(gpointer user_data)
++{
++ gchar *buffer;
++ gsize buffer_size, buffer_i, events;
++
++ G_LOCK(inotify_lock);
++ ik_read_events (&buffer_size, &buffer);
++
++ buffer_i = 0;
++ events = 0;
++ while (buffer_i < buffer_size)
++ {
++ struct inotify_event *event;
++ gsize event_size;
++ event = (struct inotify_event *)&buffer[buffer_i];
++ event_size = sizeof(struct inotify_event) + event->len;
++ g_queue_push_tail (events_to_process, ik_event_internal_new (ik_event_new (&buffer[buffer_i])));
++ buffer_i += event_size;
++ events++;
++ }
++
++ /* If the event process callback is off, turn it back on */
++ if (!process_eq_running && events)
++ {
++ process_eq_running = TRUE;
++ g_timeout_add (PROCESS_EVENTS_TIME, ik_process_eq_callback, NULL);
++ }
++
++ G_UNLOCK(inotify_lock);
++ return TRUE;
++}
++
++static gboolean
++g_timeval_lt(GTimeVal *val1, GTimeVal *val2)
++{
++ if (val1->tv_sec < val2->tv_sec)
++ return TRUE;
++
++ if (val1->tv_sec > val2->tv_sec)
++ return FALSE;
++
++ /* val1->tv_sec == val2->tv_sec */
++ if (val1->tv_usec < val2->tv_usec)
++ return TRUE;
++
++ return FALSE;
++}
++
++static gboolean
++g_timeval_eq(GTimeVal *val1, GTimeVal *val2)
++{
++ return (val1->tv_sec == val2->tv_sec) && (val1->tv_usec == val2->tv_usec);
++}
++
++static void
++ik_pair_events (ik_event_internal_t *event1, ik_event_internal_t *event2)
++{
++ g_assert (event1 && event2);
++ /* We should only be pairing events that have the same cookie */
++ g_assert (event1->event->cookie == event2->event->cookie);
++ /* We shouldn't pair an event that already is paired */
++ g_assert (event1->pair == NULL && event2->pair == NULL);
++
++ /* Pair the internal structures and the ik_event_t structures */
++ event1->pair = event2;
++ event1->event->pair = event2->event;
++
++ if (g_timeval_lt (&event1->hold_until, &event2->hold_until))
++ event1->hold_until = event2->hold_until;
++
++ event2->hold_until = event1->hold_until;
++}
++
++static void
++ik_event_add_microseconds (ik_event_internal_t *event, glong ms)
++{
++ g_assert (event);
++ g_time_val_add (&event->hold_until, ms);
++}
++
++static gboolean
++ik_event_ready (ik_event_internal_t *event)
++{
++ GTimeVal tv;
++ g_assert (event);
++
++ g_get_current_time (&tv);
++
++ /* An event is ready if,
++ *
++ * it has no cookie -- there is nothing to be gained by holding it
++ * or, it is already paired -- we don't need to hold it anymore
++ * or, we have held it long enough
++ */
++ return event->event->cookie == 0 ||
++ event->pair != NULL ||
++ g_timeval_lt(&event->hold_until, &tv) || g_timeval_eq(&event->hold_until, &tv);
++}
++
++static void
++ik_pair_moves (gpointer data, gpointer user_data)
++{
++ ik_event_internal_t *event = (ik_event_internal_t *)data;
++
++ if (event->seen == TRUE || event->sent == TRUE)
++ return;
++
++ if (event->event->cookie != 0)
++ {
++ /* When we get a MOVED_FROM event we delay sending the event by
++ * MOVE_HOLD_UNTIL_TIME microseconds. We need to do this because a
++ * MOVED_TO pair _might_ be coming in the near future */
++ if (event->event->mask & IN_MOVED_FROM) {
++ g_hash_table_insert (cookie_hash, GINT_TO_POINTER(event->event->cookie), event);
++ // because we don't deliver move events there is no point in waiting for the match right now.
++ ik_event_add_microseconds (event, MOVE_HOLD_UNTIL_TIME);
++ } else if (event->event->mask & IN_MOVED_TO) {
++ /* We need to check if we are waiting for this MOVED_TO events cookie to pair it with
++ * a MOVED_FROM */
++ ik_event_internal_t *match = NULL;
++ match = g_hash_table_lookup (cookie_hash, GINT_TO_POINTER(event->event->cookie));
++ if (match) {
++ g_hash_table_remove (cookie_hash, GINT_TO_POINTER(event->event->cookie));
++ ik_pair_events (match, event);
++ }
++ }
++ }
++ event->seen = TRUE;
++}
++
++static void
++ik_process_events ()
++{
++ g_queue_foreach (events_to_process, ik_pair_moves, NULL);
++
++ while (!g_queue_is_empty (events_to_process))
++ {
++ ik_event_internal_t *event = g_queue_peek_head (events_to_process);
++
++ /* This must have been sent as part of a MOVED_TO/MOVED_FROM */
++ if (event->sent)
++ {
++ /* Pop event */
++ g_queue_pop_head (events_to_process);
++ /* Free the internal event structure */
++ g_free (event);
++ continue;
++ }
++
++ /* The event isn't ready yet */
++ if (!ik_event_ready (event)) {
++ break;
++ }
++
++ /* Pop it */
++ event = g_queue_pop_head (events_to_process);
++
++ /* Check if this is a MOVED_FROM that is also sitting in the cookie_hash */
++ if (event->event->cookie && event->pair == NULL &&
++ g_hash_table_lookup (cookie_hash, GINT_TO_POINTER(event->event->cookie)))
++ {
++ g_hash_table_remove (cookie_hash, GINT_TO_POINTER(event->event->cookie));
++ }
++
++ if (event->pair) {
++ /* We send out paired MOVED_FROM/MOVED_TO events in the same event buffer */
++ //g_assert (event->event->mask == IN_MOVED_FROM && event->pair->event->mask == IN_MOVED_TO);
++ /* Copy the paired data */
++ event->pair->sent = TRUE;
++ event->sent = TRUE;
++ ik_move_matches++;
++ } else if (event->event->cookie) {
++ /* If we couldn't pair a MOVED_FROM and MOVED_TO together, we change
++ * the event masks */
++ /* Changeing MOVED_FROM to DELETE and MOVED_TO to create lets us make
++ * the gaurantee that you will never see a non-matched MOVE event */
++
++ if (event->event->mask & IN_MOVED_FROM) {
++ event->event->mask = IN_DELETE|(event->event->mask & IN_ISDIR);
++ ik_move_misses++; // not super accurate, if we aren't watching the destination it still counts as a miss
++ }
++ if (event->event->mask & IN_MOVED_TO)
++ event->event->mask = IN_CREATE|(event->event->mask & IN_ISDIR);
++ }
++
++ /* Push the ik_event_t onto the event queue */
++ g_queue_push_tail (event_queue, event->event);
++ /* Free the internal event structure */
++ g_free (event);
++ }
++}
++
++gboolean ik_process_eq_callback (gpointer user_data)
++{
++ /* Try and move as many events to the event queue */
++ G_LOCK(inotify_lock);
++ ik_process_events ();
++
++ while (!g_queue_is_empty (event_queue))
++ {
++ ik_event_t *event = g_queue_pop_head (event_queue);
++
++ user_cb (event);
++ }
++
++ if (g_queue_get_length (events_to_process) == 0)
++ {
++ process_eq_running = FALSE;
++ G_UNLOCK(inotify_lock);
++ return FALSE;
++ } else {
++ G_UNLOCK(inotify_lock);
++ return TRUE;
++ }
++}
+--- /dev/null 2006-08-28 15:22:40.902752500 +0200
++++ gamin-0.1.7/server/inotify-missing.c 2006-09-05 11:01:21.000000000 +0200
+@@ -0,0 +1,158 @@
++/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
++
++/* inotify-helper.c - Gnome VFS Monitor based on inotify.
++
++ Copyright (C) 2005 John McCutchan
++
++ The Gnome Library is free software; you can redistribute it and/or
++ modify it under the terms of the GNU Library General Public License as
++ published by the Free Software Foundation; either version 2 of the
++ License, or (at your option) any later version.
++
++ The Gnome Library is distributed in the hope that it will be useful,
++ but WITHOUT ANY WARRANTY; without even the implied warranty of
++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
++ Library General Public License for more details.
++
++ You should have received a copy of the GNU Library General Public
++ License along with the Gnome Library; see the file COPYING.LIB. If not,
++ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
++ Boston, MA 02111-1307, USA.
++
++ Authors:
++ John McCutchan <john@johnmccutchan.com>
++*/
++
++#include "config.h"
++#include <glib.h>
++#include "inotify-missing.h"
++#include "inotify-path.h"
++
++#define SCAN_MISSING_TIME 4000 /* 1/4 Hz */
++
++static gboolean im_debug_enabled = FALSE;
++#define IM_W if (im_debug_enabled) g_warning
++
++/* We put ih_sub_t's that are missing on this list */
++static GList *missing_sub_list = NULL;
++static gboolean im_scan_missing (gpointer user_data);
++static gboolean scan_missing_running = FALSE;
++static void (*missing_cb)(ih_sub_t *sub) = NULL;
++
++G_LOCK_EXTERN (inotify_lock);
++
++/* inotify_lock must be held before calling */
++void im_startup (void (*callback)(ih_sub_t *sub))
++{
++ static gboolean initialized = FALSE;
++
++ if (!initialized) {
++ initialized = TRUE;
++ missing_cb = callback;
++ }
++}
++
++/* inotify_lock must be held before calling */
++void im_add (ih_sub_t *sub)
++{
++ if (g_list_find (missing_sub_list, sub)) {
++ IM_W("asked to add %s to missing list but it's already on the list!\n", sub->pathname);
++ return;
++ }
++
++ IM_W("adding %s to missing list\n", sub->dirname);
++ missing_sub_list = g_list_prepend (missing_sub_list, sub);
++
++ /* If the timeout is turned off, we turn it back on */
++ if (!scan_missing_running)
++ {
++ scan_missing_running = TRUE;
++ g_timeout_add (SCAN_MISSING_TIME, im_scan_missing, NULL);
++ }
++}
++
++/* inotify_lock must be held before calling */
++void im_rm (ih_sub_t *sub)
++{
++ GList *link;
++
++ link = g_list_find (missing_sub_list, sub);
++
++ if (!link) {
++ IM_W("asked to remove %s from missing list but it isn't on the list!\n", sub->pathname);
++ return;
++ }
++
++ IM_W("removing %s from missing list\n", sub->dirname);
++
++ missing_sub_list = g_list_remove_link (missing_sub_list, link);
++ g_list_free_1 (link);
++}
++
++/* Scans the list of missing subscriptions checking if they
++ * are available yet.
++ */
++static gboolean im_scan_missing (gpointer user_data)
++{
++ GList *nolonger_missing = NULL;
++ GList *l;
++
++ G_LOCK(inotify_lock);
++
++ IM_W("scanning missing list with %d items\n", g_list_length (missing_sub_list));
++ for (l = missing_sub_list; l; l = l->next)
++ {
++ ih_sub_t *sub = l->data;
++ gboolean not_m = FALSE;
++
++ IM_W("checking %p\n", sub);
++ g_assert (sub);
++ g_assert (sub->dirname);
++ not_m = ip_start_watching (sub);
++
++ if (not_m)
++ {
++ missing_cb (sub);
++ IM_W("removed %s from missing list\n", sub->dirname);
++ /* We have to build a list of list nodes to remove from the
++ * missing_sub_list. We do the removal outside of this loop.
++ */
++ nolonger_missing = g_list_prepend (nolonger_missing, l);
++ }
++ }
++
++ for (l = nolonger_missing; l ; l = l->next)
++ {
++ GList *llink = l->data;
++ missing_sub_list = g_list_remove_link (missing_sub_list, llink);
++ g_list_free_1 (llink);
++ }
++
++ g_list_free (nolonger_missing);
++
++ /* If the missing list is now empty, we disable the timeout */
++ if (missing_sub_list == NULL)
++ {
++ scan_missing_running = FALSE;
++ G_UNLOCK(inotify_lock);
++ return FALSE;
++ } else {
++ G_UNLOCK(inotify_lock);
++ return TRUE;
++ }
++}
++
++
++/* inotify_lock must be held */
++void
++im_diag_dump (GIOChannel *ioc)
++{
++ GList *l;
++ g_io_channel_write_chars (ioc, "missing list:\n", -1, NULL, NULL);
++ for (l = missing_sub_list; l; l = l->next)
++ {
++ ih_sub_t *sub = l->data;
++ g_io_channel_write_chars (ioc, sub->pathname, -1, NULL, NULL);
++ g_io_channel_write_chars (ioc, "\n", -1, NULL, NULL);
++ }
++}
+--- gamin-0.1.7/server/gam_inotify.c.new-inotify-backend 2005-10-25 16:16:28.000000000 +0200
++++ gamin-0.1.7/server/gam_inotify.c 2006-09-05 12:13:38.000000000 +0200
+@@ -17,1582 +17,201 @@
+ */
+
+ #include "server_config.h"
+-#define _GNU_SOURCE
+-#include <errno.h>
+-#include <stdio.h>
+ #include <string.h>
+-#include <sys/types.h>
+-#include <sys/stat.h>
+-#include <sys/ioctl.h>
+-#include <fcntl.h>
+-#include <unistd.h>
+-#include <asm/unistd.h>
+-#include <time.h>
+-#include <glib.h>
++/* Just include the local header to stop all the pain */
++#include "local_inotify.h"
++#if 0
++#ifdef HAVE_SYS_INOTIFY_H
++/* We don't actually include the libc header, because there has been
++ * problems with libc versions that was built without inotify support.
++ * Instead we use the local version.
++ */
++#include "local_inotify.h"
++#elif defined (HAVE_LINUX_INOTIFY_H)
++#include <linux/inotify.h>
++#endif
++#endif
++#include "inotify-sub.h"
++#include "inotify-helper.h"
++#include "inotify-diag.h"
+ #ifdef GAMIN_DEBUG_API
+ #include "gam_debugging.h"
+ #endif
+ #include "gam_error.h"
+-#include "gam_poll_basic.h"
+-#ifdef HAVE_LINUX_INOTIFY_H
+-#include <linux/inotify.h>
+-#else
+-#include "local_inotify.h"
+-#endif
+-#include "local_inotify_syscalls.h"
+-#include "gam_inotify.h"
+-#include "gam_tree.h"
+ #include "gam_event.h"
+ #include "gam_server.h"
+-#include "gam_event.h"
+-#include "gam_fs.h"
+-
+-#define GAM_INOTIFY_SANITY
+-#define GAM_INOTIFY_WD_MISSING -1
+-#define GAM_INOTIFY_WD_PERM -2
+-#define GAM_INOTIFY_WD_LINK -3
+-
+-/* Timings for pairing MOVED_TO / MOVED_FROM events */
+-/* These numbers are in microseconds */
+-#define DEFAULT_HOLD_UNTIL_TIME 1000 /* 1 ms */
+-#define MOVE_HOLD_UNTIL_TIME 5000 /* 5 ms */
+-
+-/* Timings for main loop */
+-/* These numbers are in milliseconds */
+-#define SCAN_MISSING_TIME 1000 /* 1 Hz */
+-#define SCAN_LINKS_TIME 1000 /* 1 Hz */
+-#define PROCESS_EVENTS_TIME 33 /* 30 Hz */
+-
+-typedef struct {
+- /* The full pathname of this node */
+- char *path;
+- gboolean dir; /* Is this path a directory */
+-
+- /* Inotify */
+- int wd;
+-
+- /* State */
+- gboolean busy;
+- gboolean missing;
+- gboolean link;
+- gboolean permission; /* Exists, but don't have read access */
+- gboolean deactivated;
+- gboolean ignored;
+- int refcount;
+-
+- /* Statistics */
+- int events;
+- int deactivated_events;
+- int ignored_events;
+-
+- /* Gamin state */
+- GList *subs;
+-} inotify_data_t;
+-
+-typedef struct _inotify_event_t {
+- gint wd;
+- gint mask;
+- gint cookie;
+- char *name;
+- gboolean seen;
+- gboolean sent;
+- GTimeVal hold_until;
+- struct _inotify_event_t *pair;
+-} inotify_event_t;
+-
+-typedef struct {
+- char *path;
+- GTime last_scan_time;
+- GTime scan_interval;
+- gboolean permission;
+-} inotify_missing_t;
+-
+-typedef struct {
+- char *path;
+- struct stat sbuf;
+- GTime last_scan_time;
+- GTime scan_interval;
+-} inotify_links_t;
+-
+-static GHashTable * path_hash = NULL;
+-static GHashTable * wd_hash = NULL;
+-static GList * missing_list = NULL;
+-static GList * links_list = NULL;
+-static GHashTable * cookie_hash = NULL;
+-static GQueue * event_queue = NULL;
+-static GQueue * events_to_process = NULL;
+-static GIOChannel * inotify_read_ioc = NULL;
+-static int inotify_device_fd = -1;
+-
+-#define GAM_INOTIFY_MASK (IN_MODIFY|IN_ATTRIB|IN_MOVED_FROM|IN_MOVED_TO|IN_DELETE|IN_CREATE|IN_DELETE_SELF|IN_UNMOUNT|IN_MOVE_SELF)
+-
+-static int gam_inotify_add_watch (const char *path, __u32 mask, int *err);
+-static int gam_inotify_rm_watch (const char *path, __u32 wd);
+-static void gam_inotify_read_events (gsize *buffer_size_out, gchar **buffer_out);
+-
+-static gboolean gam_inotify_is_missing (const char *path);
+-static gboolean gam_inotify_nolonger_missing (const char *path);
+-static void gam_inotify_add_missing (const char *path, gboolean perm);
+-static void gam_inotify_rm_missing (const char *path);
+-static gboolean gam_inotify_scan_missing (gpointer userdata);
+-
+-static gboolean gam_inotify_is_link (const char *path);
+-static gboolean gam_inotify_nolonger_link (const char *path);
+-static void gam_inotify_add_link (const char *path);
+-static void gam_inotify_rm_link (const char *path);
+-static gboolean gam_inotify_scan_links (gpointer userdata);
+-static void gam_inotify_poll_link (inotify_links_t *links);
+-
+-static void gam_inotify_sanity_check (void);
+-
+-static gboolean g_timeval_lt (GTimeVal *val1, GTimeVal *val2);
+-static gboolean g_timeval_eq (GTimeVal *val1, GTimeVal *val2);
+-
+-static void
+-gam_inotify_data_debug (gpointer key, gpointer value, gpointer user_data)
+-{
+- int busy;
+- int deactivated;
+- int ignored;
+- int missing;
+- int permission;
+- inotify_data_t *data = (inotify_data_t *)value;
+-
+- if (!data)
+- return;
+-
+- busy = data->busy;
+- deactivated = data->deactivated;
+- ignored = data->ignored;
+- missing = data->missing;
+- permission = data->permission;
+-
+- GAM_DEBUG(DEBUG_INFO, "isub wd %d refs %d permission %d missing %d busy %d deactivated %d ignored %d events (%d:%d:%d): %s\n", data->wd, data->refcount, permission, missing, busy, deactivated, ignored, data->events, data->deactivated_events, data->ignored_events, data->path);
+-}
+-
+-gboolean
+-gam_inotify_is_running(void)
+-{
+- return inotify_device_fd >= 0;
+-}
+-
+-void
+-gam_inotify_debug(void)
+-{
+- if (inotify_device_fd == -1)
+- {
+- return;
+- }
+-
+- if (path_hash == NULL)
+- return;
+-
+- GAM_DEBUG(DEBUG_INFO, "Inotify device fd = %d\n", inotify_device_fd);
+- GAM_DEBUG(DEBUG_INFO, "Dumping inotify subscriptions\n");
+- g_hash_table_foreach (path_hash, gam_inotify_data_debug, NULL);
+-}
+-
+-static const char *
+-mask_to_string (int mask)
+-{
+- mask &= ~IN_ISDIR;
+- switch (mask)
+- {
+- case IN_ACCESS:
+- return "ACCESS";
+- break;
+- case IN_MODIFY:
+- return "MODIFY";
+- break;
+- case IN_ATTRIB:
+- return "ATTRIB";
+- break;
+- case IN_CLOSE_WRITE:
+- return "CLOSE_WRITE";
+- break;
+- case IN_CLOSE_NOWRITE:
+- return "CLOSE_NOWRITE";
+- break;
+- case IN_OPEN:
+- return "OPEN";
+- break;
+- case IN_MOVED_FROM:
+- return "MOVED_FROM";
+- break;
+- case IN_MOVED_TO:
+- return "MOVED_TO";
+- break;
+- case IN_DELETE:
+- return "DELETE";
+- break;
+- case IN_CREATE:
+- return "CREATE";
+- break;
+- case IN_DELETE_SELF:
+- return "DELETE_SELF";
+- break;
+- case IN_UNMOUNT:
+- return "UNMOUNT";
+- break;
+- case IN_Q_OVERFLOW:
+- return "Q_OVERFLOW";
+- break;
+- case IN_IGNORED:
+- return "IGNORED";
+- break;
+- default:
+- return "UNKNOWN_EVENT";
+- break;
+- }
+-}
+-
+-static GaminEventType
+-mask_to_gam_event (gint mask)
+-{
+- mask &= ~IN_ISDIR;
+- switch (mask)
+- {
+- case IN_MODIFY:
+- case IN_ATTRIB:
+- return GAMIN_EVENT_CHANGED;
+- break;
+- case IN_MOVE_SELF:
+- case IN_MOVED_FROM:
+- case IN_DELETE:
+- case IN_DELETE_SELF:
+- return GAMIN_EVENT_DELETED;
+- break;
+- case IN_CREATE:
+- case IN_MOVED_TO:
+- return GAMIN_EVENT_CREATED;
+- break;
+- case IN_Q_OVERFLOW:
+- case IN_OPEN:
+- case IN_CLOSE_WRITE:
+- case IN_CLOSE_NOWRITE:
+- case IN_UNMOUNT:
+- case IN_ACCESS:
+- case IN_IGNORED:
+- default:
+- return GAMIN_EVENT_UNKNOWN;
+- break;
+- }
+-}
+-
+-/* Called when a directory is being watched as a file */
+-static GaminEventType
+-gam_inotify_mask_to_gam_file_event (gint mask)
+-{
+- mask &= ~IN_ISDIR;
+- switch (mask)
+- {
+- case IN_MOVED_FROM:
+- case IN_DELETE:
+- case IN_CREATE:
+- case IN_MOVED_TO:
+- return GAMIN_EVENT_CHANGED;
+- break;
+- case IN_MOVE_SELF:
+- case IN_DELETE_SELF:
+- return GAMIN_EVENT_DELETED;
+- break;
+- case IN_ATTRIB:
+- case IN_MODIFY:
+- case IN_Q_OVERFLOW:
+- case IN_OPEN:
+- case IN_CLOSE_WRITE:
+- case IN_CLOSE_NOWRITE:
+- case IN_UNMOUNT:
+- case IN_ACCESS:
+- case IN_IGNORED:
+- default:
+- return GAMIN_EVENT_UNKNOWN;
+- break;
+- }
+-}
++#include "gam_subscription.h"
++#include "gam_inotify.h"
+
+-/* Called when a file is watched as a directory */
++/* Transforms a inotify event to a gamin event. */
+ static GaminEventType
+-gam_inotify_mask_to_gam_dir_event (gint mask)
++ih_mask_to_EventType (guint32 mask)
+ {
+- mask &= ~IN_ISDIR;
+- switch (mask)
+- {
+- case IN_MOVED_FROM:
+- case IN_DELETE:
+- case IN_CREATE:
+- case IN_MOVED_TO:
+- case IN_MOVE_SELF:
+- case IN_DELETE_SELF:
+- case IN_ATTRIB:
+- case IN_MODIFY:
+- case IN_Q_OVERFLOW:
+- case IN_OPEN:
+- case IN_CLOSE_WRITE:
+- case IN_CLOSE_NOWRITE:
+- case IN_UNMOUNT:
+- case IN_ACCESS:
+- case IN_IGNORED:
+- default:
+- return GAMIN_EVENT_UNKNOWN;
++ mask &= ~IN_ISDIR;
++ switch (mask)
++ {
++ case IN_MODIFY:
++ return GAMIN_EVENT_CHANGED;
++ break;
++ case IN_ATTRIB:
++ return GAMIN_EVENT_CHANGED;
++ break;
++ case IN_MOVE_SELF:
++ case IN_MOVED_FROM:
++ case IN_DELETE:
++ case IN_DELETE_SELF:
++ return GAMIN_EVENT_DELETED;
++ break;
++ case IN_CREATE:
++ case IN_MOVED_TO:
++ return GAMIN_EVENT_CREATED;
++ break;
++ case IN_Q_OVERFLOW:
++ case IN_OPEN:
++ case IN_CLOSE_WRITE:
++ case IN_CLOSE_NOWRITE:
++ case IN_UNMOUNT:
++ case IN_ACCESS:
++ case IN_IGNORED:
++ default:
++ return -1;
+ break;
+ }
+ }
+
+-static inotify_data_t *
+-gam_inotify_data_new(const char *path, int wd, gboolean dir)
+-{
+- inotify_data_t *data;
+-
+- data = g_new0(inotify_data_t, 1);
+-
+- data->path = g_strdup(path);
+- data->wd = wd;
+- data->busy = FALSE;
+- if (wd == GAM_INOTIFY_WD_MISSING)
+- data->missing = TRUE;
+- else
+- data->missing = FALSE;
+- if (wd == GAM_INOTIFY_WD_PERM)
+- data->permission = TRUE;
+- else
+- data->permission = FALSE;
+- if (wd == GAM_INOTIFY_WD_LINK)
+- data->link = TRUE;
+- else
+- data->link = FALSE;
+- data->deactivated = FALSE;
+- data->ignored = FALSE;
+- data->refcount = 1;
+- data->events = 0;
+- data->deactivated_events = 0;
+- data->ignored_events = 0;
+- data->dir = dir;
+-
+- return data;
+-}
+-
+-static void
+-gam_inotify_data_free(inotify_data_t * data)
+-{
+- if (data->refcount != 0)
+- GAM_DEBUG(DEBUG_INFO, "gam_inotify_data_free called with reffed data.\n");
+-
+- g_free(data->path);
+- g_free(data);
+-}
+-
+-static inotify_event_t *
+-gam_inotify_event_new (struct inotify_event *event)
+-{
+- inotify_event_t *gam_event;
+- GTimeVal tv;
+-
+- gam_event = g_new0(inotify_event_t, 1);
+-
+- gam_event->wd = event->wd;
+- gam_event->mask = event->mask;
+- gam_event->cookie = event->cookie;
+-
+- if (event->len)
+- {
+- gam_event->name = g_strdup (event->name);
+- } else {
+- gam_event->name = g_strdup ("");
+- }
+-
+- g_get_current_time (&tv);
+- g_time_val_add (&tv, DEFAULT_HOLD_UNTIL_TIME);
+- gam_event->hold_until = tv;
+-
+- return gam_event;
+-}
+-
+-static void
+-gam_inotify_event_free (inotify_event_t *event)
+-{
+- g_free (event->name);
+- g_free (event);
+-}
+-
+-static void
+-gam_inotify_event_pair_with (inotify_event_t *event1, inotify_event_t *event2)
+-{
+- g_assert (event1 && event2);
+- /* We should only be pairing events that have the same cookie */
+- g_assert (event1->cookie == event2->cookie);
+- /* We shouldn't pair an event that already is paired */
+- g_assert (event1->pair == NULL && event2->pair == NULL);
+- event1->pair = event2;
+- event2->pair = event1;
+-
+- GAM_DEBUG(DEBUG_INFO, "inotify: pairing a MOVE together\n");
+- if (g_timeval_lt (&event1->hold_until, &event2->hold_until))
+- event1->hold_until = event2->hold_until;
+-
+- event2->hold_until = event1->hold_until;
+-}
+-
+-static void
+-gam_inotify_event_add_microseconds (inotify_event_t *event, glong ms)
+-{
+- g_assert (event);
+- g_time_val_add (&event->hold_until, ms);
+-}
+-
+-static gboolean
+-gam_inotify_event_ready (inotify_event_t *event)
+-{
+- GTimeVal tv;
+- g_assert (event);
+-
+- g_get_current_time (&tv);
+-
+- /* An event is ready if,
+- *
+- * it has no cookie -- there is nothing to be gained by holding it
+- * or, it is already paired -- we don't need to hold it anymore
+- * or, we have held it long enough
+- */
+- return event->cookie == 0 ||
+- event->pair != NULL ||
+- g_timeval_lt(&event->hold_until, &tv) || g_timeval_eq(&event->hold_until, &tv);
+-}
+-
+ static void
+-gam_inotify_emit_one_event (inotify_data_t *data, inotify_event_t *event, GamSubscription *sub)
++gam_inotify_send_initial_events (const char *pathname, GamSubscription *sub, gboolean is_dir, gboolean was_missing)
+ {
+- gint force = 1;
+- gint is_dir_node = 0;
+ GaminEventType gevent;
+- gchar *fullpath = NULL;
+- gboolean watching_dir_as_file;
+- gboolean watching_file_as_dir;
+
+- g_assert (data && event);
++ gevent = was_missing ? GAMIN_EVENT_CREATED : GAMIN_EVENT_EXISTS;
+
+- is_dir_node = event->mask & IN_ISDIR;
+- watching_dir_as_file = data->dir && !gam_subscription_is_dir (sub);
+- watching_file_as_dir = !data->dir && gam_subscription_is_dir (sub);
++ gam_server_emit_one_event (pathname, is_dir ? 1 : 0, gevent, sub, 1);
+
+- if (watching_dir_as_file)
++ if (is_dir)
+ {
+- gevent = gam_inotify_mask_to_gam_file_event (event->mask);
+- fullpath = g_strdup (data->path);
+- } else if (watching_file_as_dir) {
+- gevent = gam_inotify_mask_to_gam_dir_event (event->mask);
+- fullpath = g_strdup (data->path);
+- } else {
+- gevent = mask_to_gam_event (event->mask);
+- if (strlen (event->name) == 0)
+- fullpath = g_strdup (data->path);
+- else
+- fullpath = g_strdup_printf ("%s/%s", data->path, event->name);
+- }
+-
+- if (gevent == GAMIN_EVENT_UNKNOWN) {
+- GAM_DEBUG(DEBUG_INFO, "inotify: Not handling event %d\n", event->mask);
+- g_free (fullpath);
+- return;
+- }
+-
+- GAM_DEBUG(DEBUG_INFO, "inotify: Emitting %s on %s\n", gam_event_to_string (gevent), fullpath);
+- gam_server_emit_one_event (fullpath, is_dir_node, gevent, sub, force);
+- g_free(fullpath);
+-}
+-
+-static void
+-gam_inotify_emit_events (inotify_data_t *data, inotify_event_t *event)
+-{
+- GList *l;
+-
+- if (!data||!event)
+- return;
+-
+- for (l = data->subs; l; l = l->next) {
+- GamSubscription *sub = l->data;
+- gam_inotify_emit_one_event (data, event, sub);
+- }
+-}
+-
+-static void
+-gam_inotify_process_event (inotify_event_t *event)
+-{
+- inotify_data_t *data = NULL;
+-
+- data = g_hash_table_lookup (wd_hash, GINT_TO_POINTER(event->wd));
+-
+- if (!data)
+- {
+- GAM_DEBUG (DEBUG_INFO, "inotify: got %s event for unknown wd %d\n", mask_to_string (event->mask), event->wd);
+- return;
+- }
+-
+- if (data->deactivated)
+- {
+- GAM_DEBUG (DEBUG_INFO, "inotify: ignoring event on temporarily deactivated watch %s\n", data->path);
+- data->deactivated_events++;
+- return;
+- }
+-
+- if (data->ignored) {
+- GAM_DEBUG (DEBUG_INFO, "inotify: got event on ignored watch %s\n", data->path);
+- data->ignored_events++;
+- return;
+- }
+-
+- if (event->mask & IN_IGNORED)
+- {
+- data->ignored = TRUE;
+- data->ignored_events++;
+- return;
+- }
+-
+- if (event->mask & IN_DELETE_SELF || event->mask & IN_MOVE_SELF)
+- {
+- GAM_DEBUG (DEBUG_INFO, "inotify: resource %s went away. Adding it to missing list\n", data->path);
+- /* Remove the wd from the hash table */
+- g_hash_table_remove (wd_hash, GINT_TO_POINTER(data->wd));
+-#ifdef GAMIN_DEBUG_API
+- gam_debug_report(GAMDnotifyDelete, data->path, 0);
+-#endif
+- /* Send delete event */
+- gam_inotify_emit_events (data, event);
+- data->events++;
+- /* Set state bits in struct */
+- data->wd = GAM_INOTIFY_WD_MISSING;
+- data->missing = TRUE;
+- data->permission = FALSE;
+- data->dir = FALSE;
+- /* Add path to missing list */
+- gam_inotify_add_missing (data->path, FALSE);
+- return;
+- }
+-
+- if (event->mask & GAM_INOTIFY_MASK)
+- {
+- GAM_DEBUG (DEBUG_INFO, "inotify: got %s on = %s/%s\n", mask_to_string (event->mask), data->path, event->name);
+- gam_inotify_emit_events (data, event);
+- data->events++;
+- return;
+- }
+-
+- if (event->mask & IN_Q_OVERFLOW)
+- {
+- /* At this point we have missed some events, and no longer have a consistent
+- * view of the filesystem.
+- */
+- // XXX: Kill server and hope for the best?
+- // XXX: Or we could send_initial_events , does this work for FAM?
+- GAM_DEBUG (DEBUG_INFO, "inotify: DANGER, queue over flowed! Events have been missed.\n");
+- return;
+- }
+-
+- GAM_DEBUG(DEBUG_INFO, "inotify: error event->mask = %d\n", event->mask);
+-}
+-
+-static void
+-gam_inotify_pair_moves (gpointer data, gpointer user_data)
+-{
+- inotify_event_t *event = (inotify_event_t *)data;
+-
+- if (event->seen == TRUE || event->sent == TRUE)
+- return;
++ GDir *dir;
++ GError *err = NULL;
++ dir = g_dir_open (pathname, 0, &err);
++ if (dir)
++ {
++ const char *filename;
+
+- if (event->cookie != 0)
+- {
+- if (event->mask & IN_MOVED_FROM) {
+- g_hash_table_insert (cookie_hash, GINT_TO_POINTER(event->cookie), event);
+- gam_inotify_event_add_microseconds (event, MOVE_HOLD_UNTIL_TIME);
+- } else if (event->mask & IN_MOVED_TO) {
+- inotify_event_t *match = NULL;
+- match = g_hash_table_lookup (cookie_hash, GINT_TO_POINTER(event->cookie));
+- if (match) {
+- g_hash_table_remove (cookie_hash, GINT_TO_POINTER(event->cookie));
+- gam_inotify_event_pair_with (match, event);
++ while ((filename = g_dir_read_name (dir)))
++ {
++ gchar *fullname = g_strdup_printf ("%s/%s", pathname, filename);
++ gboolean file_is_dir = FALSE;
++ struct stat fsb;
++ memset(&fsb, 0, sizeof (struct stat));
++ lstat(fullname, &fsb);
++ file_is_dir = (fsb.st_mode & S_IFDIR) != 0 ? TRUE : FALSE;
++ gam_server_emit_one_event (fullname, file_is_dir ? 1 : 0, gevent, sub, 1);
++ g_free (fullname);
+ }
+- }
+- }
+- event->seen = TRUE;
+-}
+-
+-static void
+-gam_inotify_process_internal ()
+-{
+- int ecount = 0;
+- g_queue_foreach (events_to_process, gam_inotify_pair_moves, NULL);
+
+- while (!g_queue_is_empty (events_to_process))
+- {
+- inotify_event_t *event = g_queue_peek_head (events_to_process);
+-
+- if (!gam_inotify_event_ready (event)) {
+- GAM_DEBUG(DEBUG_INFO, "inotify: event not ready\n");
+- break;
+- }
+-
+- /* Pop it */
+- event = g_queue_pop_head (events_to_process);
+- /* This must have been sent as part of a MOVED_TO/MOVED_FROM */
+- if (event->sent)
+- continue;
+-
+- /* Check if this is a MOVED_FROM that is also sitting in the cookie_hash */
+- if (event->cookie && event->pair == NULL &&
+- g_hash_table_lookup (cookie_hash, GINT_TO_POINTER(event->cookie)))
+- {
+- g_hash_table_remove (cookie_hash, GINT_TO_POINTER(event->cookie));
+- event->sent = TRUE;
+- }
+-
+- g_queue_push_tail (event_queue, event);
+- ecount++;
+- if (event->pair) {
+- // if this event has a pair
+- event->pair->sent = TRUE;
+- g_queue_push_tail (event_queue, event->pair);
+- ecount++;
++ g_dir_close (dir);
++ } else {
++ GAM_DEBUG (DEBUG_INFO, "unable to open directory %s: %s\n", pathname, err->message);
++ g_error_free (err);
+ }
+
+ }
+- if (ecount)
+- GAM_DEBUG(DEBUG_INFO, "inotify: moved %d events to event queue\n", ecount);
+-}
+
+-static gboolean
+-gam_inotify_process_event_queue (gpointer data)
+-{
+- /* Try and move as many events to the event queue */
+- gam_inotify_process_internal ();
+-
+- /* Send the events on the event queue to gam clients */
+- while (!g_queue_is_empty (event_queue))
++ if (!was_missing)
+ {
+- inotify_event_t *event = g_queue_pop_head (event_queue);
+- g_assert (event);
+- gam_inotify_process_event (event);
+- gam_inotify_event_free (event);
++ gam_server_emit_one_event (pathname, is_dir ? 1 : 0, GAMIN_EVENT_ENDEXISTS, sub, 1);
+ }
+
+- return TRUE;
+-}
+-
+-static gboolean
+-gam_inotify_read_handler(gpointer user_data)
+-{
+- gchar *buffer;
+- gsize buffer_size, buffer_i, events;
+-
+- gam_inotify_read_events (&buffer_size, &buffer);
+-
+- buffer_i = 0;
+- events = 0;
+- while (buffer_i < buffer_size)
+- {
+- struct inotify_event *event;
+- gsize event_size;
+- event = (struct inotify_event *)&buffer[buffer_i];
+- event_size = sizeof(struct inotify_event) + event->len;
+- g_queue_push_tail (events_to_process, gam_inotify_event_new (event));
+- buffer_i += event_size;
+- events++;
+- }
+-
+- GAM_DEBUG(DEBUG_INFO, "inotify recieved %d events\n", events);
+- return TRUE;
+ }
+
+ static void
+-gam_inotify_send_initial_events (inotify_data_t *data, GamSubscription *sub)
++gam_inotify_event_callback (const char *fullpath, guint32 mask, void *subdata)
+ {
++ GamSubscription *sub = (GamSubscription *)subdata;
+ GaminEventType gevent;
+- gboolean is_dir = FALSE;
+- gboolean was_missing = data->missing;
+- gboolean was_permission = data->permission;
+- gboolean exists = FALSE;
+-#if 0
+- gboolean watching_dir_as_file = data->dir && !gam_subscription_is_dir (sub);
+-#endif
+- gboolean watching_file_as_dir = FALSE;
+-
+- struct stat sb;
+- memset(&sb, 0, sizeof (struct stat));
+-
+- exists = lstat (data->path, &sb) >= 0;
+- is_dir = (exists && (sb.st_mode & S_IFDIR) != 0) ? TRUE : FALSE;
+-
+- if (was_missing) {
+- GAM_DEBUG (DEBUG_INFO, "inotify: Sending initial events for %s -- WAS_MISSING\n", data->path);
+- } else if (was_permission) {
+- GAM_DEBUG (DEBUG_INFO, "inotify: Sending initial events for %s -- WAS_PERMISSION\n", data->path);
+- } else {
+- GAM_DEBUG (DEBUG_INFO, "inotify: Sending initial events for %s\n", data->path);
+- }
+-
+- if (data->wd >= 0)
+- watching_file_as_dir = !data->dir && gam_subscription_is_dir (sub);
+- else
+- watching_file_as_dir = FALSE;
+-
+- if (!watching_file_as_dir && exists)
+- {
+- gevent = was_permission ? GAMIN_EVENT_EXISTS : was_missing ? GAMIN_EVENT_CREATED : GAMIN_EVENT_EXISTS;
+-
+- gam_server_emit_one_event (data->path, is_dir ? 1 : 0, gevent, sub, 1);
+
+- if (is_dir)
+- {
+- GDir *dir;
+- GError *err = NULL;
+- dir = g_dir_open (data->path, 0, &err);
+- if (dir)
+- {
+- const char *filename;
+-
+- while ((filename = g_dir_read_name (dir)))
+- {
+- gchar *fullname = g_strdup_printf ("%s/%s", data->path, filename);
+- gboolean file_is_dir = FALSE;
+- struct stat fsb;
+- memset(&fsb, 0, sizeof (struct stat));
+- lstat(fullname, &fsb);
+- file_is_dir = (fsb.st_mode & S_IFDIR) != 0 ? TRUE : FALSE;
+- gam_server_emit_one_event (fullname, file_is_dir ? 1 : 0, gevent, sub, 1);
+- g_free (fullname);
+- }
+-
+- g_dir_close (dir);
+- } else {
+- GAM_DEBUG (DEBUG_INFO, "unable to open directory %s: %s\n", data->path, err->message);
+- g_error_free (err);
+- }
++ gevent = ih_mask_to_EventType (mask);
+
+- }
+-
+- if (!was_missing)
+- {
+- gam_server_emit_one_event (data->path, is_dir ? 1 : 0, GAMIN_EVENT_ENDEXISTS, sub, 1);
+- }
+-
+- } else {
+- gam_server_emit_one_event (data->path, is_dir ? 1 : 0, GAMIN_EVENT_DELETED, sub, 1);
+- gam_server_emit_one_event (data->path, is_dir ? 1 : 0, GAMIN_EVENT_ENDEXISTS, sub, 1);
+- }
++ gam_server_emit_one_event (fullpath, gam_subscription_is_dir (sub), gevent, sub, 1);
+ }
+
+ static void
+-gam_inotify_send_initial_events_all (inotify_data_t *data)
+-{
+- GList *l;
+-
+- if (!data)
+- return;
+-
+- for (l = data->subs; l; l = l->next) {
+- GamSubscription *sub = l->data;
+- gam_inotify_send_initial_events (data, sub);
+- }
+-
+-}
+-
+-/**
+- * Adds a subscription to be monitored.
+- *
+- * @param sub a #GamSubscription to be polled
+- * @returns TRUE if adding the subscription succeeded, FALSE otherwise
+- */
+-gboolean
+-gam_inotify_add_subscription(GamSubscription * sub)
+-{
+- const char *path = gam_subscription_get_path (sub);
+- inotify_data_t *data = g_hash_table_lookup (path_hash, path);
+- int wd, err;
+-
+-
+- if (data)
+- {
+- data->subs = g_list_prepend (data->subs, sub);
+- data->refcount++;
+- gam_inotify_send_initial_events (data, sub);
+-#ifdef GAMIN_DEBUG_API
+- gam_debug_report(GAMDnotifyChange, path, data->refcount);
+-#endif
+- gam_listener_add_subscription(gam_subscription_get_listener(sub), sub);
+- return TRUE;
+- }
+-
+- wd = gam_inotify_add_watch (path, GAM_INOTIFY_MASK, &err);
+- if (wd < 0) {
+- GAM_DEBUG (DEBUG_INFO, "inotify: could not add watch for %s\n", path);
+- if (err == EACCES) {
+- GAM_DEBUG (DEBUG_INFO, "inotify: adding %s to missing list PERM\n", path);
+- } else {
+- GAM_DEBUG (DEBUG_INFO, "inotify: adding %s to missing list MISSING\n", path);
+- }
+- data = gam_inotify_data_new (path, err == EACCES ? GAM_INOTIFY_WD_PERM : GAM_INOTIFY_WD_MISSING, FALSE);
+- gam_inotify_add_missing (path, err == EACCES ? TRUE : FALSE);
+- } else if (gam_inotify_is_link (path)) {
+- /* The file turned out to be a link, cancel the watch, and add it to the links list */
+- gam_inotify_rm_watch (path, wd);
+- GAM_DEBUG (DEBUG_INFO, "inotify: could not add watch for %s\n", path);
+- GAM_DEBUG (DEBUG_INFO, "inotify: adding %s to links list\n", path);
+- data = gam_inotify_data_new (path, GAM_INOTIFY_WD_LINK, FALSE);
+- gam_inotify_add_link (path);
+- } else {
+- struct stat sbuf;
+- memset(&sbuf, 0, sizeof (struct stat));
+- lstat (path, &sbuf);
+- /* Just in case,
+- * Clear this path off the missing list */
+- gam_inotify_rm_missing (path);
+- data = gam_inotify_data_new (path, wd, sbuf.st_mode & S_IFDIR);
+- g_hash_table_insert(wd_hash, GINT_TO_POINTER(data->wd), data);
+- }
+-
+-#ifdef GAMIN_DEBUG_API
+- gam_debug_report(GAMDnotifyCreate, path, 0);
+-#endif
+- gam_listener_add_subscription(gam_subscription_get_listener(sub), sub);
+-
+- g_hash_table_insert(path_hash, data->path, data);
+- data->subs = g_list_prepend (data->subs, sub);
+- gam_inotify_send_initial_events (data, sub);
+- return TRUE;
+-}
+-
+-/**
+- * Removes a subscription which was being monitored.
+- *
+- * @param sub a #GamSubscription to remove
+- * @returns TRUE if removing the subscription succeeded, FALSE otherwise
+- */
+-gboolean
+-gam_inotify_remove_subscription(GamSubscription * sub)
++gam_inotify_found_callback (const char *fullpath, void *subdata)
+ {
+- const char *path = gam_subscription_get_path (sub);
+- inotify_data_t *data = g_hash_table_lookup (path_hash, path);
+-
+- g_assert (g_list_find (data->subs, sub));
+-
+- data->subs = g_list_remove_all (data->subs, sub);
+- data->refcount--;
+- /* No one is watching this path anymore */
+- if (!data->subs && data->refcount == 0)
+- {
+- if (data->link)
+- {
+- g_assert (data->wd == GAM_INOTIFY_WD_LINK);
+- g_assert (data->missing == FALSE && data->permission == FALSE);
+- g_hash_table_remove (path_hash, data->path);
+- gam_inotify_rm_link (data->path);
+- } else if (data->missing) {
+- g_assert (data->wd == GAM_INOTIFY_WD_MISSING);
+- g_assert (data->link == FALSE && data->permission == FALSE);
+- g_hash_table_remove (path_hash, data->path);
+- gam_inotify_rm_missing (data->path);
+- } else if (data->permission) {
+- g_assert (data->wd == GAM_INOTIFY_WD_PERM);
+- g_assert (data->link == FALSE && data->missing == FALSE);
+- g_hash_table_remove (path_hash, data->path);
+- gam_inotify_rm_missing (data->path);
+- } else {
+- g_hash_table_remove (wd_hash, GINT_TO_POINTER(data->wd));
+- g_hash_table_remove (path_hash, data->path);
+- gam_inotify_rm_watch (data->path, data->wd);
+- }
+-#ifdef GAMIN_DEBUG_API
+- gam_debug_report(GAMDnotifyDelete, path, 0);
+-#endif
+- gam_inotify_data_free (data);
+- } else {
+-#ifdef GAMIN_DEBUG_API
+- gam_debug_report(GAMDnotifyChange, path, data->refcount);
+-#endif
+- }
+-
+- gam_subscription_cancel (sub);
++ GamSubscription *sub = (GamSubscription *)subdata;
+
+- return TRUE;
++ gam_inotify_send_initial_events (gam_subscription_get_path (sub), sub, gam_subscription_is_dir (sub), TRUE);
+ }
+
+-/**
+- * Stop monitoring all subscriptions for a given listener.
+- *
+- * @param listener a #GamListener
+- * @returns TRUE if removing the subscriptions succeeded, FALSE otherwise
+- */
+-gboolean
+-gam_inotify_remove_all_for(GamListener * listener)
+-{
+- GList *subs;
+- GList *l;
+- gboolean success = TRUE;
+-
+- subs = gam_listener_get_subscriptions(listener);
+-
+- for (l = subs; l != NULL; l = l->next)
+- if (!gam_inotify_remove_subscription(l->data))
+- success = FALSE;
+
+- g_list_free(subs);
+-
+- return success;
+-}
+-
+-/**
+- * Initializes the inotify backend. This must be called before
+- * any other functions in this module.
+- *
+- * @returns TRUE if initialization succeeded, FALSE otherwise
+- */
+ gboolean
+-gam_inotify_init(void)
++gam_inotify_init (void)
+ {
+- GSource *source;
+-
+- inotify_device_fd = inotify_init ();
+-
+- if (inotify_device_fd < 0) {
+- GAM_DEBUG(DEBUG_INFO, "Could not initialize inotify\n");
+- return FALSE;
+- }
+-
+- inotify_read_ioc = g_io_channel_unix_new(inotify_device_fd);
+-
+- g_io_channel_set_encoding(inotify_read_ioc, NULL, NULL);
+- g_io_channel_set_flags(inotify_read_ioc, G_IO_FLAG_NONBLOCK, NULL);
+-
+- source = g_io_create_watch(inotify_read_ioc,
+- G_IO_IN | G_IO_HUP | G_IO_ERR);
+- g_source_set_callback(source, gam_inotify_read_handler, NULL, NULL);
+- g_source_attach(source, NULL);
+- g_source_unref (source);
+- g_timeout_add (SCAN_MISSING_TIME, gam_inotify_scan_missing, NULL);
+- g_timeout_add (SCAN_LINKS_TIME, gam_inotify_scan_links, NULL);
+- g_timeout_add (PROCESS_EVENTS_TIME, gam_inotify_process_event_queue, NULL);
+-
+- path_hash = g_hash_table_new(g_str_hash, g_str_equal);
+- wd_hash = g_hash_table_new(g_direct_hash, g_direct_equal);
+- cookie_hash = g_hash_table_new(g_direct_hash, g_direct_equal);
+- event_queue = g_queue_new ();
+- events_to_process = g_queue_new ();
+-
+- gam_poll_basic_init ();
+- gam_server_install_kernel_hooks (GAMIN_K_INOTIFY2,
++ gam_server_install_kernel_hooks (GAMIN_K_INOTIFY2,
+ gam_inotify_add_subscription,
+ gam_inotify_remove_subscription,
+- gam_inotify_remove_all_for, NULL, NULL);
+-
+- GAM_DEBUG(DEBUG_INFO, "inotify backend initialized\n");
+-
+-
+- return TRUE;
+-}
+-
+-int gam_inotify_add_watch (const char *path, __u32 mask, int *err)
+-{
+- int wd = -1;
+-
+- g_assert (path != NULL);
+- g_assert (inotify_device_fd >= 0);
+-
+- wd = inotify_add_watch (inotify_device_fd, path, mask);
+-
+- if (wd < 0)
+- {
+- int e = errno;
+- GAM_DEBUG (DEBUG_INFO, "inotify: failed to add watch for %s\n", path);
+- GAM_DEBUG (DEBUG_INFO, "inotify: reason %d = %s\n", e, strerror (e));
+- if (err)
+- *err = e;
+- return wd;
+- }
+- else
+- {
+- GAM_DEBUG (DEBUG_INFO, "inotify: success adding watch for %s (wd = %d)\n", path, wd);
+- }
+-
+- g_assert (wd >= 0);
+-
+- return wd;
+-}
+-
+-int gam_inotify_rm_watch (const char *path, __u32 wd)
+-{
+- g_assert (wd >= 0);
+-
+- if (inotify_rm_watch (inotify_device_fd, wd) < 0)
+- {
+- int e = errno;
+- GAM_DEBUG (DEBUG_INFO, "inotify: failed to rm watch for %s (wd = %d)\n", path, wd);
+- GAM_DEBUG (DEBUG_INFO, "inotify: reason = %s\n", strerror (e));
+- return -1;
+- }
+- else
+- {
+- GAM_DEBUG (DEBUG_INFO, "inotify: success removing watch for %s (wd = %d)\n", path, wd);
+- }
+-
+- return 0;
+-}
+-
+-/* Code below based on beagle inotify glue code. I assume it was written by Robert Love */
+-#define MAX_PENDING_COUNT 5
+-#define PENDING_THRESHOLD(qsize) ((qsize) >> 1)
+-#define PENDING_MARGINAL_COST(p) ((unsigned int)(1 << (p)))
+-#define MAX_QUEUED_EVENTS 8192
+-#define AVERAGE_EVENT_SIZE sizeof (struct inotify_event) + 16
+-#define PENDING_PAUSE_MICROSECONDS 8000
+-
+-void gam_inotify_read_events (gsize *buffer_size_out, gchar **buffer_out)
+-{
+- static int prev_pending = 0, pending_count = 0;
+- static gchar *buffer = NULL;
+- static gsize buffer_size;
+-
+-
+- /* Initialize the buffer on our first read() */
+- if (buffer == NULL)
+- {
+- buffer_size = AVERAGE_EVENT_SIZE;
+- buffer_size *= MAX_QUEUED_EVENTS;
+- buffer = g_malloc (buffer_size);
+-
+- if (!buffer) {
+- *buffer_size_out = 0;
+- *buffer_out = NULL;
+- GAM_DEBUG (DEBUG_INFO, "inotify: could not allocate read buffer\n");
+- return;
+- }
+- }
+-
+- *buffer_size_out = 0;
+- *buffer_out = NULL;
+-
+- while (pending_count < MAX_PENDING_COUNT) {
+- unsigned int pending;
+-
+- if (ioctl (inotify_device_fd, FIONREAD, &pending) == -1)
+- break;
+-
+- pending /= AVERAGE_EVENT_SIZE;
+-
+- /* Don't wait if the number of pending events is too close
+- * to the maximum queue size.
+- */
+-
+- if (pending > PENDING_THRESHOLD (MAX_QUEUED_EVENTS))
+- break;
+-
+- /* With each successive iteration, the minimum rate for
+- * further sleep doubles. */
+-
+- if (pending-prev_pending < PENDING_MARGINAL_COST(pending_count))
+- break;
+-
+- prev_pending = pending;
+- pending_count++;
+-
+- /* We sleep for a bit and try again */
+- g_usleep (PENDING_PAUSE_MICROSECONDS);
+- }
+-
+- memset(buffer, 0, buffer_size);
+-
+- if (g_io_channel_read_chars (inotify_read_ioc, (char *)buffer, buffer_size, buffer_size_out, NULL) != G_IO_STATUS_NORMAL) {
+- GAM_DEBUG (DEBUG_INFO, "inotify: failed to read from buffer\n");
+- }
+- *buffer_out = buffer;
+-
+- prev_pending = 0;
+- pending_count = 0;
+-}
+-
+-gboolean gam_inotify_is_missing (const char *path)
+-{
+- struct stat sbuf;
+-
+- /* If the file doesn't exist, it is missing. */
+- if (lstat (path, &sbuf) < 0)
+- return TRUE;
+-
+- /* If we can't read the file, it is missing. */
+- if (access (path, R_OK) < 0)
+- return TRUE;
+-
+- return FALSE;
+-}
+-
+-static gint missing_list_compare (gconstpointer a, gconstpointer b)
+-{
+- const inotify_missing_t *missing = NULL;
++ gam_inotify_remove_all_for,
++ NULL, NULL);
+
+- g_assert (a);
+- g_assert (b);
+- missing = a;
+- g_assert (missing->path);
+-
+- return strcmp (missing->path, b);
+-}
+-
+-static void gam_inotify_add_missing (const char *path, gboolean perm)
+-{
+- inotify_missing_t *missing = NULL;
+-
+- g_assert (path);
+-
+- missing = g_new0 (inotify_missing_t, 1);
+-
+- g_assert (missing);
+-
+- missing->path = g_strdup (path);
+- missing->scan_interval = gam_fs_get_poll_timeout (path);
+- missing->last_scan_time = time (NULL);
+- missing->permission = perm;
+-
+- GAM_DEBUG (DEBUG_INFO, "inotify-missing: add - %s\n", path);
+-
+- missing_list = g_list_prepend (missing_list, missing);
+-}
+-
+-static void gam_inotify_rm_missing (const char *path)
+-{
+- GList *node = NULL;
+- inotify_missing_t *missing = NULL;
+-
+- g_assert (path && *path);
+-
+- node = g_list_find_custom (missing_list, path, missing_list_compare);
+-
+- if (!node)
+- return;
+-
+- GAM_DEBUG (DEBUG_INFO, "inotify-missing: rm - %s\n", path);
+- missing = node->data;
+- g_free (missing->path);
+- g_free (missing);
+-
+- missing_list = g_list_remove_link (missing_list, node);
+-}
+-
+-static gboolean gam_inotify_nolonger_missing (const char *path)
+-{
+- int wd = -1, err;
+- inotify_data_t *data = NULL;
+- struct stat sbuf;
+- memset(&sbuf, 0, sizeof (struct stat));
+-
+- data = g_hash_table_lookup (path_hash, path);
+- if (!data) {
+- GAM_DEBUG (DEBUG_INFO, "inotify: Could not find missing %s in hash table.\n", path);
+- return FALSE;
+- }
+-
+- g_assert ((data->missing == TRUE || data->permission == TRUE) && data->link == FALSE);
+-
+- wd = gam_inotify_add_watch (path, GAM_INOTIFY_MASK,&err);
+- if (wd < 0) {
+- /* Check if we don't have access to the new file */
+- if (err == EACCES)
+- {
+- data->wd = GAM_INOTIFY_WD_PERM;
+- data->permission = TRUE;
+- data->missing = FALSE;
+- } else {
+- data->wd = GAM_INOTIFY_WD_MISSING;
+- data->permission = FALSE;
+- data->missing = TRUE;
+- }
+- return FALSE;
+- } else if (gam_inotify_is_link (path)) {
+- GAM_DEBUG(DEBUG_INFO, "inotify: Missing resource %s exists now BUT IT IS A LINK\n", path);
+- /* XXX: See NOTE1 */
+- if (g_hash_table_lookup (wd_hash, GINT_TO_POINTER(wd)) == NULL)
+- gam_inotify_rm_watch (path, wd);
+- data->missing = FALSE;
+- data->permission = FALSE;
+- data->link = TRUE;
+- data->wd = GAM_INOTIFY_WD_LINK;
+- gam_inotify_add_link (path);
+- gam_inotify_send_initial_events_all (data);
+- return TRUE;
+- }
+-
+-
+- GAM_DEBUG(DEBUG_INFO, "inotify: Missing resource %s exists now\n", path);
+-
+- lstat (path, &sbuf);
+- data->dir = (sbuf.st_mode & S_IFDIR);
+- data->wd = wd;
+- g_hash_table_insert(wd_hash, GINT_TO_POINTER(data->wd), data);
+- gam_inotify_send_initial_events_all (data);
+- data->missing = FALSE;
+- data->permission = FALSE;
+-
+- return TRUE;
++ return ih_startup (gam_inotify_event_callback,
++ gam_inotify_found_callback);
+ }
+
+-/* This function is called once per second in the main loop*/
+-static gboolean gam_inotify_scan_missing (gpointer userdata)
++gboolean
++gam_inotify_add_subscription (GamSubscription *sub)
+ {
+- guint i;
++ ih_sub_t *isub = NULL;
++ isub = ih_sub_new (gam_subscription_get_path (sub), gam_subscription_is_dir (sub), 0, sub);
+
+- gam_inotify_sanity_check ();
+- /* We have to walk the list like this because entries might be removed while we walk the list */
+- for (i = 0; ; i++)
++ if (!ih_sub_add (isub))
+ {
+- inotify_missing_t *missing = g_list_nth_data (missing_list, i);
+-
+- if (!missing)
+- break;
+-
+- /* Not enough time has passed since the last scan */
+- if (time(NULL) - missing->last_scan_time < missing->scan_interval)
+- continue;
+-
+- missing->last_scan_time = time(NULL);
+- if (!gam_inotify_is_missing (missing->path))
+- {
+- if (gam_inotify_nolonger_missing (missing->path))
+- {
+-#ifdef GAMIN_DEBUG_API
+- gam_debug_report(GAMDnotifyCreate, missing->path, 0);
+-#endif
+- gam_inotify_rm_missing (missing->path);
+- }
+- }
+- }
+-
+- gam_inotify_sanity_check ();
+- return TRUE;
+-}
+-
+-
+-static gboolean
+-gam_inotify_is_link (const char *path)
+-{
+- struct stat sbuf;
+-
+- if (lstat(path, &sbuf) < 0)
+- return FALSE;
+-
+- return S_ISLNK(sbuf.st_mode) != 0;
+-}
+-
+-static gboolean
+-gam_inotify_nolonger_link (const char *path)
+-{
+- int wd = -1, err;
+- inotify_data_t *data = NULL;
+- struct stat sbuf;
+- memset(&sbuf, 0, sizeof (struct stat));
+-
+- GAM_DEBUG(DEBUG_INFO, "inotify: link resource %s no longer a link\n", path);
+- data = g_hash_table_lookup (path_hash, path);
+- if (!data) {
+- GAM_DEBUG (DEBUG_INFO, "inotify: Could not find link %s in hash table.\n", path);
++ ih_sub_free (isub);
+ return FALSE;
+ }
+
+- g_assert (data->link == TRUE && data->missing == FALSE && data->permission == FALSE);
+-
+- wd = gam_inotify_add_watch (path, GAM_INOTIFY_MASK, &err);
+- if (wd < 0) {
+- /* The file must not exist anymore, so we add it to the missing list */
+- data->link = FALSE;
+- /* Check if we don't have access to the new file */
+- if (err == EACCES)
+- {
+- data->wd = GAM_INOTIFY_WD_PERM;
+- data->permission = TRUE;
+- data->missing = FALSE;
+- } else {
+- data->wd = GAM_INOTIFY_WD_MISSING;
+- data->permission = FALSE;
+- data->missing = TRUE;
+- }
+-
+- gam_server_emit_event (path, data->dir, GAMIN_EVENT_DELETED, data->subs, 1);
+- gam_inotify_add_missing (path, data->permission);
+- return TRUE;
+- } else if (gam_inotify_is_link (path)) {
+- GAM_DEBUG(DEBUG_INFO, "inotify: Link resource %s re-appeared as a link...\n", path);
+- /* NOTE1: This is tricky, because inotify works on the inode level and
+- * we are dealing with a link, we can be watching the same inode
+- * from two different paths (the wd's will be the same). So,
+- * if the wd isn't in the hash table, we can remvoe the watch,
+- * otherwise we just leave the watch. This should probably be
+- * handled by ref counting
+- */
+- if (g_hash_table_lookup (wd_hash, GINT_TO_POINTER(wd)) == NULL)
+- gam_inotify_rm_watch (path, wd);
+- data->missing = FALSE;
+- data->permission = FALSE;
+- data->link = TRUE;
+- data->wd = GAM_INOTIFY_WD_LINK;
+- gam_inotify_send_initial_events_all (data);
+- return FALSE;
+- }
++ gam_inotify_send_initial_events (gam_subscription_get_path (sub), sub, gam_subscription_is_dir (sub), FALSE);
+
+- lstat (path, &sbuf);
+- data->dir = (sbuf.st_mode & S_IFDIR);
+- data->wd = wd;
+- g_hash_table_insert(wd_hash, GINT_TO_POINTER(data->wd), data);
+- gam_inotify_send_initial_events_all (data);
+- data->missing = FALSE;
+- data->permission = FALSE;
+ return TRUE;
+ }
+
+-static gint links_list_compare (gconstpointer a, gconstpointer b)
+-{
+- const inotify_links_t *links = NULL;
+-
+- g_assert (a);
+- g_assert (b);
+- links = a;
+- g_assert (links->path);
+-
+- return strcmp (links->path, b);
+-}
+-
+-static void
+-gam_inotify_add_link (const char *path)
+-{
+- inotify_links_t *links = NULL;
+- struct stat sbuf;
+-
+- g_assert (path);
+-
+- links = g_new0 (inotify_links_t, 1);
+-
+- g_assert (links);
+-
+- GAM_DEBUG (DEBUG_INFO, "inotify-link: add - %s\n", path);
+- links->path = g_strdup (path);
+- links->scan_interval = gam_fs_get_poll_timeout (path);
+- links->last_scan_time = 0;
+- lstat(path, &sbuf);
+- links->sbuf = sbuf;
+- links_list = g_list_prepend (links_list, links);
+-}
+-
+-static void
+-gam_inotify_rm_link (const char *path)
++static gboolean
++gam_inotify_remove_sub_pred (ih_sub_t *sub, void *callerdata)
+ {
+- GList *node = NULL;
+- inotify_links_t *links = NULL;
+-
+- g_assert (path && *path);
+-
+- node = g_list_find_custom (links_list, path, links_list_compare);
+-
+- if (!node)
+- return;
+-
+- GAM_DEBUG (DEBUG_INFO, "inotify-link: rm - %s\n", path);
+- links = node->data;
+- g_free (links->path);
+- g_free (links);
+-
+- links_list = g_list_remove_link (links_list, node);
+-
++ return sub->usersubdata == callerdata;
+ }
+
+-static gboolean
+-gam_inotify_scan_links (gpointer userdata)
++gboolean
++gam_inotify_remove_subscription (GamSubscription *sub)
+ {
+- guint i;
+-
+- gam_inotify_sanity_check ();
+- /* We have to walk the list like this because entries might be removed while we walk the list */
+- for (i = 0; ; i++)
+- {
+- inotify_links_t *links = g_list_nth_data (links_list, i);
+-
+- if (!links)
+- break;
+-
+- /* Not enough time has passed since the last scan */
+- if (time(NULL) - links->last_scan_time < links->scan_interval)
+- continue;
+-
+- links->last_scan_time = time(NULL);
+- if (!gam_inotify_is_link (links->path))
+- {
+- if (gam_inotify_nolonger_link (links->path))
+- {
+- gam_inotify_rm_link (links->path);
+- }
+- } else {
+- gam_inotify_poll_link (links);
+- }
+-
+- }
++ ih_sub_foreach_free (sub, gam_inotify_remove_sub_pred);
+
+- gam_inotify_sanity_check ();
+ return TRUE;
+ }
+
+ static gboolean
+-gam_inotify_stat_changed (struct stat sbuf1, struct stat sbuf2)
+-{
+-#ifdef ST_MTIM_NSEC
+- return ((sbuf1.st_mtim.tv_sec != sbuf2.st_mtim.tv_sec) ||
+- (sbuf1.st_mtim.tv_nsec != sbuf2.st_mtim.tv_nsec) ||
+- (sbuf1.st_size != sbuf2.st_size) ||
+- (sbuf1.st_ctim.tv_sec != sbuf2.st_ctim.tv_sec) ||
+- (sbuf1.st_ctim.tv_nsec != sbuf2.st_ctim.tv_nsec));
+-#else
+- return ((sbuf1.st_mtime != sbuf2.st_mtime) ||
+- (sbuf1.st_size != sbuf2.st_size) ||
+- (sbuf1.st_ctime != sbuf2.st_ctime));
+-#endif
+-}
+-
+-static void
+-gam_inotify_poll_link (inotify_links_t *links)
+-{
+- struct stat sbuf;
+- g_assert (links);
+-
+- /* Next time around, we will detect the deletion, and send the event */
+- if (lstat (links->path, &sbuf) < 0)
+- return;
+-
+- if (gam_inotify_stat_changed (sbuf, links->sbuf))
+- {
+- inotify_data_t *data = g_hash_table_lookup (path_hash, links->path);
+- g_assert (data);
+- gam_server_emit_event (data->path, data->dir, GAMIN_EVENT_CHANGED, data->subs, 1);
+- }
+-
+- links->sbuf = sbuf;
+-}
+-
+-static void
+-gam_inotify_wd_check (gpointer key, gpointer value, gpointer user_data)
+-{
+- gint wd = GPOINTER_TO_INT(key);
+- inotify_data_t *data = (inotify_data_t *)value;
+- if (wd < 0)
+- {
+- GAM_DEBUG (DEBUG_INFO, "inotify-sanity: FAILURE wd hash for %s key < 0\n", data->path);
+- }
+- if (data->wd < 0)
+- {
+- GAM_DEBUG (DEBUG_INFO, "inotify-sanity: FAILURE wd hash for %s value < 0\n", data->path);
+- }
+- if (data->wd != wd)
+- {
+- GAM_DEBUG (DEBUG_INFO, "inotify-sanity: FAILURE wd hash value & key don't match\n");
+- }
+-}
+-
+-static void
+-gam_inotify_wd_hash_sanity_check (void)
+-{
+- g_hash_table_foreach (wd_hash, gam_inotify_wd_check, NULL);
+-}
+-
+-static void
+-gam_inotify_missing_check (gpointer data, gpointer user_data)
++gam_inotify_remove_listener_pred (ih_sub_t *sub, void *callerdata)
+ {
+- inotify_missing_t *missing = data;
+- inotify_data_t *idata = NULL;
+-
+- if (!missing)
+- {
+- GAM_DEBUG (DEBUG_INFO, "inotify-sanity: Missing check called with NULL argument\n");
+- return;
+- }
+-
+- if (!missing->path)
+- {
+- GAM_DEBUG (DEBUG_INFO, "inotify-sanity: Missing entry missing path name\n");
+- return;
+- }
+-
+- idata = g_hash_table_lookup (path_hash, missing->path);
+-
+- if (!idata)
+- {
+- GAM_DEBUG (DEBUG_INFO, "inotify-sanity: Could not find %s in path hash table\n", missing->path);
+- return;
+- }
+-
+- if (idata->wd != GAM_INOTIFY_WD_MISSING && idata->wd != GAM_INOTIFY_WD_PERM)
+- {
+- GAM_DEBUG (DEBUG_INFO, "inotify-sanity: data->wd != GAM_INOTIFY_WD_(MISSING/PERM) for path in missing list\n");
+- return;
+- }
+-
+- if (idata->missing != TRUE && idata->permission != TRUE)
+- {
+- GAM_DEBUG (DEBUG_INFO, "inotify-sanity: data->missing/permission != TRUE for path in missing list\n");
+- return;
+- }
+-
+- if (idata->missing == TRUE && idata->wd != GAM_INOTIFY_WD_MISSING)
+- {
+- GAM_DEBUG (DEBUG_INFO, "inotify-sanity: data->missing == TRUE && idata->wd != GAM_INOTIFY_WD_MISSING\n");
+- return;
+- }
+-
+- if (idata->permission == TRUE && idata->wd != GAM_INOTIFY_WD_PERM)
+- {
+- GAM_DEBUG (DEBUG_INFO, "inotify-sanity: data->permission == TRUE && idata->wd != GAM_INOTIFY_WD_PERM\n");
+- return;
+- }
++ GamSubscription *gsub = (GamSubscription *)sub->usersubdata;
+
+- if (idata->wd == GAM_INOTIFY_WD_MISSING && idata->missing != TRUE)
+- {
+- GAM_DEBUG (DEBUG_INFO, "inotify-sanity: data->missing == FALSE && idata->wd == GAM_INOTIFY_WD_MISSING\n");
+- return;
+- }
+-
+- if (idata->wd == GAM_INOTIFY_WD_PERM && idata->permission != TRUE)
+- {
+- GAM_DEBUG (DEBUG_INFO, "inotify-sanity: data->permission != TRUE && idata->wd == GAM_INOTIFY_WD_PERM\n");
+- return;
+- }
++ return gam_subscription_get_listener (gsub) == callerdata;
+ }
+
+-static void
+-gam_inotify_missing_list_sanity_check (void)
++gboolean
++gam_inotify_remove_all_for (GamListener *listener)
+ {
+- g_list_foreach (missing_list, gam_inotify_missing_check, NULL);
+-}
+-
++ ih_sub_foreach_free (listener, gam_inotify_remove_listener_pred);
+
+-static void
+-gam_inotify_sanity_check (void)
+-{
+-#ifdef GAM_INOTIFY_SANITY
+- gam_inotify_wd_hash_sanity_check ();
+- gam_inotify_missing_list_sanity_check ();
+-#endif
++ return TRUE;
+ }
+
+-static gboolean
+-g_timeval_lt(GTimeVal *val1, GTimeVal *val2)
++void
++gam_inotify_debug (void)
+ {
+- if (val1->tv_sec < val2->tv_sec)
+- return TRUE;
+-
+- if (val1->tv_sec > val2->tv_sec)
+- return FALSE;
+-
+- /* val1->tv_sec == val2->tv_sec */
+- if (val1->tv_usec < val2->tv_usec)
+- return TRUE;
+-
+- return FALSE;
++ id_dump (NULL);
+ }
+
+-static gboolean
+-g_timeval_eq(GTimeVal *val1, GTimeVal *val2)
++gboolean
++gam_inotify_is_running (void)
+ {
+- return (val1->tv_sec == val2->tv_sec) && (val1->tv_usec == val2->tv_usec);
++ return ih_running ();
+ }
+-
+--- /dev/null 2006-08-28 15:22:40.902752500 +0200
++++ gamin-0.1.7/server/inotify-sub.c 2006-09-05 11:01:21.000000000 +0200
+@@ -0,0 +1,121 @@
++/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
++
++/* inotify-helper.c - Gnome VFS Monitor based on inotify.
++
++ Copyright (C) 2006 John McCutchan
++
++ The Gnome Library is free software; you can redistribute it and/or
++ modify it under the terms of the GNU Library General Public License as
++ published by the Free Software Foundation; either version 2 of the
++ License, or (at your option) any later version.
++
++ The Gnome Library is distributed in the hope that it will be useful,
++ but WITHOUT ANY WARRANTY; without even the implied warranty of
++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
++ Library General Public License for more details.
++
++ You should have received a copy of the GNU Library General Public
++ License along with the Gnome Library; see the file COPYING.LIB. If not,
++ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
++ Boston, MA 02111-1307, USA.
++
++ Authors:
++ John McCutchan <john@johnmccutchan.com>
++*/
++
++#include "config.h"
++#include <string.h>
++#include <glib.h>
++#include "gam_subscription.h"
++#include "inotify-sub.h"
++
++static gboolean is_debug_enabled = FALSE;
++#define IS_W if (is_debug_enabled) g_warning
++
++static void ih_sub_setup (ih_sub_t *sub);
++
++ih_sub_t *
++ih_sub_new (const char *pathname, gboolean is_dir, guint32 flags, void *userdata)
++{
++ ih_sub_t *sub = NULL;
++
++ sub = g_new0 (ih_sub_t, 1);
++ sub->usersubdata = userdata;
++ sub->is_dir = is_dir;
++ sub->extra_flags = flags;
++ sub->pathname = g_strdup (pathname);
++
++ IS_W("new subscription for %s being setup\n", sub->pathname);
++
++ ih_sub_setup (sub);
++ return sub;
++}
++
++void
++ih_sub_free (ih_sub_t *sub)
++{
++ if (sub->filename)
++ g_free (sub->filename);
++ if (sub->dirname)
++ g_free (sub->dirname);
++ g_free (sub->pathname);
++ g_free (sub);
++}
++
++static
++gchar *ih_sub_get_dirname (gchar *pathname)
++{
++ return g_path_get_dirname (pathname);
++}
++
++static
++gchar *ih_sub_get_filename (gchar *pathname)
++{
++ gchar *out;
++ // FIXME: return filename here
++ return out;
++}
++
++static
++void ih_sub_fix_dirname (ih_sub_t *sub)
++{
++ size_t len = 0;
++
++ g_assert (sub->dirname);
++
++ len = strlen (sub->dirname);
++
++ /* We need to strip a trailing slash
++ * to get the correct behaviour
++ * out of the kernel
++ */
++ if (sub->dirname[len] == '/')
++ sub->dirname[len] = '\0';
++}
++
++/*
++ * XXX: Currently we just follow the gnome vfs monitor type flags when
++ * deciding how to treat the path. In the future we could try
++ * and determine whether the path points to a directory or a file but
++ * that is racey.
++ */
++static void
++ih_sub_setup (ih_sub_t *sub)
++{
++ if (sub->is_dir)
++ {
++ sub->dirname = g_strdup (sub->pathname);
++ sub->filename = NULL;
++ } else {
++ sub->dirname = ih_sub_get_dirname (sub->pathname);
++ sub->filename = ih_sub_get_filename (sub->pathname);
++ }
++
++ ih_sub_fix_dirname (sub);
++
++ IS_W("sub->dirname = %s\n", sub->dirname);
++ if (sub->filename)
++ {
++ IS_W("sub->filename = %s\n", sub->filename);
++ }
++}
+--- /dev/null 2006-08-28 15:22:40.902752500 +0200
++++ gamin-0.1.7/server/inotify-helper.c 2006-09-05 11:01:21.000000000 +0200
+@@ -0,0 +1,234 @@
++/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
++
++/* inotify-helper.c - Gnome VFS Monitor based on inotify.
++
++ Copyright (C) 2005 John McCutchan
++
++ The Gnome Library is free software; you can redistribute it and/or
++ modify it under the terms of the GNU Library General Public License as
++ published by the Free Software Foundation; either version 2 of the
++ License, or (at your option) any later version.
++
++ The Gnome Library is distributed in the hope that it will be useful,
++ but WITHOUT ANY WARRANTY; without even the implied warranty of
++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
++ Library General Public License for more details.
++
++ You should have received a copy of the GNU Library General Public
++ License along with the Gnome Library; see the file COPYING.LIB. If not,
++ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
++ Boston, MA 02111-1307, USA.
++
++ Authors:
++ John McCutchan <john@johnmccutchan.com>
++*/
++
++#include "config.h"
++#include <errno.h>
++#include <time.h>
++#include <string.h>
++#include <sys/ioctl.h>
++/* Just include the local header to stop all the pain */
++#include "local_inotify.h"
++#if 0
++#ifdef HAVE_SYS_INOTIFY_H
++/* We don't actually include the libc header, because there has been
++ * problems with libc versions that was built without inotify support.
++ * Instead we use the local version.
++ */
++#include "local_inotify.h"
++#elif defined (HAVE_LINUX_INOTIFY_H)
++#include <linux/inotify.h>
++#endif
++#endif
++#include "inotify-helper.h"
++#include "inotify-missing.h"
++#include "inotify-path.h"
++#include "inotify-diag.h"
++
++static gboolean ih_debug_enabled = FALSE;
++#define IH_W if (ih_debug_enabled) g_warning
++
++static void ih_event_callback (ik_event_t *event, ih_sub_t *sub);
++static void ih_found_callback (ih_sub_t *sub);
++
++/* We share this lock with inotify-kernel.c and inotify-missing.c
++ *
++ * inotify-kernel.c takes the lock when it reads events from
++ * the kernel and when it processes those events
++ *
++ * inotify-missing.c takes the lock when it is scanning the missing
++ * list.
++ *
++ * We take the lock in all public functions
++ */
++G_LOCK_DEFINE (inotify_lock);
++static GList *sub_list = NULL;
++static gboolean initialized = FALSE;
++static event_callback_t user_ecb = NULL;
++static found_callback_t user_fcb = NULL;
++
++/**
++ * Initializes the inotify backend. This must be called before
++ * any other functions in this module.
++ *
++ * @returns TRUE if initialization succeeded, FALSE otherwise
++ */
++gboolean
++ih_startup (event_callback_t ecb,
++ found_callback_t fcb)
++{
++ static gboolean result = FALSE;
++
++ G_LOCK(inotify_lock);
++
++ if (initialized == TRUE) {
++ G_UNLOCK(inotify_lock);
++ return result;
++ }
++
++ initialized = TRUE;
++
++ result = ip_startup (ih_event_callback);
++ if (!result) {
++ g_warning( "Could not initialize inotify\n");
++ G_UNLOCK(inotify_lock);
++ return FALSE;
++ }
++ user_ecb = ecb;
++ user_fcb = fcb;
++ im_startup (ih_found_callback);
++ id_startup ();
++
++ IH_W ("started gnome-vfs inotify backend\n");
++
++ G_UNLOCK(inotify_lock);
++ return TRUE;
++}
++
++gboolean
++ih_running (void)
++{
++ return initialized;
++}
++
++/**
++ * Adds a subscription to be monitored.
++ */
++gboolean
++ih_sub_add (ih_sub_t * sub)
++{
++ G_LOCK(inotify_lock);
++
++ g_assert (g_list_find (sub_list, sub) == NULL);
++
++ // make sure that sub isn't on sub_list first.
++ if (!ip_start_watching (sub))
++ {
++ im_add (sub);
++ }
++
++ sub_list = g_list_prepend (sub_list, sub);
++
++ G_UNLOCK(inotify_lock);
++ return TRUE;
++}
++
++/**
++ * Cancels a subscription which was being monitored.
++ */
++gboolean
++ih_sub_cancel (ih_sub_t * sub)
++{
++ G_LOCK(inotify_lock);
++
++
++ if (!sub->cancelled)
++ {
++ IH_W("cancelling %s\n", sub->pathname);
++ g_assert (g_list_find (sub_list, sub) != NULL);
++ sub->cancelled = TRUE;
++ im_rm (sub);
++ ip_stop_watching (sub);
++ sub_list = g_list_remove (sub_list, sub);
++ }
++
++ G_UNLOCK(inotify_lock);
++ return TRUE;
++}
++
++static void
++ih_sub_foreach_worker (void *callerdata, gboolean (*f)(ih_sub_t *sub, void *callerdata), gboolean free)
++{
++ GList *l = NULL;
++ GList *removed = NULL;
++
++ G_LOCK(inotify_lock);
++
++ for (l = sub_list; l; l = l->next)
++ {
++ ih_sub_t *sub = l->data;
++
++ if (f(sub, callerdata))
++ {
++ removed = g_list_prepend (removed, l);
++ ih_sub_cancel (sub);
++ if (free)
++ ih_sub_free (sub);
++ }
++ }
++
++ for (l = removed; l ; l = l->next)
++ {
++ GList *llink = l->data;
++ sub_list = g_list_remove_link (sub_list, llink);
++ g_list_free_1 (llink);
++ }
++
++ G_UNLOCK(inotify_lock);
++}
++
++void
++ih_sub_foreach (void *callerdata, gboolean (*f)(ih_sub_t *sub, void *callerdata))
++{
++ ih_sub_foreach_worker (callerdata, f, FALSE);
++}
++
++void
++ih_sub_foreach_free (void *callerdata, gboolean (*f)(ih_sub_t *sub, void *callerdata))
++{
++ ih_sub_foreach_worker (callerdata, f, TRUE);
++}
++
++static void ih_event_callback (ik_event_t *event, ih_sub_t *sub)
++{
++ gchar *fullpath;
++ if (event->name)
++ {
++ fullpath = g_strdup_printf ("%s/%s", sub->dirname, event->name);
++ } else {
++ fullpath = g_strdup_printf ("%s/", sub->dirname);
++ }
++
++ user_ecb (fullpath, event->mask, sub->usersubdata);
++ g_free(fullpath);
++}
++
++static void ih_found_callback (ih_sub_t *sub)
++{
++ gchar *fullpath;
++
++ if (sub->filename)
++ {
++ fullpath = g_strdup_printf ("%s/%s", sub->dirname, sub->filename);
++ if (!g_file_test (fullpath, G_FILE_TEST_EXISTS)) {
++ g_free (fullpath);
++ return;
++ }
++ } else {
++ fullpath = g_strdup_printf ("%s/", sub->dirname);
++ }
++
++ user_fcb (fullpath, sub->usersubdata);
++ g_free(fullpath);
++}
+--- /dev/null 2006-08-28 15:22:40.902752500 +0200
++++ gamin-0.1.7/server/inotify-diag.c 2006-09-05 11:01:21.000000000 +0200
+@@ -0,0 +1,67 @@
++/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
++
++/* inotify-helper.c - Gnome VFS Monitor based on inotify.
++
++ Copyright (C) 2005 John McCutchan
++
++ The Gnome Library is free software; you can redistribute it and/or
++ modify it under the terms of the GNU Library General Public License as
++ published by the Free Software Foundation; either version 2 of the
++ License, or (at your option) any later version.
++
++ The Gnome Library is distributed in the hope that it will be useful,
++ but WITHOUT ANY WARRANTY; without even the implied warranty of
++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
++ Library General Public License for more details.
++
++ You should have received a copy of the GNU Library General Public
++ License along with the Gnome Library; see the file COPYING.LIB. If not,
++ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
++ Boston, MA 02111-1307, USA.
++
++ Authors:
++ John McCutchan <john@johnmccutchan.com>
++*/
++
++#include "config.h"
++#include <glib.h>
++#include <sys/types.h>
++#include <unistd.h>
++#include "inotify-missing.h"
++#include "inotify-path.h"
++#include "inotify-diag.h"
++
++#define DIAG_DUMP_TIME 20000 /* 20 seconds */
++G_LOCK_EXTERN (inotify_lock);
++
++gboolean id_dump (gpointer userdata)
++{
++ G_LOCK (inotify_lock);
++ GIOChannel *ioc = NULL;
++ pid_t pid = getpid();
++ char *fname = g_strdup_printf("/tmp/gvfsid.%d", pid);
++ ioc = g_io_channel_new_file (fname, "w", NULL);
++ g_free (fname);
++ if (!ioc)
++ {
++ G_UNLOCK (inotify_lock);
++ return TRUE;
++ }
++
++ im_diag_dump (ioc);
++
++ g_io_channel_shutdown (ioc, TRUE, NULL);
++ g_io_channel_unref (ioc);
++ G_UNLOCK (inotify_lock);
++ return TRUE;
++}
++
++void id_startup ()
++{
++ if (!g_getenv ("GNOME_VFS_INOTIFY_DIAG"))
++ {
++ return;
++ }
++
++ g_timeout_add (DIAG_DUMP_TIME, id_dump, NULL);
++}
+--- /dev/null 2006-08-28 15:22:40.902752500 +0200
++++ gamin-0.1.7/server/inotify-path.h 2006-09-05 11:01:21.000000000 +0200
+@@ -0,0 +1,27 @@
++/*
++ Copyright (C) 2006 John McCutchan <john@johnmccutchan.com>
++
++ This program is free software; you can redistribute it and/or modify
++ it under the terms of the GNU General Public License as published by
++ the Free Software Foundation; version 2.
++
++ This program is distributed in the hope that it will be useful,
++ but WITHOUT ANY WARRANTY; without even the implied warranty of
++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ GNU General Public License version 2 for more details.
++ You should have received a copy of the GNU General Public License
++ along with this program; if not, write to the Free Software Foundation,
++ Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
++*/
++
++#ifndef __INOTIFY_PATH_H
++#define __INOTIFY_PATH_H
++
++#include "inotify-kernel.h"
++#include "inotify-sub.h"
++
++gboolean ip_startup (void (*event_cb)(ik_event_t *event, ih_sub_t *sub));
++gboolean ip_start_watching (ih_sub_t *sub);
++gboolean ip_stop_watching (ih_sub_t *sub);
++
++#endif
+--- /dev/null 2006-08-28 15:22:40.902752500 +0200
++++ gamin-0.1.7/server/inotify-path.c 2006-09-05 11:01:21.000000000 +0200
+@@ -0,0 +1,387 @@
++/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
++
++/* inotify-helper.c - Gnome VFS Monitor based on inotify.
++
++ Copyright (C) 2006 John McCutchan
++
++ The Gnome Library is free software; you can redistribute it and/or
++ modify it under the terms of the GNU Library General Public License as
++ published by the Free Software Foundation; either version 2 of the
++ License, or (at your option) any later version.
++
++ The Gnome Library is distributed in the hope that it will be useful,
++ but WITHOUT ANY WARRANTY; without even the implied warranty of
++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
++ Library General Public License for more details.
++
++ You should have received a copy of the GNU Library General Public
++ License along with the Gnome Library; see the file COPYING.LIB. If not,
++ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
++ Boston, MA 02111-1307, USA.
++
++ Authors:
++ John McCutchan <john@johnmccutchan.com>
++*/
++
++#include "config.h"
++
++/* Don't put conflicting kernel types in the global namespace: */
++#define __KERNEL_STRICT_NAMES
++
++#include "local_inotify.h"
++#if 0
++#ifdef HAVE_SYS_INOTIFY_H
++/* We don't actually include the libc header, because there has been
++ * problems with libc versions that was built without inotify support.
++ * Instead we use the local version.
++ */
++#include "local_inotify.h"
++#elif defined (HAVE_LINUX_INOTIFY_H)
++#include <linux/inotify.h>
++#endif
++#endif
++#include <string.h>
++#include <glib.h>
++#include "inotify-kernel.h"
++#include "inotify-path.h"
++#include "inotify-missing.h"
++
++#define IP_INOTIFY_MASK (IN_MODIFY|IN_ATTRIB|IN_MOVED_FROM|IN_MOVED_TO|IN_DELETE|IN_CREATE|IN_DELETE_SELF|IN_UNMOUNT|IN_MOVE_SELF)
++
++typedef struct ip_watched_dir_s {
++ char *path;
++ /* TODO: We need to maintain a tree of watched directories
++ * so that we can deliver move/delete events to sub folders.
++ * Or the application could do it...
++ */
++ struct ip_watched_dir_s *parent;
++ GList * children;
++
++ /* Inotify state */
++ gint32 wd;
++
++ /* List of inotify subscriptions */
++ GList *subs;
++} ip_watched_dir_t;
++
++static gboolean ip_debug_enabled = FALSE;
++#define IP_W if (ip_debug_enabled) g_warning
++
++/* path -> ip_watched_dir */
++static GHashTable * path_dir_hash = NULL;
++/* ih_sub_t * -> ip_watched_dir *
++ *
++ * Each subscription is attached to a watched directory or it is on
++ * the missing list
++ */
++static GHashTable * sub_dir_hash = NULL;
++/* This hash holds GLists of ip_watched_dir_t *'s
++ * We need to hold a list because symbolic links can share
++ * the same wd
++ */
++static GHashTable * wd_dir_hash = NULL;
++
++static ip_watched_dir_t * ip_watched_dir_new (const char *path, int wd);
++static void ip_watched_dir_free (ip_watched_dir_t *dir);
++static void ip_event_callback (ik_event_t *event);
++
++static void (*event_callback)(ik_event_t *event, ih_sub_t *sub);
++
++gboolean ip_startup (void (*cb)(ik_event_t *event, ih_sub_t *sub))
++{
++ static gboolean initialized = FALSE;
++ static gboolean result = FALSE;
++
++ if (initialized == TRUE) {
++ return result;
++ }
++
++ initialized = TRUE;
++ event_callback = cb;
++ result = ik_startup (ip_event_callback);
++
++ if (!result) {
++ return FALSE;
++ }
++
++ path_dir_hash = g_hash_table_new(g_str_hash, g_str_equal);
++ sub_dir_hash = g_hash_table_new(g_direct_hash, g_direct_equal);
++ wd_dir_hash = g_hash_table_new(g_direct_hash, g_direct_equal);
++
++ return TRUE;
++}
++
++static void
++ip_map_path_dir (const char *path, ip_watched_dir_t *dir)
++{
++ g_assert (path && dir);
++ g_hash_table_insert(path_dir_hash, dir->path, dir);
++}
++
++static void
++ip_map_sub_dir (ih_sub_t *sub, ip_watched_dir_t *dir)
++{
++ /* Associate subscription and directory */
++ g_assert (dir && sub);
++ g_hash_table_insert (sub_dir_hash, sub, dir);
++ dir->subs = g_list_prepend (dir->subs, sub);
++}
++
++static void
++ip_map_wd_dir (gint32 wd, ip_watched_dir_t *dir)
++{
++ g_assert (wd >= 0 && dir);
++ GList *dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER(wd));
++ dir_list = g_list_prepend (dir_list, dir);
++ g_hash_table_replace(wd_dir_hash, GINT_TO_POINTER(dir->wd), dir_list);
++}
++
++gboolean ip_start_watching (ih_sub_t *sub)
++{
++ gint32 wd;
++ int err;
++ ip_watched_dir_t *dir;
++
++ g_assert (sub);
++ g_assert (!sub->cancelled);
++ g_assert (sub->dirname);
++
++ IP_W("Starting to watch %s\n", sub->dirname);
++ dir = g_hash_table_lookup (path_dir_hash, sub->dirname);
++ if (dir)
++ {
++ IP_W("Already watching\n");
++ goto out;
++ }
++
++ IP_W("Trying to add inotify watch ");
++ wd = ik_watch (sub->dirname, IP_INOTIFY_MASK|IN_ONLYDIR|sub->extra_flags, &err);
++ if (wd < 0)
++ {
++ IP_W("Failed\n");
++ return FALSE;
++ } else {
++ /* Create new watched directory and associate it with the
++ * wd hash and path hash
++ */
++ IP_W("Success\n");
++ dir = ip_watched_dir_new (sub->dirname, wd);
++ ip_map_wd_dir (wd, dir);
++ ip_map_path_dir (sub->dirname, dir);
++ }
++
++out:
++ ip_map_sub_dir (sub, dir);
++
++ return TRUE;
++}
++
++static void
++ip_unmap_path_dir (const char *path, ip_watched_dir_t *dir)
++{
++ g_assert (path && dir);
++ g_hash_table_remove (path_dir_hash, dir->path);
++}
++
++static void
++ip_unmap_wd_dir (gint32 wd, ip_watched_dir_t *dir)
++{
++ GList *dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER(wd));
++ if (!dir_list)
++ return;
++ g_assert (wd >= 0 && dir);
++ dir_list = g_list_remove (dir_list, dir);
++ if (dir_list == NULL) {
++ g_hash_table_remove (wd_dir_hash, GINT_TO_POINTER(dir->wd));
++ } else {
++ g_hash_table_replace (wd_dir_hash, GINT_TO_POINTER(dir->wd), dir_list);
++ }
++}
++
++static void
++ip_unmap_wd (gint32 wd)
++{
++ GList *dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER(wd));
++ if (!dir_list)
++ return;
++ g_assert (wd >= 0);
++ g_hash_table_remove (wd_dir_hash, GINT_TO_POINTER(wd));
++ g_list_free (dir_list);
++}
++
++static void
++ip_unmap_sub_dir (ih_sub_t *sub, ip_watched_dir_t *dir)
++{
++ g_assert (sub && dir);
++ g_hash_table_remove (sub_dir_hash, sub);
++ dir->subs = g_list_remove (dir->subs, sub);
++}
++
++static void
++ip_unmap_all_subs (ip_watched_dir_t *dir)
++{
++ GList *l = NULL;
++
++ for (l = dir->subs; l; l = l->next)
++ {
++ ih_sub_t *sub = l->data;
++ g_hash_table_remove (sub_dir_hash, sub);
++ }
++ g_list_free (dir->subs);
++ dir->subs = NULL;
++}
++
++gboolean ip_stop_watching (ih_sub_t *sub)
++{
++ ip_watched_dir_t *dir = NULL;
++
++ dir = g_hash_table_lookup (sub_dir_hash, sub);
++ if (!dir) {
++ return TRUE;
++ }
++
++ ip_unmap_sub_dir (sub, dir);
++
++ /* No one is subscribing to this directory any more */
++ if (dir->subs == NULL) {
++ ik_ignore (dir->path, dir->wd);
++ ip_unmap_wd_dir (dir->wd, dir);
++ ip_unmap_path_dir (dir->path, dir);
++ ip_watched_dir_free (dir);
++ }
++
++ return TRUE;
++}
++
++
++static ip_watched_dir_t *
++ip_watched_dir_new (const char *path, gint32 wd)
++{
++ ip_watched_dir_t *dir = g_new0(ip_watched_dir_t, 1);
++
++ dir->path = g_strdup(path);
++ dir->wd = wd;
++
++ return dir;
++}
++
++static void
++ip_watched_dir_free (ip_watched_dir_t * dir)
++{
++ g_assert (dir->subs == 0);
++ g_free(dir->path);
++ g_free(dir);
++}
++
++static void ip_wd_delete (gpointer data, gpointer user_data)
++{
++ ip_watched_dir_t *dir = data;
++ GList *l = NULL;
++
++ for (l = dir->subs; l; l = l->next)
++ {
++ ih_sub_t *sub = l->data;
++
++ /* Add subscription to missing list */
++ im_add (sub);
++ }
++ ip_unmap_all_subs (dir);
++ /* Unassociate the path and the directory */
++ ip_unmap_path_dir (dir->path, dir);
++ ip_watched_dir_free (dir);
++}
++
++static void ip_event_dispatch (GList *dir_list, GList *pair_dir_list, ik_event_t *event)
++{
++ GList *dirl;
++
++ if (!event)
++ return;
++
++ /* TODO:
++ *
++ * Figure out how we will deliver move events
++ */
++ for (dirl = dir_list; dirl; dirl = dirl->next)
++ {
++ GList *subl;
++ ip_watched_dir_t *dir = dirl->data;
++
++ for (subl = dir->subs; subl; subl = subl->next)
++ {
++ ih_sub_t *sub = subl->data;
++
++ /* If the event and the subscription have a filename
++ * they need to match before the event could be delivered.
++ */
++ if (event->name && sub->filename) {
++ if (strcmp (event->name, sub->filename))
++ continue;
++ /* If the event doesn't have a filename, but the subscription does
++ * we shouldn't deliever the event */
++ } else if (sub->filename)
++ continue;
++
++ event_callback (event, sub);
++ }
++ }
++
++ if (!event->pair)
++ return;
++
++ for (dirl = pair_dir_list; dirl; dirl = dirl->next)
++ {
++ GList *subl;
++ ip_watched_dir_t *dir = dirl->data;
++
++ for (subl = dir->subs; subl; subl = subl->next)
++ {
++ ih_sub_t *sub = subl->data;
++
++ /* If the event and the subscription have a filename
++ * they need to match before the event could be delivered.
++ */
++ if (event->pair->name && sub->filename) {
++ if (strcmp (event->pair->name, sub->filename))
++ continue;
++ /* If the event doesn't have a filename, but the subscription does
++ * we shouldn't deliever the event */
++ } else if (sub->filename)
++ continue;
++
++ event_callback (event->pair, sub);
++ }
++ }
++}
++
++static void
++ip_event_callback (ik_event_t *event)
++{
++ GList *dir_list = NULL;
++ GList *pair_dir_list = NULL;
++
++ dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER(event->wd));
++
++ /* We can ignore IN_IGNORED events */
++ if (event->mask & IN_IGNORED) {
++ ik_event_free (event);
++ return;
++ }
++
++ if (event->pair)
++ pair_dir_list = g_hash_table_lookup (wd_dir_hash, GINT_TO_POINTER(event->pair->wd));
++
++ if (event->mask & IP_INOTIFY_MASK)
++ ip_event_dispatch (dir_list, pair_dir_list, event);
++
++ /* We have to manage the missing list when we get a DELETE event. */
++ if (event->mask & IN_DELETE_SELF || event->mask & IN_MOVE_SELF)
++ {
++ /* Add all subscriptions to missing list */
++ g_list_foreach (dir_list, ip_wd_delete, NULL);
++ /* Unmap all directories attached to this wd */
++ ip_unmap_wd (event->wd);
++ }
++
++ ik_event_free (event);
++}
+--- /dev/null 2006-08-28 15:22:40.902752500 +0200
++++ gamin-0.1.7/server/inotify-helper.h 2006-09-05 11:01:21.000000000 +0200
+@@ -0,0 +1,45 @@
++/* inotify-helper.h - GNOME VFS Monitor using inotify
++
++ Copyright (C) 2005 John McCutchan
++
++ The Gnome Library is free software; you can redistribute it and/or
++ modify it under the terms of the GNU Library General Public License as
++ published by the Free Software Foundation; either version 2 of the
++ License, or (at your option) any later version.
++
++ The Gnome Library is distributed in the hope that it will be useful,
++ but WITHOUT ANY WARRANTY; without even the implied warranty of
++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
++ Library General Public License for more details.
++
++ You should have received a copy of the GNU Library General Public
++ License along with the Gnome Library; see the file COPYING.LIB. If not,
++ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
++ Boston, MA 02111-1307, USA.
++
++ Author: John McCutchan <john@johnmccutchan.com>
++*/
++
++
++#ifndef __INOTIFY_HELPER_H
++#define __INOTIFY_HELPER_H
++
++#include "inotify-sub.h"
++#include "inotify-kernel.h"
++
++typedef void (*event_callback_t)(const char *fullpath, guint32 mask, void *subdata);
++typedef void (*found_callback_t)(const char *fullpath, void *subdata);
++
++gboolean ih_startup (event_callback_t ecb,
++ found_callback_t fcb);
++gboolean ih_running (void);
++gboolean ih_sub_add (ih_sub_t *sub);
++gboolean ih_sub_cancel (ih_sub_t *sub);
++
++/* Return FALSE from 'f' if the subscription should be cancelled */
++void ih_sub_foreach (void *callerdata, gboolean (*f)(ih_sub_t *sub, void *callerdata));
++
++/* Return FALSE from 'f' if the subscription should be cancelled and free'd */
++void ih_sub_foreach_free (void *callerdata, gboolean (*f)(ih_sub_t *sub, void *callerdata));
++
++#endif /* __INOTIFY_HELPER_H */
+--- /dev/null 2006-08-28 15:22:40.902752500 +0200
++++ gamin-0.1.7/server/inotify-sub.h 2006-09-05 11:01:21.000000000 +0200
+@@ -0,0 +1,42 @@
++/* inotify-helper.h - GNOME VFS Monitor using inotify
++
++ Copyright (C) 2006 John McCutchan
++
++ The Gnome Library is free software; you can redistribute it and/or
++ modify it under the terms of the GNU Library General Public License as
++ published by the Free Software Foundation; either version 2 of the
++ License, or (at your option) any later version.
++
++ The Gnome Library is distributed in the hope that it will be useful,
++ but WITHOUT ANY WARRANTY; without even the implied warranty of
++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
++ Library General Public License for more details.
++
++ You should have received a copy of the GNU Library General Public
++ License along with the Gnome Library; see the file COPYING.LIB. If not,
++ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
++ Boston, MA 02111-1307, USA.
++
++ Author: John McCutchan <john@johnmccutchan.com>
++*/
++
++
++#ifndef __INOTIFY_SUB_H
++#define __INOTIFY_SUB_H
++
++#include "gam_subscription.h"
++
++typedef struct {
++ gboolean is_dir;
++ char *pathname;
++ char *dirname;
++ char *filename;
++ guint32 extra_flags;
++ gboolean cancelled;
++ void *usersubdata;
++} ih_sub_t;
++
++ih_sub_t *ih_sub_new (const char *pathname, gboolean is_dir, guint32 flags, void *userdata);
++void ih_sub_free (ih_sub_t *sub);
++
++#endif /* __INOTIFY_SUB_H */
+--- gamin-0.1.7/server/local_inotify.h.new-inotify-backend 2005-08-17 15:50:04.000000000 +0200
++++ gamin-0.1.7/server/local_inotify.h 2006-09-05 11:01:21.000000000 +0200
+@@ -47,6 +47,9 @@
+ #define IN_MOVE (IN_MOVED_FROM | IN_MOVED_TO) /* moves */
+
+ /* special flags */
++#define IN_ONLYDIR 0x01000000 /* only watch the path if it is a directory */
++#define IN_DONT_FOLLOW 0x02000000 /* don't follow a sym link */
++#define IN_MASK_ADD 0x20000000 /* add to the mask of an already existing watch */
+ #define IN_ISDIR 0x40000000 /* event occurred against dir */
+ #define IN_ONESHOT 0x80000000 /* only send event once */
+