]> arthur.barton.de Git - netatalk.git/blob - etc/afpd/fce_api.c
aef6f1c426f2cc1bba2958f554996734b0009b8e
[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 (u_name == NULL)\r
274         return AFPERR_PARAM;\r
275 \r
276     static int first_event = FCE_TRUE;\r
277 \r
278         /* do some initialization on the fly the first time */\r
279         if (first_event)\r
280         {\r
281                 fce_initialize_history();\r
282         }\r
283 \r
284 \r
285         /* handle files which should not cause events (.DS_Store atc. ) */\r
286         for (int i = 0; skip_files[i] != NULL; i++)\r
287         {\r
288                 if (!strcmp( u_name, skip_files[i]))\r
289                         return AFP_OK;\r
290         }\r
291 \r
292 \r
293         char full_path_buffer[FCE_MAX_PATH_LEN + 1] = {""};\r
294         const char *cwd = getcwdpath();\r
295 \r
296         if (!is_dir || mode == FCE_DIR_DELETE)\r
297         {\r
298                 if (strlen( cwd ) + strlen( u_name) + 1 >= FCE_MAX_PATH_LEN)\r
299                 {\r
300                         LOG(log_error, logtype_afpd, "FCE file name too long: %s/%s", cwd, u_name );\r
301                         return AFPERR_PARAM;\r
302                 }\r
303                 sprintf( full_path_buffer, "%s/%s", cwd, u_name );\r
304         }\r
305         else\r
306         {\r
307                 if (strlen( cwd ) >= FCE_MAX_PATH_LEN)\r
308                 {\r
309                         LOG(log_error, logtype_afpd, "FCE directory name too long: %s", cwd);\r
310                         return AFPERR_PARAM;\r
311                 }\r
312                 strcpy( full_path_buffer, cwd);\r
313         }\r
314 \r
315         /* Can we ignore this event based on type or history? */\r
316         if (fce_handle_coalescation( full_path_buffer, is_dir, mode ))\r
317         {\r
318                 LOG(log_debug9, logtype_afpd, "Coalesced fc event <%d> for <%s>", mode, full_path_buffer );\r
319                 return AFP_OK;\r
320         }\r
321 \r
322         LOG(log_debug9, logtype_afpd, "Detected fc event <%d> for <%s>", mode, full_path_buffer );\r
323 \r
324 \r
325     /* we do initilization on the fly, no blocking calls in here \r
326      * (except when using FQDN in broken DNS environment)\r
327      */\r
328     if (first_event == FCE_TRUE)\r
329     {\r
330         fce_init_udp();\r
331         \r
332         /* Notify listeners the we start from the beginning */\r
333         send_fce_event( "", FCE_CONN_START );\r
334         \r
335         first_event = FCE_FALSE;\r
336     }\r
337 \r
338         /* Handle UDP transport */\r
339     send_fce_event( full_path_buffer, mode );\r
340 \r
341     return AFP_OK;\r
342 }\r
343 \r
344 \r
345 /******************** External calls start here **************************/\r
346 \r
347 /*\r
348  * API-Calls for file change api, called form outside (file.c directory.c ofork.c filedir.c)\r
349  * */\r
350 #ifndef FCE_TEST_MAIN\r
351 \r
352 \r
353 int fce_register_delete_file( struct path *path )\r
354 {\r
355     int ret = AFP_OK;\r
356 \r
357     if (path == NULL)\r
358         return AFPERR_PARAM;\r
359 \r
360         \r
361     ret = register_fce( path->u_name, FALSE, FCE_FILE_DELETE );\r
362 \r
363     return ret;\r
364 }\r
365 int fce_register_delete_dir( char *name )\r
366 {\r
367     int ret = AFP_OK;\r
368 \r
369     if (name == NULL)\r
370         return AFPERR_PARAM;\r
371 \r
372         \r
373     ret = register_fce( name, TRUE, FCE_DIR_DELETE);\r
374 \r
375     return ret;\r
376 }\r
377 \r
378 int fce_register_new_dir( struct path *path )\r
379 {\r
380     int ret = AFP_OK;\r
381 \r
382     if (path == NULL)\r
383         return AFPERR_PARAM;\r
384 \r
385     ret = register_fce( path->u_name, TRUE, FCE_DIR_CREATE );\r
386 \r
387     return ret;\r
388 }\r
389 \r
390 \r
391 int fce_register_new_file( struct path *path )\r
392 {\r
393     int ret = AFP_OK;\r
394 \r
395     if (path == NULL)\r
396         return AFPERR_PARAM;\r
397 \r
398     ret = register_fce( path->u_name, FALSE, FCE_FILE_CREATE );\r
399 \r
400     return ret;\r
401 }\r
402 \r
403 \r
404 int fce_register_file_modification( struct ofork *ofork )\r
405 {\r
406     char *u_name = NULL;\r
407     struct vol *vol;\r
408     int ret = AFP_OK;\r
409 \r
410     if (ofork == NULL || ofork->of_vol == NULL)\r
411         return AFPERR_PARAM;\r
412 \r
413     vol = ofork->of_vol;\r
414 \r
415     if (NULL == (u_name = mtoupath(vol, of_name(ofork), ofork->of_did, utf8_encoding()))) \r
416     {\r
417         return AFPERR_MISC;\r
418     }\r
419     \r
420     ret = register_fce( u_name, FALSE, FCE_FILE_MODIFY );\r
421     \r
422     return ret;    \r
423 }\r
424 #endif\r
425 \r
426 /*\r
427  *\r
428  * Extern connect to afpd parameter, can be called multiple times for multiple listeners (up to MAX_UDP_SOCKS times)\r
429  *\r
430  * */\r
431 int fce_add_udp_socket( char *target )\r
432 {\r
433         int port = FCE_DEFAULT_PORT;\r
434         char target_ip[256] = {""};\r
435 \r
436         strncpy( target_ip, target, sizeof(target_ip) -1);\r
437         char *port_delim = strchr( target_ip, ':' );\r
438         if (port_delim)\r
439         {\r
440                 *port_delim = 0;\r
441                 port = atoi( port_delim + 1);\r
442         }\r
443         return add_udp_socket( target_ip, port );\r
444 }\r
445 \r
446 \r
447 \r
448 #ifdef FCE_TEST_MAIN\r
449 \r
450 \r
451 void shortsleep( unsigned int us )\r
452 {    \r
453     usleep( us );\r
454 }\r
455 int main( int argc, char*argv[] )\r
456 {\r
457     int port  = 11250;\r
458     char *host = NULL;\r
459     int delay_between_events = 1000;\r
460     int event_code = FCE_FILE_MODIFY;\r
461     char pathbuff[1024];\r
462     int duration_in_seconds = 0; // TILL ETERNITY\r
463 \r
464     char *path = getcwd( pathbuff, sizeof(pathbuff) );\r
465 \r
466     // FULLSPEED TEST IS "-s 1001" -> delay is 0 -> send packets without pause\r
467 \r
468     if (argc == 1)\r
469     {\r
470         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
471         exit( 1 );\r
472     }\r
473     int ret = AFP_OK;\r
474 \r
475     for (int i = 1; i < argc; i++)\r
476     {\r
477         char *p = argv[i];\r
478         if (*p == '-' && p[1])\r
479         {\r
480             char *arg = argv[i + 1];\r
481             switch (p[1])\r
482             {\r
483                 case 'p': if (arg) port = atoi( arg ), i++; break;\r
484                 case 'P': if (arg) path =  arg, i++; break;\r
485                 case 's': if (arg) delay_between_events = atoi( arg ), i++; break;\r
486                 case 'e': if (arg) event_code = atoi( arg ), i++; break;\r
487                 case 'd': if (arg) duration_in_seconds = atoi( arg ), i++; break;\r
488                 case 'h':\r
489                 {\r
490                     if (arg)\r
491                     {\r
492                         host = arg;\r
493                                                 char target[256];\r
494                                                 sprintf( target, "%s:%d", host, port );\r
495                         ret += fce_add_udp_socket( target );\r
496                         i++;\r
497                     }\r
498                     break;\r
499                 }               \r
500             }\r
501         }\r
502     }\r
503         \r
504 \r
505     if (host == NULL)\r
506         {\r
507                 char target[256];\r
508                 sprintf( target, "127.0.0.1:%d", port );\r
509                 ret += fce_add_udp_socket( target );\r
510         }\r
511 \r
512     if (ret)\r
513         return ret;\r
514 \r
515 \r
516     int ev_cnt = 0;\r
517     time_t start_time = time(NULL);\r
518     time_t end_time = 0;\r
519 \r
520     if (duration_in_seconds)\r
521         end_time = start_time + duration_in_seconds;\r
522 \r
523     while (1)\r
524     {\r
525         time_t now = time(NULL);\r
526         if (now > start_time)\r
527         {\r
528             start_time = now;\r
529             fprintf( stdout, "%d events/s\n", ev_cnt );\r
530             ev_cnt = 0;\r
531         }\r
532         if (end_time && now >= end_time)\r
533             break;\r
534 \r
535         register_fce( path, 0, event_code );\r
536         ev_cnt++;\r
537 \r
538         \r
539         shortsleep( delay_between_events );\r
540     }\r
541 }\r
542 #endif /* TESTMAIN*/\r