[autobuild] allow sendfile() in cross-compile (fixes #2836)
[lighttpd.git] / src / stat_cache.c
blob895db7342c98d2e77cdb3ca5248069e0ba8a1d46
1 #include "first.h"
3 #include "log.h"
4 #include "stat_cache.h"
5 #include "fdevent.h"
6 #include "etag.h"
7 #include "splaytree.h"
9 #include <sys/types.h>
10 #include <sys/stat.h>
12 #include <stdlib.h>
13 #include <string.h>
14 #include <errno.h>
15 #include <unistd.h>
16 #include <fcntl.h>
18 #ifdef HAVE_ATTR_ATTRIBUTES_H
19 # include <attr/attributes.h>
20 #endif
22 #ifdef HAVE_SYS_EXTATTR_H
23 # include <sys/extattr.h>
24 #endif
26 #ifndef HAVE_LSTAT
27 # define lstat stat
28 #endif
31 * stat-cache
33 * we cache the stat() calls in our own storage
34 * the directories are cached in FAM
36 * if we get a change-event from FAM, we increment the version in the FAM->dir mapping
38 * if the stat()-cache is queried we check if the version id for the directory is the
39 * same and return immediatly.
42 * What we need:
44 * - for each stat-cache entry we need a fast indirect lookup on the directory name
45 * - for each FAMRequest we have to find the version in the directory cache (index as userdata)
47 * stat <<-> directory <-> FAMRequest
49 * if file is deleted, directory is dirty, file is rechecked ...
50 * if directory is deleted, directory mapping is removed
52 * */
54 /* the directory name is too long to always compare on it
55 * - we need a hash
56 * - the hash-key is used as sorting criteria for a tree
57 * - a splay-tree is used as we can use the caching effect of it
60 /* we want to cleanup the stat-cache every few seconds, let's say 10
62 * - remove entries which are outdated since 30s
63 * - remove entries which are fresh but havn't been used since 60s
64 * - if we don't have a stat-cache entry for a directory, release it from the monitor
68 enum {
69 STAT_CACHE_ENGINE_UNSET,
70 STAT_CACHE_ENGINE_NONE,
71 STAT_CACHE_ENGINE_SIMPLE,
72 STAT_CACHE_ENGINE_FAM
75 #ifdef HAVE_FAM_H
76 struct stat_cache_fam;
77 #endif
79 typedef struct stat_cache {
80 splay_tree *files; /* the nodes of the tree are stat_cache_entry's */
81 buffer *hash_key; /* temp-store for the hash-key */
82 #ifdef HAVE_FAM_H
83 struct stat_cache_fam *scf;
84 #endif
85 } stat_cache;
88 /* the famous DJB hash function for strings */
89 static uint32_t hashme(buffer *str) {
90 uint32_t hash = 5381;
91 const char *s;
92 for (s = str->ptr; *s; s++) {
93 hash = ((hash << 5) + hash) + *s;
96 hash &= ~(((uint32_t)1) << 31); /* strip the highest bit */
98 return hash;
102 #ifdef HAVE_FAM_H
104 #include <fam.h>
106 typedef struct {
107 FAMRequest *req;
108 buffer *name;
109 int version;
110 } fam_dir_entry;
112 typedef struct stat_cache_fam {
113 splay_tree *dirs; /* the nodes of the tree are fam_dir_entry */
115 FAMConnection fam;
116 int fam_fcce_ndx;
118 int dir_ndx;
119 fam_dir_entry *fam_dir;
120 buffer *dir_name; /* for building the dirname from the filename */
121 buffer *hash_key; /* temp-store for the hash-key */
122 } stat_cache_fam;
124 static fam_dir_entry * fam_dir_entry_init(void) {
125 fam_dir_entry *fam_dir = NULL;
127 fam_dir = calloc(1, sizeof(*fam_dir));
128 force_assert(NULL != fam_dir);
130 fam_dir->name = buffer_init();
132 return fam_dir;
135 static void fam_dir_entry_free(FAMConnection *fc, void *data) {
136 fam_dir_entry *fam_dir = data;
138 if (!fam_dir) return;
140 FAMCancelMonitor(fc, fam_dir->req);
142 buffer_free(fam_dir->name);
143 free(fam_dir->req);
145 free(fam_dir);
148 static handler_t stat_cache_handle_fdevent(server *srv, void *_fce, int revent) {
149 size_t i;
150 stat_cache_fam *scf = srv->stat_cache->scf;
151 size_t events;
153 UNUSED(_fce);
154 /* */
156 if (revent & FDEVENT_IN) {
157 events = FAMPending(&scf->fam);
159 for (i = 0; i < events; i++) {
160 FAMEvent fe;
161 fam_dir_entry *fam_dir;
162 splay_tree *node;
163 int ndx, j;
165 FAMNextEvent(&scf->fam, &fe);
167 /* handle event */
169 switch(fe.code) {
170 case FAMChanged:
171 case FAMDeleted:
172 case FAMMoved:
173 /* if the filename is a directory remove the entry */
175 fam_dir = fe.userdata;
176 fam_dir->version++;
178 /* file/dir is still here */
179 if (fe.code == FAMChanged) break;
181 /* we have 2 versions, follow and no-follow-symlink */
183 for (j = 0; j < 2; j++) {
184 buffer_copy_string(scf->hash_key, fe.filename);
185 buffer_append_int(scf->hash_key, j);
187 ndx = hashme(scf->hash_key);
189 scf->dirs = splaytree_splay(scf->dirs, ndx);
190 node = scf->dirs;
192 if (node && (node->key == ndx)) {
193 int osize = splaytree_size(scf->dirs);
195 fam_dir_entry_free(&scf->fam, node->data);
196 scf->dirs = splaytree_delete(scf->dirs, ndx);
198 force_assert(osize - 1 == splaytree_size(scf->dirs));
201 break;
202 default:
203 break;
208 if (revent & FDEVENT_HUP) {
209 /* fam closed the connection */
210 fdevent_event_del(srv->ev, &(scf->fam_fcce_ndx), FAMCONNECTION_GETFD(&scf->fam));
211 fdevent_unregister(srv->ev, FAMCONNECTION_GETFD(&scf->fam));
213 FAMClose(&scf->fam);
216 return HANDLER_GO_ON;
219 static stat_cache_fam * stat_cache_init_fam(server *srv) {
220 stat_cache_fam *scf = calloc(1, sizeof(*scf));
221 scf->fam_fcce_ndx = -1;
222 scf->dir_name = buffer_init();
223 scf->hash_key = buffer_init();
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;
231 #ifdef HAVE_FAMNOEXISTS
232 FAMNoExists(&scf->fam);
233 #endif
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);
239 return scf;
242 static void stat_cache_free_fam(stat_cache_fam *scf) {
243 if (NULL == scf) return;
244 buffer_free(scf->dir_name);
245 buffer_free(scf->hash_key);
247 while (scf->dirs) {
248 int osize;
249 splay_tree *node = scf->dirs;
251 osize = scf->dirs->size;
253 fam_dir_entry_free(&scf->fam, node->data);
254 scf->dirs = splaytree_delete(scf->dirs, node->key);
256 if (osize == 1) {
257 force_assert(NULL == scf->dirs);
258 } else {
259 force_assert(osize == (scf->dirs->size + 1));
263 if (-1 != scf->fam_fcce_ndx) {
264 /* fd events already gone */
265 scf->fam_fcce_ndx = -1;
267 FAMClose(&scf->fam);
270 free(scf);
273 static int buffer_copy_dirname(buffer *dst, const buffer *file) {
274 size_t i;
276 if (buffer_string_is_empty(file)) return -1;
278 for (i = buffer_string_length(file); i > 0; i--) {
279 if (file->ptr[i] == '/') {
280 buffer_copy_string_len(dst, file->ptr, i);
281 return 0;
285 return -1;
288 static handler_t stat_cache_fam_dir_check(server *srv, stat_cache_fam *scf, stat_cache_entry *sce, const buffer *name, unsigned int follow_symlink) {
289 if (0 != buffer_copy_dirname(scf->dir_name, name)) {
290 log_error_write(srv, __FILE__, __LINE__, "sb",
291 "no '/' found in filename:", name);
292 return HANDLER_ERROR;
295 buffer_copy_buffer(scf->hash_key, scf->dir_name);
296 buffer_append_int(scf->hash_key, (int)follow_symlink);
298 scf->dir_ndx = hashme(scf->hash_key);
300 scf->dirs = splaytree_splay(scf->dirs, scf->dir_ndx);
302 if ((NULL != scf->dirs) && (scf->dirs->key == scf->dir_ndx)) {
303 scf->fam_dir = scf->dirs->data;
305 /* check whether we got a collision */
306 if (buffer_is_equal(scf->dir_name, scf->fam_dir->name)) {
307 /* test whether a found file cache entry is still ok */
308 if ((NULL != sce) && (scf->fam_dir->version == sce->dir_version)) {
309 /* the stat()-cache entry is still ok */
310 return HANDLER_FINISHED;
312 } else {
313 /* hash collision, forget about the entry */
314 scf->fam_dir = NULL;
316 } else {
317 scf->fam_dir = NULL;
320 return HANDLER_GO_ON;
323 static void stat_cache_fam_dir_monitor(server *srv, stat_cache_fam *scf, stat_cache_entry *sce, const buffer *name) {
324 /* is this directory already registered ? */
325 fam_dir_entry *fam_dir = scf->fam_dir;
326 if (NULL == fam_dir) {
327 fam_dir = fam_dir_entry_init();
329 buffer_copy_buffer(fam_dir->name, scf->dir_name);
331 fam_dir->version = 1;
333 fam_dir->req = calloc(1, sizeof(FAMRequest));
334 force_assert(NULL != fam_dir);
336 if (0 != FAMMonitorDirectory(&scf->fam, fam_dir->name->ptr,
337 fam_dir->req, fam_dir)) {
339 log_error_write(srv, __FILE__, __LINE__, "sbsbs",
340 "monitoring dir failed:",
341 fam_dir->name,
342 "file:", name,
343 FamErrlist[FAMErrno]);
345 fam_dir_entry_free(&scf->fam, fam_dir);
346 } else {
347 int osize = splaytree_size(scf->dirs);
349 /* already splayed scf->dir_ndx */
350 if ((NULL != scf->dirs) && (scf->dirs->key == scf->dir_ndx)) {
351 /* hash collision: replace old entry */
352 fam_dir_entry_free(&scf->fam, scf->dirs->data);
353 scf->dirs->data = fam_dir;
354 } else {
355 scf->dirs = splaytree_insert(scf->dirs, scf->dir_ndx, fam_dir);
356 force_assert(osize == (splaytree_size(scf->dirs) - 1));
359 force_assert(scf->dirs);
360 force_assert(scf->dirs->data == fam_dir);
361 scf->fam_dir = fam_dir;
365 /* bind the fam_fc to the stat() cache entry */
367 if (fam_dir) {
368 sce->dir_version = fam_dir->version;
372 #endif
375 stat_cache *stat_cache_init(server *srv) {
376 stat_cache *sc = NULL;
377 UNUSED(srv);
379 sc = calloc(1, sizeof(*sc));
380 force_assert(NULL != sc);
382 sc->hash_key = buffer_init();
384 #ifdef HAVE_FAM_H
385 if (STAT_CACHE_ENGINE_FAM == srv->srvconf.stat_cache_engine) {
386 sc->scf = stat_cache_init_fam(srv);
387 if (NULL == sc->scf) {
388 free(sc);
389 return NULL;
392 #endif
394 return sc;
397 static stat_cache_entry * stat_cache_entry_init(void) {
398 stat_cache_entry *sce = NULL;
400 sce = calloc(1, sizeof(*sce));
401 force_assert(NULL != sce);
403 sce->name = buffer_init();
404 sce->etag = buffer_init();
405 sce->content_type = buffer_init();
407 return sce;
410 static void stat_cache_entry_free(void *data) {
411 stat_cache_entry *sce = data;
412 if (!sce) return;
414 buffer_free(sce->etag);
415 buffer_free(sce->name);
416 buffer_free(sce->content_type);
418 free(sce);
421 void stat_cache_free(stat_cache *sc) {
422 while (sc->files) {
423 int osize;
424 splay_tree *node = sc->files;
426 osize = sc->files->size;
428 stat_cache_entry_free(node->data);
429 sc->files = splaytree_delete(sc->files, node->key);
431 force_assert(osize - 1 == splaytree_size(sc->files));
434 buffer_free(sc->hash_key);
436 #ifdef HAVE_FAM_H
437 stat_cache_free_fam(sc->scf);
438 #endif
439 free(sc);
442 int stat_cache_choose_engine (server *srv, const buffer *stat_cache_string) {
443 if (buffer_string_is_empty(stat_cache_string)) {
444 srv->srvconf.stat_cache_engine = STAT_CACHE_ENGINE_SIMPLE;
445 } else if (buffer_is_equal_string(stat_cache_string, CONST_STR_LEN("simple"))) {
446 srv->srvconf.stat_cache_engine = STAT_CACHE_ENGINE_SIMPLE;
447 #ifdef HAVE_FAM_H
448 } else if (buffer_is_equal_string(stat_cache_string, CONST_STR_LEN("fam"))) {
449 srv->srvconf.stat_cache_engine = STAT_CACHE_ENGINE_FAM;
450 #endif
451 } else if (buffer_is_equal_string(stat_cache_string, CONST_STR_LEN("disable"))) {
452 srv->srvconf.stat_cache_engine = STAT_CACHE_ENGINE_NONE;
453 } else {
454 log_error_write(srv, __FILE__, __LINE__, "sb",
455 "server.stat-cache-engine can be one of \"disable\", \"simple\","
456 #ifdef HAVE_FAM_H
457 " \"fam\","
458 #endif
459 " but not:", stat_cache_string);
460 return -1;
462 return 0;
465 #if defined(HAVE_XATTR)
466 static int stat_cache_attr_get(buffer *buf, char *name, char *xattrname) {
467 int attrlen;
468 int ret;
470 buffer_string_prepare_copy(buf, 1023);
471 attrlen = buf->size - 1;
472 if(0 == (ret = attr_get(name, xattrname, buf->ptr, &attrlen, 0))) {
473 buffer_commit(buf, attrlen);
475 return ret;
477 #elif defined(HAVE_EXTATTR)
478 static int stat_cache_attr_get(buffer *buf, char *name, char *xattrname) {
479 ssize_t attrlen;
481 buffer_string_prepare_copy(buf, 1023);
483 if (-1 != (attrlen = extattr_get_file(name, EXTATTR_NAMESPACE_USER, xattrname, buf->ptr, buf->size - 1))) {
484 buf->used = attrlen + 1;
485 buf->ptr[attrlen] = '\0';
486 return 0;
488 return -1;
490 #endif
492 const buffer * stat_cache_mimetype_by_ext(const connection *con, const char *name, size_t nlen)
494 const char *end = name + nlen; /*(end of string)*/
495 const size_t used = con->conf.mimetypes->used;
496 if (used < 16) {
497 for (size_t i = 0; i < used; ++i) {
498 /* suffix match */
499 const data_string *ds = (data_string *)con->conf.mimetypes->data[i];
500 const size_t klen = buffer_string_length(ds->key);
501 if (klen <= nlen && 0 == strncasecmp(end-klen, ds->key->ptr, klen))
502 return ds->value;
505 else {
506 const char *s;
507 const data_string *ds;
508 if (nlen) {
509 for (s = end-1; s != name && *s != '/'; --s) ; /*(like memrchr())*/
510 if (*s == '/') ++s;
512 else {
513 s = name;
515 /* search for basename, then longest .ext2.ext1, then .ext1, then "" */
516 ds = (data_string *)array_get_element_klen(con->conf.mimetypes, s, end - s);
517 if (NULL != ds) return ds->value;
518 while (++s < end) {
519 while (*s != '.' && ++s != end) ;
520 if (s == end) break;
521 /* search ".ext" then "ext" */
522 ds = (data_string *)array_get_element_klen(con->conf.mimetypes, s, end - s);
523 if (NULL != ds) return ds->value;
524 /* repeat search without leading '.' to handle situation where
525 * admin configured mimetype.assign keys without leading '.' */
526 if (++s < end) {
527 if (*s == '.') { --s; continue; }
528 ds = (data_string *)array_get_element_klen(con->conf.mimetypes, s, end - s);
529 if (NULL != ds) return ds->value;
532 /* search for ""; catchall */
533 ds = (data_string *)array_get_element(con->conf.mimetypes, "");
534 if (NULL != ds) return ds->value;
537 return NULL;
540 #ifdef HAVE_LSTAT
541 static int stat_cache_lstat(server *srv, buffer *dname, struct stat *lst) {
542 if (lstat(dname->ptr, lst) == 0) {
543 return S_ISLNK(lst->st_mode) ? 0 : 1;
545 else {
546 log_error_write(srv, __FILE__, __LINE__, "sbs",
547 "lstat failed for:",
548 dname, strerror(errno));
550 return -1;
552 #endif
554 /***
558 * returns:
559 * - HANDLER_FINISHED on cache-miss (don't forget to reopen the file)
560 * - HANDLER_ERROR on stat() failed -> see errno for problem
563 handler_t stat_cache_get_entry(server *srv, connection *con, buffer *name, stat_cache_entry **ret_sce) {
564 stat_cache_entry *sce = NULL;
565 stat_cache *sc;
566 struct stat st;
567 int fd;
568 struct stat lst;
569 int file_ndx;
571 *ret_sce = NULL;
574 * check if the directory for this file has changed
577 sc = srv->stat_cache;
579 buffer_copy_buffer(sc->hash_key, name);
580 buffer_append_int(sc->hash_key, con->conf.follow_symlink);
582 file_ndx = hashme(sc->hash_key);
583 sc->files = splaytree_splay(sc->files, file_ndx);
585 if (sc->files && (sc->files->key == file_ndx)) {
586 /* we have seen this file already and
587 * don't stat() it again in the same second */
589 sce = sc->files->data;
591 /* check if the name is the same, we might have a collision */
593 if (buffer_is_equal(name, sce->name)) {
594 if (srv->srvconf.stat_cache_engine == STAT_CACHE_ENGINE_SIMPLE) {
595 if (sce->stat_ts == srv->cur_ts && con->conf.follow_symlink) {
596 *ret_sce = sce;
597 return HANDLER_GO_ON;
600 } else {
601 /* collision, forget about the entry */
602 sce = NULL;
606 #ifdef HAVE_FAM_H
607 /* dir-check */
608 if (srv->srvconf.stat_cache_engine == STAT_CACHE_ENGINE_FAM) {
609 switch (stat_cache_fam_dir_check(srv, sc->scf, sce, name, con->conf.follow_symlink)) {
610 case HANDLER_GO_ON:
611 break;
612 case HANDLER_FINISHED:
613 *ret_sce = sce;
614 return HANDLER_GO_ON;
615 case HANDLER_ERROR:
616 default:
617 return HANDLER_ERROR;
620 #endif
623 * *lol*
624 * - open() + fstat() on a named-pipe results in a (intended) hang.
625 * - stat() if regular file + open() to see if we can read from it is better
627 * */
628 if (-1 == stat(name->ptr, &st)) {
629 return HANDLER_ERROR;
633 if (S_ISREG(st.st_mode)) {
634 /* fix broken stat/open for symlinks to reg files with appended slash on freebsd,osx */
635 if (name->ptr[buffer_string_length(name) - 1] == '/') {
636 errno = ENOTDIR;
637 return HANDLER_ERROR;
640 /* try to open the file to check if we can read it */
641 if (-1 == (fd = open(name->ptr, O_RDONLY))) {
642 return HANDLER_ERROR;
644 close(fd);
647 if (NULL == sce) {
649 sce = stat_cache_entry_init();
650 buffer_copy_buffer(sce->name, name);
652 /* already splayed file_ndx */
653 if ((NULL != sc->files) && (sc->files->key == file_ndx)) {
654 /* hash collision: replace old entry */
655 stat_cache_entry_free(sc->files->data);
656 sc->files->data = sce;
657 } else {
658 int osize = splaytree_size(sc->files);
660 sc->files = splaytree_insert(sc->files, file_ndx, sce);
661 force_assert(osize + 1 == splaytree_size(sc->files));
663 force_assert(sc->files);
664 force_assert(sc->files->data == sce);
667 sce->st = st;
668 sce->stat_ts = srv->cur_ts;
670 /* catch the obvious symlinks
672 * this is not a secure check as we still have a race-condition between
673 * the stat() and the open. We can only solve this by
674 * 1. open() the file
675 * 2. fstat() the fd
677 * and keeping the file open for the rest of the time. But this can
678 * only be done at network level.
680 * per default it is not a symlink
681 * */
682 #ifdef HAVE_LSTAT
683 sce->is_symlink = 0;
685 /* we want to only check for symlinks if we should block symlinks.
687 if (!con->conf.follow_symlink) {
688 if (stat_cache_lstat(srv, name, &lst) == 0) {
689 sce->is_symlink = 1;
693 * we assume "/" can not be symlink, so
694 * skip the symlink stuff if our path is /
696 else if (buffer_string_length(name) > 1) {
697 buffer *dname;
698 char *s_cur;
700 dname = buffer_init();
701 buffer_copy_buffer(dname, name);
703 while ((s_cur = strrchr(dname->ptr, '/'))) {
704 buffer_string_set_length(dname, s_cur - dname->ptr);
705 if (dname->ptr == s_cur) {
706 break;
708 if (stat_cache_lstat(srv, dname, &lst) == 0) {
709 sce->is_symlink = 1;
710 break;
713 buffer_free(dname);
716 #endif
718 if (S_ISREG(st.st_mode)) {
719 /* determine mimetype */
720 buffer_reset(sce->content_type);
721 #if defined(HAVE_XATTR) || defined(HAVE_EXTATTR)
722 if (con->conf.use_xattr) {
723 stat_cache_attr_get(sce->content_type, name->ptr, srv->srvconf.xattr_name->ptr);
725 #endif
726 /* xattr did not set a content-type. ask the config */
727 if (buffer_string_is_empty(sce->content_type)) {
728 const buffer *type = stat_cache_mimetype_by_ext(con, CONST_BUF_LEN(name));
729 if (NULL != type) {
730 buffer_copy_buffer(sce->content_type, type);
733 etag_create(sce->etag, &(sce->st), con->etag_flags);
734 } else if (S_ISDIR(st.st_mode)) {
735 etag_create(sce->etag, &(sce->st), con->etag_flags);
738 #ifdef HAVE_FAM_H
739 if (srv->srvconf.stat_cache_engine == STAT_CACHE_ENGINE_FAM) {
740 stat_cache_fam_dir_monitor(srv, sc->scf, sce, name);
742 #endif
744 *ret_sce = sce;
746 return HANDLER_GO_ON;
749 int stat_cache_open_rdonly_fstat (server *srv, connection *con, buffer *name, struct stat *st) {
750 /*(Note: O_NOFOLLOW affects only the final path segment, the target file,
751 * not any intermediate symlinks along the path)*/
752 #ifndef O_BINARY
753 #define O_BINARY 0
754 #endif
755 #ifndef O_LARGEFILE
756 #define O_LARGEFILE 0
757 #endif
758 #ifndef O_NOCTTY
759 #define O_NOCTTY 0
760 #endif
761 #ifndef O_NONBLOCK
762 #define O_NONBLOCK 0
763 #endif
764 #ifndef O_NOFOLLOW
765 #define O_NOFOLLOW 0
766 #endif
767 const int oflags = O_BINARY | O_LARGEFILE | O_NOCTTY | O_NONBLOCK
768 | (con->conf.follow_symlink ? 0 : O_NOFOLLOW);
769 const int fd = open(name->ptr, O_RDONLY | oflags);
770 if (fd >= 0) {
771 if (0 == fstat(fd, st)) {
772 return fd;
773 } else {
774 close(fd);
777 UNUSED(srv); /*(might log_error_write(srv, ...) in the future)*/
778 return -1;
782 * remove stat() from cache which havn't been stat()ed for
783 * more than 10 seconds
786 * walk though the stat-cache, collect the ids which are too old
787 * and remove them in a second loop
790 static int stat_cache_tag_old_entries(server *srv, splay_tree *t, int *keys, size_t *ndx) {
791 stat_cache_entry *sce;
793 if (!t) return 0;
795 stat_cache_tag_old_entries(srv, t->left, keys, ndx);
796 stat_cache_tag_old_entries(srv, t->right, keys, ndx);
798 sce = t->data;
800 if (srv->cur_ts - sce->stat_ts > 2) {
801 keys[(*ndx)++] = t->key;
804 return 0;
807 int stat_cache_trigger_cleanup(server *srv) {
808 stat_cache *sc;
809 size_t max_ndx = 0, i;
810 int *keys;
812 sc = srv->stat_cache;
814 if (!sc->files) return 0;
816 keys = calloc(1, sizeof(int) * sc->files->size);
817 force_assert(NULL != keys);
819 stat_cache_tag_old_entries(srv, sc->files, keys, &max_ndx);
821 for (i = 0; i < max_ndx; i++) {
822 int ndx = keys[i];
823 splay_tree *node;
825 sc->files = splaytree_splay(sc->files, ndx);
827 node = sc->files;
829 if (node && (node->key == ndx)) {
830 stat_cache_entry_free(node->data);
831 sc->files = splaytree_delete(sc->files, ndx);
835 free(keys);
837 return 0;