1*76404edcSAsim Jamshed #include "log.h"
2*76404edcSAsim Jamshed #include "stat_cache.h"
3*76404edcSAsim Jamshed #include "fdevent.h"
4*76404edcSAsim Jamshed #include "etag.h"
5*76404edcSAsim Jamshed 
6*76404edcSAsim Jamshed #include <sys/types.h>
7*76404edcSAsim Jamshed #include <sys/stat.h>
8*76404edcSAsim Jamshed 
9*76404edcSAsim Jamshed #include <stdlib.h>
10*76404edcSAsim Jamshed #include <string.h>
11*76404edcSAsim Jamshed #include <errno.h>
12*76404edcSAsim Jamshed #include <unistd.h>
13*76404edcSAsim Jamshed #include <stdio.h>
14*76404edcSAsim Jamshed #include <fcntl.h>
15*76404edcSAsim Jamshed #include <assert.h>
16*76404edcSAsim Jamshed 
17*76404edcSAsim Jamshed #ifdef HAVE_ATTR_ATTRIBUTES_H
18*76404edcSAsim Jamshed # include <attr/attributes.h>
19*76404edcSAsim Jamshed #endif
20*76404edcSAsim Jamshed 
21*76404edcSAsim Jamshed #ifdef HAVE_FAM_H
22*76404edcSAsim Jamshed # include <fam.h>
23*76404edcSAsim Jamshed #endif
24*76404edcSAsim Jamshed 
25*76404edcSAsim Jamshed #include "sys-mmap.h"
26*76404edcSAsim Jamshed 
27*76404edcSAsim Jamshed /* NetBSD 1.3.x needs it */
28*76404edcSAsim Jamshed #ifndef MAP_FAILED
29*76404edcSAsim Jamshed # define MAP_FAILED -1
30*76404edcSAsim Jamshed #endif
31*76404edcSAsim Jamshed 
32*76404edcSAsim Jamshed #ifndef O_LARGEFILE
33*76404edcSAsim Jamshed # define O_LARGEFILE 0
34*76404edcSAsim Jamshed #endif
35*76404edcSAsim Jamshed 
36*76404edcSAsim Jamshed #ifndef HAVE_LSTAT
37*76404edcSAsim Jamshed # define lstat stat
38*76404edcSAsim Jamshed #endif
39*76404edcSAsim Jamshed 
40*76404edcSAsim Jamshed #if 0
41*76404edcSAsim Jamshed /* enables debug code for testing if all nodes in the stat-cache as accessable */
42*76404edcSAsim Jamshed #define DEBUG_STAT_CACHE
43*76404edcSAsim Jamshed #endif
44*76404edcSAsim Jamshed 
45*76404edcSAsim Jamshed /*
46*76404edcSAsim Jamshed  * stat-cache
47*76404edcSAsim Jamshed  *
48*76404edcSAsim Jamshed  * we cache the stat() calls in our own storage
49*76404edcSAsim Jamshed  * the directories are cached in FAM
50*76404edcSAsim Jamshed  *
51*76404edcSAsim Jamshed  * if we get a change-event from FAM, we increment the version in the FAM->dir mapping
52*76404edcSAsim Jamshed  *
53*76404edcSAsim Jamshed  * if the stat()-cache is queried we check if the version id for the directory is the
54*76404edcSAsim Jamshed  * same and return immediatly.
55*76404edcSAsim Jamshed  *
56*76404edcSAsim Jamshed  *
57*76404edcSAsim Jamshed  * What we need:
58*76404edcSAsim Jamshed  *
59*76404edcSAsim Jamshed  * - for each stat-cache entry we need a fast indirect lookup on the directory name
60*76404edcSAsim Jamshed  * - for each FAMRequest we have to find the version in the directory cache (index as userdata)
61*76404edcSAsim Jamshed  *
62*76404edcSAsim Jamshed  * stat <<-> directory <-> FAMRequest
63*76404edcSAsim Jamshed  *
64*76404edcSAsim Jamshed  * if file is deleted, directory is dirty, file is rechecked ...
65*76404edcSAsim Jamshed  * if directory is deleted, directory mapping is removed
66*76404edcSAsim Jamshed  *
67*76404edcSAsim Jamshed  * */
68*76404edcSAsim Jamshed 
69*76404edcSAsim Jamshed #ifdef HAVE_FAM_H
70*76404edcSAsim Jamshed typedef struct {
71*76404edcSAsim Jamshed 	FAMRequest *req;
72*76404edcSAsim Jamshed 	FAMConnection *fc;
73*76404edcSAsim Jamshed 
74*76404edcSAsim Jamshed 	buffer *name;
75*76404edcSAsim Jamshed 
76*76404edcSAsim Jamshed 	int version;
77*76404edcSAsim Jamshed } fam_dir_entry;
78*76404edcSAsim Jamshed #endif
79*76404edcSAsim Jamshed 
80*76404edcSAsim Jamshed /* the directory name is too long to always compare on it
81*76404edcSAsim Jamshed  * - we need a hash
82*76404edcSAsim Jamshed  * - the hash-key is used as sorting criteria for a tree
83*76404edcSAsim Jamshed  * - a splay-tree is used as we can use the caching effect of it
84*76404edcSAsim Jamshed  */
85*76404edcSAsim Jamshed 
86*76404edcSAsim Jamshed /* we want to cleanup the stat-cache every few seconds, let's say 10
87*76404edcSAsim Jamshed  *
88*76404edcSAsim Jamshed  * - remove entries which are outdated since 30s
89*76404edcSAsim Jamshed  * - remove entries which are fresh but havn't been used since 60s
90*76404edcSAsim Jamshed  * - if we don't have a stat-cache entry for a directory, release it from the monitor
91*76404edcSAsim Jamshed  */
92*76404edcSAsim Jamshed 
93*76404edcSAsim Jamshed #ifdef DEBUG_STAT_CACHE
94*76404edcSAsim Jamshed typedef struct {
95*76404edcSAsim Jamshed 	int *ptr;
96*76404edcSAsim Jamshed 
97*76404edcSAsim Jamshed 	size_t used;
98*76404edcSAsim Jamshed 	size_t size;
99*76404edcSAsim Jamshed } fake_keys;
100*76404edcSAsim Jamshed 
101*76404edcSAsim Jamshed static fake_keys ctrl;
102*76404edcSAsim Jamshed #endif
103*76404edcSAsim Jamshed 
stat_cache_init(void)104*76404edcSAsim Jamshed stat_cache *stat_cache_init(void) {
105*76404edcSAsim Jamshed 	stat_cache *fc = NULL;
106*76404edcSAsim Jamshed 
107*76404edcSAsim Jamshed 	fc = calloc(1, sizeof(*fc));
108*76404edcSAsim Jamshed 
109*76404edcSAsim Jamshed 	fc->dir_name = buffer_init();
110*76404edcSAsim Jamshed 	fc->hash_key = buffer_init();
111*76404edcSAsim Jamshed #ifdef HAVE_FAM_H
112*76404edcSAsim Jamshed 	fc->fam = calloc(1, sizeof(*fc->fam));
113*76404edcSAsim Jamshed #endif
114*76404edcSAsim Jamshed 
115*76404edcSAsim Jamshed #ifdef DEBUG_STAT_CACHE
116*76404edcSAsim Jamshed 	ctrl.size = 0;
117*76404edcSAsim Jamshed #endif
118*76404edcSAsim Jamshed 
119*76404edcSAsim Jamshed 	return fc;
120*76404edcSAsim Jamshed }
121*76404edcSAsim Jamshed 
stat_cache_entry_init(void)122*76404edcSAsim Jamshed static stat_cache_entry * stat_cache_entry_init(void) {
123*76404edcSAsim Jamshed 	stat_cache_entry *sce = NULL;
124*76404edcSAsim Jamshed 
125*76404edcSAsim Jamshed 	sce = calloc(1, sizeof(*sce));
126*76404edcSAsim Jamshed 
127*76404edcSAsim Jamshed 	sce->name = buffer_init();
128*76404edcSAsim Jamshed 	sce->etag = buffer_init();
129*76404edcSAsim Jamshed 	sce->content_type = buffer_init();
130*76404edcSAsim Jamshed 
131*76404edcSAsim Jamshed 	return sce;
132*76404edcSAsim Jamshed }
133*76404edcSAsim Jamshed 
stat_cache_entry_free(void * data)134*76404edcSAsim Jamshed static void stat_cache_entry_free(void *data) {
135*76404edcSAsim Jamshed 	stat_cache_entry *sce = data;
136*76404edcSAsim Jamshed 	if (!sce) return;
137*76404edcSAsim Jamshed 
138*76404edcSAsim Jamshed 	buffer_free(sce->etag);
139*76404edcSAsim Jamshed 	buffer_free(sce->name);
140*76404edcSAsim Jamshed 	buffer_free(sce->content_type);
141*76404edcSAsim Jamshed 
142*76404edcSAsim Jamshed 	free(sce);
143*76404edcSAsim Jamshed }
144*76404edcSAsim Jamshed 
145*76404edcSAsim Jamshed #ifdef HAVE_FAM_H
fam_dir_entry_init(void)146*76404edcSAsim Jamshed static fam_dir_entry * fam_dir_entry_init(void) {
147*76404edcSAsim Jamshed 	fam_dir_entry *fam_dir = NULL;
148*76404edcSAsim Jamshed 
149*76404edcSAsim Jamshed 	fam_dir = calloc(1, sizeof(*fam_dir));
150*76404edcSAsim Jamshed 
151*76404edcSAsim Jamshed 	fam_dir->name = buffer_init();
152*76404edcSAsim Jamshed 
153*76404edcSAsim Jamshed 	return fam_dir;
154*76404edcSAsim Jamshed }
155*76404edcSAsim Jamshed 
fam_dir_entry_free(void * data)156*76404edcSAsim Jamshed static void fam_dir_entry_free(void *data) {
157*76404edcSAsim Jamshed 	fam_dir_entry *fam_dir = data;
158*76404edcSAsim Jamshed 
159*76404edcSAsim Jamshed 	if (!fam_dir) return;
160*76404edcSAsim Jamshed 
161*76404edcSAsim Jamshed 	FAMCancelMonitor(fam_dir->fc, fam_dir->req);
162*76404edcSAsim Jamshed 
163*76404edcSAsim Jamshed 	buffer_free(fam_dir->name);
164*76404edcSAsim Jamshed 	free(fam_dir->req);
165*76404edcSAsim Jamshed 
166*76404edcSAsim Jamshed 	free(fam_dir);
167*76404edcSAsim Jamshed }
168*76404edcSAsim Jamshed #endif
169*76404edcSAsim Jamshed 
stat_cache_free(stat_cache * sc)170*76404edcSAsim Jamshed void stat_cache_free(stat_cache *sc) {
171*76404edcSAsim Jamshed 	while (sc->files) {
172*76404edcSAsim Jamshed 		int osize;
173*76404edcSAsim Jamshed 		splay_tree *node = sc->files;
174*76404edcSAsim Jamshed 
175*76404edcSAsim Jamshed 		osize = sc->files->size;
176*76404edcSAsim Jamshed 
177*76404edcSAsim Jamshed 		stat_cache_entry_free(node->data);
178*76404edcSAsim Jamshed 		sc->files = splaytree_delete(sc->files, node->key);
179*76404edcSAsim Jamshed 
180*76404edcSAsim Jamshed 		assert(osize - 1 == splaytree_size(sc->files));
181*76404edcSAsim Jamshed 	}
182*76404edcSAsim Jamshed 
183*76404edcSAsim Jamshed 	buffer_free(sc->dir_name);
184*76404edcSAsim Jamshed 	buffer_free(sc->hash_key);
185*76404edcSAsim Jamshed 
186*76404edcSAsim Jamshed #ifdef HAVE_FAM_H
187*76404edcSAsim Jamshed 	while (sc->dirs) {
188*76404edcSAsim Jamshed 		int osize;
189*76404edcSAsim Jamshed 		splay_tree *node = sc->dirs;
190*76404edcSAsim Jamshed 
191*76404edcSAsim Jamshed 		osize = sc->dirs->size;
192*76404edcSAsim Jamshed 
193*76404edcSAsim Jamshed 		fam_dir_entry_free(node->data);
194*76404edcSAsim Jamshed 		sc->dirs = splaytree_delete(sc->dirs, node->key);
195*76404edcSAsim Jamshed 
196*76404edcSAsim Jamshed 		if (osize == 1) {
197*76404edcSAsim Jamshed 			assert(NULL == sc->dirs);
198*76404edcSAsim Jamshed 		} else {
199*76404edcSAsim Jamshed 			assert(osize == (sc->dirs->size + 1));
200*76404edcSAsim Jamshed 		}
201*76404edcSAsim Jamshed 	}
202*76404edcSAsim Jamshed 
203*76404edcSAsim Jamshed 	if (sc->fam) {
204*76404edcSAsim Jamshed 		FAMClose(sc->fam);
205*76404edcSAsim Jamshed 		free(sc->fam);
206*76404edcSAsim Jamshed 	}
207*76404edcSAsim Jamshed #endif
208*76404edcSAsim Jamshed 	free(sc);
209*76404edcSAsim Jamshed }
210*76404edcSAsim Jamshed 
211*76404edcSAsim Jamshed #ifdef HAVE_XATTR
stat_cache_attr_get(buffer * buf,char * name)212*76404edcSAsim Jamshed static int stat_cache_attr_get(buffer *buf, char *name) {
213*76404edcSAsim Jamshed 	int attrlen;
214*76404edcSAsim Jamshed 	int ret;
215*76404edcSAsim Jamshed 
216*76404edcSAsim Jamshed 	attrlen = 1024;
217*76404edcSAsim Jamshed 	buffer_prepare_copy(buf, attrlen);
218*76404edcSAsim Jamshed 	attrlen--;
219*76404edcSAsim Jamshed 	if(0 == (ret = attr_get(name, "Content-Type", buf->ptr, &attrlen, 0))) {
220*76404edcSAsim Jamshed 		buf->used = attrlen + 1;
221*76404edcSAsim Jamshed 		buf->ptr[attrlen] = '\0';
222*76404edcSAsim Jamshed 	}
223*76404edcSAsim Jamshed 	return ret;
224*76404edcSAsim Jamshed }
225*76404edcSAsim Jamshed #endif
226*76404edcSAsim Jamshed 
227*76404edcSAsim Jamshed /* the famous DJB hash function for strings */
hashme(buffer * str)228*76404edcSAsim Jamshed static uint32_t hashme(buffer *str) {
229*76404edcSAsim Jamshed 	uint32_t hash = 5381;
230*76404edcSAsim Jamshed 	const char *s;
231*76404edcSAsim Jamshed 	for (s = str->ptr; *s; s++) {
232*76404edcSAsim Jamshed 		hash = ((hash << 5) + hash) + *s;
233*76404edcSAsim Jamshed 	}
234*76404edcSAsim Jamshed 
235*76404edcSAsim Jamshed 	hash &= ~(1 << 31); /* strip the highest bit */
236*76404edcSAsim Jamshed 
237*76404edcSAsim Jamshed 	return hash;
238*76404edcSAsim Jamshed }
239*76404edcSAsim Jamshed 
240*76404edcSAsim Jamshed #ifdef HAVE_FAM_H
stat_cache_handle_fdevent(server * srv,void * _fce,int revent)241*76404edcSAsim Jamshed handler_t stat_cache_handle_fdevent(server *srv, void *_fce, int revent) {
242*76404edcSAsim Jamshed 	size_t i;
243*76404edcSAsim Jamshed 	stat_cache *sc = srv->stat_cache;
244*76404edcSAsim Jamshed 	size_t events;
245*76404edcSAsim Jamshed 
246*76404edcSAsim Jamshed 	UNUSED(_fce);
247*76404edcSAsim Jamshed 	/* */
248*76404edcSAsim Jamshed 
249*76404edcSAsim Jamshed 	if ((revent & FDEVENT_IN) &&
250*76404edcSAsim Jamshed 	    sc->fam) {
251*76404edcSAsim Jamshed 
252*76404edcSAsim Jamshed 		events = FAMPending(sc->fam);
253*76404edcSAsim Jamshed 
254*76404edcSAsim Jamshed 		for (i = 0; i < events; i++) {
255*76404edcSAsim Jamshed 			FAMEvent fe;
256*76404edcSAsim Jamshed 			fam_dir_entry *fam_dir;
257*76404edcSAsim Jamshed 			splay_tree *node;
258*76404edcSAsim Jamshed 			int ndx, j;
259*76404edcSAsim Jamshed 
260*76404edcSAsim Jamshed 			FAMNextEvent(sc->fam, &fe);
261*76404edcSAsim Jamshed 
262*76404edcSAsim Jamshed 			/* handle event */
263*76404edcSAsim Jamshed 
264*76404edcSAsim Jamshed 			switch(fe.code) {
265*76404edcSAsim Jamshed 			case FAMChanged:
266*76404edcSAsim Jamshed 			case FAMDeleted:
267*76404edcSAsim Jamshed 			case FAMMoved:
268*76404edcSAsim Jamshed 				/* if the filename is a directory remove the entry */
269*76404edcSAsim Jamshed 
270*76404edcSAsim Jamshed 				fam_dir = fe.userdata;
271*76404edcSAsim Jamshed 				fam_dir->version++;
272*76404edcSAsim Jamshed 
273*76404edcSAsim Jamshed 				/* file/dir is still here */
274*76404edcSAsim Jamshed 				if (fe.code == FAMChanged) break;
275*76404edcSAsim Jamshed 
276*76404edcSAsim Jamshed 				/* we have 2 versions, follow and no-follow-symlink */
277*76404edcSAsim Jamshed 
278*76404edcSAsim Jamshed 				for (j = 0; j < 2; j++) {
279*76404edcSAsim Jamshed 					buffer_copy_string(sc->hash_key, fe.filename);
280*76404edcSAsim Jamshed 					buffer_append_long(sc->hash_key, j);
281*76404edcSAsim Jamshed 
282*76404edcSAsim Jamshed 					ndx = hashme(sc->hash_key);
283*76404edcSAsim Jamshed 
284*76404edcSAsim Jamshed 					sc->dirs = splaytree_splay(sc->dirs, ndx);
285*76404edcSAsim Jamshed 					node = sc->dirs;
286*76404edcSAsim Jamshed 
287*76404edcSAsim Jamshed 					if (node && (node->key == ndx)) {
288*76404edcSAsim Jamshed 						int osize = splaytree_size(sc->dirs);
289*76404edcSAsim Jamshed 
290*76404edcSAsim Jamshed 						fam_dir_entry_free(node->data);
291*76404edcSAsim Jamshed 						sc->dirs = splaytree_delete(sc->dirs, ndx);
292*76404edcSAsim Jamshed 
293*76404edcSAsim Jamshed 						assert(osize - 1 == splaytree_size(sc->dirs));
294*76404edcSAsim Jamshed 					}
295*76404edcSAsim Jamshed 				}
296*76404edcSAsim Jamshed 				break;
297*76404edcSAsim Jamshed 			default:
298*76404edcSAsim Jamshed 				break;
299*76404edcSAsim Jamshed 			}
300*76404edcSAsim Jamshed 		}
301*76404edcSAsim Jamshed 	}
302*76404edcSAsim Jamshed 
303*76404edcSAsim Jamshed 	if (revent & FDEVENT_HUP) {
304*76404edcSAsim Jamshed 		/* fam closed the connection */
305*76404edcSAsim Jamshed 		srv->stat_cache->fam_fcce_ndx = -1;
306*76404edcSAsim Jamshed 
307*76404edcSAsim Jamshed 		fdevent_event_del(srv->ev, &(sc->fam_fcce_ndx), FAMCONNECTION_GETFD(sc->fam));
308*76404edcSAsim Jamshed 		fdevent_unregister(srv->ev, FAMCONNECTION_GETFD(sc->fam));
309*76404edcSAsim Jamshed 
310*76404edcSAsim Jamshed 		FAMClose(sc->fam);
311*76404edcSAsim Jamshed 		free(sc->fam);
312*76404edcSAsim Jamshed 
313*76404edcSAsim Jamshed 		sc->fam = NULL;
314*76404edcSAsim Jamshed 	}
315*76404edcSAsim Jamshed 
316*76404edcSAsim Jamshed 	return HANDLER_GO_ON;
317*76404edcSAsim Jamshed }
318*76404edcSAsim Jamshed 
buffer_copy_dirname(buffer * dst,buffer * file)319*76404edcSAsim Jamshed static int buffer_copy_dirname(buffer *dst, buffer *file) {
320*76404edcSAsim Jamshed 	size_t i;
321*76404edcSAsim Jamshed 
322*76404edcSAsim Jamshed 	if (buffer_is_empty(file)) return -1;
323*76404edcSAsim Jamshed 
324*76404edcSAsim Jamshed 	for (i = file->used - 1; i+1 > 0; i--) {
325*76404edcSAsim Jamshed 		if (file->ptr[i] == '/') {
326*76404edcSAsim Jamshed 			buffer_copy_string_len(dst, file->ptr, i);
327*76404edcSAsim Jamshed 			return 0;
328*76404edcSAsim Jamshed 		}
329*76404edcSAsim Jamshed 	}
330*76404edcSAsim Jamshed 
331*76404edcSAsim Jamshed 	return -1;
332*76404edcSAsim Jamshed }
333*76404edcSAsim Jamshed #endif
334*76404edcSAsim Jamshed 
335*76404edcSAsim Jamshed #ifdef HAVE_LSTAT
stat_cache_lstat(server * srv,buffer * dname,struct stat * lst)336*76404edcSAsim Jamshed static int stat_cache_lstat(server *srv, buffer *dname, struct stat *lst) {
337*76404edcSAsim Jamshed 	if (lstat(dname->ptr, lst) == 0) {
338*76404edcSAsim Jamshed 		return S_ISLNK(lst->st_mode) ? 0 : 1;
339*76404edcSAsim Jamshed 	}
340*76404edcSAsim Jamshed 	else {
341*76404edcSAsim Jamshed 		log_error_write(srv, __FILE__, __LINE__, "sbs",
342*76404edcSAsim Jamshed 				"lstat failed for:",
343*76404edcSAsim Jamshed 				dname, strerror(errno));
344*76404edcSAsim Jamshed 	};
345*76404edcSAsim Jamshed 	return -1;
346*76404edcSAsim Jamshed }
347*76404edcSAsim Jamshed #endif
348*76404edcSAsim Jamshed 
349*76404edcSAsim Jamshed /***
350*76404edcSAsim Jamshed  *
351*76404edcSAsim Jamshed  *
352*76404edcSAsim Jamshed  *
353*76404edcSAsim Jamshed  * returns:
354*76404edcSAsim Jamshed  *  - HANDLER_FINISHED on cache-miss (don't forget to reopen the file)
355*76404edcSAsim Jamshed  *  - HANDLER_ERROR on stat() failed -> see errno for problem
356*76404edcSAsim Jamshed  */
357*76404edcSAsim Jamshed 
stat_cache_get_entry(server * srv,connection * con,buffer * name,stat_cache_entry ** ret_sce)358*76404edcSAsim Jamshed handler_t stat_cache_get_entry(server *srv, connection *con, buffer *name, stat_cache_entry **ret_sce) {
359*76404edcSAsim Jamshed #ifdef HAVE_FAM_H
360*76404edcSAsim Jamshed 	fam_dir_entry *fam_dir = NULL;
361*76404edcSAsim Jamshed 	int dir_ndx = -1;
362*76404edcSAsim Jamshed 	splay_tree *dir_node = NULL;
363*76404edcSAsim Jamshed #endif
364*76404edcSAsim Jamshed 	stat_cache_entry *sce = NULL;
365*76404edcSAsim Jamshed 	stat_cache *sc;
366*76404edcSAsim Jamshed 	struct stat st;
367*76404edcSAsim Jamshed 	size_t k;
368*76404edcSAsim Jamshed 	int fd;
369*76404edcSAsim Jamshed 	struct stat lst;
370*76404edcSAsim Jamshed #ifdef DEBUG_STAT_CACHE
371*76404edcSAsim Jamshed 	size_t i;
372*76404edcSAsim Jamshed #endif
373*76404edcSAsim Jamshed 
374*76404edcSAsim Jamshed 	int file_ndx;
375*76404edcSAsim Jamshed 	splay_tree *file_node = NULL;
376*76404edcSAsim Jamshed 
377*76404edcSAsim Jamshed 	*ret_sce = NULL;
378*76404edcSAsim Jamshed 
379*76404edcSAsim Jamshed 	/*
380*76404edcSAsim Jamshed 	 * check if the directory for this file has changed
381*76404edcSAsim Jamshed 	 */
382*76404edcSAsim Jamshed 
383*76404edcSAsim Jamshed 	sc = srv->stat_cache;
384*76404edcSAsim Jamshed 
385*76404edcSAsim Jamshed 	buffer_copy_string_buffer(sc->hash_key, name);
386*76404edcSAsim Jamshed 	buffer_append_long(sc->hash_key, con->conf.follow_symlink);
387*76404edcSAsim Jamshed 
388*76404edcSAsim Jamshed 	file_ndx = hashme(sc->hash_key);
389*76404edcSAsim Jamshed 	sc->files = splaytree_splay(sc->files, file_ndx);
390*76404edcSAsim Jamshed 
391*76404edcSAsim Jamshed #ifdef DEBUG_STAT_CACHE
392*76404edcSAsim Jamshed 	for (i = 0; i < ctrl.used; i++) {
393*76404edcSAsim Jamshed 		if (ctrl.ptr[i] == file_ndx) break;
394*76404edcSAsim Jamshed 	}
395*76404edcSAsim Jamshed #endif
396*76404edcSAsim Jamshed 
397*76404edcSAsim Jamshed 	if (sc->files && (sc->files->key == file_ndx)) {
398*76404edcSAsim Jamshed #ifdef DEBUG_STAT_CACHE
399*76404edcSAsim Jamshed 		/* it was in the cache */
400*76404edcSAsim Jamshed 		assert(i < ctrl.used);
401*76404edcSAsim Jamshed #endif
402*76404edcSAsim Jamshed 
403*76404edcSAsim Jamshed 		/* we have seen this file already and
404*76404edcSAsim Jamshed 		 * don't stat() it again in the same second */
405*76404edcSAsim Jamshed 
406*76404edcSAsim Jamshed 		file_node = sc->files;
407*76404edcSAsim Jamshed 
408*76404edcSAsim Jamshed 		sce = file_node->data;
409*76404edcSAsim Jamshed 
410*76404edcSAsim Jamshed 		/* check if the name is the same, we might have a collision */
411*76404edcSAsim Jamshed 
412*76404edcSAsim Jamshed 		if (buffer_is_equal(name, sce->name)) {
413*76404edcSAsim Jamshed 			if (srv->srvconf.stat_cache_engine == STAT_CACHE_ENGINE_SIMPLE) {
414*76404edcSAsim Jamshed 				if (sce->stat_ts == srv->cur_ts) {
415*76404edcSAsim Jamshed 					*ret_sce = sce;
416*76404edcSAsim Jamshed 					return HANDLER_GO_ON;
417*76404edcSAsim Jamshed 				}
418*76404edcSAsim Jamshed 			}
419*76404edcSAsim Jamshed 		} else {
420*76404edcSAsim Jamshed 			/* oops, a collision,
421*76404edcSAsim Jamshed 			 *
422*76404edcSAsim Jamshed 			 * file_node is used by the FAM check below to see if we know this file
423*76404edcSAsim Jamshed 			 * and if we can save a stat().
424*76404edcSAsim Jamshed 			 *
425*76404edcSAsim Jamshed 			 * BUT, the sce is not reset here as the entry into the cache is ok, we
426*76404edcSAsim Jamshed 			 * it is just not pointing to our requested file.
427*76404edcSAsim Jamshed 			 *
428*76404edcSAsim Jamshed 			 *  */
429*76404edcSAsim Jamshed 
430*76404edcSAsim Jamshed 			file_node = NULL;
431*76404edcSAsim Jamshed 		}
432*76404edcSAsim Jamshed 	} else {
433*76404edcSAsim Jamshed #ifdef DEBUG_STAT_CACHE
434*76404edcSAsim Jamshed 		if (i != ctrl.used) {
435*76404edcSAsim Jamshed 			log_error_write(srv, __FILE__, __LINE__, "xSB",
436*76404edcSAsim Jamshed 				file_ndx, "was already inserted but not found in cache, ", name);
437*76404edcSAsim Jamshed 		}
438*76404edcSAsim Jamshed 		assert(i == ctrl.used);
439*76404edcSAsim Jamshed #endif
440*76404edcSAsim Jamshed 	}
441*76404edcSAsim Jamshed 
442*76404edcSAsim Jamshed #ifdef HAVE_FAM_H
443*76404edcSAsim Jamshed 	/* dir-check */
444*76404edcSAsim Jamshed 	if (srv->srvconf.stat_cache_engine == STAT_CACHE_ENGINE_FAM) {
445*76404edcSAsim Jamshed 		if (0 != buffer_copy_dirname(sc->dir_name, name)) {
446*76404edcSAsim Jamshed 			log_error_write(srv, __FILE__, __LINE__, "sb",
447*76404edcSAsim Jamshed 				"no '/' found in filename:", name);
448*76404edcSAsim Jamshed 			return HANDLER_ERROR;
449*76404edcSAsim Jamshed 		}
450*76404edcSAsim Jamshed 
451*76404edcSAsim Jamshed 		buffer_copy_string_buffer(sc->hash_key, sc->dir_name);
452*76404edcSAsim Jamshed 		buffer_append_long(sc->hash_key, con->conf.follow_symlink);
453*76404edcSAsim Jamshed 
454*76404edcSAsim Jamshed 		dir_ndx = hashme(sc->hash_key);
455*76404edcSAsim Jamshed 
456*76404edcSAsim Jamshed 		sc->dirs = splaytree_splay(sc->dirs, dir_ndx);
457*76404edcSAsim Jamshed 
458*76404edcSAsim Jamshed 		if (sc->dirs && (sc->dirs->key == dir_ndx)) {
459*76404edcSAsim Jamshed 			dir_node = sc->dirs;
460*76404edcSAsim Jamshed 		}
461*76404edcSAsim Jamshed 
462*76404edcSAsim Jamshed 		if (dir_node && file_node) {
463*76404edcSAsim Jamshed 			/* we found a file */
464*76404edcSAsim Jamshed 
465*76404edcSAsim Jamshed 			sce = file_node->data;
466*76404edcSAsim Jamshed 			fam_dir = dir_node->data;
467*76404edcSAsim Jamshed 
468*76404edcSAsim Jamshed 			if (fam_dir->version == sce->dir_version) {
469*76404edcSAsim Jamshed 				/* the stat()-cache entry is still ok */
470*76404edcSAsim Jamshed 
471*76404edcSAsim Jamshed 				*ret_sce = sce;
472*76404edcSAsim Jamshed 				return HANDLER_GO_ON;
473*76404edcSAsim Jamshed 			}
474*76404edcSAsim Jamshed 		}
475*76404edcSAsim Jamshed 	}
476*76404edcSAsim Jamshed #endif
477*76404edcSAsim Jamshed 
478*76404edcSAsim Jamshed 	/*
479*76404edcSAsim Jamshed 	 * *lol*
480*76404edcSAsim Jamshed 	 * - open() + fstat() on a named-pipe results in a (intended) hang.
481*76404edcSAsim Jamshed 	 * - stat() if regular file + open() to see if we can read from it is better
482*76404edcSAsim Jamshed 	 *
483*76404edcSAsim Jamshed 	 * */
484*76404edcSAsim Jamshed 	if (-1 == stat(name->ptr, &st)) {
485*76404edcSAsim Jamshed 		return HANDLER_ERROR;
486*76404edcSAsim Jamshed 	}
487*76404edcSAsim Jamshed 
488*76404edcSAsim Jamshed 
489*76404edcSAsim Jamshed 	if (S_ISREG(st.st_mode)) {
490*76404edcSAsim Jamshed 		/* fix broken stat/open for symlinks to reg files with appended slash on freebsd,osx */
491*76404edcSAsim Jamshed 		if (name->ptr[name->used-2] == '/') {
492*76404edcSAsim Jamshed 			errno = ENOTDIR;
493*76404edcSAsim Jamshed 			return HANDLER_ERROR;
494*76404edcSAsim Jamshed 		}
495*76404edcSAsim Jamshed 
496*76404edcSAsim Jamshed 		/* try to open the file to check if we can read it */
497*76404edcSAsim Jamshed 		if (-1 == (fd = open(name->ptr, O_RDONLY))) {
498*76404edcSAsim Jamshed 			return HANDLER_ERROR;
499*76404edcSAsim Jamshed 		}
500*76404edcSAsim Jamshed 		close(fd);
501*76404edcSAsim Jamshed 	}
502*76404edcSAsim Jamshed 
503*76404edcSAsim Jamshed 	if (NULL == sce) {
504*76404edcSAsim Jamshed #ifdef DEBUG_STAT_CACHE
505*76404edcSAsim Jamshed 		int osize = splaytree_size(sc->files);
506*76404edcSAsim Jamshed #endif
507*76404edcSAsim Jamshed 
508*76404edcSAsim Jamshed 		sce = stat_cache_entry_init();
509*76404edcSAsim Jamshed 		buffer_copy_string_buffer(sce->name, name);
510*76404edcSAsim Jamshed 
511*76404edcSAsim Jamshed 		sc->files = splaytree_insert(sc->files, file_ndx, sce);
512*76404edcSAsim Jamshed #ifdef DEBUG_STAT_CACHE
513*76404edcSAsim Jamshed 		if (ctrl.size == 0) {
514*76404edcSAsim Jamshed 			ctrl.size = 16;
515*76404edcSAsim Jamshed 			ctrl.used = 0;
516*76404edcSAsim Jamshed 			ctrl.ptr = malloc(ctrl.size * sizeof(*ctrl.ptr));
517*76404edcSAsim Jamshed 		} else if (ctrl.size == ctrl.used) {
518*76404edcSAsim Jamshed 			ctrl.size += 16;
519*76404edcSAsim Jamshed 			ctrl.ptr = realloc(ctrl.ptr, ctrl.size * sizeof(*ctrl.ptr));
520*76404edcSAsim Jamshed 		}
521*76404edcSAsim Jamshed 
522*76404edcSAsim Jamshed 		ctrl.ptr[ctrl.used++] = file_ndx;
523*76404edcSAsim Jamshed 
524*76404edcSAsim Jamshed 		assert(sc->files);
525*76404edcSAsim Jamshed 		assert(sc->files->data == sce);
526*76404edcSAsim Jamshed 		assert(osize + 1 == splaytree_size(sc->files));
527*76404edcSAsim Jamshed #endif
528*76404edcSAsim Jamshed 	}
529*76404edcSAsim Jamshed 
530*76404edcSAsim Jamshed 	sce->st = st;
531*76404edcSAsim Jamshed 	sce->stat_ts = srv->cur_ts;
532*76404edcSAsim Jamshed 
533*76404edcSAsim Jamshed 	/* catch the obvious symlinks
534*76404edcSAsim Jamshed 	 *
535*76404edcSAsim Jamshed 	 * this is not a secure check as we still have a race-condition between
536*76404edcSAsim Jamshed 	 * the stat() and the open. We can only solve this by
537*76404edcSAsim Jamshed 	 * 1. open() the file
538*76404edcSAsim Jamshed 	 * 2. fstat() the fd
539*76404edcSAsim Jamshed 	 *
540*76404edcSAsim Jamshed 	 * and keeping the file open for the rest of the time. But this can
541*76404edcSAsim Jamshed 	 * only be done at network level.
542*76404edcSAsim Jamshed 	 *
543*76404edcSAsim Jamshed 	 * per default it is not a symlink
544*76404edcSAsim Jamshed 	 * */
545*76404edcSAsim Jamshed #ifdef HAVE_LSTAT
546*76404edcSAsim Jamshed 	sce->is_symlink = 0;
547*76404edcSAsim Jamshed 
548*76404edcSAsim Jamshed 	/* we want to only check for symlinks if we should block symlinks.
549*76404edcSAsim Jamshed 	 */
550*76404edcSAsim Jamshed 	if (!con->conf.follow_symlink) {
551*76404edcSAsim Jamshed 		if (stat_cache_lstat(srv, name, &lst)  == 0) {
552*76404edcSAsim Jamshed #ifdef DEBUG_STAT_CACHE
553*76404edcSAsim Jamshed 				log_error_write(srv, __FILE__, __LINE__, "sb",
554*76404edcSAsim Jamshed 						"found symlink", name);
555*76404edcSAsim Jamshed #endif
556*76404edcSAsim Jamshed 				sce->is_symlink = 1;
557*76404edcSAsim Jamshed 		}
558*76404edcSAsim Jamshed 
559*76404edcSAsim Jamshed 		/*
560*76404edcSAsim Jamshed 		 * we assume "/" can not be symlink, so
561*76404edcSAsim Jamshed 		 * skip the symlink stuff if our path is /
562*76404edcSAsim Jamshed 		 **/
563*76404edcSAsim Jamshed 		else if ((name->used > 2)) {
564*76404edcSAsim Jamshed 			buffer *dname;
565*76404edcSAsim Jamshed 			char *s_cur;
566*76404edcSAsim Jamshed 
567*76404edcSAsim Jamshed 			dname = buffer_init();
568*76404edcSAsim Jamshed 			buffer_copy_string_buffer(dname, name);
569*76404edcSAsim Jamshed 
570*76404edcSAsim Jamshed 			while ((s_cur = strrchr(dname->ptr,'/'))) {
571*76404edcSAsim Jamshed 				*s_cur = '\0';
572*76404edcSAsim Jamshed 				dname->used = s_cur - dname->ptr + 1;
573*76404edcSAsim Jamshed 				if (dname->ptr == s_cur) {
574*76404edcSAsim Jamshed #ifdef DEBUG_STAT_CACHE
575*76404edcSAsim Jamshed 					log_error_write(srv, __FILE__, __LINE__, "s", "reached /");
576*76404edcSAsim Jamshed #endif
577*76404edcSAsim Jamshed 					break;
578*76404edcSAsim Jamshed 				}
579*76404edcSAsim Jamshed #ifdef DEBUG_STAT_CACHE
580*76404edcSAsim Jamshed 				log_error_write(srv, __FILE__, __LINE__, "sbs",
581*76404edcSAsim Jamshed 						"checking if", dname, "is a symlink");
582*76404edcSAsim Jamshed #endif
583*76404edcSAsim Jamshed 				if (stat_cache_lstat(srv, dname, &lst)  == 0) {
584*76404edcSAsim Jamshed 					sce->is_symlink = 1;
585*76404edcSAsim Jamshed #ifdef DEBUG_STAT_CACHE
586*76404edcSAsim Jamshed 					log_error_write(srv, __FILE__, __LINE__, "sb",
587*76404edcSAsim Jamshed 							"found symlink", dname);
588*76404edcSAsim Jamshed #endif
589*76404edcSAsim Jamshed 					break;
590*76404edcSAsim Jamshed 				};
591*76404edcSAsim Jamshed 			};
592*76404edcSAsim Jamshed 			buffer_free(dname);
593*76404edcSAsim Jamshed 		};
594*76404edcSAsim Jamshed 	};
595*76404edcSAsim Jamshed #endif
596*76404edcSAsim Jamshed 
597*76404edcSAsim Jamshed 	if (S_ISREG(st.st_mode)) {
598*76404edcSAsim Jamshed 		/* determine mimetype */
599*76404edcSAsim Jamshed 		buffer_reset(sce->content_type);
600*76404edcSAsim Jamshed #ifdef HAVE_XATTR
601*76404edcSAsim Jamshed 		if (con->conf.use_xattr) {
602*76404edcSAsim Jamshed 			stat_cache_attr_get(sce->content_type, name->ptr);
603*76404edcSAsim Jamshed 		}
604*76404edcSAsim Jamshed #endif
605*76404edcSAsim Jamshed 		/* xattr did not set a content-type. ask the config */
606*76404edcSAsim Jamshed 		if (buffer_is_empty(sce->content_type)) {
607*76404edcSAsim Jamshed 			for (k = 0; k < con->conf.mimetypes->used; k++) {
608*76404edcSAsim Jamshed 				data_string *ds = (data_string *)con->conf.mimetypes->data[k];
609*76404edcSAsim Jamshed 				buffer *type = ds->key;
610*76404edcSAsim Jamshed 
611*76404edcSAsim Jamshed 				if (type->used == 0) continue;
612*76404edcSAsim Jamshed 
613*76404edcSAsim Jamshed 				/* check if the right side is the same */
614*76404edcSAsim Jamshed 				if (type->used > name->used) continue;
615*76404edcSAsim Jamshed 
616*76404edcSAsim Jamshed 				if (0 == strncasecmp(name->ptr + name->used - type->used, type->ptr, type->used - 1)) {
617*76404edcSAsim Jamshed 					buffer_copy_string_buffer(sce->content_type, ds->value);
618*76404edcSAsim Jamshed 					break;
619*76404edcSAsim Jamshed 				}
620*76404edcSAsim Jamshed 			}
621*76404edcSAsim Jamshed 		}
622*76404edcSAsim Jamshed 		etag_create(sce->etag, &(sce->st), con->etag_flags);
623*76404edcSAsim Jamshed 	} else if (S_ISDIR(st.st_mode)) {
624*76404edcSAsim Jamshed 		etag_create(sce->etag, &(sce->st), con->etag_flags);
625*76404edcSAsim Jamshed 	}
626*76404edcSAsim Jamshed 
627*76404edcSAsim Jamshed #ifdef HAVE_FAM_H
628*76404edcSAsim Jamshed 	if (sc->fam &&
629*76404edcSAsim Jamshed 	    (srv->srvconf.stat_cache_engine == STAT_CACHE_ENGINE_FAM)) {
630*76404edcSAsim Jamshed 		/* is this directory already registered ? */
631*76404edcSAsim Jamshed 		if (!dir_node) {
632*76404edcSAsim Jamshed 			fam_dir = fam_dir_entry_init();
633*76404edcSAsim Jamshed 			fam_dir->fc = sc->fam;
634*76404edcSAsim Jamshed 
635*76404edcSAsim Jamshed 			buffer_copy_string_buffer(fam_dir->name, sc->dir_name);
636*76404edcSAsim Jamshed 
637*76404edcSAsim Jamshed 			fam_dir->version = 1;
638*76404edcSAsim Jamshed 
639*76404edcSAsim Jamshed 			fam_dir->req = calloc(1, sizeof(FAMRequest));
640*76404edcSAsim Jamshed 
641*76404edcSAsim Jamshed 			if (0 != FAMMonitorDirectory(sc->fam, fam_dir->name->ptr,
642*76404edcSAsim Jamshed 						     fam_dir->req, fam_dir)) {
643*76404edcSAsim Jamshed 
644*76404edcSAsim Jamshed 				log_error_write(srv, __FILE__, __LINE__, "sbsbs",
645*76404edcSAsim Jamshed 						"monitoring dir failed:",
646*76404edcSAsim Jamshed 						fam_dir->name,
647*76404edcSAsim Jamshed 						"file:", name,
648*76404edcSAsim Jamshed 						FamErrlist[FAMErrno]);
649*76404edcSAsim Jamshed 
650*76404edcSAsim Jamshed 				fam_dir_entry_free(fam_dir);
651*76404edcSAsim Jamshed 			} else {
652*76404edcSAsim Jamshed 				int osize = 0;
653*76404edcSAsim Jamshed 
654*76404edcSAsim Jamshed 			       	if (sc->dirs) {
655*76404edcSAsim Jamshed 					osize = sc->dirs->size;
656*76404edcSAsim Jamshed 				}
657*76404edcSAsim Jamshed 
658*76404edcSAsim Jamshed 				sc->dirs = splaytree_insert(sc->dirs, dir_ndx, fam_dir);
659*76404edcSAsim Jamshed 				assert(sc->dirs);
660*76404edcSAsim Jamshed 				assert(sc->dirs->data == fam_dir);
661*76404edcSAsim Jamshed 				assert(osize == (sc->dirs->size - 1));
662*76404edcSAsim Jamshed 			}
663*76404edcSAsim Jamshed 		} else {
664*76404edcSAsim Jamshed 			fam_dir = dir_node->data;
665*76404edcSAsim Jamshed 		}
666*76404edcSAsim Jamshed 
667*76404edcSAsim Jamshed 		/* bind the fam_fc to the stat() cache entry */
668*76404edcSAsim Jamshed 
669*76404edcSAsim Jamshed 		if (fam_dir) {
670*76404edcSAsim Jamshed 			sce->dir_version = fam_dir->version;
671*76404edcSAsim Jamshed 			sce->dir_ndx     = dir_ndx;
672*76404edcSAsim Jamshed 		}
673*76404edcSAsim Jamshed 	}
674*76404edcSAsim Jamshed #endif
675*76404edcSAsim Jamshed 
676*76404edcSAsim Jamshed 	*ret_sce = sce;
677*76404edcSAsim Jamshed 
678*76404edcSAsim Jamshed 	return HANDLER_GO_ON;
679*76404edcSAsim Jamshed }
680*76404edcSAsim Jamshed 
681*76404edcSAsim Jamshed /**
682*76404edcSAsim Jamshed  * remove stat() from cache which havn't been stat()ed for
683*76404edcSAsim Jamshed  * more than 10 seconds
684*76404edcSAsim Jamshed  *
685*76404edcSAsim Jamshed  *
686*76404edcSAsim Jamshed  * walk though the stat-cache, collect the ids which are too old
687*76404edcSAsim Jamshed  * and remove them in a second loop
688*76404edcSAsim Jamshed  */
689*76404edcSAsim Jamshed 
stat_cache_tag_old_entries(server * srv,splay_tree * t,int * keys,size_t * ndx)690*76404edcSAsim Jamshed static int stat_cache_tag_old_entries(server *srv, splay_tree *t, int *keys, size_t *ndx) {
691*76404edcSAsim Jamshed 	stat_cache_entry *sce;
692*76404edcSAsim Jamshed 
693*76404edcSAsim Jamshed 	if (!t) return 0;
694*76404edcSAsim Jamshed 
695*76404edcSAsim Jamshed 	stat_cache_tag_old_entries(srv, t->left, keys, ndx);
696*76404edcSAsim Jamshed 	stat_cache_tag_old_entries(srv, t->right, keys, ndx);
697*76404edcSAsim Jamshed 
698*76404edcSAsim Jamshed 	sce = t->data;
699*76404edcSAsim Jamshed 
700*76404edcSAsim Jamshed 	if (srv->cur_ts - sce->stat_ts > 2) {
701*76404edcSAsim Jamshed 		keys[(*ndx)++] = t->key;
702*76404edcSAsim Jamshed 	}
703*76404edcSAsim Jamshed 
704*76404edcSAsim Jamshed 	return 0;
705*76404edcSAsim Jamshed }
706*76404edcSAsim Jamshed 
stat_cache_trigger_cleanup(server * srv)707*76404edcSAsim Jamshed int stat_cache_trigger_cleanup(server *srv) {
708*76404edcSAsim Jamshed 	stat_cache *sc;
709*76404edcSAsim Jamshed 	size_t max_ndx = 0, i;
710*76404edcSAsim Jamshed 	int *keys;
711*76404edcSAsim Jamshed 
712*76404edcSAsim Jamshed 	sc = srv->stat_cache;
713*76404edcSAsim Jamshed 
714*76404edcSAsim Jamshed 	if (!sc->files) return 0;
715*76404edcSAsim Jamshed 
716*76404edcSAsim Jamshed 	keys = calloc(1, sizeof(size_t) * sc->files->size);
717*76404edcSAsim Jamshed 
718*76404edcSAsim Jamshed 	stat_cache_tag_old_entries(srv, sc->files, keys, &max_ndx);
719*76404edcSAsim Jamshed 
720*76404edcSAsim Jamshed 	for (i = 0; i < max_ndx; i++) {
721*76404edcSAsim Jamshed 		int ndx = keys[i];
722*76404edcSAsim Jamshed 		splay_tree *node;
723*76404edcSAsim Jamshed 
724*76404edcSAsim Jamshed 		sc->files = splaytree_splay(sc->files, ndx);
725*76404edcSAsim Jamshed 
726*76404edcSAsim Jamshed 		node = sc->files;
727*76404edcSAsim Jamshed 
728*76404edcSAsim Jamshed 		if (node && (node->key == ndx)) {
729*76404edcSAsim Jamshed #ifdef DEBUG_STAT_CACHE
730*76404edcSAsim Jamshed 			size_t j;
731*76404edcSAsim Jamshed 			int osize = splaytree_size(sc->files);
732*76404edcSAsim Jamshed 			stat_cache_entry *sce = node->data;
733*76404edcSAsim Jamshed #endif
734*76404edcSAsim Jamshed 			stat_cache_entry_free(node->data);
735*76404edcSAsim Jamshed 			sc->files = splaytree_delete(sc->files, ndx);
736*76404edcSAsim Jamshed 
737*76404edcSAsim Jamshed #ifdef DEBUG_STAT_CACHE
738*76404edcSAsim Jamshed 			for (j = 0; j < ctrl.used; j++) {
739*76404edcSAsim Jamshed 				if (ctrl.ptr[j] == ndx) {
740*76404edcSAsim Jamshed 					ctrl.ptr[j] = ctrl.ptr[--ctrl.used];
741*76404edcSAsim Jamshed 					break;
742*76404edcSAsim Jamshed 				}
743*76404edcSAsim Jamshed 			}
744*76404edcSAsim Jamshed 
745*76404edcSAsim Jamshed 			assert(osize - 1 == splaytree_size(sc->files));
746*76404edcSAsim Jamshed #endif
747*76404edcSAsim Jamshed 		}
748*76404edcSAsim Jamshed 	}
749*76404edcSAsim Jamshed 
750*76404edcSAsim Jamshed 	free(keys);
751*76404edcSAsim Jamshed 
752*76404edcSAsim Jamshed 	return 0;
753*76404edcSAsim Jamshed }
754