xref: /lighttpd1.4/src/stat_cache.c (revision 672891a7)
1 #include "first.h"
2 
3 #include "stat_cache.h"
4 #include "log.h"
5 #include "fdevent.h"
6 #include "etag.h"
7 #include "splaytree.h"
8 
9 #include <sys/types.h>
10 #include <sys/stat.h>
11 
12 #include <stdlib.h>
13 #include <string.h>
14 #include <errno.h>
15 #include <unistd.h>
16 #include <fcntl.h>
17 
18 #if defined(HAVE_SYS_XATTR_H)
19 # include <sys/xattr.h>
20 #elif defined(HAVE_ATTR_ATTRIBUTES_H)
21 # include <attr/attributes.h>
22 #endif
23 
24 #ifdef HAVE_SYS_EXTATTR_H
25 # include <sys/extattr.h>
26 #endif
27 
28 #ifndef HAVE_LSTAT
29 #define lstat stat
30 #ifndef S_ISLNK
31 #define S_ISLNK(mode) (0)
32 #endif
33 #endif
34 
35 /*
36  * stat-cache
37  *
38  * - a splay-tree is used as we can use the caching effect of it
39  */
40 
41 enum {
42   STAT_CACHE_ENGINE_SIMPLE, /*(default)*/
43   STAT_CACHE_ENGINE_NONE,
44   STAT_CACHE_ENGINE_FAM
45 };
46 
47 struct stat_cache_fam;  /* declaration */
48 
49 typedef struct stat_cache {
50 	int stat_cache_engine;
51 	splay_tree *files; /* nodes of tree are (stat_cache_entry *) */
52 	struct stat_cache_fam *scf;
53 } stat_cache;
54 
55 static stat_cache sc;
56 
57 
58 static void * stat_cache_sptree_find(splay_tree ** const sptree,
59                                      const char * const name,
60                                      uint32_t len)
61 {
62     const int ndx = splaytree_djbhash(name, len);
63     *sptree = splaytree_splay(*sptree, ndx);
64     return (*sptree && (*sptree)->key == ndx) ? (*sptree)->data : NULL;
65 }
66 
67 
68 #ifdef HAVE_FAM_H
69 
70 /* monitor changes in directories using FAM
71  *
72  * This implementation employing FAM monitors directories as they are used,
73  * and maintains a reference count for cache use within stat_cache.c.
74  * A periodic job runs in lighttpd every 32 seconds, expiring entries unused
75  * in last 64 seconds out of the cache and cancelling FAM monitoring.  Items
76  * within the cache are checked against the filesystem upon use if last stat()
77  * was greater than or equal to 16 seconds ago.
78  *
79  * This implementation does not monitor every directory in a tree, and therefore
80  * the cache may get out-of-sync with the filesystem.  Delays in receiving and
81  * processing events from FAM might also lead to stale cache entries.
82  *
83  * For many websites, a large number of files are seldom, if ever, modified,
84  * and a common practice with images is to create a new file with a new name
85  * when a new version is needed, in order for client browsers and CDNs to better
86  * cache the content.  Given this, most use will see little difference in
87  * performance between server.stat-cache-engine = "fam" and "simple" (default).
88  * The default server.stat-cache-engine = "simple" calls stat() on a target once
89  * per second, and reuses that information until the next second.  For use where
90  * changes must be immediately visible, server.stat-cache-engine = "disable"
91  * should be used.
92  *
93  * When considering use of server.stat-cache-engine = "fam", there are a few
94  * additional limitations for this cache implementation using FAM.
95  * - symlinks to files located outside of the current directory do not result
96  *   in changes to that file being monitored (unless that file is in a directory
97  *   which is monitored as a result of a different request).  symlinks can be
98  *   chained and can be circular.  This implementation *does not* readlink() or
99  *   realpath() to resolve the chains to find and monitor the ultimate target
100  *   directory.  While symlinks to files located outside the current directory
101  *   are not monitored, symlinks to directories *are* monitored, though chains
102  *   of symlinks to directories do not result in monitoring of the directories
103  *   containing intermediate symlinks to the target directory.
104  * - directory rename of a directory which is not currently being monitored will
105  *   result in stale information in the cache if there is a subdirectory that is
106  *   being monitored.
107  * Even though lighttpd will not receive FAM events in the above cases, lighttpd
108  * does re-validate the information in the cache upon use if the cache entry has
109  * not been checked in 16 seconds, so that is the upper limit for use of stale
110  * data.
111  *
112  * Use of server.stat-cache-engine = "fam" is discouraged for extremely volatile
113  * directories such as temporary directories (e.g. /tmp and maybe /var/tmp) due
114  * to the overhead of processing the additional noise generated from changes.
115  * Related, server.stat-cache-engine = "fam" is not recommended on trees of
116  * untrusted files where a malicious user could generate an excess of change
117  * events.
118  *
119  * Internal note: lighttpd walks the caches to prune trees in stat_cache when an
120  * event is received for a directory (or symlink to a directory) which has been
121  * deleted or renamed.  The splaytree data structure is suboptimal for frequent
122  * changes of large directories trees where there have been a large number of
123  * different files recently accessed and part of the stat_cache.
124  */
125 
126 #include <fam.h>
127 
128 typedef struct fam_dir_entry {
129 	buffer *name;
130 	int refcnt;
131 	FAMRequest req;
132 	time_t stat_ts;
133 	dev_t st_dev;
134 	ino_t st_ino;
135 	struct fam_dir_entry *fam_parent;
136 } fam_dir_entry;
137 
138 typedef struct stat_cache_fam {
139 	splay_tree *dirs; /* the nodes of the tree are fam_dir_entry */
140 	FAMConnection fam;
141 	log_error_st *errh;
142 	fdevents *ev;
143 	fdnode *fdn;
144 	int fd;
145 } stat_cache_fam;
146 
147 static fam_dir_entry * fam_dir_entry_init(const char *name, size_t len)
148 {
149     fam_dir_entry * const fam_dir = calloc(1, sizeof(*fam_dir));
150     force_assert(NULL != fam_dir);
151 
152     fam_dir->name = buffer_init();
153     buffer_copy_string_len(fam_dir->name, name, len);
154     fam_dir->refcnt = 0;
155 
156     return fam_dir;
157 }
158 
159 static void fam_dir_entry_free(fam_dir_entry *fam_dir)
160 {
161     if (!fam_dir) return;
162     /*(fam_dir->parent might be invalid pointer here; ignore)*/
163     buffer_free(fam_dir->name);
164     free(fam_dir);
165 }
166 
167 static void fam_dir_invalidate_node(fam_dir_entry *fam_dir)
168 {
169     fam_dir->stat_ts = 0;
170     if (fam_dir->fam_parent) {
171         --fam_dir->fam_parent->refcnt;
172         fam_dir->fam_parent = NULL;
173     }
174 }
175 
176 /*
177  * walk though splay_tree and collect contents of dir tree.
178  * remove tagged entries in a second loop
179  */
180 
181 static void fam_dir_tag_refcnt(splay_tree *t, int *keys, int *ndx)
182 {
183     if (*ndx == 8192) return; /*(must match num array entries in keys[])*/
184     if (t->left)  fam_dir_tag_refcnt(t->left,  keys, ndx);
185     if (t->right) fam_dir_tag_refcnt(t->right, keys, ndx);
186     if (*ndx == 8192) return; /*(must match num array entries in keys[])*/
187 
188     fam_dir_entry * const fam_dir = t->data;
189     if (0 == fam_dir->refcnt) {
190         fam_dir_invalidate_node(fam_dir);
191         keys[(*ndx)++] = t->key;
192     }
193 }
194 
195 __attribute_noinline__
196 static void fam_dir_periodic_cleanup() {
197     stat_cache_fam * const scf = sc.scf;
198     int max_ndx, i;
199     int keys[8192]; /* 32k size on stack */
200     do {
201         if (!scf->dirs) break;
202         max_ndx = 0;
203         fam_dir_tag_refcnt(scf->dirs, keys, &max_ndx);
204         for (i = 0; i < max_ndx; ++i) {
205             const int ndx = keys[i];
206             splay_tree *node = scf->dirs = splaytree_splay(scf->dirs, ndx);
207             if (node && node->key == ndx) {
208                 fam_dir_entry *fam_dir = node->data;
209                 scf->dirs = splaytree_delete(scf->dirs, ndx);
210                 FAMCancelMonitor(&scf->fam, &fam_dir->req);
211                 fam_dir_entry_free(fam_dir);
212             }
213         }
214     } while (max_ndx == sizeof(keys)/sizeof(int));
215 }
216 
217 static void fam_dir_invalidate_tree(splay_tree *t, const char *name, size_t len)
218 {
219   #ifdef __clang_analyzer__
220     force_assert(name);
221   #endif
222     /*force_assert(t);*/
223     if (t->left)  fam_dir_invalidate_tree(t->left,  name, len);
224     if (t->right) fam_dir_invalidate_tree(t->right, name, len);
225 
226     fam_dir_entry * const fam_dir = t->data;
227   #ifdef __clang_analyzer__
228     force_assert(fam_dir);
229   #endif
230     buffer *b = fam_dir->name;
231     size_t blen = buffer_string_length(b);
232     if (blen > len && b->ptr[len] == '/' && 0 == memcmp(b->ptr, name, len))
233         fam_dir_invalidate_node(fam_dir);
234 }
235 
236 /* declarations */
237 static void stat_cache_delete_tree(const char *name, uint32_t len);
238 static void stat_cache_invalidate_dir_tree(const char *name, size_t len);
239 
240 static void stat_cache_handle_fdevent_in(stat_cache_fam *scf)
241 {
242     for (int i = 0, ndx; i || (i = FAMPending(&scf->fam)) > 0; --i) {
243         FAMEvent fe;
244         if (FAMNextEvent(&scf->fam, &fe) < 0) break;
245 
246         /* ignore events which may have been pending for
247          * paths recently cancelled via FAMCancelMonitor() */
248         ndx = (int)(intptr_t)fe.userdata;
249         scf->dirs = splaytree_splay(scf->dirs, ndx);
250         if (!scf->dirs || scf->dirs->key != ndx) {
251             continue;
252         }
253         fam_dir_entry *fam_dir = scf->dirs->data;
254         if (FAMREQUEST_GETREQNUM(&fam_dir->req)
255             != FAMREQUEST_GETREQNUM(&fe.fr)) {
256             continue;
257         }
258 
259         if (fe.filename[0] != '/') {
260             buffer * const n = fam_dir->name;
261             fam_dir_entry *fam_link;
262             size_t len;
263             switch(fe.code) {
264             case FAMCreated:
265                 /* file created in monitored dir modifies dir and
266                  * we should get a separate FAMChanged event for dir.
267                  * Therefore, ignore file FAMCreated event here.
268                  * Also, if FAMNoExists() is used, might get spurious
269                  * FAMCreated events as changes are made e.g. in monitored
270                  * sub-sub-sub dirs and the library discovers new (already
271                  * existing) dir entries */
272                 continue;
273             case FAMChanged:
274                 /* file changed in monitored dir does not modify dir */
275             case FAMDeleted:
276             case FAMMoved:
277                 /* file deleted or moved in monitored dir modifies dir,
278                  * but FAM provides separate notification for that */
279 
280                 /* temporarily append filename to dir in fam_dir->name to
281                  * construct path, then delete stat_cache entry (if any)*/
282                 len = buffer_string_length(n);
283                 buffer_append_string_len(n, CONST_STR_LEN("/"));
284                 buffer_append_string_len(n,fe.filename,strlen(fe.filename));
285                 /* (alternatively, could chose to stat() and update)*/
286                 stat_cache_invalidate_entry(CONST_BUF_LEN(n));
287 
288                 fam_link = /*(check if might be symlink to monitored dir)*/
289                   stat_cache_sptree_find(&scf->dirs, CONST_BUF_LEN(n));
290                 if (fam_link && !buffer_is_equal(fam_link->name, n))
291                     fam_link = NULL;
292 
293                 buffer_string_set_length(n, len);
294 
295                 if (fam_link) {
296                     /* replaced symlink changes containing dir */
297                     stat_cache_invalidate_entry(CONST_BUF_LEN(n));
298                     /* handle symlink to dir as deleted dir below */
299                     fe.code = FAMDeleted;
300                     fam_dir = fam_link;
301                     break;
302                 }
303                 continue;
304             default:
305                 continue;
306             }
307         }
308 
309         switch(fe.code) {
310         case FAMChanged:
311             stat_cache_invalidate_entry(CONST_BUF_LEN(fam_dir->name));
312             break;
313         case FAMDeleted:
314         case FAMMoved:
315             stat_cache_delete_tree(CONST_BUF_LEN(fam_dir->name));
316             fam_dir_invalidate_node(fam_dir);
317             if (scf->dirs)
318                 fam_dir_invalidate_tree(scf->dirs,CONST_BUF_LEN(fam_dir->name));
319             fam_dir_periodic_cleanup();
320             break;
321         default:
322             break;
323         }
324     }
325 }
326 
327 static handler_t stat_cache_handle_fdevent(void *ctx, int revent)
328 {
329 	stat_cache_fam * const scf = ctx; /* sc.scf */
330 
331 	if (revent & FDEVENT_IN) {
332 		stat_cache_handle_fdevent_in(scf);
333 	}
334 
335 	if (revent & (FDEVENT_HUP|FDEVENT_RDHUP)) {
336 		/* fam closed the connection */
337 		log_error(scf->errh, __FILE__, __LINE__,
338 		  "FAM connection closed; disabling stat_cache.");
339 		/* (although effectively STAT_CACHE_ENGINE_NONE,
340 		 *  do not change here so that periodic jobs clean up memory)*/
341 		/*sc.stat_cache_engine = STAT_CACHE_ENGINE_NONE; */
342 		fdevent_fdnode_event_del(scf->ev, scf->fdn);
343 		fdevent_unregister(scf->ev, scf->fd);
344 		scf->fdn = NULL;
345 
346 		FAMClose(&scf->fam);
347 		scf->fd = -1;
348 	}
349 
350 	return HANDLER_GO_ON;
351 }
352 
353 static stat_cache_fam * stat_cache_init_fam(fdevents *ev, log_error_st *errh) {
354 	stat_cache_fam *scf = calloc(1, sizeof(*scf));
355 	force_assert(scf);
356 	scf->fd = -1;
357 	scf->ev = ev;
358 	scf->errh = errh;
359 
360 	/* setup FAM */
361 	if (0 != FAMOpen2(&scf->fam, "lighttpd")) {
362 		log_error(errh, __FILE__, __LINE__,
363 		  "could not open a fam connection, dying.");
364 		return NULL;
365 	}
366       #ifdef HAVE_FAMNOEXISTS
367 	FAMNoExists(&scf->fam);
368       #endif
369 
370 	scf->fd = FAMCONNECTION_GETFD(&scf->fam);
371 	fdevent_setfd_cloexec(scf->fd);
372 	scf->fdn = fdevent_register(scf->ev, scf->fd, stat_cache_handle_fdevent, scf);
373 	fdevent_fdnode_event_set(scf->ev, scf->fdn, FDEVENT_IN | FDEVENT_RDHUP);
374 
375 	return scf;
376 }
377 
378 static void stat_cache_free_fam(stat_cache_fam *scf) {
379 	if (NULL == scf) return;
380 
381 	while (scf->dirs) {
382 		/*(skip entry invalidation and FAMCancelMonitor())*/
383 		splay_tree *node = scf->dirs;
384 		fam_dir_entry_free((fam_dir_entry *)node->data);
385 		scf->dirs = splaytree_delete(scf->dirs, node->key);
386 	}
387 
388 	if (-1 != scf->fd) {
389 		/*scf->fdn already cleaned up in fdevent_free()*/
390 		FAMClose(&scf->fam);
391 		/*scf->fd = -1;*/
392 	}
393 
394 	free(scf);
395 }
396 
397 static fam_dir_entry * fam_dir_monitor(stat_cache_fam *scf, char *fn, uint32_t dirlen, struct stat *st)
398 {
399     if (NULL == scf->fdn) return NULL; /* FAM connection closed; do nothing */
400     const int fn_is_dir = S_ISDIR(st->st_mode);
401     /*force_assert(0 != dirlen);*/
402     /*force_assert(fn[0] == '/');*/
403     /* consistency: ensure fn does not end in '/' unless root "/"
404      * FAM events will not end in '/', so easier to match this way */
405     if (fn[dirlen-1] == '/') --dirlen;
406     if (0 == dirlen) dirlen = 1; /* root dir ("/") */
407     /* Note: paths are expected to be normalized before calling stat_cache,
408      * e.g. without repeated '/' */
409     if (!fn_is_dir) {
410         while (fn[--dirlen] != '/') ;
411         if (0 == dirlen) dirlen = 1; /*(should not happen for file)*/
412     }
413     int dir_ndx = splaytree_djbhash(fn, dirlen);
414     fam_dir_entry *fam_dir = NULL;
415 
416     scf->dirs = splaytree_splay(scf->dirs, dir_ndx);
417     if (NULL != scf->dirs && scf->dirs->key == dir_ndx) {
418         fam_dir = scf->dirs->data;
419         if (!buffer_is_equal_string(fam_dir->name, fn, dirlen)) {
420             /* hash collision; preserve existing
421              * do not monitor new to avoid cache thrashing */
422             return NULL;
423         }
424         /* directory already registered */
425     }
426 
427     const time_t cur_ts = log_epoch_secs;
428     struct stat lst;
429     int ck_dir = fn_is_dir;
430     if (!fn_is_dir && (NULL==fam_dir || cur_ts - fam_dir->stat_ts >= 16)) {
431         ck_dir = 1;
432         /*(temporarily modify fn)*/
433         fn[dirlen] = '\0';
434         if (0 != lstat(fn, &lst)) {
435             fn[dirlen] = '/';
436             return NULL;
437         }
438         if (!S_ISLNK(lst.st_mode)) {
439             st = &lst;
440         }
441         else if (0 != stat(fn, st)) { /*st passed in now is stat() of dir*/
442             fn[dirlen] = '/';
443             return NULL;
444         }
445         fn[dirlen] = '/';
446     }
447 
448     int ck_lnk = (NULL == fam_dir);
449     if (ck_dir && NULL != fam_dir) {
450         /* check stat() matches device and inode, just in case an external event
451          * not being monitored occurs (e.g. rename of unmonitored parent dir)*/
452         if (st->st_dev != fam_dir->st_dev || st->st_ino != fam_dir->st_ino) {
453             ck_lnk = 1;
454             /*(modifies scf->dirs but no need to re-splay for dir_ndx since
455              * fam_dir is not NULL and so splaytree_insert not called below)*/
456             if (scf->dirs) fam_dir_invalidate_tree(scf->dirs, fn, dirlen);
457             if (!fn_is_dir) /*(if dir, caller is updating stat_cache_entry)*/
458                 stat_cache_update_entry(fn, dirlen, st, NULL);
459             /*(must not delete tree since caller is holding a valid node)*/
460             stat_cache_invalidate_dir_tree(fn, dirlen);
461             if (0 != FAMCancelMonitor(&scf->fam, &fam_dir->req)
462                 || 0 != FAMMonitorDirectory(&scf->fam, fam_dir->name->ptr,
463                                             &fam_dir->req,
464                                             (void *)(intptr_t)dir_ndx)) {
465                 fam_dir->stat_ts = 0; /* invalidate */
466                 return NULL;
467             }
468             fam_dir->st_dev = st->st_dev;
469             fam_dir->st_ino = st->st_ino;
470         }
471         fam_dir->stat_ts = cur_ts;
472     }
473 
474     if (NULL == fam_dir) {
475         fam_dir = fam_dir_entry_init(fn, dirlen);
476 
477         if (0 != FAMMonitorDirectory(&scf->fam,fam_dir->name->ptr,&fam_dir->req,
478                                      (void *)(intptr_t)dir_ndx)) {
479             log_error(scf->errh, __FILE__, __LINE__,
480               "monitoring dir failed: %s file: %s %s",
481               fam_dir->name->ptr, fn, FamErrlist[FAMErrno]);
482             fam_dir_entry_free(fam_dir);
483             return NULL;
484         }
485 
486         scf->dirs = splaytree_insert(scf->dirs, dir_ndx, fam_dir);
487         fam_dir->stat_ts= cur_ts;
488         fam_dir->st_dev = st->st_dev;
489         fam_dir->st_ino = st->st_ino;
490     }
491 
492     if (ck_lnk) {
493         if (fn_is_dir) {
494             /*(temporarily modify fn)*/
495             char e = fn[dirlen];
496             fn[dirlen] = '\0';
497             if (0 != lstat(fn, &lst)) {
498                 fn[dirlen] = e;
499                 return NULL;
500             }
501             fn[dirlen] = e;
502         }
503         if (fam_dir->fam_parent) {
504             --fam_dir->fam_parent->refcnt;
505             fam_dir->fam_parent = NULL;
506         }
507         if (S_ISLNK(lst.st_mode)) {
508             fam_dir->fam_parent = fam_dir_monitor(scf, fn, dirlen, &lst);
509         }
510     }
511 
512     ++fam_dir->refcnt;
513     return fam_dir;
514 }
515 
516 #endif
517 
518 
519 static stat_cache_entry * stat_cache_entry_init(void) {
520     stat_cache_entry *sce = calloc(1, sizeof(*sce));
521     force_assert(NULL != sce);
522     return sce;
523 }
524 
525 static void stat_cache_entry_free(void *data) {
526     stat_cache_entry *sce = data;
527     if (!sce) return;
528 
529   #ifdef HAVE_FAM_H
530     /*(decrement refcnt only;
531      * defer cancelling FAM monitor on dir even if refcnt reaches zero)*/
532     if (sce->fam_dir) --((fam_dir_entry *)sce->fam_dir)->refcnt;
533   #endif
534 
535     free(sce->name.ptr);
536     free(sce->etag.ptr);
537     if (sce->content_type.size) free(sce->content_type.ptr);
538 
539     free(sce);
540 }
541 
542 #if defined(HAVE_XATTR) || defined(HAVE_EXTATTR)
543 
544 static const char *attrname = "Content-Type";
545 static char attrval[128];
546 static buffer attrb = { attrval, 0, 0 };
547 
548 static int stat_cache_attr_get(const char *name) {
549   #if defined(HAVE_XATTR)
550    #if defined(HAVE_SYS_XATTR_H)
551     ssize_t attrlen;
552     if (0 < (attrlen = getxattr(name, attrname,
553                                 attrval, sizeof(attrval)-1)))
554    #else
555     int attrlen = sizeof(attrval)-1;
556     if (0 == attr_get(name, attrname, attrval, &attrlen, 0))
557    #endif
558   #elif defined(HAVE_EXTATTR)
559     ssize_t attrlen;
560     if (0 < (attrlen = extattr_get_file(name, EXTATTR_NAMESPACE_USER, attrname,
561                                         attrval, sizeof(attrval)-1)))
562   #endif
563     {
564         attrval[attrlen] = '\0';
565         attrb.used = (uint32_t)(attrlen + 1);
566         return 1;
567     }
568     return 0;
569 }
570 
571 #endif
572 
573 int stat_cache_init(fdevents *ev, log_error_st *errh) {
574   #ifdef HAVE_FAM_H
575     if (sc.stat_cache_engine == STAT_CACHE_ENGINE_FAM) {
576         sc.scf = stat_cache_init_fam(ev, errh);
577         if (NULL == sc.scf) return 0;
578     }
579   #else
580     UNUSED(ev);
581     UNUSED(errh);
582   #endif
583 
584     return 1;
585 }
586 
587 void stat_cache_free(void) {
588     splay_tree *sptree = sc.files;
589     while (sptree) {
590         stat_cache_entry_free(sptree->data);
591         sptree = splaytree_delete(sptree, sptree->key);
592     }
593     sc.files = NULL;
594 
595   #ifdef HAVE_FAM_H
596     stat_cache_free_fam(sc.scf);
597     sc.scf = NULL;
598   #endif
599 
600   #if defined(HAVE_XATTR) || defined(HAVE_EXTATTR)
601     attrname = "Content-Type";
602   #endif
603 
604     sc.stat_cache_engine = STAT_CACHE_ENGINE_SIMPLE; /*(default)*/
605 }
606 
607 void stat_cache_xattrname (const char *name) {
608   #if defined(HAVE_XATTR) || defined(HAVE_EXTATTR)
609     attrname = name;
610   #else
611     UNUSED(name);
612   #endif
613 }
614 
615 int stat_cache_choose_engine (const buffer *stat_cache_string, log_error_st *errh) {
616     if (buffer_string_is_empty(stat_cache_string))
617         sc.stat_cache_engine = STAT_CACHE_ENGINE_SIMPLE;
618     else if (buffer_eq_slen(stat_cache_string, CONST_STR_LEN("simple")))
619         sc.stat_cache_engine = STAT_CACHE_ENGINE_SIMPLE;
620 #ifdef HAVE_FAM_H
621     else if (buffer_eq_slen(stat_cache_string, CONST_STR_LEN("fam")))
622         sc.stat_cache_engine = STAT_CACHE_ENGINE_FAM;
623 #endif
624     else if (buffer_eq_slen(stat_cache_string, CONST_STR_LEN("disable")))
625         sc.stat_cache_engine = STAT_CACHE_ENGINE_NONE;
626     else {
627         log_error(errh, __FILE__, __LINE__,
628           "server.stat-cache-engine can be one of \"disable\", \"simple\","
629 #ifdef HAVE_FAM_H
630           " \"fam\","
631 #endif
632           " but not: %s", stat_cache_string->ptr);
633         return -1;
634     }
635     return 0;
636 }
637 
638 const buffer * stat_cache_mimetype_by_ext(const array * const mimetypes, const char * const name, const uint32_t nlen)
639 {
640     const char * const end = name + nlen; /*(end of string)*/
641     const uint32_t used = mimetypes->used;
642     if (used < 16) {
643         for (uint32_t i = 0; i < used; ++i) {
644             /* suffix match */
645             const data_string *ds = (data_string *)mimetypes->data[i];
646             const size_t klen = buffer_string_length(&ds->key);
647             if (klen <= nlen && buffer_eq_icase_ssn(end-klen, ds->key.ptr, klen))
648                 return &ds->value;
649         }
650     }
651     else {
652         const char *s;
653         const data_string *ds;
654         if (nlen) {
655             for (s = end-1; s != name && *s != '/'; --s) ; /*(like memrchr())*/
656             if (*s == '/') ++s;
657         }
658         else {
659             s = name;
660         }
661         /* search for basename, then longest .ext2.ext1, then .ext1, then "" */
662         ds = (const data_string *)array_get_element_klen(mimetypes, s, end - s);
663         if (NULL != ds) return &ds->value;
664         while (++s < end) {
665             while (*s != '.' && ++s != end) ;
666             if (s == end) break;
667             /* search ".ext" then "ext" */
668             ds = (const data_string *)array_get_element_klen(mimetypes, s, end - s);
669             if (NULL != ds) return &ds->value;
670             /* repeat search without leading '.' to handle situation where
671              * admin configured mimetype.assign keys without leading '.' */
672             if (++s < end) {
673                 if (*s == '.') { --s; continue; }
674                 ds = (const data_string *)array_get_element_klen(mimetypes, s, end - s);
675                 if (NULL != ds) return &ds->value;
676             }
677         }
678         /* search for ""; catchall */
679         ds = (const data_string *)array_get_element_klen(mimetypes, CONST_STR_LEN(""));
680         if (NULL != ds) return &ds->value;
681     }
682 
683     return NULL;
684 }
685 
686 #if defined(HAVE_XATTR) || defined(HAVE_EXTATTR)
687 
688 const buffer * stat_cache_mimetype_by_xattr(const char * const name)
689 {
690     return stat_cache_attr_get(name) ? &attrb : NULL;
691 }
692 
693 const buffer * stat_cache_content_type_get_by_xattr(stat_cache_entry *sce, const array *mimetypes, int use_xattr)
694 {
695     /*(invalid caching if user config has multiple, different
696      * r->conf.mimetypes for same extension (not expected))*/
697     if (!buffer_string_is_empty(&sce->content_type)) return &sce->content_type;
698 
699     if (!S_ISREG(sce->st.st_mode)) return NULL;
700 
701     /* cache mimetype */
702     const buffer *mtype =
703       (use_xattr) ? stat_cache_mimetype_by_xattr(sce->name.ptr) : NULL;
704     if (NULL == mtype)
705         mtype = stat_cache_mimetype_by_ext(mimetypes,CONST_BUF_LEN(&sce->name));
706     if (NULL != mtype) {
707         if (sce->content_type.size) {
708             buffer_copy_buffer(&sce->content_type, mtype);
709         }
710         else if (mtype == &attrb) {
711             sce->content_type.ptr = NULL;
712             buffer_copy_buffer(&sce->content_type, mtype);
713         }
714         else {
715             /*(copy pointers from mimetypes array; avoid allocation)*/
716             sce->content_type.ptr = mtype->ptr;
717             sce->content_type.used = mtype->used;
718             /*(leave sce->content_type.size = 0 to flag not-allocated)*/
719         }
720     }
721     else
722         buffer_clear(&sce->content_type);
723 
724     return &sce->content_type;
725 }
726 
727 #else
728 
729 const buffer * stat_cache_content_type_get_by_ext(stat_cache_entry *sce, const array *mimetypes)
730 {
731     /*(invalid caching if user config has multiple, different
732      * r->conf.mimetypes for same extension (not expected))*/
733     if (!buffer_string_is_empty(&sce->content_type)) return &sce->content_type;
734 
735     if (!S_ISREG(sce->st.st_mode)) return NULL;
736 
737     /* cache mimetype */
738     const buffer * const mtype =
739       stat_cache_mimetype_by_ext(mimetypes, CONST_BUF_LEN(&sce->name));
740     if (NULL != mtype) {
741         /*(copy pointers from mimetypes array; avoid allocation)*/
742         sce->content_type.ptr = mtype->ptr;
743         sce->content_type.used = mtype->used;
744         /*(leave sce->content_type.size = 0 to flag not-allocated)*/
745     }
746     else
747         buffer_clear(&sce->content_type);
748 
749     return &sce->content_type;
750 }
751 
752 #endif
753 
754 const buffer * stat_cache_etag_get(stat_cache_entry *sce, int flags) {
755     /*(invalid caching if user cfg has multiple, different r->conf.etag_flags
756      * for same path (not expected, since etag flags should be by filesystem))*/
757     if (!buffer_string_is_empty(&sce->etag)) return &sce->etag;
758 
759     if (S_ISREG(sce->st.st_mode) || S_ISDIR(sce->st.st_mode)) {
760         if (0 == flags) return NULL;
761         etag_create(&sce->etag, &sce->st, flags);
762         return &sce->etag;
763     }
764 
765     return NULL;
766 }
767 
768 void stat_cache_update_entry(const char *name, uint32_t len,
769                              struct stat *st, buffer *etagb)
770 {
771     if (sc.stat_cache_engine == STAT_CACHE_ENGINE_NONE) return;
772     force_assert(0 != len);
773     if (name[len-1] == '/') { if (0 == --len) len = 1; }
774     splay_tree **sptree = &sc.files;
775     stat_cache_entry *sce =
776       stat_cache_sptree_find(sptree, name, len);
777     if (sce && buffer_is_equal_string(&sce->name, name, len)) {
778         sce->stat_ts = log_epoch_secs;
779         sce->st = *st; /* etagb might be NULL to clear etag (invalidate) */
780         buffer_copy_string_len(&sce->etag, CONST_BUF_LEN(etagb));
781       #if defined(HAVE_XATTR) || defined(HAVE_EXTATTR)
782         buffer_clear(&sce->content_type);
783       #endif
784     }
785 }
786 
787 void stat_cache_delete_entry(const char *name, uint32_t len)
788 {
789     if (sc.stat_cache_engine == STAT_CACHE_ENGINE_NONE) return;
790     force_assert(0 != len);
791     if (name[len-1] == '/') { if (0 == --len) len = 1; }
792     splay_tree **sptree = &sc.files;
793     stat_cache_entry *sce = stat_cache_sptree_find(sptree, name, len);
794     if (sce && buffer_is_equal_string(&sce->name, name, len)) {
795         stat_cache_entry_free(sce);
796         *sptree = splaytree_delete(*sptree, (*sptree)->key);
797     }
798 }
799 
800 void stat_cache_invalidate_entry(const char *name, uint32_t len)
801 {
802     splay_tree **sptree = &sc.files;
803     stat_cache_entry *sce = stat_cache_sptree_find(sptree, name, len);
804     if (sce && buffer_is_equal_string(&sce->name, name, len)) {
805         sce->stat_ts = 0;
806       #ifdef HAVE_FAM_H
807         if (sce->fam_dir != NULL) {
808             --((fam_dir_entry *)sce->fam_dir)->refcnt;
809             sce->fam_dir = NULL;
810         }
811       #endif
812     }
813 }
814 
815 #ifdef HAVE_FAM_H
816 
817 static void stat_cache_invalidate_dir_tree_walk(splay_tree *t,
818                                                 const char *name, size_t len)
819 {
820     if (t->left)  stat_cache_invalidate_dir_tree_walk(t->left,  name, len);
821     if (t->right) stat_cache_invalidate_dir_tree_walk(t->right, name, len);
822 
823     buffer *b = &((stat_cache_entry *)t->data)->name;
824     size_t blen = buffer_string_length(b);
825     if (blen > len && b->ptr[len] == '/' && 0 == memcmp(b->ptr, name, len)) {
826         stat_cache_entry *sce = t->data;
827         sce->stat_ts = 0;
828         if (sce->fam_dir != NULL) {
829             --((fam_dir_entry *)sce->fam_dir)->refcnt;
830             sce->fam_dir = NULL;
831         }
832     }
833 }
834 
835 static void stat_cache_invalidate_dir_tree(const char *name, size_t len)
836 {
837     splay_tree * const sptree = sc.files;
838     if (sptree) stat_cache_invalidate_dir_tree_walk(sptree, name, len);
839 }
840 
841 #endif
842 
843 /*
844  * walk though splay_tree and collect contents of dir tree.
845  * remove tagged entries in a second loop
846  */
847 
848 static void stat_cache_tag_dir_tree(splay_tree *t, const char *name, size_t len,
849                                     int *keys, int *ndx)
850 {
851     if (*ndx == 8192) return; /*(must match num array entries in keys[])*/
852     if (t->left)  stat_cache_tag_dir_tree(t->left,  name, len, keys, ndx);
853     if (t->right) stat_cache_tag_dir_tree(t->right, name, len, keys, ndx);
854     if (*ndx == 8192) return; /*(must match num array entries in keys[])*/
855 
856     buffer *b = &((stat_cache_entry *)t->data)->name;
857     size_t blen = buffer_string_length(b);
858     if (blen > len && b->ptr[len] == '/' && 0 == memcmp(b->ptr, name, len))
859         keys[(*ndx)++] = t->key;
860 }
861 
862 __attribute_noinline__
863 static void stat_cache_prune_dir_tree(const char *name, size_t len)
864 {
865     splay_tree *sptree = sc.files;
866     int max_ndx, i;
867     int keys[8192]; /* 32k size on stack */
868     do {
869         if (!sptree) break;
870         max_ndx = 0;
871         stat_cache_tag_dir_tree(sptree, name, len, keys, &max_ndx);
872         for (i = 0; i < max_ndx; ++i) {
873             const int ndx = keys[i];
874             splay_tree *node = sptree = splaytree_splay(sptree, ndx);
875             if (node && node->key == ndx) {
876                 stat_cache_entry_free(node->data);
877                 sptree = splaytree_delete(sptree, ndx);
878             }
879         }
880     } while (max_ndx == sizeof(keys)/sizeof(int));
881     sc.files = sptree;
882 }
883 
884 static void stat_cache_delete_tree(const char *name, uint32_t len)
885 {
886     stat_cache_delete_entry(name, len);
887     stat_cache_prune_dir_tree(name, len);
888 }
889 
890 void stat_cache_delete_dir(const char *name, uint32_t len)
891 {
892     force_assert(0 != len);
893     if (name[len-1] == '/') { if (0 == --len) len = 1; }
894     stat_cache_delete_tree(name, len);
895   #ifdef HAVE_FAM_H
896     if (sc.stat_cache_engine == STAT_CACHE_ENGINE_FAM) {
897         splay_tree **sptree = &sc.scf->dirs;
898         fam_dir_entry *fam_dir = stat_cache_sptree_find(sptree, name, len);
899         if (fam_dir && buffer_is_equal_string(fam_dir->name, name, len))
900             fam_dir_invalidate_node(fam_dir);
901         if (*sptree) fam_dir_invalidate_tree(*sptree, name, len);
902         fam_dir_periodic_cleanup();
903     }
904   #endif
905 }
906 
907 /***
908  *
909  *
910  *
911  * returns:
912  *  - HANDLER_FINISHED on cache-miss (don't forget to reopen the file)
913  *  - HANDLER_ERROR on stat() failed -> see errno for problem
914  */
915 
916 stat_cache_entry * stat_cache_get_entry(const buffer *name) {
917 	stat_cache_entry *sce = NULL;
918 	struct stat st;
919 	int file_ndx;
920 
921 	/* consistency: ensure lookup name does not end in '/' unless root "/"
922 	 * (but use full path given with stat(), even with trailing '/') */
923 	int final_slash = 0;
924 	size_t len = buffer_string_length(name);
925 	force_assert(0 != len);
926 	if (name->ptr[len-1] == '/') { final_slash = 1; if (0 == --len) len = 1; }
927 	/* Note: paths are expected to be normalized before calling stat_cache,
928 	 * e.g. without repeated '/' */
929 
930 	if (name->ptr[0] != '/') {
931 		errno = EINVAL;
932 		return NULL;
933 	}
934 
935 	/*
936 	 * check if the directory for this file has changed
937 	 */
938 
939 	const time_t cur_ts = log_epoch_secs;
940 
941 	file_ndx = splaytree_djbhash(name->ptr, len);
942 	splay_tree * const sptree = sc.files = splaytree_splay(sc.files, file_ndx);
943 
944 	if (sptree && (sptree->key == file_ndx)) {
945 		/* we have seen this file already and
946 		 * don't stat() it again in the same second */
947 
948 		sce = sptree->data;
949 
950 		/* check if the name is the same, we might have a collision */
951 
952 		if (buffer_is_equal_string(&sce->name, name->ptr, len)) {
953 			if (sc.stat_cache_engine == STAT_CACHE_ENGINE_SIMPLE) {
954 				if (sce->stat_ts == cur_ts) {
955 					if (final_slash && !S_ISDIR(sce->st.st_mode)) {
956 						errno = ENOTDIR;
957 						return NULL;
958 					}
959 					return sce;
960 				}
961 			}
962 		      #ifdef HAVE_FAM_H
963 			else if (sc.stat_cache_engine == STAT_CACHE_ENGINE_FAM
964 				 && sce->fam_dir) { /* entry is in monitored dir */
965 				/* re-stat() periodically, even if monitoring for changes
966 				 * (due to limitations in stat_cache.c use of FAM)
967 				 * (gaps due to not continually monitoring an entire tree) */
968 				if (cur_ts - sce->stat_ts < 16) {
969 					if (final_slash && !S_ISDIR(sce->st.st_mode)) {
970 						errno = ENOTDIR;
971 						return NULL;
972 					}
973 					return sce;
974 				}
975 			}
976 		      #endif
977 		} else {
978 			/* collision, forget about the entry */
979 			sce = NULL;
980 		}
981 	}
982 
983 	if (-1 == stat(name->ptr, &st)) {
984 		return NULL;
985 	}
986 
987 	if (S_ISREG(st.st_mode)) {
988 		/* fix broken stat/open for symlinks to reg files with appended slash on freebsd,osx */
989 		if (name->ptr[buffer_string_length(name) - 1] == '/') {
990 			errno = ENOTDIR;
991 			return NULL;
992 		}
993 	}
994 
995 	if (NULL == sce) {
996 
997 		sce = stat_cache_entry_init();
998 		buffer_copy_string_len(&sce->name, name->ptr, len);
999 
1000 		/* already splayed file_ndx */
1001 		if (NULL != sptree && sptree->key == file_ndx) {
1002 			/* hash collision: replace old entry */
1003 			stat_cache_entry_free(sptree->data);
1004 			sptree->data = sce;
1005 		} else {
1006 			sc.files = splaytree_insert(sptree, file_ndx, sce);
1007 		}
1008 
1009 	} else {
1010 
1011 		buffer_clear(&sce->etag);
1012 	      #if defined(HAVE_XATTR) || defined(HAVE_EXTATTR)
1013 		buffer_clear(&sce->content_type);
1014 	      #endif
1015 
1016 	}
1017 
1018 	sce->st = st; /*(copy prior to calling fam_dir_monitor())*/
1019 
1020 #ifdef HAVE_FAM_H
1021 	if (sc.stat_cache_engine == STAT_CACHE_ENGINE_FAM) {
1022 		if (sce->fam_dir) --((fam_dir_entry *)sce->fam_dir)->refcnt;
1023 		sce->fam_dir =
1024 		  fam_dir_monitor(sc.scf, CONST_BUF_LEN(name), &st);
1025 	      #if 0 /*(performed below)*/
1026 		if (NULL != sce->fam_dir) {
1027 			/*(may have been invalidated by dir change)*/
1028 			sce->stat_ts = cur_ts;
1029 		}
1030 	      #endif
1031 	}
1032 #endif
1033 
1034 	sce->stat_ts = cur_ts;
1035 	return sce;
1036 }
1037 
1038 int stat_cache_path_contains_symlink(const buffer *name, log_error_st *errh) {
1039     /* caller should check for symlinks only if we should block symlinks. */
1040 
1041     /* catch the obvious symlinks
1042      *
1043      * this is not a secure check as we still have a race-condition between
1044      * the stat() and the open. We can only solve this by
1045      * 1. open() the file
1046      * 2. fstat() the fd
1047      *
1048      * and keeping the file open for the rest of the time. But this can
1049      * only be done at network level.
1050      * */
1051 
1052   #ifdef HAVE_LSTAT
1053     /* we assume "/" can not be symlink,
1054      * so skip the symlink stuff if path is "/" */
1055     size_t len = buffer_string_length(name);
1056     force_assert(0 != len);
1057     force_assert(name->ptr[0] == '/');
1058     if (1 == len) return 0;
1059    #ifndef PATH_MAX
1060    #define PATH_MAX 4096
1061    #endif
1062     if (len >= PATH_MAX) return -1;
1063 
1064     char buf[PATH_MAX];
1065     memcpy(buf, name->ptr, len);
1066     char *s_cur = buf+len;
1067     do {
1068         *s_cur = '\0';
1069         struct stat st;
1070         if (0 == lstat(buf, &st)) {
1071             if (S_ISLNK(st.st_mode)) return 1;
1072         }
1073         else {
1074             log_perror(errh, __FILE__, __LINE__, "lstat failed for: %s", buf);
1075             return -1;
1076         }
1077     } while ((s_cur = strrchr(buf, '/')) > buf); /*(&buf[0]==buf; NULL < buf)*/
1078   #endif
1079 
1080     return 0;
1081 }
1082 
1083 int stat_cache_open_rdonly_fstat (const buffer *name, struct stat *st, int symlinks) {
1084 	/*(Note: O_NOFOLLOW affects only the final path segment, the target file,
1085 	 * not any intermediate symlinks along the path)*/
1086 	const int fd = fdevent_open_cloexec(name->ptr, symlinks, O_RDONLY, 0);
1087 	if (fd >= 0) {
1088 		if (0 == fstat(fd, st)) {
1089 			return fd;
1090 		} else {
1091 			close(fd);
1092 		}
1093 	}
1094 	return -1;
1095 }
1096 
1097 /**
1098  * remove stat() from cache which haven't been stat()ed for
1099  * more than 2 seconds
1100  *
1101  *
1102  * walk though the stat-cache, collect the ids which are too old
1103  * and remove them in a second loop
1104  */
1105 
1106 static void stat_cache_tag_old_entries(splay_tree * const t, int * const keys, int * const ndx, const time_t max_age, const time_t cur_ts) {
1107     if (*ndx == 8192) return; /*(must match num array entries in keys[])*/
1108     if (t->left)
1109         stat_cache_tag_old_entries(t->left, keys, ndx, max_age, cur_ts);
1110     if (t->right)
1111         stat_cache_tag_old_entries(t->right, keys, ndx, max_age, cur_ts);
1112     if (*ndx == 8192) return; /*(must match num array entries in keys[])*/
1113 
1114     const stat_cache_entry * const sce = t->data;
1115     if (cur_ts - sce->stat_ts > max_age)
1116         keys[(*ndx)++] = t->key;
1117 }
1118 
1119 static void stat_cache_periodic_cleanup(const time_t max_age, const time_t cur_ts) {
1120     splay_tree *sptree = sc.files;
1121     int max_ndx, i;
1122     int keys[8192]; /* 32k size on stack */
1123     do {
1124         if (!sptree) break;
1125         max_ndx = 0;
1126         stat_cache_tag_old_entries(sptree, keys, &max_ndx, max_age, cur_ts);
1127         for (i = 0; i < max_ndx; ++i) {
1128             int ndx = keys[i];
1129             sptree = splaytree_splay(sptree, ndx);
1130             if (sptree && sptree->key == ndx) {
1131                 stat_cache_entry_free(sptree->data);
1132                 sptree = splaytree_delete(sptree, ndx);
1133             }
1134         }
1135     } while (max_ndx == sizeof(keys)/sizeof(int));
1136     sc.files = sptree;
1137 }
1138 
1139 void stat_cache_trigger_cleanup(void) {
1140 	time_t max_age = 2;
1141 
1142       #ifdef HAVE_FAM_H
1143 	if (STAT_CACHE_ENGINE_FAM == sc.stat_cache_engine) {
1144 		if (log_epoch_secs & 0x1F) return;
1145 		/* once every 32 seconds (0x1F == 31) */
1146 		max_age = 32;
1147 		fam_dir_periodic_cleanup();
1148 		/* By doing this before stat_cache_periodic_cleanup(),
1149 		 * entries used within the next max_age secs will remain
1150 		 * monitored, instead of effectively flushing and
1151 		 * rebuilding the FAM monitoring every max_age seconds */
1152 	}
1153       #endif
1154 
1155 	stat_cache_periodic_cleanup(max_age, log_epoch_secs);
1156 }
1157