1 #include "server_config.h"
2 #include <string.h> /* for memmove */
3 #include <stdlib.h> /* for exit() */
4 #include <time.h>
5 #include "gam_connection.h"
6 #include "gam_subscription.h"
7 #include "gam_listener.h"
8 #include "gam_server.h"
9 #include "gam_event.h"
10 #include "gam_protocol.h"
11 #include "gam_channel.h"
12 #include "gam_error.h"
13 #include "gam_pidname.h"
14 #include "gam_eq.h"
15 #ifdef GAMIN_DEBUG_API
16 #include "gam_debugging.h"
17 #endif
18 #ifdef ENABLE_INOTIFY
19 #include "gam_inotify.h"
20 #endif
21 #include "fam.h"
22
23 /************************************************************************
24 * *
25 * Connection data handling *
26 * *
27 ************************************************************************/
28
29 static GList *gamConnList;
30
31 struct GamConnData {
32 GamConnState state; /* the state for the connection */
33 int fd; /* the file descriptor */
34 int pid; /* the PID of the remote process */
35 gchar *pidname; /* The name of the process */
36 GMainLoop *loop; /* the Glib loop used */
37 GIOChannel *source; /* the Glib I/O Channel used */
38 int request_len; /* how many bytes of request are valid */
39 GAMPacket request; /* the next request being read */
40 GamListener *listener; /* the listener associated with the connection */
41 gam_eq_t *eq; /* the event queue */
42 guint eq_source; /* the event queue GSource id */
43 };
44
45 static void gam_cancel_server_timeout (void);
46
47
48 static const char *
gam_reqtype_to_string(GAMReqType type)49 gam_reqtype_to_string (GAMReqType type)
50 {
51 switch (type)
52 {
53 case GAM_REQ_FILE:
54 return "MONFILE";
55 case GAM_REQ_DIR:
56 return "MONDIR";
57 case GAM_REQ_CANCEL:
58 return "CANCEL";
59 case GAM_REQ_DEBUG:
60 return "4";
61 }
62
63 return "";
64 }
65
66 /**
67 * gam_connections_init:
68 *
69 * Initialize the connections data layer
70 *
71 * Returns 0 on success; -1 on failure
72 */
73 int
gam_connections_init(void)74 gam_connections_init(void)
75 {
76 return (0);
77 }
78
79 /**
80 * gam_connection_exists:
81 * @conn: the connection
82 *
83 * Routine to chech whether a connection still exists
84 *
85 * Returns 1 if still registered, 0 otherwise
86 */
87 int
gam_connection_exists(GamConnDataPtr conn)88 gam_connection_exists(GamConnDataPtr conn)
89 {
90 g_assert(conn);
91 return g_list_find(gamConnList, (gconstpointer) conn) != NULL;
92 }
93
94 /**
95 * gam_connection_close:
96 * @conn: the connection
97 *
98 * Routine to close a connection and discard the associated data
99 *
100 * Returns 0 on success; -1 on error
101 */
102 int
gam_connection_close(GamConnDataPtr conn)103 gam_connection_close(GamConnDataPtr conn)
104 {
105 g_assert (conn);
106 /* A valid connection is on gamConnList. */
107 g_assert(g_list_find(gamConnList, (gconstpointer) conn));
108 g_assert(conn->source);
109
110 /* Kill the queue event source */
111 if (conn->eq_source != 0)
112 g_source_remove (conn->eq_source);
113 /* Flush the event queue */
114 gam_eq_flush (conn->eq, conn);
115 /* Kill the event queue */
116 gam_eq_free (conn->eq);
117
118 if (conn->listener != NULL) {
119 gam_listener_free(conn->listener);
120 }
121
122 #ifdef GAMIN_DEBUG_API
123 gam_debug_release(conn);
124 #endif
125 GAM_DEBUG(DEBUG_INFO, "Closing connection %d\n", conn->fd);
126
127 g_io_channel_unref(conn->source);
128 gamConnList = g_list_remove(gamConnList, conn);
129 g_assert (!g_list_find(gamConnList, conn));
130 g_free(conn->pidname);
131 g_free(conn);
132
133 if (gamConnList == NULL && gam_server_use_timeout ())
134 gam_schedule_server_timeout ();
135
136 return (0);
137 }
138
139 /**
140 * gam_connections_close:
141 *
142 * Close all the registered connections
143 *
144 * Returns 0 on success; -1 if at least one connection failed to close
145 */
146 int
gam_connections_close(void)147 gam_connections_close(void)
148 {
149 int ret = 0;
150 GList *cur;
151
152 while ((cur = g_list_first(gamConnList)) != NULL) {
153 if (gam_connection_close((GamConnDataPtr) cur->data) < 0)
154 ret = -1;
155 }
156 return (ret);
157 }
158
159 /**
160 * gam_connection_eq_flush:
161 *
162 * Flushes the connections event queue
163 *
164 * returns TRUE
165 */
166 static gboolean
gam_connection_eq_flush(gpointer data)167 gam_connection_eq_flush (gpointer data)
168 {
169 gboolean work;
170 GamConnDataPtr conn = (GamConnDataPtr)data;
171 if (!conn)
172 return FALSE;
173
174 work = gam_eq_flush (conn->eq, conn);
175 if (!work)
176 conn->eq_source = 0;
177 return work;
178 }
179
180 /**
181 * gam_connection_new:
182 * @loop: the Glib loop
183 * @source: the Glib I/O Channel
184 *
185 * Create a new connection data structure.
186 *
187 * Returns a new connection structure on success; NULL on error.
188 */
189 GamConnDataPtr
gam_connection_new(GMainLoop * loop,GIOChannel * source)190 gam_connection_new(GMainLoop *loop, GIOChannel *source)
191 {
192 GamConnDataPtr ret;
193
194 g_assert(loop);
195 g_assert(source);
196
197 ret = g_malloc0(sizeof(GamConnData));
198 if (ret == NULL)
199 return (NULL);
200
201 ret->state = GAM_STATE_AUTH;
202 ret->fd = g_io_channel_unix_get_fd(source);
203 ret->loop = loop;
204 ret->source = source;
205 ret->eq = gam_eq_new ();
206 ret->eq_source = g_timeout_add (100 /* 100 milisecond */, gam_connection_eq_flush, ret);
207 gamConnList = g_list_prepend(gamConnList, ret);
208
209 gam_cancel_server_timeout ();
210
211 GAM_DEBUG(DEBUG_INFO, "Created connection %d\n", ret->fd);
212
213 return (ret);
214 }
215
216 /**
217 * gam_connection_get_fd:
218 * @conn: a connection data structure.
219 *
220 * Get the file descriptor associated with a connection
221 *
222 * Returns the file descriptor or -1 in case of error.
223 */
224 int
gam_connection_get_fd(GamConnDataPtr conn)225 gam_connection_get_fd(GamConnDataPtr conn)
226 {
227 g_assert(conn);
228 return (conn->fd);
229 }
230
231 /**
232 * gam_connection_get_pid:
233 * @conn: a connection data structure.
234 *
235 * accessor for the pid associated to the connection
236 *
237 * Returns the process identifier or -1 in case of error.
238 */
239 int
gam_connection_get_pid(GamConnDataPtr conn)240 gam_connection_get_pid(GamConnDataPtr conn)
241 {
242 g_assert(conn);
243 return (conn->pid);
244 }
245
246 gchar *
gam_connection_get_pidname(GamConnDataPtr conn)247 gam_connection_get_pidname(GamConnDataPtr conn)
248 {
249 g_assert (conn);
250 return conn->pidname;
251 }
252
253 /**
254 * gam_connection_set_pid:
255 * @conn: a connection data structure.
256 * @pid: the client process id
257 *
258 * Set the client process id, this also indicate that authentication was done.
259 *
260 * Returns 0 in case of success or -1 in case of error.
261 */
262 int
gam_connection_set_pid(GamConnDataPtr conn,int pid)263 gam_connection_set_pid(GamConnDataPtr conn, int pid)
264 {
265 g_assert(conn);
266
267 if (conn->state != GAM_STATE_AUTH) {
268 GAM_DEBUG(DEBUG_INFO, "Connection in unexpected state: "
269 "not waiting for authentication\n");
270 conn->state = GAM_STATE_ERROR;
271 return (-1);
272 }
273
274 conn->state = GAM_STATE_OKAY;
275 conn->pid = pid;
276 conn->pidname = gam_get_pidname (pid);
277 conn->listener = gam_listener_new(conn, pid);
278 if (conn->listener == NULL) {
279 GAM_DEBUG(DEBUG_INFO, "Failed to create listener\n");
280 conn->state = GAM_STATE_ERROR;
281 return (-1);
282 }
283 return (0);
284 }
285
286 /**
287 * gam_connection_get_state:
288 * @conn: a connection
289 *
290 * Accessor for the connection state
291 *
292 * Returns the connection's connection state
293 */
294 GamConnState
gam_connection_get_state(GamConnDataPtr conn)295 gam_connection_get_state(GamConnDataPtr conn)
296 {
297 g_assert(conn);
298 return (conn->state);
299 }
300
301 /**
302 * gam_connection_get_data:
303 * @conn: a connection
304 * @data: address to store pointer to data
305 * @size: amount of data available
306 *
307 * Get the address and length of the data store for the connection
308 *
309 * Returns 0 on success; -1 on failure
310 */
311 int
gam_connection_get_data(GamConnDataPtr conn,char ** data,int * size)312 gam_connection_get_data(GamConnDataPtr conn, char **data, int *size)
313 {
314 g_assert(conn);
315 g_assert(data);
316 g_assert(size);
317
318 *data = (char *) &conn->request + conn->request_len;
319 *size = sizeof(GAMPacket) - conn->request_len;
320
321 return (0);
322 }
323
324 /**
325 * gam_connection_request:
326 *
327 * @conn: connection data structure.
328 * @req: the request
329 *
330 * Process a complete request.
331 *
332 * Returns 0 on success; -1 on error
333 */
334 static int
gam_connection_request(GamConnDataPtr conn,GAMPacketPtr req)335 gam_connection_request(GamConnDataPtr conn, GAMPacketPtr req)
336 {
337 GamSubscription *sub;
338 int events;
339 gboolean is_dir = TRUE;
340 char byte_save;
341 int type;
342 int options;
343
344 g_assert(conn);
345 g_assert(req);
346 g_assert(conn->state == GAM_STATE_OKAY);
347 g_assert(conn->fd >= 0);
348 g_assert(conn->listener);
349
350 type = req->type & 0xF;
351 options = req->type & 0xFFF0;
352 GAM_DEBUG(DEBUG_INFO, "%s request: from %s, seq %d, type %x options %x\n",
353 gam_reqtype_to_string (type), conn->pidname, req->seq, type, options);
354
355 if (req->pathlen >= MAXPATHLEN)
356 return (-1);
357
358 /*
359 * zero-terminate the string in the buffer, but keep the byte as
360 * it may be the first one of the next request.
361 */
362 byte_save = req->path[req->pathlen];
363 req->path[req->pathlen] = 0;
364
365 switch (type) {
366 case GAM_REQ_FILE:
367 case GAM_REQ_DIR:
368 events = GAMIN_EVENT_CHANGED | GAMIN_EVENT_CREATED |
369 GAMIN_EVENT_DELETED | GAMIN_EVENT_MOVED |
370 GAMIN_EVENT_EXISTS;
371
372 is_dir = (type == GAM_REQ_DIR);
373 sub = gam_subscription_new(req->path, events, req->seq,
374 is_dir, options);
375 gam_subscription_set_listener(sub, conn->listener);
376 gam_add_subscription(sub);
377 break;
378 case GAM_REQ_CANCEL: {
379 char *path;
380 int pathlen;
381
382 sub = gam_listener_get_subscription_by_reqno(conn->listener,
383 req->seq);
384 if (sub == NULL) {
385 GAM_DEBUG(DEBUG_INFO,
386 "Cancel: no subscription with reqno %d found\n",
387 req->seq);
388 goto error;
389 }
390
391 GAM_DEBUG(DEBUG_INFO, "Cancelling subscription with reqno %d\n",
392 req->seq);
393 /* We need to make a copy of sub's path as gam_send_ack
394 needs it but gam_listener_remove_subscription frees
395 it. */
396 path = g_strdup(gam_subscription_get_path(sub));
397 pathlen = gam_subscription_pathlen(sub);
398
399 gam_listener_remove_subscription(conn->listener, sub);
400 gam_remove_subscription(sub);
401 #ifdef ENABLE_INOTIFY
402 if ((gam_inotify_is_running()) && (!gam_exclude_check(path))) {
403 gam_fs_mon_type type;
404
405 type = gam_fs_get_mon_type (path);
406 if (type != GFS_MT_POLL)
407 gam_subscription_free(sub);
408 }
409 #endif
410
411 if (gam_send_ack(conn, req->seq, path, pathlen) < 0) {
412 GAM_DEBUG(DEBUG_INFO, "Failed to send cancel ack to PID %d\n",
413 gam_connection_get_pid(conn));
414 }
415 g_free(path);
416 break;
417 }
418 case GAM_REQ_DEBUG:
419 #ifdef GAMIN_DEBUG_API
420 gam_debug_add(conn, req->path, options);
421 #else
422 GAM_DEBUG(DEBUG_INFO, "Unhandled debug request for %s\n",
423 req->path);
424 #endif
425 break;
426 default:
427 GAM_DEBUG(DEBUG_INFO, "Unknown request type %d for %s\n",
428 type, req->path);
429 goto error;
430 }
431
432 req->path[req->pathlen] = byte_save;
433 return (0);
434 error:
435 req->path[req->pathlen] = byte_save;
436 return (-1);
437 }
438
439 /**
440 * gam_connection_data:
441 * @conn: the connection data structure
442 * @len: the amount of data added to the request buffer
443 *
444 * When receiving data, it should be read into an internal buffer
445 * retrieved using gam_connection_get_data. After receiving some
446 * incoming data, call this to process the data.
447 *
448 * Returns 0 in case of success, -1 in case of error
449 */
450 int
gam_connection_data(GamConnDataPtr conn,int len)451 gam_connection_data(GamConnDataPtr conn, int len)
452 {
453 GAMPacketPtr req;
454
455 g_assert(conn);
456 g_assert(len >= 0);
457 g_assert(conn->request_len >= 0);
458 g_assert(len + conn->request_len <= (int) sizeof(GAMPacket));
459
460 conn->request_len += len;
461 req = &conn->request;
462
463 /*
464 * loop processing all complete requests available in conn->request
465 */
466 while (1) {
467 if (conn->request_len < (int) GAM_PACKET_HEADER_LEN) {
468 /*
469 * we don't have enough data to check the current request
470 * keep it as a pending incomplete request and wait for more.
471 */
472 break;
473 }
474 /* check the packet total length */
475 if (req->len > sizeof(GAMPacket)) {
476 GAM_DEBUG(DEBUG_INFO, "malformed request: invalid length %d\n",
477 req->len);
478 return (-1);
479 }
480 /* check the version */
481 if (req->version != GAM_PROTO_VERSION) {
482 GAM_DEBUG(DEBUG_INFO, "unsupported version %d\n", req->version);
483 return (-1);
484 }
485 if (GAM_REQ_CANCEL != req->type) {
486 /* double check pathlen and total length */
487 if ((req->pathlen <= 0) || (req->pathlen > MAXPATHLEN)) {
488 GAM_DEBUG(DEBUG_INFO,
489 "malformed request: invalid path length %d\n",
490 req->pathlen);
491 return (-1);
492 }
493 }
494 if (req->pathlen + GAM_PACKET_HEADER_LEN != req->len) {
495 GAM_DEBUG(DEBUG_INFO,
496 "malformed request: invalid packet sizes: %d %d\n",
497 req->len, req->pathlen);
498 return (-1);
499 }
500 /* Check the type of the request: TODO !!! */
501
502 if (conn->request_len < req->len) {
503 /*
504 * the current request is incomplete, wait for the rest.
505 */
506 break;
507 }
508
509 if (gam_connection_request(conn, req) < 0) {
510 GAM_DEBUG(DEBUG_INFO, "gam_connection_request() failed\n");
511 return (-1);
512 }
513
514 /*
515 * process any remaining request piggy-back'ed on the same packet
516 */
517 conn->request_len -= req->len;
518 if (conn->request_len == 0)
519 break;
520
521 #if defined(__i386__) || defined(__x86_64__)
522 req = (void *) req + req->len;
523 #else
524 memmove(&conn->request, (void *)req + req->len, conn->request_len);
525 #endif
526 }
527
528 if ((conn->request_len > 0) && (req != &conn->request))
529 memmove(&conn->request, req, conn->request_len);
530
531 return (0);
532 }
533
534
535 /**
536 * gam_send_event:
537 * @conn: the connection
538 * @event: the event type
539 * @path: the path
540 *
541 * Send an event over a connection
542 *
543 * Returns 0 on success; -1 on failure
544 */
545 int
gam_send_event(GamConnDataPtr conn,int reqno,int event,const char * path,int len)546 gam_send_event(GamConnDataPtr conn, int reqno, int event,
547 const char *path, int len)
548 {
549 GAMPacket req;
550 size_t tlen;
551 int ret;
552 int type;
553
554 g_assert(conn);
555 g_assert(conn->fd >= 0);
556 g_assert(path);
557 g_assert(path[len] == '\0');
558
559 if (len >= MAXPATHLEN) {
560 GAM_DEBUG(DEBUG_INFO, "File path too long %s\n", path);
561 return (-1);
562 }
563
564 /*
565 * Convert between Gamin/Marmot internal values and FAM ones.
566 */
567 switch (event) {
568 case GAMIN_EVENT_CHANGED:
569 type = FAMChanged;
570 break;
571 case GAMIN_EVENT_CREATED:
572 type = FAMCreated;
573 break;
574 case GAMIN_EVENT_DELETED:
575 type = FAMDeleted;
576 break;
577 case GAMIN_EVENT_MOVED:
578 type = FAMMoved;
579 break;
580 case GAMIN_EVENT_EXISTS:
581 type = FAMExists;
582 break;
583 case GAMIN_EVENT_ENDEXISTS:
584 type = FAMEndExist;
585 break;
586 #ifdef GAMIN_DEBUG_API
587 case 50:
588 type = 50 + reqno;
589 break;
590 #endif
591 default:
592 GAM_DEBUG(DEBUG_INFO, "Unknown event type %d\n", event);
593 return (-1);
594 }
595
596 GAM_DEBUG(DEBUG_INFO, "Event to %s : %d, %d, %s %s\n", conn->pidname,
597 reqno, type, path, gam_event_to_string(event));
598 /*
599 * prepare the packet
600 */
601 tlen = GAM_PACKET_HEADER_LEN + len;
602 /* We use only local socket so no need for network byte order conversion */
603 req.len = (unsigned short) tlen;
604 req.version = GAM_PROTO_VERSION;
605 req.seq = reqno;
606 req.type = (unsigned short) type;
607 req.pathlen = len;
608 memcpy(req.path, path, len);
609 ret = gam_client_conn_write(conn->source, conn->fd, (gpointer) &req, tlen);
610 if (!ret) {
611 GAM_DEBUG(DEBUG_INFO, "Failed to send event to %s\n", conn->pidname);
612 return (-1);
613 }
614 return (0);
615 }
616
617 /**
618 * gam_queue_event:
619 * @conn: the connection
620 * @event: the event type
621 * @path: the path
622 *
623 * Queue an event to be sent over a connection within the next second.
624 * If an identical event is found at the tail of the event queue
625 * no event will be queued.
626 */
627 void
gam_queue_event(GamConnDataPtr conn,int reqno,int event,const char * path,int len)628 gam_queue_event(GamConnDataPtr conn, int reqno, int event,
629 const char *path, int len)
630 {
631 g_assert (conn);
632 g_assert (conn->eq);
633
634 gam_eq_queue (conn->eq, reqno, event, path, len);
635 if (!conn->eq_source)
636 conn->eq_source = g_timeout_add (100 /* 100 milisecond */, gam_connection_eq_flush, conn);
637 }
638
639
640 /**
641 * gam_send_ack:
642 * @conn: the connection data
643 * @path: the file/directory path
644 *
645 * Emit an acknowledge event on the connection
646 *
647 * Returns 0 on success; -1 on failure
648 */
649 int
gam_send_ack(GamConnDataPtr conn,int reqno,const char * path,int len)650 gam_send_ack(GamConnDataPtr conn, int reqno,
651 const char *path, int len)
652 {
653 GAMPacket req;
654 size_t tlen;
655 int ret;
656
657 g_assert(conn);
658 g_assert(conn->fd >= 0);
659 g_assert(path);
660 g_assert(len > 0);
661 g_assert(path[len] == '\0');
662
663 if (len >= MAXPATHLEN) {
664 GAM_DEBUG(DEBUG_INFO,
665 "path (%s)'s length (%d) exceeds MAXPATHLEN (%d)\n",
666 path, len, MAXPATHLEN);
667 return (-1);
668 }
669
670 GAM_DEBUG(DEBUG_INFO, "Event to %s: %d, %d, %s\n", conn->pidname,
671 reqno, FAMAcknowledge, path);
672
673 /*
674 * prepare the packet
675 */
676 tlen = GAM_PACKET_HEADER_LEN + len;
677 /* We only use local sockets so no need for network byte order
678 conversion */
679 req.len = (unsigned short) tlen;
680 req.version = GAM_PROTO_VERSION;
681 req.seq = reqno;
682 req.type = FAMAcknowledge;
683 req.pathlen = len;
684 memcpy(req.path, path, len);
685
686 ret = gam_client_conn_write(conn->source, conn->fd, (gpointer) &req, tlen);
687 if (!ret) {
688 GAM_DEBUG(DEBUG_INFO, "Failed to send event to %s\n", conn->pidname);
689 return (-1);
690 }
691 return (0);
692 }
693
694 /************************************************************************
695 * *
696 * Automatic exit handling *
697 * *
698 ************************************************************************/
699
700 #define MAX_IDLE_TIMEOUT_MSEC (30*1000) /* 30 seconds */
701
702 static guint server_timeout_id = 0;
703
704 /**
705 * gam_connections_check:
706 *
707 * This function can be called periodically by e.g. g_timeout_add and
708 * shuts the server down if there have been no outstanding connections
709 * for a while.
710 */
711 static gboolean
gam_connections_check(void)712 gam_connections_check(void)
713 {
714 server_timeout_id = 0;
715
716 if (gamConnList == NULL) {
717 GAM_DEBUG(DEBUG_INFO, "Exiting on timeout\n");
718 gam_shutdown();
719 exit(0);
720 }
721 return (FALSE);
722 }
723
724 static void
gam_cancel_server_timeout(void)725 gam_cancel_server_timeout (void)
726 {
727 if (server_timeout_id)
728 g_source_remove (server_timeout_id);
729 server_timeout_id = 0;
730 }
731
732 void
gam_schedule_server_timeout(void)733 gam_schedule_server_timeout (void)
734 {
735 gam_cancel_server_timeout ();
736 server_timeout_id =
737 g_timeout_add(MAX_IDLE_TIMEOUT_MSEC, (GSourceFunc) gam_connections_check, NULL);
738 }
739
740 /**
741 * gam_connections_debug:
742 *
743 * Calling this function generate debugging informations about the set
744 * of existing connections.
745 */
746 void
gam_connections_debug(void)747 gam_connections_debug(void)
748 {
749 #ifdef GAM_DEBUG_ENABLED
750 GamConnDataPtr conn;
751 GList *cur;
752
753 if (!gam_debug_active)
754 return;
755 if (gamConnList == NULL) {
756 GAM_DEBUG(DEBUG_INFO, "No active connections\n");
757 return;
758 }
759
760 for (cur = gamConnList; cur; cur = g_list_next(cur)) {
761 conn = (GamConnDataPtr) cur->data;
762 if (conn == NULL) {
763 GAM_DEBUG(DEBUG_INFO, "Error: connection with no data\n");
764 } else {
765 const char *state = "unknown";
766
767 switch (conn->state) {
768 case GAM_STATE_ERROR:
769 state = "error";
770 break;
771 case GAM_STATE_AUTH:
772 state = "need auth";
773 break;
774 case GAM_STATE_OKAY:
775 state = "okay";
776 break;
777 case GAM_STATE_CLOSED:
778 state = "closed";
779 break;
780 }
781 GAM_DEBUG(DEBUG_INFO,
782 "Connection fd %d to %s: state %s, %d read\n",
783 conn->fd, conn->pidname, state, conn->request_len);
784 gam_listener_debug(conn->listener);
785 }
786 }
787 #endif
788 }
789