]> arthur.barton.de Git - netatalk.git/commitdiff
Import FCE
authorFrank Lahm <franklahm@googlemail.com>
Tue, 24 May 2011 12:24:41 +0000 (14:24 +0200)
committerFrank Lahm <franklahm@googlemail.com>
Tue, 24 May 2011 12:24:41 +0000 (14:24 +0200)
etc/afpd/afp_options.c
etc/afpd/directory.c
etc/afpd/fce_api.c [new file with mode: 0755]
etc/afpd/fce_api.h [new file with mode: 0755]
etc/afpd/fce_api_internal.h [new file with mode: 0755]
etc/afpd/fce_util.c [new file with mode: 0755]
etc/afpd/file.c
etc/afpd/filedir.c
etc/afpd/fork.c
etc/afpd/fork.h
etc/afpd/ofork.c

index 057a1a32954164ef13330e27398273d27c65c9e9..ff807404b36df7955379934ff0738544446bf3da 100644 (file)
@@ -47,6 +47,7 @@ char *strchr (), *strrchr ();
 #include "globals.h"
 #include "status.h"
 #include "auth.h"
+#include "fce_api.h"
 
 #include <atalk/compat.h>
 
@@ -448,6 +449,16 @@ int afp_options_parseline(char *buf, struct afp_options *options)
     if ((c = getoption(buf, "-ntseparator")) && (opt = strdup(c)))
        options->ntseparator = opt;
 
+       if ((c = getoption(buf, "-fcelistener"))) {
+               LOG(log_note, logtype_afpd, "Adding fce listener \"%s\"", c);
+               fce_add_udp_socket(c);
+       }
+       if ((c = getoption(buf, "-fcecoalesce"))) {
+               LOG(log_ote, logtype_afpd, "Fce coalesce: %s", c);
+               fce_set_coalesce(c);
+       }
+
+
     return 1;
 }
 
index 0e13a162805ed81f3c3fbb0c55f94aedf16cf38f..4544d28e153084897194e44f1286c0952f501aba 100644 (file)
@@ -57,6 +57,7 @@ char *strchr (), *strrchr ();
 #include "unix.h"
 #include "mangle.h"
 #include "hash.h"
+#include "fce_api.h"
 
 #ifdef HAVE_NFSv4_ACLS
 extern void addir_inherit_acl(const struct vol *vol);
