[csw-devel] SF.net SVN: gar:[16232] csw/mgar/pkg/gamin/trunk

dmichelsen at users.sourceforge.net dmichelsen at users.sourceforge.net
Tue Nov 22 15:31:53 CET 2011


Revision: 16232
          http://gar.svn.sourceforge.net/gar/?rev=16232&view=rev
Author:   dmichelsen
Date:     2011-11-22 14:31:53 +0000 (Tue, 22 Nov 2011)
Log Message:
-----------
gamin: Complete rewrite during port into GAR

Added Paths:
-----------
    csw/mgar/pkg/gamin/trunk/Makefile
    csw/mgar/pkg/gamin/trunk/checksums
    csw/mgar/pkg/gamin/trunk/files/
    csw/mgar/pkg/gamin/trunk/files/0001-Patch-from-bugid-107675.patch
    csw/mgar/pkg/gamin/trunk/files/0003-Enable-64-bit-builds.patch

Property Changed:
----------------
    csw/mgar/pkg/gamin/trunk/


Property changes on: csw/mgar/pkg/gamin/trunk
___________________________________________________________________
Added: svn:ignore
   + work


Added: csw/mgar/pkg/gamin/trunk/Makefile
===================================================================
--- csw/mgar/pkg/gamin/trunk/Makefile	                        (rev 0)
+++ csw/mgar/pkg/gamin/trunk/Makefile	2011-11-22 14:31:53 UTC (rev 16232)
@@ -0,0 +1,84 @@
+NAME = gamin
+VERSION = 0.1.10
+CATEGORIES = server
+
+DESCRIPTION = GNOME replacement for FAM which watches for file alterations
+define BLURB
+endef
+
+MASTER_SITES += http://people.gnome.org/~veillard/gamin/sources/
+DISTFILES  = $(DISTNAME).tar.gz
+
+# Solaris Nevada FEN support from
+#   https://bugzilla.gnome.org/show_bug.cgi?id=491319
+# The patch has been reformatted
+# PATCHFILES += 0001-Patch-from-bugid-107675.patch
+
+# As taken from
+#   http://src.opensolaris.org/source/xref/jds/spec-files/trunk/base-specs/gamin.spec
+
+MASTER_SITES += http://src.opensolaris.org/source/raw/jds/spec-files/branches/opensolaris-2010-03/patches/
+PATCHDIRLEVEL = 0
+PATCHFILES += gamin-01-all.diff
+PATCHFILES += gamin-02-gamin.diff
+
+PATCHFILES += 0003-Enable-64-bit-builds.patch
+
+VENDOR_URL = http://people.gnome.org/~veillard/gamin/
+
+PACKAGES += CSWlibfam0
+SPKG_DESC_CSWlibfam0 = FAM compatibility library from GAMIN, libfam.so.0
+PKGFILES_CSWlibfam0 += $(call pkgfiles_lib,libfam.so.0)
+OBSOLETED_BY_CSWlibfam0 += CSWfam
+
+PACKAGES += CSWlibgamin1-0
+SPKG_DESC_CSWlibgamin1-0 = GNOME replacement for FAM library, libgamin-1.so.0
+PKGFILES_CSWlibgamin1-0 += $(call pkgfiles_lib,libgamin-1.so.0)
+
+PACKAGES += CSWlibgamin-dev
+SPKG_DESC_CSWlibgamin-dev = Development files for libgamin-1.so.0 and legacy compat libfam.so.0
+PKGFILES_CSWlibgamin-dev += $(PKGFILES_DEVEL)
+RUNTIME_DEP_PKGS_CSWlibgamin-dev += CSWlibgamin1-0
+# That is a legacy-only library, no need to pull it in unless for legacy CSWfam
+CHECKPKG_OVERRIDES_CSWlibgamin-dev += missing-dependency|CSWlibfam0
+
+PACKAGES += CSWpy-gamin
+SPKG_DESC_CSWpy-gamin = Python bindings for GAMIN file alteration monitor
+PKGFILES_CSWpy-gamin += $(libdir)/site-packages/.*
+RUNTIME_DEP_PKGS_CSWpy-gamin += CSWpython
+RUNTIME_DEP_PKGS_CSWpy-gamin += CSWlibgamin1-0
+
+PACKAGES += CSWgamin
+SPKG_DESC_CSWgamin = GAMIN file alteration monitor daemon (compatible to FAM)
+# PKGFILES is catchall
+RUNTIME_DEP_PKGS_CSWgamin += CSWlibintl8
+RUNTIME_DEP_PKGS_CSWgamin += CSWglib2
+OBSOLETED_BY_CSWgamin += CSWfam
+
+# For socket, recvmsg, connect
+EXTRA_LINKER_FLAGS = -lsocket -lnsl
+
+BUILD64 = 1
+
+# This is not passed to configure, although it is defined (but wrong) in configure.in
+EXTRA_CONFIGURE_EXPORTS += ENV_CFLAGS
+CONFIGURE_ENV_ENV_CFLAGS = $(CFLAGS)
+
+# No 64 bit Python yet
+CONFIGURE_ARGS-64 += --without-python
+
+CONFIGURE_ARGS += $(DIRPATHS)
+CONFIGURE_ARGS += $(CONFIGURE_ARGS-$(MEMORYMODEL))
+
+PYCOMPILE = 1
+EXTRA_MERGE_EXCLUDE_FILES += .*\.pyo .*\.pyc
+EXTRA_PAX_ARGS += -s ,$(libdir)/python2.6/site-packages,$(libdir)/site-packages,
+
+include gar/category.mk
+
+pre-configure-modulated:
+	cd $(WORKSRC) && libtoolize --copy --force
+	cd $(WORKSRC) && aclocal
+	cd $(WORKSRC) && autoconf --force
+	cd $(WORKSRC) && automake -a -c -f
+	@$(MAKECOOKIE)

Added: csw/mgar/pkg/gamin/trunk/checksums
===================================================================
--- csw/mgar/pkg/gamin/trunk/checksums	                        (rev 0)
+++ csw/mgar/pkg/gamin/trunk/checksums	2011-11-22 14:31:53 UTC (rev 16232)
@@ -0,0 +1,3 @@
+b4ec549e57da470c04edd5ec2876a028  gamin-0.1.10.tar.gz
+7470c29bca30a8e5a8e7db3066cc8ec3  gamin-01-all.diff
+a4904d53e3ba167705966e6334667fab  gamin-02-gamin.diff

