]> arthur.barton.de Git - netatalk.git/blob - etc/afpd/fce_api.c
Import FCE
[netatalk.git] / etc / afpd / fce_api.c
1 /*\r
2  * $Id: fce_api.c,v 0.01 2010-10-01 00:00:0 mw Exp $\r
3  *\r
4  * Copyright (c) 2010 Mark Williams\r
5  *\r
6  * File change event API for netatalk\r
7  *\r
8  * for every detected filesystem change a UDP packet is sent to an arbitrary list\r
9  * of listeners. Each packet contains unix path of modified filesystem element,\r
10  * event reason, and a consecutive event id (32 bit). Technically we are UDP client and are sending\r
11  * out packets synchronuosly as they are created by the afp functions. This should not affect\r
12  * performance measurably. The only delaying calls occur during initialization, if we have to\r
13  * resolve non-IP hostnames to IP. All numeric data inside the packet is network byte order, so use\r
14  * ntohs / ntohl to resolve length and event id. Ideally a listener receives every packet with\r
15  * no gaps in event ids, starting with event id 1 and mode FCE_CONN_START followed by\r
16  * data events from id 2 up to 0xFFFFFFFF, followed by 0 to 0xFFFFFFFF and so on.\r
17  *\r
18  * A gap or not starting with 1 mode FCE_CONN_START or receiving mode FCE_CONN_BROKEN means that\r
19  * the listener has lost at least one filesystem event\r
20  * \r
21  * All Rights Reserved.  See COPYRIGHT.\r
22  */\r
23 \r
24 #ifdef HAVE_CONFIG_H\r
25 #include "config.h"\r
26 #endif /* HAVE_CONFIG_H */\r
27 \r
28 #include <stdio.h>\r
29 \r
30 #include <string.h>\r
31 #include <stdlib.h>\r
32 #include <errno.h>\r
33 #include <time.h>\r
34 \r
35 \r
36 #include <sys/param.h>\r
37 #include <sys/socket.h>\r
38 #include <netinet/in.h>\r
39 #include <arpa/inet.h>\r
40 #include <netdb.h>\r
41 \r
42 #include <netatalk/at.h>\r
43 \r
44 #include <atalk/adouble.h>\r
45 #include <atalk/vfs.h>\r
46 #include <atalk/logger.h>\r
47 #include <atalk/afp.h>\r
48 #include <atalk/util.h>\r
49 #include <atalk/cnid.h>\r
50 #include <atalk/unix.h>\r
51 \r
52 #include "fork.h"\r
53 #include "file.h"\r
54 #include "globals.h"\r
55 #include "directory.h"\r
56 #include "desktop.h"\r
57 #include "volume.h"\r
58 \r
59 #include "fce_api.h"\r
60 \r
61 // ONLY USED IN THIS FILE\r
62 #include "fce_api_internal.h"\r
63 \r
64 #define FCE_TRUE 1\r
65 #define FCE_FALSE 0\r
66 \r
67 /* We store our connection data here */\r
68 static struct udp_entry udp_socket_list[FCE_MAX_UDP_SOCKS];\r
69 static int udp_sockets = 0;\r
70 static int udp_initialized = FCE_FALSE;\r
71 \r
72 \r
73 static const char *skip_files[] = \r
74 {\r
75         ".DS_Store",\r
76         NULL\r
77 };\r
78 \r
79 /*\r
80  *\r
81  * Initialize network structs for any listeners\r
82  * We dont give return code because all errors are handled internally (I hope..)\r
83  *\r
84  * */\r
85 void fce_init_udp()\r
86 {\r
87     if (udp_initialized == FCE_TRUE)\r
88         return;\r
89 \r
90 \r
91     for (int i = 0; i < udp_sockets; i++)\r
92     {\r
93         struct udp_entry *udp_entry = udp_socket_list + i;\r
94 \r
95         /* Close any pending sockets */\r
96         if (udp_entry->sock != -1)\r
97         {\r
98             close( udp_entry->sock );\r
99         }\r
100 \r
101         /* resolve IP to network address */\r
102         if (inet_aton( udp_entry->ip, &udp_entry->addr.sin_addr ) ==0 )\r
103         {\r
104             /* Hmm, failed try to resolve host */\r
105             struct hostent *hp = gethostbyname( udp_entry->ip );\r
106             if (hp == NULL)\r
107             {\r
108                 LOG(log_error, logtype_afpd, "Cannot resolve host name for fce UDP connection: %s (errno %d)", udp_entry->ip, errno  );\r
109                 continue;\r
110             }\r
111             memcpy( &udp_entry->addr.sin_addr, &hp->h_addr, sizeof(udp_entry->addr.sin_addr) );\r
112         }\r
113 \r
114         /* Create UDP socket */\r
115         udp_entry->sock = socket( AF_INET, SOCK_DGRAM, 0 );\r
116         if (udp_entry->sock == -1)\r
117         {\r
118             LOG(log_error, logtype_afpd, "Cannot create socket for fce UDP connection: errno %d", errno  );\r
119             continue;\r
120         }\r
121 \r
122         /* Set socket address params */\r
123         udp_entry->addr.sin_family = AF_INET;\r
124         udp_entry->addr.sin_port = htons(udp_entry->port);\r
125     }\r
126     udp_initialized = FCE_TRUE;\r
127 \r
128 }\r
129 void fce_cleanup()\r
130 {\r
131     if (udp_initialized == FCE_FALSE )\r
132         return;\r
133 \r
134     for (int i = 0; i < udp_sockets; i++)\r
135     {\r
136         struct udp_entry *udp_entry = udp_socket_list + i;\r
137 \r
138         /* Close any pending sockets */\r
139         if (udp_entry->sock != -1)\r
140         {\r
141             close( udp_entry->sock );\r
142             udp_entry->sock = -1;\r
143         }\r
144     }\r
145     udp_initialized = FCE_FALSE;\r
146 }\r
147 \r
148 \r
149 /*\r
150  * Construct a UDP packet for our listeners and return packet size\r
151  * */\r
152 static unsigned short build_fce_packet( struct fce_packet *packet, char *path, int mode, uint32_t event_id )\r
153 {\r
154     unsigned short data_len = 0;\r
155 \r
156     strncpy(packet->magic, FCE_PACKET_MAGIC, sizeof(packet->magic) );\r
157     packet->version = FCE_PACKET_VERSION;\r
158     packet->mode = mode;\r
159 \r
160     data_len = strlen( path );\r
161 \r
162     /* This should never happen, but before we bust this server, we send nonsense, fce listener has to cope */\r
163     if (data_len >= FCE_MAX_PATH_LEN)\r
164     {\r
165         data_len = FCE_MAX_PATH_LEN - 1;\r
166     }\r
167 \r
168     /* This is the payload len. Means: the stream has len bytes more until packet is finished */\r
169     /* A server should read the first 16 byte, decode them and then fetch the rest */\r
170     packet->len = htons( data_len);\r
171     packet->event_id = htonl( event_id );\r
172 \r
173     strncpy( packet->data, path, data_len );\r
174 \r
175     /* return the packet len */\r
176     return sizeof(struct fce_packet) - FCE_MAX_PATH_LEN + data_len;\r
177 }\r
178 \r
179 /*\r
180  * Send the fce information to all (connected) listeners\r
181  * We dont give return code because all errors are handled internally (I hope..)\r
182  * */\r
183 static void send_fce_event( char *path, int mode )\r
184 {    \r
185     struct fce_packet packet;\r
186     void *data = &packet;\r
187     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
188 \r
189     time_t now = time(NULL);\r
190 \r
191     /* build our data packet */\r
192     int data_len = build_fce_packet( &packet, path, mode, ++event_id );\r
193 \r
194 \r
195     for (int i = 0; i < udp_sockets; i++)\r
196     {\r
197         int sent_data = 0;\r
198         struct udp_entry *udp_entry = udp_socket_list + i;\r
199 \r
200         /* we had a problem earlier ? */\r
201         if (udp_entry->sock == -1)\r
202         {\r
203             /* We still have to wait ?*/\r
204             if (now < udp_entry->next_try_on_error)\r
205                 continue;\r
206 \r
207             /* Reopen socket */\r
208             udp_entry->sock = socket( AF_INET, SOCK_DGRAM, 0 );\r
209 \r
210             if (udp_entry->sock == -1)\r
211             {\r
212                 /* failed again, so go to rest again */\r
213                 LOG(log_error, logtype_afpd, "Cannot recreate socket for fce UDP connection: errno %d", errno  );\r
214 \r
215                 udp_entry->next_try_on_error = now + FCE_SOCKET_RETRY_DELAY_S;\r
216                 continue;\r
217             }\r
218 \r
219             udp_entry->next_try_on_error = 0;\r
220 \r
221             /* Okay, we have a running socket again, send server that we had a problem on our side*/\r
222             data_len = build_fce_packet( &packet, "", FCE_CONN_BROKEN, 0 );\r
223 \r
224             sendto( udp_entry->sock, data, data_len, 0, &udp_entry->addr, sizeof(udp_entry->addr) );\r
225 \r
226             /* Rebuild our original data packet */\r
227             data_len = build_fce_packet( &packet, path, mode, event_id );\r
228         }\r
229 \r
230         sent_data =  sendto( udp_entry->sock, data, data_len, 0, &udp_entry->addr, sizeof(udp_entry->addr) );\r
231 \r
232         /* Problems ? */\r
233         if (sent_data != data_len)\r
234         {\r
235             /* Argh, socket broke, we close and retry later */\r
236             LOG(log_error, logtype_afpd, "Error while sending packet to %s for fce UDP connection: transfered: %d of %d errno %d",\r
237                     udp_entry->port, sent_data, data_len, errno  );\r
238 \r
239             close( udp_entry->sock );\r
240             udp_entry->sock = -1;\r
241             udp_entry->next_try_on_error = now + FCE_SOCKET_RETRY_DELAY_S;\r
242         }\r
243     }\r
244 }\r
245 \r
246 static int add_udp_socket( char *target_ip, int target_port )\r
247 {\r
248     if (target_port == 0)\r
249         target_port = FCE_DEFAULT_PORT;\r
250 \r
251     if (udp_sockets >= FCE_MAX_UDP_SOCKS)\r
252     {\r
253         LOG(log_error, logtype_afpd, "Too many file change api UDP connections (max %d allowed)", FCE_MAX_UDP_SOCKS );\r
254         return AFPERR_PARAM;\r
255     }\r
256 \r
257     strncpy( udp_socket_list[udp_sockets].ip, target_ip, FCE_MAX_IP_LEN - 1);\r
258     udp_socket_list[udp_sockets].port = target_port;\r
259     udp_socket_list[udp_sockets].sock = -1;\r
260     memset( &udp_socket_list[udp_sockets].addr, 0, sizeof(struct sockaddr_in) );\r
261     udp_socket_list[udp_sockets].next_try_on_error = 0;\r
262 \r
263     udp_sockets++;\r
264 \r
265     return AFP_OK;\r
266 }\r
267 \r
268 /*\r
269  *\r
270  * Dispatcher for all incoming file change events\r
271  *\r
272  * */\r
273 static int register_fce( char *u_name, int is_dir, int mode )\r
274 {\r
275     if (u_name == NULL)\r
276         return AFPERR_PARAM;\r
277 \r
278     static int first_event = FCE_TRUE;\r
279 \r
280         /* do some initialization on the fly the first time */\r
281         if (first_event)\r
282         {\r
283                 fce_initialize_history();\r
284         }\r
285 \r
286 \r
287         /* handle files which should not cause events (.DS_Store atc. ) */\r
288         for (int i = 0; skip_files[i] != NULL; i++)\r
289         {\r
290                 if (!strcmp( u_name, skip_files[i]))\r
291                         return AFP_OK;\r
292         }\r
293 \r
294 \r
295         char full_path_buffer[FCE_MAX_PATH_LEN + 1] = {""};\r
296         const char *cwd = getcwdpath();\r
297 \r
298         if (!is_dir || mode == FCE_DIR_DELETE)\r
299         {\r
300                 if (strlen( cwd ) + strlen( u_name) + 1 >= FCE_MAX_PATH_LEN)\r
301                 {\r
302                         LOG(log_error, logtype_afpd, "FCE file name too long: %s/%s", cwd, u_name );\r
303                         return AFPERR_PARAM;\r
304                 }\r
305                 sprintf( full_path_buffer, "%s/%s", cwd, u_name );\r
306         }\r
307         else\r
308         {\r
309                 if (strlen( cwd ) >= FCE_MAX_PATH_LEN)\r
310                 {\r
311                         LOG(log_error, logtype_afpd, "FCE directory name too long: %s", cwd);\r
312                         return AFPERR_PARAM;\r
313                 }\r
314                 strcpy( full_path_buffer, cwd);\r
315         }\r
316 \r
317         /* Can we ignore this event based on type or history? */\r
318         if (fce_handle_coalescation( full_path_buffer, is_dir, mode ))\r
319         {\r
320                 LOG(log_debug9, logtype_afpd, "Coalesced fc event <%d> for <%s>", mode, full_path_buffer );\r
321                 return AFP_OK;\r
322         }\r
323 \r
324         LOG(log_debug9, logtype_afpd, "Detected fc event <%d> for <%s>", mode, full_path_buffer );\r
325 \r
326 \r
327     /* we do initilization on the fly, no blocking calls in here \r
328      * (except when using FQDN in broken DNS environment)\r
329      */\r
330     if (first_event == FCE_TRUE)\r
331     {\r
332         fce_init_udp();\r
333         \r
334         /* Notify listeners the we start from the beginning */\r
335         send_fce_event( "", FCE_CONN_START );\r
336         \r
337         first_event = FCE_FALSE;\r
338     }\r
339 \r
340         /* Handle UDP transport */\r
341     send_fce_event( full_path_buffer, mode );\r
342 \r
343     return AFP_OK;\r
344 }\r
345 \r
346 \r
347 /******************** External calls start here **************************/\r
348 \r
349 /*\r
350  * API-Calls for file change api, called form outside (file.c directory.c ofork.c filedir.c)\r
351  * */\r
352 #ifndef FCE_TEST_MAIN\r
353 \r
354 \r
355 int fce_register_delete_file( struct path *path )\r
356 {\r
357     int ret = AFP_OK;\r
358 \r
359     if (path == NULL)\r
360         return AFPERR_PARAM;\r
361 \r
362         \r
363     ret = register_fce( path->u_name, FALSE, FCE_FILE_DELETE );\r
364 \r
365     return ret;\r
366 }\r
367 int fce_register_delete_dir( char *name )\r
368 {\r
369     int ret = AFP_OK;\r
370 \r
371     if (name == NULL)\r
372         return AFPERR_PARAM;\r
373 \r
374         \r
375     ret = register_fce( name, TRUE, FCE_DIR_DELETE);\r
376 \r
377     return ret;\r
378 }\r
379 \r
380 int fce_register_new_dir( struct path *path )\r
381 {\r
382     int ret = AFP_OK;\r
383 \r
384     if (path == NULL)\r
385         return AFPERR_PARAM;\r
386 \r
387     ret = register_fce( path->u_name, TRUE, FCE_DIR_CREATE );\r
388 \r
389     return ret;\r
390 }\r
391 \r
392 \r
393 int fce_register_new_file( struct path *path )\r
394 {\r
395     int ret = AFP_OK;\r
396 \r
397     if (path == NULL)\r
398         return AFPERR_PARAM;\r
399 \r
400     ret = register_fce( path->u_name, FALSE, FCE_FILE_CREATE );\r
401 \r
402     return ret;\r
403 }\r
404 \r
405 \r
406 int fce_register_file_modification( struct ofork *ofork )\r
407 {\r
408     char *u_name = NULL;\r
409     struct dir *dir;\r
410     struct vol *vol;\r
411     int ret = AFP_OK;\r
412 \r
413     if (ofork == NULL || ofork->of_vol == NULL || ofork->of_dir == NULL)\r
414         return AFPERR_PARAM;\r
415 \r
416     vol = ofork->of_vol;\r
417     dir = ofork->of_dir;\r
418 \r
419     if (NULL == (u_name = mtoupath(vol, of_name(ofork), dir->d_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, 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