--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/patches/gamin-01-all.diff Mon Jan 14 09:13:19 2008 +0000
@@ -0,0 +1,2563 @@
+Index: gamin/MAINTAINERS
+===================================================================
+--- gamin/MAINTAINERS (revision 0)
++++ gamin/MAINTAINERS (revision 328)
+@@ -0,0 +1,6 @@
++Please see http://www.gnome.org/~veillard/gamin/contacts.html for
++contact informations on this project:
++
++Daniel Veillard
++E-mail: [email protected]
++Userid: veillard
+Index: gamin/libgamin/fam.h
+===================================================================
+--- gamin/libgamin/fam.h (revision 325)
++++ gamin/libgamin/fam.h (working copy)
+@@ -190,6 +190,54 @@
+ extern int FAMErrno;
+
+ /**
++ * FAMDebugLevel:
++ *
++ * Currently unimplemented as in the SGI FAM. Exists only for
++ * compatibility.
++ */
++extern int FAMDebugLevel (FAMConnection *fc,
++ int level);
++/**
++ * FAM_DEBUG_OFF:
++ * Unused macro, compatibility for SGI FAM API.
++ */
++#define FAM_DEBUG_OFF 0
++/**
++ * FAM_DEBUG_ON:
++ * Unused macro, compatibility for SGI FAM API.
++ */
++#define FAM_DEBUG_ON 1
++/**
++ * FAM_DEBUG_VERBOSE:
++ * Unused macro, compatibility for SGI FAM API.
++ */
++#define FAM_DEBUG_VERBOSE 2
++
++/**
++ * FAMDebugLevel:
++ *
++ * Currently unimplemented as in the SGI FAM. Exists only for
++ * compatibility.
++ */
++extern int FAMDebugLevel (FAMConnection *fc,
++ int level);
++/**
++ * FAM_DEBUG_OFF:
++ * Unused macro, compatibility for SGI FAM API.
++ */
++#define FAM_DEBUG_OFF 0
++/**
++ * FAM_DEBUG_ON:
++ * Unused macro, compatibility for SGI FAM API.
++ */
++#define FAM_DEBUG_ON 1
++/**
++ * FAM_DEBUG_VERBOSE:
++ * Unused macro, compatibility for SGI FAM API.
++ */
++#define FAM_DEBUG_VERBOSE 2
++
++/**
+ * FamErrList:
+ *
+ * In case FAMErrno is set, FAMErrlist is a global string array indexed
+Index: gamin/libgamin/gam_api.c
+===================================================================
+--- gamin/libgamin/gam_api.c (revision 325)
++++ gamin/libgamin/gam_api.c (working copy)
+@@ -14,6 +14,12 @@
+ #include <sys/socket.h>
+ #include <sys/un.h>
+ #include <sys/uio.h>
++#if defined(sun)
++#include <string.h>
++#endif
++#if defined(HAVE_UCRED_H)
++#include <ucred.h>
++#endif defined(HAVE_UCRED_H)
+ #include "fam.h"
+ #include "gam_protocol.h"
+ #include "gam_data.h"
+@@ -660,6 +666,10 @@
+ } cmsg;
+ #endif
+
++#if defined(HAVE_GETPEERUCRED)
++ ucred_t *creds;
++#endif
++
+ s_uid = getuid();
+
+ #if defined(LOCAL_CREDS) && defined(HAVE_CMSGCRED)
+@@ -726,11 +736,25 @@
+ fd, cr_len, (int) sizeof(cr));
+ goto failed;
+ }
++#elif defined(HAVE_GETPEERUCRED)
++ if ((creds = (ucred_t *)malloc(ucred_size()))==(ucred_t *)NULL){
++ GAM_DEBUG(DEBUG_INFO,"Malloc failed for ucreds");
++ goto failed;
++ }
++
++ if (getpeerucred(fd, &creds)!=0){
++ GAM_DEBUG(DEBUG_INFO,"getpeerucred call failed");
++ goto failed;
++ }
++ c_uid = ucred_getruid(creds);
++ c_gid = ucred_getrgid(creds);
++ c_pid = ucred_getpid(creds);
++ ucred_free(creds);
+ #elif defined(HAVE_CMSGCRED)
+ c_pid = cmsg.cred.cmcred_pid;
+ c_uid = cmsg.cred.cmcred_euid;
+ c_gid = cmsg.cred.cmcred_groups[0];
+-#else /* !SO_PEERCRED && !HAVE_CMSGCRED */
++#else /* !SO_PEERCRED && !HAVE_CMSGCRED && !HAVE_GETPEERUCRED */
+ GAM_DEBUG(DEBUG_INFO,
+ "Socket credentials not supported on this OS\n");
+ goto failed;
+@@ -1340,6 +1364,7 @@
+ gamin_data_lock(conn);
+ if (gamin_data_event_ready(conn)) {
+ gamin_data_unlock(conn);
++ GAM_DEBUG(DEBUG_INFO, "FAMPending()gamin_data_event_ready\n");
+ return (1);
+ }
+
+@@ -1347,15 +1372,18 @@
+ * make sure we won't block if reading
+ */
+ ret = gamin_data_available(fc->fd);
++ GAM_DEBUG(DEBUG_INFO, "FAMPending() gamin_data_available ret = %d \n", ret);
+ if (ret < 0)
+ return (-1);
+ if (ret > 0) {
++ GAM_DEBUG(DEBUG_INFO, "FAMPending() ret >0 \n");
+ if (gamin_read_data(conn, fc->fd, 0) < 0) {
+ gamin_try_reconnect(conn, fc->fd);
+ }
+ }
+
+ ret = (gamin_data_event_ready(conn));
++ GAM_DEBUG(DEBUG_INFO, "FAMPending() gamin_data_event_ready ret = %d \n", ret);
+ gamin_data_unlock(conn);
+
+ return ret;
+@@ -1529,4 +1557,36 @@
+ }
+ return(ret);
+ }
++
++/**
++ * FAMDebugLevel:
++ * @fc: pointer to a connection structure.
++ * @level: level of debug
++ *
++ * Entry point installed only for ABI compatibility with SGI FAM,
++ * doesn't do anything.
++ *
++ * Returns 1
++ */
++int
++FAMDebugLevel(FAMConnection *fc, int level)
++{
++ return(1);
++}
++
++/**
++ * FAMDebugLevel:
++ * @fc: pointer to a connection structure.
++ * @level: level of debug
++ *
++ * Entry point installed only for ABI compatibility with SGI FAM,
++ * doesn't do anything.
++ *
++ * Returns 1
++ */
++int
++FAMDebugLevel(FAMConnection *fc, int level)
++{
++ return(1);
++}
+ #endif
+Index: gamin/libgamin/gamin_sym.version
+===================================================================
+--- gamin/libgamin/gamin_sym.version (revision 328)
++++ gamin/libgamin/gamin_sym.version (working copy)
+@@ -2,8 +2,6 @@
+ global:
+ FAMCancelMonitor;
+ FAMClose;
+- FAMDebugLevel;
+- FAMDebug;
+ FamErrlist;
+ FAMErrno;
+ FAMMonitorCollection;
+Index: gamin/libgamin/Makefile.am
+===================================================================
+--- gamin/libgamin/Makefile.am (revision 328)
++++ gamin/libgamin/Makefile.am (working copy)
+@@ -39,13 +39,24 @@
+
+ libgamin_1_la_LIBADD =
+
++if ON_SOLARIS
++libgamin_1_la_LDFLAGS = -Wl,-M$(srcdir)/gamin_sym.version \
++ -version-info @GAMIN_VERSION_INFO@ @THREAD_LIBS@
++else
+ libgamin_1_la_LDFLAGS = -Wl,--version-script=$(srcdir)/gamin_sym.version \
+ -version-info @GAMIN_VERSION_INFO@ @THREAD_LIBS@
++endif
+
+ libfam_la_SOURCES = $(libgamin_1_la_SOURCES)
+ libfam_la_LIBADD = $(libgamin_1_la_LIBADD)
+-libfam_la_LDFLAGS = -Wl,--version-script=$(srcdir)/gamin_sym.version \
++
++if ON_SOLARIS
++libfam_la_LDFLAGS = -Wl,-M$(srcdir)/gamin_sym.version \
++ -version-info @FAM_VERSION_INFO@ @THREAD_LIBS@
++else
++libfam_la_LDFLAGS = -Wl,--version-script=$(srcdir)/gamin_sym.version \
+ -version-info @FAM_VERSION_INFO@ @THREAD_LIBS@
++endif
+
+ #
+ # Compile a program locally to check
+Index: gamin/configure.in
+===================================================================
+--- gamin/configure.in (revision 325)
++++ gamin/configure.in (working copy)
+@@ -1,13 +1,12 @@
+ dnl Process this file with autoconf to produce a configure script.
+-
+-# get any external flags setting before we start playing with the CFLAGS variable
+-ENV_CFLAGS=$CFLAGS
+-
+ AC_PREREQ(2.52)
+ AC_INIT(libgamin)
+ AM_CONFIG_HEADER(config.h)
+ AC_CANONICAL_SYSTEM
+
++# get any external flags setting before we start playing with the CFLAGS variable
++ENV_CFLAGS="$CFLAGS"
++
+ GAMIN_MAJOR_VERSION=0
+ GAMIN_MINOR_VERSION=1
+ GAMIN_MICRO_VERSION=9
+@@ -37,6 +36,18 @@
+ AC_PROG_INSTALL
+ AC_PROG_MAKE_SET
+
++dnl If the user set no CFLAGS, then don't assume the autotools defaults of
++dnl "-g -O2". We set default CFLAGS later based on the --disable-debug flag.
++if test -z "$ENV_CFLAGS"; then
++ CFLAGS=""
++fi
++
++dnl If the user set no CFLAGS, then don't assume the autotools defaults of
++dnl "-g -O2". We set default CFLAGS later based on the --disable-debug flag.
++if test -z "$ENV_CFLAGS"; then
++ CFLAGS=""
++fi
++
+ dnl for the spec file
+ RELDATE=`date +'%a %b %e %Y'`
+ AC_SUBST(RELDATE)
+@@ -248,6 +259,46 @@
+ backends="${backends}, kqueue"
+ fi
+
++case "$os" in
++ solaris*)
++ PKG_CHECK_MODULES(GTHREAD, gthread-2.0 >= 2.14.0)
++ AC_SUBST(GTHREAD_CFLAGS)
++ AC_SUBST(GTHREAD_LIBS)
++ AM_CONDITIONAL(ON_SOLARIS, true)
++ AC_COMPILE_IFELSE([
++ #include <port.h>
++ #ifndef PORT_SOURCE_FILE
++ #error "Please upgrade to Nevada 72 or above to suppoert FEN"
++ #endif
++ int main() { return 0; }
++ ],[have_fen=1],)
++ if test x$have_fen = x1 ; then
++ AC_ARG_ENABLE(fen,
++ AC_HELP_STRING([--disable-fen], [Disable the FEN backend]),
++ [fen="${enableval}"], [fen=true])
++
++ if test x$fen = xyes; then
++ fen=true
++ elif test x$fen = xno; then
++ fen=false
++ elif test x$fen != xtrue; then
++ AC_MSG_ERROR(bad value ${enableval} for --disable-fen)
++ fi
++ fi
++ break;
++ ;;
++ *)
++ fen=false
++ break;
++ ;;
++esac
++
++AM_CONDITIONAL(ENABLE_FEN, test x$fen = xtrue)
++if test x$fen = xtrue; then
++ AC_DEFINE(ENABLE_FEN,1,[Use Solaris FEN as backend])
++ backends="${backends}, FEN"
++fi
++
+ dnl pthread support for reentrance of the client library.
+ AC_ARG_WITH(threads,
+ [ --with-threads add multithread support(on)])
+@@ -354,6 +405,14 @@
+ AC_DEFINE(HAVE_CMSGCRED,1,[Have cmsgcred structure])
+ fi
+
++dnl Check for getpeerucred support - Solaris
++
++AC_CHECK_HEADER(ucred.h,
++ AC_CHECK_LIB(c, getpeerucred,[
++ AC_DEFINE([HAVE_GETPEERUCRED],[],[Define if has getpeerucred])
++ AC_DEFINE([HAVE_UCRED_H],[],[Define if <ucred.h> exists])]))
++
++
+ #### Abstract sockets
+
+ AC_MSG_CHECKING(abstract socket namespace)
+@@ -501,49 +560,60 @@
+ AC_SUBST(PYTHON_INCLUDES)
+ AC_SUBST(PYTHON_SITE_PACKAGES)
+
++dnl Check for -lsocket -lnsl
++
++AC_CHECK_FUNC(gethostent, , AC_CHECK_LIB(nsl, gethostent))
++AC_CHECK_FUNC(setsockopt, , AC_CHECK_LIB(socket, setsockopt))
++
++dnl Check for <sys/mnttab.h>
++
++AC_CHECK_HEADER(sys/mnttab.h,
++ AC_DEFINE([HAVE_SYS_MNTTAB_H], [], [Define if <sys/mnttab.h> is there]))
++
+ dnl After all config-related tweaking of CFLAGS, set it to its "build" value
+
+ AC_MSG_CHECKING(for more compiler warnings)
+ if test "$GCC" = "yes" -a "$set_more_warnings" != "no"; then
+ AC_MSG_RESULT(yes)
+- CFLAGS="\
++ warning_cflags="\
+ -Wall\
+ -Wchar-subscripts -Wmissing-declarations -Wmissing-prototypes\
+ -Wnested-externs\
+ -Wsign-compare"
+
++ SAVE_CFLAGS="$CFLAGS"
+ for option in -Wno-sign-compare; do
+- SAVE_CFLAGS="$CFLAGS"
+- CFLAGS="$CFLAGS $option"
++ CFLAGS="$option"
+ AC_MSG_CHECKING([whether gcc understands $option])
+ AC_TRY_COMPILE([], [],
+ has_option=yes,
+ has_option=no,)
+- if test $has_option = no; then
+- CFLAGS="$SAVE_CFLAGS"
++ if test "$has_option" != "no"; then
++ warning_cflags="$warning_cflags $option"
+ fi
+ AC_MSG_RESULT($has_option)
+ unset has_option
+- unset SAVE_CFLAGS
+ done
++ CFLAGS="$SAVE_CFLAGS"
+ unset option
+ else
+ AC_MSG_RESULT(no)
+- unset CFLAGS
+ fi
+
+ if test "$GCC" = "yes"; then
+ if test "$debug" = "yes"; then
+- CFLAGS="$CFLAGS -g"
++ debug_cflags="-g"
+ else
+- #don't optimise with -g
++ # autotools defaults to "-O2 -g" for cflags, but we don't
++ # want -g in non-debug builds
+ if test -z "$ENV_CFLAGS"; then
+- ENV_CFLAGS="-O2"
++ CFLAGS="-O2"
+ fi
+ fi
+ fi
+
+-CFLAGS="$CFLAGS $ENV_CFLAGS"
++AM_CFLAGS="$warning_cflags $debug_cflags"
++AC_SUBST(AM_CFLAGS)
+
+ dnl ==========================================================================
+
+@@ -569,7 +639,7 @@
+ prefix: ${prefix}
+ source code location: ${srcdir}
+ compiler: ${CC}
+- compiler flags: ${CFLAGS}
++ compiler flags: ${AM_CFLAGS} ${CFLAGS}
+
+ backends: ${backends}
+ build documentation: ${build_docs}
+Index: gamin/tests/testing.c
+===================================================================
+--- gamin/tests/testing.c (revision 328)
++++ gamin/tests/testing.c (working copy)
+@@ -1,3 +1,4 @@
++#include "config.h"
+ #include <stdio.h>
+ #include <stdlib.h>
+ #include <unistd.h>
+@@ -31,6 +32,11 @@
+
+ #define IS_BLANK(p) ((*(p) == ' ') || (*(p) == '\t') || \
+ (*(p) == '\n') || (*(p) == '\r'))
++#ifdef ENABLE_FEN
++#define KILLCMD "pkill"
++#else
++#define KILLCMD "killall"
++#endif
+
+ static int
+ scanCommand(char *line, char **command, char **arg, char **arg2)
+@@ -268,13 +274,13 @@
+ * okay, it's heavy but that's the simplest way since we do not have
+ * the pid(s) of the servers running.
+ */
+- ret = system("killall gam_server");
++ ret = system(KILLCMD" gam_server");
+ if (ret < 0) {
+ fprintf(stderr, "kill line %d: failed to killall gam_server\n",
+ no);
+ return (-1);
+ }
+- printf("killall gam_server\n");
++ printf(KILLCMD" gam_server\n");
+ } else if (!strcmp(command, "disconnect")) {
+ if (testState.connected == 0) {
+ fprintf(stderr, "disconnect line %d: not connected\n", no);
+Index: gamin/server/gam_server.h
+===================================================================
+--- gamin/server/gam_server.h (revision 328)
++++ gamin/server/gam_server.h (working copy)
+@@ -16,7 +16,8 @@
+ GAMIN_K_INOTIFY = 2,
+ GAMIN_K_KQUEUE = 3,
+ GAMIN_K_MACH = 4,
+- GAMIN_K_INOTIFY2 = 5
++ GAMIN_K_INOTIFY2 = 5,
++ GAMIN_K_FEN = 6
+ } GamKernelHandler;
+
+ typedef enum {
+Index: gamin/server/gam_fen.h
+===================================================================
+--- gamin/server/gam_fen.h (revision 0)
++++ gamin/server/gam_fen.h (revision 0)
+@@ -0,0 +1,20 @@
++/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:set expandtab ts=4 shiftwidth=4: */
++
++#ifndef __GAM_FEN_H__
++#define __GAM_FEN_H__
++
++#include <glib.h>
++#include "gam_subscription.h"
++
++G_BEGIN_DECLS
++
++gboolean gam_fen_init (void);
++gboolean gam_fen_add_subscription (GamSubscription *sub);
++gboolean gam_fen_remove_subscription (GamSubscription *sub);
++gboolean gam_fen_remove_all_for (GamListener *listener);
++
++G_END_DECLS
++
++#endif /* __GAM_FEN_H__ */
++
+Index: gamin/server/fen-thread-pool.c
+===================================================================
+--- gamin/server/fen-thread-pool.c (revision 0)
++++ gamin/server/fen-thread-pool.c (revision 0)
+@@ -0,0 +1,216 @@
++/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:set expandtab ts=4 shiftwidth=4: */
++
++#include <strings.h>
++#include <sys/types.h>
++#include <sys/stat.h>
++#include <sys/time.h>
++#include <errno.h>
++#include <stdio.h>
++#include <stdlib.h>
++#include <unistd.h>
++#include "fen-thread-pool.h"
++
++typedef struct thread_data th_data_t;
++
++struct thread_pool_data {
++ pthread_t *threads;
++ pthread_mutex_t q_mutex; /* protect the queue */
++ pthread_cond_t q_cond;
++ th_data_t *q_head; /* queue of data */
++ th_data_t *q_tail;
++ long q_len;
++
++ // pthread_mutex_t mutex; protect the following
++ long max_t;
++ long max_q;
++};
++
++struct thread_data {
++ thp_run_cb run;
++ thp_data_destroy_cb destroy;
++ void *data;
++ struct thread_data *prev;
++ struct thread_data *next;
++};
++
++static void *thread_run (void * data);
++#define MUTEX_LOCK(lock) \
++do { \
++ if (pthread_mutex_lock(&lock) != 0) { \
++ abort (); \
++ } \
++} while (0)
++#define MUTEX_UNLOCK(lock) \
++do { \
++ if (pthread_mutex_unlock(&lock) != 0) { \
++ abort (); \
++ } \
++} while (0)
++
++static void *
++thread_run (void * data)
++{
++ thp_t *tp = (thp_t *) data;
++ int ret;
++ th_data_t *td = NULL;
++
++ for (;;) {
++ MUTEX_LOCK (tp->q_mutex);
++ while (tp->q_len == 0) {
++ if ((ret = pthread_cond_wait(&tp->q_cond, &tp->q_mutex)) != 0) {
++ perror ("thread_run - pthread_cond_wait");
++ exit (1);
++ }
++ }
++
++ td = tp->q_head;
++ if (--tp->q_len == 0) {
++ tp->q_head = tp->q_tail = NULL;
++ } else {
++ tp->q_head = td->next;
++ tp->q_head->prev = NULL;
++ }
++
++ MUTEX_UNLOCK (tp->q_mutex);
++
++ /* run task */
++ td->run (td->data);
++ if (td->destroy) {
++ td->destroy (td->data);
++ }
++ free (td);
++ td = NULL;
++ }
++}
++
++/**
++ * Create a thread pool, returns the pointer. Return NULL if failed.
++ */
++
++extern thp_t *
++thread_pool_new (int max_thread_num, int max_queue_num)
++{
++ thp_t *tp;
++ int i, ret;
++
++ tp = (thp_t *) calloc (1, sizeof (thp_t));
++ if (tp == NULL)
++ return NULL;
++
++ tp->max_t = max_thread_num;
++ tp->max_q = max_queue_num;
++ if ((ret = pthread_mutex_init (&tp->q_mutex, NULL)) != 0) {
++ perror ("thread_pool_new - pthread_mutex_init");
++ free (tp);
++ return NULL;
++ }
++ if ((ret = pthread_cond_init(&tp->q_cond, NULL)) != 0) {
++ perror ("thread_pool_new - pthread_cond_init");
++ if (pthread_mutex_destroy (&tp->q_mutex) != 0) {
++ perror ("thread_pool_new - pthread_mutex_destroy");
++ }
++ free (tp);
++ return NULL;
++ }
++ tp->threads = (pthread_t *) calloc (max_thread_num, sizeof(pthread_t));
++ if (tp->threads == NULL) {
++ free (tp);
++ return NULL;
++ }
++ for (i = 0; i < max_thread_num; i++) {
++ if (pthread_create (&tp->threads[i], NULL,
++ thread_run,
++ (void *)tp) != 0) {
++ perror ("thread_pool_new - pthread_create");
++ exit (1);
++ }
++ }
++ return tp;
++}
++
++/**
++ * Dostroy a thread pool.
++ *
++ * (Not finished yet, shouldn't be invoked.)
++ */
++
++extern void
++thread_pool_destroy (thp_t *tp, int wait)
++{
++ int i;
++ /* clean the queue first */
++ /* clean up the resources */
++ if (pthread_mutex_destroy (&tp->q_mutex) != 0) {
++ perror ("pthread_mutex_destroy");
++ }
++ if (pthread_cond_destroy (&tp->q_cond) != 0) {
++ perror ("pthread_cond_destroy");
++ }
++ for (i = 0; i < tp->max_t; i++) {
++ pthread_join (tp->threads[i], NULL);
++ }
++ free (tp);
++}
++
++/**
++ * Run a task via run_cb.
++ * Returns 0 if successful.
++ */
++
++extern int
++thread_pool_run (thp_t *tp, thp_run_cb run_cb, void *data)
++{
++ return thread_pool_run_full (tp, run_cb, data, NULL);
++}
++
++/**
++ * Run a task via run_cb. After run_cb, the data will destroyed by destroy_cb.
++ * Returns 0 if successful.
++ */
++
++extern int
++thread_pool_run_full (thp_t *tp,
++ thp_run_cb run_cb,
++ void *data,
++ thp_data_destroy_cb destroy_cb)
++{
++ th_data_t *td = NULL;
++ int ret;
++
++ if ((td = (th_data_t *) calloc (1, sizeof (th_data_t))) == NULL) {
++ return errno;
++ }
++
++ td->run = run_cb;
++ td->data = data;
++ td->destroy = destroy_cb;
++
++ /* ready for adding */
++ MUTEX_LOCK (tp->q_mutex);
++
++ if (tp->q_len >= tp->max_q) {
++ /* is full */
++ MUTEX_UNLOCK (tp->q_mutex);
++ free (td);
++ return tp->max_q;
++ }
++
++ if (tp->q_len == 0) {
++ tp->q_tail = tp->q_head = td;
++ } else {
++ td->prev = tp->q_tail;
++ tp->q_tail->next = td;
++ tp->q_tail = td;
++ }
++ tp->q_len ++;
++
++ if ((ret = pthread_cond_broadcast(&tp->q_cond)) != 0) {
++ perror ("thread_pool_run_full - pthread_cond_wait");
++ abort ();
++ }
++
++ MUTEX_UNLOCK (tp->q_mutex);
++
++ return 0;
++}
+Index: gamin/server/fen-thread-pool.h
+===================================================================
+--- gamin/server/fen-thread-pool.h (revision 0)
++++ gamin/server/fen-thread-pool.h (revision 0)
+@@ -0,0 +1,22 @@
++/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:set expandtab ts=4 shiftwidth=4: */
++
++#include <pthread.h>
++
++#ifndef _FEN_THREAD_POOL_H_
++#define _FEN_THREAD_POOL_H_
++
++typedef struct thread_pool_data thp_t;
++
++typedef void (*thp_run_cb) (void *data);
++typedef void (*thp_data_destroy_cb) (void *data);
++
++extern thp_t *thread_pool_new (int max_thread_num, int max_queue_num);
++extern void thread_pool_destroy (thp_t *tp, int wait);
++extern int thread_pool_run (thp_t *tp, thp_run_cb run_cb, void *data);
++extern int thread_pool_run_full (thp_t *tp,
++ thp_run_cb run_cb,
++ void *data,
++ thp_data_destroy_cb destroy_cb);
++
++#endif /* _FEN_THREAD_POOL_H_ */
+Index: gamin/server/gam_channel.c
+===================================================================
+--- gamin/server/gam_channel.c (revision 328)
++++ gamin/server/gam_channel.c (working copy)
+@@ -7,6 +7,12 @@
+ #include <sys/stat.h>
+ #include <sys/un.h>
+ #include <sys/uio.h>
++#if defined(sun)
++#include <string.h>
++#endif
++#if defined(HAVE_UCRED_H)
++#include <ucred.h>
++#endif defined(HAVE_UCRED_H)
+ #include "gam_error.h"
+ #include "gam_connection.h"
+ #include "gam_channel.h"
+@@ -101,6 +107,10 @@
+ } cmsg;
+ #endif
+
++#if defined(HAVE_GETPEERUCRED)
++ ucred_t *creds;
++#endif
++
+ s_uid = getuid();
+
+ #if defined(LOCAL_CREDS) && defined(HAVE_CMSGCRED)
+@@ -167,11 +177,25 @@
+ fd, cr_len, (int) sizeof(cr));
+ goto failed;
+ }
++#elif defined(HAVE_GETPEERUCRED)
++ if ((creds = (ucred_t *)malloc(ucred_size()))==(ucred_t *)NULL){
++ GAM_DEBUG(DEBUG_INFO,"Malloc failed for ucreds");
++ goto failed;
++ }
++
++ if (getpeerucred(fd, &creds)!=0){
++ GAM_DEBUG(DEBUG_INFO,"getpeerucred call failed");
++ goto failed;
++ }
++ c_uid = ucred_getruid(creds);
++ c_gid = ucred_getrgid(creds);
++ c_pid = ucred_getpid(creds);
++ ucred_free(creds);
+ #elif defined(HAVE_CMSGCRED)
+ c_pid = cmsg.cred.cmcred_pid;
+ c_uid = cmsg.cred.cmcred_euid;
+ c_gid = cmsg.cred.cmcred_groups[0];
+-#else /* !SO_PEERCRED && !HAVE_CMSGCRED */
++#else /* !SO_PEERCRED && !HAVE_CMSGCRED && !HAVE_GETPEERUCRED */
+ GAM_DEBUG(DEBUG_INFO,
+ "Socket credentials not supported on this OS\n");
+ goto failed;
+Index: gamin/server/gam_fs.c
+===================================================================
+--- gamin/server/gam_fs.c (revision 328)
++++ gamin/server/gam_fs.c (working copy)
+@@ -7,9 +7,20 @@
+ #include <string.h>
+ #include <errno.h>
+ #include <glib.h>
++#ifdef HAVE_SYS_MNTTAB_H
++#include <sys/mnttab.h>
++#endif
+ #include "gam_error.h"
+ #include "gam_fs.h"
+
++#ifdef HAVE_SYS_MNTTAB_H
++#define MTAB MNTTAB
++#define MTABDEL "\t"
++#else
++#define MTAB "/etc/mtab"
++#define MTABDEL "\t"
++#endif
++
+ #define DEFAULT_POLL_TIMEOUT 0
+
+ typedef struct _gam_fs_properties {
+@@ -119,7 +130,7 @@
+ gam_fs *fs = NULL;
+ int i;
+
+- g_file_get_contents ("/etc/mtab", &contents, &len, NULL);
++ g_file_get_contents (MTAB, &contents, &len, NULL);
+ if (contents == NULL)
+ return;
+
+@@ -133,7 +144,7 @@
+ if (line[0] == '\0')
+ continue;
+
+- words = g_strsplit (line, " ", 0);
++ words = g_strsplit (line, MTABDEL, 0);
+
+ if (words == NULL)
+ continue;
+@@ -176,19 +187,23 @@
+ gam_fs_set ("ext2", GFS_MT_DEFAULT, 0);
+ gam_fs_set ("reiser4", GFS_MT_DEFAULT, 0);
+ gam_fs_set ("reiserfs", GFS_MT_DEFAULT, 0);
++ gam_fs_set ("nfs", GFS_MT_DEFAULT, 0);
++ gam_fs_set ("zfs", GFS_MT_DEFAULT, 0);
++ gam_fs_set ("ufs", GFS_MT_DEFAULT, 0);
++ gam_fs_set ("vxfs", GFS_MT_DEFAULT, 0);
+ gam_fs_set ("novfs", GFS_MT_POLL, 30);
+- gam_fs_set ("nfs", GFS_MT_POLL, 5);
+- if (stat("/etc/mtab", &mtab_sbuf) != 0)
++
++ if (stat(MTAB, &mtab_sbuf) != 0)
+ {
+- GAM_DEBUG(DEBUG_INFO, "Could not stat /etc/mtab\n");
++ GAM_DEBUG(DEBUG_INFO, "Could not stat %s\n",MTAB);
+ }
+ gam_fs_scan_mtab ();
+ } else {
+ struct stat sbuf;
+
+- if (stat("/etc/mtab", &sbuf) != 0)
++ if (stat(MTAB, &sbuf) != 0)
+ {
+- GAM_DEBUG(DEBUG_INFO, "Could not stat /etc/mtab\n");
++ GAM_DEBUG(DEBUG_INFO, "Could not stat %s\n",MTAB);
+ }
+
+ /* /etc/mtab has changed */
+Index: gamin/server/gam_fs.h
+===================================================================
+--- gamin/server/gam_fs.h (revision 328)
++++ gamin/server/gam_fs.h (working copy)
+@@ -8,6 +8,7 @@
+ #if !defined(ENABLE_DNOTIFY) && \
+ !defined(ENABLE_INOTIFY) && \
+ !defined(ENABLE_KQUEUE) && \
++ !defined(ENABLE_FEN) && \
+ !defined(ENABLE_HURD_MACH_NOTIFY)
+ GFS_MT_DEFAULT = GFS_MT_POLL,
+ #else
+Index: gamin/server/fen-data.c
+===================================================================
+--- gamin/server/fen-data.c (revision 0)
++++ gamin/server/fen-data.c (revision 0)
+@@ -0,0 +1,322 @@
++/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:set expandtab ts=4 shiftwidth=4: */
++
++#include "config.h"
++#include <sys/types.h>
++#include <strings.h>
++#include <fcntl.h>
++#include <unistd.h>
++#include <pthread.h>
++#include <sys/param.h>
++#include <sys/time.h>
++#include <sys/stat.h>
++#include <errno.h>
++#include <stdio.h>
++#include <stdlib.h>
++#include "gam_error.h"
++#include "fen-data.h"
++#include "fen-kernel.h"
++
++//#define FEN_DEBUG_ALL
++static GHashTable *fnode_hash = NULL;
++static pthread_mutex_t fnode_hash_mutex = PTHREAD_MUTEX_INITIALIZER;
++
++#define MUTEX_LOCK(lock) \
++do { \
++ if (pthread_mutex_lock(&lock) != 0) { \
++ g_assert_not_reached (); \
++ } \
++} while (0)
++
++#define MUTEX_UNLOCK(lock) \
++do { \
++ if (pthread_mutex_unlock(&lock) != 0) { \
++ g_assert_not_reached (); \
++ } \
++} while (0)
++
++int
++fnode_trylock (pthread_mutex_t *mutex)
++{
++ int ret;
++ if ((ret = pthread_mutex_trylock (mutex)) == 0) {
++ return 0;
++ } else if (ret != EBUSY) {
++ perror ("fnode_trylock");
++ abort ();
++ }
++ return 1;
++}
++
++fnode_t *
++fnode_new (const gchar *path, port_emit_events_cb emit_events)
++{
++ fnode_t *fn = NULL;
++
++ g_assert (path && path[0]);
++ if ((fn = calloc (1, sizeof (fnode_t))) != NULL) {
++ if (pthread_mutex_init (&fn->ref_lock, NULL) != 0) {
++ perror ("fnode_new - pthread_mutex_init");
++ free (fn);
++ return NULL;
++ }
++ if (pthread_mutex_init (&fn->fn_lock, NULL) != 0) {
++ perror ("fnode_new - pthread_mutex_init");
++ free (fn);
++ return NULL;
++ }
++ if (pthread_mutex_init (&fn->prop_lock, NULL) != 0) {
++ perror ("fnode_new - pthread_mutex_init");
++ free (fn);
++ return NULL;
++ }
++ FN_PATH(fn) = strdup (path);
++ fn->emit_events = emit_events;
++ fn->ref = 1;
++ }
++ return fn;
++}
++
++void
++fnode_delete (fnode_t *fn)
++{
++ g_assert (fn->ref == 0);
++ g_assert (fn->subs == NULL);
++ GAM_DEBUG(DEBUG_INFO, "FENDATA : FNODE_DELETE 0x%p %s\n", fn, FN_PATH(fn));
++ if (pthread_mutex_destroy (&fn->prop_lock) != 0) {
++ g_assert_not_reached ();
++ }
++ if (pthread_mutex_destroy (&fn->fn_lock) != 0) {
++ g_assert_not_reached ();
++ }
++ if (pthread_mutex_destroy (&fn->ref_lock) != 0) {
++ g_assert_not_reached ();
++ }
++ free (FN_PATH(fn));
++ free (fn);
++}
++
++void
++fnode_set_stat (fnode_t *f, struct stat *sbuf)
++{
++ file_obj_t *fobj = &f->fobj;
++
++ MUTEX_LOCK (f->prop_lock);
++ fobj->fo_atime = sbuf->st_atim;
++ fobj->fo_mtime = sbuf->st_mtim;
++ fobj->fo_ctime = sbuf->st_ctim;
++ f->is_dir = S_ISDIR (sbuf->st_mode) ? TRUE : FALSE;
++ MUTEX_UNLOCK (f->prop_lock);
++}
++
++int
++fnode_mdcount (fnode_t *f)
++{
++ int count;
++ MUTEX_LOCK (f->prop_lock);
++ g_assert (f->md_ref >= 0);
++ count = f->md_ref;
++ MUTEX_UNLOCK (f->prop_lock);
++ return count;
++}
++
++void
++fnode_mdref (fnode_t *f)
++{
++ MUTEX_LOCK (f->prop_lock);
++ f->md_ref ++;
++ g_assert (f->md_ref > 0);
++ MUTEX_UNLOCK (f->prop_lock);
++}
++
++void
++fnode_mdunref (fnode_t *f)
++{
++ MUTEX_LOCK (f->prop_lock);
++ g_assert (f->md_ref > 0);
++ f->md_ref --;
++ MUTEX_UNLOCK (f->prop_lock);
++}
++
++void
++fnode_ref (fnode_t *f)
++{
++ MUTEX_LOCK (f->ref_lock);
++ f->ref ++;
++ MUTEX_UNLOCK (f->ref_lock);
++}
++
++void
++fnode_unref (fnode_t *f)
++{
++ MUTEX_LOCK (f->ref_lock);
++ f->ref --;
++ MUTEX_UNLOCK (f->ref_lock);
++}
++
++gboolean
++fnode_is_dir (fnode_t *f)
++{
++ gboolean retval;
++
++ MUTEX_LOCK (f->prop_lock);
++ retval = f->is_dir;
++ MUTEX_UNLOCK (f->prop_lock);
++ return retval;
++}
++
++void
++fnode_emit_events (fnode_t *f, int events)
++{
++ g_assert (f->emit_events);
++ f->emit_events (f, events);
++}
++
++gboolean
++fn_hash_init ()
++{
++ fnode_hash = g_hash_table_new(g_str_hash, g_str_equal);
++}
++
++fnode_t *
++fn_hash_safe_lookup (const gchar *file_name)
++{
++ fnode_t *fn;
++
++ MUTEX_LOCK (fnode_hash_mutex);
++ fn = (fnode_t *) g_hash_table_lookup (fnode_hash, file_name);
++ MUTEX_UNLOCK (fnode_hash_mutex);
++ return fn;
++}
++
++fnode_t *
++fn_hash_safe_node_new (const gchar *file_name,
++ port_emit_events_cb emit_events)
++{
++ fnode_t *fn;
++
++ MUTEX_LOCK (fnode_hash_mutex);
++ fn = (fnode_t *) g_hash_table_lookup (fnode_hash, file_name);
++ if (fn == NULL) {
++ /* there is no lock now */
++ fn = fnode_new (file_name, emit_events);
++ if (fn != NULL) {
++ GAM_DEBUG(DEBUG_INFO, "FENDATA : FNODE_NEW 0x%p %s\n", fn, FN_PATH(fn));
++ g_hash_table_insert (fnode_hash, (gpointer) FN_PATH(fn), fn);
++ }
++ } else {
++ fnode_ref (fn);
++ }
++ MUTEX_UNLOCK (fnode_hash_mutex);
++ return fn;
++}
++
++void
++fn_hash_safe_foreach (GHFunc func, gpointer user_data)
++{
++ MUTEX_LOCK (fnode_hash_mutex);
++ g_hash_table_foreach (fnode_hash, func, user_data);
++ MUTEX_UNLOCK (fnode_hash_mutex);
++}
++
++void
++fn_hash_safe_foreach_remove (GHRFunc func, gpointer user_data)
++{
++ MUTEX_LOCK (fnode_hash_mutex);
++ g_hash_table_foreach_remove (fnode_hash, func, user_data);
++ MUTEX_UNLOCK (fnode_hash_mutex);
++}
++
++void
++fn_hash_foreach_remove (GHRFunc func, gpointer user_data)
++{
++ g_hash_table_foreach_remove (fnode_hash, func, user_data);
++}
++
++void
++fn_hash_lock ()
++{
++ MUTEX_LOCK (fnode_hash_mutex);
++}
++
++int
++fn_hash_trylock ()
++{
++ int ret;
++ if ((ret = pthread_mutex_trylock (&fnode_hash_mutex)) == 0) {
++ return 0;
++ } else if (ret != EBUSY) {
++ perror ("fn_hash_trylock");
++ abort ();
++ }
++ return 1;
++}
++
++void
++fn_hash_unlock ()
++{
++ MUTEX_UNLOCK (fnode_hash_mutex);
++}
++
++static void
++fn_hash_foreach_report_fnode (gpointer key,
++ gpointer value,
++ gpointer user_data)
++{
++ fnode_t *fn = (fnode_t *) value;
++ GList *idx;
++
++ g_assert (key == FN_PATH (fn));
++ GAM_DEBUG(DEBUG_INFO, "FENDATA : fnode 0x%p\n", fn);
++
++ GAM_DEBUG(DEBUG_INFO, "FENDATA : {\n");
++ MUTEX_LOCK (fn->fn_lock);
++ GAM_DEBUG(DEBUG_INFO, "FENDATA : %-14s = %s\n", "path", (gchar*)key);
++ GAM_DEBUG(DEBUG_INFO, "FENDATA : %-14s = %d\n", "ref", fn->ref);
++ GAM_DEBUG(DEBUG_INFO, "FENDATA %-14s = 0x%p\n", "parent", fn->parent);
++ GAM_DEBUG(DEBUG_INFO, "FENDATA : %-14s = 0x%p\n", "subs", fn->subs);
++
++ for (idx = fn->subs; idx; idx = idx->next) {
++ GAM_DEBUG(DEBUG_INFO, "FENDATA : {\n");
++ GAM_DEBUG(DEBUG_INFO, "FENDATA : %-4s = 0x%p\n", "p", idx->data);
++ GAM_DEBUG(DEBUG_INFO, "FENDATA : %-4s = %s\n", "s", gam_subscription_get_path(idx->data));
++ GAM_DEBUG(DEBUG_INFO, "FENDATA : }\n");
++ }
++
++ GAM_DEBUG(DEBUG_INFO, "FENDATA %-14s = 0x%p\n", "pnode", fn->pn);
++ MUTEX_UNLOCK (fn->fn_lock);
++ GAM_DEBUG(DEBUG_INFO, "FENDATA : }\n");
++}
++
++void
++fn_hash_safe_debug ()
++{
++ while (1) {
++ guint size;
++ GList *keys, *idx;
++
++ if (fn_hash_trylock () == 0) {
++ size = g_hash_table_size (fnode_hash);
++#ifdef FEN_DEBUG_ALL
++ if (size > 0) {
++ g_hash_table_foreach (fnode_hash,
++ fn_hash_foreach_report_fnode,
++ NULL);
++ }
++#else
++ if (size > 0 && size <= 2) {
++ g_hash_table_foreach (fnode_hash,
++ fn_hash_foreach_report_fnode,
++ NULL);
++ }
++#endif
++ MUTEX_UNLOCK (fnode_hash_mutex);
++ }
++#ifdef FEN_DEBUG_ALL
++ if (size > 0) {
++ GAM_DEBUG(DEBUG_INFO, "FENDATA : HASH HAS [ %6d ] NODES\n", size);
++ }
++#endif
++ sleep (10);
++ }
++}
+Index: gamin/server/fen-data.h
+===================================================================
+--- gamin/server/fen-data.h (revision 0)
++++ gamin/server/fen-data.h (revision 0)
+@@ -0,0 +1,73 @@
++/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:set expandtab ts=4 shiftwidth=4: */
++
++#include <port.h>
++#include <glib.h>
++
++#ifndef _FEN_DATA_H_
++#define _FEN_DATA_H_
++
++#define FN_PATH(fp) (((fnode_t *)(fp))->fobj.fo_name)
++
++typedef struct fnode fnode_t;
++typedef void (*port_emit_events_cb) (fnode_t *f, int events);
++
++struct fnode
++{
++ /* must be the first member */
++ file_obj_t fobj;
++
++ /* to identify if the path is dir */
++ gboolean is_dir;
++
++ /* the number of mondir subs this fnode has */
++ int md_ref;
++
++ /* protect above all */
++ pthread_mutex_t prop_lock;
++
++ /* state */
++ pthread_mutex_t ref_lock;
++ int ref;
++
++ /* lock/unlock by fn_hash, protect the following members */
++ pthread_mutex_t fn_lock;
++
++ /* List of subscriptions monitoring this fnode/path */
++ GList *subs;
++
++ /* the parent fnode monitoring the directory of this fnode/path */
++ fnode_t *parent;
++
++/* private data */
++
++ /* call backs */
++ port_emit_events_cb emit_events;
++
++ /* fen-kernel private */
++ void *pn;
++ gboolean is_poll;
++};
++
++/* fnode functions */
++fnode_t *fnode_new (const gchar *path, port_emit_events_cb emit_events);
++void fnode_delete (fnode_t *fn);
++void fnode_set_stat (fnode_t *f, struct stat *sbuf);
++gboolean fnode_is_dir (fnode_t *f);
++void fnode_emit_events (fnode_t *f, int events);
++
++/* path <-> fnode_t hash */
++gboolean fn_hash_init ();
++fnode_t *fn_hash_safe_node_new (const gchar *file_name,
++ port_emit_events_cb emit_events);
++fnode_t *fn_hash_safe_node_ref (fnode_t *f);
++fnode_t *fn_hash_safe_node_unref (fnode_t *f);
++fnode_t *fn_hash_safe_lookup (const gchar *file_name);
++gint fn_hash_safe_node_remove (fnode_t *f);
++void fn_hash_safe_foreach (GHFunc func, gpointer user_data);
++void fn_hash_safe_foreach_remove (GHRFunc func, gpointer user_data);
++void fn_hash_safe_debug ();
++
++/* fnode_t */
++
++#endif /* _FEN_DATA_H_ */
+Index: gamin/server/fen-kernel.c
+===================================================================
+--- gamin/server/fen-kernel.c (revision 0)
++++ gamin/server/fen-kernel.c (revision 0)
+@@ -0,0 +1,364 @@
++/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:set expandtab ts=4 shiftwidth=4: */
++
++#include <pthread.h>
++#include <rctl.h>
++#include <strings.h>
++#include <sys/types.h>
++#include <sys/stat.h>
++#include <sys/time.h>
++#include <errno.h>
++#include <stdio.h>
++#include <stdlib.h>
++#include <unistd.h>
++#include <glib.h>
++#include "fen-kernel.h"
++#include "gam_error.h"
++
++#define PE_ALLOC 128
++#define PNP_FD(pp) (((pnode_t *)(pp))->port)
++#define MON_EVENTS (/* FILE_ACCESS | */ FILE_MODIFIED | FILE_ATTRIB | FILE_NOFOLLOW)
++
++static ulong max_port_evnets = 512;
++static ulong max_port_events = 256;
++static GList *pn_vq; /* the queue of ports which don't have the max objs */
++static GList *pn_fq; /* the queue of ports which have the max objs */
++static pthread_mutex_t pn_queues_lock = PTHREAD_MUTEX_INITIALIZER;
++
++#define MUTEX_LOCK(lock) \
++do { \
++ if (pthread_mutex_lock(&lock) != 0) { \
++ abort (); \
++ } \
++} while (0)
++#define MUTEX_UNLOCK(lock) \
++do { \
++ if (pthread_mutex_unlock(&lock) != 0) { \
++ abort (); \
++ } \
++} while (0)
++
++static void pnode_list_walker_cb (gpointer data, gpointer udata);
++static void *port_fetch_event_cb (void *arg);
++static pnode_t *pnode_new ();
++static void pnode_delete (pnode_t *pn);
++
++struct pnode
++{
++ ulong ref; /* how many fds are associated to this port */
++ pthread_mutex_t m_ref; /* protect ref */
++ pthread_t tid;
++ int port;
++};
++
++enum {
++ CTL_CLOSE_PORT = 0,
++ CTL_JOIN_THREAD,
++ CTL_FREE_PNODE
++};
++
++void
++printevent(int event, char *pname)
++{
++ GAM_DEBUG(DEBUG_INFO, "FENKERNEL - %s :",pname);
++ if (event & FILE_ACCESS) {
++ GAM_DEBUG(DEBUG_INFO, " FILE_ACCESS");
++ }
++ if (event & FILE_MODIFIED) {
++ GAM_DEBUG(DEBUG_INFO, " FILE_MODIFIED");
++ }
++ if (event & FILE_ATTRIB) {
++ GAM_DEBUG(DEBUG_INFO, " FILE_ATTRIB");
++ }
++ if (event & FILE_DELETE) {
++ GAM_DEBUG(DEBUG_INFO, " FILE_DELETE");
++ }
++ if (event & FILE_RENAME_TO) {
++ GAM_DEBUG(DEBUG_INFO, " FILE_RENAME_TO");
++ }
++ if (event & FILE_RENAME_FROM) {
++ GAM_DEBUG(DEBUG_INFO, " FILE_RENAME_FROM");
++ }
++ if (event & UNMOUNTED) {
++ GAM_DEBUG(DEBUG_INFO, " UNMOUNTED");
++ }
++ if (event & MOUNTEDOVER) {
++ GAM_DEBUG(DEBUG_INFO, " MOUNTEDOVER");
++ }
++ GAM_DEBUG(DEBUG_INFO, "\n");
++}
++
++static void *
++port_fetch_event_cb (void *arg)
++{
++ pnode_t *pn = (pnode_t *)arg;
++ uint_t nget;
++ port_event_t pe[PE_ALLOC];
++ int ret;
++
++ GAM_DEBUG(DEBUG_INFO, "FENKERNEL : [THREAD] started [pn] 0x%p [tid] %d\n", pn, pn->tid);
++
++ while (1) {
++#if 0
++ /* first get the number of available events */
++ if (port_getn (PNP_FD(pn), pe, 0, &nget, NULL) == 0) {
++ if (nget > PE_ALLOC)
++ nget = PE_ALLOC;
++ } else {
++ break;
++ }
++#endif
++ nget = 1;
++ if (port_getn (PNP_FD(pn), pe, PE_ALLOC, &nget, NULL) == 0) {
++ int i;
++ for (i = 0; i < nget; i++) {
++ fnode_t *f;
++ f = (fnode_t *)pe[i].portev_user;
++ /* handle event */
++ printevent(pe[i].portev_events, FN_PATH(f));
++ MUTEX_LOCK (f->ref_lock);
++ g_assert (f->ref > 0);
++ pnode_delete (f->pn);
++ f->pn = NULL;
++ f->ref --;
++ MUTEX_UNLOCK (f->ref_lock);
++ fnode_emit_events (f, pe[i].portev_events);
++ }
++ } else {
++ break;
++ }
++ }
++ GAM_DEBUG(DEBUG_INFO, "FENKERNEL : [THREAD] stopped [pn] 0x%p [tid] %d\n", pn, pn->tid);
++ return (void *)0;
++}
++
++/*
++ * ref - 1 if remove a watching file succeeded.
++ */
++static void
++pnode_delete (pnode_t *pn)
++{
++ MUTEX_LOCK (pn->m_ref);
++ MUTEX_LOCK (pn_queues_lock);
++ if (pn->ref == max_port_evnets) {
++ GAM_DEBUG(DEBUG_INFO, "FENKERNEL : move to visible queue - [pn] 0x%p [ref] %d\n", pn, pn->ref);
++ pn_fq = g_list_remove (pn_fq, pn);
++ pn_vq = g_list_prepend (pn_vq, pn);
++ }
++ if ((-- pn->ref) == 0) {
++ // corrently we can't stop a thread except we close the port fd
++ // pthread_join (pn->tid, NULL);
++ // pn->tid = 0;
++ }
++ GAM_DEBUG(DEBUG_INFO, "FENKERNEL : pnode_delete: [pn] 0x%p [ref] %d\n", pn, pn->ref);
++ MUTEX_UNLOCK (pn_queues_lock);
++ MUTEX_UNLOCK (pn->m_ref);
++}
++
++/*
++ * malloc pnode_t and port_create, start thread at pnode_ref.
++ * if pnode_new succeeded, the pnode_t will never
++ * be freed. So pnode_t can be freed only in pnode_new.
++ * Note pnode_monitor_remove_all can also free pnode_t, but currently no one
++ * invork it.
++ */
++static pnode_t *
++pnode_new ()
++{
++ pnode_t *pn = NULL;
++
++ MUTEX_LOCK (pn_queues_lock);
++ if (pn_vq) {
++ pn = (pnode_t*)pn_vq->data;
++ g_assert (pn->ref < max_port_evnets);
++ } else {
++ pn = malloc (sizeof (pnode_t));
++ if (pn != NULL) {
++ bzero ((void *)pn, sizeof (pnode_t));
++ if (pthread_mutex_init (&pn->m_ref, NULL) != 0) {
++ perror ("pthread_mutex_init");
++ free (pn);
++ pn = NULL;
++ } else if ((PNP_FD(pn) = port_create ()) == -1) {
++ perror ("port_create");
++ free (pn);
++ pn = NULL;
++ } else if (pthread_create (&pn->tid, NULL,
++ port_fetch_event_cb,
++ (void *)pn) != 0) {
++ perror ("pthread_create");
++ free (pn);
++ pn = NULL;
++ } else {
++ g_assert (g_list_find (pn_vq, pn) == NULL);
++ pn_vq = g_list_prepend (pn_vq, pn);
++ }
++ }
++ }
++ if (pn) {
++ GAM_DEBUG(DEBUG_INFO, "FENKERNEL : pnode_new: [pn] 0x%p [ref] %d\n", pn, pn->ref);
++ pn->ref++;
++ if (pn->ref == max_port_evnets) {
++ GAM_DEBUG(DEBUG_INFO, "FENKERNEL : move to full queue - [pn] 0x%p [ref] %d\n", pn, pn->ref);
++ pn_vq = g_list_remove (pn_vq, pn);
++ pn_fq = g_list_prepend (pn_fq, pn);
++ g_assert (g_list_find (pn_vq, pn) == NULL);
++ }
++ }
++ MUTEX_UNLOCK (pn_queues_lock);
++ return pn;
++}
++
++/**
++ * There is no guarantee that one fobj must be associated to one port fd.
++ * And no guarantee that the same file will be added only once.
++ * So it's up level duty to make sure only add a fobj one time.
++ *
++ * Should call fnode_ref before call this function to protect the data.
++ * And call fnode_unref if receive FILE_EXCEPTION events.
++ *
++ * Returns 0 if succeeded, -1 already added.
++ */
++extern int
++port_monitor_add (fnode_t *f, struct stat * st)
++{
++ int ret;
++ struct stat buf;
++
++ MUTEX_LOCK (f->ref_lock);
++ if (f->pn == NULL) {
++ if (st == NULL) {
++ st = &buf;
++ if ((ret = lstat (FN_PATH(f), st)) != 0) {
++ ret = errno;
++ perror ("FENKERNEL - lstat");
++ MUTEX_UNLOCK (f->ref_lock);
++ return ret;
++ }
++ }
++ fnode_set_stat (f, st);
++
++ if ((f->pn = pnode_new ()) != NULL) {
++
++ if ((ret = port_associate (PNP_FD(f->pn),
++ PORT_SOURCE_FILE,
++ (uintptr_t)&f->fobj,
++ MON_EVENTS,
++ (void *)f)) == 0) {
++ f->ref ++;
++ GAM_DEBUG(DEBUG_INFO, "FENKERNEL : associated file %s\n",
++ FN_PATH(f));
++ } else {
++ perror ("FENKERNEL - port_associate");
++ pnode_delete (f->pn);
++ f->pn = NULL;
++ ret = errno;
++ }
++ } else {
++ ret = ENOMEM;
++ }
++ } else {
++ ret = -1;
++ }
++ MUTEX_UNLOCK (f->ref_lock);
++ return ret;
++}
++
++/**
++ * Should call fnode_unref if call this function successfully
++ *
++ * Return 0 if succeeded, -1 already removed.
++ */
++extern int
++port_monitor_remove (fnode_t *f)
++{
++ int ret;
++
++ MUTEX_LOCK (f->ref_lock);
++ if (f->pn) {
++ if ((ret = port_dissociate (PNP_FD(f->pn), PORT_SOURCE_FILE,
++ (uintptr_t)&f->fobj)) == 0) {
++ GAM_DEBUG(DEBUG_INFO, "FENKERNEL : dissociated file %s\n",
++ FN_PATH(f));
++ pnode_delete (f->pn);
++ f->pn = NULL;
++ f->ref --;
++ } else {
++ ret = errno;
++ perror ("FENKERNEL - port_dissociate");
++ }
++ } else {
++ ret = -1;
++ }
++ MUTEX_UNLOCK (f->ref_lock);
++ return ret;
++}
++
++static void
++port_list_walker_cb (gpointer data, gpointer udata)
++{
++ pnode_t *pn = (pnode_t *)data;
++ switch (GPOINTER_TO_INT (udata)) {
++ case CTL_CLOSE_PORT:
++ close (PNP_FD(pn));
++ break;
++ case CTL_JOIN_THREAD:
++ pthread_join (pn->tid, NULL);
++ break;
++ case CTL_FREE_PNODE:
++ {
++ if (pthread_mutex_destroy (&pn->m_ref) != 0) {
++ perror ("pthread_mutex_destroy");
++ }
++ /* free anyway */
++ free (pn);
++ break;
++ }
++ default:
++ break;
++ }
++}
++
++static void
++pnode_monitor_remove_all ()
++{
++ pnode_t *pn;
++
++ /* close ports */
++ MUTEX_LOCK (pn_queues_lock);
++ g_list_foreach (pn_vq, port_list_walker_cb, (gpointer)CTL_CLOSE_PORT);
++ g_list_foreach (pn_fq, port_list_walker_cb, (gpointer)CTL_CLOSE_PORT);
++ /* wait for threads */
++ g_list_foreach (pn_vq, port_list_walker_cb, (gpointer)CTL_JOIN_THREAD);
++ g_list_foreach (pn_fq, port_list_walker_cb, (gpointer)CTL_JOIN_THREAD);
++ /* free pnodes */
++ g_list_foreach (pn_vq, port_list_walker_cb, (gpointer)CTL_FREE_PNODE);
++ g_list_foreach (pn_fq, port_list_walker_cb, (gpointer)CTL_FREE_PNODE);
++ MUTEX_UNLOCK (pn_queues_lock);
++}
++
++/**
++ * Get Solaris resouce values.
++ *
++ */
++
++extern int
++fen_kernel_init ()
++{
++ rctlblk_t *rblk;
++
++ if ((rblk = malloc (rctlblk_size ())) == NULL) {
++ perror ("rblk malloc");
++ exit (1);
++ }
++ if (getrctl ("process.max-port-events", NULL, rblk, RCTL_FIRST) == -1) {
++ perror ("getrctl");
++ exit (1);
++ } else {
++ if (max_port_evnets > rctlblk_get_value(rblk))
++ max_port_evnets = rctlblk_get_value(rblk);
++ GAM_DEBUG(DEBUG_INFO, "FENKERNEL : max event of a port: %u\n", max_port_evnets);
++ }
++ free (rblk);
++ return 0;
++}
+Index: gamin/server/Makefile.am
+===================================================================
+--- gamin/server/Makefile.am (revision 328)
++++ gamin/server/Makefile.am (working copy)
+@@ -10,7 +10,7 @@
+ -DG_DISABLE_DEPRECATED
+
+ if GAMIN_DEBUG
+-INCLUDES += -DGAM_DEBUG_ENABLED
++INCLUDES += -DGAM_DEBUG_ENABLED -g
+ endif
+
+
+@@ -69,6 +69,13 @@
+ gam_server_SOURCES += gam_kqueue.c gam_kqueue.h
+ endif
+
++if ENABLE_FEN
++gam_server_SOURCES += gam_fen.c gam_fen.h \
++ fen-kernel.c fen-kernel.h \
++ fen-thread-pool.c fen-thread-pool.h \
++ fen-data.c fen-data.h
++endif
++
+ if ENABLE_HURD_MACH_NOTIFY
+ gam_server_SOURCES += gam_hurd_mach_notify.c gam_hurd_mach_notify.h
+
+@@ -79,6 +86,7 @@
+ @MIG@ -s -server $(top_builddir)/server/fs_notify.c $(includedir)/hurd/fs_notify.defs
+ endif
+
++gam_server_CFLAGS =
+ gam_server_LDFLAGS =
+ gam_server_DEPENDENCIES = $(DEPS)
+ gam_server_LDADD= $(top_builddir)/lib/libgamin_shared.a $(LDADDS) $(LIBGAMIN_LIBS)
+@@ -86,3 +94,8 @@
+ if ENABLE_HURD_MACH_NOTIFY
+ gam_server_LDADD += -lports -lthreads
+ endif
++
++if ENABLE_FEN
++gam_server_CFLAGS += $(GTHREAD_CFLAGS)
++gam_server_LDADD += $(GTHREAD_LIBS)
++endif
+Index: gamin/server/gam_server.c
+===================================================================
+--- gamin/server/gam_server.c (revision 328)
++++ gamin/server/gam_server.c (working copy)
+@@ -45,6 +45,9 @@
+ #ifdef ENABLE_HURD_MACH_NOTIFY
+ #include "gam_hurd_mach_notify.h"
+ #endif
++#ifdef ENABLE_FEN
++#include "gam_fen.h"
++#endif
+ #include "gam_excludes.h"
+ #include "gam_fs.h"
+ #include "gam_conf.h"
+@@ -162,6 +165,12 @@
+ return(TRUE);
+ }
+ #endif
++#ifdef ENABLE_FEN
++ if (gam_fen_init()) {
++ GAM_DEBUG(DEBUG_INFO, "Using fen as backend\n");
++ return(TRUE);
++ }
++#endif
+ }
+
+ if (gam_poll_basic_init()) {
+@@ -627,6 +636,10 @@
+ signal(SIGQUIT, gam_exit);
+ signal(SIGTERM, gam_exit);
+ signal(SIGPIPE, SIG_IGN);
++#ifdef ENABLE_FEN
++ signal(SIGUSR1, SIG_IGN);
++ signal(SIGUSR2, SIG_IGN);
++#endif
+
+ if (!gam_init_subscriptions()) {
+ GAM_DEBUG(DEBUG_INFO, "Could not initialize the subscription system.\n");
+Index: gamin/server/fen-kernel.h
+===================================================================
+--- gamin/server/fen-kernel.h (revision 0)
++++ gamin/server/fen-kernel.h (revision 0)
+@@ -0,0 +1,18 @@
++/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:set expandtab ts=4 shiftwidth=4: */
++
++#include <port.h>
++#include <fen-data.h>
++#include <sys/stat.h>
++
++#ifndef _FEN_KERNEL_H_
++#define _FEN_KERNEL_H_
++
++typedef struct pnode pnode_t;
++
++extern int fen_kernel_init ();
++extern int port_monitor_add (fnode_t *f, struct stat * st);
++extern int port_monitor_remove (fnode_t *f);
++extern void port_monitor_remove_all ();
++
++#endif /* _FEN_KERNEL_H_ */
+Index: gamin/server/gam_fen.c
+===================================================================
+--- gamin/server/gam_fen.c (revision 0)
++++ gamin/server/gam_fen.c (revision 0)
+@@ -0,0 +1,801 @@
++/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:set expandtab ts=4 shiftwidth=4: */
++/*
++ * Design:
++ * A Solaris port has a resource limit of events (port_max_events) which
++ * limits the number of objects (fds) that can be actively associated objects
++ * whith the port. The default is (65536), but can be changed.
++ *
++ * project.max-port-ids identify the max number of ports
++ * process.max-port-events identify the max objs of a port
++ * process.max-file-descriptor identify the max fds of a process
++ *
++ * For a user server process, process.max-file-descriptor seems a bottleneck.
++ * I will use a port list for monitor fds to avoid process.max-file-descriptor
++ * is greater than process.max-port-events.
++ */
++#include "config.h"
++#include <strings.h>
++#include <fcntl.h>
++#include <unistd.h>
++#include <sys/param.h>
++#include <sys/time.h>
++#include <sys/types.h>
++#include <stdlib.h>
++#include <errno.h>
++#include "gam_error.h"
++#include "gam_fen.h"
++#include "gam_event.h"
++#include "gam_server.h"
++#include "gam_protocol.h"
++#include "fen-kernel.h"
++#include "fen-thread-pool.h"
++#include "fen-data.h"
++#include <glib.h>
++
++//#define FEN_DEBUG
++#define GC_INTERVAL 15000 /* once every 15 second */
++#define POLL_INTERVAL 1500 /* poll once every 1.5 second */
++#define MAX_EVENT_THREADS 25
++#define MAX_EVENT_QUEUE_LEN G_MAXINT
++
++#define MSLEEP(ms) (usleep ((ms)*1000))
++#define MUTEX_LOCK(lock) \
++do { \
++ if (pthread_mutex_lock(&lock) != 0) { \
++ g_assert_not_reached (); \
++ } \
++} while (0)
++
++#define MUTEX_UNLOCK(lock) \
++do { \
++ if (pthread_mutex_unlock(&lock) != 0) { \
++ g_assert_not_reached (); \
++ } \
++} while (0)
++
++typedef struct event_pair
++{
++ int events;
++ fnode_t *f;
++} event_pair_t;
++
++static void
++ep_free (event_pair_t *ep)
++{
++ g_free (ep);
++}
++
++static GList *delete_pending_list = NULL;
++static thp_t *thread_pool = NULL;
++
++/* run by thread pool */
++static void events_process_task (event_pair_t *ep);
++static void poll_file_task (fnode_t *f);
++static void events_unsafe_emit (fnode_t *f,
++ GaminEventType event,
++ const gchar *path);
++static void find_new_file_in_dir (fnode_t *parent);
++
++/* others */
++static GaminEventType fen_to_gamin_event (int events);
++static void create_children_initially (const char *fullname,
++ fnode_t *parent);
++static gboolean fen_monitor_add (fnode_t *f);
++static gboolean poll_monitor_add (fnode_t *f);
++
++static int
++get_lstat (const char *path, struct stat *sbuf)
++{
++ int retval;
++ if ((retval = lstat (path, sbuf)) != 0) {
++ perror ("get_lstat");
++ retval = errno;
++ }
++ return retval;
++}
++
++static void
++publish_gam_events (fnode_t *f, int events)
++{
++ event_pair_t *ep;
++
++ fnode_ref (f);
++ ep = g_new (event_pair_t, 1);
++ ep->f = f;
++ ep->events = events;
++
++ if (thread_pool_run_full (thread_pool,
++ (thp_run_cb) events_process_task,
++ (gpointer) ep,
++ (GDestroyNotify) ep_free) != 0) {
++ GAM_DEBUG(DEBUG_INFO, "FEN : LOSING EVENTS ON %s\n", FN_PATH (f));
++ fnode_unref (f);
++ ep_free (ep);
++ }
++}
++
++static void
++publish_fen_events (fnode_t *f, int events)
++{
++ publish_gam_events (f, fen_to_gamin_event (events));
++}
++
++static gboolean
++fn_hash_foreach_free_fnode (gpointer key,
++ gpointer value,
++ gpointer user_data)
++{
++ fnode_t *f = (fnode_t *) value;
++ if (fnode_trylock (&f->fn_lock) == 0) {
++ if (fnode_trylock (&f->ref_lock) != 0) {
++ MUTEX_UNLOCK (f->fn_lock);
++ return FALSE;
++ }
++ if (f->ref != 0 ) {
++ MUTEX_UNLOCK (f->ref_lock);
++ MUTEX_UNLOCK (f->fn_lock);
++ return FALSE;
++ }
++ /* ref_lock and fn_lock are locked now */
++ if (f->parent == NULL && f->subs == NULL) {
++ gboolean ret;
++ if (f->pn || f->is_poll) {
++ MUTEX_UNLOCK (f->ref_lock);
++ port_monitor_remove (f->pn);
++ ret = FALSE;
++ MUTEX_UNLOCK (f->fn_lock);
++ } else {
++ /* we can free this f now */
++ delete_pending_list = g_list_prepend (delete_pending_list, f);
++ ret = TRUE;
++ MUTEX_UNLOCK (f->ref_lock);
++ MUTEX_UNLOCK (f->fn_lock);
++ }
++ return ret;
++ }
++ MUTEX_UNLOCK (f->ref_lock);
++ MUTEX_UNLOCK (f->fn_lock);
++ }
++ return FALSE;
++}
++
++/* occupy a thread */
++void
++gc_thread ()
++{
++ while (1) {
++ guint size;
++ GList *keys, *idx;
++
++ if (fn_hash_trylock () == 0) {
++ fn_hash_foreach_remove (fn_hash_foreach_free_fnode,
++ NULL);
++ fn_hash_unlock ();
++ }
++ if (delete_pending_list) {
++ g_list_foreach (delete_pending_list,
++ (GFunc) fnode_delete,
++ NULL);
++ g_list_free (delete_pending_list);
++ delete_pending_list = NULL;
++ }
++ MSLEEP (GC_INTERVAL);
++ }
++}
++
++static GaminEventType
++fen_to_gamin_event (int events)
++{
++ GaminEventType et = 0;
++
++ if (events & FILE_MODIFIED ||
++ events & FILE_ATTRIB ||
++ events & FILE_ACCESS ||
++ events & FILE_RENAME_TO ||
++ events & MOUNTEDOVER ||
++ events & UNMOUNTED) {
++ et |= GAMIN_EVENT_CHANGED;
++ }
++ if (events & FILE_DELETE ||
++ events & FILE_RENAME_FROM) {
++ et |= GAMIN_EVENT_DELETED;
++ }
++ return et;
++}
++
++static void
++events_unsafe_emit (fnode_t *f, GaminEventType events, const gchar *path)
++{
++ gboolean file_is_dir;
++ if (path == NULL) {
++ path = FN_PATH(f);
++ file_is_dir = fnode_is_dir (f);
++ } else {
++ struct stat fsb;
++ memset (&fsb, 0, sizeof (struct stat));
++ lstat (path, &fsb);
++ file_is_dir = S_ISDIR (fsb.st_mode) ? TRUE : FALSE;
++ }
++
++ if (events & GAMIN_EVENT_CREATED) {
++ gam_server_emit_event(path, file_is_dir,
++ GAMIN_EVENT_CREATED, f->subs, 1);
++ }
++ if (events & GAMIN_EVENT_CHANGED) {
++ GList *idx;
++ for (idx = f->subs; idx; idx = idx->next) {
++ GamSubscription *sub = (GamSubscription *)idx->data;
++ if (!gam_subscription_is_dir (sub)) {
++ gam_server_emit_one_event (path, file_is_dir, GAMIN_EVENT_CHANGED, sub, 1);
++ }
++ }
++ if (f->parent) {
++ MUTEX_LOCK (f->parent->fn_lock);
++ gam_server_emit_event(path, file_is_dir,
++ GAMIN_EVENT_CHANGED, f->parent->subs, 1);
++ MUTEX_UNLOCK (f->parent->fn_lock);
++ }
++ }
++ if (events & GAMIN_EVENT_DELETED) {
++ gam_server_emit_event(path, file_is_dir,
++ GAMIN_EVENT_DELETED, f->subs, 1);
++ if (f->parent) {
++ MUTEX_LOCK (f->parent->fn_lock);
++ gam_server_emit_event(path, file_is_dir,
++ GAMIN_EVENT_DELETED, f->parent->subs, 1);
++ MUTEX_UNLOCK (f->parent->fn_lock);
++ }
++ }
++ if (events & GAMIN_EVENT_MOVED) {
++ gam_server_emit_event(path, file_is_dir,
++ GAMIN_EVENT_MOVED, f->subs, 1);
++ if (f->parent) {
++ MUTEX_LOCK (f->parent->fn_lock);
++ gam_server_emit_event(path, file_is_dir,
++ GAMIN_EVENT_MOVED, f->parent->subs, 1);
++ MUTEX_UNLOCK (f->parent->fn_lock);
++ }
++ }
++}
++
++/**
++ * When got events from PORT, process them here.
++ * fnode of event_pair is refed before it's called, so should unref before
++ * it returns.
++ */
++
++static void
++events_process_task (event_pair_t *ep)
++{
++ int ret;
++ fnode_t *f = ep->f;
++ GaminEventType events = ep->events;
++
++ g_assert (f);
++ MUTEX_LOCK (f->ref_lock);
++ g_assert (f->ref > 0);
++ MUTEX_UNLOCK (f->ref_lock);
++
++ GAM_DEBUG(DEBUG_INFO, "FEN : events_process_task 0x%p %s\n", f, FN_PATH(f));
++
++ MUTEX_LOCK (f->fn_lock);
++ events_unsafe_emit (f, events, NULL);
++ if (f->parent == NULL && f->subs == NULL) {
++ fnode_unref (f);
++ MUTEX_UNLOCK (f->fn_lock);
++ return;
++ }
++ MUTEX_UNLOCK (f->fn_lock);
++
++ if (events & GAMIN_EVENT_CHANGED || events & GAMIN_EVENT_CREATED) {
++ switch (port_monitor_add (f, NULL)) {
++ case 0:
++ case -1:
++ /* scan for new created files if somebody mondir */
++ if (fnode_is_dir (f) && fnode_mdcount (f) > 0) {
++ find_new_file_in_dir (f);
++ }
++ break;
++ case ENOENT:
++ publish_gam_events (f, GAMIN_EVENT_DELETED);
++ break;
++ default:
++ if (!poll_monitor_add(f)) {
++ g_assert_not_reached ();
++ }
++ }
++ } else if (events & GAMIN_EVENT_DELETED || events == -1) {
++ /* if the parent dir is mondir,
++ unsub the parent's subs from mine. */
++ MUTEX_LOCK (f->fn_lock);
++ f->parent = NULL;
++ if (f->subs != NULL) {
++ /* turn to polling */
++ if (!poll_monitor_add(f)) {
++ g_assert_not_reached ();
++ }
++ }
++ MUTEX_UNLOCK (f->fn_lock);
++ }
++ fnode_unref (f);
++ GAM_DEBUG(DEBUG_INFO, "FEN : events_process_task 0x%p\n", f);
++}
++
++/**
++ * When a path can be monitored by PORT, we should thread to run stat(2) on
++ * and detect if the file node is created. After that we finish this thread
++ * and pass the node to PORT.
++ */
++
++static void
++poll_file_task (fnode_t *f)
++{
++ int ret;
++ struct stat buf;
++
++ //GAM_DEBUG(DEBUG_INFO, "FEN : poll_file_task %s\n", FN_PATH(f));
++
++ MUTEX_LOCK (f->fn_lock);
++ MUTEX_LOCK (f->ref_lock);
++ if (f->parent == NULL && f->subs == NULL) {
++ f->is_poll = FALSE;
++ f->ref --;
++ MUTEX_UNLOCK (f->ref_lock);
++ MUTEX_UNLOCK (f->fn_lock);
++ return;
++ }
++ MUTEX_UNLOCK (f->ref_lock);
++ MUTEX_UNLOCK (f->fn_lock);
++
++ if ((ret = get_lstat (FN_PATH(f), &buf)) == 0) {
++ switch (port_monitor_add (f, &buf)) {
++ case 0:
++ case -1:
++ MUTEX_LOCK (f->ref_lock);
++ f->is_poll = FALSE;
++ MUTEX_UNLOCK (f->ref_lock);
++ publish_gam_events (f, GAMIN_EVENT_CREATED);
++ /* scan for new created files if somebody mondir */
++ if (fnode_is_dir (f) && fnode_mdcount (f) > 0) {
++ find_new_file_in_dir (f);
++ }
++ break;
++ case ENOENT:
++ publish_gam_events (f, GAMIN_EVENT_DELETED);
++ default:
++ goto l_poll;
++ }
++ } else {
++ l_poll:
++ MSLEEP (POLL_INTERVAL);
++ MUTEX_LOCK (f->ref_lock);
++ f->is_poll = FALSE;
++ MUTEX_UNLOCK (f->ref_lock);
++ if (!poll_monitor_add(f)) {
++ g_assert_not_reached ();
++ }
++ }
++ fnode_unref (f);
++ return;
++}
++
++static gboolean
++poll_monitor_add (fnode_t *f)
++{
++ MUTEX_LOCK (f->ref_lock);
++ if (!f->is_poll) {
++ if (thread_pool_run (thread_pool,
++ (thp_run_cb) poll_file_task, f) == 0) {
++ //GAM_DEBUG(DEBUG_INFO, "FEN : [ polling ] %s\n", FN_PATH(f));
++ f->ref ++;
++ f->is_poll = TRUE;
++ } else {
++ MUTEX_UNLOCK (f->ref_lock);
++ return FALSE;
++ }
++ }
++ MUTEX_UNLOCK (f->ref_lock);
++ return TRUE;
++}
++
++/**
++ * If the fnode_t is a directory and GAMIN_EVENT_CHANGED happenned on it,
++ * Use it to detect what happenned in the first level of the directory.
++ *
++ * In thread
++ */
++
++static void
++find_new_file_in_dir (fnode_t *parent)
++{
++ GDir *dir;
++ GError *err = NULL;
++
++ GAM_DEBUG(DEBUG_INFO, "FEN : find_new_file_in_dir 0x%p %s\n",
++ parent, FN_PATH(parent));
++ dir = g_dir_open (FN_PATH(parent), 0, &err);
++ if (dir) {
++ const char *filename;
++
++ while ((filename = g_dir_read_name (dir)))
++ {
++ fnode_t *tmpf;
++ GList *idx;
++ gchar *fullname = g_build_filename (FN_PATH(parent),
++ filename,
++ NULL);
++ /* If can't found the fullname from the global hash,
++ emit the event on all mondir subs.
++ Else if foreach mondir subs, if can't find in fn subs of
++ fullname, emit event on that sub.
++ */
++ //GAM_DEBUG(DEBUG_INFO, "FEN : found %s\n", fullname);
++ if ((tmpf = fn_hash_safe_node_new (fullname,
++ publish_fen_events)) == NULL) {
++ GAM_DEBUG(DEBUG_INFO, "FEN : create fn failed: %s\n", fullname);
++ goto l_continue;
++ }
++
++ switch (port_monitor_add (tmpf, NULL)) {
++ case 0:
++ case -1:
++ break;
++ case ENOENT:
++ default:
++ goto l_skip;
++ }
++
++ /* set parent iff the parent has subscriptions or is mondired */
++ MUTEX_LOCK (tmpf->fn_lock);
++ MUTEX_LOCK (parent->fn_lock);
++ if ((parent->parent || parent->subs) && tmpf->parent == NULL) {
++ tmpf->parent = parent;
++ gam_server_emit_event (fullname, fnode_is_dir(tmpf), GAMIN_EVENT_CREATED, parent->subs, 1);
++ }
++ MUTEX_UNLOCK (parent->fn_lock);
++ MUTEX_UNLOCK (tmpf->fn_lock);
++
++ l_skip:
++ fnode_unref (tmpf);
++ l_continue:
++ g_free (fullname);
++ }
++ g_dir_close (dir);
++ } else {
++ GAM_DEBUG (DEBUG_INFO, "unable to open directory %s: %s\n", FN_PATH(parent), err->message);
++ g_error_free (err);
++ }
++}
++
++static void
++foreach_hash_remove_parent (gpointer key,
++ gpointer value,
++ gpointer user_data)
++{
++ /* hash is locked outside, note
++ do not lock hash here or the invorked functions */
++ fnode_t *parent = (fnode_t *) user_data;
++ const gchar *path = FN_PATH(parent);
++ gchar *dirname = g_path_get_dirname (key);
++
++ /* skip root "/" */
++ if (parent != value &&
++ strcmp (path, dirname) == 0) {
++ fnode_t *f = (fnode_t *) value;
++
++ MUTEX_LOCK (f->fn_lock);
++ f->parent = NULL;
++ if (f->subs == NULL) {
++ port_monitor_remove (f);
++ }
++ MUTEX_UNLOCK (f->fn_lock);
++ }
++ g_free (dirname);
++}
++
++/*
++ * We do it because we know the parent has some mondir subscriptions.
++ * Parent is refed.
++ * Fullname is known to be existing, but skip it if it's removed when
++ * add it to port.
++ */
++
++static void
++create_children_initially (const char *fullname,
++ fnode_t *parent)
++
++{
++ fnode_t *f;
++
++ if ((f = fn_hash_safe_node_new (fullname, publish_fen_events)) == NULL) {
++ GAM_DEBUG(DEBUG_INFO, "FEN : create_children_initially - failed to create fn\n");
++ return;
++ }
++
++ MUTEX_LOCK (f->fn_lock);
++ MUTEX_LOCK (parent->fn_lock);
++ switch (port_monitor_add (f, NULL)) {
++ case 0:
++ case -1:
++ case ENOENT:
++ break;
++ default:
++ /* skip any other unsopport error */
++ goto l_skip;
++ }
++ /* append the mondir subs */
++ f->parent = parent;
++
++ l_skip:
++ fnode_unref (f);
++ MUTEX_UNLOCK (parent->fn_lock);
++ MUTEX_UNLOCK (f->fn_lock);
++}
++
++/**
++ * First time scan the subscription file or directory. Only invoked in
++ * gam_fen_add_subscription. Here f is refed.
++ * Returns TRUE, if pathname exists.
++ */
++
++static gboolean
++gam_fen_send_initial_events (const char *pathname,
++ fnode_t *f,
++ GamSubscription *sub)
++{
++ int retval;
++ gboolean is_dir;
++ GaminEventType gevent;
++ int req = 1;
++ struct stat buf;
++
++ GAM_DEBUG (DEBUG_INFO, "gam_fen_send_initial_events on %s\n", pathname);
++ is_dir = gam_subscription_is_dir (sub);
++
++ switch (get_lstat (FN_PATH(f), &buf)) {
++ case 0:
++ fnode_set_stat (f, &buf);
++ gevent = GAMIN_EVENT_EXISTS;
++ retval = TRUE;
++ break;
++ case ENOENT:
++ gevent = GAMIN_EVENT_DELETED;
++ retval = FALSE;
++ is_dir = FALSE;
++ break;
++ default:
++ perror ("gam_fen_send_initial_events");
++ }
++
++ if (req != 0 || gevent == GAMIN_EVENT_DELETED)
++ gam_server_emit_one_event (pathname, is_dir, gevent, sub, 1);
++
++ if (req == 0)
++ return retval;
++
++ /* NOTE: If the sub is mondiring, we need create the new fnode
++ for each child files/dirs and mark the f->is_md_child. */
++ if (is_dir && fnode_is_dir (f)) {
++ GDir *dir;
++ GError *err = NULL;
++
++ dir = g_dir_open (FN_PATH(f), 0, &err);
++ if (dir) {
++ const char *filename;
++ while ((filename = g_dir_read_name (dir)))
++ {
++ gboolean file_is_dir = FALSE;
++ gchar *fullname = g_build_filename (FN_PATH(f),
++ filename,
++ NULL);
++ if (lstat (fullname, &buf) == 0) {
++ file_is_dir = S_ISDIR (buf.st_mode) ? TRUE : FALSE;
++ }
++ MUTEX_LOCK (f->fn_lock);
++ gam_server_emit_one_event (fullname, file_is_dir, GAMIN_EVENT_EXISTS, sub, 1);
++ MUTEX_UNLOCK (f->fn_lock);
++
++ /* if I'm a mondir */
++ if (fnode_mdcount (f) > 0) {
++ create_children_initially (fullname, f);
++ }
++
++ g_free (fullname);
++ }
++ g_dir_close (dir);
++ } else {
++ GAM_DEBUG (DEBUG_INFO, "unable to open directory %s: %s\n", FN_PATH(f), err->message);
++ g_error_free (err);
++ }
++ }
++
++ gam_server_emit_one_event (pathname, is_dir, GAMIN_EVENT_ENDEXISTS, sub, 1);
++ return retval;
++}
++
++/**
++ * Initializes the FEN system. This must be called before
++ * any other functions in this module.
++ *
++ * @returns TRUE if initialization succeeded, FALSE otherwise
++ */
++
++gboolean
++gam_fen_init (void)
++{
++ GError *err = NULL;
++
++ if (fen_kernel_init () != 0) {
++ GAM_DEBUG(DEBUG_INFO, "FEN : initializing failed - fen_kernel_init.\n");
++ return FALSE;
++ }
++
++ if (!g_thread_supported ()) g_thread_init (NULL);
++
++ if (!fn_hash_init ()) {
++ GAM_DEBUG(DEBUG_INFO, "FEN : initializing failed - fn_hash_init.\n");
++ return FALSE;
++ }
++
++ thread_pool = thread_pool_new (MAX_EVENT_THREADS, MAX_EVENT_QUEUE_LEN);
++ if (thread_pool == NULL) {
++ GAM_DEBUG(DEBUG_INFO, "FEN : initializing failed - thread_pool_new.\n");
++ return FALSE;
++ }
++
++#ifdef GAMIN_DEBUG
++#ifdef FEN_DEBUG
++ if (thread_pool_run (thread_pool,
++ (thp_run_cb) fn_hash_safe_debug,
++ NULL) != 0) {
++ g_assert_not_reached ();
++ }
++#endif
++#endif
++ if (thread_pool_run (thread_pool,
++ (thp_run_cb) gc_thread,
++ NULL) != 0) {
++ g_assert_not_reached ();
++ }
++
++ gam_server_install_kernel_hooks (GAMIN_K_FEN,
++ gam_fen_add_subscription,
++ gam_fen_remove_subscription,
++ gam_fen_remove_all_for,
++ NULL, NULL);
++
++ GAM_DEBUG(DEBUG_INFO, "FEN : 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_fen_add_subscription (GamSubscription *sub)
++{
++ int ret;
++ fnode_t *f;
++ gboolean is_existing;
++ const gchar *filename;
++
++ GAM_DEBUG(DEBUG_INFO, "FEN : gam_fen_add_subscription\n");
++ filename = gam_subscription_get_path (sub);
++ gam_listener_add_subscription (gam_subscription_get_listener (sub), sub);
++
++ if ((f = fn_hash_safe_node_new (filename,
++ publish_fen_events)) == NULL) {
++ GAM_DEBUG(DEBUG_INFO, "%s: new sub failed\n", filename);
++ return FALSE;
++ }
++
++ if (gam_subscription_is_dir (sub)) {
++ fnode_mdref (f);
++ }
++
++ is_existing = gam_fen_send_initial_events (filename, f, sub);
++
++ MUTEX_LOCK (f->fn_lock);
++ switch (port_monitor_add (f, NULL)) {
++ case 0:
++ case -1:
++ ret = TRUE;
++ break;
++ case ENOENT:
++ default:
++ if (!poll_monitor_add (f)) {
++ g_assert_not_reached ();
++ }
++ ret = TRUE;
++ break;
++ }
++ g_assert (g_list_find (f->subs, sub) == NULL);
++ f->subs = g_list_prepend (f->subs, sub);
++
++ fnode_unref (f);
++ MUTEX_UNLOCK (f->fn_lock);
++
++ return ret;
++}
++
++/**
++ * Removes a subscription which was being monitored.
++ *
++ * @param sub a #GamSubscription to remove
++ * @returns TRUE if removing the subscription succeeded, FALSE otherwise
++ */
++
++gboolean
++gam_fen_remove_subscription (GamSubscription *sub)
++{
++ fnode_t *f;
++ GList *targ;
++
++ GAM_DEBUG(DEBUG_INFO, "FEN : gam_fen_remove_subscription\n");
++
++ if ((f = fn_hash_safe_lookup (gam_subscription_get_path(sub))) == NULL) {
++ GAM_DEBUG(DEBUG_INFO, "FEN : can't find %s\n",
++ FN_PATH(gam_subscription_get_path(sub)));
++ return TRUE;
++ }
++
++ MUTEX_LOCK (f->fn_lock);
++
++ /* If no one monitor this f, remove the sub from f->subs */
++ /* the removal of the fn will be pending in thread_pool */
++ f->subs = g_list_remove (f->subs, sub);
++
++ if (f->parent == NULL && f->subs == NULL) {
++ /* remove the childen's parent if I'm the parent */
++ fn_hash_safe_foreach (foreach_hash_remove_parent, f);
++ port_monitor_remove (f);
++ }
++ MUTEX_UNLOCK (f->fn_lock);
++
++ if (gam_subscription_is_dir (sub)) {
++ fnode_mdunref (f);
++ }
++
++ /* free subscription */
++ gam_subscription_free(sub);
++ 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_fen_remove_all_for (GamListener *listener)
++{
++ GList *subs;
++ GList *idx;
++ gboolean success = TRUE;
++
++ subs = gam_listener_get_subscriptions (listener);
++
++ if (subs == NULL)
++ return FALSE;
++
++ for (idx = subs; idx != NULL; idx = idx->next) {
++ GamSubscription *sub = (GamSubscription *)idx->data;
++ g_assert (sub);
++ if (!gam_fen_remove_subscription (sub))
++ success = FALSE;
++ }
++
++ if (subs) {
++ g_list_free (subs);
++ return TRUE;
++ } else {
++ return FALSE;
++ }
++}