Added: csw/mgar/pkg/gamin/trunk/files/0001-Patch-from-bugid-107675.patch
===================================================================
--- csw/mgar/pkg/gamin/trunk/files/0001-Patch-from-bugid-107675.patch	                        (rev 0)
+++ csw/mgar/pkg/gamin/trunk/files/0001-Patch-from-bugid-107675.patch	2011-11-22 14:31:53 UTC (rev 16232)
@@ -0,0 +1,3424 @@
+From b817cb4d04a99894af10e38737399802b1677046 Mon Sep 17 00:00:00 2001
+From: Dagobert Michelsen <dam at opencsw.org>
+Date: Tue, 22 Nov 2011 12:44:16 +0100
+Subject: [PATCH] Patch from bugid #107675
+
+---
+ configure.in         |   61 +++++
+ libgamin/fam.h       |   24 ++
+ libgamin/gam_api.c   |   46 +++-
+ server/Makefile.am   |   20 ++-
+ server/fen-data.c    |  720 ++++++++++++++++++++++++++++++++++++++++++++++++++
+ server/fen-data.h    |   88 ++++++
+ server/fen-dump.c    |   99 +++++++
+ server/fen-dump.h    |   28 ++
+ server/fen-helper.c  |  334 +++++++++++++++++++++++
+ server/fen-helper.h  |   35 +++
+ server/fen-kernel.c  |  527 ++++++++++++++++++++++++++++++++++++
+ server/fen-kernel.h  |   53 ++++
+ server/fen-missing.c |  114 ++++++++
+ server/fen-missing.h |   37 +++
+ server/fen-node.c    |  460 ++++++++++++++++++++++++++++++++
+ server/fen-node.h    |   72 +++++
+ server/fen-sub.c     |   41 +++
+ server/fen-sub.h     |   38 +++
+ server/gam_channel.c |   26 ++-
+ server/gam_fen.c     |  119 +++++++++
+ server/gam_fen.h     |   20 ++
+ server/gam_fs.c      |   29 ++-
+ server/gam_fs.h      |    1 +
+ server/gam_server.c  |   13 +
+ server/gam_server.h  |    3 +-
+ tests/testing.c      |    8 +-
+ 26 files changed, 3004 insertions(+), 12 deletions(-)
+ create mode 100644 server/fen-data.c
+ create mode 100644 server/fen-data.h
+ create mode 100644 server/fen-dump.c
+ create mode 100644 server/fen-dump.h
+ create mode 100644 server/fen-helper.c
+ create mode 100644 server/fen-helper.h
+ create mode 100644 server/fen-kernel.c
+ create mode 100644 server/fen-kernel.h
+ create mode 100644 server/fen-missing.c
+ create mode 100644 server/fen-missing.h
+ create mode 100644 server/fen-node.c
+ create mode 100644 server/fen-node.h
+ create mode 100644 server/fen-sub.c
+ create mode 100644 server/fen-sub.h
+ create mode 100644 server/gam_fen.c
+ create mode 100644 server/gam_fen.h
+
+diff --git a/configure.in b/configure.in
+index 9fd852e..a2604f4 100644
+--- a/configure.in
++++ b/configure.in
+@@ -37,6 +37,12 @@ AC_HEADER_STDC
+ 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 for the spec file
+ RELDATE=`date +'%a %b %e %Y'`
+ AC_SUBST(RELDATE)
+@@ -248,6 +254,43 @@ if test x$kqueue = xtrue; then
+ 	backends="${backends}, kqueue"
+ fi
+ 
++case "$os" in
++    solaris*)
++	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 +397,14 @@ if test x$dbus_have_struct_cmsgcred = xyes; then
+     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,6 +552,16 @@ AC_SUBST(PYTHON_SUBDIR)
+ 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)
+diff --git a/libgamin/fam.h b/libgamin/fam.h
+index b7490da..3e1d562 100644
+--- a/libgamin/fam.h
++++ b/libgamin/fam.h
+@@ -190,6 +190,30 @@ extern int FAMPending		(FAMConnection* fc);
+ 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
++
++/**
+  * FamErrList:
+  *
+  * In case FAMErrno is set, FAMErrlist is a global string array indexed
+diff --git a/libgamin/gam_api.c b/libgamin/gam_api.c
+index cf0fd5b..86d6dcd 100644
+--- a/libgamin/gam_api.c
++++ b/libgamin/gam_api.c
+@@ -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 @@ gamin_check_cred(GAMDataPtr conn, int fd)
+     } cmsg;
+ #endif
+ 
++#if defined(HAVE_GETPEERUCRED)
++    ucred_t *creds;
++#endif
++
+     s_uid = getuid();
+ 
+ #if defined(LOCAL_CREDS) && defined(HAVE_CMSGCRED)
+@@ -726,11 +736,25 @@ retry:
+                       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 @@ FAMPending(FAMConnection * fc)
+     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 @@ FAMPending(FAMConnection * fc)
+      * 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,20 @@ FAMDebug(FAMConnection *fc, const char *filename, FAMRequest * fr,
+     }
+     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);
++}
+ #endif
+diff --git a/server/Makefile.am b/server/Makefile.am
+index 30fbe92..f98a335 100644
+--- a/server/Makefile.am
++++ b/server/Makefile.am
+@@ -10,7 +10,7 @@ INCLUDES =						\
+ 	-DG_DISABLE_DEPRECATED				
+ 
+ if GAMIN_DEBUG
+-INCLUDES += -DGAM_DEBUG_ENABLED
++INCLUDES += -DGAM_DEBUG_ENABLED -g
+ endif
+ 
+ 
+@@ -69,6 +69,24 @@ if ENABLE_KQUEUE
+ gam_server_SOURCES += gam_kqueue.c gam_kqueue.h
+ endif
+ 
++if ENABLE_FEN
++gam_server_SOURCES += gam_fen.c gam_fen.h		\
++	fen-dump.c		\
++	fen-dump.h		\
++	fen-kernel.c 		\
++	fen-kernel.h 		\
++	fen-missing.c		\
++	fen-missing.h		\
++	fen-helper.c 		\
++	fen-helper.h		\
++	fen-data.c		\
++	fen-data.h		\
++	fen-node.c		\
++	fen-node.h		\
++	fen-sub.c		\
++	fen-sub.h
++endif
++
+ if ENABLE_HURD_MACH_NOTIFY
+ gam_server_SOURCES += gam_hurd_mach_notify.c gam_hurd_mach_notify.h
+ 
+diff --git a/server/fen-data.c b/server/fen-data.c
+new file mode 100644
+index 0000000..9a44b67
+--- /dev/null
++++ b/server/fen-data.c
+@@ -0,0 +1,720 @@
++/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:set expandtab ts=4 shiftwidth=4: */
++/* 
++ * Copyright (C) 2008 Sun Microsystem.
++ *
++ * This library is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU Lesser General Public
++ * License as published by the Free Software Foundation; either
++ * version 2 of the License, or (at your option) any later version.
++ *
++ * This library is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
++ * Lesser General Public License for more details.
++ *
++ * You should have received a copy of the GNU Lesser General
++ * Public License along with this library; if not, write to the
++ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
++ * Boston, MA 02111-1307, USA.
++ *
++ * Authors: Lin Ma <lin.ma at sun.com>
++ */
++
++#include "config.h"
++#include <port.h>
++#include <sys/types.h>
++#include <sys/time.h>
++#include <sys/stat.h>
++#include <errno.h>
++#include <glib.h>
++#include "fen-data.h"
++#include "fen-kernel.h"
++#include "fen-missing.h"
++#include "fen-dump.h"
++
++#define	PROCESS_EVENTQ_TIME	10	/* in milliseconds */
++#define PAIR_EVENTS_TIMEVAL	00000	/* in microseconds */
++#define	PAIR_EVENTS_INC_TIMEVAL	0000	/* in microseconds */
++#define SCAN_CHANGINGS_TIME	50	/* in milliseconds */
++#define	SCAN_CHANGINGS_MAX_TIME	(4*100)	/* in milliseconds */
++#define	SCAN_CHANGINGS_MIN_TIME	(4*100)	/* in milliseconds */
++#define	INIT_CHANGES_NUM	2
++#define	BASE_NUM	2
++
++#define FD_W if (fd_debug_enabled) g_warning
++#ifdef GIO_COMPILATION
++static gboolean fd_debug_enabled = FALSE;
++#else
++static gboolean fd_debug_enabled = TRUE;
++#endif
++
++G_LOCK_EXTERN (fen_lock);
++static GList *deleting_data = NULL;
++static guint deleting_data_id = 0;
++
++static void (*emit_once_cb) (fdata *f, int events, gpointer sub);
++static void (*emit_cb) (fdata *f, int events);
++static int (*_event_converter) (int event);
++
++static gboolean fdata_delete (fdata* f);
++static gint fdata_sub_find (gpointer a, gpointer b);
++static void scan_children (node_t *f);
++static void scan_known_children (node_t* f);
++
++node_t*
++add_missing_cb (node_t* parent, gpointer user_data)
++{
++    g_assert (parent);
++    FD_W ("%s p:0x%p %s\n", __func__, parent, (gchar*)user_data);
++    return add_node (parent, (gchar*)user_data);
++}
++
++gboolean
++pre_del_cb (node_t* node, gpointer user_data)
++{
++    fdata* data;
++    
++    g_assert (node);
++    data = node_get_data (node);
++    FD_W ("%s node:0x%p %s\n", __func__, node, NODE_NAME(node));
++    if (data != NULL) {
++        if (!FN_IS_PASSIVE(data)) {
++            return FALSE;
++        }
++        fdata_delete (data);
++    }
++    return TRUE;
++}
++
++static guint
++_pow (guint x, guint y)
++{
++    guint z = 1;
++    g_assert (x >= 0 && y >= 0);
++    for (; y > 0; y--) {
++        z *= x;
++    }
++    return z;
++}
++
++static guint
++get_scalable_scan_time (fdata* data)
++{
++    guint sleep_time;
++    /* Caculate from num = 0 */
++    sleep_time = _pow (BASE_NUM, data->changed_event_num) * SCAN_CHANGINGS_TIME;
++    if (sleep_time < SCAN_CHANGINGS_MIN_TIME) {
++        sleep_time = SCAN_CHANGINGS_MIN_TIME;
++    } else if (sleep_time > SCAN_CHANGINGS_MAX_TIME) {
++        sleep_time = SCAN_CHANGINGS_MAX_TIME;
++        data->change_update_id = INIT_CHANGES_NUM;
++    }
++    FD_W ("SCALABE SCAN num:time [ %4u : %4u ] %s\n", data->changed_event_num, sleep_time, FN_NAME(data));
++    return sleep_time;
++}
++
++static gboolean
++g_timeval_lt (GTimeVal *val1, GTimeVal *val2)
++{
++    if (val1->tv_sec < val2->tv_sec)
++        return TRUE;
++  
++    if (val1->tv_sec > val2->tv_sec)
++        return FALSE;
++  
++    /* val1->tv_sec == val2->tv_sec */
++    if (val1->tv_usec < val2->tv_usec)
++        return TRUE;
++  
++    return FALSE;
++}
++
++/**
++ * If all active children nodes are ported, then cancel monitor the parent node
++ *
++ * Unsafe, need lock.
++ */
++static void
++scan_known_children (node_t* f)
++{
++	GDir *dir;
++	GError *err = NULL;
++    fdata* pdata;
++    
++    FD_W ("%s %s [0x%p]\n", __func__, NODE_NAME(f), f);
++    pdata = node_get_data (f);
++    /*
++     * Currect fdata must is directly monitored. Be sure it is 1 level monitor.
++     */
++	dir = g_dir_open (NODE_NAME(f), 0, &err);
++	if (dir) {
++		const char *basename;
++        
++		while ((basename = g_dir_read_name (dir)))
++		{
++            node_t* childf = NULL;
++            fdata* data;
++			GList *idx;
++			/*
++             * If the node is existed, and isn't ported, then emit created
++             * event. Ignore others.
++             */
++            childf = children_find (f, basename);
++            if (childf &&
++              (data = node_get_data (childf)) != NULL &&
++              !FN_IS_PASSIVE (data)) {
++                if (!is_monitoring (data) &&
++                  port_add (&data->fobj, &data->len, data)) {
++                    fdata_emit_events (data, FN_EVENT_CREATED);
++                }
++            }
++        }
++		g_dir_close (dir);
++    } else {
++        FD_W (err->message);
++        g_error_free (err);
++    }
++}
++
++static void
++scan_children (node_t *f)
++{
++	GDir *dir;
++	GError *err = NULL;
++    fdata* pdata;
++    
++    FD_W ("%s %s [0x%p]\n", __func__, NODE_NAME(f), f);
++    pdata = node_get_data (f);
++    /*
++     * Currect fdata must is directly monitored. Be sure it is 1 level monitor.
++     */
++	dir = g_dir_open (NODE_NAME(f), 0, &err);
++	if (dir) {
++		const char *basename;
++        
++		while ((basename = g_dir_read_name (dir)))
++		{
++            node_t* childf = NULL;
++            fdata* data;
++			GList *idx;
++
++            childf = children_find (f, basename);
++            if (childf == NULL) {
++                gchar *filename;
++
++                filename = g_build_filename (NODE_NAME(f), basename, NULL);
++                childf = add_node (f, filename);
++                g_assert (childf);
++                data = fdata_new (childf, FALSE);
++                g_free (filename);
++            }
++            if ((data = node_get_data (childf)) == NULL) {
++                data = fdata_new (childf, FALSE);
++            }
++            /* Be sure data isn't ported and add to port successfully */
++            /* Don't need delete it, it will be deleted by the parent */
++            if (is_monitoring (data)) {
++                /* Ignored */
++            } else if (/* !is_ported (data) && */
++                port_add (&data->fobj, &data->len, data)) {
++                fdata_emit_events (data, FN_EVENT_CREATED);
++            }
++        }
++		g_dir_close (dir);
++    } else {
++        FD_W (err->message);
++        g_error_free (err);
++    }
++}
++
++static gboolean
++scan_deleting_data (gpointer data)
++{
++    fdata *f;
++    GList* i;
++    GList* deleted_list = NULL;
++    gboolean ret = TRUE;
++
++    if (G_TRYLOCK (fen_lock)) {
++        for (i = deleting_data; i; i = i->next) {
++            f = (fdata*)i->data;
++            if (fdata_delete (f)) {
++                deleted_list = g_list_prepend (deleted_list, i);
++            }
++        }
++
++        for (i = deleted_list; i; i = i->next) {
++            deleting_data = g_list_remove_link (deleting_data,
++              (GList *)i->data);
++            g_list_free_1 ((GList *)i->data);
++        }
++        g_list_free (deleted_list);
++
++        if (deleting_data == NULL) {
++            deleting_data_id = 0;
++            ret = FALSE;
++        }
++        G_UNLOCK (fen_lock);
++    }
++    return ret;
++}
++
++gboolean
++is_monitoring (fdata* data)
++{
++    return is_ported (data) || data->change_update_id > 0;
++}
++
++fdata*
++get_parent_data (fdata* data)
++{
++    if (FN_NODE(data) && !IS_TOPNODE(FN_NODE(data))) {
++        return node_get_data (FN_NODE(data)->parent);
++    }
++    return NULL;
++}
++
++node_t*
++get_parent_node (fdata* data)
++{
++    if (FN_NODE(data)) {
++        return (FN_NODE(data)->parent);
++    }
++    return NULL;
++}
++
++fdata *
++fdata_new (node_t* node, gboolean is_mondir)
++{
++	fdata *f = NULL;
++
++    g_assert (node);
++	if ((f = g_new0 (fdata, 1)) != NULL) {
++        FN_NODE(f) = node;
++		FN_NAME(f) = g_strdup (NODE_NAME(node));
++        f->is_dir = is_mondir;
++        f->eventq = g_queue_new ();
++        FD_W ("[ %s ] 0x%p %s\n", __func__, f, FN_NAME(f));
++        node_set_data (node, f);
++	}
++	return f;
++}
++
++static gboolean
++fdata_delete (fdata *f)
++{
++    fnode_event_t *ev;
++
++    FD_W ("[ TRY %s ] 0x%p id[%4d:%4d] %s\n", __func__, f, f->eventq_id, f->change_update_id, FN_NAME(f));
++    g_assert (FN_IS_PASSIVE(f));
++
++    port_remove (f);
++    /* missing_remove (f); */
++
++    if (f->node != NULL) {
++        node_set_data (f->node, NULL);
++        f->node = NULL;
++    }
++
++    if (f->change_update_id > 0 || f->eventq_id > 0) {
++        if (FN_IS_LIVING(f)) {
++            f->is_cancelled = TRUE;
++            deleting_data = g_list_prepend (deleting_data, f);
++            if (deleting_data_id == 0) {
++                deleting_data_id = g_idle_add (scan_deleting_data, NULL);
++                g_assert (deleting_data_id > 0);
++            }
++        }
++        return FALSE;
++    }
++    FD_W ("[ %s ] 0x%p %s\n", __func__, f, FN_NAME(f));
++
++    while ((ev = g_queue_pop_head (f->eventq)) != NULL) {
++        fnode_event_delete (ev);
++    }
++
++    g_queue_free (f->eventq);
++    g_free (FN_NAME(f));
++    g_free (f);
++    return TRUE;
++}
++
++void
++fdata_reset (fdata* data)
++{
++    fnode_event_t *ev;
++
++    g_assert (data);
++
++    while ((ev = g_queue_pop_head (data->eventq)) != NULL) {
++        fnode_event_delete (ev);
++    }
++}
++
++static gint
++fdata_sub_find (gpointer a, gpointer b)
++{
++    if (a != b) {
++        return 1;
++    } else {
++        return 0;
++    }
++}
++
++void
++fdata_sub_add (fdata *f, gpointer sub)
++{
++    FD_W ("[%s] [data: 0x%p ] [s: 0x%p ] %s\n", __func__, f, sub, FN_NAME(f));
++    g_assert (g_list_find_custom (f->subs, sub, (GCompareFunc)fdata_sub_find) == NULL);
++    f->subs = g_list_prepend (f->subs, sub);
++}
++
++void
++fdata_sub_remove (fdata *f, gpointer sub)
++{
++    GList *l;
++    FD_W ("[%s] [data: 0x%p ] [s: 0x%p ] %s\n", __func__, f, sub, FN_NAME(f));
++    g_assert (g_list_find_custom (f->subs, sub, (GCompareFunc)fdata_sub_find) != NULL);
++    l = g_list_find_custom (f->subs, sub, (GCompareFunc)fdata_sub_find);
++    g_assert (l);
++    g_assert (sub == l->data);
++    f->subs = g_list_delete_link (f->subs, l);
++}
++
++/**
++ * Adjust self on failing to Port
++ */
++void
++fdata_adjust_deleted (fdata* f)
++{
++    node_t* parent;
++    fdata* pdata;
++    node_op_t op = {NULL, NULL, pre_del_cb, NULL};
++
++    /*
++     * It's a top node. We move it to missing list.
++     */
++    parent = get_parent_node (f);
++    pdata = get_parent_data (f);
++    if (!FN_IS_PASSIVE(f) ||
++      children_num (FN_NODE(f)) > 0 ||
++      (pdata && !FN_IS_PASSIVE(pdata))) {
++        if (parent) {
++            if (pdata == NULL) {
++                pdata = fdata_new (parent, FALSE);
++            }
++            g_assert (pdata);
++            if (!port_add (&pdata->fobj, &pdata->len, pdata)) {
++                fdata_adjust_deleted (pdata);
++            }
++        } else {
++            /* f is root */
++            g_assert (IS_TOPNODE(FN_NODE(f)));
++            missing_add (f);
++        }
++    } else {
++#ifdef GIO_COMPILATION
++        pending_remove_node (FN_NODE(f), &op);
++#else
++        remove_node (FN_NODE(f), &op);
++#endif
++    }
++}
++
++static gboolean
++fdata_adjust_changed (fdata *f)
++{
++    fnode_event_t *ev;
++    struct stat buf;
++    node_t* parent;
++    fdata* pdata;
++
++    G_LOCK (fen_lock);
++    parent = get_parent_node (f);
++    pdata = get_parent_data (f);
++
++    if (!FN_IS_LIVING(f) ||
++      (children_num (FN_NODE(f)) == 0 &&
++        FN_IS_PASSIVE(f) &&
++        pdata && FN_IS_PASSIVE(pdata))) {
++        f->change_update_id = 0;
++        G_UNLOCK (fen_lock);
++        return FALSE;
++    }
++
++    FD_W ("[ %s ] %s\n", __func__, FN_NAME(f));
++    if (FN_STAT (FN_NAME(f), &buf) != 0) {
++        FD_W ("LSTAT [%-20s] %s\n", FN_NAME(f), g_strerror (errno));
++        goto L_delete;
++    }
++    f->is_dir = S_ISDIR (buf.st_mode) ? TRUE : FALSE;
++    if (f->len != buf.st_size) {
++        /* FD_W ("LEN [%lld:%lld] %s\n", f->len, buf.st_size, FN_NAME(f)); */
++        f->len = buf.st_size;
++        ev = fnode_event_new (FILE_MODIFIED, TRUE, f);
++        if (ev != NULL) {
++            ev->is_pending = TRUE;
++            fdata_add_event (f, ev);
++        }
++        /* Fdata is still changing, so scalable scan */
++        f->change_update_id = g_timeout_add (get_scalable_scan_time (f),
++          (GSourceFunc)fdata_adjust_changed,
++          (gpointer)f);
++        G_UNLOCK (fen_lock);
++        return FALSE;
++    } else {
++        f->changed_event_num = 0;
++        f->fobj.fo_atime = buf.st_atim;
++        f->fobj.fo_mtime = buf.st_mtim;
++        f->fobj.fo_ctime = buf.st_ctim;
++        if (FN_IS_DIR(f)) {
++            if (FN_IS_MONDIR(f)) {
++                scan_children (FN_NODE(f));
++            } else {
++                scan_known_children (FN_NODE(f));
++                if ((children_num (FN_NODE(f)) == 0 &&
++                      FN_IS_PASSIVE(f) &&
++                      pdata && FN_IS_PASSIVE(pdata))) {
++                    port_remove (f);
++                    goto L_exit;
++                }
++            }
++        }
++        if (!port_add_simple (&f->fobj, f)) {
++        L_delete:
++            ev = fnode_event_new (FILE_DELETE, FALSE, f);
++            if (ev != NULL) {
++                fdata_add_event (f, ev);
++            }
++        }
++    }
++L_exit:
++    f->change_update_id = 0;
++    G_UNLOCK (fen_lock);
++    return FALSE;
++}
++
++void
++fdata_emit_events_once (fdata *f, int event, gpointer sub)
++{
++    emit_once_cb (f, _event_converter (event), sub);
++}
++
++void
++fdata_emit_events (fdata *f, int event)
++{
++    emit_cb (f, _event_converter (event));
++}
++
++static gboolean
++process_events (gpointer udata)
++{
++    node_op_t op = {NULL, NULL, pre_del_cb, NULL};
++    fdata* f;
++    fnode_event_t* ev;
++    int e;
++
++    /* FD_W ("IN <======== %s\n", __func__); */
++
++    f = (fdata*)udata;
++    FD_W ("%s 0x%p id:%-4d %s\n", __func__, f, f->eventq_id, FN_NAME(f));
++    
++    G_LOCK (fen_lock);
++
++    if (!FN_IS_LIVING(f)) {
++        f->eventq_id = 0;
++        G_UNLOCK (fen_lock);
++        return FALSE;
++    }
++    
++    if ((ev = (fnode_event_t*)g_queue_pop_head (f->eventq)) != NULL) {
++        /* Send events to clients. */
++        e = ev->e;
++        if (!ev->is_pending) {
++#ifdef GIO_COMPILATION
++            if (ev->has_twin) {
++                fdata_emit_events (f, FILE_ATTRIB);
++            }
++#endif
++            fdata_emit_events (f, ev->e);
++        }
++        
++        fnode_event_delete (ev);
++        ev = NULL;
++
++        /* Adjust node state. */
++        /*
++         * Node the node has been created, so we can delete create event in
++         * optimizing. To reduce the statings, we add it to Port on discoving
++         * it then emit CREATED event. So we don't need to do anything here.
++         */
++        switch (e) {
++        case FILE_MODIFIED:
++        case MOUNTEDOVER:
++        case UNMOUNTED:
++            /* If the event is a changed event, then pending process it */
++            if (f->change_update_id == 0) {
++                f->change_update_id = g_timeout_add (get_scalable_scan_time(f),
++                  (GSourceFunc)fdata_adjust_changed,
++                  (gpointer)f);
++                g_assert (f->change_update_id > 0);
++            }
++            break;
++        case FILE_ATTRIB:
++            g_assert (f->change_update_id == 0);
++            if (!port_add (&f->fobj, &f->len, f)) {
++                ev = fnode_event_new (FILE_DELETE, FALSE, f);
++                if (ev != NULL) {
++                    fdata_add_event (f, ev);
++                }
++            }
++            break;
++        case FILE_DELETE: /* Ignored */
++            break;
++        default:
++            g_assert_not_reached ();
++            break;
++        }
++        /* Process one event a time */
++        G_UNLOCK (fen_lock); 
++        return TRUE;
++    }
++    f->eventq_id = 0;
++    G_UNLOCK (fen_lock); 
++    /* FD_W ("OUT ========> %s\n", __func__); */
++    return FALSE;
++}
++
++/**
++ * fdata_add_event:
++ *
++ */
++void
++fdata_add_event (fdata *f, fnode_event_t *ev)
++{
++    node_op_t op = {NULL, NULL, pre_del_cb, NULL};
++    fnode_event_t *tail;
++
++    if (!FN_IS_LIVING(f)) {
++        fnode_event_delete (ev);
++        return;
++    }
++    
++    FD_W ("%s %d\n", __func__, ev->e);
++    g_get_current_time (&ev->t);
++    /*
++     * If created/deleted events of child node happened, then we use parent
++     * event queue to handle.
++     * If child node emits deleted event, it seems no changes for the parent
++     * node, but the attr is changed. So we may try to cancel processing the
++     * coming changed events of the parent node.
++     */
++    tail = (fnode_event_t*)g_queue_peek_tail (f->eventq);
++    switch (ev->e) {
++    case FILE_RENAME_FROM:
++    case FILE_RENAME_TO:
++    case FILE_ACCESS:
++        fnode_event_delete (ev);
++        g_assert_not_reached ();
++        return;
++    case FILE_DELETE:
++        /* clear changed event number */
++        f->changed_event_num = 0;
++        /*
++         * We will cancel all previous events.
++         */
++        if (tail) {
++            g_queue_pop_tail (f->eventq);
++            do {
++                fnode_event_delete (tail);
++            } while ((tail = (fnode_event_t*)g_queue_pop_tail (f->eventq)) != NULL);
++        }
++        /*
++         * Given a node "f" is deleted, process it ASAP.
++         */
++        fdata_emit_events (f, ev->e);
++        fnode_event_delete (ev);
++        fdata_adjust_deleted (f);
++        return;
++    case FILE_MODIFIED:
++    case UNMOUNTED:
++    case MOUNTEDOVER:
++        /* clear changed event number */
++        f->changed_event_num ++;
++    case FILE_ATTRIB:
++    default:
++        /*
++         * If in the time range, we will try optimizing
++         * (changed+) to (changed)
++         * (attrchanged changed) to ([changed, attrchanged])
++         * (event attrchanged) to ([event, attrchanged])
++         */
++        if (tail) {
++            do {
++                if (tail->e == ev->e) {
++                    if (g_timeval_lt (&ev->t, &tail->t)) {
++                        g_queue_peek_tail (f->eventq);
++                        /* Add the increment */
++                        g_time_val_add (&ev->t, PAIR_EVENTS_INC_TIMEVAL);
++                        /* skip the previous event */
++                        FD_W ("SKIPPED -- %s\n", _event_string (tail->e));
++                        fnode_event_delete (tail);
++                    } else {
++                        break;
++                    }
++                } else if (ev->e == FILE_MODIFIED && tail->e == FILE_ATTRIB) {
++                    ev->has_twin = TRUE;
++                    fnode_event_delete (tail);
++                } else if (ev->e == FILE_ATTRIB && f->change_update_id > 0) {
++                    tail->has_twin = TRUE;
++                    /* skip the current event */
++                    fnode_event_delete (ev);
++                    return;
++                } else {
++                    break;
++                }
++            } while ((tail = (fnode_event_t*)g_queue_peek_tail (f->eventq)) != NULL);
++        }
++    }
++
++    /* must add the threshold time */
++    g_time_val_add (&ev->t, PAIR_EVENTS_TIMEVAL);
++    
++    g_queue_push_tail (f->eventq, ev);
++
++    /* starting process_events */
++    if (f->eventq_id == 0) {
++        f->eventq_id = g_timeout_add (PROCESS_EVENTQ_TIME,
++          process_events,
++          (gpointer)f);
++        g_assert (f->eventq_id > 0);
++    }
++    FD_W ("%s 0x%p id:%-4d %s\n", __func__, f, f->eventq_id, FN_NAME(f));
++}
++
++gboolean
++fdata_class_init (void (*user_emit_cb) (fdata*, int),
++  void (*user_emit_once_cb) (fdata*, int,  gpointer),
++  int (*user_event_converter) (int event))
++{
++    FD_W ("%s\n", __func__);
++    if (user_emit_cb == NULL) {
++        return FALSE;
++    }
++    if (user_emit_once_cb == NULL) {
++        return FALSE;
++    }
++    if (user_event_converter == NULL) {
++        return FALSE;
++    }
++    emit_cb = user_emit_cb;
++    emit_once_cb = user_emit_once_cb;
++    _event_converter = user_event_converter;
++    
++    if (!port_class_init (fdata_add_event)) {
++        FD_W ("port_class_init failed.");
++        return FALSE;
++    }
++    return TRUE;
++}
+diff --git a/server/fen-data.h b/server/fen-data.h
+new file mode 100644
+index 0000000..842358b
+--- /dev/null
++++ b/server/fen-data.h
+@@ -0,0 +1,88 @@
++/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:set expandtab ts=4 shiftwidth=4: */
++/* 
++ * Copyright (C) 2008 Sun Microsystem.
++ *
++ * This library is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU Lesser General Public
++ * License as published by the Free Software Foundation; either
++ * version 2 of the License, or (at your option) any later version.
++ *
++ * This library is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
++ * Lesser General Public License for more details.
++ *
++ * You should have received a copy of the GNU Lesser General
++ * Public License along with this library; if not, write to the
++ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
++ * Boston, MA 02111-1307, USA.
++ *
++ * Authors: Lin Ma <lin.ma at sun.com>
++ */
++
++#include <fcntl.h>
++#include <sys/types.h>
++#include <sys/stat.h>
++#include "fen-node.h"
++#include "fen-kernel.h"
++
++#ifndef _FEN_DATA_H_
++#define _FEN_DATA_H_
++
++#define FN_EVENT_CREATED	0
++#define FN_NAME(fp)	(((fdata*)(fp))->fobj.fo_name)
++#define FN_NODE(fp)	(((fdata*)(fp))->node)
++#define	FN_IS_DIR(fp)	(((fdata*)(fp))->is_dir)
++#define	FN_IS_PASSIVE(fp)	(((fdata*)(fp))->subs == NULL)
++#define	FN_IS_MONDIR(fp)	(((fdata*)(fp))->mon_dir_num > 0)
++#define	FN_IS_LIVING(fp)	(!((fdata*)(fp))->is_cancelled)
++
++typedef struct
++{
++	file_obj_t fobj;
++    off_t len;
++    gboolean is_cancelled;
++
++    node_t* node;
++	/* to identify if the path is dir */
++	gboolean is_dir;
++    guint mon_dir_num;
++
++	/* List of subscriptions monitoring this fdata/path */
++	GList *subs;
++
++    /* prcessed changed events num */
++    guint changed_event_num;
++    
++    /* process events source id */
++    GQueue* eventq;
++    guint eventq_id;
++    guint change_update_id;
++} fdata;
++
++/* fdata functions */
++fdata* fdata_new (node_t* node, gboolean is_mondir);
++void fdata_reset (fdata* data);
++void fdata_emit_events_once (fdata *f, int event, gpointer sub);
++void fdata_emit_events (fdata *f, int event);
++void fdata_add_event (fdata *f, fnode_event_t *ev);
++void fdata_adjust_deleted (fdata *f);
++fdata* get_parent_data (fdata* data);
++node_t* get_parent_node (fdata* data);
++gboolean is_monitoring (fdata* data);
++
++/* sub */
++void fdata_sub_add (fdata *f, gpointer sub);
++void fdata_sub_remove (fdata *f, gpointer sub);
++
++/* misc */
++node_t* add_missing_cb (node_t* parent, gpointer user_data);
++gboolean pre_del_cb (node_t* node, gpointer user_data);
++
++/* init */
++gboolean fdata_class_init (void (*user_emit_cb) (fdata*, int),
++  void (*user_emit_once_cb) (fdata*, int,  gpointer),
++  int (*user_event_converter) (int event));
++
++#endif /* _FEN_DATA_H_ */
+diff --git a/server/fen-dump.c b/server/fen-dump.c
+new file mode 100644
+index 0000000..cd4de28
+--- /dev/null
++++ b/server/fen-dump.c
+@@ -0,0 +1,99 @@
++/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:set expandtab ts=4 shiftwidth=4: */
++/* 
++ * Copyright (C) 2008 Sun Microsystem.
++ *
++ * This library is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU Lesser General Public
++ * License as published by the Free Software Foundation; either
++ * version 2 of the License, or (at your option) any later version.
++ *
++ * This library is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
++ * Lesser General Public License for more details.
++ *
++ * You should have received a copy of the GNU Lesser General
++ * Public License along with this library; if not, write to the
++ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
++ * Boston, MA 02111-1307, USA.
++ *
++ * Authors: Lin Ma <lin.ma at sun.com>
++ */
++
++#include "config.h"
++#include <glib.h>
++#include <glib/gprintf.h>
++#include "fen-node.h"
++#include "fen-data.h"
++#include "fen-kernel.h"
++#include "fen-missing.h"
++#include "fen-dump.h"
++
++G_LOCK_EXTERN (fen_lock);
++
++/*-------------------- node ------------------*/
++static void
++dump_node (node_t* node, gpointer data)
++{
++    if (data && node->user_data) {
++        return;
++    }
++    g_printf ("[%s] < 0x%p : 0x%p > %s\n", __func__, node, node->user_data, NODE_NAME(node));
++}
++
++static gboolean
++dump_node_tree (node_t* node, gpointer user_data)
++{
++    node_op_t op = {dump_node, NULL, NULL, user_data};
++    GList* children;
++    GList* i;
++    if (G_TRYLOCK (fen_lock)) {
++        if (node) {
++            travel_nodes (node, &op);
++        }
++        G_UNLOCK (fen_lock);
++    }
++    return TRUE;
++}
++
++/* ------------------ fdata port hash --------------------*/
++void
++dump_hash_cb (gpointer key,
++  gpointer value,
++  gpointer user_data)
++{
++    g_printf ("[%s] < 0x%p : 0x%p >\n", __func__, key, value);
++}
++
++gboolean
++dump_hash (GHashTable* hash, gpointer user_data)
++{
++    if (G_TRYLOCK (fen_lock)) {
++        if (g_hash_table_size (hash) > 0) {
++            g_hash_table_foreach (hash, dump_hash_cb, user_data);
++        }
++        G_UNLOCK (fen_lock);
++    }
++    return TRUE;
++}
++
++/* ------------------ event --------------------*/
++void
++dump_event (fnode_event_t* ev, gpointer user_data)
++{
++    fdata* data = ev->user_data;
++    g_printf ("[%s] < 0x%p : 0x%p > [ %10s ] %s\n", __func__, ev, ev->user_data, _event_string (ev->e), FN_NAME(data));
++}
++
++void
++dump_event_queue (fdata* data, gpointer user_data)
++{
++    if (G_TRYLOCK (fen_lock)) {
++        if (data->eventq) {
++            g_queue_foreach (data->eventq, (GFunc)dump_event, user_data);
++        }
++        G_UNLOCK (fen_lock);
++    }
++}
++
+diff --git a/server/fen-dump.h b/server/fen-dump.h
+new file mode 100644
+index 0000000..a4b0e81
+--- /dev/null
++++ b/server/fen-dump.h
+@@ -0,0 +1,28 @@
++/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:set expandtab ts=4 shiftwidth=4: */
++/* 
++ * Copyright (C) 2008 Sun Microsystem.
++ *
++ * This library is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU Lesser General Public
++ * License as published by the Free Software Foundation; either
++ * version 2 of the License, or (at your option) any later version.
++ *
++ * This library is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
++ * Lesser General Public License for more details.
++ *
++ * You should have received a copy of the GNU Lesser General
++ * Public License along with this library; if not, write to the
++ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
++ * Boston, MA 02111-1307, USA.
++ *
++ * Authors: Lin Ma <lin.ma at sun.com>
++ */
++
++#ifndef _FEN_DUMP_H_
++#define _FEN_DUMP_H_
++
++
++#endif /* _FEN_DUMP_H_ */
+diff --git a/server/fen-helper.c b/server/fen-helper.c
+new file mode 100644
+index 0000000..6d304ac
+--- /dev/null
++++ b/server/fen-helper.c
+@@ -0,0 +1,334 @@
++/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:set expandtab ts=4 shiftwidth=4: */
++/* 
++ * Copyright (C) 2008 Sun Microsystem.
++ *
++ * This library is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU Lesser General Public
++ * License as published by the Free Software Foundation; either
++ * version 2 of the License, or (at your option) any later version.
++ *
++ * This library is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
++ * Lesser General Public License for more details.
++ *
++ * You should have received a copy of the GNU Lesser General
++ * Public License along with this library; if not, write to the
++ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
++ * Boston, MA 02111-1307, USA.
++ *
++ * Authors: Lin Ma <lin.ma at sun.com>
++ */
++
++#include "config.h"
++#include <glib.h>
++#include "fen-data.h"
++#include "fen-helper.h"
++#include "fen-kernel.h"
++#ifdef GIO_COMPILATION
++#include "gfilemonitor.h"
++#else
++#include "gam_event.h"
++#include "gam_server.h"
++#include "gam_protocol.h"
++#endif
++
++#define FH_W if (fh_debug_enabled) g_warning
++#ifdef GIO_COMPILATION
++static gboolean fh_debug_enabled = FALSE;
++#else
++static gboolean fh_debug_enabled = TRUE;
++#endif
++
++G_LOCK_EXTERN (fen_lock);
++
++static void default_emit_event_cb (fdata *f, int events);
++static void default_emit_once_event_cb (fdata *f, int events, gpointer sub);
++static int default_event_converter (int event);
++
++static void
++scan_children_init (node_t *f, gpointer sub)
++{
++	GDir *dir;
++	GError *err = NULL;
++    node_op_t op = {NULL, NULL, pre_del_cb, NULL};
++    fdata* pdata;
++    
++    FH_W ("%s %s [0x%p]\n", __func__, NODE_NAME(f), f);
++    pdata = node_get_data (f);
++
++    dir = g_dir_open (NODE_NAME(f), 0, &err);
++    if (dir) {
++        const char *basename;
++        
++        while ((basename = g_dir_read_name (dir)))
++        {
++            node_t *childf = NULL;
++            fdata* data;
++            GList *idx;
++
++            childf = children_find (f, basename);
++            if (childf == NULL) {
++                gchar *filename;
++            
++                filename = g_build_filename (NODE_NAME(f), basename, NULL);
++                childf = add_node (f, filename);
++                g_assert (childf);
++                g_free (filename);
++            }
++            if ((data = node_get_data (childf)) == NULL) {
++                data = fdata_new (childf, FALSE);
++            }
++            
++            if (is_monitoring (data)) {
++                /* Ignored */
++            } else if (/* !is_ported (data) && */
++                port_add (&data->fobj, &data->len, data)) {
++                /* Emit created to all other subs */
++                fdata_emit_events (data, FN_EVENT_CREATED);
++            }
++            /* Emit created to the new sub */
++#ifdef GIO_COMPILATION
++            /* fdata_emit_events_once (data, FN_EVENT_CREATED, sub); */
++#else
++            gam_server_emit_one_event (NODE_NAME(childf),
++              gam_subscription_is_dir (sub), GAMIN_EVENT_EXISTS, sub, 1);
++#endif
++        }
++        g_dir_close (dir);
++    } else {
++        FH_W (err->message);
++        g_error_free (err);
++    }
++}
++
++/**
++ * fen_add
++ * 
++ * Won't hold a ref, we have a timout callback to clean unused fdata.
++ * If there is no value for a key, add it and return it; else return the old
++ * one.
++ */
++void
++fen_add (const gchar *filename, gpointer sub, gboolean is_mondir)
++{
++    node_op_t op = {NULL, add_missing_cb, pre_del_cb, (gpointer)filename};
++	node_t* f;
++    fdata* data;
++    
++    g_assert (filename);
++    g_assert (sub);
++
++    G_LOCK (fen_lock);
++	f = find_node_full (filename, &op);
++    FH_W ("[ %s ] f[0x%p] sub[0x%p] %s\n", __func__, f, sub, filename);
++    g_assert (f);
++    data = node_get_data (f);
++    if (data == NULL) {
++        data = fdata_new (f, is_mondir);
++    }
++
++    if (is_mondir) {
++        data->mon_dir_num ++;
++    }
++    
++    /* Change to active */
++#ifdef GIO_COMPILATION
++    if (port_add (&data->fobj, &data->len, data) ||
++      g_file_test (FN_NAME(data), G_FILE_TEST_EXISTS)) {
++        if (is_mondir) {
++            scan_children_init (f, sub);
++        }
++        fdata_sub_add (data, sub);
++    } else {
++        fdata_sub_add (data, sub);
++        fdata_adjust_deleted (data);
++    }
++#else
++    if (port_add (&data->fobj, &data->len, data) ||
++      g_file_test (FN_NAME(data), G_FILE_TEST_EXISTS)) {
++        gam_server_emit_one_event (FN_NAME(data),
++          gam_subscription_is_dir (sub), GAMIN_EVENT_EXISTS, sub, 1);
++        if (is_mondir) {
++            scan_children_init (f, sub);
++        }
++        gam_server_emit_one_event (FN_NAME(data),
++          gam_subscription_is_dir (sub), GAMIN_EVENT_ENDEXISTS, sub, 1);
++        fdata_sub_add (data, sub);
++    } else {
++        fdata_sub_add (data, sub);
++        gam_server_emit_one_event (FN_NAME(data),
++          gam_subscription_is_dir (sub), GAMIN_EVENT_DELETED, sub, 1);
++        fdata_adjust_deleted (data);
++        gam_server_emit_one_event (FN_NAME(data),
++          gam_subscription_is_dir (sub), GAMIN_EVENT_ENDEXISTS, sub, 1);
++    }
++#endif
++    G_UNLOCK (fen_lock);
++}
++
++void
++fen_remove (const gchar *filename, gpointer sub, gboolean is_mondir)
++{
++    node_op_t op = {NULL, add_missing_cb, pre_del_cb, (gpointer)filename};
++    node_t* f;
++    fdata* data;
++    
++    g_assert (filename);
++    g_assert (sub);
++
++    G_LOCK (fen_lock);
++	f = find_node (filename);
++    FH_W ("[ %s ] f[0x%p] sub[0x%p] %s\n", __func__, f, sub, filename);
++
++    g_assert (f);
++    data = node_get_data (f);
++    g_assert (data);
++    
++    if (is_mondir) {
++        data->mon_dir_num --;
++    }
++    fdata_sub_remove (data, sub);
++    if (FN_IS_PASSIVE(data)) {
++#ifdef GIO_COMPILATION
++        pending_remove_node (f, &op);
++#else
++        remove_node (f, &op);
++#endif
++    }
++    G_UNLOCK (fen_lock);
++}
++
++static gboolean
++fen_init_once_func (gpointer data)
++{
++    FH_W ("%s\n", __func__);
++    if (!node_class_init ()) {
++        FH_W ("node_class_init failed.");
++        return FALSE;
++    }
++    if (!fdata_class_init (default_emit_event_cb,
++          default_emit_once_event_cb,
++          default_event_converter)) {
++        FH_W ("fdata_class_init failed.");
++        return FALSE;
++    }
++    return TRUE;
++}
++
++gboolean
++fen_init ()
++{
++#ifdef GIO_COMPILATION
++    static GOnce fen_init_once = G_ONCE_INIT;
++    g_once (&fen_init_once, (GThreadFunc)fen_init_once_func, NULL);
++    return (gboolean)fen_init_once.retval;
++#else
++    return fen_init_once_func (NULL);
++#endif
++}
++
++static void
++default_emit_once_event_cb (fdata *f, int events, gpointer sub)
++{
++#ifdef GIO_COMPILATION
++    GFile* child;
++    fen_sub* _sub = (fen_sub*)sub;
++    child = g_file_new_for_path (FN_NAME(f));
++    g_file_monitor_emit_event (G_FILE_MONITOR (_sub->user_data),
++      child, NULL, events);
++    g_object_unref (child);
++#else
++    gam_server_emit_one_event (FN_NAME(f),
++      gam_subscription_is_dir (sub), events, sub, 1);
++#endif
++}
++
++static void
++default_emit_event_cb (fdata *f, int events)
++{
++    GList* i;
++    fdata* pdata;
++    
++#ifdef GIO_COMPILATION
++    GFile* child;
++    child = g_file_new_for_path (FN_NAME(f));
++    for (i = f->subs; i; i = i->next) {
++        fen_sub* sub = (fen_sub*)i->data;
++        gboolean file_is_dir = sub->is_mondir;
++        if ((events != G_FILE_MONITOR_EVENT_CHANGED &&
++              events != G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED) ||
++          !file_is_dir) {
++            g_file_monitor_emit_event (G_FILE_MONITOR (sub->user_data),
++              child, NULL, events);
++        }
++    }
++    if ((pdata = get_parent_data (f)) != NULL) {
++        for (i = pdata->subs; i; i = i->next) {
++            fen_sub* sub = (fen_sub*)i->data;
++            gboolean file_is_dir = sub->is_mondir;
++            g_file_monitor_emit_event (G_FILE_MONITOR (sub->user_data),
++              child, NULL, events);
++        }
++    }
++    g_object_unref (child);
++#else
++    for (i = f->subs; i; i = i->next) {
++        gboolean file_is_dir = gam_subscription_is_dir (i->data);
++        if (events != GAMIN_EVENT_CHANGED || !file_is_dir) {
++            gam_server_emit_one_event (FN_NAME(f), file_is_dir, events, i->data, 1);
++        }
++    }
++    if ((pdata = get_parent_data (f)) != NULL) {
++        for (i = pdata->subs; i; i = i->next) {
++            gboolean file_is_dir = gam_subscription_is_dir (i->data);
++            gam_server_emit_one_event (FN_NAME(f), file_is_dir, events, i->data, 1);
++        }
++    }
++#endif
++}
++
++static int
++default_event_converter (int event)
++{
++#ifdef GIO_COMPILATION
++    switch (event) {
++    case FN_EVENT_CREATED:
++        return G_FILE_MONITOR_EVENT_CREATED;
++    case FILE_DELETE:
++    case FILE_RENAME_FROM:
++        return G_FILE_MONITOR_EVENT_DELETED;
++    case UNMOUNTED:
++        return G_FILE_MONITOR_EVENT_UNMOUNTED;
++    case FILE_ATTRIB:
++        return G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED;
++    case MOUNTEDOVER:
++    case FILE_MODIFIED:
++    case FILE_RENAME_TO:
++        return G_FILE_MONITOR_EVENT_CHANGED;
++    default:
++        /* case FILE_ACCESS: */
++        g_assert_not_reached ();
++        return -1;
++    }
++#else
++    switch (event) {
++    case FN_EVENT_CREATED:
++        return GAMIN_EVENT_CREATED;
++    case FILE_DELETE:
++    case FILE_RENAME_FROM:
++        return GAMIN_EVENT_DELETED;
++    case FILE_ATTRIB:
++    case MOUNTEDOVER:
++    case UNMOUNTED:
++    case FILE_MODIFIED:
++    case FILE_RENAME_TO:
++        return GAMIN_EVENT_CHANGED;
++    default:
++        /* case FILE_ACCESS: */
++        g_assert_not_reached ();
++        return -1;
++    }
++#endif
++}
+diff --git a/server/fen-helper.h b/server/fen-helper.h
+new file mode 100644
+index 0000000..75fa0df
+--- /dev/null
++++ b/server/fen-helper.h
+@@ -0,0 +1,35 @@
++/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:set expandtab ts=4 shiftwidth=4: */
++/* 
++ * Copyright (C) 2008 Sun Microsystem.
++ *
++ * This library is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU Lesser General Public
++ * License as published by the Free Software Foundation; either
++ * version 2 of the License, or (at your option) any later version.
++ *
++ * This library is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
++ * Lesser General Public License for more details.
++ *
++ * You should have received a copy of the GNU Lesser General
++ * Public License along with this library; if not, write to the
++ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
++ * Boston, MA 02111-1307, USA.
++ *
++ * Authors: Lin Ma <lin.ma at sun.com>
++ */
++
++#include "fen-sub.h"
++
++#ifndef _FEN_HELPER_H_
++#define _FEN_HELPER_H_
++
++void fen_add (const gchar *filename, gpointer sub, gboolean is_mondir);
++void fen_remove (const gchar *filename, gpointer sub, gboolean is_mondir);
++
++/* FEN subsystem initializing */
++gboolean fen_init ();
++
++#endif /* _FEN_HELPER_H_ */
+diff --git a/server/fen-kernel.c b/server/fen-kernel.c
+new file mode 100644
+index 0000000..d9531da
+--- /dev/null
++++ b/server/fen-kernel.c
+@@ -0,0 +1,527 @@
++/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:set expandtab ts=4 shiftwidth=4: */
++/* 
++ * Copyright (C) 2008 Sun Microsystem.
++ *
++ * This library is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU Lesser General Public
++ * License as published by the Free Software Foundation; either
++ * version 2 of the License, or (at your option) any later version.
++ *
++ * This library is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
++ * Lesser General Public License for more details.
++ *
++ * You should have received a copy of the GNU Lesser General
++ * Public License along with this library; if not, write to the
++ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
++ * Boston, MA 02111-1307, USA.
++ *
++ * Authors: Lin Ma <lin.ma at sun.com>
++ */
++
++#include "config.h"
++#include <rctl.h>
++#include <strings.h>
++#include <errno.h>
++#include <stdio.h>
++#include <stdlib.h>
++#include <unistd.h>
++#include <glib.h>
++#include "fen-kernel.h"
++#include "fen-dump.h"
++
++#define FK_W if (fk_debug_enabled) g_warning
++#ifdef GIO_COMPILATION
++static gboolean fk_debug_enabled = FALSE;
++#else
++static gboolean fk_debug_enabled = TRUE;
++#endif
++
++G_GNUC_INTERNAL G_LOCK_DEFINE (fen_lock);
++#define PE_ALLOC	64
++#define F_PORT(pp)		(((_f *)(fo))->port->port)
++#define F_NAME(pp)		(((_f *)(fo))->fobj->fo_name)
++#define FEN_ALL_EVENTS	(FILE_MODIFIED | FILE_ATTRIB | FILE_NOFOLLOW)
++#define FEN_IGNORE_EVENTS	(FILE_ACCESS)
++#define PROCESS_PORT_EVENTS_TIME	400	/* in milliseconds */
++
++static GHashTable *_obj_fen_hash = NULL;	/* <user_data, port> */
++static ulong max_port_events = 512;
++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 GQueue *g_eventq = NULL;
++static void (*add_event_cb) (gpointer, fnode_event_t*);
++
++typedef struct pnode
++{
++	long ref;	/* how many fds are associated to this port */
++	int port;
++    guint port_source_id;
++} pnode_t;
++
++typedef struct {
++    pnode_t*	port;
++    file_obj_t*	fobj;
++
++    gboolean	is_active;
++    gpointer	user_data;
++} _f;
++
++static gboolean port_fetch_event_cb (void *arg);
++static pnode_t *pnode_new ();
++static void pnode_delete (pnode_t *pn);
++
++gboolean
++is_ported (gpointer f)
++{
++    _f* fo = g_hash_table_lookup (_obj_fen_hash, f);
++    
++    if (fo) {
++        return fo->is_active;
++    }
++    return FALSE;
++}
++
++static void
++printevent (const char *pname, int event, const char *tag)
++{
++    GString* str;
++    str = g_string_new ("");
++    
++    g_string_printf (str, "[%s] [%-20s]", tag, pname);
++    if (event & FILE_ACCESS) {
++        str = g_string_append (str, " ACCESS");
++    }
++    if (event & FILE_MODIFIED) {
++        str = g_string_append (str, " MODIFIED");
++    }
++    if (event & FILE_ATTRIB) {
++        str = g_string_append (str, " ATTRIB");
++    }
++    if (event & FILE_DELETE) {
++        str = g_string_append (str, " DELETE");
++    }
++    if (event & FILE_RENAME_TO) {
++        str = g_string_append (str, " RENAME_TO");
++    }
++    if (event & FILE_RENAME_FROM) {
++        str = g_string_append (str, " RENAME_FROM");
++    }
++    if (event & UNMOUNTED) {
++        str = g_string_append (str, " UNMOUNTED");
++    }
++    if (event & MOUNTEDOVER) {
++        str = g_string_append (str, " MOUNTEDOVER");
++    }
++
++    FK_W ("%s\n", str->str);
++    g_string_free (str, TRUE);
++}
++
++static void
++port_add_kevent (int e, gpointer f)
++{
++    fnode_event_t *ev, *tail;
++    GTimeVal t;
++    gboolean has_twin = FALSE;
++    
++    /* printevent (F_NAME(f), e, "org"); */
++    /*
++     * Child FILE_DELETE | FILE_RENAME_FROM will trigger parent FILE_MODIFIED.
++     * FILE_MODIFIED will trigger FILE_ATTRIB.
++     */
++
++    if ((e & FILE_ATTRIB) && e != FILE_ATTRIB) {
++        e ^= FILE_ATTRIB;
++        has_twin = TRUE;
++    }
++    if (e == FILE_RENAME_FROM) {
++        e = FILE_DELETE;
++    }
++    if (e == FILE_RENAME_TO) {
++        e = FILE_MODIFIED;
++    }
++    
++    switch (e) {
++    case FILE_DELETE:
++    case FILE_RENAME_FROM:
++    case FILE_MODIFIED:
++    case FILE_ATTRIB:
++    case UNMOUNTED:
++    case MOUNTEDOVER:
++        break;
++    case FILE_RENAME_TO:
++    case FILE_ACCESS:
++    default:
++        g_assert_not_reached ();
++        return;
++    }
++
++    tail = (fnode_event_t*) g_queue_peek_tail (g_eventq);
++    if (tail) {
++        if (tail->user_data == f) {
++            if (tail->e == e) {
++                tail->has_twin = (has_twin | (tail->has_twin ^ has_twin));
++                /* skip the current */
++                return;
++            } else if (e == FILE_MODIFIED && !has_twin
++              && tail->e == FILE_ATTRIB) {
++                tail->e = FILE_MODIFIED;
++                tail->has_twin = TRUE;
++                return;
++            } else if (e == FILE_ATTRIB
++              && tail->e == FILE_MODIFIED && !tail->has_twin) {
++                tail->has_twin = TRUE;
++                return;
++            }
++        }
++    }
++    
++    if ((ev = fnode_event_new (e, has_twin, f)) != NULL) {
++        g_queue_push_tail (g_eventq, ev);
++    }
++}
++
++static void
++port_process_kevents ()
++{
++    fnode_event_t *ev;
++    
++    while ((ev = (fnode_event_t*)g_queue_pop_head (g_eventq)) != NULL) {
++        FK_W ("[%s] 0x%p %s\n", __func__, ev, _event_string (ev->e));
++        add_event_cb (ev->user_data, ev);
++    }
++}
++
++static gboolean
++port_fetch_event_cb (void *arg)
++{
++	pnode_t *pn = (pnode_t *)arg;
++    _f* fo;
++	uint_t nget = 0;
++	port_event_t pe[PE_ALLOC];
++    timespec_t timeout;
++    gpointer f;
++    gboolean ret = TRUE;
++    
++    /* FK_W ("IN <======== %s\n", __func__); */
++    G_LOCK (fen_lock);
++    
++    memset (&timeout, 0, sizeof (timespec_t));
++    do {
++        nget = 1;
++        if (port_getn (pn->port, pe, PE_ALLOC, &nget, &timeout) == 0) {
++            int i;
++            for (i = 0; i < nget; i++) {
++                fo = (_f*)pe[i].portev_user;
++                /* handle event */
++                switch (pe[i].portev_source) {
++                case PORT_SOURCE_FILE:
++                    /* If got FILE_EXCEPTION or add to port failed,
++                       delete the pnode */
++                    fo->is_active = FALSE;
++                    if (fo->user_data) {
++                        printevent (F_NAME(fo), pe[i].portev_events, "RAW");
++                        port_add_kevent (pe[i].portev_events, fo->user_data);
++                    } else {
++                        /* fnode is deleted */
++                        goto L_delete;
++                    }
++                    if (pe[i].portev_events & FILE_EXCEPTION) {
++                        g_hash_table_remove (_obj_fen_hash, fo->user_data);
++                    L_delete:
++                        FK_W ("[ FREE_FO ] [0x%p]\n", fo);
++                        pnode_delete (fo->port);
++                        g_free (fo);
++                    }
++                    break;
++                default:
++                    /* case PORT_SOURCE_TIMER: */
++                    FK_W ("[kernel] unknown portev_source %d\n", pe[i].portev_source);
++                }
++            }
++        } else {
++            FK_W ("[kernel] port_getn %s\n", g_strerror (errno));
++            nget = 0;
++        }
++    } while (nget == PE_ALLOC);
++
++	/* Processing g_eventq */
++    port_process_kevents ();
++    
++    if (pn->ref == 0) {
++        pn->port_source_id = 0;
++        ret = FALSE;
++    }
++    G_UNLOCK (fen_lock);
++    /* FK_W ("OUT ========> %s\n", __func__); */
++	return ret;
++}
++
++/*
++ * ref - 1 if remove a watching file succeeded.
++ */
++static void
++pnode_delete (pnode_t *pn)
++{
++    g_assert (pn->ref <= max_port_events);
++    
++	if (pn->ref == max_port_events) {
++        FK_W ("PORT : 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) {
++        /* Should dispatch the source */
++	}
++	FK_W ("%s [pn] 0x%p [ref] %d\n", __func__, pn, pn->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;
++
++	if (pn_vq) {
++		pn = (pnode_t*)pn_vq->data;
++        g_assert (pn->ref < max_port_events);
++	} else {
++		pn = g_new0 (pnode_t, 1);
++		if (pn != NULL) {
++            if ((pn->port = port_create ()) >= 0) {
++                g_assert (g_list_find (pn_vq, pn) == NULL);
++                pn_vq = g_list_prepend (pn_vq, pn);
++            } else {
++                FK_W ("PORT_CREATE %s\n", g_strerror (errno));
++                g_free (pn);
++                pn = NULL;
++			}
++		}
++	}
++	if (pn) {
++		FK_W ("%s [pn] 0x%p [ref] %d\n", __func__, pn, pn->ref);
++        pn->ref++;
++        if (pn->ref == max_port_events) {
++            FK_W ("PORT : 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);
++        }
++        /* attach the source */
++        if (pn->port_source_id == 0) {
++            pn->port_source_id = g_timeout_add (PROCESS_PORT_EVENTS_TIME,
++              port_fetch_event_cb,
++              (void *)pn);
++            g_assert (pn->port_source_id > 0);
++        }
++	}
++
++	return pn;
++}
++
++/**
++ * port_add_internal
++ *
++ * < private >
++ * Unsafe, need lock fen_lock.
++ */
++static gboolean
++port_add_internal (file_obj_t* fobj, off_t* len,
++  gpointer f, gboolean need_stat)
++{
++    int ret;
++    struct stat buf;
++    _f* fo = NULL;
++
++    g_assert (f && fobj);
++    FK_W ("%s [0x%p] %s\n", __func__, f, fobj->fo_name);
++
++    if ((fo = g_hash_table_lookup (_obj_fen_hash, f)) == NULL) {
++        fo = g_new0 (_f, 1);
++        fo->fobj = fobj;
++        fo->user_data = f;
++        g_assert (fo);
++        FK_W ("[ NEW_FO ] [0x%p] %s\n", fo, F_NAME(fo));
++        g_hash_table_insert (_obj_fen_hash, f, fo);
++    }
++
++    if (fo->is_active) {
++        return TRUE;
++    }
++
++    if (fo->port == NULL) {
++        fo->port = pnode_new ();
++    }
++    
++    if (need_stat) {
++        if (FN_STAT (F_NAME(fo), &buf) != 0) {
++            FK_W ("LSTAT [%-20s] %s\n", F_NAME(fo), g_strerror (errno));
++            goto L_exit;
++        }
++        g_assert (len);
++        fo->fobj->fo_atime = buf.st_atim;
++        fo->fobj->fo_mtime = buf.st_mtim;
++        fo->fobj->fo_ctime = buf.st_ctim;
++        *len = buf.st_size;
++    }
++    
++    if (port_associate (F_PORT(fo),
++          PORT_SOURCE_FILE,
++          (uintptr_t)fo->fobj,
++          FEN_ALL_EVENTS,
++          (void *)fo) == 0) {
++        fo->is_active = TRUE;
++        FK_W ("%s %s\n", "PORT_ASSOCIATE", F_NAME(fo));
++        return TRUE;
++    } else {
++        FK_W ("PORT_ASSOCIATE [%-20s] %s\n", F_NAME(fo), g_strerror (errno));
++    L_exit:
++        FK_W ("[ FREE_FO ] [0x%p]\n", fo);
++        g_hash_table_remove (_obj_fen_hash, f);
++        pnode_delete (fo->port);
++        g_free (fo);
++    }
++    return FALSE;
++}
++
++gboolean
++port_add (file_obj_t* fobj, off_t* len, gpointer f)
++{
++    return port_add_internal (fobj, len, f, TRUE);
++}
++
++gboolean
++port_add_simple (file_obj_t* fobj, gpointer f)
++{
++    return port_add_internal (fobj, NULL, f, FALSE);
++}
++
++/**
++ * port_remove
++ *
++ * < private >
++ * Unsafe, need lock fen_lock.
++ */
++void
++port_remove (gpointer f)
++{
++    _f* fo = NULL;
++
++    FK_W ("%s\n", __func__);
++    if ((fo = g_hash_table_lookup (_obj_fen_hash, f)) != NULL) {
++        /* Marked */
++        fo->user_data = NULL;
++        g_hash_table_remove (_obj_fen_hash, f);
++        
++        if (port_dissociate (F_PORT(fo),
++              PORT_SOURCE_FILE,
++              (uintptr_t)fo->fobj) == 0) {
++            /*
++             * Note, we can run foode_delete if dissociating is failed,
++             * because there may be some pending events (mostly like
++             * FILE_DELETE) in the port_get. If we delete the foode
++             * the fnode may be deleted, then port_get will run on an invalid
++             * address.
++             */
++            FK_W ("[ FREE_FO ] [0x%p]\n", fo);
++            pnode_delete (fo->port);
++            g_free (fo);
++        } else {
++            FK_W ("PORT_DISSOCIATE [%-20s] %s\n", F_NAME(fo), g_strerror (errno));
++        }
++    }
++}
++
++const gchar *
++_event_string (int event)
++{
++    switch (event) {
++    case FILE_DELETE:
++        return "FILE_DELETE";
++    case FILE_RENAME_FROM:
++        return "FILE_RENAME_FROM";
++    case FILE_MODIFIED:
++        return "FILE_MODIFIED";
++    case FILE_RENAME_TO:
++        return "FILE_RENAME_TO";
++    case MOUNTEDOVER:
++        return "MOUNTEDOVER";
++    case FILE_ATTRIB:
++        return "FILE_ATTRIB";
++    case UNMOUNTED:
++        return "UNMOUNTED";
++    case FILE_ACCESS:
++        return "FILE_ACCESS";
++    default:
++        return "EVENT_UNKNOWN";
++    }
++}
++
++/**
++ * Get Solaris resouce values.
++ *
++ */
++
++extern gboolean
++port_class_init (void (*user_add_event) (gpointer, fnode_event_t*))
++{
++	rctlblk_t *rblk;
++    FK_W ("%s\n", __func__);
++	if ((rblk = malloc (rctlblk_size ())) == NULL) {
++        FK_W ("[kernel] rblk malloc %s\n", g_strerror (errno));
++		return FALSE;
++	}
++	if (getrctl ("process.max-port-events", NULL, rblk, RCTL_FIRST) == -1) {
++        FK_W ("[kernel] getrctl %s\n", g_strerror (errno));
++        free (rblk);
++        return FALSE;
++	} else {
++        max_port_events = rctlblk_get_value(rblk);
++		FK_W ("[kernel] max_port_events = %u\n", max_port_events);
++        free (rblk);
++	}
++    if ((_obj_fen_hash = g_hash_table_new(g_direct_hash,
++           g_direct_equal)) == NULL) {
++        FK_W ("[kernel] fobj hash initializing faild\n");
++        return FALSE;
++    }
++    if ((g_eventq = g_queue_new ()) == NULL) {
++		FK_W ("[kernel] FEN global event queue initializing faild\n");
++    }
++    if (user_add_event == NULL) {
++        return FALSE;
++    }
++    add_event_cb = user_add_event;
++	return TRUE;
++}
++
++fnode_event_t*
++fnode_event_new (int event, gboolean has_twin, gpointer user_data)
++{
++    fnode_event_t *ev;
++    
++    if ((ev = g_new (fnode_event_t, 1)) != NULL) {
++        g_assert (ev);
++        ev->e = event;
++        ev->user_data = user_data;
++        ev->has_twin = has_twin;
++        /* Default isn't a pending event. */
++        ev->is_pending = FALSE;
++    }
++    return ev;
++}
++
++void
++fnode_event_delete (fnode_event_t* ev)
++{
++    g_free (ev);
++}
+diff --git a/server/fen-kernel.h b/server/fen-kernel.h
+new file mode 100644
+index 0000000..cf3ae42
+--- /dev/null
++++ b/server/fen-kernel.h
+@@ -0,0 +1,53 @@
++/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:set expandtab ts=4 shiftwidth=4: */
++/* 
++ * Copyright (C) 2008 Sun Microsystem.
++ *
++ * This library is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU Lesser General Public
++ * License as published by the Free Software Foundation; either
++ * version 2 of the License, or (at your option) any later version.
++ *
++ * This library is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
++ * Lesser General Public License for more details.
++ *
++ * You should have received a copy of the GNU Lesser General
++ * Public License along with this library; if not, write to the
++ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
++ * Boston, MA 02111-1307, USA.
++ *
++ * Authors: Lin Ma <lin.ma at sun.com>
++ */
++
++#include <port.h>
++#include <sys/types.h>
++#include <sys/stat.h>
++
++#ifndef _FEN_KERNEL_H_
++#define _FEN_KERNEL_H_
++
++#define FN_STAT	lstat
++
++typedef struct fnode_event
++{
++    int e;
++    gboolean has_twin;
++    gboolean is_pending;
++    gpointer user_data;
++    GTimeVal t;
++} fnode_event_t;
++
++gboolean port_add (file_obj_t* fobj, off_t* len, gpointer f);
++gboolean port_add_simple (file_obj_t* fobj, gpointer f);
++void port_remove (gpointer f);
++gboolean is_ported (gpointer f);
++
++fnode_event_t* fnode_event_new (int event, gboolean has_twin, gpointer user_data);
++void fnode_event_delete (fnode_event_t* ev);
++const gchar * _event_string (int event);
++
++extern gboolean port_class_init ();
++
++#endif /* _FEN_KERNEL_H_ */
+diff --git a/server/fen-missing.c b/server/fen-missing.c
+new file mode 100644
+index 0000000..84662c2
+--- /dev/null
++++ b/server/fen-missing.c
+@@ -0,0 +1,114 @@
++/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:set expandtab ts=4 shiftwidth=4: */
++/* 
++ * Copyright (C) 2008 Sun Microsystem.
++ *
++ * This library is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU Lesser General Public
++ * License as published by the Free Software Foundation; either
++ * version 2 of the License, or (at your option) any later version.
++ *
++ * This library is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
++ * Lesser General Public License for more details.
++ *
++ * You should have received a copy of the GNU Lesser General
++ * Public License along with this library; if not, write to the
++ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
++ * Boston, MA 02111-1307, USA.
++ *
++ * Authors: Lin Ma <lin.ma at sun.com>
++ */
++
++#include "config.h"
++#include <glib.h>
++#include "fen-data.h"
++#include "fen-missing.h"
++
++G_LOCK_EXTERN (fen_lock);
++#define SCAN_MISSING_INTERVAL 4000	/* in milliseconds */
++#define FM_W if (fm_debug_enabled) g_warning
++
++/* global data structure for scan missing files */
++gboolean fm_debug_enabled = TRUE;
++static GList *missing_list = NULL;
++static guint scan_missing_source_id = 0;
++
++static gboolean scan_missing_list (gpointer data);
++
++static gboolean
++scan_missing_list (gpointer data)
++{
++    GList *existing_list = NULL;
++    GList *idx = NULL;
++    fdata *f;
++    gboolean ret = TRUE;
++
++    G_LOCK (fen_lock);
++    
++    for (idx = missing_list; idx; idx = idx->next) {
++        f = (fdata*)idx->data;
++        
++        if (port_add (&f->fobj, &f->len, f)) {
++            /* TODO - emit CREATE event */
++            fdata_emit_events (f, FN_EVENT_CREATED);
++            existing_list = g_list_prepend (existing_list, idx);
++        }
++    }
++    
++    for (idx = existing_list; idx; idx = idx->next) {
++        missing_list = g_list_remove_link (missing_list, (GList *)idx->data);
++        g_list_free_1 ((GList *)idx->data);
++    }
++    g_list_free (existing_list);
++
++    if (missing_list == NULL) {
++        scan_missing_source_id = 0;
++        ret = FALSE;
++    }
++
++    G_UNLOCK (fen_lock);
++    return ret;
++}
++
++/**
++ * missing_add
++ *
++ * Unsafe, need lock fen_lock.
++ */
++void
++missing_add (fdata *f)
++{
++    GList *idx;
++    
++    g_assert (!is_ported (f));
++
++    if (g_list_find (missing_list, f) != NULL) {
++        FM_W ("%s is ALREADY added %s\n", __func__, FN_NAME(f));
++        return;
++    }
++    FM_W ("%s is added %s\n", __func__, FN_NAME(f));
++    
++    missing_list = g_list_prepend (missing_list, f);
++    
++    /* if doesn't scan, then start */
++    if (scan_missing_source_id == 0) {
++        scan_missing_source_id = g_timeout_add (SCAN_MISSING_INTERVAL,
++          scan_missing_list,
++          NULL);
++        g_assert (scan_missing_source_id > 0);
++    }
++}
++
++/**
++ * missing_remove
++ *
++ * Unsafe, need lock fen_lock.
++ */
++void
++missing_remove (fdata *f)
++{
++    FM_W ("%s %s\n", __func__, FN_NAME(f));
++    missing_list = g_list_remove (missing_list, f);
++}
+diff --git a/server/fen-missing.h b/server/fen-missing.h
+new file mode 100644
+index 0000000..89e4389
+--- /dev/null
++++ b/server/fen-missing.h
+@@ -0,0 +1,37 @@
++/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:set expandtab ts=4 shiftwidth=4: */
++/* 
++ * Copyright (C) 2008 Sun Microsystem.
++ *
++ * This library is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU Lesser General Public
++ * License as published by the Free Software Foundation; either
++ * version 2 of the License, or (at your option) any later version.
++ *
++ * This library is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
++ * Lesser General Public License for more details.
++ *
++ * You should have received a copy of the GNU Lesser General
++ * Public License along with this library; if not, write to the
++ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
++ * Boston, MA 02111-1307, USA.
++ *
++ * Authors: Lin Ma <lin.ma at sun.com>
++ */
++
++#ifndef __GAM_FEN_H__
++#define __GAM_FEN_H__
++
++#include "fen-data.h"
++
++G_BEGIN_DECLS
++
++extern void missing_add (fdata *f);
++extern void missing_remove (fdata *f);
++
++G_END_DECLS
++
++#endif /* __GAM_FEN_H__ */
++
+diff --git a/server/fen-node.c b/server/fen-node.c
+new file mode 100644
+index 0000000..7285c72
+--- /dev/null
++++ b/server/fen-node.c
+@@ -0,0 +1,460 @@
++/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:set expandtab ts=4 shiftwidth=4: */
++/* 
++ * Copyright (C) 2008 Sun Microsystem.
++ *
++ * This library is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU Lesser General Public
++ * License as published by the Free Software Foundation; either
++ * version 2 of the License, or (at your option) any later version.
++ *
++ * This library is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
++ * Lesser General Public License for more details.
++ *
++ * You should have received a copy of the GNU Lesser General
++ * Public License along with this library; if not, write to the
++ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
++ * Boston, MA 02111-1307, USA.
++ *
++ * Authors: Lin Ma <lin.ma at sun.com>
++ */
++
++#include "config.h"
++#include <errno.h>
++#include <strings.h>
++#include <glib.h>
++#include "fen-node.h"
++#include "fen-dump.h"
++
++#define	NODE_STAT(n)	(((node_t*)(n))->stat)
++
++struct _dnode {
++    gchar* filename;
++    node_op_t* op;
++    GTimeVal tv;
++};
++
++#define FN_W if (fn_debug_enabled) g_warning
++static gboolean fn_debug_enabled = FALSE;
++
++G_LOCK_EXTERN (fen_lock);
++#define	PROCESS_DELETING_INTERVAL	900 /* in second */
++
++static node_t* _head = NULL;
++static GList *deleting_nodes = NULL;
++static guint deleting_nodes_id = 0;
++
++static node_t* node_new (node_t* parent, const gchar* basename);
++static void node_delete (node_t* parent);
++static gboolean remove_node_internal (node_t* node, node_op_t* op);
++static void children_add (node_t *p, node_t *f);
++static void children_remove (node_t *p, node_t *f);
++static guint children_foreach_remove (node_t *f, GHRFunc func, gpointer user_data);
++static void children_foreach (node_t *f, GHFunc func, gpointer user_data);
++static gboolean children_remove_cb (gpointer key,
++  gpointer value,
++  gpointer user_data);
++
++static struct _dnode*
++_dnode_new (const gchar* filename, node_op_t* op)
++{
++    struct _dnode* d;
++
++    g_assert (op);
++    if ((d = g_new (struct _dnode, 1)) != NULL) {
++        d->filename = g_strdup (filename);
++        d->op = g_memdup (op, sizeof (node_op_t));
++        g_assert (d->op);
++        g_get_current_time (&d->tv);
++        g_time_val_add (&d->tv, PROCESS_DELETING_INTERVAL);
++    }
++    return d;
++}
++
++static void
++_dnode_free (struct _dnode* d)
++{
++    g_assert (d);
++    g_free (d->filename);
++    g_free (d->op);
++    g_free (d);
++}
++
++static gboolean
++g_timeval_lt (GTimeVal *val1, GTimeVal *val2)
++{
++    if (val1->tv_sec < val2->tv_sec)
++        return TRUE;
++  
++    if (val1->tv_sec > val2->tv_sec)
++        return FALSE;
++  
++    /* val1->tv_sec == val2->tv_sec */
++    if (val1->tv_usec < val2->tv_usec)
++        return TRUE;
++  
++    return FALSE;
++}
++
++static gboolean
++scan_deleting_nodes (gpointer data)
++{
++    struct _dnode* d;
++    GTimeVal tv_now;
++    GList* i;
++    GList* deleted_list = NULL;
++    gboolean ret = TRUE;
++    node_t* node;
++
++    g_get_current_time (&tv_now);
++
++    if (G_TRYLOCK (fen_lock)) {
++        for (i = deleting_nodes; i; i = i->next) {
++            d = (struct _dnode*)i->data;
++            /* Time to free, try only once */
++            if (g_timeval_lt (&d->tv, &tv_now)) {
++                if ((node = find_node (d->filename)) != NULL) {
++                    remove_node_internal (node, d->op);
++                }
++                _dnode_free (d);
++                deleted_list = g_list_prepend (deleted_list, i);
++            }
++        }
++
++        for (i = deleted_list; i; i = i->next) {
++            deleting_nodes = g_list_remove_link (deleting_nodes,
++              (GList *)i->data);
++            g_list_free_1 ((GList *)i->data);
++        }
++        g_list_free (deleted_list);
++
++        if (deleting_nodes == NULL) {
++            deleting_nodes_id = 0;
++            ret = FALSE;
++        }
++        G_UNLOCK (fen_lock);
++    }
++    return ret;
++}
++
++gpointer
++node_get_data (node_t* node)
++{
++    g_assert (node);
++    return node->user_data;
++}
++
++gpointer
++node_set_data (node_t* node, gpointer user_data)
++{
++    gpointer data = node->user_data;
++    g_assert (node);
++    node->user_data = user_data;
++    return data;
++}
++
++void
++travel_nodes (node_t* node, node_op_t* op)
++{
++    GList* children;
++    GList* i;
++
++    if (node) {
++        if (op && op->hit) {
++            op->hit (node, op->user_data);
++        }
++    }
++    children = g_hash_table_get_values (node->children);
++    if (children) {
++        for (i = children; i; i = i->next) {
++            travel_nodes (i->data, op);
++        }
++        g_list_free (children);
++    }
++}
++
++static node_t*
++find_node_internal (node_t* node, const gchar* filename, node_op_t* op)
++{
++    gchar* str;
++    gchar* token;
++    gchar* lasts;
++    node_t* parent;
++    node_t* child;
++    
++    g_assert (filename && filename[0] == '/');
++    g_assert (node);
++    
++    parent = node;
++    str = g_strdup (filename + strlen (NODE_NAME(parent)));
++    
++    if ((token = strtok_r (str, G_DIR_SEPARATOR_S, &lasts)) != NULL) {
++        do {
++            FN_W ("%s %s + %s\n", __func__, NODE_NAME(parent), token);
++            child = children_find (parent, token);
++            if (child) {
++                parent = child;
++            } else {
++                if (op && op->add_missing) {
++                    child = op->add_missing (parent, op->user_data);
++                    goto L_hit;
++                }
++                break;
++            }
++        } while ((token = strtok_r (NULL, G_DIR_SEPARATOR_S, &lasts)) != NULL);
++    } else {
++        /* It's the head */
++        g_assert (parent == _head);
++        child = _head;
++    }
++    
++    if (token == NULL && child) {
++    L_hit:
++        if (op && op->hit) {
++            op->hit (child, op->user_data);
++        }
++    }
++    g_free (str);
++    return child;
++}
++
++node_t*
++find_node (const gchar *filename)
++{
++    return find_node_internal (_head, filename, NULL);
++}
++
++node_t*
++find_node_full (const gchar* filename, node_op_t* op)
++{
++    return find_node_internal (_head, filename, op);
++}
++
++node_t*
++add_node (node_t* parent, const gchar* filename)
++{
++    gchar* str;
++    gchar* token;
++    gchar* lasts;
++    node_t* child = NULL;
++
++    g_assert (_head);
++    g_assert (filename && filename[0] == '/');
++
++    if (parent == NULL) {
++        parent = _head;
++    }
++    
++    str = g_strdup (filename + strlen (NODE_NAME(parent)));
++    
++    if ((token = strtok_r (str, G_DIR_SEPARATOR_S, &lasts)) != NULL) {
++        do {
++            FN_W ("%s %s + %s\n", __func__, NODE_NAME(parent), token);
++            child = node_new (parent, token);
++            if (child) {
++                children_add (parent, child);
++                parent = child;
++            } else {
++                break;
++            }
++        } while ((token = strtok_r (NULL, G_DIR_SEPARATOR_S, &lasts)) != NULL);
++    }
++    g_free (str);
++    if (token == NULL) {
++        return child;
++    } else {
++        return NULL;
++    }
++}
++
++/**
++ * delete recursively
++ */
++static gboolean
++remove_children (node_t* node, node_op_t* op)
++{
++    FN_W ("%s 0x%p %s\n", __func__, node, NODE_NAME(node));
++    if (children_num (node) > 0) {
++        children_foreach_remove (node, children_remove_cb,
++          (gpointer)op);
++    }
++    if (children_num (node) == 0) {
++        return TRUE;
++    }
++    return FALSE;
++}
++
++static gboolean
++remove_node_internal (node_t* node, node_op_t* op)
++{
++    node_t* parent = NULL;
++    /*
++     * If the parent is passive and doesn't have children, delete it.
++     * NOTE node_delete_deep is a depth first delete recursively.
++     * Top node is deleted in node_cancel_sub
++     */
++    g_assert (node);
++    g_assert (op && op->pre_del);
++    if (node != _head) {
++        if (remove_children (node, op)) {
++            if (node->user_data) {
++                if (!op->pre_del (node, op->user_data)) {
++                    return FALSE;
++                }
++            }
++            parent = node->parent;
++            children_remove (parent, node);
++            node_delete (node);
++            if (children_num (parent) == 0) {
++                remove_node_internal (parent, op);
++            }
++            return TRUE;
++        }
++        return FALSE;
++    }
++    return TRUE;
++}
++
++void
++pending_remove_node (node_t* node, node_op_t* op)
++{
++    struct _dnode* d;
++    GList* l;
++    
++    for (l = deleting_nodes; l; l=l->next) {
++        d = (struct _dnode*) l->data;
++        if (g_ascii_strcasecmp (d->filename, NODE_NAME(node)) == 0) {
++            return;
++        }
++    }
++    
++    d = _dnode_new (NODE_NAME(node), op);
++    g_assert (d);
++    deleting_nodes = g_list_prepend (deleting_nodes, d);
++    if (deleting_nodes_id == 0) {
++        deleting_nodes_id = g_timeout_add_seconds (PROCESS_DELETING_INTERVAL,
++          scan_deleting_nodes,
++          NULL);
++        g_assert (deleting_nodes_id > 0);
++    }
++}
++
++void
++remove_node (node_t* node, node_op_t* op)
++{
++    remove_node_internal (node, op);
++}
++
++static node_t*
++node_new (node_t* parent, const gchar* basename)
++{
++	node_t *f = NULL;
++
++    g_assert (basename && basename[0]);
++    if ((f = g_new0 (node_t, 1)) != NULL) {
++        if (parent) {
++            f->basename = g_strdup (basename);
++            f->filename = g_build_filename (G_DIR_SEPARATOR_S,
++              NODE_NAME(parent), basename, NULL);
++        } else {
++            f->basename = g_strdup (basename);
++            f->filename = g_strdup (basename);
++        }
++        f->children = g_hash_table_new_full (g_str_hash, g_str_equal,
++          NULL, (GDestroyNotify)node_delete);
++        FN_W ("[ %s ] 0x%p %s\n", __func__, f, NODE_NAME(f));
++    }
++	return f;
++}
++
++static void
++node_delete (node_t *f)
++{
++    FN_W ("[ %s ] 0x%p %s\n", __func__, f, NODE_NAME(f));
++    g_assert (g_hash_table_size (f->children) == 0);
++    g_assert (f->user_data == NULL);
++
++    g_hash_table_unref (f->children);
++    g_free (f->basename);
++    g_free (f->filename);
++    g_free (f);
++}
++
++static void
++children_add (node_t *p, node_t *f)
++{
++    FN_W ("%s [p] %8s [c] %8s\n", __func__, p->basename, f->basename);
++    g_hash_table_insert (p->children, f->basename, f);
++    f->parent = p;
++}
++
++static void
++children_remove (node_t *p, node_t *f)
++{
++    FN_W ("%s [p] %8s [c] %8s\n", __func__, p->basename, f->basename);
++    g_hash_table_steal (p->children, f->basename);
++    f->parent = NULL;
++}
++
++guint
++children_num (node_t *f)
++{
++    return g_hash_table_size (f->children);
++}
++
++node_t *
++children_find (node_t *f, const gchar *basename)
++{
++    return (node_t *) g_hash_table_lookup (f->children, (gpointer)basename);
++}
++
++/**
++ * depth first delete recursively
++ */
++static gboolean
++children_remove_cb (gpointer key,
++  gpointer value,
++  gpointer user_data)
++{
++    node_t* f = (node_t*)value;
++    node_op_t* op = (node_op_t*) user_data;
++    
++    g_assert (f->parent);
++
++    FN_W ("%s [p] %8s [c] %8s\n", __func__, f->parent->basename, f->basename);
++    if (remove_children (f, op)) {
++        if (f->user_data != NULL) {
++            return op->pre_del (f, op->user_data);
++        }
++        return TRUE;
++    }
++    return FALSE;
++}
++
++static guint
++children_foreach_remove (node_t *f, GHRFunc func, gpointer user_data)
++{
++    g_assert (f);
++    
++    return g_hash_table_foreach_remove (f->children, func, user_data);
++}
++
++static void
++children_foreach (node_t *f, GHFunc func, gpointer user_data)
++{
++    g_assert (f);
++    
++    g_hash_table_foreach (f->children, func, user_data);
++}
++
++gboolean
++node_class_init ()
++{
++    FN_W ("%s\n", __func__);
++    if (_head == NULL) {
++        _head = node_new (NULL, G_DIR_SEPARATOR_S);
++    }
++    return _head != NULL;
++}
+diff --git a/server/fen-node.h b/server/fen-node.h
+new file mode 100644
+index 0000000..7c16762
+--- /dev/null
++++ b/server/fen-node.h
+@@ -0,0 +1,72 @@
++/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:set expandtab ts=4 shiftwidth=4: */
++/* 
++ * Copyright (C) 2008 Sun Microsystem.
++ *
++ * This library is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU Lesser General Public
++ * License as published by the Free Software Foundation; either
++ * version 2 of the License, or (at your option) any later version.
++ *
++ * This library is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
++ * Lesser General Public License for more details.
++ *
++ * You should have received a copy of the GNU Lesser General
++ * Public License along with this library; if not, write to the
++ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
++ * Boston, MA 02111-1307, USA.
++ *
++ * Authors: Lin Ma <lin.ma at sun.com>
++ */
++
++#ifndef _FEN_NODE_H_
++#define _FEN_NODE_H_
++
++typedef struct node node_t;
++
++struct node
++{
++    gchar *filename;
++    gchar *basename;
++    gint stat;
++    
++	/* the parent and children of node */
++    node_t *parent;
++    GHashTable *children; /* children in basename */
++
++    gpointer user_data;
++};
++
++#define	IS_TOPNODE(fp)	(((node_t *)(fp))->parent == NULL)
++#define NODE_NAME(fp)	(((node_t *)(fp))->filename)
++
++typedef struct node_op
++{
++    /* find */
++    void (*hit) (node_t* node, gpointer user_data);
++    node_t* (*add_missing) (node_t* parent, gpointer user_data);
++    /* delete */
++    gboolean (*pre_del) (node_t* node, gpointer user_data);
++	/* data */
++    gpointer user_data;
++} node_op_t;
++
++node_t* add_node (node_t* parent, const gchar* filename);
++void remove_node (node_t* node, node_op_t* op);
++void pending_remove_node (node_t* node, node_op_t* op);
++
++void travel_nodes (node_t* node, node_op_t* op);
++node_t* find_node_full (const gchar* filename, node_op_t* op);
++node_t* find_node (const gchar *filename);
++
++node_t* children_find (node_t *f, const gchar *basename);
++guint children_num (node_t *f);
++
++gpointer node_get_data (node_t* node);
++gpointer node_set_data (node_t* node, gpointer user_data);
++
++gboolean node_class_init ();
++
++#endif /* _FEN_NODE_H_ */
+diff --git a/server/fen-sub.c b/server/fen-sub.c
+new file mode 100644
+index 0000000..373cba5
+--- /dev/null
++++ b/server/fen-sub.c
+@@ -0,0 +1,41 @@
++/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:set expandtab ts=4 shiftwidth=4: */
++/* 
++ * Copyright (C) 2008 Sun Microsystem.
++ *
++ * This library is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU Lesser General Public
++ * License as published by the Free Software Foundation; either
++ * version 2 of the License, or (at your option) any later version.
++ *
++ * This library is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
++ * Lesser General Public License for more details.
++ *
++ * You should have received a copy of the GNU Lesser General
++ * Public License along with this library; if not, write to the
++ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
++ * Boston, MA 02111-1307, USA.
++ *
++ * Authors: Lin Ma <lin.ma at sun.com>
++ */
++
++#include "config.h"
++#include "fen-sub.h"
++
++fen_sub*
++fen_sub_new (gpointer udata, gboolean is_mondir)
++{
++    fen_sub *sub;
++    sub = g_new (fen_sub, 1);
++    sub->user_data = udata;
++    sub->is_mondir = is_mondir;
++    return sub;
++}
++
++void
++fen_sub_delete (fen_sub *sub)
++{
++    g_free (sub);
++}
+diff --git a/server/fen-sub.h b/server/fen-sub.h
+new file mode 100644
+index 0000000..1f4bb5e
+--- /dev/null
++++ b/server/fen-sub.h
+@@ -0,0 +1,38 @@
++/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* vim:set expandtab ts=4 shiftwidth=4: */
++/* 
++ * Copyright (C) 2008 Sun Microsystem.
++ *
++ * This library is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU Lesser General Public
++ * License as published by the Free Software Foundation; either
++ * version 2 of the License, or (at your option) any later version.
++ *
++ * This library is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
++ * Lesser General Public License for more details.
++ *
++ * You should have received a copy of the GNU Lesser General
++ * Public License along with this library; if not, write to the
++ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
++ * Boston, MA 02111-1307, USA.
++ *
++ * Authors: Lin Ma <lin.ma at sun.com>
++ */
++
++#include <glib.h>
++
++#ifndef _FEN_SUB_H_
++#define _FEN_SUB_H_
++
++typedef struct _fen_sub
++{
++    gpointer user_data;
++    gboolean is_mondir;
++} fen_sub;
++
++fen_sub*	fen_sub_new (gpointer udata, gboolean is_mondir);
++void		fen_sub_delete (fen_sub *sub);
++
++#endif _FEN_SUB_H_
+diff --git a/server/gam_channel.c b/server/gam_channel.c
+index 56c3ea7..6db1692 100644
+--- a/server/gam_channel.c
++++ b/server/gam_channel.c
+@@ -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 @@ gam_client_conn_check_cred(GIOChannel * source, int fd,
+     } cmsg;
+ #endif
+ 
++#if defined(HAVE_GETPEERUCRED)
++    ucred_t *creds;
++#endif
++
+     s_uid = getuid();
+ 
+ #if defined(LOCAL_CREDS) && defined(HAVE_CMSGCRED)
+@@ -167,11 +177,25 @@ gam_client_conn_check_cred(GIOChannel * source, int fd,
+                       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;
+diff --git a/server/gam_fen.c b/server/gam_fen.c
+new file mode 100644
+index 0000000..303f191
+--- /dev/null
++++ b/server/gam_fen.c
+@@ -0,0 +1,119 @@
++/* -*- 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 "gam_error.h"
++#include "gam_fen.h"
++#include "gam_event.h"
++#include "gam_server.h"
++#include "gam_protocol.h"
++#include <glib.h>
++#include "fen-helper.h"
++
++/**
++ * 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)
++{
++    if (!fen_init ())
++        return FALSE;
++	
++	gam_server_install_kernel_hooks (GAMIN_K_FEN,
++      gam_fen_add_subscription,
++      gam_fen_remove_subscription,
++      gam_fen_remove_all_for,
++      NULL, NULL);
++	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)
++{
++    g_debug ("[ %s ] sub[0x%p]\n", __func__, sub);
++
++	gam_listener_add_subscription (gam_subscription_get_listener (sub), sub);
++    fen_add (gam_subscription_get_path(sub),
++      sub,
++      gam_subscription_is_dir (sub));
++	return TRUE;
++}
++
++/**
++ * 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)
++{
++    g_debug ("[ %s ] sub[0x%p]\n", __func__, sub);
++
++    fen_remove (gam_subscription_get_path(sub),
++      sub,
++      gam_subscription_is_dir (sub));
++	/* free subscription */
++    gam_subscription_cancel(sub);
++	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;
++	}
++}
+diff --git a/server/gam_fen.h b/server/gam_fen.h
+new file mode 100644
+index 0000000..07bba39
+--- /dev/null
++++ b/server/gam_fen.h
+@@ -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__ */
++
+diff --git a/server/gam_fs.c b/server/gam_fs.c
+index dd414b5..052e4d9 100644
+--- a/server/gam_fs.c
++++ b/server/gam_fs.c
+@@ -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_scan_mtab (void)
+ 	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 @@ gam_fs_scan_mtab (void)
+ 			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_init (void)
+ 		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 */
+diff --git a/server/gam_fs.h b/server/gam_fs.h
+index bc2d538..94e70fd 100644
+--- a/server/gam_fs.h
++++ b/server/gam_fs.h
+@@ -8,6 +8,7 @@ typedef enum {
+ #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
+diff --git a/server/gam_server.c b/server/gam_server.c
+index f92a691..e5da29f 100644
+--- a/server/gam_server.c
++++ b/server/gam_server.c
+@@ -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 @@ gam_init_subscriptions(void)
+ 			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 @@ main(int argc, const char *argv[])
+     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");
+diff --git a/server/gam_server.h b/server/gam_server.h
+index bc99e09..313dd84 100644
+--- a/server/gam_server.h
++++ b/server/gam_server.h
+@@ -16,7 +16,8 @@ typedef enum {
+ 	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 {
+diff --git a/tests/testing.c b/tests/testing.c
+index 9926c0a..4c08740 100644

@@ Diff output truncated at 100000 characters. @@
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.



More information about the devel mailing list