Skip site navigation (1)Skip section navigation (2)
Date:      Wed,  6 Apr 2005 19:19:15 +0200 (CEST)
From:      Jean-Yves Lefort <jylefort@brutele.be>
To:        FreeBSD-gnats-submit@FreeBSD.org
Cc:        gnome@FreeBSD.org
Subject:   ports/79605: Update port: devel/gamin (fix and improve the kqueue backend)
Message-ID:  <20050406171915.16B91C459@jsite.lefort.net>
Resent-Message-ID: <200504061720.j36HK56D030073@freefall.freebsd.org>

next in thread | raw e-mail | index | archive | help

>Number:         79605
>Category:       ports
>Synopsis:       Update port: devel/gamin (fix and improve the kqueue backend)
>Confidential:   no
>Severity:       non-critical
>Priority:       medium
>Responsible:    freebsd-ports-bugs
>State:          open
>Quarter:        
>Keywords:       
>Date-Required:
>Class:          update
>Submitter-Id:   current-users
>Arrival-Date:   Wed Apr 06 17:20:05 GMT 2005
>Closed-Date:
>Last-Modified:
>Originator:     Jean-Yves Lefort
>Release:        FreeBSD 5.3-RELEASE-p6 i386
>Organization:
>Environment:
System: FreeBSD jsite.lefort.net 5.3-RELEASE-p6 FreeBSD 5.3-RELEASE-p6 #0: Tue Apr 5 17:03:19 CEST 2005 jylefort@jsite.lefort.net:/usr/obj/usr/src/sys/JSITE i386
>Description:
* When a file monitored with kqueue is moved or removed, gamin does
  not monitor it anymore. The patch fixes the issue by adding
  moved/removed files to the exist_list, so that gamin detects when
  they are recreated.
* No CHANGED event is emitted for the files contained in a monitored
  directory, because kqueue can't do that. The patch adds periodic
  polling for these files.
* Instead of calling kevent() every second, the patch uses an I/O
  watch (g_io_add_watch()).
>How-To-Repeat:
>Fix:
diff -ruN /usr/ports/devel/gamin/Makefile gamin/Makefile
--- /usr/ports/devel/gamin/Makefile	Sat Apr  2 11:08:46 2005
+++ gamin/Makefile	Wed Apr  6 18:57:30 2005
@@ -7,7 +7,7 @@
 
 PORTNAME=	gamin
 PORTVERSION=	0.0.26
-PORTREVISION?=	8
+PORTREVISION?=	9
 CATEGORIES?=	devel
 MASTER_SITES=	http://www.gnome.org/~veillard/gamin/sources/
 
