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