2008-01-14 Lin Ma <[email protected]> gnome-2-20 NEVADA_82
authorlin
Mon, 14 Jan 2008 09:13:19 +0000
branchgnome-2-20
changeset 10767 993844b29ed4
parent 10766 c6392ead2f0b
child 10768 4d352186a4b9
2008-01-14 Lin Ma <[email protected]> * SUNWgamin.spec: * base-specs/gamin.spec: * patches/gamin-01-all.diff: Gamin 0.1.9 is integrated into NV82.
ChangeLog
SUNWgamin.spec
base-specs/gamin.spec
patches/gamin-01-all.diff
--- a/ChangeLog	Fri Jan 11 13:24:42 2008 +0000
+++ b/ChangeLog	Mon Jan 14 09:13:19 2008 +0000
@@ -1,3 +1,10 @@
+2008-01-14  Lin Ma  <[email protected]>
+
+	* SUNWgamin.spec:
+	* base-specs/gamin.spec:
+	* patches/gamin-01-all.diff:
+	Gamin 0.1.9 is integrated into NV82.
+
 2008-01-11  Damien Carbery <[email protected]>
 
 	* SUNWdbus.spec: Fix amd64/sparcv9 symlink to point to libdbus-1.so.3
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SUNWgamin.spec	Mon Jan 14 09:13:19 2008 +0000
@@ -0,0 +1,76 @@
+#
+# spec file for package SUNWgamin, SUNWgamin-devel
+#
+# includes module(s): gamin
+#
+# Copyright 2007 Sun Microsystems, Inc.
+# This file and all modifications and additions to the pristine
+# package are under the same license as the package itself.
+#
+# Owner: lin
+#
+
+%include Solaris.inc
+
+%include base.inc
+%use gamin = gamin.spec
+
+Name:			SUNWgamin
+Summary:		%{gamin.summary}
+Version:		%{gamin.version}
+SUNW_BaseDir:		%{_basedir}
+BuildRoot:		%{_tmppath}/%{name}-%{version}-build
+%include default-depend.inc
+Requires:      SUNWgnome-base-libs
+BuildRequires: SUNWgnome-base-libs-devel
+
+%package devel
+Summary:		%{summary} - developer files
+Group:			Development/Libraries
+SUNW_BaseDir:		%{_basedir}
+Requires:		%{name}
+
+%prep
+rm -rf %name-%version
+mkdir %name-%version
+
+mkdir %name-%version/%base_arch
+%gamin.prep -d %name-%version/%base_arch
+
+%build
+%gamin.build -d %name-%version/%base_arch
+
+%install
+rm -rf $RPM_BUILD_ROOT
+%gamin.install -d %name-%version/%base_arch
+
+# move python stuff to vendor-packages
+(
+  cd $RPM_BUILD_ROOT%{_libdir}/python*
+  mv site-packages vendor-packages
+  rm vendor-packages/*.la
+)
+
+%clean
+rm -rf $RPM_BUILD_ROOT
+
+%files
+%defattr(-,root,bin)
+%dir %attr (0755, root, bin) %{_libdir}
+%{_libdir}/lib*.so*
+%{_libexecdir}/gam_server
+%{_libdir}/python2.4/*
+
+%files devel
+%defattr(-, root, bin)
+%dir %attr (0755, root, bin) %{_libdir}
+%dir %attr (0755, root, bin) %{_includedir}
+%dir %attr (0755, root, other) %{_libdir}/pkgconfig
+%{_libdir}/pkgconfig/*.pc
+%{_includedir}/*
+
+%changelog
+* Sat Oct 13 2007 - [email protected]
+- Initial FEN backend
+* Sun Apr 15 2007 - [email protected]
+- Initial version
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base-specs/gamin.spec	Mon Jan 14 09:13:19 2008 +0000
@@ -0,0 +1,68 @@
+#
+# spec file for package SUNWgamin, SUNWgamin-devel
+#
+# includes module(s): gamin
+#
+# Copyright 2008 Sun Microsystems, Inc.
+# This file and all modifications and additions to the pristine
+# package are under the same license as the package itself.
+#
+# Owner: lin
+#
+
+Name:		gamin
+Summary:	Library providing the FAM File Alteration Monitor API
+URL:		http://www.gnome.org/~veillard/gamin/
+Version:	0.1.9
+Source0:	http://www.gnome.org/~veillard/gamin/sources/%{name}-%{version}.tar.gz
+# owner:lin date:2007-12-05 type:feature
+Patch1:		gamin-01-all.diff
+License:	LGPL
+
+%prep
+%setup -q -n %{name}-%{version}
+# date:2007-11-01 type:feature owner:lin bugzilla:491319
+%patch1 -p1
+
+%build
+CPUS=`/usr/sbin/psrinfo | grep on-line | wc -l | tr -d ' '`
+if test "x$CPUS" = "x" -o $CPUS = 0; then
+  CPUS=1
+fi
+
+libtoolize --copy --force
+aclocal
+autoconf --force
+automake
+
+config=./configure
+
+export CFLAGS="%optflags"
+export LDFLAGS="%_ldflags"
+$config	--prefix=%{_prefix}		\
+	--libdir=%{_libdir}		\
+	--libexecdir=%{_libexecdir}	\
+	--datadir=%{_datadir}		\
+	--mandir=%{_mandir}		\
+	--sysconfdir=%{_sysconfdir}	\
+	--disable-static
+make -j $CPUS
+
+%install
+make install DESTDIR=$RPM_BUILD_ROOT mandir=%{_mandir}
+#
+rm $RPM_BUILD_ROOT%{_libdir}/*.a
+rm $RPM_BUILD_ROOT%{_libdir}/*.la
+
+%clean
+rm -rf $RPM_BUILD_ROOT
+
+%changelog
+* Wed Dec 05 2007 - [email protected]
+- Changed the type of gamin patch 01 to feature.
+
+* Sat Oct 13 2007 - [email protected]
+- Initial FEN backend
+
+* Sun Apr 14 2007 - [email protected]
+- Initial version
--- /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;
++	}
++}