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