/** * 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)); } } }
/* * port_remove * * < private > * Unsafe, need lock fen_lock. */ void port_remove (node_t *f) { /* 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 * 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. */ 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 0x%p %s\n", f, g_strerror (errno)); g_return_if_reached(); } } }
static gboolean port_dispatch(GSource *source, GSourceFunc callback, gpointer user_data) { node_t *f; uint_t nget = 0; uint_t total = 0; FK_W ("%s 0x%p fd %d\n", __func__, source, PGPFD(source)->fd); G_LOCK (fen_lock); do { nget = 1; if (port_getn(PGPFD(source)->fd, pevents, PE_ALLOC, &nget, &zero_wait) == 0) { int i; for (i = 0; i < nget; i++) { 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; } } /* 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)); 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; }
/* * 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); }
static gboolean port_events_process_cb(gpointer user_data) { node_event_t *ev; G_LOCK (fen_lock); /* Processing g_eventq */ 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 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); process_port_event_id = 0; return FALSE; }
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); } }
/* * 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; }
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; }
/** * 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; }
/* * 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 GSource* psource_new() { 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 source; }
extern gboolean port_class_init (void (*user_process_events_callback) (gpointer, node_event_t*)) { rctlblk_t *rblk; 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 ("max_port_events = %u\n", max_port_events); free (rblk); } 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; } 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; } return TRUE; }
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) { 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); } 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; }
/* * port_add: * @f: * * Unsafe, need lock fen_lock. * port_add will associate a GSource to @f->source */ gint port_add(node_t *f) { GSource *source = f->source; FK_W ("%s [0x%p] %s\n", __func__, f, NODE_NAME(f)); 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); } } } 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 0x%p %s\n", f, g_strerror (errno)); } /* No matter if associated successfully, stat info is out-of-date, so clean it. */ NODE_CLE_FLAG(f, NODE_FLAG_STAT_UPDATED); return errno; }