diff -ruN /usr/ports/devel/gamin/files/patch-server_gam_kqueue.c gamin/files/patch-server_gam_kqueue.c
--- /usr/ports/devel/gamin/files/patch-server_gam_kqueue.c	Sat Apr  2 11:08:47 2005
+++ gamin/files/patch-server_gam_kqueue.c	Wed Apr  6 18:57:11 2005
@@ -1,8 +1,9 @@
---- server/gam_kqueue.c.orig	Thu Mar 31 20:39:54 2005
-+++ server/gam_kqueue.c	Fri Apr  1 01:09:11 2005
-@@ -0,0 +1,636 @@
+--- server/gam_kqueue.c.orig	Wed Apr  6 18:21:46 2005
++++ server/gam_kqueue.c	Wed Apr  6 18:57:07 2005
+@@ -0,0 +1,712 @@
 +/*
 + * Copyright (C) 2005 Joe Marcus Clarke <marcus@FreeBSD.org>
++ * Copyright (C) 2005 Jean-Yves Lefort <jylefort@brutele.be>
 + *
 + * This library is free software; you can redistribute it and/or
 + * modify it under the terms of the GNU Lesser General Public
@@ -22,6 +23,7 @@
 +
 +#include <config.h>
 +#include <sys/types.h>
++#include <sys/stat.h>
 +#include <sys/event.h>
 +#include <sys/time.h>
 +#include <fcntl.h>
@@ -47,6 +49,19 @@
 +    GSList *dirlist;
 +} KQueueData;
 +
++typedef struct
++{
++    time_t mtime;
++    time_t ctime;
++    off_t size;
++} MiniStat;
++
++typedef struct {
++    char *filename;
++    char *pathname;
++    MiniStat sb;
++} FileData;
++  
 +static GHashTable *dir_path_hash = NULL;
 +static GHashTable *file_path_hash = NULL;
 +static GHashTable *fd_hash = NULL;
@@ -80,70 +95,42 @@
 +    return data;
 +}
 +
-+static GSList *
-+gam_kqueue_lsdir(const char *path)
++static void
++gam_kqueue_mini_stat (const char *pathname, MiniStat *mini_sb)
 +{
-+    GDir *dir;
-+    GSList *lst = NULL;
-+    const gchar *entry;
-+
-+    if (!path)
-+        return NULL;
++    struct stat sb;
 +
-+    dir = g_dir_open(path, 0, NULL);
-+    if (!dir)
-+        return NULL;
-+
-+    entry = g_dir_read_name(dir);
-+
-+    while (entry) {
-+        lst = g_slist_prepend(lst, g_strdup(entry));
-+        entry = g_dir_read_name(dir);
++    if (stat(pathname, &sb) == 0) {
++        mini_sb->mtime = sb.st_mtime;
++	mini_sb->ctime = sb.st_ctime;
++	mini_sb->size = sb.st_size;
++    } else {
++        mini_sb->mtime = 0;
++	mini_sb->ctime = 0;
++	mini_sb->size = 0;
 +    }
-+
-+    g_dir_close(dir);
-+
-+    return lst;
 +}
 +
-+static void
-+gam_kqueue_cmplst(GSList *lst1, GSList *lst2, GSList **added, GSList **deleted)
++static FileData *
++gam_kqueue_file_data_new (const char *path, const char *filename)
 +{
-+    int found;
-+    GSList *l;
-+
-+    if (!lst1 && !lst2)
-+        return;
++    FileData *fdata;
++    struct stat sb;
 +
-+    if (!lst1) {
-+        *added = g_slist_copy(lst2);
-+        return;
-+    }
-+
-+    if (!lst2) {
-+        *deleted = g_slist_copy(lst1);
-+        return;
-+    }
++    fdata = g_new(FileData, 1);
++    fdata->filename = g_strdup(filename);
++    fdata->pathname = g_build_filename(path, filename, NULL);
++    gam_kqueue_mini_stat(fdata->pathname, &fdata->sb);
 +
-+    for (l = lst1; l; l = l->next) {
-+        found = 0;
-+        if (g_slist_find_custom(lst2, l->data, (GCompareFunc)strcmp)) {
-+            found = 1;
-+        }
-+        if (found == 0) {
-+            *deleted = g_slist_prepend(*deleted, l->data);
-+        }
-+    }
++    return fdata;
++}
 +
-+    for (l = lst2; l; l = l->next) {
-+        found = 0;
-+        if (g_slist_find_custom(lst1, l->data, (GCompareFunc)strcmp)) {
-+            found = 1;
-+        }
-+        if (found == 0) {
-+            *added = g_slist_prepend(*added, l->data);
-+        }
-+    }
++static void
++gam_kqueue_file_data_free (FileData *fdata)
++{
++    g_free(fdata->filename);
++    g_free(fdata->pathname);
++    g_free(fdata);
 +}
 +
 +static void
@@ -151,7 +138,7 @@
 +{
 +    g_free(data->path);
 +    if (data->dirlist) {
-+        g_slist_foreach(data->dirlist, (GFunc)g_free, NULL);
++        g_slist_foreach(data->dirlist, (GFunc)gam_kqueue_file_data_free, NULL);
 +        g_slist_free(data->dirlist);
 +    }
 +    if (data->subs) {
@@ -160,6 +147,12 @@
 +    g_free(data);
 +}
 +
++static int
++gam_kqueue_dirlist_find (FileData *fdata, const char *filename)
++{
++    return strcmp(fdata->filename, filename);
++}
++
 +static void
 +gam_kqueue_add_rm_handler(const char *path, GamSubscription *sub, gboolean added, gboolean was_missing)
 +{
@@ -230,21 +223,29 @@
 +            gam_server_emit_event (path, isdir, GAMIN_EVENT_CREATED, subs, 1);
 +	}
 +        if (gam_subscription_is_dir(sub) && isdir) {
-+	    GSList *l;
++	    GDir *dir;
 +
 +            data->isdir = TRUE;
-+            data->dirlist = gam_kqueue_lsdir(path);
++	    data->dirlist = NULL;
 +
-+	    for (l = data->dirlist; l; l = l->next) {
-+                char *tmpentry;
-+
-+                tmpentry = g_build_filename(path, l->data, NULL);
-+		if (!was_missing) {
-+		    gam_server_emit_event (tmpentry,
-+                        g_file_test(tmpentry, G_FILE_TEST_IS_DIR),
-+		        GAMIN_EVENT_EXISTS, subs, 1);
++	    dir = g_dir_open(path, 0, NULL);
++	    if (dir) {
++	        const char *entry;
++
++		while ((entry = g_dir_read_name(dir))) {
++		    FileData *fdata;
++
++		    fdata = gam_kqueue_file_data_new(path, entry);
++		    data->dirlist = g_slist_prepend(data->dirlist, fdata);
++
++		    if (!was_missing) {
++		        gam_server_emit_event(fdata->pathname,
++					      g_file_test(fdata->pathname, G_FILE_TEST_IS_DIR),
++					      GAMIN_EVENT_EXISTS, subs, 1);
++		    }
 +		}
-+		g_free(tmpentry);
++
++		g_dir_close(dir);
 +	    }
 +	}
 +
@@ -332,44 +333,50 @@
 +	isdir = g_file_test(data->path, G_FILE_TEST_IS_DIR);
 +
 +	if (gevent == GAMIN_EVENT_CHANGED && data->isdir) {
-+	    GSList *dirlist = NULL, *added = NULL, *deleted = NULL;
++	    GSList *dirlist = NULL;
 +	    GSList *l;
++	    GDir *dir;
++
++	    dir = g_dir_open(data->path, 0, NULL);
++	    if (dir) {
++	        const char *entry;
 +
-+	    dirlist = gam_kqueue_lsdir(data->path);
-+	    gam_kqueue_cmplst(data->dirlist, dirlist, &added, &deleted);
-+	    if (added || deleted) {
-+	        for (l = deleted; l; l = l->next) {
-+                    data->dirlist = g_slist_remove(data->dirlist, l->data);
-+		    event_path = g_build_filename(data->path, l->data, NULL);
-+		    g_free(l->data);
-+		    isdir = g_file_test(event_path, G_FILE_TEST_IS_DIR);
-+
-+                    GAM_DEBUG(DEBUG_INFO, "kqueue emitting event %s for %s\n", gam_event_to_string(GAMIN_EVENT_DELETED) , event_path);
-+
-+                    gam_server_emit_event (event_path, isdir,
-+                        GAMIN_EVENT_DELETED, data->subs, 1);
-+		    g_free(event_path);
++		while ((entry = g_dir_read_name(dir))) {
++		    dirlist = g_slist_prepend(dirlist, g_strdup(entry));
 +		}
 +
-+		for (l = added; l; l = l->next) {
-+                    dirlist = g_slist_remove(dirlist, l->data);
-+		    data->dirlist = g_slist_prepend(data->dirlist,
-+                        g_strdup(l->data));
-+		    event_path = g_build_filename(data->path, l->data, NULL);
-+		    g_free(l->data);
-+		    isdir = g_file_test(event_path, G_FILE_TEST_IS_DIR);
-+
-+		    GAM_DEBUG(DEBUG_INFO, "kqueue emitting event %s for %s\n", gam_event_to_string(GAMIN_EVENT_CREATED) , event_path);
-+
-+		    gam_server_emit_event (event_path, isdir,
-+                        GAMIN_EVENT_CREATED, data->subs, 1);
-+		    g_free(event_path);
++		g_dir_close(dir);
++	    }
++
++	    for (l = dirlist; l; l = l->next) {
++	        if (! g_slist_find_custom(data->dirlist, l->data, (GCompareFunc) gam_kqueue_dirlist_find)) {
++		    FileData *fdata;
++
++		    fdata = gam_kqueue_file_data_new(data->path, l->data);
++		    data->dirlist = g_slist_prepend(data->dirlist, fdata);
++
++		    GAM_DEBUG(DEBUG_INFO, "kqueue emitting event %s for %s\n", gam_event_to_string(GAMIN_EVENT_CREATED), fdata->pathname);
++		    gam_server_emit_event(fdata->pathname,
++					  g_file_test(fdata->pathname, G_FILE_TEST_IS_DIR),
++					  GAMIN_EVENT_CREATED, data->subs, 1);
 +		}
++	    }
++
++	iterate:
++	    for (l = data->dirlist; l; l = l->next) {
++	        FileData *fdata = l->data;
++
++	        if (! g_slist_find_custom(dirlist, fdata->filename, (GCompareFunc) strcmp)) {
++		    data->dirlist = g_slist_remove(data->dirlist, fdata);
 +
-+		if (added)
-+                  g_slist_free(added);
-+		if (deleted)
-+                  g_slist_free(deleted);
++                    GAM_DEBUG(DEBUG_INFO, "kqueue emitting event %s for %s\n", gam_event_to_string(GAMIN_EVENT_DELETED), fdata->pathname);
++		    gam_server_emit_event(fdata->pathname,
++					  g_file_test(fdata->pathname, G_FILE_TEST_IS_DIR),
++					  GAMIN_EVENT_DELETED, data->subs, 1);
++
++		    gam_kqueue_file_data_free(fdata);
++		    goto iterate; /* list changed, start again */
++		}
 +	    }
 +
 +	    if (dirlist) {
@@ -380,6 +387,22 @@
 +	}
 +	else {
 +	    event_path = g_strdup (data->path);
++
++	    if (gevent == GAMIN_EVENT_DELETED
++		|| gevent == GAMIN_EVENT_ENDEXISTS
++		|| gevent == GAMIN_EVENT_MOVED) {
++	      /* close and move to exist_list, to catch next creation */
++	      close(data->fd);
++	      if (data->isdir) {
++		  g_hash_table_remove(dir_path_hash, data->path);
++	      }
++	      else {
++		  g_hash_table_remove(file_path_hash, data->path);
++	      }
++	      g_hash_table_remove(fd_hash, GINT_TO_POINTER(data->fd));
++
++	      exist_list = g_slist_append(exist_list, data);
++	    }
 +	}
 +
 +        isdir = g_file_test(event_path, G_FILE_TEST_IS_DIR);
