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