doc: Clarify documentation regarding g_file_replace and etags
[platform/upstream/glib.git] / gio / fen / fen-kernel.c
index 1747d8f..ed1e304 100644 (file)
@@ -1,8 +1,8 @@
 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* vim:set expandtab ts=4 shiftwidth=4: */
 /* 
- * Copyright (C) 2008 Sun Microsystems, Inc. All rights reserved.
- * Use is subject to license terms.
+ * Copyright (c) 2008, 2010 Oracle and/or its affiliates, Inc. All rights
+ * reserved.
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -15,9 +15,7 @@
  * 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.
+ * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
  *
  * Authors: Lin Ma <lin.ma@sun.com>
  */
@@ -25,7 +23,6 @@
 #include "config.h"
 #include <rctl.h>
 #include <strings.h>
-#include <errno.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
 #include "fen-dump.h"
 
 #ifdef GIO_COMPILATION
-#define FK_W if (fk_debug_enabled) g_warning
-static gboolean fk_debug_enabled = FALSE;
+#define FK_W if (FALSE) g_debug
 #else
 #include "gam_error.h"
 #define FK_W(...) GAM_DEBUG(DEBUG_INFO, __VA_ARGS__)
 #endif
 
-G_GNUC_INTERNAL G_LOCK_DEFINE (fen_lock);
-#define PE_ALLOC       64
-#define F_PORT(pfo)            (((_f *)(pfo))->port->port)
-#define F_NAME(pfo)            (((_f *)(pfo))->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 */
+G_LOCK_DEFINE (fen_lock);
 
-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 GList *pn_visible_list; /* the queue of ports which don't have the max objs */
 static GQueue *g_eventq = NULL;
-static void (*add_event_cb) (gpointer, fnode_event_t*);
+static timespec_t zero_wait;
+static void (*user_process_events_cb) (gpointer, node_event_t*);
+static port_event_t *pevents = NULL;
+static gint PE_ALLOC = 2048;
+static GHashTable *renamed_hash = NULL; /* <parent node, ev> */
+
+typedef struct _PSource {
+    GSource  source;            /* Inherit from GSource, must be the first. */
+    GPollFD  gfd;
+    gboolean pending;
+    uint_t   event_growing_factor;
+    uint_t   pending_events;
+} PSource;
+
+#define PGPFD(s)             (&((PSource *)(s))->gfd)
+#define SLEEP_BASE_TIME      10        /* in milliseconds */
+#define EXPECT_INC_EVENTS(pn)  (1 << (pn->event_growing_factor))
+
+#define RENAME_EVENTS_INTERVAL 500 /* in milliseconds */
+#define PROCESS_PORT_EVENTS_TIME 1000 /* in milliseconds */
+guint process_port_event_id = 0;
+
+static gchar* _event_strings(int event);
+static const gchar* _event_string (int event);
+static GSource *psource_new();
+
+static gboolean port_prepare(GSource *source, gint *timeout_);
+static gboolean port_check(GSource *source);
+static gboolean port_dispatch(GSource *source, GSourceFunc callback, gpointer user_data);
+static GSourceFuncs fen_source_func = {
+    port_prepare,
+    port_check,
+    port_dispatch,
+    NULL
+};
 
-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)
+static gboolean
+port_prepare(GSource *source, gint *timeout_)
 {
-    _f* fo = g_hash_table_lookup (_obj_fen_hash, f);
-    
-    if (fo) {
-        return fo->is_active;
-    }
     return FALSE;
 }
 
