From owner-freebsd-gnome@FreeBSD.ORG Wed Apr 6 17:19:19 2005 Return-Path: Delivered-To: freebsd-gnome@freebsd.org Received: from mx1.FreeBSD.org (mx1.freebsd.org [216.136.204.125]) by hub.freebsd.org (Postfix) with ESMTP id F290C16A4CE; Wed, 6 Apr 2005 17:19:18 +0000 (GMT) Received: from gateway.lefort.net (212.68.244.102.brutele.be [212.68.244.102]) by mx1.FreeBSD.org (Postfix) with ESMTP id 30E1843D54; Wed, 6 Apr 2005 17:19:17 +0000 (GMT) (envelope-from jylefort@brutele.be) Received: from jsite.lefort.net (jsite.lefort.net [192.168.1.2]) by gateway.lefort.net (Postfix) with ESMTP id 5E6CD74B3; Wed, 6 Apr 2005 19:19:15 +0200 (CEST) Received: by jsite.lefort.net (Postfix, from userid 1000) id 16B91C459; Wed, 6 Apr 2005 19:19:15 +0200 (CEST) To: FreeBSD-gnats-submit@freebsd.org From: Jean-Yves Lefort X-send-pr-version: 3.113 X-GNATS-Notify: Message-Id: <20050406171915.16B91C459@jsite.lefort.net> Date: Wed, 6 Apr 2005 19:19:15 +0200 (CEST) cc: gnome@FreeBSD.org Subject: Update port: devel/gamin (fix and improve the kqueue backend) X-BeenThere: freebsd-gnome@freebsd.org X-Mailman-Version: 2.1.1 Precedence: list Reply-To: Jean-Yves Lefort List-Id: GNOME for FreeBSD -- porting and maintaining List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Wed, 06 Apr 2005 17:19:19 -0000 >Submitter-Id: current-users >Originator: Jean-Yves Lefort >Organization: >Confidential: no >Synopsis: Update port: devel/gamin (fix and improve the kqueue backend) >Severity: non-critical >Priority: medium >Category: ports >Class: update >Release: FreeBSD 5.3-RELEASE-p6 i386 >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 ++ * Copyright (C) 2005 Jean-Yves Lefort + * + * 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 +#include ++#include +#include +#include +#include @@ -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"); +