1 /* Gamin
2  * Copyright (C) 2003 James Willcox, Corey Bowers
3  * Copyright (C) 2004 Daniel Veillard, Red Hat, Inc.
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 
21 #include "server_config.h"
22 #include <unistd.h>
23 #include <signal.h>
24 #include <string.h>
25 #include <stdlib.h>
26 #include <glib.h>
27 #include <sys/stat.h>
28 #include "gam_error.h"
29 #include "gam_protocol.h"
30 #include "gam_event.h"
31 #include "gam_listener.h"
32 #include "gam_server.h"
33 #include "gam_channel.h"
34 #include "gam_subscription.h"
35 #include "gam_poll_basic.h"
36 #ifdef ENABLE_INOTIFY
37 #include "gam_inotify.h"
38 #endif
39 #ifdef ENABLE_DNOTIFY
40 #include "gam_dnotify.h"
41 #endif
42 #ifdef ENABLE_KQUEUE
43 #include "gam_kqueue.h"
44 #endif
45 #ifdef ENABLE_HURD_MACH_NOTIFY
46 #include "gam_hurd_mach_notify.h"
47 #endif
48 #include "gam_excludes.h"
49 #include "gam_fs.h"
50 #include "gam_conf.h"
51 
52 static int poll_only = 0;
53 static const char *session;
54 
55 static GamKernelHandler __gam_kernel_handler = GAMIN_K_NONE;
56 static gboolean (*__gam_kernel_add_subscription) (GamSubscription *sub) = NULL;
57 static gboolean (*__gam_kernel_remove_subscription) (GamSubscription *sub) = NULL;
58 static gboolean (*__gam_kernel_remove_all_for) (GamListener *listener) = NULL;
59 static void (*__gam_kernel_dir_handler) (const char *path, pollHandlerMode mode) = NULL;
60 static void (*__gam_kernel_file_handler) (const char *path, pollHandlerMode mode) = NULL;
61 
62 static GamPollHandler __gam_poll_handler = GAMIN_P_NONE;
63 static gboolean (*__gam_poll_add_subscription) (GamSubscription *sub) = NULL;
64 static gboolean (*__gam_poll_remove_subscription) (GamSubscription *sub) = NULL;
65 static gboolean (*__gam_poll_remove_all_for) (GamListener *listener) = NULL;
66 static GaminEventType (*__gam_poll_file) (GamNode *node) = NULL;
67 
68 #ifndef ENABLE_INOTIFY
69 /**
70  * gam_inotify_is_running
71  *
72  * Unless built with inotify support, always
73  * return false.
74  */
75 gboolean
gam_inotify_is_running(void)76 gam_inotify_is_running(void)
77 {
78 	return FALSE;
79 }
80 #endif
81 
82 
83 /**
84  * gam_exit:
85  *
86  * Call the shutdown routine, then just exit.
87  * This function is designed to be called from a
88  * signal handler.
89  */
90 static void
gam_exit(int signo)91 gam_exit(int signo) {
92 	gam_shutdown();
93 	exit(0);
94 }
95 
96 /**
97  * gam_shutdown:
98  *
99  * Shutdown routine called when the server exits
100  */
101 void
gam_shutdown(void)102 gam_shutdown(void) {
103     gam_conn_shutdown(session);
104 }
105 
106 /**
107  * gam_debug:
108  *
109  * Debug routine called when the debugging starts
110  */
111 void
gam_show_debug(void)112 gam_show_debug(void) {
113 	gam_exclude_debug ();
114     gam_fs_debug ();
115     gam_connections_debug();
116 #ifdef ENABLE_INOTIFY
117     gam_inotify_debug ();
118 #endif
119 #ifdef ENABLE_DNOTIFY
120     gam_dnotify_debug ();
121 #endif
122     gam_poll_generic_debug();
123 }
124 
125 /**
126  * gam_init_subscriptions:
127  *
128  * Initialize the subscription checking backend, on Linux we will use
129  * the DNotify kernel support, otherwise the polling module.
130  *
131  * Return TRUE in case of success and FALSE otherwise
132  */
133 gboolean
gam_init_subscriptions(void)134 gam_init_subscriptions(void)
135 {
136 	gam_conf_read ();
137 	gam_exclude_init();
138 
139 	if (!poll_only) {
140 #ifdef ENABLE_INOTIFY
141 		if (!getenv("GAM_TEST_DNOTIFY") && gam_inotify_init()) {
142 			GAM_DEBUG(DEBUG_INFO, "Using inotify as backend\n");
143 			return(TRUE);
144 		}
145 #endif
146 #ifdef ENABLE_DNOTIFY
147 		if (gam_dnotify_init()) {
148 			GAM_DEBUG(DEBUG_INFO, "Using dnotify as backend\n");
149 			return(TRUE);
150 		}
151 #endif
152 #ifdef ENABLE_KQUEUE
153 		if (gam_kqueue_init()) {
154 			GAM_DEBUG(DEBUG_INFO, "Using kqueue as backend\n");
155 			return(TRUE);
156 		}
157 #endif
158 #ifdef ENABLE_HURD_MACH_NOTIFY
159 		if (gam_hurd_notify_init())
160 		{
161 			GAM_DEBUG(DEBUG_INFO, "Using hurd notify as backend\n");
162 			return(TRUE);
163 		}
164 #endif
165 	}
166 
167 	if (gam_poll_basic_init()) {
168 		GAM_DEBUG(DEBUG_INFO, "Using poll as backend\n");
169 		return(TRUE);
170 	}
171 
172 	GAM_DEBUG(DEBUG_INFO, "Cannot initialize any backend\n");
173 
174 	return(FALSE);
175 }
176 
177 /**
178  * gam_add_subscription:
179  *
180  * Register a subscription to the checking backend, on Linux we will use
181  * the DNotify kernel support, otherwise the polling module.
182  *
183  * Return TRUE in case of success and FALSE otherwise
184  */
185 gboolean
gam_add_subscription(GamSubscription * sub)186 gam_add_subscription(GamSubscription * sub)
187 {
188 	const char *path = NULL;
189 
190 	if (sub == NULL)
191 		return(FALSE);
192 
193 	path = gam_subscription_get_path (sub);
194 
195 	if (gam_exclude_check (path))
196 	{
197 		GAM_DEBUG(DEBUG_INFO, "g_a_s: %s excluded\n", path);
198 #if ENABLE_INOTIFY
199 		if (gam_inotify_is_running())
200 			return gam_poll_add_subscription (sub);
201 		else
202 #endif
203 			/*return gam_kernel_add_subscription (sub);*/
204 			return gam_poll_add_subscription (sub);
205 	} else {
206 		gam_fs_mon_type type;
207 		type = gam_fs_get_mon_type (path);
208 		if (type == GFS_MT_KERNEL)
209 		{
210 			GAM_DEBUG(DEBUG_INFO, "g_a_s: %s using kernel monitoring\n", path);
211 			return gam_kernel_add_subscription(sub);
212 		}
213 		else if (type == GFS_MT_POLL)
214 		{
215 			GAM_DEBUG(DEBUG_INFO, "g_a_s: %s using poll monitoring\n", path);
216 			return gam_poll_add_subscription (sub);
217 		}
218 	}
219 
220 	return FALSE;
221 }
222 
223 /**
224  * gam_remove_subscription:
225  *
226  * Remove a subscription from the checking backend.
227  *
228  * Return TRUE in case of success and FALSE otherwise
229  */
230 gboolean
gam_remove_subscription(GamSubscription * sub)231 gam_remove_subscription(GamSubscription * sub)
232 {
233 	const char *path = NULL;
234 
235 	if (sub == NULL)
236 		return(FALSE);
237 
238 	path = gam_subscription_get_path (sub);
239 
240 	if (gam_exclude_check (path))
241 	{
242 #if ENABLE_INOTIFY
243 		if (gam_inotify_is_running())
244 			return gam_poll_remove_subscription (sub);
245 		else
246 #endif
247 			/*return gam_kernel_remove_subscription(sub);*/
248 			return gam_poll_remove_subscription (sub);
249 	} else {
250 		gam_fs_mon_type type;
251 		type = gam_fs_get_mon_type (path);
252 		if (type == GFS_MT_KERNEL)
253 			return gam_kernel_remove_subscription(sub);
254 		else if (type == GFS_MT_POLL)
255 			return gam_poll_remove_subscription (sub);
256 	}
257 
258 	return FALSE;
259 }
260 
261 /**
262  * @defgroup Daemon Daemon
263  *
264  */
265 
266 /**
267  * @defgroup Backends Backends
268  * @ingroup Daemon
269  *
270  * One of the goals for Gamin is providing a uniform and consistent
271  * monitoring solution, which works even across different platforms.  Different
272  * platforms have different kernel-level monitoring systems available (or
273  * none at all).  A "backend" simply takes advantage of the services available
274  * on a given platform and makes them work with the rest of Gamin.
275  *
276  *
277  */
278 
279 static int no_timeout = 0;
280 static GHashTable *listeners = NULL;
281 static GIOChannel *socket = NULL;
282 
283 /**
284  * gam_server_use_timeout:
285  *
286  * Returns TRUE if idle server should exit after a timeout.
287  */
288 gboolean
gam_server_use_timeout(void)289 gam_server_use_timeout (void)
290 {
291   return !no_timeout;
292 }
293 
294 /**
295  * gam_server_emit_one_event:
296  * @path: the file/directory path
297  * @event: the event type
298  * @sub: the subscription for this event
299  * @force: try to force the event though as much as possible
300  *
301  * Checks which subscriptions are interested in this event and
302  * make sure the event are sent to the associated clients.
303  */
304 void
gam_server_emit_one_event(const char * path,int node_is_dir,GaminEventType event,GamSubscription * sub,int force)305 gam_server_emit_one_event(const char *path, int node_is_dir,
306                           GaminEventType event, GamSubscription *sub,
307 			  int force)
308 {
309     int pathlen, len;
310     const char *subpath;
311     GamListener *listener;
312     GamConnDataPtr conn;
313     int reqno;
314 
315 
316     pathlen = strlen(path);
317 
318     if (!gam_subscription_wants_event(sub, path, node_is_dir, event, force))
319 	return;
320 
321     listener = gam_subscription_get_listener(sub);
322     if (listener == NULL)
323 	return;
324     conn = (GamConnDataPtr) gam_listener_get_service(listener);
325     if (conn == NULL)
326 	return;
327 
328     /*
329      * When sending directory related entries, for items in the
330      * directory the FAM protocol removes the common direcory part.
331      */
332     subpath = path;
333     len = pathlen;
334     if (gam_subscription_is_dir(sub)) {
335 	int dlen = gam_subscription_pathlen(sub);
336 
337 	if ((pathlen > dlen + 1) && (path[dlen] == '/')) {
338 	    subpath += dlen + 1;
339 	    len -= dlen + 1;
340 	}
341     }
342 
343     reqno = gam_subscription_get_reqno(sub);
344 
345 #ifdef ENABLE_INOTIFY
346 	if (gam_inotify_is_running())
347 	{
348 		gam_queue_event(conn, reqno, event, subpath, len);
349 	} else
350 #endif
351 	{
352 		if (gam_send_event(conn, reqno, event, subpath, len) < 0) {
353 		    GAM_DEBUG(DEBUG_INFO, "Failed to send event to PID %d\n",
354 			  gam_connection_get_pid(conn));
355 		}
356 	}
357 }
358 
359 /**
360  * gam_server_emit_event:
361  * @path: the file/directory path
362  * @is_dir_node: is the target a directory
363  * @event: the event type
364  * @subs: the list of subscription for this event
365  * @force: force the emission of the events
366  *
367  * Checks which subscriptions are interested in this event and
368  * make sure the event are sent to the associated clients.
369  */
370 void
gam_server_emit_event(const char * path,int is_dir_node,GaminEventType event,GList * subs,int force)371 gam_server_emit_event(const char *path, int is_dir_node, GaminEventType event,
372                       GList * subs, int force)
373 {
374     GList *l;
375     int pathlen;
376 
377     if ((path == NULL) || (subs == NULL))
378         return;
379     pathlen = strlen(path);
380 
381     for (l = subs; l; l = l->next) {
382         GamSubscription *sub = l->data;
383 	gam_server_emit_one_event (path, is_dir_node, event, sub, force);
384     }
385 }
386 
387 int
gam_server_num_listeners(void)388 gam_server_num_listeners(void)
389 {
390     return g_hash_table_size(listeners);
391 }
392 
393 static GamKernelHandler __gam_kernel_handler;
394 static gboolean (*__gam_kernel_add_subscription) (GamSubscription *sub);
395 static gboolean (*__gam_kernel_remove_subscription) (GamSubscription *sub);
396 static gboolean (*__gam_kernel_remove_all_for) (GamListener *listener);
397 
398 static GamPollHandler __gam_poll_handler;
399 static gboolean (*__gam_poll_add_subscription) (GamSubscription *sub);
400 static gboolean (*__gam_poll_remove_subscription) (GamSubscription *sub);
401 static gboolean (*__gam_poll_remove_all_for) (GamListener *listener);
402 
403 
404 void
gam_server_install_kernel_hooks(GamKernelHandler name,gboolean (* add)(GamSubscription * sub),gboolean (* remove)(GamSubscription * sub),gboolean (* remove_all)(GamListener * listener),void (* dir_handler)(const char * path,pollHandlerMode mode),void (* file_handler)(const char * path,pollHandlerMode mode))405 gam_server_install_kernel_hooks (GamKernelHandler name,
406 				 gboolean (*add)(GamSubscription *sub),
407 				 gboolean (*remove)(GamSubscription *sub),
408 				 gboolean (*remove_all)(GamListener *listener),
409 				 void (*dir_handler)(const char *path, pollHandlerMode mode),
410 				 void (*file_handler)(const char *path, pollHandlerMode mode))
411 {
412 	__gam_kernel_handler = name;
413 	__gam_kernel_add_subscription = add;
414 	__gam_kernel_remove_subscription = remove;
415 	__gam_kernel_remove_all_for = remove_all;
416 	__gam_kernel_dir_handler = dir_handler;
417 	__gam_kernel_file_handler = file_handler;
418 }
419 
420 void
gam_server_install_poll_hooks(GamPollHandler name,gboolean (* add)(GamSubscription * sub),gboolean (* remove)(GamSubscription * sub),gboolean (* remove_all)(GamListener * listener),GaminEventType (* poll_file)(GamNode * node))421 gam_server_install_poll_hooks (GamPollHandler name,
422 				gboolean (*add)(GamSubscription *sub),
423 				gboolean (*remove)(GamSubscription *sub),
424 				gboolean (*remove_all)(GamListener *listener),
425 				GaminEventType (*poll_file)(GamNode *node))
426 {
427 	__gam_poll_handler = name;
428 	__gam_poll_add_subscription = add;
429 	__gam_poll_remove_subscription = remove;
430 	__gam_poll_remove_all_for = remove_all;
431 	__gam_poll_file = poll_file;
432 }
433 
434 GamKernelHandler
gam_server_get_kernel_handler(void)435 gam_server_get_kernel_handler (void)
436 {
437 	return __gam_kernel_handler;
438 }
439 
440 GamPollHandler
gam_server_get_poll_handler(void)441 gam_server_get_poll_handler (void)
442 {
443 	return __gam_poll_handler;
444 }
445 
446 gboolean
gam_kernel_add_subscription(GamSubscription * sub)447 gam_kernel_add_subscription (GamSubscription *sub)
448 {
449 	if (__gam_kernel_add_subscription)
450 		return __gam_kernel_add_subscription (sub);
451 
452 	return FALSE;
453 }
454 
455 gboolean
gam_kernel_remove_subscription(GamSubscription * sub)456 gam_kernel_remove_subscription (GamSubscription *sub)
457 {
458 	if (__gam_kernel_remove_subscription)
459 		return __gam_kernel_remove_subscription (sub);
460 
461 	return FALSE;
462 }
463 
464 gboolean
gam_kernel_remove_all_for(GamListener * listener)465 gam_kernel_remove_all_for (GamListener *listener)
466 {
467 	if (__gam_kernel_remove_all_for)
468 		return __gam_kernel_remove_all_for (listener);
469 
470 	return FALSE;
471 }
472 
473 void
gam_kernel_dir_handler(const char * path,pollHandlerMode mode)474 gam_kernel_dir_handler(const char *path, pollHandlerMode mode)
475 {
476 	if (__gam_kernel_dir_handler)
477 		__gam_kernel_dir_handler (path, mode);
478 }
479 
480 void
gam_kernel_file_handler(const char * path,pollHandlerMode mode)481 gam_kernel_file_handler(const char *path, pollHandlerMode mode)
482 {
483 	if (__gam_kernel_file_handler)
484 		__gam_kernel_file_handler (path, mode);
485 }
486 
487 gboolean
gam_poll_add_subscription(GamSubscription * sub)488 gam_poll_add_subscription (GamSubscription *sub)
489 {
490 	if (__gam_poll_add_subscription)
491 		return __gam_poll_add_subscription (sub);
492 
493 	return FALSE;
494 }
495 
496 gboolean
gam_poll_remove_subscription(GamSubscription * sub)497 gam_poll_remove_subscription (GamSubscription *sub)
498 {
499 	if (__gam_poll_remove_subscription)
500 		return __gam_poll_remove_subscription (sub);
501 
502 	return FALSE;
503 }
504 
505 gboolean
gam_poll_remove_all_for(GamListener * listener)506 gam_poll_remove_all_for (GamListener *listener)
507 {
508 	if (__gam_poll_remove_all_for)
509 		return __gam_poll_remove_all_for (listener);
510 
511 	return FALSE;
512 }
513 
514 GaminEventType
gam_poll_file(GamNode * node)515 gam_poll_file (GamNode *node)
516 {
517 	if (__gam_poll_file)
518 		return __gam_poll_file (node);
519 
520 	return 0;
521 }
522 
523 #ifdef GAM_DEBUG_ENABLED
524 
525 static GIOChannel *pipe_read_ioc = NULL;
526 static GIOChannel *pipe_write_ioc = NULL;
527 
528 static gboolean
gam_error_signal_pipe_handler(gpointer user_data)529 gam_error_signal_pipe_handler(gpointer user_data)
530 {
531   char buf[5000];
532 
533   if (pipe_read_ioc)
534     g_io_channel_read_chars(pipe_read_ioc, buf, sizeof(buf), NULL, NULL);
535 
536   gam_error_check();
537 }
538 
539 static void
gam_setup_error_handler(void)540 gam_setup_error_handler (void)
541 {
542   int signal_pipe[2];
543   GSource *source;
544 
545   if (pipe(signal_pipe) != -1) {
546     pipe_read_ioc = g_io_channel_unix_new(signal_pipe[0]);
547     pipe_write_ioc = g_io_channel_unix_new(signal_pipe[1]);
548 
549     g_io_channel_set_flags(pipe_read_ioc, G_IO_FLAG_NONBLOCK, NULL);
550     g_io_channel_set_flags(pipe_write_ioc, G_IO_FLAG_NONBLOCK, NULL);
551 
552     source = g_io_create_watch(pipe_read_ioc, G_IO_IN | G_IO_HUP | G_IO_ERR);
553     g_source_set_callback(source, gam_error_signal_pipe_handler, NULL, NULL);
554 
555     g_source_attach(source, NULL);
556     g_source_unref(source);
557   }
558 }
559 #endif
560 
561 void
gam_got_signal()562 gam_got_signal()
563 {
564 #ifdef GAM_DEBUG_ENABLED
565   /* Wake up main loop */
566   if (pipe_write_ioc) {
567     g_io_channel_write_chars(pipe_write_ioc, "a", 1, NULL, NULL);
568     g_io_channel_flush(pipe_write_ioc, NULL);
569   }
570 #endif
571 }
572 
573 
574 
575 /**
576  * gam_server_init:
577  * @loop:  the main event loop of the daemon
578  * @session: the session name or NULL
579  *
580  * Initialize the gamin server
581  *
582  * Returns TRUE in case of success and FALSE in case of error
583  */
584 static gboolean
gam_server_init(GMainLoop * loop,const char * session)585 gam_server_init(GMainLoop * loop, const char *session)
586 {
587     if (socket != NULL) {
588         return (FALSE);
589     }
590     socket = gam_server_create(session);
591     if (socket == NULL)
592         return (FALSE);
593     g_io_add_watch(socket, G_IO_IN, gam_incoming_conn_read, loop);
594     g_io_add_watch(socket, G_IO_HUP | G_IO_NVAL | G_IO_ERR, gam_conn_error,
595                    NULL);
596 
597     /*
598      * Register the timeout checking function
599      */
600     if (no_timeout == 0)
601       gam_schedule_server_timeout ();
602 #ifdef GAM_DEBUG_ENABLED
603     gam_setup_error_handler ();
604 #endif
605 
606     return TRUE;
607 }
608 
609 int
main(int argc,const char * argv[])610 main(int argc, const char *argv[])
611 {
612     GMainLoop *loop;
613     int i;
614 
615     if (argc > 1) {
616         for (i = 1;i < argc;i++) {
617 	    if (!strcmp(argv[i], "--notimeout"))
618 		no_timeout = 1;
619             else if (!strcmp(argv[i], "--pollonly"))
620 	        poll_only = 1;
621 	    else
622 		session = argv[i];
623 	}
624     }
625 
626     gam_error_init();
627     signal(SIGHUP, gam_exit);
628     signal(SIGINT, gam_exit);
629     signal(SIGQUIT, gam_exit);
630     signal(SIGTERM, gam_exit);
631     signal(SIGPIPE, SIG_IGN);
632 
633     if (!gam_init_subscriptions()) {
634 	GAM_DEBUG(DEBUG_INFO, "Could not initialize the subscription system.\n");
635         exit(0);
636     }
637 
638     loop = g_main_loop_new(NULL, FALSE);
639     if (loop == NULL) {
640         g_error("Failed to create the main loop.\n");
641         exit(1);
642     }
643 
644     if (!gam_server_init(loop, session)) {
645         GAM_DEBUG(DEBUG_INFO, "Couldn't initialize the server.\n");
646         exit(0);
647     }
648 
649     g_main_loop_run(loop);
650 
651     gam_shutdown();
652 
653     return (0);
654 }
655