1/* 2 * $Id: fce_api.c,v 0.01 2010-10-01 00:00:0 mw Exp $ 3 * 4 * Copyright (c) 2010 Mark Williams 5 * 6 * File change event API for netatalk 7 * 8 * for every detected filesystem change a UDP packet is sent to an arbitrary list 9 * of listeners. Each packet contains unix path of modified filesystem element, 10 * event reason, and a consecutive event id (32 bit). Technically we are UDP client and are sending 11 * out packets synchronuosly as they are created by the afp functions. This should not affect 12 * performance measurably. The only delaying calls occur during initialization, if we have to 13 * resolve non-IP hostnames to IP. All numeric data inside the packet is network byte order, so use 14 * ntohs / ntohl to resolve length and event id. Ideally a listener receives every packet with 15 * no gaps in event ids, starting with event id 1 and mode FCE_CONN_START followed by 16 * data events from id 2 up to 0xFFFFFFFF, followed by 0 to 0xFFFFFFFF and so on. 17 * 18 * A gap or not starting with 1 mode FCE_CONN_START or receiving mode FCE_CONN_BROKEN means that 19 * the listener has lost at least one filesystem event 20 * 21 * All Rights Reserved. See COPYRIGHT. 22 */ 23 24#ifdef HAVE_CONFIG_H 25#include "config.h" 26#endif /* HAVE_CONFIG_H */ 27 28#include <stdio.h> 29 30#include <string.h> 31#include <stdlib.h> 32#include <errno.h> 33#include <time.h> 34 35 36#include <sys/param.h> 37#include <sys/socket.h> 38#include <netinet/in.h> 39#include <arpa/inet.h> 40#include <netdb.h> 41 42#include <netatalk/at.h> 43 44#include <atalk/adouble.h> 45#include <atalk/vfs.h> 46#include <atalk/logger.h> 47#include <atalk/afp.h> 48#include <atalk/util.h> 49#include <atalk/cnid.h> 50#include <atalk/unix.h> 51#include <atalk/fce_api.h> 52#include <atalk/globals.h> 53 54#include "fork.h" 55#include "file.h" 56#include "directory.h" 57#include "desktop.h" 58#include "volume.h" 59 60// ONLY USED IN THIS FILE 61#include "fce_api_internal.h" 62 63#define FCE_TRUE 1 64#define FCE_FALSE 0 65 66/* We store our connection data here */ 67static char coalesce[80] = {""}; 68static struct fce_history fce_history_list[FCE_HISTORY_LEN]; 69 70 71 72 73/**** 74* With coalesce we try to reduce the events over UDP, the eventlistener would throw these 75* events away anyway. 76* This works only, if the connected listener uses the events on a "per directory" base 77* It is a very simple aproach, but saves a lot of events sent to listeners. 78* Every "child element" event is ignored as long as its parent event is not older 79* than MAX_COALESCE_TIME_MS ms. If large directory trees or large files are created or deleted, 80* this probably will not work recursive, because the time to copy data will exceed this 81* event timeout. 82* 83****/ 84static int coalesce_none() 85{ 86 return coalesce[0] == 0; 87} 88static int coalesce_all() 89{ 90 return !strcmp( coalesce, "all" ); 91} 92static int coalesce_create() 93{ 94 return !strcmp( coalesce, "create" ) || coalesce_all(); 95} 96static int coalesce_delete() 97{ 98 return !strcmp( coalesce, "delete" ) || coalesce_all(); 99} 100 101void fce_initialize_history() 102{ 103 int i; 104 for (i = 0; i < FCE_HISTORY_LEN; i++) 105 { 106 memset( &fce_history_list[i], 0, sizeof(fce_history_list[i]) ); 107 } 108} 109 110static long get_ms_difftime ( struct timeval *tv1, struct timeval *tv2 ) 111{ 112 unsigned long s = tv2->tv_sec - tv1->tv_sec; 113 long us = tv2->tv_usec - tv1->tv_usec; 114 115 return s * 1000 + us/1000; 116} 117 118int fce_handle_coalescation( char *path, int is_dir, int mode ) 119{ 120 int i; 121 if (coalesce_none()) 122 return FALSE; 123 124 125 126 // First one: 127 // After a file creation *ALWAYS* a file modification is produced 128 if (mode == FCE_FILE_CREATE) 129 { 130 if (coalesce_create()) 131 { 132 return TRUE; 133 } 134 } 135 136 /* get timestamp */ 137 struct timeval tv; 138 gettimeofday(&tv, 0); 139 140 141 /* These two are used to eval our next index in history */ 142 /* the history is unsorted, speed should not be a problem, length is 10 */ 143 unsigned long oldest_entry = (unsigned long )((long)-1); 144 int oldest_entry_idx = -1; 145 146 /* Now detect events in the very near history */ 147 for (i = 0; i < FCE_HISTORY_LEN; i++) 148 { 149 struct fce_history *fh = &fce_history_list[i]; 150 151 //* Not inited ? */ 152 if (fh->tv.tv_sec == 0) 153 { 154 /* we can use it for new elements */ 155 oldest_entry = 0; 156 oldest_entry_idx = i; 157 continue; 158 } 159 160 //* Too old ? */ 161 if (get_ms_difftime( &fh->tv, &tv ) > MAX_COALESCE_TIME_MS) 162 { 163 /* Invalidate entry */ 164 fh->tv.tv_sec = 0; 165 166 oldest_entry = 0; 167 oldest_entry_idx = i; 168 continue; 169 } 170 171 172 /* If we find a parent dir wich was created we are done */ 173 if (coalesce_create() && fh->mode == FCE_DIR_CREATE) 174 { 175 //* Parent dir ? */ 176 if (!strncmp( fh->path, path, strlen( fh->path ) ) ) 177 { 178 return TRUE; 179 } 180 } 181 182 /* If we find a parent dir we should be DELETED we are done */ 183 if (coalesce_delete() && fh->is_dir && (mode == FCE_FILE_DELETE || mode == FCE_DIR_DELETE)) 184 { 185 //* Parent dir ? */ 186 if (!strncmp( fh->path, path, strlen( fh->path ) ) ) 187 { 188 return TRUE; 189 } 190 } 191 192 //* Detect oldest entry for next new entry */ 193 if (oldest_entry_idx == -1 || fh->tv.tv_sec < oldest_entry) 194 { 195 oldest_entry = fh->tv.tv_sec; 196 oldest_entry_idx = i; 197 } 198 } 199 200 /* We have a new entry for the history, register it */ 201 fce_history_list[oldest_entry_idx].tv = tv; 202 fce_history_list[oldest_entry_idx].mode = mode; 203 fce_history_list[oldest_entry_idx].is_dir = is_dir; 204 strncpy( fce_history_list[oldest_entry_idx].path, path, MAXPATHLEN); 205 206 /* we have to handle this event */ 207 return FALSE; 208} 209 210/* 211 * 212 * Set event coalescation to reduce number of events sent over UDP 213 * all|delete|create 214 * 215 * 216 * */ 217 218int fce_set_coalesce( char *coalesce_opt ) 219{ 220 strncpy( coalesce, coalesce_opt, sizeof(coalesce) - 1 ); 221} 222 223 224 225 226