-static gchar*
-printevent (const char *pname, int event, const char *tag)
-{
-    static gchar       *event_string = NULL;
-    GString                    *str;
-
-    if (event_string) {
-        g_free(event_string);
-    }
-
-    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");
-    }
-    event_string = str->str;
-    g_string_free (str, FALSE);
-    return event_string;
-}
-
-static void
-port_add_kevent (int e, gpointer f)
+static gboolean
+port_check(GSource *source)
 {
-    fnode_event_t *ev, *tail;
-    GTimeVal t;
-    gboolean has_twin = FALSE;
-    
-    /*
-     * 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;
-    }
+       PSource *pn = (PSource *)source;
+    uint_t nget;
     
-    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;
+    if (pn->pending) {
+        pn->pending = FALSE;
+        g_source_add_poll(source, PGPFD(source));
+        g_source_unref(source);
+        return FALSE;
     }
 
-    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 (!(PGPFD(pn)->revents & G_IO_IN))
+        return FALSE;
+
+    if (port_getn(PGPFD(source)->fd, NULL, 0, &nget, 0) == 0) {
+        if (nget - pn->pending_events > EXPECT_INC_EVENTS(pn)) {
+            /* Sleep for a while. */
+            pn->pending_events = nget;
+            pn->event_growing_factor ++;
+
+            pn->pending = TRUE;
+            g_source_ref(source);
+            g_source_remove_poll(source, PGPFD(source));
+            g_timeout_add(SLEEP_BASE_TIME,
+              (GSourceFunc)port_check,
+              (gpointer)pn);
+            return FALSE;
         }
     }
-    
-    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);
-    }
+    pn->pending_events = 0;
+    pn->event_growing_factor = 0;
+
+    return TRUE;
 }
 
 static gboolean
