--- /dev/null
+/*\r
+ * $Id: fce_api.c,v 0.01 2010-10-01 00:00:0 mw Exp $\r
+ *\r
+ * Copyright (c) 2010 Mark Williams\r
+ *\r
+ * File change event API for netatalk\r
+ *\r
+ * for every detected filesystem change a UDP packet is sent to an arbitrary list\r
+ * of listeners. Each packet contains unix path of modified filesystem element,\r
+ * event reason, and a consecutive event id (32 bit). Technically we are UDP client and are sending\r
+ * out packets synchronuosly as they are created by the afp functions. This should not affect\r
+ * performance measurably. The only delaying calls occur during initialization, if we have to\r
+ * resolve non-IP hostnames to IP. All numeric data inside the packet is network byte order, so use\r
+ * ntohs / ntohl to resolve length and event id. Ideally a listener receives every packet with\r
+ * no gaps in event ids, starting with event id 1 and mode FCE_CONN_START followed by\r
+ * data events from id 2 up to 0xFFFFFFFF, followed by 0 to 0xFFFFFFFF and so on.\r
+ *\r
+ * A gap or not starting with 1 mode FCE_CONN_START or receiving mode FCE_CONN_BROKEN means that\r
+ * the listener has lost at least one filesystem event\r
+ * \r
+ * All Rights Reserved. See COPYRIGHT.\r
+ */\r
+\r
+#ifdef HAVE_CONFIG_H\r
+#include "config.h"\r
+#endif /* HAVE_CONFIG_H */\r
+\r
+#include <stdio.h>\r
+\r
+#include <string.h>\r
+#include <stdlib.h>\r
+#include <errno.h>\r
+#include <time.h>\r
+\r
+\r
+#include <sys/param.h>\r
+#include <sys/socket.h>\r
+#include <netinet/in.h>\r
+#include <arpa/inet.h>\r
+#include <netdb.h>\r
+\r
+#include <netatalk/at.h>\r
+\r
+#include <atalk/adouble.h>\r
+#include <atalk/vfs.h>\r
+#include <atalk/logger.h>\r
+#include <atalk/afp.h>\r
+#include <atalk/util.h>\r
+#include <atalk/cnid.h>\r
+#include <atalk/unix.h>\r
+\r
+#include "fork.h"\r
+#include "file.h"\r
+#include "globals.h"\r
+#include "directory.h"\r
+#include "desktop.h"\r
+#include "volume.h"\r
+\r
+#include "fce_api.h"\r
+\r
+// ONLY USED IN THIS FILE\r
+#include "fce_api_internal.h"\r
+\r
+#define FCE_TRUE 1\r
+#define FCE_FALSE 0\r
+\r
+/* We store our connection data here */\r
+static struct udp_entry udp_socket_list[FCE_MAX_UDP_SOCKS];\r
+static int udp_sockets = 0;\r
+static int udp_initialized = FCE_FALSE;\r
+\r
+\r
+static const char *skip_files[] = \r
+{\r
+ ".DS_Store",\r
+ NULL\r
+};\r
+\r
+/*\r
+ *\r
+ * Initialize network structs for any listeners\r
+ * We dont give return code because all errors are handled internally (I hope..)\r
+ *\r
+ * */\r
+void fce_init_udp()\r
+{\r
+ if (udp_initialized == FCE_TRUE)\r
+ return;\r
+\r
+\r
+ for (int i = 0; i < udp_sockets; i++)\r
+ {\r
+ struct udp_entry *udp_entry = udp_socket_list + i;\r
+\r
+ /* Close any pending sockets */\r
+ if (udp_entry->sock != -1)\r
+ {\r
+ close( udp_entry->sock );\r
+ }\r
+\r
+ /* resolve IP to network address */\r
+ if (inet_aton( udp_entry->ip, &udp_entry->addr.sin_addr ) ==0 )\r
+ {\r
+ /* Hmm, failed try to resolve host */\r
+ struct hostent *hp = gethostbyname( udp_entry->ip );\r
+ if (hp == NULL)\r
+ {\r
+ LOG(log_error, logtype_afpd, "Cannot resolve host name for fce UDP connection: %s (errno %d)", udp_entry->ip, errno );\r
+ continue;\r
+ }\r
+ memcpy( &udp_entry->addr.sin_addr, &hp->h_addr, sizeof(udp_entry->addr.sin_addr) );\r
+ }\r
+\r
+ /* Create UDP socket */\r
+ udp_entry->sock = socket( AF_INET, SOCK_DGRAM, 0 );\r
+ if (udp_entry->sock == -1)\r
+ {\r
+ LOG(log_error, logtype_afpd, "Cannot create socket for fce UDP connection: errno %d", errno );\r
+ continue;\r
+ }\r
+\r
+ /* Set socket address params */\r
+ udp_entry->addr.sin_family = AF_INET;\r
+ udp_entry->addr.sin_port = htons(udp_entry->port);\r
+ }\r
+ udp_initialized = FCE_TRUE;\r
+\r
+}\r
+void fce_cleanup()\r
+{\r
+ if (udp_initialized == FCE_FALSE )\r
+ return;\r
+\r
+ for (int i = 0; i < udp_sockets; i++)\r
+ {\r
+ struct udp_entry *udp_entry = udp_socket_list + i;\r
+\r
+ /* Close any pending sockets */\r
+ if (udp_entry->sock != -1)\r
+ {\r
+ close( udp_entry->sock );\r
+ udp_entry->sock = -1;\r
+ }\r
+ }\r
+ udp_initialized = FCE_FALSE;\r
+}\r
+\r
+\r
+/*\r
+ * Construct a UDP packet for our listeners and return packet size\r
+ * */\r
+static unsigned short build_fce_packet( struct fce_packet *packet, char *path, int mode, uint32_t event_id )\r
+{\r
+ unsigned short data_len = 0;\r
+\r
+ strncpy(packet->magic, FCE_PACKET_MAGIC, sizeof(packet->magic) );\r
+ packet->version = FCE_PACKET_VERSION;\r
+ packet->mode = mode;\r
+\r
+ data_len = strlen( path );\r
+\r
+ /* This should never happen, but before we bust this server, we send nonsense, fce listener has to cope */\r
+ if (data_len >= FCE_MAX_PATH_LEN)\r
+ {\r
+ data_len = FCE_MAX_PATH_LEN - 1;\r
+ }\r
+\r
+ /* This is the payload len. Means: the stream has len bytes more until packet is finished */\r
+ /* A server should read the first 16 byte, decode them and then fetch the rest */\r
+ packet->len = htons( data_len);\r
+ packet->event_id = htonl( event_id );\r
+\r
+ strncpy( packet->data, path, data_len );\r
+\r
+ /* return the packet len */\r
+ return sizeof(struct fce_packet) - FCE_MAX_PATH_LEN + data_len;\r
+}\r
+\r
+/*\r
+ * Send the fce information to all (connected) listeners\r
+ * We dont give return code because all errors are handled internally (I hope..)\r
+ * */\r
+static void send_fce_event( char *path, int mode )\r
+{ \r
+ struct fce_packet packet;\r
+ void *data = &packet;\r
+ 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
+\r
+ time_t now = time(NULL);\r
+\r
+ /* build our data packet */\r
+ int data_len = build_fce_packet( &packet, path, mode, ++event_id );\r
+\r
+\r
+ for (int i = 0; i < udp_sockets; i++)\r
+ {\r
+ int sent_data = 0;\r
+ struct udp_entry *udp_entry = udp_socket_list + i;\r
+\r
+ /* we had a problem earlier ? */\r
+ if (udp_entry->sock == -1)\r
+ {\r
+ /* We still have to wait ?*/\r
+ if (now < udp_entry->next_try_on_error)\r
+ continue;\r
+\r
+ /* Reopen socket */\r
+ udp_entry->sock = socket( AF_INET, SOCK_DGRAM, 0 );\r
+\r
+ if (udp_entry->sock == -1)\r
+ {\r
+ /* failed again, so go to rest again */\r
+ LOG(log_error, logtype_afpd, "Cannot recreate socket for fce UDP connection: errno %d", errno );\r
+\r
+ udp_entry->next_try_on_error = now + FCE_SOCKET_RETRY_DELAY_S;\r
+ continue;\r
+ }\r
+\r
+ udp_entry->next_try_on_error = 0;\r
+\r
+ /* Okay, we have a running socket again, send server that we had a problem on our side*/\r
+ data_len = build_fce_packet( &packet, "", FCE_CONN_BROKEN, 0 );\r
+\r
+ sendto( udp_entry->sock, data, data_len, 0, &udp_entry->addr, sizeof(udp_entry->addr) );\r
+\r
+ /* Rebuild our original data packet */\r
+ data_len = build_fce_packet( &packet, path, mode, event_id );\r
+ }\r
+\r
+ sent_data = sendto( udp_entry->sock, data, data_len, 0, &udp_entry->addr, sizeof(udp_entry->addr) );\r
+\r
+ /* Problems ? */\r
+ if (sent_data != data_len)\r
+ {\r
+ /* Argh, socket broke, we close and retry later */\r
+ LOG(log_error, logtype_afpd, "Error while sending packet to %s for fce UDP connection: transfered: %d of %d errno %d",\r
+ udp_entry->port, sent_data, data_len, errno );\r
+\r
+ close( udp_entry->sock );\r
+ udp_entry->sock = -1;\r
+ udp_entry->next_try_on_error = now + FCE_SOCKET_RETRY_DELAY_S;\r
+ }\r
+ }\r
+}\r
+\r
+static int add_udp_socket( char *target_ip, int target_port )\r
+{\r
+ if (target_port == 0)\r
+ target_port = FCE_DEFAULT_PORT;\r
+\r
+ if (udp_sockets >= FCE_MAX_UDP_SOCKS)\r
+ {\r
+ LOG(log_error, logtype_afpd, "Too many file change api UDP connections (max %d allowed)", FCE_MAX_UDP_SOCKS );\r
+ return AFPERR_PARAM;\r
+ }\r
+\r
+ strncpy( udp_socket_list[udp_sockets].ip, target_ip, FCE_MAX_IP_LEN - 1);\r
+ udp_socket_list[udp_sockets].port = target_port;\r
+ udp_socket_list[udp_sockets].sock = -1;\r
+ memset( &udp_socket_list[udp_sockets].addr, 0, sizeof(struct sockaddr_in) );\r
+ udp_socket_list[udp_sockets].next_try_on_error = 0;\r
+\r
+ udp_sockets++;\r
+\r
+ return AFP_OK;\r
+}\r
+\r
+/*\r
+ *\r
+ * Dispatcher for all incoming file change events\r
+ *\r
+ * */\r
+static int register_fce( char *u_name, int is_dir, int mode )\r
+{\r
+ if (u_name == NULL)\r
+ return AFPERR_PARAM;\r
+\r
+ static int first_event = FCE_TRUE;\r
+\r
+ /* do some initialization on the fly the first time */\r
+ if (first_event)\r
+ {\r
+ fce_initialize_history();\r
+ }\r
+\r
+\r
+ /* handle files which should not cause events (.DS_Store atc. ) */\r
+ for (int i = 0; skip_files[i] != NULL; i++)\r
+ {\r
+ if (!strcmp( u_name, skip_files[i]))\r
+ return AFP_OK;\r
+ }\r
+\r
+\r
+ char full_path_buffer[FCE_MAX_PATH_LEN + 1] = {""};\r
+ const char *cwd = getcwdpath();\r
+\r
+ if (!is_dir || mode == FCE_DIR_DELETE)\r
+ {\r
+ if (strlen( cwd ) + strlen( u_name) + 1 >= FCE_MAX_PATH_LEN)\r
+ {\r
+ LOG(log_error, logtype_afpd, "FCE file name too long: %s/%s", cwd, u_name );\r
+ return AFPERR_PARAM;\r
+ }\r
+ sprintf( full_path_buffer, "%s/%s", cwd, u_name );\r
+ }\r
+ else\r
+ {\r
+ if (strlen( cwd ) >= FCE_MAX_PATH_LEN)\r
+ {\r
+ LOG(log_error, logtype_afpd, "FCE directory name too long: %s", cwd);\r
+ return AFPERR_PARAM;\r
+ }\r
+ strcpy( full_path_buffer, cwd);\r
+ }\r
+\r
+ /* Can we ignore this event based on type or history? */\r
+ if (fce_handle_coalescation( full_path_buffer, is_dir, mode ))\r
+ {\r
+ LOG(log_debug9, logtype_afpd, "Coalesced fc event <%d> for <%s>", mode, full_path_buffer );\r
+ return AFP_OK;\r
+ }\r
+\r
+ LOG(log_debug9, logtype_afpd, "Detected fc event <%d> for <%s>", mode, full_path_buffer );\r
+\r
+\r
+ /* we do initilization on the fly, no blocking calls in here \r
+ * (except when using FQDN in broken DNS environment)\r
+ */\r
+ if (first_event == FCE_TRUE)\r
+ {\r
+ fce_init_udp();\r
+ \r
+ /* Notify listeners the we start from the beginning */\r
+ send_fce_event( "", FCE_CONN_START );\r
+ \r
+ first_event = FCE_FALSE;\r
+ }\r
+\r
+ /* Handle UDP transport */\r
+ send_fce_event( full_path_buffer, mode );\r
+\r
+ return AFP_OK;\r
+}\r
+\r
+\r
+/******************** External calls start here **************************/\r
+\r
+/*\r
+ * API-Calls for file change api, called form outside (file.c directory.c ofork.c filedir.c)\r
+ * */\r
+#ifndef FCE_TEST_MAIN\r
+\r
+\r
+int fce_register_delete_file( struct path *path )\r
+{\r
+ int ret = AFP_OK;\r
+\r
+ if (path == NULL)\r
+ return AFPERR_PARAM;\r
+\r
+ \r
+ ret = register_fce( path->u_name, FALSE, FCE_FILE_DELETE );\r
+\r
+ return ret;\r
+}\r
+int fce_register_delete_dir( char *name )\r
+{\r
+ int ret = AFP_OK;\r
+\r
+ if (name == NULL)\r
+ return AFPERR_PARAM;\r
+\r
+ \r
+ ret = register_fce( name, TRUE, FCE_DIR_DELETE);\r
+\r
+ return ret;\r
+}\r
+\r
+int fce_register_new_dir( struct path *path )\r
+{\r
+ int ret = AFP_OK;\r
+\r
+ if (path == NULL)\r
+ return AFPERR_PARAM;\r
+\r
+ ret = register_fce( path->u_name, TRUE, FCE_DIR_CREATE );\r
+\r
+ return ret;\r
+}\r
+\r
+\r
+int fce_register_new_file( struct path *path )\r
+{\r
+ int ret = AFP_OK;\r
+\r
+ if (path == NULL)\r
+ return AFPERR_PARAM;\r
+\r
+ ret = register_fce( path->u_name, FALSE, FCE_FILE_CREATE );\r
+\r
+ return ret;\r
+}\r
+\r
+\r
+int fce_register_file_modification( struct ofork *ofork )\r
+{\r
+ char *u_name = NULL;\r
+ struct dir *dir;\r
+ struct vol *vol;\r
+ int ret = AFP_OK;\r
+\r
+ if (ofork == NULL || ofork->of_vol == NULL || ofork->of_dir == NULL)\r
+ return AFPERR_PARAM;\r
+\r
+ vol = ofork->of_vol;\r
+ dir = ofork->of_dir;\r
+\r
+ if (NULL == (u_name = mtoupath(vol, of_name(ofork), dir->d_did, utf8_encoding()))) \r
+ {\r
+ return AFPERR_MISC;\r
+ }\r
+ \r
+ ret = register_fce( u_name, FALSE, FCE_FILE_MODIFY );\r
+ \r
+ return ret; \r
+}\r
+#endif\r
+\r
+/*\r
+ *\r
+ * Extern connect to afpd parameter, can be called multiple times for multiple listeners (up to MAX_UDP_SOCKS times)\r
+ *\r
+ * */\r
+int fce_add_udp_socket( char *target )\r
+{\r
+ int port = FCE_DEFAULT_PORT;\r
+ char target_ip[256] = {""};\r
+\r
+ strncpy( target_ip, target, sizeof(target_ip) -1);\r
+ char *port_delim = strchr( target_ip, ':' );\r
+ if (port_delim)\r
+ {\r
+ *port_delim = 0;\r
+ port = atoi( port_delim + 1);\r
+ }\r
+ return add_udp_socket( target_ip, port );\r
+}\r
+\r
+\r
+\r
+#ifdef FCE_TEST_MAIN\r
+\r
+\r
+void shortsleep( unsigned int us )\r
+{ \r
+ usleep( us );\r
+}\r
+int main( int argc, char*argv[] )\r
+{\r
+ int port = 11250;\r
+ char *host = NULL;\r
+ int delay_between_events = 1000;\r
+ int event_code = FCE_FILE_MODIFY;\r
+ char pathbuff[1024];\r
+ int duration_in_seconds = 0; // TILL ETERNITY\r
+\r
+ char *path = getcwd( pathbuff, sizeof(pathbuff) );\r
+\r
+ // FULLSPEED TEST IS "-s 1001" -> delay is 0 -> send packets without pause\r
+\r
+ if (argc == 1)\r
+ {\r
+ 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
+ exit( 1 );\r
+ }\r
+ int ret = AFP_OK;\r
+\r
+ for (int i = 1; i < argc; i++)\r
+ {\r
+ char *p = argv[i];\r
+ if (*p == '-' && p[1])\r
+ {\r
+ char *arg = argv[i + 1];\r
+ switch (p[1])\r
+ {\r
+ case 'p': if (arg) port = atoi( arg ), i++; break;\r
+ case 'P': if (arg) path = arg, i++; break;\r
+ case 's': if (arg) delay_between_events = atoi( arg ), i++; break;\r
+ case 'e': if (arg) event_code = atoi( arg ), i++; break;\r
+ case 'd': if (arg) duration_in_seconds = atoi( arg ), i++; break;\r
+ case 'h':\r
+ {\r
+ if (arg)\r
+ {\r
+ host = arg;\r
+ char target[256];\r
+ sprintf( target, "%s:%d", host, port );\r
+ ret += fce_add_udp_socket( target );\r
+ i++;\r
+ }\r
+ break;\r
+ } \r
+ }\r
+ }\r
+ }\r
+ \r
+\r
+ if (host == NULL)\r
+ {\r
+ char target[256];\r
+ sprintf( target, "127.0.0.1:%d", port );\r
+ ret += fce_add_udp_socket( target );\r
+ }\r
+\r
+ if (ret)\r
+ return ret;\r
+\r
+\r
+ int ev_cnt = 0;\r
+ time_t start_time = time(NULL);\r
+ time_t end_time = 0;\r
+\r
+ if (duration_in_seconds)\r
+ end_time = start_time + duration_in_seconds;\r
+\r
+ while (1)\r
+ {\r
+ time_t now = time(NULL);\r
+ if (now > start_time)\r
+ {\r
+ start_time = now;\r
+ fprintf( stdout, "%d events/s\n", ev_cnt );\r
+ ev_cnt = 0;\r
+ }\r
+ if (end_time && now >= end_time)\r
+ break;\r
+\r
+ register_fce( path, event_code );\r
+ ev_cnt++;\r
+\r
+ \r
+ shortsleep( delay_between_events );\r
+ }\r
+}\r
+#endif /* TESTMAIN*/\r
--- /dev/null
+/*\r
+ * $Id: fce_api.c,v 0.01 2010-10-01 00:00:0 mw Exp $\r
+ *\r
+ * Copyright (c) 2010 Mark Williams\r
+ *\r
+ * File change event API for netatalk\r
+ *\r
+ * for every detected filesystem change a UDP packet is sent to an arbitrary list\r
+ * of listeners. Each packet contains unix path of modified filesystem element,\r
+ * event reason, and a consecutive event id (32 bit). Technically we are UDP client and are sending\r
+ * out packets synchronuosly as they are created by the afp functions. This should not affect\r
+ * performance measurably. The only delaying calls occur during initialization, if we have to\r
+ * resolve non-IP hostnames to IP. All numeric data inside the packet is network byte order, so use\r
+ * ntohs / ntohl to resolve length and event id. Ideally a listener receives every packet with\r
+ * no gaps in event ids, starting with event id 1 and mode FCE_CONN_START followed by\r
+ * data events from id 2 up to 0xFFFFFFFF, followed by 0 to 0xFFFFFFFF and so on.\r
+ *\r
+ * A gap or not starting with 1 mode FCE_CONN_START or receiving mode FCE_CONN_BROKEN means that\r
+ * the listener has lost at least one filesystem event\r
+ * \r
+ * All Rights Reserved. See COPYRIGHT.\r
+ */\r
+\r
+#ifdef HAVE_CONFIG_H\r
+#include "config.h"\r
+#endif /* HAVE_CONFIG_H */\r
+\r
+#include <stdio.h>\r
+\r
+#include <string.h>\r
+#include <stdlib.h>\r
+#include <errno.h>\r
+#include <time.h>\r
+\r
+\r
+#include <sys/param.h>\r
+#include <sys/socket.h>\r
+#include <netinet/in.h>\r
+#include <arpa/inet.h>\r
+#include <netdb.h>\r
+\r
+#include <netatalk/at.h>\r
+\r
+#include <atalk/adouble.h>\r
+#include <atalk/vfs.h>\r
+#include <atalk/logger.h>\r
+#include <atalk/afp.h>\r
+#include <atalk/util.h>\r
+#include <atalk/cnid.h>\r
+#include <atalk/unix.h>\r
+\r
+#include "fork.h"\r
+#include "file.h"\r
+#include "globals.h"\r
+#include "directory.h"\r
+#include "desktop.h"\r
+#include "volume.h"\r
+\r
+#include "fce_api.h"\r
+\r
+// ONLY USED IN THIS FILE\r
+#include "fce_api_internal.h"\r
+\r
+#define FCE_TRUE 1\r
+#define FCE_FALSE 0\r
+\r
+/* We store our connection data here */\r
+static char coalesce[80] = {""};\r
+static struct fce_history fce_history_list[FCE_HISTORY_LEN];\r
+\r
+\r
+\r
+\r
+/****\r
+* With coalesce we try to reduce the events over UDP, the eventlistener would throw these \r
+* events away anyway.\r
+* This works only, if the connected listener uses the events on a "per directory" base\r
+* It is a very simple aproach, but saves a lot of events sent to listeners.\r
+* Every "child element" event is ignored as long as its parent event is not older \r
+* than MAX_COALESCE_TIME_MS ms. If large directory trees or large files are created or deleted, \r
+* this probably will not work recursive, because the time to copy data will exceed this \r
+* event timeout. \r
+* \r
+****/\r
+static int coalesce_none()\r
+{\r
+ return coalesce[0] == 0;\r
+}\r
+static int coalesce_all()\r
+{\r
+ return !strcmp( coalesce, "all" );\r
+}\r
+static int coalesce_create()\r
+{\r
+ return !strcmp( coalesce, "create" ) || coalesce_all();\r
+}\r
+static int coalesce_delete()\r
+{\r
+ return !strcmp( coalesce, "delete" ) || coalesce_all();\r
+}\r
+\r
+void fce_initialize_history()\r
+{\r
+ for (int i = 0; i < FCE_HISTORY_LEN; i++)\r
+ {\r
+ memset( &fce_history_list[i], 0, sizeof(fce_history_list[i]) );\r
+ }\r
+}\r
+\r
+static long get_ms_difftime ( struct timeval *tv1, struct timeval *tv2 )\r
+{\r
+ unsigned long s = tv2->tv_sec - tv1->tv_sec;\r
+ long us = tv2->tv_usec - tv1->tv_usec;\r
+\r
+ return s * 1000 + us/1000;\r
+}\r
+\r
+int fce_handle_coalescation( char *path, int is_dir, int mode )\r
+{\r
+ if (coalesce_none())\r
+ return FALSE;\r
+\r
+ \r
+\r
+ // First one:\r
+ // After a file creation *ALWAYS* a file modification is produced\r
+ if (mode == FCE_FILE_CREATE)\r
+ {\r
+ if (coalesce_create())\r
+ {\r
+ return TRUE;\r
+ }\r
+ }\r
+\r
+ /* get timestamp */\r
+ struct timeval tv;\r
+ gettimeofday(&tv, 0);\r
+\r
+\r
+ /* These two are used to eval our next index in history */\r
+ /* the history is unsorted, speed should not be a problem, length is 10 */\r
+ unsigned long oldest_entry = (unsigned long )((long)-1);\r
+ int oldest_entry_idx = -1;\r
+\r
+ /* Now detect events in the very near history */\r
+ for (int i = 0; i < FCE_HISTORY_LEN; i++)\r
+ {\r
+ struct fce_history *fh = &fce_history_list[i];\r
+\r
+ //* Not inited ? */\r
+ if (fh->tv.tv_sec == 0)\r
+ {\r
+ /* we can use it for new elements */\r
+ oldest_entry = 0;\r
+ oldest_entry_idx = i;\r
+ continue;\r
+ }\r
+\r
+ //* Too old ? */\r
+ if (get_ms_difftime( &fh->tv, &tv ) > MAX_COALESCE_TIME_MS)\r
+ {\r
+ /* Invalidate entry */\r
+ fh->tv.tv_sec = 0;\r
+\r
+ oldest_entry = 0;\r
+ oldest_entry_idx = i; \r
+ continue;\r
+ }\r
+\r
+\r
+ /* If we find a parent dir wich was created we are done */\r
+ if (coalesce_create() && fh->mode == FCE_DIR_CREATE)\r
+ {\r
+ //* Parent dir ? */\r
+ if (!strncmp( fh->path, path, strlen( fh->path ) ) )\r
+ {\r
+ return TRUE;\r
+ }\r
+ }\r
+\r
+ /* If we find a parent dir we should be DELETED we are done */\r
+ if (coalesce_delete() && fh->is_dir && (mode == FCE_FILE_DELETE || mode == FCE_DIR_DELETE))\r
+ {\r
+ //* Parent dir ? */\r
+ if (!strncmp( fh->path, path, strlen( fh->path ) ) )\r
+ {\r
+ return TRUE;\r
+ }\r
+ }\r
+\r
+ //* Detect oldest entry for next new entry */\r
+ if (oldest_entry_idx == -1 || fh->tv.tv_sec < oldest_entry)\r
+ {\r
+ oldest_entry = fh->tv.tv_sec;\r
+ oldest_entry_idx = i;\r
+ }\r
+ }\r
+\r
+ /* We have a new entry for the history, register it */\r
+ fce_history_list[oldest_entry_idx].tv = tv;\r
+ fce_history_list[oldest_entry_idx].mode = mode;\r
+ fce_history_list[oldest_entry_idx].is_dir = is_dir;\r
+ strncpy( fce_history_list[oldest_entry_idx].path, path, FCE_MAX_PATH_LEN );\r
+\r
+ /* we have to handle this event */\r
+ return FALSE;\r
+\r
+}\r
+\r
+\r
+\r
+/*\r
+ *\r
+ * Set event coalescation to reduce number of events sent over UDP \r
+ * all|delete|create\r
+ *\r
+ *\r
+ * */\r
+\r
+int fce_set_coalesce( char *coalesce_opt )\r
+{\r
+ strncpy( coalesce, coalesce_opt, sizeof(coalesce) - 1 ); \r
+}\r
+\r
+\r
+\r
+\r