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