-port_fetch_event_cb (void *arg)
+port_dispatch(GSource *source, GSourceFunc callback, gpointer user_data)
 {
-       pnode_t *pn = (pnode_t *)arg;
-    _f* fo;
+    node_t *f;
        uint_t nget = 0;
-       port_event_t pe[PE_ALLOC];
-    timespec_t timeout;
-    gpointer f;
-    gboolean ret = TRUE;
-    
-    /* FK_W ("IN <======== %s\n", __func__); */
+       uint_t total = 0;
+
+    FK_W ("%s 0x%p fd %d\n", __func__, source, PGPFD(source)->fd);
+
     G_LOCK (fen_lock);
-    
-    memset (&timeout, 0, sizeof (timespec_t));
     do {
         nget = 1;
-        if (port_getn (pn->port, pe, PE_ALLOC, &nget, &timeout) == 0) {
+        if (port_getn(PGPFD(source)->fd, pevents, PE_ALLOC, &nget, &zero_wait) == 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) {
-                        FK_W("%s\n",
-                          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);
+                f = (node_t *)pevents[i].portev_user;
+
+                if (pevents[i].portev_source == PORT_SOURCE_FILE) {
+
+                    NODE_CLE_STATE(f, NODE_STATE_ASSOCIATED);
+                    NODE_SET_STATE(f, NODE_STATE_HAS_EVENTS);
+
+                    if (HAS_NO_EXCEPTION_EVENTS(pevents[i].portev_events)) {
+                        /* If the events do not show it's deleted, update
+                         * file timestamp to avoid missing events next time.
+                         */
+                        if (node_lstat(f) != 0 /* || port_add(f) != 0 */) {
+                            /* Included deleted event. */
+                            pevents[i].portev_events |= FILE_DELETE;
+                        }
                     }
-                    break;
-                default:
-                    /* case PORT_SOURCE_TIMER: */
-                    FK_W ("[kernel] unknown portev_source %d\n", pe[i].portev_source);
+
+                    /* Queue it and waiting for processing. */
+                    g_queue_push_tail(g_eventq,
+                      node_event_new(pevents[i].portev_events, (gpointer)f));
+
+                } else {
+                    FK_W ("[kernel] unknown portev_source %d\n", pevents[i].portev_source);
                 }
             }
+
+            total += nget;
+
         } else {
             FK_W ("[kernel] port_getn %s\n", g_strerror (errno));
-            nget = 0;
+            break;
         }
     } while (nget == PE_ALLOC);
 
+    G_UNLOCK (fen_lock);
+
+    if (total > 0 && callback) {
+        FK_W ("[kernel] get total %ld events\n", total);
+        return callback (user_data);
+    }
+    return TRUE;
+}
+
+static gboolean
+process_renamed_hash_cb(gpointer key, gpointer value, gpointer user_data)
+{
+    node_event_t *ev = value;
+
+#if 0
+    node_add_event(ev->user_data, ev);
+#else
+    user_process_events_cb(ev->user_data, ev);
+#endif
+    /* Always delete self from hash. */
+    return TRUE;
+}
+
+static gboolean
+port_events_process_cb(gpointer user_data)
+{
+    node_event_t *ev;
+    
+    G_LOCK (fen_lock);
+
        /* Processing g_eventq */
-    port_process_kevents ();
+    while ((ev = (node_event_t*)g_queue_pop_head (g_eventq)) != NULL) {
+
+        /* FK_W ("[%s] 0x%p %s\n", __func__, ev, _event_string (ev->e)); */
+
+        {
+            gchar *log = _event_strings(ev->e);
+            FK_W ("%s %s %s\n", __func__, NODE_NAME(ev->user_data), log);
+            g_free(log);
+        }
+
+#ifdef GIO_COMPILATION
+        /* Use the parent node as a hash, because only the dir_subs in the
+         * parent node should receive MOVE event.
+         */
+        if (NODE_PARENT(ev->user_data)) {
+            if (ev->e == FILE_RENAME_TO) {
+                g_hash_table_insert(renamed_hash, NODE_PARENT(ev->user_data), ev);
+                g_time_val_add(&ev->rename_tv, RENAME_EVENTS_INTERVAL);
+                continue;
+            }
+            if (ev->e == FILE_RENAME_FROM) {
+                node_event_t *pair_ev;
+
+                pair_ev = g_hash_table_lookup(renamed_hash, NODE_PARENT(ev->user_data));
+                if (pair_ev && node_timeval_lt(&ev->ctv, &pair_ev->rename_tv)) {
+                    g_hash_table_remove(renamed_hash, NODE_PARENT(ev->user_data));
+                    pair_ev->pair_data = ev->user_data;
+                    /* Free ev, exchange pair_ev and ev. */
+                    node_event_delete(ev);
+                    ev = pair_ev;
+                }
+            }
+        }
+#endif
     
-    if (pn->ref == 0) {
-        pn->port_source_id = 0;
-        ret = FALSE;
+#if 0
+        node_add_event(ev->user_data, ev);
+#else
+        user_process_events_cb(ev->user_data, ev);
+#endif
     }
+
+    /* Processing the events in renamed_hash. TODO we should delay it and wait
+     * for more possible pair.
+     */
+    g_hash_table_foreach_remove(renamed_hash, process_renamed_hash_cb, NULL);
+
     G_UNLOCK (fen_lock);
-    /* FK_W ("OUT ========> %s\n", __func__); */
-       return ret;
+
+    process_port_event_id = 0;
+    return FALSE;
 }
 
-/*
- * ref - 1 if remove a watching file succeeded.
- */
-static void
-pnode_delete (pnode_t *pn)
+static gboolean
+port_events_read_cb(gpointer user_data)
 {
-    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);
+
+    if (process_port_event_id == 0) {
+        process_port_event_id = g_timeout_add(PROCESS_PORT_EVENTS_TIME,
+          port_events_process_cb,
+          NULL);
+    }
+
+       return TRUE;
 }
 
 /*
- * 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
+ * malloc PSource and port_create, start thread at pnode_ref.
+ * if psource_new succeeded, the PSource will never
+ * be freed. So PSource can be freed only in psource_new.
+ * Note pnode_monitor_remove_all can also free PSource, but currently no one
  * invork it.
  */
-static pnode_t *
-pnode_new ()
+static GSource*
+psource_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);
-        }
-       }
+    GSource *source = NULL;
+    int fd;
+
+    if ((fd = port_create()) >= 0) {
+        source = g_source_new(&fen_source_func, sizeof(PSource));
+        PGPFD(source)->fd = fd;
+        PGPFD(source)->events = G_IO_IN | G_IO_HUP | G_IO_ERR;
+        g_source_set_callback(source, port_events_read_cb, NULL, NULL);
+        g_source_attach(source, NULL);
+        g_source_unref(source);
+        g_source_add_poll(source, PGPFD(source));
+
+        FK_W ("%s 0x%p fd %d\n", __func__, source, PGPFD(source)->fd);
+    } else {
+        FK_W ("PORT_CREATE %s\n", g_strerror(errno));
+        g_return_val_if_reached(NULL);
+    }
 
