]> arthur.barton.de Git - netatalk.git/blob - etc/afpd/fce_api.c
8c7f48492d6b5af16618f7cb47a61e099ae4eb95
[netatalk.git] / etc / afpd / fce_api.c
1 /*
2  * Copyright (c) 2010 Mark Williams
3  * Copyright (c) 2012 Frank Lahm <franklahm@gmail.com>
4  *
5  * File change event API for netatalk
6  *
7  * for every detected filesystem change a UDP packet is sent to an arbitrary list
8  * of listeners. Each packet contains unix path of modified filesystem element,
9  * event reason, and a consecutive event id (32 bit). Technically we are UDP client and are sending
10  * out packets synchronuosly as they are created by the afp functions. This should not affect
11  * performance measurably. The only delaying calls occur during initialization, if we have to
12  * resolve non-IP hostnames to IP. All numeric data inside the packet is network byte order, so use
13  * ntohs / ntohl to resolve length and event id. Ideally a listener receives every packet with
14  * no gaps in event ids, starting with event id 1 and mode FCE_CONN_START followed by
15  * data events from id 2 up to 0xFFFFFFFF, followed by 0 to 0xFFFFFFFF and so on.
16  *
17  * A gap or not starting with 1 mode FCE_CONN_START or receiving mode FCE_CONN_BROKEN means that
18  * the listener has lost at least one filesystem event
19  * 
20  * All Rights Reserved.  See COPYRIGHT.
21  */
22
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif /* HAVE_CONFIG_H */
26
27 #include <stdio.h>
28
29 #include <string.h>
30 #include <stdlib.h>
31 #include <errno.h>
32 #include <time.h>
33 #include <sys/param.h>
34 #include <sys/socket.h>
35 #include <netinet/in.h>
36 #include <arpa/inet.h>
37 #include <netdb.h>
38 #include <stdbool.h>
39
40 #include <atalk/adouble.h>
41 #include <atalk/vfs.h>
42 #include <atalk/logger.h>
43 #include <atalk/afp.h>
44 #include <atalk/util.h>
45 #include <atalk/cnid.h>
46 #include <atalk/unix.h>
47 #include <atalk/fce_api.h>
48 #include <atalk/globals.h>
49
50 #include "fork.h"
51 #include "file.h"
52 #include "directory.h"
53 #include "desktop.h"
54 #include "volume.h"
55
56 // ONLY USED IN THIS FILE
57 #include "fce_api_internal.h"
58
59 extern int afprun_bg(int root, char *cmd);
60
61 /* We store our connection data here */
62 static struct udp_entry udp_socket_list[FCE_MAX_UDP_SOCKS];
63 static int udp_sockets = 0;
64 static bool udp_initialized = false;
65 static unsigned long fce_ev_enabled =
66     (1 << FCE_FILE_MODIFY) |
67     (1 << FCE_FILE_DELETE) |
68     (1 << FCE_DIR_DELETE) |
69     (1 << FCE_FILE_CREATE) |
70     (1 << FCE_DIR_CREATE) |
71     (1 << FCE_FILE_MOVE) |
72     (1 << FCE_DIR_MOVE) |
73     (1 << FCE_LOGIN) |
74     (1 << FCE_LOGOUT);
75
76 static uint8_t fce_ev_info;    /* flags of additional info to send in events */
77
78 #define MAXIOBUF 4096
79 static unsigned char iobuf[MAXIOBUF];
80 static const char **skip_files;
81 static struct fce_close_event last_close_event;
82
83 static char *fce_event_names[] = {
84     "",
85     "FCE_FILE_MODIFY",
86     "FCE_FILE_DELETE",
87     "FCE_DIR_DELETE",
88     "FCE_FILE_CREATE",
89     "FCE_DIR_CREATE",
90     "FCE_FILE_MOVE",
91     "FCE_DIR_MOVE",
92     "FCE_LOGIN",
93     "FCE_LOGOUT"
94 };
95
96 /*
97  *
98  * Initialize network structs for any listeners
99  * We dont give return code because all errors are handled internally (I hope..)
100  *
101  * */
102 void fce_init_udp()
103 {
104     int rv;
105     struct addrinfo hints, *servinfo, *p;
106
107     if (udp_initialized == true)
108         return;
109
110     memset(&hints, 0, sizeof hints);
111     hints.ai_family = AF_UNSPEC;
112     hints.ai_socktype = SOCK_DGRAM;
113
114     for (int i = 0; i < udp_sockets; i++) {
115         struct udp_entry *udp_entry = udp_socket_list + i;
116
117         /* Close any pending sockets */
118         if (udp_entry->sock != -1)
119             close(udp_entry->sock);
120
121         if ((rv = getaddrinfo(udp_entry->addr, udp_entry->port, &hints, &servinfo)) != 0) {
122             LOG(log_error, logtype_fce, "fce_init_udp: getaddrinfo(%s:%s): %s",
123                 udp_entry->addr, udp_entry->port, gai_strerror(rv));
124             continue;
125         }
126
127         /* loop through all the results and make a socket */
128         for (p = servinfo; p != NULL; p = p->ai_next) {
129             if ((udp_entry->sock = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) {
130                 LOG(log_error, logtype_fce, "fce_init_udp: socket(%s:%s): %s",
131                     udp_entry->addr, udp_entry->port, strerror(errno));
132                 continue;
133             }
134             break;
135         }
136
137         if (p == NULL) {
138             LOG(log_error, logtype_fce, "fce_init_udp: no socket for %s:%s",
139                 udp_entry->addr, udp_entry->port);
140         }
141         udp_entry->addrinfo = *p;
142         memcpy(&udp_entry->addrinfo, p, sizeof(struct addrinfo));
143         memcpy(&udp_entry->sockaddr, p->ai_addr, sizeof(struct sockaddr_storage));
144         freeaddrinfo(servinfo);
145     }
146
147     udp_initialized = true;
148 }
149
150 void fce_cleanup()
151 {
152     if (udp_initialized == false )
153         return;
154
155     for (int i = 0; i < udp_sockets; i++)
156     {
157         struct udp_entry *udp_entry = udp_socket_list + i;
158
159         /* Close any pending sockets */
160         if (udp_entry->sock != -1)
161         {
162             close( udp_entry->sock );
163             udp_entry->sock = -1;
164         }
165     }
166     udp_initialized = false;
167 }
168
169 /*
170  * Construct a UDP packet for our listeners and return packet size
171  * */
172 static ssize_t build_fce_packet(const AFPObj *obj,
173                                 char *iobuf,
174                                 fce_ev_t event,
175                                 const char *path,
176                                 const char *oldpath,
177                                 pid_t pid,
178                                 const char *user,
179                                 uint32_t event_id)
180 {
181     char *p = iobuf;
182     size_t pathlen;
183     ssize_t datalen = 0;
184     uint16_t uint16;
185     uint32_t uint32;
186     uint64_t uint64;
187     uint8_t packet_info = fce_ev_info;
188
189     /* FCE magic */
190     memcpy(p, FCE_PACKET_MAGIC, 8);
191     p += 8;
192     datalen += 8;
193
194     /* version */
195     *p = FCE_PACKET_VERSION;
196     p += 1;
197     datalen += 1;
198
199     /* optional: options */
200     if (FCE_PACKET_VERSION > 1) {
201         if (oldpath)
202             packet_info |= FCE_EV_INFO_SRCPATH;
203         *p = packet_info;
204         p += 1;
205         datalen += 1;
206     }
207
208     /* event */
209     *p = event;
210     p += 1;
211     datalen += 1;
212
213     /* optional: padding */
214     if (FCE_PACKET_VERSION > 1) {
215         p += 1;
216         datalen += 1;
217     }
218
219     /* optional: reserved */
220     if (FCE_PACKET_VERSION > 1) {
221         p += 8;
222         datalen += 8;
223     }
224
225     /* event ID */
226     uint32 = htonl(event_id);
227     memcpy(p, &uint32, sizeof(uint32));
228     p += sizeof(uint32);
229     datalen += sizeof(uint32);
230
231     /* optional: pid */
232     if (packet_info & FCE_EV_INFO_PID) {
233         uint64 = pid;
234         uint64 = hton64(uint64);
235         memcpy(p, &uint64, sizeof(uint64));
236         p += sizeof(uint64);
237         datalen += sizeof(uint64);
238     }
239
240     /* optional: username */
241     if (packet_info & FCE_EV_INFO_USER) {
242         uint16 = strlen(user);
243         uint16 = htons(uint16);
244         memcpy(p, &uint16, sizeof(uint16));
245         p += sizeof(uint16);
246         datalen += sizeof(uint16);
247         memcpy(p, user, strlen(user));
248         p += strlen(user);
249         datalen += strlen(user);
250     }
251
252     /* path */
253     if ((pathlen = strlen(path)) >= MAXPATHLEN)
254         pathlen = MAXPATHLEN - 1;
255     uint16 = pathlen;
256     uint16 = htons(uint16);
257     memcpy(p, &uint16, sizeof(uint16));
258     p += sizeof(uint16);
259     datalen += sizeof(uint16);
260     memcpy(p, path, pathlen);
261     p += pathlen;
262     datalen += pathlen;
263
264     /* optional: source path */
265     if (packet_info & FCE_EV_INFO_SRCPATH) {
266         if ((pathlen = strlen(oldpath)) >= MAXPATHLEN)
267             pathlen = MAXPATHLEN - 1;
268         uint16 = pathlen;
269         uint16 = htons(uint16);
270         memcpy(p, &uint16, sizeof(uint16));
271         p += sizeof(uint16);
272         datalen += sizeof(uint16);
273         memcpy(p, oldpath, pathlen);
274         p += pathlen;
275         datalen += pathlen;
276     }
277
278     /* return the packet len */
279     return datalen;
280 }
281
282 /*
283  * Send the fce information to all (connected) listeners
284  * We dont give return code because all errors are handled internally (I hope..)
285  * */
286 static void send_fce_event(const AFPObj *obj, int event, const char *path, const char *oldpath)
287 {    
288     static bool first_event = true;
289     static uint32_t event_id = 0; /* the unique packet couter to detect packet/data loss. Going from 0xFFFFFFFF to 0x0 is a valid increment */
290     static char *user;
291     time_t now = time(NULL);
292     ssize_t data_len;
293
294     /* initialized ? */
295     if (first_event == true) {
296         first_event = false;
297
298         struct passwd *pwd = getpwuid(obj->uid);
299         user = strdup(pwd->pw_name);
300
301         switch (obj->fce_version) {
302         case 1:
303             /* fce_ev_info unused */
304             break;
305         case 2:
306             fce_ev_info = FCE_EV_INFO_PID | FCE_EV_INFO_USER;
307             break;
308         default:
309             fce_ev_info = 0;
310             LOG(log_error, logtype_fce, "Unsupported FCE protocol version %d", obj->fce_version);
311             break;
312         }
313
314         fce_init_udp();
315         /* Notify listeners the we start from the beginning */
316         send_fce_event(obj, FCE_CONN_START, "", NULL);
317     }
318
319     /* run script */
320     if (obj->fce_notify_script) {
321         static bstring quote = NULL;
322         static bstring quoterep = NULL;
323         static bstring slash = NULL;
324         static bstring slashrep = NULL;
325
326         if (!quote) {
327             quote = bfromcstr("'");
328             quoterep = bfromcstr("'\\''");
329             slash = bfromcstr("\\");
330             slashrep = bfromcstr("\\\\");
331         }
332
333         bstring cmd = bformat("%s -v %d -e %s -i %" PRIu32 "",
334                               obj->fce_notify_script,
335                               FCE_PACKET_VERSION,
336                               fce_event_names[event],
337                               event_id);
338
339         if (path[0]) {
340             bstring bpath = bfromcstr(path);
341             bfindreplace(bpath, slash, slashrep, 0);
342             bfindreplace(bpath, quote, quoterep, 0);
343             bformata(cmd, " -P '%s'", bdata(bpath));
344             bdestroy(bpath);
345         }
346         if (fce_ev_info | FCE_EV_INFO_PID)
347             bformata(cmd, " -p %" PRIu64 "", (uint64_t)getpid());
348         if (fce_ev_info | FCE_EV_INFO_USER)
349             bformata(cmd, " -u %s", user);
350         if (oldpath) {
351             bstring boldpath = bfromcstr(oldpath);
352             bfindreplace(boldpath, slash, slashrep, 0);
353             bfindreplace(boldpath, quote, quoterep, 0);
354             bformata(cmd, " -S '%s'", bdata(boldpath));
355             bdestroy(boldpath);
356         }
357         (void)afprun_bg(1, bdata(cmd));
358         bdestroy(cmd);
359     }
360
361     for (int i = 0; i < udp_sockets; i++) {
362         int sent_data = 0;
363         struct udp_entry *udp_entry = udp_socket_list + i;
364
365         /* we had a problem earlier ? */
366         if (udp_entry->sock == -1) {
367             /* We still have to wait ?*/
368             if (now < udp_entry->next_try_on_error)
369                 continue;
370
371             /* Reopen socket */
372             udp_entry->sock = socket(udp_entry->addrinfo.ai_family,
373                                      udp_entry->addrinfo.ai_socktype,
374                                      udp_entry->addrinfo.ai_protocol);
375             
376             if (udp_entry->sock == -1) {
377                 /* failed again, so go to rest again */
378                 LOG(log_error, logtype_fce, "Cannot recreate socket for fce UDP connection: errno %d", errno  );
379
380                 udp_entry->next_try_on_error = now + FCE_SOCKET_RETRY_DELAY_S;
381                 continue;
382             }
383
384             udp_entry->next_try_on_error = 0;
385
386             /* Okay, we have a running socket again, send server that we had a problem on our side*/
387             data_len = build_fce_packet(obj, iobuf, FCE_CONN_BROKEN, "", NULL, getpid(), user, 0);
388
389             sendto(udp_entry->sock,
390                    iobuf,
391                    data_len,
392                    0,
393                    (struct sockaddr *)&udp_entry->sockaddr,
394                    udp_entry->addrinfo.ai_addrlen);
395         }
396
397         /* build our data packet */
398         data_len = build_fce_packet(obj, iobuf, event, path, oldpath, getpid(), user, ++event_id);
399
400         sent_data = sendto(udp_entry->sock,
401                            iobuf,
402                            data_len,
403                            0,
404                            (struct sockaddr *)&udp_entry->sockaddr,
405                            udp_entry->addrinfo.ai_addrlen);
406
407         /* Problems ? */
408         if (sent_data != data_len) {
409             /* Argh, socket broke, we close and retry later */
410             LOG(log_error, logtype_fce, "send_fce_event: error sending packet to %s:%s, transfered %d of %d: %s",
411                 udp_entry->addr, udp_entry->port, sent_data, data_len, strerror(errno));
412
413             close( udp_entry->sock );
414             udp_entry->sock = -1;
415             udp_entry->next_try_on_error = now + FCE_SOCKET_RETRY_DELAY_S;
416         }
417     }
418 }
419
420 static int add_udp_socket(const char *target_ip, const char *target_port )
421 {
422     if (target_port == NULL)
423         target_port = FCE_DEFAULT_PORT_STRING;
424
425     if (udp_sockets >= FCE_MAX_UDP_SOCKS) {
426         LOG(log_error, logtype_fce, "Too many file change api UDP connections (max %d allowed)", FCE_MAX_UDP_SOCKS );
427         return AFPERR_PARAM;
428     }
429
430     udp_socket_list[udp_sockets].addr = strdup(target_ip);
431     udp_socket_list[udp_sockets].port = strdup(target_port);
432     udp_socket_list[udp_sockets].sock = -1;
433     memset(&udp_socket_list[udp_sockets].addrinfo, 0, sizeof(struct addrinfo));
434     memset(&udp_socket_list[udp_sockets].sockaddr, 0, sizeof(struct sockaddr_storage));
435     udp_socket_list[udp_sockets].next_try_on_error = 0;
436
437     udp_sockets++;
438
439     return AFP_OK;
440 }
441
442 static void save_close_event(const AFPObj *obj, const char *path)
443 {
444     time_t now = time(NULL);
445
446     /* Check if it's a close for the same event as the last one */
447     if (last_close_event.time   /* is there any saved event ? */
448         && (strcmp(path, last_close_event.path) != 0)) {
449         /* no, so send the saved event out now */
450         send_fce_event(obj, FCE_FILE_MODIFY,last_close_event.path, NULL);
451     }
452
453     LOG(log_debug, logtype_fce, "save_close_event: %s", path);
454
455     last_close_event.time = now;
456     strncpy(last_close_event.path, path, MAXPATHLEN);
457 }
458
459 static void fce_init_ign_names(const char *ignores)
460 {
461     int count = 0;
462     char *names = strdup(ignores);
463     char *p;
464     int i = 0;
465
466     while (names[i]) {
467         count++;
468         for (; names[i] && names[i] != '/'; i++)
469             ;
470         if (!names[i])
471             break;
472         i++;
473     }
474
475     skip_files = calloc(count + 1, sizeof(char *));
476
477     for (i = 0, p = strtok(names, "/"); p ; p = strtok(NULL, "/"))
478         skip_files[i++] = strdup(p);
479
480     free(names);
481 }
482
483 /*
484  *
485  * Dispatcher for all incoming file change events
486  *
487  * */
488 int fce_register(const AFPObj *obj, fce_ev_t event, const char *path, const char *oldpath)
489 {
490     static bool first_event = true;
491     const char *bname;
492
493     if (!(fce_ev_enabled & (1 << event)))
494         return AFP_OK;
495
496     AFP_ASSERT(event >= FCE_FIRST_EVENT && event <= FCE_LAST_EVENT);
497     AFP_ASSERT(path);
498
499     LOG(log_debug, logtype_fce, "register_fce(path: %s, event: %s)",
500         path, fce_event_names[event]);
501
502     bname = basename_safe(path);
503
504     if ((udp_sockets == 0) && (obj->fce_notify_script == NULL)) {
505         /* No listeners configured */
506         return AFP_OK;
507     }
508
509         /* do some initialization on the fly the first time */
510         if (first_event) {
511                 fce_initialize_history();
512         fce_init_ign_names(obj->fce_ign_names);
513         first_event = false;
514         }
515
516         /* handle files which should not cause events (.DS_Store atc. ) */
517     for (int i = 0; skip_files[i] != NULL; i++) {
518         if (strcmp(bname, skip_files[i]) == 0)
519                         return AFP_OK;
520         }
521
522         /* Can we ignore this event based on type or history? */
523         if (fce_handle_coalescation(event, path)) {
524                 LOG(log_debug9, logtype_fce, "Coalesced fc event <%d> for <%s>", event, path);
525                 return AFP_OK;
526         }
527
528     switch (event) {
529     case FCE_FILE_MODIFY:
530         save_close_event(obj, path);
531         break;
532     default:
533         send_fce_event(obj, event, path, oldpath);
534         break;
535     }
536
537     return AFP_OK;
538 }
539
540 static void check_saved_close_events(const AFPObj *obj)
541 {
542     time_t now = time(NULL);
543
544     /* check if configured holdclose time has passed */
545     if (last_close_event.time && ((last_close_event.time + obj->options.fce_fmodwait) < now)) {
546         LOG(log_debug, logtype_fce, "check_saved_close_events: sending event: %s", last_close_event.path);
547         /* yes, send event */
548         send_fce_event(obj, FCE_FILE_MODIFY, &last_close_event.path[0], NULL);
549         last_close_event.path[0] = 0;
550         last_close_event.time = 0;
551     }
552 }
553
554 /******************** External calls start here **************************/
555
556 /*
557  * API-Calls for file change api, called form outside (file.c directory.c ofork.c filedir.c)
558  * */
559 void fce_pending_events(const AFPObj *obj)
560 {
561     if (!udp_sockets)
562         return;
563     check_saved_close_events(obj);
564 }
565
566 /*
567  *
568  * Extern connect to afpd parameter, can be called multiple times for multiple listeners (up to MAX_UDP_SOCKS times)
569  *
570  * */
571 int fce_add_udp_socket(const char *target)
572 {
573         const char *port = FCE_DEFAULT_PORT_STRING;
574         char target_ip[256] = {""};
575
576         strncpy(target_ip, target, sizeof(target_ip) -1);
577
578         char *port_delim = strchr( target_ip, ':' );
579         if (port_delim) {
580                 *port_delim = 0;
581                 port = port_delim + 1;
582         }
583         return add_udp_socket(target_ip, port);
584 }
585
586 int fce_set_events(const char *events)
587 {
588     char *e;
589     char *p;
590     
591     if (events == NULL)
592         return AFPERR_PARAM;
593
594     e = strdup(events);
595
596     fce_ev_enabled = 0;
597
598     for (p = strtok(e, ", "); p; p = strtok(NULL, ", ")) {
599         if (strcmp(p, "fmod") == 0) {
600             fce_ev_enabled |= (1 << FCE_FILE_MODIFY);
601         } else if (strcmp(p, "fdel") == 0) {
602             fce_ev_enabled |= (1 << FCE_FILE_DELETE);
603         } else if (strcmp(p, "ddel") == 0) {
604             fce_ev_enabled |= (1 << FCE_DIR_DELETE);
605         } else if (strcmp(p, "fcre") == 0) {
606             fce_ev_enabled |= (1 << FCE_FILE_CREATE);
607         } else if (strcmp(p, "dcre") == 0) {
608             fce_ev_enabled |= (1 << FCE_DIR_CREATE);
609         } else if (strcmp(p, "fmov") == 0) {
610             fce_ev_enabled |= (1 << FCE_FILE_MOVE);
611         } else if (strcmp(p, "dmov") == 0) {
612             fce_ev_enabled |= (1 << FCE_DIR_MOVE);
613         } else if (strcmp(p, "login") == 0) {
614             fce_ev_enabled |= (1 << FCE_LOGIN);
615         } else if (strcmp(p, "logout") == 0) {
616             fce_ev_enabled |= (1 << FCE_LOGOUT);
617         }
618     }
619
620     free(e);
621
622     return AFP_OK;
623 }