| version 1.1.1.2, 2014/06/15 20:20:06 | version 1.1.1.3, 2016/11/02 10:35:00 | 
| Line 1 | Line 1 | 
 |  | #include "first.h" | 
 |  |  | 
 | #include "log.h" | #include "log.h" | 
 | #include "stat_cache.h" | #include "stat_cache.h" | 
 | #include "fdevent.h" | #include "fdevent.h" | 
| Line 18 | Line 20 | 
 | # include <attr/attributes.h> | # include <attr/attributes.h> | 
 | #endif | #endif | 
 |  |  | 
 |  | #ifdef HAVE_SYS_EXTATTR_H | 
 |  | # include <sys/extattr.h> | 
 |  | #endif | 
 |  |  | 
 | #ifdef HAVE_FAM_H | #ifdef HAVE_FAM_H | 
 | # include <fam.h> | # include <fam.h> | 
 | #endif | #endif | 
| Line 104  stat_cache *stat_cache_init(void) { | Line 110  stat_cache *stat_cache_init(void) { | 
 | stat_cache *sc = NULL; | stat_cache *sc = NULL; | 
 |  |  | 
 | sc = calloc(1, sizeof(*sc)); | sc = calloc(1, sizeof(*sc)); | 
 |  | force_assert(NULL != sc); | 
 |  |  | 
 | sc->dir_name = buffer_init(); | sc->dir_name = buffer_init(); | 
 | sc->hash_key = buffer_init(); | sc->hash_key = buffer_init(); | 
| Line 123  static stat_cache_entry * stat_cache_entry_init(void) | Line 130  static stat_cache_entry * stat_cache_entry_init(void) | 
 | stat_cache_entry *sce = NULL; | stat_cache_entry *sce = NULL; | 
 |  |  | 
 | sce = calloc(1, sizeof(*sce)); | sce = calloc(1, sizeof(*sce)); | 
 |  | force_assert(NULL != sce); | 
 |  |  | 
 | sce->name = buffer_init(); | sce->name = buffer_init(); | 
 | sce->etag = buffer_init(); | sce->etag = buffer_init(); | 
| Line 147  static fam_dir_entry * fam_dir_entry_init(void) { | Line 155  static fam_dir_entry * fam_dir_entry_init(void) { | 
 | fam_dir_entry *fam_dir = NULL; | fam_dir_entry *fam_dir = NULL; | 
 |  |  | 
 | fam_dir = calloc(1, sizeof(*fam_dir)); | fam_dir = calloc(1, sizeof(*fam_dir)); | 
 |  | force_assert(NULL != fam_dir); | 
 |  |  | 
 | fam_dir->name = buffer_init(); | fam_dir->name = buffer_init(); | 
 |  |  | 
| Line 210  void stat_cache_free(stat_cache *sc) { | Line 219  void stat_cache_free(stat_cache *sc) { | 
 | free(sc); | free(sc); | 
 | } | } | 
 |  |  | 
| #ifdef HAVE_XATTR | #if defined(HAVE_XATTR) | 
| static int stat_cache_attr_get(buffer *buf, char *name) { | static int stat_cache_attr_get(buffer *buf, char *name, char *xattrname) { | 
 | int attrlen; | int attrlen; | 
 | int ret; | int ret; | 
 |  |  | 
| attrlen = 1024; | buffer_string_prepare_copy(buf, 1023); | 
| buffer_prepare_copy(buf, attrlen); | attrlen = buf->size - 1; | 
| attrlen--; | if(0 == (ret = attr_get(name, xattrname, buf->ptr, &attrlen, 0))) { | 
| if(0 == (ret = attr_get(name, "Content-Type", buf->ptr, &attrlen, 0))) { | buffer_commit(buf, attrlen); | 
|  | } | 
|  | return ret; | 
|  | } | 
|  | #elif defined(HAVE_EXTATTR) | 
|  | static int stat_cache_attr_get(buffer *buf, char *name, char *xattrname) { | 
|  | ssize_t attrlen; | 
|  |  | 
|  | buffer_string_prepare_copy(buf, 1023); | 
|  |  | 
|  | if (-1 != (attrlen = extattr_get_file(name, EXTATTR_NAMESPACE_USER, xattrname, buf->ptr, buf->size - 1))) { | 
 | buf->used = attrlen + 1; | buf->used = attrlen + 1; | 
 | buf->ptr[attrlen] = '\0'; | buf->ptr[attrlen] = '\0'; | 
 |  | return 0; | 
 | } | } | 
| return ret; | return -1; | 
 | } | } | 
 | #endif | #endif | 
 |  |  | 
| Line 234  static uint32_t hashme(buffer *str) { | Line 254  static uint32_t hashme(buffer *str) { | 
 | hash = ((hash << 5) + hash) + *s; | hash = ((hash << 5) + hash) + *s; | 
 | } | } | 
 |  |  | 
| hash &= ~(1 << 31); /* strip the highest bit */ | hash &= ~(((uint32_t)1) << 31); /* strip the highest bit */ | 
 |  |  | 
 | return hash; | return hash; | 
 | } | } | 
| Line 277  handler_t stat_cache_handle_fdevent(server *srv, void | Line 297  handler_t stat_cache_handle_fdevent(server *srv, void | 
 |  |  | 
 | for (j = 0; j < 2; j++) { | for (j = 0; j < 2; j++) { | 
 | buffer_copy_string(sc->hash_key, fe.filename); | buffer_copy_string(sc->hash_key, fe.filename); | 
| buffer_append_long(sc->hash_key, j); | buffer_append_int(sc->hash_key, j); | 
 |  |  | 
 | ndx = hashme(sc->hash_key); | ndx = hashme(sc->hash_key); | 
 |  |  | 
| Line 314  handler_t stat_cache_handle_fdevent(server *srv, void | Line 334  handler_t stat_cache_handle_fdevent(server *srv, void | 
 | static int buffer_copy_dirname(buffer *dst, buffer *file) { | static int buffer_copy_dirname(buffer *dst, buffer *file) { | 
 | size_t i; | size_t i; | 
 |  |  | 
| if (buffer_is_empty(file)) return -1; | if (buffer_string_is_empty(file)) return -1; | 
 |  |  | 
| for (i = file->used - 1; i+1 > 0; i--) { | for (i = buffer_string_length(file); i > 0; i--) { | 
 | if (file->ptr[i] == '/') { | if (file->ptr[i] == '/') { | 
 | buffer_copy_string_len(dst, file->ptr, i); | buffer_copy_string_len(dst, file->ptr, i); | 
 | return 0; | return 0; | 
| Line 354  handler_t stat_cache_get_entry(server *srv, connection | Line 374  handler_t stat_cache_get_entry(server *srv, connection | 
 | #ifdef HAVE_FAM_H | #ifdef HAVE_FAM_H | 
 | fam_dir_entry *fam_dir = NULL; | fam_dir_entry *fam_dir = NULL; | 
 | int dir_ndx = -1; | int dir_ndx = -1; | 
 | splay_tree *dir_node = NULL; |  | 
 | #endif | #endif | 
 | stat_cache_entry *sce = NULL; | stat_cache_entry *sce = NULL; | 
 | stat_cache *sc; | stat_cache *sc; | 
| Line 367  handler_t stat_cache_get_entry(server *srv, connection | Line 386  handler_t stat_cache_get_entry(server *srv, connection | 
 | #endif | #endif | 
 |  |  | 
 | int file_ndx; | int file_ndx; | 
 | splay_tree *file_node = NULL; |  | 
 |  |  | 
 | *ret_sce = NULL; | *ret_sce = NULL; | 
 |  |  | 
| Line 377  handler_t stat_cache_get_entry(server *srv, connection | Line 395  handler_t stat_cache_get_entry(server *srv, connection | 
 |  |  | 
 | sc = srv->stat_cache; | sc = srv->stat_cache; | 
 |  |  | 
| buffer_copy_string_buffer(sc->hash_key, name); | buffer_copy_buffer(sc->hash_key, name); | 
| buffer_append_long(sc->hash_key, con->conf.follow_symlink); | buffer_append_int(sc->hash_key, con->conf.follow_symlink); | 
 |  |  | 
 | file_ndx = hashme(sc->hash_key); | file_ndx = hashme(sc->hash_key); | 
 | sc->files = splaytree_splay(sc->files, file_ndx); | sc->files = splaytree_splay(sc->files, file_ndx); | 
| Line 398  handler_t stat_cache_get_entry(server *srv, connection | Line 416  handler_t stat_cache_get_entry(server *srv, connection | 
 | /* we have seen this file already and | /* we have seen this file already and | 
 | * don't stat() it again in the same second */ | * don't stat() it again in the same second */ | 
 |  |  | 
| file_node = sc->files; | sce = sc->files->data; | 
 |  |  | 
 | sce = file_node->data; |  | 
 |  |  | 
 | /* check if the name is the same, we might have a collision */ | /* check if the name is the same, we might have a collision */ | 
 |  |  | 
 | if (buffer_is_equal(name, sce->name)) { | if (buffer_is_equal(name, sce->name)) { | 
 | if (srv->srvconf.stat_cache_engine == STAT_CACHE_ENGINE_SIMPLE) { | if (srv->srvconf.stat_cache_engine == STAT_CACHE_ENGINE_SIMPLE) { | 
| if (sce->stat_ts == srv->cur_ts) { | if (sce->stat_ts == srv->cur_ts && con->conf.follow_symlink) { | 
 | *ret_sce = sce; | *ret_sce = sce; | 
 | return HANDLER_GO_ON; | return HANDLER_GO_ON; | 
 | } | } | 
 | } | } | 
 | } else { | } else { | 
| /* oops, a collision, | /* collision, forget about the entry */ | 
| * | sce = NULL; | 
| * file_node is used by the FAM check below to see if we know this file |  | 
| * and if we can save a stat(). |  | 
| * |  | 
| * BUT, the sce is not reset here as the entry into the cache is ok, we |  | 
| * it is just not pointing to our requested file. |  | 
| * |  | 
| *  */ |  | 
|  |  | 
| file_node = NULL; |  | 
 | } | } | 
 | } else { | } else { | 
 | #ifdef DEBUG_STAT_CACHE | #ifdef DEBUG_STAT_CACHE | 
| Line 443  handler_t stat_cache_get_entry(server *srv, connection | Line 450  handler_t stat_cache_get_entry(server *srv, connection | 
 | return HANDLER_ERROR; | return HANDLER_ERROR; | 
 | } | } | 
 |  |  | 
| buffer_copy_string_buffer(sc->hash_key, sc->dir_name); | buffer_copy_buffer(sc->hash_key, sc->dir_name); | 
| buffer_append_long(sc->hash_key, con->conf.follow_symlink); | buffer_append_int(sc->hash_key, con->conf.follow_symlink); | 
 |  |  | 
 | dir_ndx = hashme(sc->hash_key); | dir_ndx = hashme(sc->hash_key); | 
 |  |  | 
 | sc->dirs = splaytree_splay(sc->dirs, dir_ndx); | sc->dirs = splaytree_splay(sc->dirs, dir_ndx); | 
 |  |  | 
| if (sc->dirs && (sc->dirs->key == dir_ndx)) { | if ((NULL != sc->dirs) && (sc->dirs->key == dir_ndx)) { | 
| dir_node = sc->dirs; | fam_dir = sc->dirs->data; | 
| } |  | 
 |  |  | 
| if (dir_node && file_node) { | /* check whether we got a collision */ | 
| /* we found a file */ | if (buffer_is_equal(sc->dir_name, fam_dir->name)) { | 
|  | /* test whether a found file cache entry is still ok */ | 
|  | if ((NULL != sce) && (fam_dir->version == sce->dir_version)) { | 
|  | /* the stat()-cache entry is still ok */ | 
 |  |  | 
| sce = file_node->data; | *ret_sce = sce; | 
| fam_dir = dir_node->data; | return HANDLER_GO_ON; | 
|  | } | 
| if (fam_dir->version == sce->dir_version) { | } else { | 
| /* the stat()-cache entry is still ok */ | /* hash collision, forget about the entry */ | 
|  | fam_dir = NULL; | 
| *ret_sce = sce; |  | 
| return HANDLER_GO_ON; |  | 
 | } | } | 
 | } | } | 
 | } | } | 
| Line 483  handler_t stat_cache_get_entry(server *srv, connection | Line 490  handler_t stat_cache_get_entry(server *srv, connection | 
 |  |  | 
 | if (S_ISREG(st.st_mode)) { | if (S_ISREG(st.st_mode)) { | 
 | /* fix broken stat/open for symlinks to reg files with appended slash on freebsd,osx */ | /* fix broken stat/open for symlinks to reg files with appended slash on freebsd,osx */ | 
| if (name->ptr[name->used-2] == '/') { | if (name->ptr[buffer_string_length(name) - 1] == '/') { | 
 | errno = ENOTDIR; | errno = ENOTDIR; | 
 | return HANDLER_ERROR; | return HANDLER_ERROR; | 
 | } | } | 
| Line 496  handler_t stat_cache_get_entry(server *srv, connection | Line 503  handler_t stat_cache_get_entry(server *srv, connection | 
 | } | } | 
 |  |  | 
 | if (NULL == sce) { | if (NULL == sce) { | 
 | #ifdef DEBUG_STAT_CACHE |  | 
 | int osize = splaytree_size(sc->files); |  | 
 | #endif |  | 
 |  |  | 
 | sce = stat_cache_entry_init(); | sce = stat_cache_entry_init(); | 
| buffer_copy_string_buffer(sce->name, name); | buffer_copy_buffer(sce->name, name); | 
 |  |  | 
| sc->files = splaytree_insert(sc->files, file_ndx, sce); | /* already splayed file_ndx */ | 
| #ifdef DEBUG_STAT_CACHE | if ((NULL != sc->files) && (sc->files->key == file_ndx)) { | 
| if (ctrl.size == 0) { | /* hash collision: replace old entry */ | 
| ctrl.size = 16; | stat_cache_entry_free(sc->files->data); | 
| ctrl.used = 0; | sc->files->data = sce; | 
| ctrl.ptr = malloc(ctrl.size * sizeof(*ctrl.ptr)); | } else { | 
| } else if (ctrl.size == ctrl.used) { | int osize = splaytree_size(sc->files); | 
| ctrl.size += 16; |  | 
| ctrl.ptr = realloc(ctrl.ptr, ctrl.size * sizeof(*ctrl.ptr)); |  | 
| } |  | 
 |  |  | 
| ctrl.ptr[ctrl.used++] = file_ndx; | sc->files = splaytree_insert(sc->files, file_ndx, sce); | 
|  | force_assert(osize + 1 == splaytree_size(sc->files)); | 
 |  |  | 
 |  | #ifdef DEBUG_STAT_CACHE | 
 |  | if (ctrl.size == 0) { | 
 |  | ctrl.size = 16; | 
 |  | ctrl.used = 0; | 
 |  | ctrl.ptr = malloc(ctrl.size * sizeof(*ctrl.ptr)); | 
 |  | force_assert(NULL != ctrl.ptr); | 
 |  | } else if (ctrl.size == ctrl.used) { | 
 |  | ctrl.size += 16; | 
 |  | ctrl.ptr = realloc(ctrl.ptr, ctrl.size * sizeof(*ctrl.ptr)); | 
 |  | force_assert(NULL != ctrl.ptr); | 
 |  | } | 
 |  |  | 
 |  | ctrl.ptr[ctrl.used++] = file_ndx; | 
 |  | #endif | 
 |  | } | 
 | force_assert(sc->files); | force_assert(sc->files); | 
 | force_assert(sc->files->data == sce); | force_assert(sc->files->data == sce); | 
 | force_assert(osize + 1 == splaytree_size(sc->files)); |  | 
 | #endif |  | 
 | } | } | 
 |  |  | 
 | sce->st = st; | sce->st = st; | 
| Line 555  handler_t stat_cache_get_entry(server *srv, connection | Line 570  handler_t stat_cache_get_entry(server *srv, connection | 
 | * we assume "/" can not be symlink, so | * we assume "/" can not be symlink, so | 
 | * skip the symlink stuff if our path is / | * skip the symlink stuff if our path is / | 
 | **/ | **/ | 
| else if ((name->used > 2)) { | else if (buffer_string_length(name) > 1) { | 
 | buffer *dname; | buffer *dname; | 
 | char *s_cur; | char *s_cur; | 
 |  |  | 
 | dname = buffer_init(); | dname = buffer_init(); | 
| buffer_copy_string_buffer(dname, name); | buffer_copy_buffer(dname, name); | 
 |  |  | 
| while ((s_cur = strrchr(dname->ptr,'/'))) { | while ((s_cur = strrchr(dname->ptr, '/'))) { | 
| *s_cur = '\0'; | buffer_string_set_length(dname, s_cur - dname->ptr); | 
| dname->used = s_cur - dname->ptr + 1; |  | 
 | if (dname->ptr == s_cur) { | if (dname->ptr == s_cur) { | 
 | #ifdef DEBUG_STAT_CACHE | #ifdef DEBUG_STAT_CACHE | 
 | log_error_write(srv, __FILE__, __LINE__, "s", "reached /"); | log_error_write(srv, __FILE__, __LINE__, "s", "reached /"); | 
| Line 592  handler_t stat_cache_get_entry(server *srv, connection | Line 606  handler_t stat_cache_get_entry(server *srv, connection | 
 | if (S_ISREG(st.st_mode)) { | if (S_ISREG(st.st_mode)) { | 
 | /* determine mimetype */ | /* determine mimetype */ | 
 | buffer_reset(sce->content_type); | buffer_reset(sce->content_type); | 
| #ifdef HAVE_XATTR | #if defined(HAVE_XATTR) || defined(HAVE_EXTATTR) | 
 | if (con->conf.use_xattr) { | if (con->conf.use_xattr) { | 
| stat_cache_attr_get(sce->content_type, name->ptr); | stat_cache_attr_get(sce->content_type, name->ptr, srv->srvconf.xattr_name->ptr); | 
 | } | } | 
 | #endif | #endif | 
 | /* xattr did not set a content-type. ask the config */ | /* xattr did not set a content-type. ask the config */ | 
| if (buffer_is_empty(sce->content_type)) { | if (buffer_string_is_empty(sce->content_type)) { | 
|  | size_t namelen = buffer_string_length(name); | 
|  |  | 
 | for (k = 0; k < con->conf.mimetypes->used; k++) { | for (k = 0; k < con->conf.mimetypes->used; k++) { | 
 | data_string *ds = (data_string *)con->conf.mimetypes->data[k]; | data_string *ds = (data_string *)con->conf.mimetypes->data[k]; | 
 | buffer *type = ds->key; | buffer *type = ds->key; | 
 |  | size_t typelen = buffer_string_length(type); | 
 |  |  | 
| if (type->used == 0) continue; | if (buffer_is_empty(type)) continue; | 
 |  |  | 
 | /* check if the right side is the same */ | /* check if the right side is the same */ | 
| if (type->used > name->used) continue; | if (typelen > namelen) continue; | 
 |  |  | 
| if (0 == strncasecmp(name->ptr + name->used - type->used, type->ptr, type->used - 1)) { | if (0 == strncasecmp(name->ptr + namelen - typelen, type->ptr, typelen)) { | 
| buffer_copy_string_buffer(sce->content_type, ds->value); | buffer_copy_buffer(sce->content_type, ds->value); | 
 | break; | break; | 
 | } | } | 
 | } | } | 
| Line 622  handler_t stat_cache_get_entry(server *srv, connection | Line 639  handler_t stat_cache_get_entry(server *srv, connection | 
 | #ifdef HAVE_FAM_H | #ifdef HAVE_FAM_H | 
 | if (srv->srvconf.stat_cache_engine == STAT_CACHE_ENGINE_FAM) { | if (srv->srvconf.stat_cache_engine == STAT_CACHE_ENGINE_FAM) { | 
 | /* is this directory already registered ? */ | /* is this directory already registered ? */ | 
| if (!dir_node) { | if (NULL == fam_dir) { | 
 | fam_dir = fam_dir_entry_init(); | fam_dir = fam_dir_entry_init(); | 
 |  |  | 
| buffer_copy_string_buffer(fam_dir->name, sc->dir_name); | buffer_copy_buffer(fam_dir->name, sc->dir_name); | 
 |  |  | 
 | fam_dir->version = 1; | fam_dir->version = 1; | 
 |  |  | 
 | fam_dir->req = calloc(1, sizeof(FAMRequest)); | fam_dir->req = calloc(1, sizeof(FAMRequest)); | 
 |  | force_assert(NULL != fam_dir); | 
 |  |  | 
 | if (0 != FAMMonitorDirectory(&sc->fam, fam_dir->name->ptr, | if (0 != FAMMonitorDirectory(&sc->fam, fam_dir->name->ptr, | 
 | fam_dir->req, fam_dir)) { | fam_dir->req, fam_dir)) { | 
| Line 643  handler_t stat_cache_get_entry(server *srv, connection | Line 661  handler_t stat_cache_get_entry(server *srv, connection | 
 | fam_dir_entry_free(&sc->fam, fam_dir); | fam_dir_entry_free(&sc->fam, fam_dir); | 
 | fam_dir = NULL; | fam_dir = NULL; | 
 | } else { | } else { | 
| int osize = 0; | int osize = splaytree_size(sc->dirs); | 
 |  |  | 
| if (sc->dirs) { | /* already splayed dir_ndx */ | 
| osize = sc->dirs->size; | if ((NULL != sc->dirs) && (sc->dirs->key == dir_ndx)) { | 
|  | /* hash collision: replace old entry */ | 
|  | fam_dir_entry_free(&sc->fam, sc->dirs->data); | 
|  | sc->dirs->data = fam_dir; | 
|  | } else { | 
|  | sc->dirs = splaytree_insert(sc->dirs, dir_ndx, fam_dir); | 
|  | force_assert(osize == (splaytree_size(sc->dirs) - 1)); | 
 | } | } | 
 |  |  | 
 | sc->dirs = splaytree_insert(sc->dirs, dir_ndx, fam_dir); |  | 
 | force_assert(sc->dirs); | force_assert(sc->dirs); | 
 | force_assert(sc->dirs->data == fam_dir); | force_assert(sc->dirs->data == fam_dir); | 
 | force_assert(osize == (sc->dirs->size - 1)); |  | 
 | } | } | 
 | } else { |  | 
 | fam_dir = dir_node->data; |  | 
 | } | } | 
 |  |  | 
 | /* bind the fam_fc to the stat() cache entry */ | /* bind the fam_fc to the stat() cache entry */ | 
| Line 671  handler_t stat_cache_get_entry(server *srv, connection | Line 691  handler_t stat_cache_get_entry(server *srv, connection | 
 | return HANDLER_GO_ON; | return HANDLER_GO_ON; | 
 | } | } | 
 |  |  | 
 |  | int stat_cache_open_rdonly_fstat (server *srv, connection *con, buffer *name, struct stat *st) { | 
 |  | /*(Note: O_NOFOLLOW affects only the final path segment, the target file, | 
 |  | * not any intermediate symlinks along the path)*/ | 
 |  | #ifndef O_BINARY | 
 |  | #define O_BINARY 0 | 
 |  | #endif | 
 |  | #ifndef O_LARGEFILE | 
 |  | #define O_LARGEFILE 0 | 
 |  | #endif | 
 |  | #ifndef O_NOCTTY | 
 |  | #define O_NOCTTY 0 | 
 |  | #endif | 
 |  | #ifndef O_NONBLOCK | 
 |  | #define O_NONBLOCK 0 | 
 |  | #endif | 
 |  | #ifndef O_NOFOLLOW | 
 |  | #define O_NOFOLLOW 0 | 
 |  | #endif | 
 |  | const int oflags = O_BINARY | O_LARGEFILE | O_NOCTTY | O_NONBLOCK | 
 |  | | (con->conf.follow_symlink ? 0 : O_NOFOLLOW); | 
 |  | const int fd = open(name->ptr, O_RDONLY | oflags); | 
 |  | if (fd >= 0) { | 
 |  | if (0 == fstat(fd, st)) { | 
 |  | return fd; | 
 |  | } else { | 
 |  | close(fd); | 
 |  | } | 
 |  | } | 
 |  | UNUSED(srv); /*(might log_error_write(srv, ...) in the future)*/ | 
 |  | return -1; | 
 |  | } | 
 |  |  | 
 | /** | /** | 
 | * remove stat() from cache which havn't been stat()ed for | * remove stat() from cache which havn't been stat()ed for | 
 | * more than 10 seconds | * more than 10 seconds | 
| Line 707  int stat_cache_trigger_cleanup(server *srv) { | Line 759  int stat_cache_trigger_cleanup(server *srv) { | 
 | if (!sc->files) return 0; | if (!sc->files) return 0; | 
 |  |  | 
 | keys = calloc(1, sizeof(int) * sc->files->size); | keys = calloc(1, sizeof(int) * sc->files->size); | 
 |  | force_assert(NULL != keys); | 
 |  |  | 
 | stat_cache_tag_old_entries(srv, sc->files, keys, &max_ndx); | stat_cache_tag_old_entries(srv, sc->files, keys, &max_ndx); | 
 |  |  |