diff --git a/src/mod_auth.c b/src/mod_auth.c index 200ef5ec..5191ca2e 100644 --- a/src/mod_auth.c +++ b/src/mod_auth.c @@ -10,14 +10,22 @@ #include "http_auth.h" #include "http_header.h" #include "log.h" +#include "safe_memclear.h" +#include "splaytree.h" /** * auth framework */ +typedef struct { + splay_tree *sptree; /* data in nodes of tree are (http_auth_cache_entry *)*/ + time_t max_age; +} http_auth_cache; + typedef struct { const http_auth_backend_t *auth_backend; const array *auth_require; + http_auth_cache *auth_cache; unsigned int auth_extern_authn; } plugin_config; @@ -27,23 +35,191 @@ typedef struct { plugin_config conf; } plugin_data; +typedef struct { + const struct http_auth_require_t *require; + time_t ctime; + int dalgo; + uint32_t dlen; + uint32_t ulen; + char *username; + char *pwdigest; +} http_auth_cache_entry; + +static http_auth_cache_entry * +http_auth_cache_entry_init (const struct http_auth_require_t * const require, const int dalgo, const char *username, const uint32_t ulen, const char *pw, const uint32_t pwlen) +{ + /*(similar to buffer_copy_string_len() for each element, + * but allocate exact lengths in single chunk of memory + * for cache to avoid wasting space and for memory locality)*/ + /* http_auth_require_t is stored instead of copying realm + *(store pointer to http_auth_require_t, which is persistent + * and will be different for each realm + permissions combo)*/ + http_auth_cache_entry * const ae = + malloc(sizeof(http_auth_cache_entry) + ulen + pwlen); + force_assert(ae); + ae->require = require; + ae->ctime = log_epoch_secs; + ae->dalgo = dalgo; + ae->ulen = ulen; + ae->dlen = pwlen; + ae->username = (char *)(ae + 1); + ae->pwdigest = ae->username + ulen; + memcpy(ae->username, username, ulen); + memcpy(ae->pwdigest, pw, pwlen); + return ae; +} + +static void +http_auth_cache_entry_free (void *data) +{ + http_auth_cache_entry * const ae = data; + safe_memclear(ae->pwdigest, ae->dlen); + free(ae); +} + +static void +http_auth_cache_free (http_auth_cache *ac) +{ + splay_tree *sptree = ac->sptree; + while (sptree) { + http_auth_cache_entry_free(sptree->data); + sptree = splaytree_delete(sptree, sptree->key); + } + free(ac); +} + +static http_auth_cache * +http_auth_cache_init (const array *opts) +{ + http_auth_cache *ac = malloc(sizeof(http_auth_cache)); + force_assert(ac); + ac->sptree = NULL; + ac->max_age = 600; /* 10 mins */ + for (uint32_t i = 0, used = opts->used; i < used; ++i) { + data_string *ds = (data_string *)opts->data[i]; + if (buffer_is_equal_string(&ds->key, CONST_STR_LEN("max-age"))) { + if (ds->type == TYPE_STRING) + ac->max_age = (time_t)strtol(ds->value.ptr, NULL, 10); + else if (ds->type == TYPE_INTEGER) + ac->max_age = (time_t)((data_integer *)ds)->value; + } + } + return ac; +} + +static int +http_auth_cache_hash (const struct http_auth_require_t * const require, const char *username, const uint32_t ulen) +{ + uint32_t h = /*(hash pointer value, which includes realm and permissions)*/ + djbhash((char *)(intptr_t)require, sizeof(intptr_t), DJBHASH_INIT); + h = djbhash(username, ulen, h); + /* strip highest bit of hash value for splaytree (see splaytree_djbhash())*/ + return (int32_t)(h & ~(((uint32_t)1) << 31)); +} + +static http_auth_cache_entry * +http_auth_cache_query (splay_tree ** const sptree, const int ndx) +{ + *sptree = splaytree_splay(*sptree, ndx); + return (*sptree && (*sptree)->key == ndx) ? (*sptree)->data : NULL; +} + +static void +http_auth_cache_insert (splay_tree ** const sptree, const int ndx, void * const data, void(data_free_fn)(void *)) +{ + /*(not necessary to re-splay (with current usage) since single-threaded + * and splaytree has not been modified since http_auth_cache_query())*/ + /* *sptree = splaytree_splay(*sptree, ndx); */ + if (NULL == *sptree || (*sptree)->key != ndx) + *sptree = splaytree_insert(*sptree, ndx, data); + else { /* collision; replace old entry */ + data_free_fn((*sptree)->data); + (*sptree)->data = data; + } +} + +/* walk though cache, collect expired ids, and remove them in a second loop */ +static void +mod_auth_tag_old_entries (splay_tree * const t, int * const keys, int * const ndx, const time_t max_age, const time_t cur_ts) +{ + if (*ndx == 8192) return; /*(must match num array entries in keys[])*/ + if (t->left) + mod_auth_tag_old_entries(t->left, keys, ndx, max_age, cur_ts); + if (t->right) + mod_auth_tag_old_entries(t->right, keys, ndx, max_age, cur_ts); + if (*ndx == 8192) return; /*(must match num array entries in keys[])*/ + + const http_auth_cache_entry * const ae = t->data; + if (cur_ts - ae->ctime > max_age) + keys[(*ndx)++] = t->key; +} + +__attribute_noinline__ +static void +mod_auth_periodic_cleanup(splay_tree **sptree_ptr, const time_t max_age, const time_t cur_ts) +{ + splay_tree *sptree = *sptree_ptr; + int max_ndx, i; + int keys[8192]; /* 32k size on stack */ + do { + if (!sptree) break; + max_ndx = 0; + mod_auth_tag_old_entries(sptree, keys, &max_ndx, max_age, cur_ts); + for (i = 0; i < max_ndx; ++i) { + int ndx = keys[i]; + sptree = splaytree_splay(sptree, ndx); + if (sptree && sptree->key == ndx) { + http_auth_cache_entry_free(sptree->data); + sptree = splaytree_delete(sptree, ndx); + } + } + } while (max_ndx == sizeof(keys)/sizeof(int)); + *sptree_ptr = sptree; +} + +TRIGGER_FUNC(mod_auth_periodic) +{ + const plugin_data * const p = p_d; + const time_t cur_ts = log_epoch_secs; + if (cur_ts & 0x7) return HANDLER_GO_ON; /*(continue once each 8 sec)*/ + UNUSED(srv); + + /* future: might construct array of (http_auth_cache *) at startup + * to avoid the need to search for them here */ + for (int i = 0, used = p->nconfig; i < used; ++i) { + const config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0]; + for (; cpv->k_id != -1; ++cpv) { + if (cpv->k_id != 3) continue; /* k_id == 3 for auth.cache */ + if (cpv->vtype != T_CONFIG_LOCAL) continue; + http_auth_cache *ac = cpv->v.v; + mod_auth_periodic_cleanup(&ac->sptree, ac->max_age, cur_ts); + } + } + + return HANDLER_GO_ON; +} + + + + static handler_t mod_auth_check_basic(request_st *r, void *p_d, const struct http_auth_require_t *require, const struct http_auth_backend_t *backend); static handler_t mod_auth_check_digest(request_st *r, void *p_d, const struct http_auth_require_t *require, const struct http_auth_backend_t *backend); static handler_t mod_auth_check_extern(request_st *r, void *p_d, const struct http_auth_require_t *require, const struct http_auth_backend_t *backend); INIT_FUNC(mod_auth_init) { - static const http_auth_scheme_t http_auth_scheme_basic = { "basic", mod_auth_check_basic, NULL }; - static const http_auth_scheme_t http_auth_scheme_digest = { "digest", mod_auth_check_digest, NULL }; + static http_auth_scheme_t http_auth_scheme_basic = { "basic", mod_auth_check_basic, NULL }; + static http_auth_scheme_t http_auth_scheme_digest = { "digest", mod_auth_check_digest, NULL }; static const http_auth_scheme_t http_auth_scheme_extern = { "extern", mod_auth_check_extern, NULL }; - plugin_data *p; + plugin_data *p = calloc(1, sizeof(*p)); + force_assert(p); /* register http_auth_scheme_* */ + http_auth_scheme_basic.p_d = p; http_auth_scheme_set(&http_auth_scheme_basic); + http_auth_scheme_digest.p_d = p; http_auth_scheme_set(&http_auth_scheme_digest); http_auth_scheme_set(&http_auth_scheme_extern); - p = calloc(1, sizeof(*p)); - return p; } @@ -59,6 +235,9 @@ FREE_FUNC(mod_auth_free) { case 1: /* auth.require */ array_free(cpv->v.v); break; + case 3: /* auth.cache */ + http_auth_cache_free(cpv->v.v); + break; default: break; } @@ -372,6 +551,11 @@ static void mod_auth_merge_config_cpv(plugin_config * const pconf, const config_ break; case 2: /* auth.extern-authn */ pconf->auth_extern_authn = cpv->v.u; + break; + case 3: /* auth.cache */ + if (cpv->vtype == T_CONFIG_LOCAL) + pconf->auth_cache = cpv->v.v; + break; default:/* should not happen */ return; } @@ -403,6 +587,9 @@ SETDEFAULTS_FUNC(mod_auth_set_defaults) { ,{ CONST_STR_LEN("auth.extern-authn"), T_CONFIG_BOOL, T_CONFIG_SCOPE_CONNECTION } + ,{ CONST_STR_LEN("auth.cache"), + T_CONFIG_ARRAY, + T_CONFIG_SCOPE_CONNECTION } ,{ NULL, 0, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } @@ -445,6 +632,10 @@ SETDEFAULTS_FUNC(mod_auth_set_defaults) { break; case 2: /* auth.extern-authn */ break; + case 3: /* auth.cache */ + cpv->v.v = http_auth_cache_init(cpv->v.a); + cpv->vtype = T_CONFIG_LOCAL; + break; default:/* should not happen */ break; } @@ -488,12 +679,14 @@ static handler_t mod_auth_uri_handler(request_st * const r, void *p_d) { } } + int mod_auth_plugin_init(plugin *p); int mod_auth_plugin_init(plugin *p) { p->version = LIGHTTPD_VERSION_ID; p->name = "auth"; p->init = mod_auth_init; p->set_defaults = mod_auth_set_defaults; + p->handle_trigger = mod_auth_periodic; p->handle_uri_clean = mod_auth_uri_handler; p->cleanup = mod_auth_free; @@ -544,8 +737,6 @@ static handler_t mod_auth_check_basic(request_st * const r, void *p_d, const str char *pw; handler_t rc = HANDLER_UNSET; - UNUSED(p_d); - if (NULL == backend) { log_error(r->conf.errh, __FILE__, __LINE__, "auth.backend not configured for %s", r->uri.path.ptr); r->http_status = 500; @@ -584,13 +775,41 @@ static handler_t mod_auth_check_basic(request_st * const r, void *p_d, const str return mod_auth_send_400_bad_request(r); } + uint32_t pwlen = buffer_string_length(username); buffer_string_set_length(username, pw - username->ptr); pw++; + pwlen -= (pw - username->ptr); + + plugin_data * const p = p_d; + splay_tree ** sptree = p->conf.auth_cache + ? &p->conf.auth_cache->sptree + : NULL; + http_auth_cache_entry *ae = NULL; + int ndx = -1; + if (sptree) { + ndx = http_auth_cache_hash(require, CONST_BUF_LEN(username)); + ae = http_auth_cache_query(sptree, ndx); + if (ae && ae->require == require + && buffer_is_equal_string(username, ae->username, ae->ulen)) + rc = http_auth_const_time_memeq_pad(ae->pwdigest, ae->dlen, + pw, pwlen) + ? HANDLER_GO_ON + : HANDLER_ERROR; + else /*(not found or hash collision)*/ + ae = NULL; + } + + if (NULL == ae) /* (HANDLER_UNSET == rc) */ + rc = backend->basic(r, backend->p_d, require, username, pw); - rc = backend->basic(r, backend->p_d, require, username, pw); switch (rc) { case HANDLER_GO_ON: http_auth_setenv(r, CONST_BUF_LEN(username), CONST_STR_LEN("Basic")); + if (sptree && NULL == ae) { /*(cache (new) successful result)*/ + ae = http_auth_cache_entry_init(require, 0, CONST_BUF_LEN(username), + pw, pwlen); + http_auth_cache_insert(sptree, ndx, ae, http_auth_cache_entry_free); + } break; case HANDLER_WAIT_FOR_EVENT: case HANDLER_FINISHED: @@ -988,8 +1207,6 @@ static handler_t mod_auth_check_digest(request_st * const r, void *p_d, const st dkv[7].ptr = &nc; dkv[8].ptr = &respons; - UNUSED(p_d); - if (NULL == backend) { log_error(r->conf.errh, __FILE__, __LINE__, "auth.backend not configured for %s", r->uri.path.ptr); @@ -1192,7 +1409,33 @@ static handler_t mod_auth_check_digest(request_st * const r, void *p_d, const st } } - switch (backend->digest(r, backend->p_d, &ai)) { + handler_t rc = HANDLER_UNSET; + + plugin_data * const p = p_d; + splay_tree ** sptree = p->conf.auth_cache + ? &p->conf.auth_cache->sptree + : NULL; + http_auth_cache_entry *ae = NULL; + int ndx = -1; + if (sptree) { + ndx = http_auth_cache_hash(require, ai.username, ai.ulen); + ae = http_auth_cache_query(sptree, ndx); + if (ae && ae->require == require + && ae->dalgo == ai.dalgo + && ae->dlen == ai.dlen + && ae->ulen == ai.ulen + && 0 == memcmp(ae->username, ai.username, ai.ulen)) { + rc = HANDLER_GO_ON; + memcpy(ai.digest, ae->pwdigest, ai.dlen); + } + else /*(not found or hash collision)*/ + ae = NULL; + } + + if (HANDLER_UNSET == rc) + rc = backend->digest(r, backend->p_d, &ai); + + switch (rc) { case HANDLER_GO_ON: break; case HANDLER_WAIT_FOR_EVENT: @@ -1208,6 +1451,12 @@ static handler_t mod_auth_check_digest(request_st * const r, void *p_d, const st return mod_auth_send_401_unauthorized_digest(r, require, 0); } + if (sptree && NULL == ae) { /*(cache digest from backend)*/ + ae = http_auth_cache_entry_init(require, ai.dalgo, ai.username, ai.ulen, + (char *)ai.digest, ai.dlen); + http_auth_cache_insert(sptree, ndx, ae, http_auth_cache_entry_free); + } + const char *m = get_http_method_name(r->http_method); force_assert(m); diff --git a/src/mod_vhostdb.c b/src/mod_vhostdb.c index 7ebefe55..bfded9f5 100644 --- a/src/mod_vhostdb.c +++ b/src/mod_vhostdb.c @@ -11,6 +11,7 @@ #include "http_vhostdb.h" #include "log.h" #include "stat_cache.h" +#include "splaytree.h" #include #include @@ -19,8 +20,14 @@ * vhostdb framework */ +typedef struct { + splay_tree *sptree; /* data in nodes of tree are (vhostdb_cache_entry *) */ + time_t max_age; +} vhostdb_cache; + typedef struct { const http_vhostdb_backend_t *vhostdb_backend; + vhostdb_cache *vhostdb_cache; } plugin_config; typedef struct { @@ -31,6 +38,98 @@ typedef struct { buffer tmp_buf; } plugin_data; +typedef struct { + char *server_name; + char *document_root; + uint32_t slen; + uint32_t dlen; + time_t ctime; +} vhostdb_cache_entry; + +static vhostdb_cache_entry * +vhostdb_cache_entry_init (const buffer * const server_name, const buffer * const docroot) +{ + const uint32_t slen = buffer_string_length(server_name); + const uint32_t dlen = buffer_string_length(docroot); + vhostdb_cache_entry * const ve = + malloc(sizeof(vhostdb_cache_entry) + slen + dlen); + ve->ctime = log_epoch_secs; + ve->slen = slen; + ve->dlen = dlen; + ve->server_name = (char *)(ve + 1); + ve->document_root = ve->server_name + slen; + memcpy(ve->server_name, server_name->ptr, slen); + memcpy(ve->document_root, docroot->ptr, dlen); + return ve; +} + +static void +vhostdb_cache_entry_free (vhostdb_cache_entry *ve) +{ + free(ve); +} + +static void +vhostdb_cache_free (vhostdb_cache *vc) +{ + splay_tree *sptree = vc->sptree; + while (sptree) { + vhostdb_cache_entry_free(sptree->data); + sptree = splaytree_delete(sptree, sptree->key); + } + free(vc); +} + +static vhostdb_cache * +vhostdb_cache_init (const array *opts) +{ + vhostdb_cache *vc = malloc(sizeof(vhostdb_cache)); + force_assert(vc); + vc->sptree = NULL; + vc->max_age = 600; /* 10 mins */ + for (uint32_t i = 0, used = opts->used; i < used; ++i) { + data_string *ds = (data_string *)opts->data[i]; + if (buffer_is_equal_string(&ds->key, CONST_STR_LEN("max-age"))) { + if (ds->type == TYPE_STRING) + vc->max_age = (time_t)strtol(ds->value.ptr, NULL, 10); + else if (ds->type == TYPE_INTEGER) + vc->max_age = (time_t)((data_integer *)ds)->value; + } + } + return vc; +} + +static vhostdb_cache_entry * +mod_vhostdb_cache_query (request_st * const r, plugin_data * const p) +{ + const int ndx = splaytree_djbhash(CONST_BUF_LEN(&r->uri.authority)); + splay_tree ** const sptree = &p->conf.vhostdb_cache->sptree; + *sptree = splaytree_splay(*sptree, ndx); + vhostdb_cache_entry * const ve = + (*sptree && (*sptree)->key == ndx) ? (*sptree)->data : NULL; + + return ve + && buffer_is_equal_string(&r->uri.authority, ve->server_name, ve->slen) + ? ve + : NULL; +} + +static void +mod_vhostdb_cache_insert (request_st * const r, plugin_data * const p, vhostdb_cache_entry * const ve) +{ + const int ndx = splaytree_djbhash(CONST_BUF_LEN(&r->uri.authority)); + splay_tree ** const sptree = &p->conf.vhostdb_cache->sptree; + /*(not necessary to re-splay (with current usage) since single-threaded + * and splaytree has not been modified since mod_vhostdb_cache_query())*/ + /* *sptree = splaytree_splay(*sptree, ndx); */ + if (NULL == *sptree || (*sptree)->key != ndx) + *sptree = splaytree_insert(*sptree, ndx, ve); + else { /* collision; replace old entry */ + vhostdb_cache_entry_free((*sptree)->data); + (*sptree)->data = ve; + } +} + INIT_FUNC(mod_vhostdb_init) { return calloc(1, sizeof(plugin_data)); } @@ -38,6 +137,22 @@ INIT_FUNC(mod_vhostdb_init) { FREE_FUNC(mod_vhostdb_free) { plugin_data *p = p_d; free(p->tmp_buf.ptr); + + if (NULL == p->cvlist) return; + /* (init i to 0 if global context; to 1 to skip empty global context) */ + for (int i = !p->cvlist[0].v.u2[1], used = p->nconfig; i < used; ++i) { + config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0]; + for (; -1 != cpv->k_id; ++cpv) { + if (cpv->vtype != T_CONFIG_LOCAL || NULL == cpv->v.v) continue; + switch (cpv->k_id) { + case 1: /* vhostdb.cache */ + vhostdb_cache_free(cpv->v.v); + break; + default: + break; + } + } + } } static void mod_vhostdb_merge_config_cpv(plugin_config * const pconf, const config_plugin_value_t * const cpv) { @@ -46,6 +161,10 @@ static void mod_vhostdb_merge_config_cpv(plugin_config * const pconf, const conf if (cpv->vtype == T_CONFIG_LOCAL) pconf->vhostdb_backend = cpv->v.v; break; + case 1: /* vhostdb.cache */ + if (cpv->vtype == T_CONFIG_LOCAL) + pconf->vhostdb_cache = cpv->v.v; + break; default:/* should not happen */ return; } @@ -70,6 +189,9 @@ SETDEFAULTS_FUNC(mod_vhostdb_set_defaults) { { CONST_STR_LEN("vhostdb.backend"), T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION } + ,{ CONST_STR_LEN("vhostdb.cache"), + T_CONFIG_ARRAY, + T_CONFIG_SCOPE_CONNECTION } ,{ NULL, 0, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } @@ -97,6 +219,10 @@ SETDEFAULTS_FUNC(mod_vhostdb_set_defaults) { cpv->vtype = T_CONFIG_LOCAL; } break; + case 1: /* vhostdb.cache */ + cpv->v.v = vhostdb_cache_init(cpv->v.a); + cpv->vtype = T_CONFIG_LOCAL; + break; default:/* should not happen */ break; } @@ -113,33 +239,13 @@ SETDEFAULTS_FUNC(mod_vhostdb_set_defaults) { return HANDLER_GO_ON; } -typedef struct { - buffer *server_name; - buffer *document_root; -} vhostdb_entry; - -static vhostdb_entry * vhostdb_entry_init (void) -{ - vhostdb_entry *ve = calloc(1, sizeof(*ve)); - ve->server_name = buffer_init(); - ve->document_root = buffer_init(); - return ve; -} - -static void vhostdb_entry_free (vhostdb_entry *ve) -{ - buffer_free(ve->server_name); - buffer_free(ve->document_root); - free(ve); -} - REQUEST_FUNC(mod_vhostdb_handle_connection_reset) { plugin_data *p = p_d; - vhostdb_entry *ve; + vhostdb_cache_entry *ve; if ((ve = r->plugin_ctx[p->id])) { r->plugin_ctx[p->id] = NULL; - vhostdb_entry_free(ve); + vhostdb_cache_entry_free(ve); } return HANDLER_GO_ON; @@ -153,18 +259,18 @@ static handler_t mod_vhostdb_error_500 (request_st * const r) return HANDLER_FINISHED; } -static handler_t mod_vhostdb_found (request_st * const r, vhostdb_entry * const ve) +static handler_t mod_vhostdb_found (request_st * const r, vhostdb_cache_entry * const ve) { /* fix virtual server and docroot */ r->server_name = &r->server_name_buf; - buffer_copy_buffer(&r->server_name_buf, ve->server_name); - buffer_copy_buffer(&r->physical.doc_root, ve->document_root); + buffer_copy_string_len(&r->server_name_buf, ve->server_name, ve->slen); + buffer_copy_string_len(&r->physical.doc_root, ve->document_root, ve->dlen); return HANDLER_GO_ON; } REQUEST_FUNC(mod_vhostdb_handle_docroot) { plugin_data *p = p_d; - vhostdb_entry *ve; + vhostdb_cache_entry *ve; const http_vhostdb_backend_t *backend; buffer *b; stat_cache_entry *sce; @@ -172,18 +278,18 @@ REQUEST_FUNC(mod_vhostdb_handle_docroot) { /* no host specified? */ if (buffer_string_is_empty(&r->uri.authority)) return HANDLER_GO_ON; - /* XXX: future: implement larger, managed cache - * of database responses (positive and negative) */ - /* check if cached this connection */ ve = r->plugin_ctx[p->id]; - if (ve && buffer_is_equal(ve->server_name, &r->uri.authority)) { + if (ve + && buffer_is_equal_string(&r->uri.authority, ve->server_name, ve->slen)) return mod_vhostdb_found(r, ve); /* HANDLER_GO_ON */ - } mod_vhostdb_patch_config(r, p); if (!p->conf.vhostdb_backend) return HANDLER_GO_ON; + if (p->conf.vhostdb_cache && (ve = mod_vhostdb_cache_query(r, p))) + return mod_vhostdb_found(r, ve); /* HANDLER_GO_ON */ + b = &p->tmp_buf; backend = p->conf.vhostdb_backend; if (0 != backend->query(r, backend->p_d, b)) { @@ -208,14 +314,81 @@ REQUEST_FUNC(mod_vhostdb_handle_docroot) { return mod_vhostdb_error_500(r); /* HANDLER_FINISHED */ } - /* cache the data */ - if (!ve) r->plugin_ctx[p->id] = ve = vhostdb_entry_init(); - buffer_copy_buffer(ve->server_name, &r->uri.authority); - buffer_copy_buffer(ve->document_root, b); + if (ve && !p->conf.vhostdb_cache) + vhostdb_cache_entry_free(ve); + + ve = vhostdb_cache_entry_init(&r->uri.authority, b); + + if (!p->conf.vhostdb_cache) + r->plugin_ctx[p->id] = ve; + else + mod_vhostdb_cache_insert(r, p, ve); return mod_vhostdb_found(r, ve); /* HANDLER_GO_ON */ } +/* walk though cache, collect expired ids, and remove them in a second loop */ +static void +mod_vhostdb_tag_old_entries (splay_tree * const t, int * const keys, int * const ndx, const time_t max_age, const time_t cur_ts) +{ + if (*ndx == 8192) return; /*(must match num array entries in keys[])*/ + if (t->left) + mod_vhostdb_tag_old_entries(t->left, keys, ndx, max_age, cur_ts); + if (t->right) + mod_vhostdb_tag_old_entries(t->right, keys, ndx, max_age, cur_ts); + if (*ndx == 8192) return; /*(must match num array entries in keys[])*/ + + const vhostdb_cache_entry * const ve = t->data; + if (cur_ts - ve->ctime > max_age) + keys[(*ndx)++] = t->key; +} + +__attribute_noinline__ +static void +mod_vhostdb_periodic_cleanup(splay_tree **sptree_ptr, const time_t max_age, const time_t cur_ts) +{ + splay_tree *sptree = *sptree_ptr; + int max_ndx, i; + int keys[8192]; /* 32k size on stack */ + do { + if (!sptree) break; + max_ndx = 0; + mod_vhostdb_tag_old_entries(sptree, keys, &max_ndx, max_age, cur_ts); + for (i = 0; i < max_ndx; ++i) { + int ndx = keys[i]; + sptree = splaytree_splay(sptree, ndx); + if (sptree && sptree->key == ndx) { + vhostdb_cache_entry_free(sptree->data); + sptree = splaytree_delete(sptree, ndx); + } + } + } while (max_ndx == sizeof(keys)/sizeof(int)); + *sptree_ptr = sptree; +} + +TRIGGER_FUNC(mod_vhostdb_periodic) +{ + const plugin_data * const p = p_d; + const time_t cur_ts = log_epoch_secs; + if (cur_ts & 0x7) return HANDLER_GO_ON; /*(continue once each 8 sec)*/ + UNUSED(srv); + + /* future: might construct array of (vhostdb_cache *) at startup + * to avoid the need to search for them here */ + for (int i = 0, used = p->nconfig; i < used; ++i) { + const config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0]; + for (; cpv->k_id != -1; ++cpv) { + if (cpv->k_id != 1) continue; /* k_id == 1 for vhostdb.cache */ + if (cpv->vtype != T_CONFIG_LOCAL) continue; + vhostdb_cache *vc = cpv->v.v; + mod_vhostdb_periodic_cleanup(&vc->sptree, vc->max_age, cur_ts); + } + } + + return HANDLER_GO_ON; +} + + int mod_vhostdb_plugin_init(plugin *p); int mod_vhostdb_plugin_init(plugin *p) { p->version = LIGHTTPD_VERSION_ID; @@ -223,6 +396,7 @@ int mod_vhostdb_plugin_init(plugin *p) { p->init = mod_vhostdb_init; p->cleanup = mod_vhostdb_free; p->set_defaults = mod_vhostdb_set_defaults; + p->handle_trigger = mod_vhostdb_periodic; p->handle_docroot = mod_vhostdb_handle_docroot; p->connection_reset = mod_vhostdb_handle_connection_reset; diff --git a/src/splaytree.h b/src/splaytree.h index d7fe1a2d..c4074b65 100644 --- a/src/splaytree.h +++ b/src/splaytree.h @@ -23,17 +23,17 @@ splay_tree * splaytree_size(splay_tree *t); __attribute_pure__ -static inline uint32_t djbhash(const char *str, const uint32_t len); +static inline uint32_t djbhash(const char *str, const uint32_t len, uint32_t hash); __attribute_pure__ static inline int32_t splaytree_djbhash(const char *str, const uint32_t len); /* the famous DJB hash function for strings */ -static inline uint32_t djbhash(const char *str, const uint32_t len) +#define DJBHASH_INIT 5381 +static inline uint32_t djbhash(const char *str, const uint32_t len, uint32_t hash) { const unsigned char * const s = (const unsigned char *)str; - uint32_t hash = 5381; for (uint32_t i = 0; i < len; ++i) hash = ((hash << 5) + hash) ^ s[i]; return hash; } @@ -42,7 +42,7 @@ static inline uint32_t djbhash(const char *str, const uint32_t len) static inline int32_t splaytree_djbhash(const char *str, const uint32_t len) { /* strip highest bit of hash value for splaytree */ - return (int32_t)(djbhash(str,len) & ~(((uint32_t)1) << 31)); + return (int32_t)(djbhash(str,len,DJBHASH_INIT) & ~(((uint32_t)1) << 31)); } diff --git a/src/stat_cache.c b/src/stat_cache.c index fa73f11c..add0207c 100644 --- a/src/stat_cache.c +++ b/src/stat_cache.c @@ -192,12 +192,13 @@ static void fam_dir_tag_refcnt(splay_tree *t, int *keys, int *ndx) } } +__attribute_noinline__ static void fam_dir_periodic_cleanup() { + stat_cache_fam * const scf = sc.scf; int max_ndx, i; int keys[8192]; /* 32k size on stack */ - stat_cache_fam * const scf = sc.scf; do { - if (!scf->dirs) return; + if (!scf->dirs) break; max_ndx = 0; fam_dir_tag_refcnt(scf->dirs, keys, &max_ndx); for (i = 0; i < max_ndx; ++i) { @@ -858,13 +859,14 @@ static void stat_cache_tag_dir_tree(splay_tree *t, const char *name, size_t len, keys[(*ndx)++] = t->key; } +__attribute_noinline__ static void stat_cache_prune_dir_tree(const char *name, size_t len) { + splay_tree *sptree = sc.files; int max_ndx, i; int keys[8192]; /* 32k size on stack */ - splay_tree *sptree = sc.files; do { - if (!sptree) return; + if (!sptree) break; max_ndx = 0; stat_cache_tag_dir_tree(sptree, name, len, keys, &max_ndx); for (i = 0; i < max_ndx; ++i) { @@ -1101,41 +1103,37 @@ int stat_cache_open_rdonly_fstat (const buffer *name, struct stat *st, int symli * and remove them in a second loop */ -static void stat_cache_tag_old_entries(splay_tree * const t, int * const keys, uint32_t * const ndx, const time_t max_age, const time_t cur_ts) { - if (!t) return; - - stat_cache_tag_old_entries(t->left, keys, ndx, max_age, cur_ts); - stat_cache_tag_old_entries(t->right, keys, ndx, max_age, cur_ts); +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) { + if (*ndx == 8192) return; /*(must match num array entries in keys[])*/ + if (t->left) + stat_cache_tag_old_entries(t->left, keys, ndx, max_age, cur_ts); + if (t->right) + stat_cache_tag_old_entries(t->right, keys, ndx, max_age, cur_ts); + if (*ndx == 8192) return; /*(must match num array entries in keys[])*/ const stat_cache_entry * const sce = t->data; - - if (cur_ts - sce->stat_ts > max_age) { + if (cur_ts - sce->stat_ts > max_age) keys[(*ndx)++] = t->key; - } } static void stat_cache_periodic_cleanup(const time_t max_age, const time_t cur_ts) { - splay_tree *sptree = sc.files; - if (!sptree) return; - - int * const keys = calloc(1, sizeof(int) * sptree->size); - force_assert(NULL != keys); - - uint32_t max_ndx = 0; - stat_cache_tag_old_entries(sptree, keys, &max_ndx, max_age, cur_ts); - - for (uint32_t i = 0; i < max_ndx; ++i) { - int ndx = keys[i]; - sptree = splaytree_splay(sptree, ndx); - if (sptree && sptree->key == ndx) { - stat_cache_entry_free(sptree->data); - sptree = splaytree_delete(sptree, ndx); - } - } - - sc.files = sptree; - - free(keys); + splay_tree *sptree = sc.files; + int max_ndx, i; + int keys[8192]; /* 32k size on stack */ + do { + if (!sptree) break; + max_ndx = 0; + stat_cache_tag_old_entries(sptree, keys, &max_ndx, max_age, cur_ts); + for (i = 0; i < max_ndx; ++i) { + int ndx = keys[i]; + sptree = splaytree_splay(sptree, ndx); + if (sptree && sptree->key == ndx) { + stat_cache_entry_free(sptree->data); + sptree = splaytree_delete(sptree, ndx); + } + } + } while (max_ndx == sizeof(keys)/sizeof(int)); + sc.files = sptree; } void stat_cache_trigger_cleanup(void) {