@@ -418,8 +441,47 @@
 +    return TRUE;
 +}
 +
++static void
++gam_kqueue_dirlist_check_cb (const char *path, KQueueData *data, gpointer user_data)
++{
++    GSList *l;
++
++    for (l = data->dirlist; l; l = l->next) {
++        FileData *fdata = l->data;
++	MiniStat sb;
++
++	gam_kqueue_mini_stat(fdata->pathname, &sb);
++
++	if (sb.mtime != fdata->sb.mtime
++	    || sb.ctime != fdata->sb.ctime
++	    || sb.size != fdata->sb.size)
++	  {
++	      memcpy(&fdata->sb, &sb, sizeof(sb));
++
++	      GAM_DEBUG(DEBUG_INFO, "kqueue emitting event %s for %s\n", gam_event_to_string(GAMIN_EVENT_CHANGED), fdata->pathname);
++	      gam_server_emit_event(fdata->pathname,
++				    g_file_test(fdata->pathname, G_FILE_TEST_IS_DIR),
++				    GAMIN_EVENT_CHANGED, data->subs, 1);
++	  }
++    }
++}
++
++static gboolean
++gam_kqueue_dirlist_check (gpointer user_data)
++{
++    G_LOCK(kqueue);
++
++    GAM_DEBUG(DEBUG_INFO, "gam_kqueue_dirlist_check()\n");
++
++    g_hash_table_foreach(dir_path_hash, (GHFunc) gam_kqueue_dirlist_check_cb, NULL);
++
++    G_UNLOCK(kqueue);
++
++    return TRUE;
++}
++
 +static gboolean
