1 /* Gamin
2  * Copyright (C) 2003 James Willcox, Corey Bowers
3  * Copyright (C) 2004 Daniel Veillard
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free
17  * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18  */
19 
20 #include "server_config.h"
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <sys/types.h>
24 #include <sys/stat.h>
25 #include <unistd.h>
26 #include <time.h>
27 #include <errno.h>
28 #include <string.h>
29 #include <glib.h>
30 #include "fam.h"
31 #include "gam_error.h"
32 #include "gam_tree.h"
33 #include "gam_poll_dnotify.h"
34 #include "gam_event.h"
35 #include "gam_server.h"
36 #include "gam_protocol.h"
37 #include "gam_event.h"
38 #include "gam_excludes.h"
39 
40 #define VERBOSE_POLL
41 #define VERBOSE_POLL2
42 
43 static gboolean gam_poll_dnotify_add_subscription(GamSubscription * sub);
44 static gboolean gam_poll_dnotify_remove_subscription(GamSubscription * sub);
45 static gboolean gam_poll_dnotify_remove_all_for(GamListener * listener);
46 static GaminEventType gam_poll_dnotify_poll_file(GamNode * node);
47 static gboolean gam_poll_dnotify_scan_callback(gpointer data);
48 
49 
50 gboolean
gam_poll_dnotify_init()51 gam_poll_dnotify_init ()
52 {
53 	gam_poll_generic_init ();
54 	gam_server_install_poll_hooks (GAMIN_P_DNOTIFY,
55 				       gam_poll_dnotify_add_subscription,
56 				       gam_poll_dnotify_remove_subscription,
57 				       gam_poll_dnotify_remove_all_for,
58 				       gam_poll_dnotify_poll_file);
59 
60 	g_timeout_add(1000, gam_poll_dnotify_scan_callback, NULL);
61 	return TRUE;
62 }
63 
64 /**
65  * gam_poll_delist_node:
66  * @node: the node to delist
67  *
68  * This function is called when kernel monitoring for a node should
69  * be turned off.
70  */
71 static void
gam_poll_dnotify_delist_node(GamNode * node)72 gam_poll_dnotify_delist_node(GamNode * node)
73 {
74 	GList *subs;
75 	const char *path;
76 
77 	path = gam_node_get_path(node);
78 
79 	if (gam_exclude_check(path) || gam_fs_get_mon_type (path) != GFS_MT_KERNEL)
80 		return;
81 
82 	GAM_DEBUG(DEBUG_INFO, "poll-dnotify: Disabling kernel monitoring for %s\n", path);
83 
84 	subs = gam_node_get_subscriptions(node);
85 	while (subs != NULL) {
86 		gam_poll_generic_trigger_handler (path, GAMIN_DEACTIVATE, node);
87 		subs = subs->next;
88 	}
89 }
90 
91 /**
92  * gam_poll_relist_node:
93  * @node: the node to delist
94  *
95  * This function is called when kernel monitoring for a node should
96  * be turned on (again).
97  */
98 static void
gam_poll_dnotify_relist_node(GamNode * node)99 gam_poll_dnotify_relist_node(GamNode * node)
100 {
101 	GList *subs;
102 	const char *path;
103 
104 	path = gam_node_get_path(node);
105 	GAM_DEBUG(DEBUG_INFO, "poll-dnotify: Enabling kernel monitoring for %s\n", path);
106 
107 	if (gam_exclude_check(path) || gam_fs_get_mon_type(path) != GFS_MT_KERNEL)
108 		return;
109 
110 	subs = gam_node_get_subscriptions(node);
111 
112 	while (subs != NULL) {
113 		gam_poll_generic_trigger_handler (path, GAMIN_ACTIVATE, node);
114 		subs = subs->next;
115 	}
116 }
117 
118 /**
119  * gam_poll_flowon_node:
120  * @node: the node to delist
121  *
122  * This function is called when kernel monitoring flow control for a
123  * node should be started
124  */
125 static void
gam_poll_dnotify_flowon_node(GamNode * node)126 gam_poll_dnotify_flowon_node(GamNode * node)
127 {
128 	const char *path;
129 
130 	path = gam_node_get_path(node);
131 
132 	if (gam_exclude_check(path) || gam_fs_get_mon_type(path) != GFS_MT_KERNEL)
133 		return;
134 
135 	GAM_DEBUG(DEBUG_INFO, "poll-dnotify: Enabling flow control for %s\n", path);
136 
137 	gam_poll_generic_trigger_handler (path, GAMIN_FLOWCONTROLSTART, node);
138 }
139 
140 /**
141  * gam_poll_flowoff_node:
142  * @node: the node to delist
143  *
144  * This function is called when kernel monitoring flow control for a
145  * node should be started
146  */
147 static void
gam_poll_dnotify_flowoff_node(GamNode * node)148 gam_poll_dnotify_flowoff_node(GamNode * node)
149 {
150 	const char *path;
151 
152 	path = gam_node_get_path(node);
153 
154 	if (gam_exclude_check(path) || gam_fs_get_mon_type(path) != GFS_MT_KERNEL)
155 		return;
156 
157 	GAM_DEBUG(DEBUG_INFO, "poll-dnotify: Disabling flow control for %s\n", path);
158 
159 	gam_poll_generic_trigger_handler (path, GAMIN_FLOWCONTROLSTOP, node);
160 }
161 
162 static GaminEventType
gam_poll_dnotify_poll_file(GamNode * node)163 gam_poll_dnotify_poll_file(GamNode * node)
164 {
165     GaminEventType event;
166     struct stat sbuf;
167     int stat_ret;
168     const char *path;
169 
170     /* If not enough time has passed since the last time we polled this node, stop here */
171     if (node->lasttime && gam_poll_generic_get_delta_time (node->lasttime) < node->poll_time)
172         return 0;
173 
174     path = gam_node_get_path(node);
175 #ifdef VERBOSE_POLL
176     GAM_DEBUG(DEBUG_INFO, "Poll: poll_file for %s called\n", path);
177 #endif
178 
179     memset(&sbuf, 0, sizeof(struct stat));
180     if (node->lasttime == 0) {
181         GAM_DEBUG(DEBUG_INFO, "Poll: file is new\n");
182         stat_ret = stat(node->path, &sbuf);
183         if (stat_ret != 0)
184             gam_node_set_pflag (node, MON_MISSING);
185         else
186             gam_node_set_is_dir(node, (S_ISDIR(sbuf.st_mode) != 0));
187 
188         if (gam_exclude_check(path) || gam_fs_get_mon_type (path) != GFS_MT_KERNEL)
189             gam_node_set_pflag (node, MON_NOKERNEL);
190 
191         memcpy(&(node->sbuf), &(sbuf), sizeof(struct stat));
192         node->lasttime = gam_poll_generic_get_time ();
193 
194         if (stat_ret == 0)
195             return 0;
196         else
197             return GAMIN_EVENT_DELETED;
198     }
199 
200 #ifdef VERBOSE_POLL
201     GAM_DEBUG(DEBUG_INFO, " at %d delta %d : %d\n", gam_poll_generic_get_time(), gam_poll_generic_get_time() - node->lasttime, node->checks);
202 #endif
203 
204     event = 0;
205 
206     stat_ret = stat(node->path, &sbuf);
207     if (stat_ret != 0) {
208         if ((gam_errno() == ENOENT) && (!gam_node_has_pflag(node, MON_MISSING))) {
209             /* deleted */
210             gam_node_set_pflags (node, MON_MISSING);
211 
212             gam_poll_generic_remove_busy(node);
213             if (gam_node_get_subscriptions(node) != NULL) {
214                 gam_poll_dnotify_delist_node(node);
215                 gam_poll_generic_add_missing(node);
216             }
217             event = GAMIN_EVENT_DELETED;
218         }
219     } else if (gam_node_has_pflag (node, MON_MISSING)) {
220         /* created */
221         gam_node_unset_pflag (node, MON_MISSING);
222         event = GAMIN_EVENT_CREATED;
223 #ifdef ST_MTIM_NSEC
224     } else if ((node->sbuf.st_mtim.tv_sec != sbuf.st_mtim.tv_sec) ||
225            (node->sbuf.st_mtim.tv_nsec != sbuf.st_mtim.tv_nsec) ||
226            (node->sbuf.st_size != sbuf.st_size) ||
227            (node->sbuf.st_ctim.tv_sec != sbuf.st_ctim.tv_sec) ||
228            (node->sbuf.st_ctim.tv_nsec != sbuf.st_ctim.tv_nsec))
229     {
230         event = GAMIN_EVENT_CHANGED;
231     } else {
232 #ifdef VERBOSE_POLL
233         GAM_DEBUG(DEBUG_INFO, "Poll: poll_file %s unchanged\n", path);
234         GAM_DEBUG(DEBUG_INFO, "%d %d : %d %d\n", node->sbuf.st_mtim.tv_sec, node->sbuf.st_mtim.tv_nsec, sbuf.st_mtim.tv_sec, sbuf.st_mtim.tv_nsec);
235 #endif
236 #else
237     } else if ((node->sbuf.st_mtime != sbuf.st_mtime) ||
238            (node->sbuf.st_size != sbuf.st_size) ||
239            (node->sbuf.st_ctime != sbuf.st_ctime))
240     {
241         event = GAMIN_EVENT_CHANGED;
242 #ifdef VERBOSE_POLL
243         GAM_DEBUG(DEBUG_INFO, "%d : %d\n", node->sbuf.st_mtime, sbuf.st_mtime);
244 #endif
245 #endif
246     }
247 
248     /*
249     * TODO: handle the case where a file/dir is removed and replaced by
250     *       a dir/file
251     */
252     if (stat_ret == 0)
253         gam_node_set_is_dir(node, (S_ISDIR(sbuf.st_mode) != 0));
254 
255     memcpy(&(node->sbuf), &(sbuf), sizeof(struct stat));
256     node->sbuf.st_mtime = sbuf.st_mtime; // VALGRIND!
257 
258     /*
259     * if kernel monitoring prohibited, stop here
260     */
261     if (gam_node_has_pflag (node, MON_NOKERNEL))
262         return (event);
263 
264     /*
265     * load control, switch back to poll on very busy resources
266     * and back when no update has happened in 5 seconds
267     */
268     if (gam_poll_generic_get_time() == node->lasttime) {
269         if (!gam_node_has_pflag (node, MON_BUSY)) {
270             if (node->sbuf.st_mtime == gam_poll_generic_get_time())
271                 node->checks++;
272         }
273     } else {
274         node->lasttime = gam_poll_generic_get_time();
275         if (gam_node_has_pflag (node, MON_BUSY)) {
276             if (event == 0)
277                 node->checks++;
278         } else {
279             node->checks = 0;
280         }
281     }
282 
283     if ((node->checks >= 4) && (!gam_node_has_pflag (node, MON_BUSY))) {
284         if ((gam_node_get_subscriptions(node) != NULL) &&
285             (!gam_exclude_check(node->path) && gam_fs_get_mon_type (node->path) == GFS_MT_KERNEL))
286         {
287             GAM_DEBUG(DEBUG_INFO, "switching %s back to polling\n", path);
288             gam_node_set_pflag (node, MON_BUSY);
289             node->checks = 0;
290             gam_poll_generic_add_busy(node);
291             gam_poll_dnotify_flowon_node(node);
292             /*
293             * DNotify can be nasty here, we will miss events for parent dir
294             * if we are not careful about it
295             */
296             if (!gam_node_is_dir(node)) {
297                 GamNode *parent = gam_node_parent(node);
298 
299                 if ((parent != NULL) &&
300                     (gam_node_get_subscriptions(parent) != NULL))
301                 {
302                     gam_poll_generic_add_busy(parent);
303                     /* gam_poll_flowon_node(parent); */
304                 }
305             }
306         }
307     }
308 
309     if ((event == 0) && gam_node_has_pflag (node, MON_BUSY) && (node->checks > 5))
310     {
311         if ((gam_node_get_subscriptions(node) != NULL) &&
312             (!gam_exclude_check(node->path) && gam_fs_get_mon_type (node->path) == GFS_MT_KERNEL))
313         {
314             GAM_DEBUG(DEBUG_INFO, "switching %s back to kernel monitoring\n", path);
315             gam_node_unset_pflag (node, MON_BUSY);
316             node->checks = 0;
317             gam_poll_generic_remove_busy(node);
318             gam_poll_dnotify_flowoff_node(node);
319         }
320     }
321 
322     return (event);
323 }
324 
325 /**
326  * node_add_subscription:
327  * @node: the node tree pointer
328  * @sub: the pointer to the subscription
329  *
330  * register a subscription for this node
331  *
332  * Returns 0 in case of success and -1 in case of failure
333  */
334 static int
node_add_subscription(GamNode * node,GamSubscription * sub)335 node_add_subscription(GamNode * node, GamSubscription * sub)
336 {
337     if ((node == NULL) || (sub == NULL))
338         return (-1);
339 
340     if ((node->path == NULL) || (node->path[0] != '/'))
341         return (-1);
342 
343     GAM_DEBUG(DEBUG_INFO, "node_add_subscription(%s)\n", node->path);
344     gam_node_add_subscription(node, sub);
345 
346     if (gam_exclude_check(node->path) || gam_fs_get_mon_type (node->path) == GFS_MT_POLL) {
347         GAM_DEBUG(DEBUG_INFO, "  gam_exclude_check: true\n");
348         if (node->lasttime == 0)
349             gam_poll_dnotify_poll_file(node);
350 
351         gam_poll_generic_add_missing(node);
352         return (0);
353     }
354 
355 	gam_poll_generic_trigger_handler (node->path, GAMIN_ACTIVATE, node);
356 
357     return (0);
358 }
359 
360 /**
361  * node_remove_subscription:
362  * @node: the node tree pointer
363  * @sub: the pointer to the subscription
364  *
365  * Removes a subscription for this node
366  *
367  * Returns 0 in case of success and -1 in case of failure
368  */
369 
370 static int
node_remove_subscription(GamNode * node,GamSubscription * sub)371 node_remove_subscription(GamNode * node, GamSubscription * sub)
372 {
373     const char *path;
374 
375     if ((node == NULL) || (sub == NULL))
376         return (-1);
377 
378     if ((node->path == NULL) || (node->path[0] != '/'))
379         return (-1);
380 
381     GAM_DEBUG(DEBUG_INFO, "node_remove_subscription(%s)\n", node->path);
382 
383     gam_node_remove_subscription(node, sub);
384 
385     path = node->path;
386     if (gam_exclude_check(path) || gam_fs_get_mon_type (path) == GFS_MT_POLL) {
387         GAM_DEBUG(DEBUG_INFO, "  gam_exclude_check: true\n");
388         return (0);
389     }
390 
391     if (node->pflags == MON_BUSY) {
392         GAM_DEBUG(DEBUG_INFO, "  node is busy\n");
393     } else if (gam_node_has_pflags (node, MON_ALL_PFLAGS)) {
394         GAM_DEBUG(DEBUG_INFO, "  node has flag %d\n", node->pflags);
395         return (0);
396     }
397 
398     /* DNotify makes our life miserable here */
399 	gam_poll_generic_trigger_handler (node->path, GAMIN_DEACTIVATE, node);
400 
401     return (0);
402 }
403 
404 static gboolean
node_remove_directory_subscription(GamNode * node,GamSubscription * sub)405 node_remove_directory_subscription(GamNode * node, GamSubscription * sub)
406 {
407     GList *children, *l;
408     gboolean remove_dir;
409 
410     GAM_DEBUG(DEBUG_INFO, "remove_directory_subscription %s\n",
411               gam_node_get_path(node));
412 
413     node_remove_subscription(node, sub);
414 
415     remove_dir = (gam_node_get_subscriptions(node) == NULL);
416 
417     children = gam_tree_get_children(gam_poll_generic_get_tree(), node);
418     for (l = children; l; l = l->next) {
419         GamNode *child = (GamNode *) l->data;
420 
421         if ((!gam_node_get_subscriptions(child)) && (remove_dir) &&
422             (!gam_tree_has_children(gam_poll_generic_get_tree(), child))) {
423             gam_poll_generic_unregister_node (child);
424 
425             gam_tree_remove(gam_poll_generic_get_tree(), child);
426         } else {
427             remove_dir = FALSE;
428         }
429     }
430 
431     g_list_free(children);
432 
433     /*
434      * do not remove the directory if the parent has a directory subscription
435      */
436     remove_dir = ((gam_node_get_subscriptions(node) == NULL) &&
437                   (!gam_node_has_dir_subscriptions
438                    (gam_node_parent(node))));
439 
440     if (remove_dir) {
441         GAM_DEBUG(DEBUG_INFO, "  => remove_dir %s\n",
442                   gam_node_get_path(node));
443     }
444     return remove_dir;
445 }
446 
447 
448 /**
449  * Adds a subscription to be polled.
450  *
451  * @param sub a #GamSubscription to be polled
452  * @returns TRUE if adding the subscription succeeded, FALSE otherwise
453  */
454 static gboolean
gam_poll_dnotify_add_subscription(GamSubscription * sub)455 gam_poll_dnotify_add_subscription(GamSubscription * sub)
456 {
457     const char *path = gam_subscription_get_path (sub);
458     GamNode *node = gam_tree_get_at_path (gam_poll_generic_get_tree(), path);
459     int node_is_dir = FALSE;
460 
461     gam_listener_add_subscription(gam_subscription_get_listener(sub), sub);
462 
463 	gam_poll_generic_update_time ();
464 
465     if (!node)
466     {
467         node = gam_tree_add_at_path(gam_poll_generic_get_tree(), path, gam_subscription_is_dir(sub));
468     }
469 
470     if (node_add_subscription(node, sub) < 0)
471     {
472         gam_error(DEBUG_INFO, "Failed to add subscription for: %s\n", path);
473         return FALSE;
474     }
475 
476     node_is_dir = gam_node_is_dir(node);
477     if (node_is_dir)
478     {
479         gam_poll_generic_first_scan_dir(sub, node, path);
480     } else {
481         GaminEventType event;
482 
483         event = gam_poll_dnotify_poll_file (node);
484         GAM_DEBUG(DEBUG_INFO, "New file subscription: %s event %d\n", path, event);
485 
486         if ((event == 0) || (event == GAMIN_EVENT_EXISTS) ||
487             (event == GAMIN_EVENT_CHANGED) ||
488             (event == GAMIN_EVENT_CREATED))
489         {
490             if (gam_subscription_is_dir(sub)) {
491                 /* we are watching a file but requested a directory */
492                 gam_server_emit_one_event(path, node_is_dir, GAMIN_EVENT_DELETED, sub, 0);
493             } else {
494                 gam_server_emit_one_event(path, node_is_dir, GAMIN_EVENT_EXISTS, sub, 0);
495             }
496         } else if (event != 0) {
497             gam_server_emit_one_event(path, node_is_dir, GAMIN_EVENT_DELETED, sub, 0);
498         }
499 
500         gam_server_emit_one_event(path, node_is_dir, GAMIN_EVENT_ENDEXISTS, sub, 0);
501     }
502     if (gam_node_has_pflag (node, MON_MISSING) || gam_node_has_pflag (node, MON_NOKERNEL))
503         gam_poll_generic_add_missing(node);
504 
505     if (!node_is_dir) {
506         char *parent;
507         parent = g_path_get_dirname(path);
508         node = gam_tree_get_at_path(gam_poll_generic_get_tree(), parent);
509         if (!node)
510         {
511             node = gam_tree_add_at_path(gam_poll_generic_get_tree(), parent, gam_subscription_is_dir (sub));
512         }
513         g_free(parent);
514     }
515 
516 	gam_poll_generic_add (node);
517 
518     GAM_DEBUG(DEBUG_INFO, "Poll: added subscription\n");
519     return TRUE;
520 }
521 
522 /**
523  * gam_poll_remove_subscription_real:
524  * @sub: a subscription
525  *
526  * Implements the removal of a subscription, including
527  * trimming the tree and deactivating the kernel back-end if needed.
528  */
529 static void
gam_poll_dnotify_remove_subscription_real(GamSubscription * sub)530 gam_poll_dnotify_remove_subscription_real(GamSubscription * sub)
531 {
532     GamNode *node;
533 
534     node = gam_tree_get_at_path(gam_poll_generic_get_tree(), gam_subscription_get_path(sub));
535 
536     if (node != NULL) {
537         if (!gam_node_is_dir(node)) {
538             GAM_DEBUG(DEBUG_INFO, "Removing node sub: %s\n",
539                       gam_subscription_get_path(sub));
540             node_remove_subscription(node, sub);
541 
542             if (!gam_node_get_subscriptions(node)) {
543                 GamNode *parent;
544 
545                 gam_poll_generic_unregister_node (node);
546                 if (gam_tree_has_children(gam_poll_generic_get_tree(), node)) {
547                     fprintf(stderr,
548                             "node %s is not dir but has children\n",
549                             gam_node_get_path(node));
550                 } else {
551                     parent = gam_node_parent(node);
552                     if ((parent != NULL) &&
553                         (!gam_node_has_dir_subscriptions(parent))) {
554                         gam_tree_remove(gam_poll_generic_get_tree(), node);
555 
556                         gam_poll_generic_prune_tree(parent);
557                     }
558                 }
559             }
560         } else {
561             GAM_DEBUG(DEBUG_INFO, "Removing directory sub: %s\n",
562                       gam_subscription_get_path(sub));
563             if (node_remove_directory_subscription(node, sub)) {
564                 GamNode *parent;
565 
566                 gam_poll_generic_unregister_node (node);
567                 parent = gam_node_parent(node);
568                 if (!gam_tree_has_children(gam_poll_generic_get_tree(), node)) {
569                     gam_tree_remove(gam_poll_generic_get_tree(), node);
570                 }
571 
572                 gam_poll_generic_prune_tree(parent);
573             }
574         }
575     }
576 
577     gam_subscription_free(sub);
578 }
579 
580 /**
581  * Removes a subscription which was being polled.
582  *
583  * @param sub a #GamSubscription to remove
584  * @returns TRUE if removing the subscription succeeded, FALSE otherwise
585  */
586 static gboolean
gam_poll_dnotify_remove_subscription(GamSubscription * sub)587 gam_poll_dnotify_remove_subscription(GamSubscription * sub)
588 {
589     GamNode *node;
590 
591     node = gam_tree_get_at_path(gam_poll_generic_get_tree(), gam_subscription_get_path(sub));
592     if (node == NULL) {
593         /* free directly */
594         gam_subscription_free(sub);
595         return TRUE;
596     }
597 
598     gam_subscription_cancel(sub);
599 
600     GAM_DEBUG(DEBUG_INFO, "Tree has %d nodes\n", gam_tree_get_size(gam_poll_generic_get_tree()));
601     gam_poll_dnotify_remove_subscription_real(sub);
602     GAM_DEBUG(DEBUG_INFO, "Tree has %d nodes\n", gam_tree_get_size(gam_poll_generic_get_tree()));
603 
604     GAM_DEBUG(DEBUG_INFO, "Poll: removed subscription\n");
605     return TRUE;
606 }
607 
608 /**
609  * Stop polling all subscriptions for a given #GamListener.
610  *
611  * @param listener a #GamListener
612  * @returns TRUE if removing the subscriptions succeeded, FALSE otherwise
613  */
614 static gboolean
gam_poll_dnotify_remove_all_for(GamListener * listener)615 gam_poll_dnotify_remove_all_for(GamListener * listener)
616 {
617     GList *subs, *l = NULL;
618 
619     subs = gam_listener_get_subscriptions(listener);
620 
621     for (l = subs; l; l = l->next) {
622         GamSubscription *sub = l->data;
623 
624         g_assert(sub != NULL);
625 
626         gam_poll_remove_subscription(sub);
627     }
628 
629     if (subs) {
630         g_list_free(subs);
631         return TRUE;
632     } else
633         return FALSE;
634 }
635 
636 static gboolean
gam_poll_dnotify_scan_callback(gpointer data)637 gam_poll_dnotify_scan_callback(gpointer data)
638 {
639     int idx;
640 
641 #ifdef VERBOSE_POLL
642 	GAM_DEBUG(DEBUG_INFO, "gam_poll_scan_callback(): %d missing, %d busy\n", g_list_length(gam_poll_generic_get_missing_list()), g_list_length(gam_poll_generic_get_busy_list()));
643 #endif
644 
645 	gam_poll_generic_update_time ();
646 
647 
648 	/*
649 	 * do not simply walk the list as it may be modified in the callback
650 	 */
651 	for (idx = 0;; idx++)
652 	{
653 		GamNode *node = g_list_nth_data(gam_poll_generic_get_missing_list(), idx);
654 
655 		if (node == NULL)
656 			break;
657 
658 		g_assert (node);
659 
660 #ifdef VERBOSE_POLL
661 		GAM_DEBUG(DEBUG_INFO, "Checking missing file %s", node->path);
662 #endif
663 		if (node->is_dir) {
664 			gam_poll_generic_scan_directory_internal(node);
665 		} else {
666 			GaminEventType event = gam_poll_dnotify_poll_file (node);
667 			gam_node_emit_event(node, event);
668 		}
669 
670 		/*
671 		* if the resource exists again and is not in a special monitoring
672 		* mode then switch back to dnotify for monitoring.
673 		*/
674 		if (!gam_node_has_pflags (node, MON_ALL_PFLAGS) &&
675 		    !gam_exclude_check(node->path) &&
676 		    gam_fs_get_mon_type (node->path) == GFS_MT_KERNEL)
677 		{
678 			gam_poll_generic_remove_missing(node);
679 			if (gam_node_get_subscriptions(node) != NULL) {
680 				gam_poll_dnotify_relist_node(node);
681 			}
682 		}
683 	}
684 
685 	for (idx = 0;; idx++)
686 	{
687 		GamNode *node = (GamNode *) g_list_nth_data(gam_poll_generic_get_busy_list(), idx);
688 		/*
689 		 * do not simply walk the list as it may be modified in the callback
690 		 */
691 		if (node == NULL)
692 			break;
693 
694 		g_assert (node);
695 #ifdef VERBOSE_POLL
696 		GAM_DEBUG(DEBUG_INFO, "Checking busy file %s", node->path);
697 #endif
698 		if (node->is_dir) {
699 			gam_poll_generic_scan_directory_internal(node);
700 		} else {
701 			GaminEventType event = gam_poll_dnotify_poll_file (node);
702 			gam_node_emit_event(node, event);
703 		}
704 
705 		/*
706 		* if the resource exists again and is not in a special monitoring
707 		* mode then switch back to dnotify for monitoring.
708 		*/
709 		if (!gam_node_has_pflags (node, MON_ALL_PFLAGS) &&
710 		    !gam_exclude_check(node->path) &&
711 		    gam_fs_get_mon_type (node->path) == GFS_MT_KERNEL)
712 		{
713 			gam_poll_generic_remove_busy(node);
714 			if (gam_node_get_subscriptions(node) != NULL) {
715 				gam_poll_dnotify_flowoff_node(node);
716 			}
717 		}
718 	}
719 	return TRUE;
720 }
721