--- /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 */