]> arthur.barton.de Git - netatalk.git/blob - etc/afpd/fce_api.c
Check if there are configured listeners
[netatalk.git] / etc / afpd / fce_api.c
1 /*\r
2  * Copyright (c) 2010 Mark Williams\r
3  *\r
4  * File change event API for netatalk\r
5  *\r
6  * for every detected filesystem change a UDP packet is sent to an arbitrary list\r
7  * of listeners. Each packet contains unix path of modified filesystem element,\r
8  * event reason, and a consecutive event id (32 bit). Technically we are UDP client and are sending\r
9  * out packets synchronuosly as they are created by the afp functions. This should not affect\r
10  * performance measurably. The only delaying calls occur during initialization, if we have to\r
11  * resolve non-IP hostnames to IP. All numeric data inside the packet is network byte order, so use\r
12  * ntohs / ntohl to resolve length and event id. Ideally a listener receives every packet with\r
13  * no gaps in event ids, starting with event id 1 and mode FCE_CONN_START followed by\r
14  * data events from id 2 up to 0xFFFFFFFF, followed by 0 to 0xFFFFFFFF and so on.\r
15  *\r
16  * A gap or not starting with 1 mode FCE_CONN_START or receiving mode FCE_CONN_BROKEN means that\r
17  * the listener has lost at least one filesystem event\r
18  * \r
19  * All Rights Reserved.  See COPYRIGHT.\r
20  */\r
21 \r
22 #ifdef HAVE_CONFIG_H\r
23 #include "config.h"\r
24 #endif /* HAVE_CONFIG_H */\r
25 \r
26 #include <stdio.h>\r
27 \r
28 #include <string.h>\r
29 #include <stdlib.h>\r
30 #include <errno.h>\r
31 #include <time.h>\r
32 \r
33 \r
34 #include <sys/param.h>\r
35 #include <sys/socket.h>\r
36 #include <netinet/in.h>\r
37 #include <arpa/inet.h>\r
38 #include <netdb.h>\r
39 \r
40 #include <netatalk/at.h>\r
41 \r
42 #include <atalk/adouble.h>\r
43 #include <atalk/vfs.h>\r
44 #include <atalk/logger.h>\r
45 #include <atalk/afp.h>\r
46 #include <atalk/util.h>\r
47 #include <atalk/cnid.h>\r
48 #include <atalk/unix.h>\r
49 \r
50 #include "fork.h"\r
51 #include "file.h"\r
52 #include "globals.h"\r
53 #include "directory.h"\r
54 #include "desktop.h"\r
55 #include "volume.h"\r
56 \r
57 #include "fce_api.h"\r
58 \r
59 // ONLY USED IN THIS FILE\r
60 #include "fce_api_internal.h"\r
61 \r
62 #define FCE_TRUE 1\r
63 #define FCE_FALSE 0\r
64 \r
65 /* We store our connection data here */\r
66 static struct udp_entry udp_socket_list[FCE_MAX_UDP_SOCKS];\r
67 static int udp_sockets = 0;\r
68 static int udp_initialized = FCE_FALSE;\r
69 \r
70 \r
71 static const char *skip_files[] = \r
72 {\r
73         ".DS_Store",\r
74         NULL\r
75 };\r
76 \r
77 /*\r
78  *\r
79  * Initialize network structs for any listeners\r
80  * We dont give return code because all errors are handled internally (I hope..)\r
81  *\r
82  * */\r
83 void fce_init_udp()\r
84 {\r
85     if (udp_initialized == FCE_TRUE)\r
86         return;\r
87 \r
88 \r
89     for (int i = 0; i < udp_sockets; i++)\r
90     {\r
91         struct udp_entry *udp_entry = udp_socket_list + i;\r
92 \r
93         /* Close any pending sockets */\r
94         if (udp_entry->sock != -1)\r
95         {\r
96             close( udp_entry->sock );\r
97         }\r
98 \r
99         /* resolve IP to network address */\r
100         if (inet_aton( udp_entry->ip, &udp_entry->addr.sin_addr ) ==0 )\r
101         {\r
102             /* Hmm, failed try to resolve host */\r
103             struct hostent *hp = gethostbyname( udp_entry->ip );\r
104             if (hp == NULL)\r
105             {\r
106                 LOG(log_error, logtype_afpd, "Cannot resolve host name for fce UDP connection: %s (errno %d)", udp_entry->ip, errno  );\r
107                 continue;\r
108             }\r
109             memcpy( &udp_entry->addr.sin_addr, &hp->h_addr, sizeof(udp_entry->addr.sin_addr) );\r
110         }\r
111 \r
112         /* Create UDP socket */\r
113         udp_entry->sock = socket( AF_INET, SOCK_DGRAM, 0 );\r
114         if (udp_entry->sock == -1)\r
115         {\r
116             LOG(log_error, logtype_afpd, "Cannot create socket for fce UDP connection: errno %d", errno  );\r
117             continue;\r
118         }\r
119 \r
120         /* Set socket address params */\r
121         udp_entry->addr.sin_family = AF_INET;\r
122         udp_entry->addr.sin_port = htons(udp_entry->port);\r
123     }\r
124     udp_initialized = FCE_TRUE;\r
125 \r
126 }\r
127 void fce_cleanup()\r
128 {\r
129     if (udp_initialized == FCE_FALSE )\r
130         return;\r
131 \r
132     for (int i = 0; i < udp_sockets; i++)\r
133     {\r
134         struct udp_entry *udp_entry = udp_socket_list + i;\r
135 \r
136         /* Close any pending sockets */\r
137         if (udp_entry->sock != -1)\r
138         {\r
139             close( udp_entry->sock );\r
140             udp_entry->sock = -1;\r
141         }\r
142     }\r
143     udp_initialized = FCE_FALSE;\r
144 }\r
145 \r
146 \r
147 /*\r
148  * Construct a UDP packet for our listeners and return packet size\r
149  * */\r
150 static unsigned short build_fce_packet( struct fce_packet *packet, char *path, int mode, uint32_t event_id )\r
151 {\r
152     unsigned short data_len = 0;\r
153 \r
154     strncpy(packet->magic, FCE_PACKET_MAGIC, sizeof(packet->magic) );\r
155     packet->version = FCE_PACKET_VERSION;\r
156     packet->mode = mode;\r
157 \r
158     data_len = strlen( path );\r
159 \r
160     /* This should never happen, but before we bust this server, we send nonsense, fce listener has to cope */\r
161     if (data_len >= FCE_MAX_PATH_LEN)\r
162     {\r
163         data_len = FCE_MAX_PATH_LEN - 1;\r
164     }\r
165 \r
166     /* This is the payload len. Means: the stream has len bytes more until packet is finished */\r
167     /* A server should read the first 16 byte, decode them and then fetch the rest */\r
168     packet->len = htons( data_len);\r
169     packet->event_id = htonl( event_id );\r
170 \r
171     strncpy( packet->data, path, data_len );\r
172 \r
173     /* return the packet len */\r
174     return sizeof(struct fce_packet) - FCE_MAX_PATH_LEN + data_len;\r
175 }\r
176 \r
177 /*\r
178  * Send the fce information to all (connected) listeners\r
179  * We dont give return code because all errors are handled internally (I hope..)\r
180  * */\r
181 static void send_fce_event( char *path, int mode )\r
182 {    \r
183     struct fce_packet packet;\r
184     void *data = &packet;\r
185     static uint32_t event_id = 0; /* the unique packet couter to detect packet/data loss. Going from 0xFFFFFFFF to 0x0 is a valid increment */\r
186 \r
187     time_t now = time(NULL);\r
188 \r
189     /* build our data packet */\r
190     int data_len = build_fce_packet( &packet, path, mode, ++event_id );\r
191 \r
192 \r
193     for (int i = 0; i < udp_sockets; i++)\r
194     {\r
195         int sent_data = 0;\r
196         struct udp_entry *udp_entry = udp_socket_list + i;\r
197 \r
198         /* we had a problem earlier ? */\r
199         if (udp_entry->sock == -1)\r
200         {\r
201             /* We still have to wait ?*/\r
202             if (now < udp_entry->next_try_on_error)\r
203                 continue;\r
204 \r
205             /* Reopen socket */\r
206             udp_entry->sock = socket( AF_INET, SOCK_DGRAM, 0 );\r
207 \r
208             if (udp_entry->sock == -1)\r
209             {\r
210                 /* failed again, so go to rest again */\r
211                 LOG(log_error, logtype_afpd, "Cannot recreate socket for fce UDP connection: errno %d", errno  );\r
212 \r
213                 udp_entry->next_try_on_error = now + FCE_SOCKET_RETRY_DELAY_S;\r
214                 continue;\r
215             }\r
216 \r
217             udp_entry->next_try_on_error = 0;\r
218 \r
219             /* Okay, we have a running socket again, send server that we had a problem on our side*/\r
220             data_len = build_fce_packet( &packet, "", FCE_CONN_BROKEN, 0 );\r
221 \r
222             sendto( udp_entry->sock, data, data_len, 0, &udp_entry->addr, sizeof(udp_entry->addr) );\r
223 \r
224             /* Rebuild our original data packet */\r
225             data_len = build_fce_packet( &packet, path, mode, event_id );\r
226         }\r
227 \r
228         sent_data =  sendto( udp_entry->sock, data, data_len, 0, &udp_entry->addr, sizeof(udp_entry->addr) );\r
229 \r
230         /* Problems ? */\r
231         if (sent_data != data_len)\r
232         {\r
233             /* Argh, socket broke, we close and retry later */\r
234             LOG(log_error, logtype_afpd, "Error while sending packet to %s for fce UDP connection: transfered: %d of %d errno %d",\r
235                     udp_entry->port, sent_data, data_len, errno  );\r
236 \r
237             close( udp_entry->sock );\r
238             udp_entry->sock = -1;\r
239             udp_entry->next_try_on_error = now + FCE_SOCKET_RETRY_DELAY_S;\r
240         }\r
241     }\r
242 }\r
243 \r
244 static int add_udp_socket( char *target_ip, int target_port )\r
245 {\r
246     if (target_port == 0)\r
247         target_port = FCE_DEFAULT_PORT;\r
248 \r
249     if (udp_sockets >= FCE_MAX_UDP_SOCKS)\r
250     {\r
251         LOG(log_error, logtype_afpd, "Too many file change api UDP connections (max %d allowed)", FCE_MAX_UDP_SOCKS );\r
252         return AFPERR_PARAM;\r
253     }\r
254 \r
255     strncpy( udp_socket_list[udp_sockets].ip, target_ip, FCE_MAX_IP_LEN - 1);\r
256     udp_socket_list[udp_sockets].port = target_port;\r
257     udp_socket_list[udp_sockets].sock = -1;\r
258     memset( &udp_socket_list[udp_sockets].addr, 0, sizeof(struct sockaddr_in) );\r
259     udp_socket_list[udp_sockets].next_try_on_error = 0;\r
260 \r
261     udp_sockets++;\r
262 \r
263     return AFP_OK;\r
264 }\r
265 \r
266 /*\r
267  *\r
268  * Dispatcher for all incoming file change events\r
269  *\r
270  * */\r
271 static int register_fce( char *u_name, int is_dir, int mode )\r
272 {\r
273     if (udp_sockets == 0)\r
274         /* No listeners configured */\r
275         return AFP_OK;\r
276 \r
277     if (u_name == NULL)\r
278         return AFPERR_PARAM;\r
279 \r
280     static int first_event = FCE_TRUE;\r
281 \r
282         /* do some initialization on the fly the first time */\r
283         if (first_event)\r
284         {\r
285                 fce_initialize_history();\r
286         }\r
287 \r
288 \r
289         /* handle files which should not cause events (.DS_Store atc. ) */\r
290         for (int i = 0; skip_files[i] != NULL; i++)\r
291         {\r
292                 if (!strcmp( u_name, skip_files[i]))\r
293                         return AFP_OK;\r
294         }\r
295 \r
296 \r
297         char full_path_buffer[FCE_MAX_PATH_LEN + 1] = {""};\r
298         const char *cwd = getcwdpath();\r
299 \r
300         if (!is_dir || mode == FCE_DIR_DELETE)\r
301         {\r
302                 if (strlen( cwd ) + strlen( u_name) + 1 >= FCE_MAX_PATH_LEN)\r
303                 {\r
304                         LOG(log_error, logtype_afpd, "FCE file name too long: %s/%s", cwd, u_name );\r
305                         return AFPERR_PARAM;\r
306                 }\r
307                 sprintf( full_path_buffer, "%s/%s", cwd, u_name );\r
308         }\r
309         else\r
310         {\r
311                 if (strlen( cwd ) >= FCE_MAX_PATH_LEN)\r
312                 {\r
313                         LOG(log_error, logtype_afpd, "FCE directory name too long: %s", cwd);\r
314                         return AFPERR_PARAM;\r
315                 }\r
316                 strcpy( full_path_buffer, cwd);\r
317         }\r
318 \r
319         /* Can we ignore this event based on type or history? */\r
320         if (fce_handle_coalescation( full_path_buffer, is_dir, mode ))\r
321         {\r
322                 LOG(log_debug9, logtype_afpd, "Coalesced fc event <%d> for <%s>", mode, full_path_buffer );\r
323                 return AFP_OK;\r
324         }\r
325 \r
326         LOG(log_debug9, logtype_afpd, "Detected fc event <%d> for <%s>", mode, full_path_buffer );\r
327 \r
328 \r
329     /* we do initilization on the fly, no blocking calls in here \r
330      * (except when using FQDN in broken DNS environment)\r
331      */\r
332     if (first_event == FCE_TRUE)\r
333     {\r
334         fce_init_udp();\r
335         \r
336         /* Notify listeners the we start from the beginning */\r
337         send_fce_event( "", FCE_CONN_START );\r
338         \r
339         first_event = FCE_FALSE;\r
340     }\r
341 \r
342         /* Handle UDP transport */\r
343     send_fce_event( full_path_buffer, mode );\r
344 \r
345     return AFP_OK;\r
346 }\r
347 \r
348 \r
349 /******************** External calls start here **************************/\r
350 \r
351 /*\r
352  * API-Calls for file change api, called form outside (file.c directory.c ofork.c filedir.c)\r
353  * */\r
354 #ifndef FCE_TEST_MAIN\r
355 \r
356 \r
357 int fce_register_delete_file( struct path *path )\r
358 {\r
359     int ret = AFP_OK;\r
360 \r
361     if (path == NULL)\r
362         return AFPERR_PARAM;\r
363 \r
364         \r
365     ret = register_fce( path->u_name, FALSE, FCE_FILE_DELETE );\r
366 \r
367     return ret;\r
368 }\r
369 int fce_register_delete_dir( char *name )\r
370 {\r
371     int ret = AFP_OK;\r
372 \r
373     if (name == NULL)\r
374         return AFPERR_PARAM;\r
375 \r
376         \r
377     ret = register_fce( name, TRUE, FCE_DIR_DELETE);\r
378 \r
379     return ret;\r
380 }\r
381 \r
382 int fce_register_new_dir( struct path *path )\r
383 {\r
384     int ret = AFP_OK;\r
385 \r
386     if (path == NULL)\r
387         return AFPERR_PARAM;\r
388 \r
389     ret = register_fce( path->u_name, TRUE, FCE_DIR_CREATE );\r
390 \r
391     return ret;\r
392 }\r
393 \r
394 \r
395 int fce_register_new_file( struct path *path )\r
396 {\r
397     int ret = AFP_OK;\r
398 \r
399     if (path == NULL)\r
400         return AFPERR_PARAM;\r
401 \r
402     ret = register_fce( path->u_name, FALSE, FCE_FILE_CREATE );\r
403 \r
404     return ret;\r
405 }\r
406 \r
407 \r
408 int fce_register_file_modification( struct ofork *ofork )\r
409 {\r
410     char *u_name = NULL;\r
411     struct vol *vol;\r
412     int ret = AFP_OK;\r
413 \r
414     if (ofork == NULL || ofork->of_vol == NULL)\r
415         return AFPERR_PARAM;\r
416 \r
417     vol = ofork->of_vol;\r
418 \r
419     if (NULL == (u_name = mtoupath(vol, of_name(ofork), ofork->of_did, utf8_encoding()))) \r
420     {\r
421         return AFPERR_MISC;\r
422     }\r
423     \r
424     ret = register_fce( u_name, FALSE, FCE_FILE_MODIFY );\r
425     \r
426     return ret;    \r
427 }\r
428 #endif\r
429 \r
430 /*\r
431  *\r
432  * Extern connect to afpd parameter, can be called multiple times for multiple listeners (up to MAX_UDP_SOCKS times)\r
433  *\r
434  * */\r
435 int fce_add_udp_socket( char *target )\r
436 {\r
437         int port = FCE_DEFAULT_PORT;\r
438         char target_ip[256] = {""};\r
439 \r
440         strncpy( target_ip, target, sizeof(target_ip) -1);\r
441         char *port_delim = strchr( target_ip, ':' );\r
442         if (port_delim)\r
443         {\r
444                 *port_delim = 0;\r
445                 port = atoi( port_delim + 1);\r
446         }\r
447         return add_udp_socket( target_ip, port );\r
448 }\r
449 \r
450 \r
451 \r
452 #ifdef FCE_TEST_MAIN\r
453 \r
454 \r
455 void shortsleep( unsigned int us )\r
456 {    \r
457     usleep( us );\r
458 }\r
459 int main( int argc, char*argv[] )\r
460 {\r
461     int port  = 11250;\r
462     char *host = NULL;\r
463     int delay_between_events = 1000;\r
464     int event_code = FCE_FILE_MODIFY;\r
465     char pathbuff[1024];\r
466     int duration_in_seconds = 0; // TILL ETERNITY\r
467 \r
468     char *path = getcwd( pathbuff, sizeof(pathbuff) );\r
469 \r
470     // FULLSPEED TEST IS "-s 1001" -> delay is 0 -> send packets without pause\r
471 \r
472     if (argc == 1)\r
473     {\r
474         fprintf( stdout, "%s: -p Port -h Listener1 [ -h Listener2 ...] -P path -s Delay_between_events_in_us -e event_code -d Duration \n", argv[0]);\r
475         exit( 1 );\r
476     }\r
477     int ret = AFP_OK;\r
478 \r
479     for (int i = 1; i < argc; i++)\r
480     {\r
481         char *p = argv[i];\r
482         if (*p == '-' && p[1])\r
483         {\r
484             char *arg = argv[i + 1];\r
485             switch (p[1])\r
486             {\r
487                 case 'p': if (arg) port = atoi( arg ), i++; break;\r
488                 case 'P': if (arg) path =  arg, i++; break;\r
489                 case 's': if (arg) delay_between_events = atoi( arg ), i++; break;\r
490                 case 'e': if (arg) event_code = atoi( arg ), i++; break;\r
491                 case 'd': if (arg) duration_in_seconds = atoi( arg ), i++; break;\r
492                 case 'h':\r
493                 {\r
494                     if (arg)\r
495                     {\r
496                         host = arg;\r
497                                                 char target[256];\r
498                                                 sprintf( target, "%s:%d", host, port );\r
499                         ret += fce_add_udp_socket( target );\r
500                         i++;\r
501                     }\r
502                     break;\r
503                 }               \r
504             }\r
505         }\r
506     }\r
507         \r
508 \r
509     if (host == NULL)\r
510         {\r
511                 char target[256];\r
512                 sprintf( target, "127.0.0.1:%d", port );\r
513                 ret += fce_add_udp_socket( target );\r
514         }\r
515 \r
516     if (ret)\r
517         return ret;\r
518 \r
519 \r
520     int ev_cnt = 0;\r
521     time_t start_time = time(NULL);\r
522     time_t end_time = 0;\r
523 \r
524     if (duration_in_seconds)\r
525         end_time = start_time + duration_in_seconds;\r
526 \r
527     while (1)\r
528     {\r
529         time_t now = time(NULL);\r
530         if (now > start_time)\r
531         {\r
532             start_time = now;\r
533             fprintf( stdout, "%d events/s\n", ev_cnt );\r
534             ev_cnt = 0;\r
535         }\r
536         if (end_time && now >= end_time)\r
537             break;\r
538 \r
539         register_fce( path, 0, event_code );\r
540         ev_cnt++;\r
541 \r
542         \r
543         shortsleep( delay_between_events );\r
544     }\r
545 }\r
546 #endif /* TESTMAIN*/\r