-       return pn;
+       return source;
 }
 
-/*
- * port_add_internal
+/**
+ * port_add:
+ * @f:
  *
- * < private >
  * Unsafe, need lock fen_lock.
+ * port_add will associate a GSource to @f->source
  */
-static gboolean
-port_add_internal (file_obj_t* fobj, off_t* len,
-  gpointer f, gboolean need_stat)
+gint
+port_add(node_t *f)
 {
-    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);
-    }
+       GSource *source = f->source;
 
-    if (fo->is_active) {
-        return TRUE;
-    }
+    FK_W ("%s [0x%p] %s\n", __func__, f, NODE_NAME(f));
 
-    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(f);
+    g_assert(NODE_HAS_FLAG(f, NODE_FLAG_STAT_UPDATED));
+
+    /* if (!NODE_HAS_FLAG(f, NODE_FLAG_STAT_DONE)) { */
+    /*     if (NODE_STAT(f) != 0) { */
+    /*         return errno; */
+    /*     } */
+    /* } */
+
+    /* Try re-use f->pn. f->pn may be used by other request, e.g. f is deleted
+     * for a long time. So if pn is full, we try to open a new one.
+     */
+    if (!source) {
+start_over:
+        /* Try the next visible source. */
+        if (pn_visible_list) {
+            source = (GSource *)pn_visible_list->data;
+        } else {
+            if ((source = psource_new()) != NULL) {
+                g_assert (g_list_find (pn_visible_list, source) == NULL);
+                pn_visible_list = g_list_prepend (pn_visible_list, source);
+            }
         }
-        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;
+
+    if (port_associate(PGPFD(source)->fd, PORT_SOURCE_FILE, (uintptr_t)FILE_OBJECT(f),
+        CONCERNED_EVENTS,
+        (void *)f) == 0) {
+        f->source = source;
+        NODE_SET_STATE(f, NODE_STATE_ASSOCIATED);
+        NODE_CLE_FLAG(f, NODE_FLAG_STAT_UPDATED);
+        FK_W ("PORT_ASSOCIATE 0x%p OK\n", f);
+        return 0;
+    } else if (errno == EAGAIN) {
+        /* Full, remove it. */
+        pn_visible_list = g_list_remove (pn_visible_list, source);
+        /* Re-add to port */
+        goto start_over;
+
+    } else if (errno == ENOENT) {
+        /* File is not exist */
+    } else if (errno == ENOTSUP) {
+        /* FS is not supported. Currently we think it no longer make sense to
+         * monitor it, so clean the stat info and return 0 to ignore this
+         * node. If there are requirement, we can consider to add polling
+         * method.
+         */
+        NODE_CLE_FLAG(f, NODE_FLAG_STAT_UPDATED);
+        return 0;
     } 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);
+        FK_W ("PORT_ASSOCIATE 0x%p %s\n", f, g_strerror (errno));
     }
-    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);
+    /* No matter if associated successfully, stat info is out-of-date, so clean it. */
+    NODE_CLE_FLAG(f, NODE_FLAG_STAT_UPDATED);
+    return errno;
 }
 
-/*
+/**
  * port_remove:
  *
  * < private >
  * Unsafe, need lock fen_lock.
  */
 void