-+gam_kqueue_event_handler (gpointer user_data)
++gam_kqueue_event_handler (GIOChannel *source, GIOCondition condition, gpointer user_data)
 +{
 +    KQueueData *data;
 +    struct kevent ev[1];
@@ -531,6 +593,8 @@
 +gboolean
 +gam_kqueue_init(void)
 +{
++    GIOChannel *channel;
++
 +    kq = kqueue();
 +    if (kq == -1) {
 +	GAM_DEBUG(DEBUG_INFO, "Could not initialize a new kqueue\n");
@@ -538,11 +602,23 @@
 +    }
 +
 +    g_timeout_add(1000, gam_kqueue_exist_check, NULL);
-+    g_timeout_add(1000, gam_kqueue_event_handler, NULL);
++
++    channel = g_io_channel_unix_new(kq);
++    g_io_add_watch(channel, G_IO_IN, gam_kqueue_event_handler, NULL);
 +
 +    dir_path_hash = g_hash_table_new(g_str_hash, g_str_equal);
 +    file_path_hash = g_hash_table_new(g_str_hash, g_str_equal);
 +    fd_hash = g_hash_table_new(g_direct_hash, g_direct_equal);
++
++    /*
++     * gam_kqueue_dirlist_check() has to stat() every file in every
++     * monitored directory. This can easily become an intensive task
++     * if a few large directories are monitored (for instance a mail
++     * checker monitoring a couple of Maildir folders), therefore we
++     * use a reasonable poll interval (6 seconds, same as FAM's
++     * default).
++     */
++    g_timeout_add(6000, gam_kqueue_dirlist_check, NULL);
 +
 +    GAM_DEBUG(DEBUG_INFO, "kqueue initialized\n");
 +
>Release-Note:
>Audit-Trail:
>Unformatted:



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?20050406171915.16B91C459>