xref: /lighttpd1.4/src/stat_cache.c (revision 07517ff3)
1 #include "first.h"
2 
3 #include "stat_cache.h"
4 #include "base.h"
5 #include "log.h"
6 #include "fdevent.h"
7 #include "etag.h"
8 #include "splaytree.h"
9 
10 #include <sys/types.h>
11 #include <sys/stat.h>
12 
13 #include <stdlib.h>
14 #include <string.h>
15 #include <errno.h>
16 #include <unistd.h>
17 #include <fcntl.h>
18 
19 #ifdef HAVE_ATTR_ATTRIBUTES_H
20 # include <attr/attributes.h>
21 #endif
22 
23 #ifdef HAVE_SYS_EXTATTR_H
24 # include <sys/extattr.h>
25 #endif
26 
27 #ifndef HAVE_LSTAT
28 # define lstat stat
29 #endif
30 
31 /*
32  * stat-cache
33  *
34  * we cache the stat() calls in our own storage
35  * the directories are cached in FAM
36  *
37  * if we get a change-event from FAM, we increment the version in the FAM->dir mapping
38  *
39  * if the stat()-cache is queried we check if the version id for the directory is the
40  * same and return immediatly.
41  *
42  *
43  * What we need:
44  *
45  * - for each stat-cache entry we need a fast indirect lookup on the directory name
46  * - for each FAMRequest we have to find the version in the directory cache (index as userdata)
47  *
48  * stat <<-> directory <-> FAMRequest
49  *
50  * if file is deleted, directory is dirty, file is rechecked ...
51  * if directory is deleted, directory mapping is removed
52  *
53  * */
54 
55 /* the directory name is too long to always compare on it
56  * - we need a hash
57  * - the hash-key is used as sorting criteria for a tree
58  * - a splay-tree is used as we can use the caching effect of it
59  */
60 
61 /* we want to cleanup the stat-cache every few seconds, let's say 10
62  *
63  * - remove entries which are outdated since 30s
64  * - remove entries which are fresh but havn't been used since 60s
65  * - if we don't have a stat-cache entry for a directory, release it from the monitor
66  */
67 
68 
69 enum {
70   STAT_CACHE_ENGINE_UNSET,
71   STAT_CACHE_ENGINE_NONE,
72   STAT_CACHE_ENGINE_SIMPLE,
73   STAT_CACHE_ENGINE_FAM
74 };
75 
76 #ifdef HAVE_FAM_H
77 struct stat_cache_fam;
78 #endif
79 
80 typedef struct stat_cache {
81 	splay_tree *files; /* the nodes of the tree are stat_cache_entry's */
82 	buffer *hash_key;  /* temp-store for the hash-key */
83       #ifdef HAVE_FAM_H
84 	struct stat_cache_fam *scf;
85       #endif
86 } stat_cache;
87 
88 
89 /* the famous DJB hash function for strings */
90 static uint32_t hashme(buffer *str) {
91 	uint32_t hash = 5381;
92 	for (const unsigned char *s = (unsigned char *)str->ptr; *s; ++s) {
93 		hash = ((hash << 5) + hash) ^ *s;
94 	}
95 
96 	hash &= ~(((uint32_t)1) << 31); /* strip the highest bit */
97 
98 	return hash;
99 }
100 
101 
102 #ifdef HAVE_FAM_H
103 
104 #include <fam.h>
105 
106 typedef struct {
107 	FAMRequest *req;
108 	buffer *name;
109 	int version;
110 } fam_dir_entry;
111 
112 typedef struct stat_cache_fam {
113 	splay_tree *dirs; /* the nodes of the tree are fam_dir_entry */
114 
115 	FAMConnection fam;
116 	int    fam_fcce_ndx;
117 
118 	int dir_ndx;
119 	fam_dir_entry *fam_dir;
120 	buffer *dir_name; /* for building the dirname from the filename */
121 	buffer *hash_key;  /* temp-store for the hash-key */
122 } stat_cache_fam;
123 
124 static fam_dir_entry * fam_dir_entry_init(void) {
125 	fam_dir_entry *fam_dir = NULL;
126 
127 	fam_dir = calloc(1, sizeof(*fam_dir));
128 	force_assert(NULL != fam_dir);
129 
130 	fam_dir->name = buffer_init();
131 
132 	return fam_dir;
133 }
134 
135 static void fam_dir_entry_free(FAMConnection *fc, void *data) {
136 	fam_dir_entry *fam_dir = data;
137 
138 	if (!fam_dir) return;
139 
140 	FAMCancelMonitor(fc, fam_dir->req);
141 
142 	buffer_free(fam_dir->name);
143 	free(fam_dir->req);
144 
145 	free(fam_dir);
146 }
147 
148 static handler_t stat_cache_handle_fdevent(server *srv, void *_fce, int revent) {
149 	size_t i;
150 	stat_cache_fam *scf = srv->stat_cache->scf;
151 	size_t events;
152 
153 	UNUSED(_fce);
154 	/* */
155 
156 	if (revent & FDEVENT_IN) {
157 		events = FAMPending(&scf->fam);
158 
159 		for (i = 0; i < events; i++) {
160 			FAMEvent fe;
161 			fam_dir_entry *fam_dir;
162 			splay_tree *node;
163 			int ndx, j;
164 
165 			FAMNextEvent(&scf->fam, &fe);
166 
167 			/* handle event */
168 
169 			switch(fe.code) {
170 			case FAMChanged:
171 			case FAMDeleted:
172 			case FAMMoved:
173 				/* if the filename is a directory remove the entry */
174 
175 				fam_dir = fe.userdata;
176 				fam_dir->version++;
177 
178 				/* file/dir is still here */
179 				if (fe.code == FAMChanged) break;
180 
181 				/* we have 2 versions, follow and no-follow-symlink */
182 
183 				for (j = 0; j < 2; j++) {
184 					buffer_copy_string(scf->hash_key, fe.filename);
185 					buffer_append_string_len(scf->hash_key, (0 == j ? "0" : "1"), 1);
186 
187 					ndx = hashme(scf->hash_key);
188 
189 					scf->dirs = splaytree_splay(scf->dirs, ndx);
190 					node = scf->dirs;
191 
192 					if (node && (node->key == ndx)) {
193 						int osize = splaytree_size(scf->dirs);
194 
195 						fam_dir_entry_free(&scf->fam, node->data);
196 						scf->dirs = splaytree_delete(scf->dirs, ndx);
197 
198 						force_assert(osize - 1 == splaytree_size(scf->dirs));
199 					}
200 				}
201 				break;
202 			default:
203 				break;
204 			}
205 		}
206 	}
207 
208 	if (revent & (FDEVENT_HUP|FDEVENT_RDHUP)) {
209 		/* fam closed the connection */
210 		fdevent_event_del(srv->ev, &(scf->fam_fcce_ndx), FAMCONNECTION_GETFD(&scf->fam));
211 		fdevent_unregister(srv->ev, FAMCONNECTION_GETFD(&scf->fam));
212 
213 		FAMClose(&scf->fam);
214 	}
215 
216 	return HANDLER_GO_ON;
217 }
218 
219 static stat_cache_fam * stat_cache_init_fam(server *srv) {
220 	stat_cache_fam *scf = calloc(1, sizeof(*scf));
221 	scf->fam_fcce_ndx = -1;
222 	scf->dir_name = buffer_init();
223 	scf->hash_key = buffer_init();
224 
225 	/* setup FAM */
226 	if (0 != FAMOpen2(&scf->fam, "lighttpd")) {
227 		log_error_write(srv, __FILE__, __LINE__, "s",
228 				"could not open a fam connection, dieing.");
229 		return NULL;
230 	}
231       #ifdef HAVE_FAMNOEXISTS
232 	FAMNoExists(&scf->fam);
233       #endif
234 
235 	fdevent_setfd_cloexec(FAMCONNECTION_GETFD(&scf->fam));
236 	fdevent_register(srv->ev, FAMCONNECTION_GETFD(&scf->fam), stat_cache_handle_fdevent, NULL);
237 	fdevent_event_set(srv->ev, &(scf->fam_fcce_ndx), FAMCONNECTION_GETFD(&scf->fam), FDEVENT_IN | FDEVENT_RDHUP);
238 
239 	return scf;
240 }
241 
242 static void stat_cache_free_fam(stat_cache_fam *scf) {
243 	if (NULL == scf) return;
244 	buffer_free(scf->dir_name);
245 	buffer_free(scf->hash_key);
246 
247 	while (scf->dirs) {
248 		int osize;
249 		splay_tree *node = scf->dirs;
250 
251 		osize = scf->dirs->size;
252 
253 		fam_dir_entry_free(&scf->fam, node->data);
254 		scf->dirs = splaytree_delete(scf->dirs, node->key);
255 
256 		if (osize == 1) {
257 			force_assert(NULL == scf->dirs);
258 		} else {
259 			force_assert(osize == (scf->dirs->size + 1));
260 		}
261 	}
262 
263 	if (-1 != scf->fam_fcce_ndx) {
264 		/* fd events already gone */
265 		scf->fam_fcce_ndx = -1;
266 
267 		FAMClose(&scf->fam);
268 	}
269 
270 	free(scf);
271 }
272 
273 static int buffer_copy_dirname(buffer *dst, const buffer *file) {
274 	size_t i;
275 
276 	if (buffer_string_is_empty(file)) return -1;
277 
278 	for (i = buffer_string_length(file); i > 0; i--) {
279 		if (file->ptr[i] == '/') {
280 			buffer_copy_string_len(dst, file->ptr, i);
281 			return 0;
282 		}
283 	}
284 
285 	return -1;
286 }
287 
288 static handler_t stat_cache_fam_dir_check(server *srv, stat_cache_fam *scf, stat_cache_entry *sce, const buffer *name, unsigned int follow_symlink) {
289 	if (0 != buffer_copy_dirname(scf->dir_name, name)) {
290 		log_error_write(srv, __FILE__, __LINE__, "sb",
291 				"no '/' found in filename:", name);
292 		return HANDLER_ERROR;
293 	}
294 
295 	buffer_copy_buffer(scf->hash_key, scf->dir_name);
296 	buffer_append_string_len(scf->hash_key, (0 != follow_symlink ? "1" : "0"), 1);
297 
298 	scf->dir_ndx = hashme(scf->hash_key);
299 
300 	scf->dirs = splaytree_splay(scf->dirs, scf->dir_ndx);
301 
302 	if ((NULL != scf->dirs) && (scf->dirs->key == scf->dir_ndx)) {
303 		scf->fam_dir = scf->dirs->data;
304 
305 		/* check whether we got a collision */
306 		if (buffer_is_equal(scf->dir_name, scf->fam_dir->name)) {
307 			/* test whether a found file cache entry is still ok */
308 			if ((NULL != sce) && (scf->fam_dir->version == sce->dir_version)) {
309 				/* the stat()-cache entry is still ok */
310 				return HANDLER_FINISHED;
311 			}
312 		} else {
313 			/* hash collision, forget about the entry */
314 			scf->fam_dir = NULL;
315 		}
316 	} else {
317 		scf->fam_dir = NULL;
318 	}
319 
320 	return HANDLER_GO_ON;
321 }
322 
323 static void stat_cache_fam_dir_monitor(server *srv, stat_cache_fam *scf, stat_cache_entry *sce, const buffer *name) {
324 	/* is this directory already registered ? */
325 	fam_dir_entry *fam_dir = scf->fam_dir;
326 	if (NULL == fam_dir) {
327 		fam_dir = fam_dir_entry_init();
328 
329 		buffer_copy_buffer(fam_dir->name, scf->dir_name);
330 
331 		fam_dir->version = 1;
332 
333 		fam_dir->req = calloc(1, sizeof(FAMRequest));
334 		force_assert(NULL != fam_dir);
335 
336 		if (0 != FAMMonitorDirectory(&scf->fam, fam_dir->name->ptr,
337 					     fam_dir->req, fam_dir)) {
338 
339 			log_error_write(srv, __FILE__, __LINE__, "sbsbs",
340 					"monitoring dir failed:",
341 					fam_dir->name,
342 					"file:", name,
343 					FamErrlist[FAMErrno]);
344 
345 			fam_dir_entry_free(&scf->fam, fam_dir);
346 			fam_dir = NULL;
347 		} else {
348 			int osize = splaytree_size(scf->dirs);
349 
350 			/* already splayed scf->dir_ndx */
351 			if ((NULL != scf->dirs) && (scf->dirs->key == scf->dir_ndx)) {
352 				/* hash collision: replace old entry */
353 				fam_dir_entry_free(&scf->fam, scf->dirs->data);
354 				scf->dirs->data = fam_dir;
355 			} else {
356 				scf->dirs = splaytree_insert(scf->dirs, scf->dir_ndx, fam_dir);
357 				force_assert(osize == (splaytree_size(scf->dirs) - 1));
358 			}
359 
360 			force_assert(scf->dirs);
361 			force_assert(scf->dirs->data == fam_dir);
362 			scf->fam_dir = fam_dir;
363 		}
364 	}
365 
366 	/* bind the fam_fc to the stat() cache entry */
367 
368 	if (fam_dir) {
369 		sce->dir_version = fam_dir->version;
370 	}
371 }
372 
373 #endif
374 
375 
376 stat_cache *stat_cache_init(server *srv) {
377 	stat_cache *sc = NULL;
378 	UNUSED(srv);
379 
380 	sc = calloc(1, sizeof(*sc));
381 	force_assert(NULL != sc);
382 
383 	sc->hash_key = buffer_init();
384 
385 #ifdef HAVE_FAM_H
386 	if (STAT_CACHE_ENGINE_FAM == srv->srvconf.stat_cache_engine) {
387 		sc->scf = stat_cache_init_fam(srv);
388 		if (NULL == sc->scf) {
389 			free(sc);
390 			return NULL;
391 		}
392 	}
393 #endif
394 
395 	return sc;
396 }
397 
398 static stat_cache_entry * stat_cache_entry_init(void) {
399 	stat_cache_entry *sce = NULL;
400 
401 	sce = calloc(1, sizeof(*sce));
402 	force_assert(NULL != sce);
403 
404 	sce->name = buffer_init();
405 	sce->etag = buffer_init();
406 	sce->content_type = buffer_init();
407 
408 	return sce;
409 }
410 
411 static void stat_cache_entry_free(void *data) {
412 	stat_cache_entry *sce = data;
413 	if (!sce) return;
414 
415 	buffer_free(sce->etag);
416 	buffer_free(sce->name);
417 	buffer_free(sce->content_type);
418 
419 	free(sce);
420 }
421 
422 void stat_cache_free(stat_cache *sc) {
423 	while (sc->files) {
424 		int osize;
425 		splay_tree *node = sc->files;
426 
427 		osize = sc->files->size;
428 
429 		stat_cache_entry_free(node->data);
430 		sc->files = splaytree_delete(sc->files, node->key);
431 
432 		force_assert(osize - 1 == splaytree_size(sc->files));
433 	}
434 
435 	buffer_free(sc->hash_key);
436 
437 #ifdef HAVE_FAM_H
438 	stat_cache_free_fam(sc->scf);
439 #endif
440 	free(sc);
441 }
442 
443 int stat_cache_choose_engine (server *srv, const buffer *stat_cache_string) {
444 	if (buffer_string_is_empty(stat_cache_string)) {
445 		srv->srvconf.stat_cache_engine = STAT_CACHE_ENGINE_SIMPLE;
446 	} else if (buffer_is_equal_string(stat_cache_string, CONST_STR_LEN("simple"))) {
447 		srv->srvconf.stat_cache_engine = STAT_CACHE_ENGINE_SIMPLE;
448 #ifdef HAVE_FAM_H
449 	} else if (buffer_is_equal_string(stat_cache_string, CONST_STR_LEN("fam"))) {
450 		srv->srvconf.stat_cache_engine = STAT_CACHE_ENGINE_FAM;
451 #endif
452 	} else if (buffer_is_equal_string(stat_cache_string, CONST_STR_LEN("disable"))) {
453 		srv->srvconf.stat_cache_engine = STAT_CACHE_ENGINE_NONE;
454 	} else {
455 		log_error_write(srv, __FILE__, __LINE__, "sb",
456 				"server.stat-cache-engine can be one of \"disable\", \"simple\","
457 #ifdef HAVE_FAM_H
458 				" \"fam\","
459 #endif
460 				" but not:", stat_cache_string);
461 		return -1;
462 	}
463 	return 0;
464 }
465 
466 #if defined(HAVE_XATTR)
467 static int stat_cache_attr_get(buffer *buf, char *name, char *xattrname) {
468 	int attrlen;
469 	int ret;
470 
471 	buffer_string_prepare_copy(buf, 1023);
472 	attrlen = buf->size - 1;
473 	if(0 == (ret = attr_get(name, xattrname, buf->ptr, &attrlen, 0))) {
474 		buffer_commit(buf, attrlen);
475 	}
476 	return ret;
477 }
478 #elif defined(HAVE_EXTATTR)
479 static int stat_cache_attr_get(buffer *buf, char *name, char *xattrname) {
480 	ssize_t attrlen;
481 
482 	buffer_string_prepare_copy(buf, 1023);
483 
484 	if (-1 != (attrlen = extattr_get_file(name, EXTATTR_NAMESPACE_USER, xattrname, buf->ptr, buf->size - 1))) {
485 		buf->used = attrlen + 1;
486 		buf->ptr[attrlen] = '\0';
487 		return 0;
488 	}
489 	return -1;
490 }
491 #endif
492 
493 const buffer * stat_cache_mimetype_by_ext(const connection *con, const char *name, size_t nlen)
494 {
495     const char *end = name + nlen; /*(end of string)*/
496     const size_t used = con->conf.mimetypes->used;
497     if (used < 16) {
498         for (size_t i = 0; i < used; ++i) {
499             /* suffix match */
500             const data_string *ds = (data_string *)con->conf.mimetypes->data[i];
501             const size_t klen = buffer_string_length(ds->key);
502             if (klen <= nlen && 0 == strncasecmp(end-klen, ds->key->ptr, klen))
503                 return ds->value;
504         }
505     }
506     else {
507         const char *s;
508         const data_string *ds;
509         if (nlen) {
510             for (s = end-1; s != name && *s != '/'; --s) ; /*(like memrchr())*/
511             if (*s == '/') ++s;
512         }
513         else {
514             s = name;
515         }
516         /* search for basename, then longest .ext2.ext1, then .ext1, then "" */
517         ds = (data_string *)array_get_element_klen(con->conf.mimetypes, s, end - s);
518         if (NULL != ds) return ds->value;
519         while (++s < end) {
520             while (*s != '.' && ++s != end) ;
521             if (s == end) break;
522             /* search ".ext" then "ext" */
523             ds = (data_string *)array_get_element_klen(con->conf.mimetypes, s, end - s);
524             if (NULL != ds) return ds->value;
525             /* repeat search without leading '.' to handle situation where
526              * admin configured mimetype.assign keys without leading '.' */
527             if (++s < end) {
528                 if (*s == '.') { --s; continue; }
529                 ds = (data_string *)array_get_element_klen(con->conf.mimetypes, s, end - s);
530                 if (NULL != ds) return ds->value;
531             }
532         }
533         /* search for ""; catchall */
534         ds = (data_string *)array_get_element(con->conf.mimetypes, "");
535         if (NULL != ds) return ds->value;
536     }
537 
538     return NULL;
539 }
540 
541 const buffer * stat_cache_content_type_get(server *srv, connection *con, const buffer *name, stat_cache_entry *sce)
542 {
543     /*(invalid caching if user config has multiple, different
544      * con->conf.mimetypes for same extension (not expected))*/
545     if (!buffer_string_is_empty(sce->content_type)) return sce->content_type;
546 
547     if (S_ISREG(sce->st.st_mode)) {
548         /* determine mimetype */
549         buffer_clear(sce->content_type);
550       #if defined(HAVE_XATTR) || defined(HAVE_EXTATTR)
551         if (con->conf.use_xattr) {
552             stat_cache_attr_get(sce->content_type, name->ptr, srv->srvconf.xattr_name->ptr);
553         }
554       #else
555         UNUSED(srv);
556       #endif
557         /* xattr did not set a content-type. ask the config */
558         if (buffer_string_is_empty(sce->content_type)) {
559             const buffer *type = stat_cache_mimetype_by_ext(con, CONST_BUF_LEN(name));
560             if (NULL != type) {
561                 buffer_copy_buffer(sce->content_type, type);
562             }
563         }
564         return sce->content_type;
565     }
566 
567     return NULL;
568 }
569 
570 const buffer * stat_cache_etag_get(stat_cache_entry *sce, etag_flags_t flags) {
571     /*(invalid caching if user config has multiple, different con->etag_flags
572      * for same path (not expected, since etag flags should be by filesystem))*/
573     if (!buffer_string_is_empty(sce->etag)) return sce->etag;
574 
575     if (S_ISREG(sce->st.st_mode) || S_ISDIR(sce->st.st_mode)) {
576         etag_create(sce->etag, &sce->st, flags);
577         return sce->etag;
578     }
579 
580     return NULL;
581 }
582 
583 #ifdef HAVE_LSTAT
584 static int stat_cache_lstat(server *srv, buffer *dname, struct stat *lst) {
585 	if (lstat(dname->ptr, lst) == 0) {
586 		return S_ISLNK(lst->st_mode) ? 0 : 1;
587 	}
588 	else {
589 		log_error_write(srv, __FILE__, __LINE__, "sbs",
590 				"lstat failed for:",
591 				dname, strerror(errno));
592 	};
593 	return -1;
594 }
595 #endif
596 
597 /***
598  *
599  *
600  *
601  * returns:
602  *  - HANDLER_FINISHED on cache-miss (don't forget to reopen the file)
603  *  - HANDLER_ERROR on stat() failed -> see errno for problem
604  */
605 
606 handler_t stat_cache_get_entry(server *srv, connection *con, buffer *name, stat_cache_entry **ret_sce) {
607 	stat_cache_entry *sce = NULL;
608 	stat_cache *sc;
609 	struct stat st;
610 	int fd;
611 	const int follow_symlink = con->conf.follow_symlink;
612 	struct stat lst;
613 	int file_ndx;
614 
615 	*ret_sce = NULL;
616 
617 	/*
618 	 * check if the directory for this file has changed
619 	 */
620 
621 	sc = srv->stat_cache;
622 
623 	buffer_copy_buffer(sc->hash_key, name);
624 	buffer_append_string_len(sc->hash_key, (0 != follow_symlink ? "1" : "0"), 1);
625 
626 	file_ndx = hashme(sc->hash_key);
627 	sc->files = splaytree_splay(sc->files, file_ndx);
628 
629 	if (sc->files && (sc->files->key == file_ndx)) {
630 		/* we have seen this file already and
631 		 * don't stat() it again in the same second */
632 
633 		sce = sc->files->data;
634 
635 		/* check if the name is the same, we might have a collision */
636 
637 		if (buffer_is_equal(name, sce->name)) {
638 			if (srv->srvconf.stat_cache_engine == STAT_CACHE_ENGINE_SIMPLE) {
639 				if (sce->stat_ts == srv->cur_ts && follow_symlink) {
640 					*ret_sce = sce;
641 					return HANDLER_GO_ON;
642 				}
643 			}
644 		} else {
645 			/* collision, forget about the entry */
646 			sce = NULL;
647 		}
648 	}
649 
650 #ifdef HAVE_FAM_H
651 	/* dir-check */
652 	if (srv->srvconf.stat_cache_engine == STAT_CACHE_ENGINE_FAM) {
653 		switch (stat_cache_fam_dir_check(srv, sc->scf, sce, name, follow_symlink)) {
654 		case HANDLER_GO_ON:
655 			break;
656 		case HANDLER_FINISHED:
657 			*ret_sce = sce;
658 			return HANDLER_GO_ON;
659 		case HANDLER_ERROR:
660 		default:
661 			return HANDLER_ERROR;
662 		}
663 	}
664 #endif
665 
666 	/*
667 	 * *lol*
668 	 * - open() + fstat() on a named-pipe results in a (intended) hang.
669 	 * - stat() if regular file + open() to see if we can read from it is better
670 	 *
671 	 * */
672 	if (-1 == stat(name->ptr, &st)) {
673 		return HANDLER_ERROR;
674 	}
675 
676 
677 	if (S_ISREG(st.st_mode)) {
678 		/* fix broken stat/open for symlinks to reg files with appended slash on freebsd,osx */
679 		if (name->ptr[buffer_string_length(name) - 1] == '/') {
680 			errno = ENOTDIR;
681 			return HANDLER_ERROR;
682 		}
683 
684 		/* try to open the file to check if we can read it */
685 		if (-1 == (fd = open(name->ptr, O_RDONLY))) {
686 			return HANDLER_ERROR;
687 		}
688 		close(fd);
689 	}
690 
691 	if (NULL == sce) {
692 
693 		sce = stat_cache_entry_init();
694 		buffer_copy_buffer(sce->name, name);
695 
696 		/* already splayed file_ndx */
697 		if ((NULL != sc->files) && (sc->files->key == file_ndx)) {
698 			/* hash collision: replace old entry */
699 			stat_cache_entry_free(sc->files->data);
700 			sc->files->data = sce;
701 		} else {
702 			int osize = splaytree_size(sc->files);
703 
704 			sc->files = splaytree_insert(sc->files, file_ndx, sce);
705 			force_assert(osize + 1 == splaytree_size(sc->files));
706 		}
707 		force_assert(sc->files);
708 		force_assert(sc->files->data == sce);
709 
710 	} else {
711 
712 		buffer_clear(sce->etag);
713 	      #if defined(HAVE_XATTR) || defined(HAVE_EXTATTR)
714 		buffer_clear(sce->content_type);
715 	      #endif
716 
717 	}
718 
719 	sce->st = st;
720 	sce->stat_ts = srv->cur_ts;
721 
722 	/* catch the obvious symlinks
723 	 *
724 	 * this is not a secure check as we still have a race-condition between
725 	 * the stat() and the open. We can only solve this by
726 	 * 1. open() the file
727 	 * 2. fstat() the fd
728 	 *
729 	 * and keeping the file open for the rest of the time. But this can
730 	 * only be done at network level.
731 	 *
732 	 * per default it is not a symlink
733 	 * */
734 #ifdef HAVE_LSTAT
735 	sce->is_symlink = 0;
736 
737 	/* we want to only check for symlinks if we should block symlinks.
738 	 */
739 	if (!follow_symlink) {
740 		if (stat_cache_lstat(srv, name, &lst)  == 0) {
741 				sce->is_symlink = 1;
742 		}
743 
744 		/*
745 		 * we assume "/" can not be symlink, so
746 		 * skip the symlink stuff if our path is /
747 		 **/
748 		else if (buffer_string_length(name) > 1) {
749 			buffer *dname;
750 			char *s_cur;
751 
752 			dname = buffer_init();
753 			buffer_copy_buffer(dname, name);
754 
755 			while ((s_cur = strrchr(dname->ptr, '/'))) {
756 				buffer_string_set_length(dname, s_cur - dname->ptr);
757 				if (dname->ptr == s_cur) {
758 					break;
759 				}
760 				if (stat_cache_lstat(srv, dname, &lst)  == 0) {
761 					sce->is_symlink = 1;
762 					break;
763 				};
764 			};
765 			buffer_free(dname);
766 		};
767 	}
768 #endif
769 
770 #ifdef HAVE_FAM_H
771 	if (srv->srvconf.stat_cache_engine == STAT_CACHE_ENGINE_FAM) {
772 		stat_cache_fam_dir_monitor(srv, sc->scf, sce, name);
773 	}
774 #endif
775 
776 	*ret_sce = sce;
777 
778 	return HANDLER_GO_ON;
779 }
780 
781 int stat_cache_open_rdonly_fstat (server *srv, connection *con, buffer *name, struct stat *st) {
782 	/*(Note: O_NOFOLLOW affects only the final path segment, the target file,
783 	 * not any intermediate symlinks along the path)*/
784 	#ifndef O_BINARY
785 	#define O_BINARY 0
786 	#endif
787 	#ifndef O_LARGEFILE
788 	#define O_LARGEFILE 0
789 	#endif
790 	#ifndef O_NOCTTY
791 	#define O_NOCTTY 0
792 	#endif
793 	#ifndef O_NONBLOCK
794 	#define O_NONBLOCK 0
795 	#endif
796 	#ifndef O_NOFOLLOW
797 	#define O_NOFOLLOW 0
798 	#endif
799 	const int oflags = O_BINARY | O_LARGEFILE | O_NOCTTY | O_NONBLOCK
800 			 | (con->conf.follow_symlink ? 0 : O_NOFOLLOW);
801 	const int fd = fdevent_open_cloexec(name->ptr, O_RDONLY | oflags, 0);
802 	if (fd >= 0) {
803 		if (0 == fstat(fd, st)) {
804 			return fd;
805 		} else {
806 			close(fd);
807 		}
808 	}
809 	UNUSED(srv); /*(might log_error_write(srv, ...) in the future)*/
810 	return -1;
811 }
812 
813 /**
814  * remove stat() from cache which havn't been stat()ed for
815  * more than 10 seconds
816  *
817  *
818  * walk though the stat-cache, collect the ids which are too old
819  * and remove them in a second loop
820  */
821 
822 static int stat_cache_tag_old_entries(server *srv, splay_tree *t, int *keys, size_t *ndx) {
823 	stat_cache_entry *sce;
824 
825 	if (!t) return 0;
826 
827 	stat_cache_tag_old_entries(srv, t->left, keys, ndx);
828 	stat_cache_tag_old_entries(srv, t->right, keys, ndx);
829 
830 	sce = t->data;
831 
832 	if (srv->cur_ts - sce->stat_ts > 2) {
833 		keys[(*ndx)++] = t->key;
834 	}
835 
836 	return 0;
837 }
838 
839 int stat_cache_trigger_cleanup(server *srv) {
840 	stat_cache *sc;
841 	size_t max_ndx = 0, i;
842 	int *keys;
843 
844 	sc = srv->stat_cache;
845 
846 	if (!sc->files) return 0;
847 
848 	keys = calloc(1, sizeof(int) * sc->files->size);
849 	force_assert(NULL != keys);
850 
851 	stat_cache_tag_old_entries(srv, sc->files, keys, &max_ndx);
852 
853 	for (i = 0; i < max_ndx; i++) {
854 		int ndx = keys[i];
855 		splay_tree *node;
856 
857 		sc->files = splaytree_splay(sc->files, ndx);
858 
859 		node = sc->files;
860 
861 		if (node && (node->key == ndx)) {
862 			stat_cache_entry_free(node->data);
863 			sc->files = splaytree_delete(sc->files, ndx);
864 		}
865 	}
866 
867 	free(keys);
868 
869 	return 0;
870 }
871