-_port_remove (gpointer f)
+port_remove (node_t *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) {
+    /* g_assert(f->source); */
+
+    if (NODE_HAS_STATE(f, NODE_STATE_ASSOCIATED)) {
+        /* Mark unregisted. */
+        if (port_dissociate(PGPFD(f->source)->fd, PORT_SOURCE_FILE, (uintptr_t)FILE_OBJECT(f)) == 0) {
             /*
              * Note, we can run foode_delete if dissociating is failed,
              * because there may be some pending events (mostly like
@@ -439,50 +392,29 @@ _port_remove (gpointer f)
              * 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);
+            NODE_CLE_STATE(f, NODE_STATE_ASSOCIATED);
+            FK_W ("PORT_DISSOCIATE 0x%p OK\n", f);
+        } else if (errno == ENOENT) {
+            /* The file has been removed from port, after port_get or before
+             * port_get but DELETED event has been generated.
+             * Ignored. */
         } else {
-            FK_W ("PORT_DISSOCIATE [%-20s] %s\n", F_NAME(fo), g_strerror (errno));
+            FK_W ("PORT_DISSOCIATE 0x%p %s\n", f, g_strerror (errno));
+            g_return_if_reached();
         }
     }
 }
 
-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*))
+port_class_init (void (*user_process_events_callback) (gpointer, node_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;
@@ -493,42 +425,127 @@ _port_class_init (void (*user_add_event) (gpointer, fnode_event_t*))
         return FALSE;
        } else {
         max_port_events = rctlblk_get_value(rblk);
-               FK_W ("[kernel] max_port_events = %u\n", max_port_events);
+               FK_W ("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");
+    renamed_hash = g_hash_table_new_full(g_direct_hash, g_direct_equal,
+      NULL, NULL);
+    if (renamed_hash == NULL) {
+               FK_W ("[kernel] FEN global renamed queue initializing faild\n");
         return FALSE;
     }
     if ((g_eventq = g_queue_new ()) == NULL) {
                FK_W ("[kernel] FEN global event queue initializing faild\n");
+        return FALSE;
+    }
+    if (user_process_events_callback == NULL) {
+               FK_W ("[kernel] FEN global no user_process_events_callback\n");
+        return FALSE;
     }
-    if (user_add_event == NULL) {
+    user_process_events_cb = user_process_events_callback;
+    memset (&zero_wait, 0, sizeof (timespec_t));
+
+    pevents = g_malloc(PE_ALLOC * sizeof(port_event_t));
+    if (pevents == NULL) {
+               FK_W ("[kernel] FEN global alloc pevents failed\n");
         return FALSE;
     }
-    add_event_cb = user_add_event;
+
        return TRUE;
 }
 
-fnode_event_t*
-_fnode_event_new (int event, gboolean has_twin, gpointer user_data)
+static gchar*
+printevent (const char *pname, int event, const char *tag)
 {
-    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;
+    static gchar       *event_string = NULL;
+    GString                    *str;
+
+    g_free(event_string);
+
+    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");
     }
-    return ev;
+    if (event & MOUNTEDOVER) {
+        str = g_string_append (str, " MOUNTEDOVER");
+    }
+    event_string = str->str;
+    g_string_free (str, FALSE);
+    return event_string;
 }
 
-void
-_fnode_event_delete (fnode_event_t* ev)
+static gchar *
+_event_strings(int event)
+{
+    GString *str = g_string_sized_new(80);
+
+    if (event & FILE_DELETE)
+        g_string_append(str, " FILE_DELETE");
+
+    if (event & FILE_RENAME_FROM)
+        g_string_append(str, " FILE_RENAME_FROM");
+
+    if (event & FILE_MODIFIED)
+        g_string_append(str, " FILE_MODIFIED");
+
+    if (event & FILE_RENAME_TO)
+        g_string_append(str, " FILE_RENAME_TO");
+
+    if (event & MOUNTEDOVER)
+        g_string_append(str, " MOUNTEDOVER");
+
+    if (event & FILE_ATTRIB)
+        g_string_append(str, " FILE_ATTRIB");
+
+    if (event & UNMOUNTED)
+        g_string_append(str, " UNMOUNTED");
+
+    if (event & FILE_ACCESS)
+        g_string_append(str, " FILE_ACCESS");
+
+    return g_string_free(str, FALSE);
+}
+
+static const gchar *
+_event_string (int event)
 {
-    g_free (ev);
+    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";
+    }
 }