@@ -2530,6 +2531,8 @@ int afp_createdir(AFPObj *obj, char *ibuf, size_t ibuflen _U_, char *rbuf, size_
     ad_setname(&ad, s_path->m_name);
     ad_setid( &ad, s_path->st.st_dev, s_path->st.st_ino, dir->d_did, did, vol->v_stamp);
 
+    fce_register_new_dir(s_path);
+
     ad_flush( &ad);
     ad_close_metadata( &ad);
 
diff --git a/etc/afpd/fce_api.c b/etc/afpd/fce_api.c
new file mode 100755 (executable)
index 0000000..6443bf3
--- /dev/null
@@ -0,0 +1,546 @@
+/*\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
diff --git a/etc/afpd/fce_api.h b/etc/afpd/fce_api.h
new file mode 100755 (executable)
index 0000000..9d896f6
--- /dev/null
@@ -0,0 +1,28 @@
+/* 
+ * File:   fce_api.h
+ * Author: mw
+ *
+ * Created on 1. Oktober 2010, 21:35
+ *
+ * API calls for file change event api
+ */
+
+#ifndef _FCE_API_H
+#define        _FCE_API_H
+
+struct path;
+struct ofork;
+int fce_register_delete_file( struct path *path );
+int fce_register_delete_dir( char *name );
+int fce_register_new_dir( struct path *path );
+int fce_register_new_file( struct path *path );
+int fce_register_file_modification( struct ofork *ofork );
+
+int fce_add_udp_socket( char *target );  // IP or IP:Port
+int fce_set_coalesce( char *coalesce_opt ); // all|delete|create
+
+#define FCE_DEFAULT_PORT 12250\r
+
+
+#endif /* _FCE_API_H */
+
diff --git a/etc/afpd/fce_api_internal.h b/etc/afpd/fce_api_internal.h
new file mode 100755 (executable)
index 0000000..959ce9e
--- /dev/null
@@ -0,0 +1,71 @@
+/* \r
+ * File:   fce_api_internal.h\r
+ * Author: mw\r
+ *\r
+ * Created on 1. Oktober 2010, 23:48\r
+ */\r
+\r
+#ifndef _FCE_API_INTERNAL_H\r
+#define        _FCE_API_INTERNAL_H\r
+\r
+#define FCE_FILE_MODIFY     1\r
+#define FCE_FILE_DELETE     2\r
+#define FCE_DIR_DELETE      3\r
+#define FCE_FILE_CREATE     4\r
+#define FCE_DIR_CREATE      5\r
+#define FCE_CONN_START     42\r
+#define FCE_CONN_BROKEN    99\r
+\r
+\r
+#define FCE_MAX_PATH_LEN 1024\r
+\r
+#define FCE_MAX_UDP_SOCKS 5     /* Allow a maximum of udp listeners for file change events */\r
+#define FCE_MAX_IP_LEN 255      /* Man len of listener name */\r
+#define FCE_SOCKET_RETRY_DELAY_S 600 /* Pause this time in s after socket was broken */\r
+#define FCE_PACKET_VERSION  1\r
+\r
+\r
+#define FCE_PACKET_MAGIC  "at_fcapi" /* Must fit to size of fce_packet.fce_magic */\r
+\r
+#define FCE_HISTORY_LEN 10  /* This is used to coalesce events */\r
+#define MAX_COALESCE_TIME_MS 1000  /* Events oldeer than this are not coalesced */\r
+\r
+struct udp_entry\r
+{\r
+    int sock;\r
+    char ip[FCE_MAX_IP_LEN];\r
+    int port;\r
+    struct sockaddr_in addr;\r
+    time_t next_try_on_error;      /* In case of error set next timestamp to retry */\r
+};\r
+\r
+/* This packet goes over the network, so we want to\r
+ *  be shure about datastructs and type sizes between platforms\r
+ */\r
+struct fce_packet\r
+{\r
+    char magic[8];\r
+    unsigned char version;\r
+    unsigned char mode;\r
+    uint16_t len;  /* network byte order */\r
+    uint32_t event_id; /* network byte order */\r
+    char data[FCE_MAX_PATH_LEN];\r
+};\r
+\r
+struct fce_history\r
+{\r
+    unsigned char mode;\r
+       int is_dir;\r
+       char path[FCE_MAX_PATH_LEN + 1];\r
+       struct timeval tv;\r
+};\r
+\r
+\r
+#define PACKET_HDR_LEN (sizeof(struct fce_packet) - FCE_MAX_PATH_LEN)\r
+\r
+int fce_handle_coalescation( char *path, int is_dir, int mode );\r
+void fce_initialize_history();\r
+\r
+\r
+#endif /* _FCE_API_INTERNAL_H */\r
+\r
diff --git a/etc/afpd/fce_util.c b/etc/afpd/fce_util.c
new file mode 100755 (executable)
index 0000000..8a89e24
--- /dev/null
@@ -0,0 +1,227 @@
+/*\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
index b10330c8aebaeef678a449f46e432988c43b841a..4aea84dde1dcfc59bdbb56db2fd2c4ba96a44f25 100644 (file)
@@ -48,6 +48,7 @@ char *strchr (), *strrchr ();
 #include "filedir.h"
 #include "globals.h"
 #include "unix.h"
+#include "fce_api.h"
 
 /* the format for the finderinfo fields (from IM: Toolbox Essentials):
  * field         bytes        subfield    bytes
@@ -704,6 +705,9 @@ int afp_createfile(AFPObj *obj, char *ibuf, size_t ibuflen _U_, char *rbuf _U_,
     path = s_path->m_name;
     ad_setname(adp, path);
     ad_flush( adp);
+
+    fce_register_new_file(s_path);
+
     ad_close( adp, ADFLAGS_DF|ADFLAGS_HF );
 
 createfile_done:
index 3e5cb2f5ee8048597a75d0552c971f7c769821f9..469586966ea53680094d79e180aade23f09ad828 100644 (file)
@@ -48,6 +48,7 @@ char *strchr (), *strrchr ();
 #include "globals.h"
 #include "filedir.h"
 #include "unix.h"
+#include "fce_api.h"
 
 #ifdef DROPKLUDGE
 int matchfile2dirperms(
@@ -562,8 +563,15 @@ int afp_delete(AFPObj *obj, char *ibuf, size_t ibuflen _U_, char *rbuf _U_, size
        if (*s_path->m_name != '\0') {
            rc = AFPERR_ACCESS;
        }
-       else {
-            rc = deletecurdir( vol);
+       else 
+               {
+
+                       /* we have to cache this, the structs are lost in deletcurdir*/
+                       /* but we need the positive returncode to send our event */
+                       char dname[256];
+                       strncpy(dname,  curdir->d_u_name, 255 );
+            if ((rc = deletecurdir(vol)) == AFP_OK)
+                               fce_register_delete_dir(dname);
         }
     } else if (of_findname(s_path)) {
         rc = AFPERR_BUSY;
@@ -574,9 +582,9 @@ int afp_delete(AFPObj *obj, char *ibuf, size_t ibuflen _U_, char *rbuf _U_, size
          */
         if (s_path->st_valid && s_path->st_errno == ENOENT) {
             rc = AFPERR_NOOBJ;
-        }
-        else {
-            rc = deletefile(vol, -1, upath, 1);
+        } else {
+            if ((rc = deletefile(vol, -1, upath, 1)) === AFP_OK)
+                               fce_register_delete_file( s_path );
         }
     }
     if ( rc == AFP_OK ) {
index b9d03507e6b249e427dab70e8a6b6643388d3092..78d965ee50fc98c18c9c2c0a7328998f28fc13a2 100644 (file)
@@ -1336,6 +1336,9 @@ static int write_fork(AFPObj *obj, char *ibuf, size_t ibuflen _U_, char *rbuf, s
     if ( ad_meta_fileno( ofork->of_ad ) != -1 ) /* META */
         ofork->of_flags |= AFPFORK_DIRTY;
 
+    /* we have modified any fork, remember until close_fork */
+    ofork->of_flags |= AFPFORK_MODIFIED;
+
     *rbuflen = set_off_t (offset, rbuf, is64);
     return( AFP_OK );
 
index 5a42afc71dc202492c3ca875876b913a5f944a86..c4e25de1696faee8f57f90d93590ce7f38515906 100644 (file)
@@ -50,6 +50,7 @@ struct ofork {
 #define AFPFORK_ACCRD   (1<<4)
 #define AFPFORK_ACCWR   (1<<5)
 #define AFPFORK_ACCMASK (AFPFORK_ACCRD | AFPFORK_ACCWR)
+#define AFPFORK_MODIFIED (1<<6) /* used in FCE for modified files */
 
 #ifdef AFS
 extern struct ofork *writtenfork;
index 49c014ccef9befec7be7f15068e5c7039e757139..5d3c48bc05c6479724788f324040e1c0448ec19e 100644 (file)
@@ -26,6 +26,7 @@
 #include "volume.h"
 #include "directory.h"
 #include "fork.h"
+#include "fce_api.h"
 
 /* we need to have a hashed list of oforks (by dev inode). just hash
  * by first letter. */
@@ -454,6 +455,12 @@ int of_closefork(struct ofork *ofork)
             }
         }
     }
+
+    /* Somone has used write_fork, we assume file was changed, register it to file change event api */
+    if (ofork->of_flags & AFPFORK_MODIFIED) {
+        fce_register_file_modification(ofork);
+    }
+
     ret = 0;
     if ( ad_close( ofork->of_ad, adflags ) < 0 ) {
         ret = -1;