gamin inotify patches



Here is an inotify backend for gamin. I have done some basic testing and
it appears to work.

Attached are:
A build fix for gamin CVS
A patch for the inotify backend
gam_inotify.[ch] which goes under the server directory in gamin.


To build gamin with the inotify backend just pass '--enable-inotify' to
configure. I would really appreciate some testing.

John
Index: libgamin/fam.h
===================================================================
RCS file: /cvs/gnome/gamin/libgamin/fam.h,v
retrieving revision 1.3
diff -u -r1.3 fam.h
--- libgamin/fam.h	17 Jul 2004 23:40:16 -0000	1.3
+++ libgamin/fam.h	19 Jul 2004 22:04:55 -0000
@@ -80,7 +80,7 @@
  * The FAMCodes indicates what kind of event happened that raised
  * the callback at the application level.
  */
-enum {
+typedef enum {
     FAMChanged=1,
     FAMDeleted=2,
     FAMStartExecuting=3,
Index: configure.in
===================================================================
RCS file: /cvs/gnome/gamin/configure.in,v
retrieving revision 1.2
diff -u -r1.2 configure.in
--- configure.in	18 Jun 2004 12:14:52 -0000	1.2
+++ configure.in	20 Jul 2004 00:02:59 -0000
@@ -164,6 +164,18 @@
 	backend="polling"
 fi
 
+AC_ARG_ENABLE(inotify,
+[  --enable-inotify Uses inotify as backend],
+[case "${enableval}" in
+  yes) backend="inotify" ;;
+  no) ;;
+  *) AC_MSG_ERROR(bad value ${enableval} for --enable-inotify) ;;
+esac])
+
+if test x$backend = xinotify; then
+    AC_DEFINE(USE_INOTIFY,1,[Use inotify as backend])
+fi
+
 dnl check for flavours of varargs macros (test from GLib)
 AC_MSG_CHECKING(for ISO C99 varargs macros in C)
 AC_TRY_COMPILE([],[
Index: server/Makefile.am
===================================================================
RCS file: /cvs/gnome/gamin/server/Makefile.am,v
retrieving revision 1.1.1.1
diff -u -r1.1.1.1 Makefile.am
--- server/Makefile.am	15 Jun 2004 19:30:48 -0000	1.1.1.1
+++ server/Makefile.am	20 Jul 2004 00:02:59 -0000
@@ -30,6 +30,8 @@
 	gam_error.h					\
 	gam_dnotify.c					\
 	gam_dnotify.h					\
+	gam_inotify.c					\
+	gam_inotify.h					\
 	gam_channel.c					\
 	gam_channel.h					\
 	gam_connection.c				\
Index: server/gam_server.c
===================================================================
RCS file: /cvs/gnome/gamin/server/gam_server.c,v
retrieving revision 1.2
diff -u -r1.2 gam_server.c
--- server/gam_server.c	13 Jul 2004 14:24:34 -0000	1.2
+++ server/gam_server.c	20 Jul 2004 00:02:59 -0000
@@ -47,8 +47,9 @@
 gboolean
 gam_init_subscriptions(void)
 {
-
-#ifdef linux
+#ifdef USE_INOTIFY
+    return (gam_inotify_init());
+#elif linux
     return (gam_dnotify_init());
 #else
     return (gam_poll_init());
/* Marmot
 * Copyright (C) 2004 John McCutchan, James Willcox, Corey Bowers
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */


#include <config.h>
#define _GNU_SOURCE
#include <fcntl.h>
#include <sys/ioctl.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <glib.h>
#include "/usr/src/linux/include/linux/inotify.h"
#include "gam_error.h"
#include "gam_poll.h"
#include "gam_inotify.h"
#include "gam_tree.h"
#include "gam_event.h"
#include "gam_server.h"
#include "gam_event.h"

/* just pulling a value out of nowhere here...may need tweaking */
#define MAX_QUEUE_SIZE 500

typedef struct {
    char *path;
    int wd;
    int refcount;
} INotifyData;

static GHashTable *path_hash = NULL;
static GHashTable *wd_hash = NULL;

G_LOCK_DEFINE_STATIC(inotify);

static GQueue *changes = NULL;

#ifdef WITH_TREADING
static GMainContext *loop_context;
#endif
static GIOChannel *inotify_read_ioc = NULL;

static gboolean have_consume_idler = FALSE;

int fd = -1; // the device fd

static INotifyData *
gam_inotify_data_new(const char *path, int wd)
{
    INotifyData *data;

    data = g_new0(INotifyData, 1);
    data->path = g_strdup(path);
    data->wd = wd;
    data->refcount = 1;

    return data;
}

static void
gam_inotify_data_free(INotifyData * data)
{
    g_free(data->path);
    g_free(data);
}

static void
gam_inotify_directory_handler(const char *path, gboolean added)
{
    INotifyData *data;
    struct inotify_watch_request iwr;
    int wd,r;

    G_LOCK(inotify);

    if (added) {

        if ((data = g_hash_table_lookup(path_hash, path)) != NULL) {
            data->refcount++;
            G_UNLOCK(inotify);
            return;
        }

	iwr.dirname = g_strdup(path);
	iwr.mask = IN_MODIFY|IN_CREATE|IN_DELETE|IN_RENAME|IN_ATTRIB|IN_UNMOUNT|IN_IGNORED;

        wd = ioctl(fd, INOTIFY_WATCH,&iwr);
        g_free(iwr.dirname);	

        if (wd < 0) {
            G_UNLOCK(inotify);
            return;
        }


        data = gam_inotify_data_new(path, wd);
        g_hash_table_insert(wd_hash, GINT_TO_POINTER(data->wd), data);
        g_hash_table_insert(path_hash, data->path, data);

        gam_debug(DEBUG_INFO, "activated INotify for %s\n", path);
    } else {
        data = g_hash_table_lookup(path_hash, path);

        if (!data) {
            G_UNLOCK(inotify);
            return;
        }

        data->refcount--;

        if (data->refcount == 0) {
            r = ioctl (fd, INOTIFY_IGNORE, &data->wd); 
	    if (r < 0) {
                gam_debug (DEBUG_INFO, "INOTIFY_IGNORE failed for %s\n", data->path);
            }
            gam_debug(DEBUG_INFO, "deactivated INotify for %s\n",
                      data->path);
            g_hash_table_remove(path_hash, data->path);
            g_hash_table_remove(wd_hash, GINT_TO_POINTER(data->wd));
            gam_inotify_data_free(data);
        }
    }

    G_UNLOCK(inotify);
}

static void
gam_inotify_file_handler(const char *path, gboolean added)
{
    char *dir;

    dir = g_path_get_dirname(path);
    gam_inotify_directory_handler(dir, added);
    g_free(dir);
}

#ifdef WITH_TREADING
static gpointer
gam_inotify_scan_loop(gpointer data)
{
    g_main_loop_run(g_main_loop_new(loop_context, TRUE));
    return (NULL);
}
#endif

static gboolean
gam_inotify_read_handler(gpointer user_data)
{
    struct inotify_event event;
    INotifyData *data;

    gam_debug(DEBUG_INFO, "gam_inotify_read_handler()\n");

    if (g_io_channel_read_chars(inotify_read_ioc, (char *)&event, sizeof(struct inotify_event), NULL, NULL) != G_IO_STATUS_NORMAL) {
        gam_debug(DEBUG_INFO, "gam_inotify_read_handler failed\n");
	return FALSE;
    }

    ioctl(fd, INOTIFY_STATS,0);

    G_LOCK(inotify);
    data = g_hash_table_lookup (wd_hash, GINT_TO_POINTER(event.wd));
    G_UNLOCK(inotify);

    if (!data) {
	gam_debug(DEBUG_INFO, "Could not find WD %d in hash\n", event.wd);
        return TRUE;
    }

    gam_debug(DEBUG_INFO, "gam_inotify event for %s (%x)\n", data->path, event.mask);
    gam_poll_scan_directory(data->path, NULL);

    gam_debug(DEBUG_INFO, "gam_inotify_read_handler() done\n");

    return TRUE;
}

static gboolean
gam_inotify_consume_subscriptions_real(gpointer data)
{
    gam_poll_consume_subscriptions();
    have_consume_idler = FALSE;
    return FALSE;
}

static void
gam_inotify_consume_subscriptions(void)
{
    GSource *source;

    if (have_consume_idler)
        return;

    have_consume_idler = TRUE;
    source = g_idle_source_new();
    g_source_set_callback(source, gam_inotify_consume_subscriptions_real,
                          NULL, NULL);
#ifdef WITH_TREADING
    g_source_attach(source, loop_context);
#else
    g_source_attach(source, NULL);
#endif
}

/**
 * @defgroup INotify INotify Backend
 * @ingroup Backends
 * @brief INotify backend API
 *
 * Since version 2.X, Linux kernels have included the Linux Inode
 * Notification system (inotify).  This backend uses inotify to know when
 * files are changed/created/deleted.  Since inotify doesn't tell us
 * exactly what event happened to which file (just that some even happened
 * in some directory), we still have to cache stat() information.  For this,
 * we can just use the code in the polling backend.
 *
 * @{
 */


/**
 * Initializes the inotify system.  This must be called before
 * any other functions in this module.
 *
 * @returns TRUE if initialization succeeded, FALSE otherwise
 */
gboolean
gam_inotify_init(void)
{
    GSource *source;

    g_return_val_if_fail(gam_poll_init_full(FALSE), FALSE);

    fd = open("/dev/inotify", O_RDONLY);

    if (fd < 0) {
        g_warning("Could not open /dev/inotify\n");
        return FALSE;
    }

    inotify_read_ioc = g_io_channel_unix_new(fd);

    /* For binary data */
    g_io_channel_set_encoding (inotify_read_ioc, NULL, NULL);
    /* Non blocking */
    g_io_channel_set_flags(inotify_read_ioc, G_IO_FLAG_NONBLOCK, NULL);

#ifdef WITH_TREADING
    loop_context = g_main_context_new();
#endif

    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);

#ifdef WITH_TREADING
    g_source_attach(source, loop_context);
#else
    g_source_attach(source, NULL);
#endif

    changes = g_queue_new();

#ifdef WITH_TREADING
    g_thread_create(gam_inotify_scan_loop, NULL, TRUE, NULL);
#endif

    path_hash = g_hash_table_new(g_str_hash, g_str_equal);
    wd_hash = g_hash_table_new(g_direct_hash, g_direct_equal);
    gam_poll_set_directory_handler(gam_inotify_directory_handler);
    gam_poll_set_file_handler(gam_inotify_file_handler);

    gam_debug(DEBUG_INFO, "inotify initialized\n");

    return TRUE;
}

/**
 * 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)
{
    if (!gam_poll_add_subscription(sub)) {
        return FALSE;
    }

    if (gam_subscription_is_dir(sub)) {
        gam_inotify_consume_subscriptions();
    }

    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)
{
    if (!gam_poll_remove_subscription(sub)) {
        return FALSE;
    }

    if (gam_subscription_is_dir(sub)) {
        gam_inotify_consume_subscriptions();
    }

    return 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)
{
    if (!gam_poll_remove_all_for(listener)) {
        return FALSE;
    }

    gam_inotify_consume_subscriptions();

    return TRUE;
}

/** @} */
#ifndef __MD_INOTIFY_H__
#define __MD_INOTIFY_H__

#include <glib.h>
#include "gam_poll.h"
#include "gam_subscription.h"

G_BEGIN_DECLS

gboolean   gam_inotify_init                  (void);
gboolean   gam_inotify_add_subscription      (GamSubscription *sub);
gboolean   gam_inotify_remove_subscription   (GamSubscription *sub);
gboolean   gam_inotify_remove_all_for        (GamListener *listener);

G_END_DECLS

#endif /* __MD_INOTIFY_H__ */


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]