Index: server/fen-data.c
===================================================================
--- server/fen-data.c	(revision 0)
+++ server/fen-data.c	(revision 0)
@@ -0,0 +1,322 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set expandtab ts=4 shiftwidth=4: */
+
+#include "config.h"
+#include <sys/types.h>
+#include <strings.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <sys/param.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "gam_error.h"
+#include "fen-data.h"
+#include "fen-kernel.h"
+
+//#define FEN_DEBUG_ALL
+static GHashTable *fnode_hash = NULL;
+static pthread_mutex_t fnode_hash_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+#define MUTEX_LOCK(lock) \
+do { \
+	if (pthread_mutex_lock(&lock) != 0) { \
+		g_assert_not_reached (); \
+	} \
+} while (0)
+
+#define MUTEX_UNLOCK(lock) \
+do { \
+	if (pthread_mutex_unlock(&lock) != 0) { \
+		g_assert_not_reached (); \
+	} \
+} while (0)
+
+int
+fnode_trylock (pthread_mutex_t *mutex)
+{
+    int ret;
+    if ((ret = pthread_mutex_trylock (mutex)) == 0) {
+        return 0;
+    } else if (ret != EBUSY) {
+        perror ("fnode_trylock");
+        abort ();
+    }
+    return 1;
+}
+
+fnode_t *
+fnode_new (const gchar *path, port_emit_events_cb emit_events)
+{
+	fnode_t *fn = NULL;
+
+    g_assert (path && path[0]);
+	if ((fn = calloc (1, sizeof (fnode_t))) != NULL) {
+		if (pthread_mutex_init (&fn->ref_lock, NULL) != 0) {
+			perror ("fnode_new - pthread_mutex_init");
+			free (fn);
+			return NULL;
+		}
+		if (pthread_mutex_init (&fn->fn_lock, NULL) != 0) {
+			perror ("fnode_new - pthread_mutex_init");
+			free (fn);
+			return NULL;
+		}
+		if (pthread_mutex_init (&fn->prop_lock, NULL) != 0) {
+			perror ("fnode_new - pthread_mutex_init");
+			free (fn);
+			return NULL;
+		}
+		FN_PATH(fn) = strdup (path);
+        fn->emit_events = emit_events;
+        fn->ref = 1;
+	}
+	return fn;
+}
+
+void
+fnode_delete (fnode_t *fn)
+{
+    g_assert (fn->ref == 0);
+    g_assert (fn->subs == NULL);
+    GAM_DEBUG(DEBUG_INFO, "FENDATA : FNODE_DELETE 0x%p %s\n", fn, FN_PATH(fn));
+    if (pthread_mutex_destroy (&fn->prop_lock) != 0) {
+        g_assert_not_reached ();
+    }
+    if (pthread_mutex_destroy (&fn->fn_lock) != 0) {
+        g_assert_not_reached ();
+    }
+    if (pthread_mutex_destroy (&fn->ref_lock) != 0) {
+        g_assert_not_reached ();
+    }
+    free (FN_PATH(fn));
+    free (fn);
+}
+
+void
+fnode_set_stat (fnode_t *f, struct stat *sbuf)
+{
+	file_obj_t *fobj = &f->fobj;
+
+    MUTEX_LOCK (f->prop_lock);
+    fobj->fo_atime = sbuf->st_atim;
+    fobj->fo_mtime = sbuf->st_mtim;
+    fobj->fo_ctime = sbuf->st_ctim;
+    f->is_dir = S_ISDIR (sbuf->st_mode) ? TRUE : FALSE;
+    MUTEX_UNLOCK (f->prop_lock);
+}
+
+int
+fnode_mdcount (fnode_t *f)
+{
+    int count;
+    MUTEX_LOCK (f->prop_lock);
+    g_assert (f->md_ref >= 0);
+    count = f->md_ref;
+    MUTEX_UNLOCK (f->prop_lock);
+    return count;
+}
+
+void
+fnode_mdref (fnode_t *f)
+{
+    MUTEX_LOCK (f->prop_lock);
+    f->md_ref ++;
+    g_assert (f->md_ref > 0);
+    MUTEX_UNLOCK (f->prop_lock);
+}
+
+void
+fnode_mdunref (fnode_t *f)
+{
+    MUTEX_LOCK (f->prop_lock);
+    g_assert (f->md_ref > 0);
+    f->md_ref --;
+    MUTEX_UNLOCK (f->prop_lock);
+}
+
+void
+fnode_ref (fnode_t *f)
+{
+    MUTEX_LOCK (f->ref_lock);
+    f->ref ++;
+    MUTEX_UNLOCK (f->ref_lock);
+}
+
+void
+fnode_unref (fnode_t *f)
+{
+    MUTEX_LOCK (f->ref_lock);
+    f->ref --;
+    MUTEX_UNLOCK (f->ref_lock);
+}
+
+gboolean
+fnode_is_dir (fnode_t *f)
+{
+    gboolean retval;
+
+    MUTEX_LOCK (f->prop_lock);
+    retval = f->is_dir;
+    MUTEX_UNLOCK (f->prop_lock);
+    return retval;
+}
+
+void
+fnode_emit_events (fnode_t *f, int events)
+{
+    g_assert (f->emit_events);
+    f->emit_events (f, events);
+}
+
+gboolean
+fn_hash_init ()
+{
+	fnode_hash = g_hash_table_new(g_str_hash, g_str_equal);
+}
+
+fnode_t *
+fn_hash_safe_lookup (const gchar *file_name)
+{
+	fnode_t *fn;
+
+    MUTEX_LOCK (fnode_hash_mutex);
+	fn = (fnode_t *) g_hash_table_lookup (fnode_hash, file_name);
+    MUTEX_UNLOCK (fnode_hash_mutex);
+    return fn;
+}
+
+fnode_t *
+fn_hash_safe_node_new (const gchar *file_name,
+                       port_emit_events_cb emit_events)
+{
+	fnode_t *fn;
+
+    MUTEX_LOCK (fnode_hash_mutex);
+	fn = (fnode_t *) g_hash_table_lookup (fnode_hash, file_name);
+	if (fn == NULL) {
+		/* there is no lock now */
+		fn = fnode_new (file_name, emit_events);
+		if (fn != NULL) {
+            GAM_DEBUG(DEBUG_INFO, "FENDATA : FNODE_NEW 0x%p %s\n", fn, FN_PATH(fn));
+            g_hash_table_insert (fnode_hash, (gpointer) FN_PATH(fn), fn);
+		}
+	} else {
+        fnode_ref (fn);
+	}
+    MUTEX_UNLOCK (fnode_hash_mutex);
+	return fn;
+}
+
+void
+fn_hash_safe_foreach (GHFunc func, gpointer user_data)
+{
+	MUTEX_LOCK (fnode_hash_mutex);
+	g_hash_table_foreach (fnode_hash, func, user_data);
+	MUTEX_UNLOCK (fnode_hash_mutex);
+}
+
+void
+fn_hash_safe_foreach_remove (GHRFunc func, gpointer user_data)
+{
+	MUTEX_LOCK (fnode_hash_mutex);
+	g_hash_table_foreach_remove (fnode_hash, func, user_data);
+	MUTEX_UNLOCK (fnode_hash_mutex);
+}
+
+void
+fn_hash_foreach_remove (GHRFunc func, gpointer user_data)
+{
+	g_hash_table_foreach_remove (fnode_hash, func, user_data);
+}
+
+void
+fn_hash_lock ()
+{
+	MUTEX_LOCK (fnode_hash_mutex);
+}
+
+int
+fn_hash_trylock ()
+{
+    int ret;
+    if ((ret = pthread_mutex_trylock (&fnode_hash_mutex)) == 0) {
+        return 0;
+    } else if (ret != EBUSY) {
+        perror ("fn_hash_trylock");
+        abort ();
+    }
+    return 1;
+}
+
+void
+fn_hash_unlock ()
+{
+	MUTEX_UNLOCK (fnode_hash_mutex);
+}
+
+static void
+fn_hash_foreach_report_fnode (gpointer key,
+                              gpointer value,
+                              gpointer user_data)
+{
+    fnode_t *fn = (fnode_t *) value;
+    GList *idx;
+
+    g_assert (key == FN_PATH (fn));
+    GAM_DEBUG(DEBUG_INFO, "FENDATA : fnode 0x%p\n", fn);
+
+    GAM_DEBUG(DEBUG_INFO, "FENDATA : {\n");
+    MUTEX_LOCK (fn->fn_lock);
+    GAM_DEBUG(DEBUG_INFO, "FENDATA :   %-14s = %s\n", "path", (gchar*)key);
+    GAM_DEBUG(DEBUG_INFO, "FENDATA :   %-14s = %d\n", "ref", fn->ref);
+    GAM_DEBUG(DEBUG_INFO, "FENDATA     %-14s = 0x%p\n", "parent", fn->parent);
+    GAM_DEBUG(DEBUG_INFO, "FENDATA :   %-14s = 0x%p\n", "subs", fn->subs);
+
+    for (idx = fn->subs; idx; idx = idx->next) {
+        GAM_DEBUG(DEBUG_INFO, "FENDATA :     {\n");
+        GAM_DEBUG(DEBUG_INFO, "FENDATA :       %-4s = 0x%p\n", "p", idx->data);
+        GAM_DEBUG(DEBUG_INFO, "FENDATA :       %-4s = %s\n", "s", gam_subscription_get_path(idx->data));
+        GAM_DEBUG(DEBUG_INFO, "FENDATA :     }\n");
+    }
+
+    GAM_DEBUG(DEBUG_INFO, "FENDATA     %-14s = 0x%p\n", "pnode", fn->pn);
+    MUTEX_UNLOCK (fn->fn_lock);
+    GAM_DEBUG(DEBUG_INFO, "FENDATA : }\n");
+}
+
+void
+fn_hash_safe_debug ()
+{
+    while (1) {
+        guint size;
+        GList *keys, *idx;
+
+        if (fn_hash_trylock () == 0) {
+            size = g_hash_table_size (fnode_hash);
+#ifdef FEN_DEBUG_ALL
+            if (size > 0) {
+                g_hash_table_foreach (fnode_hash,
+                                      fn_hash_foreach_report_fnode,
+                                      NULL);
+            }
+#else
+            if (size > 0 && size <= 2) {
+                g_hash_table_foreach (fnode_hash,
+                                      fn_hash_foreach_report_fnode,
+                                      NULL);
+            }
+#endif
+            MUTEX_UNLOCK (fnode_hash_mutex);
+        }
+#ifdef FEN_DEBUG_ALL
+        if (size > 0) {
+            GAM_DEBUG(DEBUG_INFO, "FENDATA : HASH HAS [ %6d ] NODES\n", size);
+        }
+#endif
+        sleep (10);
+    }
+}
