* 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())

PR:		79605
Submitted by:	Jean-Yves Lefort <jylefort@brutele.be>
This commit is contained in:
Joe Marcus Clarke 2005-04-07 01:18:24 +00:00
parent 40bda7dba8
commit 179b70aa16
Notes: svn2git 2021-03-31 03:12:20 +00:00
svn path=/head/; revision=132661
2 changed files with 185 additions and 101 deletions

View file

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

View file

@ -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 22:46:40 2005
+++ server/gam_kqueue.c Wed Apr 6 22:47:16 2005
@@ -0,0 +1,720 @@
+/*
+ * 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,23 @@
+ GSList *dirlist;
+} KQueueData;
+
+typedef struct
+{
+ ino_t ino;
+ mode_t mode;
+ uid_t uid;
+ gid_t gid;
+ time_t mtime;
+ time_t ctime;
+ off_t size;
+} MiniStat;
+
+typedef struct {
+ char *pathname;
+ char *filename; /* pointer into pathname */
+ MiniStat sb;
+} FileData;
+
+static GHashTable *dir_path_hash = NULL;
+static GHashTable *file_path_hash = NULL;
+static GHashTable *fd_hash = NULL;
@ -80,70 +99,43 @@
+ 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;
+ struct stat sb;
+
+ if (!path)
+ return NULL;
+
+ 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 (lstat(pathname, &sb) == 0) {
+ mini_sb->ino = sb.st_ino;
+ mini_sb->mode = sb.st_mode;
+ mini_sb->uid = sb.st_uid;
+ mini_sb->gid = sb.st_gid;
+ mini_sb->mtime = sb.st_mtime;
+ mini_sb->ctime = sb.st_ctime;
+ mini_sb->size = sb.st_size;
+ } else {
+ memset(mini_sb, 0, sizeof(*mini_sb));
+ }
+}
+
+ g_dir_close(dir);
+static FileData *
+gam_kqueue_file_data_new (const char *path, const char *filename)
+{
+ FileData *fdata;
+
+ return lst;
+ fdata = g_new(FileData, 1);
+ fdata->pathname = g_build_filename(path, filename, NULL);
+ fdata->filename = strrchr(fdata->pathname, G_DIR_SEPARATOR);
+ fdata->filename = fdata->filename ? fdata->filename + 1 : fdata->pathname;
+ gam_kqueue_mini_stat(fdata->pathname, &fdata->sb);
+
+ return fdata;
+}
+
+static void
+gam_kqueue_cmplst(GSList *lst1, GSList *lst2, GSList **added, GSList **deleted)
+gam_kqueue_file_data_free (FileData *fdata)
+{
+ int found;
+ GSList *l;
+
+ if (!lst1 && !lst2)
+ return;
+
+ if (!lst1) {
+ *added = g_slist_copy(lst2);
+ return;
+ }
+
+ if (!lst2) {
+ *deleted = g_slist_copy(lst1);
+ return;
+ }
+
+ 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);
+ }
+ }
+
+ 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);
+ }
+ }
+ g_free(fdata->pathname);
+ g_free(fdata);
+}
+
+static void
@ -151,7 +143,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 +152,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 +228,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;
+ dir = g_dir_open(path, 0, NULL);
+ if (dir) {
+ const char *entry;
+
+ 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);
+ 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 +338,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;
+
+ 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);
+ dir = g_dir_open(data->path, 0, NULL);
+ if (dir) {
+ const char *entry;
+
+ 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);
+ g_dir_close(dir);
+ }
+
+ GAM_DEBUG(DEBUG_INFO, "kqueue emitting event %s for %s\n", gam_event_to_string(GAMIN_EVENT_CREATED) , event_path);
+ for (l = dirlist; l; l = l->next) {
+ if (! g_slist_find_custom(data->dirlist, l->data, (GCompareFunc) gam_kqueue_dirlist_find)) {
+ FileData *fdata;
+
+ gam_server_emit_event (event_path, isdir,
+ GAMIN_EVENT_CREATED, data->subs, 1);
+ g_free(event_path);
+ 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);
+ }
+ }
+
+ if (added)
+ g_slist_free(added);
+ if (deleted)
+ g_slist_free(deleted);
+ iterate:
+ for (l = data->dirlist; l; l = l->next) {
+ FileData *fdata = l->data;
+
+ if (! g_slist_find_custom(dirlist, fdata->filename, (GCompareFunc) strcmp)) {
+ data->dirlist = g_slist_remove(data->dirlist, fdata);
+
+ GAM_DEBUG(DEBUG_INFO, "kqueue emitting event %s for %s\n", gam_event_to_string(GAMIN_EVENT_DELETED), fdata->pathname);
+ gam_server_emit_event(fdata->pathname,
+ g_file_test(fdata->pathname, G_FILE_TEST_IS_DIR),
+ GAMIN_EVENT_DELETED, data->subs, 1);
+
+ gam_kqueue_file_data_free(fdata);
+ goto iterate; /* list changed, start again */
+ }
+ }
+
+ if (dirlist) {
@ -380,6 +392,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 +446,51 @@
+ 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
+ || sb.mode != fdata->sb.mode
+ || sb.uid != fdata->sb.uid
+ || sb.gid != fdata->sb.gid
+ || sb.ino != fdata->sb.ino)
+ {
+ memcpy(&fdata->sb, &sb, sizeof(sb));
+
+ GAM_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_event_handler (gpointer user_data)
+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 (GIOChannel *source, GIOCondition condition, gpointer user_data)
+{
+ KQueueData *data;
+ struct kevent ev[1];
@ -531,6 +602,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,12 +611,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 lstat() every file in every
+ * monitored directory. This can easily become an intensive task
+ * if a few large directories are monitored (for instance a mail
+ * checker monitoring a couple of MH folders), therefore we use a
+ * reasonable poll interval (6 seconds, same as FAM's default).
+ */
+ g_timeout_add(6000, gam_kqueue_dirlist_check, NULL);
+
+ GAM_DEBUG(DEBUG_INFO, "kqueue initialized\n");
+
+ gam_backend_add_subscription = gam_kqueue_add_subscription;