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