| /* Licensed to the Apache Software Foundation (ASF) under one or more | |
| * contributor license agreements. See the NOTICE file distributed with | |
| * this work for additional information regarding copyright ownership. | |
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |
| * (the "License"); you may not use this file except in compliance with | |
| * the License. You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| #include "httpd.h" | |
| #include "http_log.h" | |
| #include "http_request.h" | |
| #include "http_protocol.h" | |
| #include "http_config.h" | |
| #include "mod_status.h" | |
| #include "apr.h" | |
| #include "apr_strings.h" | |
| #include "apr_time.h" | |
| #include "apr_shm.h" | |
| #define APR_WANT_STRFUNC | |
| #include "apr_want.h" | |
| #include "apr_general.h" | |
| #if APR_HAVE_LIMITS_H | |
| #include <limits.h> | |
| #endif | |
| #include "ap_socache.h" | |
| /* XXX Unfortunately, there are still many unsigned ints in use here, so we | |
| * XXX cannot allow more than UINT_MAX. Since some of the ints are exposed in | |
| * XXX public interfaces, a simple search and replace is not enough. | |
| * XXX It should be possible to extend that so that the total cache size can | |
| * XXX be APR_SIZE_MAX and only the object size needs to be smaller than | |
| * XXX UINT_MAX. | |
| */ | |
| #define SHMCB_MAX_SIZE (UINT_MAX<APR_SIZE_MAX ? UINT_MAX : APR_SIZE_MAX) | |
| #define DEFAULT_SHMCB_PREFIX "socache-shmcb-" | |
| #define DEFAULT_SHMCB_SUFFIX ".cache" | |
| #define ALIGNED_HEADER_SIZE APR_ALIGN_DEFAULT(sizeof(SHMCBHeader)) | |
| #define ALIGNED_SUBCACHE_SIZE APR_ALIGN_DEFAULT(sizeof(SHMCBSubcache)) | |
| #define ALIGNED_INDEX_SIZE APR_ALIGN_DEFAULT(sizeof(SHMCBIndex)) | |
| /* | |
| * Header structure - the start of the shared-mem segment | |
| */ | |
| typedef struct { | |
| /* Stats for cache operations */ | |
| unsigned long stat_stores; | |
| unsigned long stat_replaced; | |
| unsigned long stat_expiries; | |
| unsigned long stat_scrolled; | |
| unsigned long stat_retrieves_hit; | |
| unsigned long stat_retrieves_miss; | |
| unsigned long stat_removes_hit; | |
| unsigned long stat_removes_miss; | |
| /* Number of subcaches */ | |
| unsigned int subcache_num; | |
| /* How many indexes each subcache's queue has */ | |
| unsigned int index_num; | |
| /* How large each subcache is, including the queue and data */ | |
| unsigned int subcache_size; | |
| /* How far into each subcache the data area is (optimisation) */ | |
| unsigned int subcache_data_offset; | |
| /* How large the data area in each subcache is (optimisation) */ | |
| unsigned int subcache_data_size; | |
| } SHMCBHeader; | |
| /* | |
| * Subcache structure - the start of each subcache, followed by | |
| * indexes then data | |
| */ | |
| typedef struct { | |
| /* The start position and length of the cyclic buffer of indexes */ | |
| unsigned int idx_pos, idx_used; | |
| /* Same for the data area */ | |
| unsigned int data_pos, data_used; | |
| } SHMCBSubcache; | |
| /* | |
| * Index structure - each subcache has an array of these | |
| */ | |
| typedef struct { | |
| /* absolute time this entry expires */ | |
| apr_time_t expires; | |
| /* location within the subcache's data area */ | |
| unsigned int data_pos; | |
| /* size (most logic ignores this, we keep it only to minimise memcpy) */ | |
| unsigned int data_used; | |
| /* length of the used data which contains the id */ | |
| unsigned int id_len; | |
| /* Used to mark explicitly-removed socache entries */ | |
| unsigned char removed; | |
| } SHMCBIndex; | |
| struct ap_socache_instance_t { | |
| apr_pool_t *pool; | |
| const char *data_file; | |
| apr_size_t shm_size; | |
| apr_shm_t *shm; | |
| SHMCBHeader *header; | |
| }; | |
| /* The SHM data segment is of fixed size and stores data as follows. | |
| * | |
| * [ SHMCBHeader | Subcaches ] | |
| * | |
| * The SHMCBHeader header structure stores metadata concerning the | |
| * cache and the contained subcaches. | |
| * | |
| * Subcaches is a hash table of header->subcache_num SHMCBSubcache | |
| * structures. The hash table is indexed by SHMCB_MASK(id). Each | |
| * SHMCBSubcache structure has a fixed size (header->subcache_size), | |
| * which is determined at creation time, and looks like the following: | |
| * | |
| * [ SHMCBSubcache | Indexes | Data ] | |
| * | |
| * Each subcache is prefixed by the SHMCBSubcache structure. | |
| * | |
| * The subcache's "Data" segment is a single cyclic data buffer, of | |
| * total size header->subcache_data_size; data inside is referenced | |
| * using byte offsets. The offset marking the beginning of the cyclic | |
| * buffer is subcache->data_pos; the buffer's length is | |
| * subcache->data_used. | |
| * | |
| * "Indexes" is an array of header->index_num SHMCBIndex structures, | |
| * which is used as a cyclic queue; subcache->idx_pos gives the array | |
| * index of the first in use, subcache->idx_used gives the number in | |
| * use. Both ->idx_* values have a range of [0, header->index_num) | |
| * | |
| * Each in-use SHMCBIndex structure represents a single cached object. | |
| * The ID and data segment are stored consecutively in the subcache's | |
| * cyclic data buffer. The "Data" segment can thus be seen to | |
| * look like this, for example | |
| * | |
| * offset: [ 0 1 2 3 4 5 6 ... | |
| * contents:[ ID1 Data1 ID2 Data2 ID3 ... | |
| * | |
| * where the corresponding indices would look like: | |
| * | |
| * idx1 = { data_pos = 0, data_used = 3, id_len = 1, ...} | |
| * idx2 = { data_pos = 3, data_used = 3, id_len = 1, ...} | |
| * ... | |
| */ | |
| /* This macro takes a pointer to the header and a zero-based index and returns | |
| * a pointer to the corresponding subcache. */ | |
| #define SHMCB_SUBCACHE(pHeader, num) \ | |
| (SHMCBSubcache *)(((unsigned char *)(pHeader)) + \ | |
| ALIGNED_HEADER_SIZE + \ | |
| (num) * ((pHeader)->subcache_size)) | |
| /* This macro takes a pointer to the header and an id and returns a | |
| * pointer to the corresponding subcache. */ | |
| #define SHMCB_MASK(pHeader, id) \ | |
| SHMCB_SUBCACHE((pHeader), *(id) & ((pHeader)->subcache_num - 1)) | |
| /* This macro takes the same params as the last, generating two outputs for use | |
| * in ap_log_error(...). */ | |
| #define SHMCB_MASK_DBG(pHeader, id) \ | |
| *(id), (*(id) & ((pHeader)->subcache_num - 1)) | |
| /* This macro takes a pointer to a subcache and a zero-based index and returns | |
| * a pointer to the corresponding SHMCBIndex. */ | |
| #define SHMCB_INDEX(pSubcache, num) \ | |
| (SHMCBIndex *)(((unsigned char *)pSubcache) + \ | |
| ALIGNED_SUBCACHE_SIZE + \ | |
| (num) * ALIGNED_INDEX_SIZE) | |
| /* This macro takes a pointer to the header and a subcache and returns a | |
| * pointer to the corresponding data area. */ | |
| #define SHMCB_DATA(pHeader, pSubcache) \ | |
| ((unsigned char *)(pSubcache) + (pHeader)->subcache_data_offset) | |
| /* | |
| * Cyclic functions - assists in "wrap-around"/modulo logic | |
| */ | |
| /* Addition modulo 'mod' */ | |
| #define SHMCB_CYCLIC_INCREMENT(val,inc,mod) \ | |
| (((val) + (inc)) % (mod)) | |
| /* Subtraction (or "distance between") modulo 'mod' */ | |
| #define SHMCB_CYCLIC_SPACE(val1,val2,mod) \ | |
| ((val2) >= (val1) ? ((val2) - (val1)) : \ | |
| ((val2) + (mod) - (val1))) | |
| /* A "normal-to-cyclic" memcpy. */ | |
| static void shmcb_cyclic_ntoc_memcpy(unsigned int buf_size, unsigned char *data, | |
| unsigned int dest_offset, const unsigned char *src, | |
| unsigned int src_len) | |
| { | |
| if (dest_offset + src_len < buf_size) | |
| /* It be copied all in one go */ | |
| memcpy(data + dest_offset, src, src_len); | |
| else { | |
| /* Copy the two splits */ | |
| memcpy(data + dest_offset, src, buf_size - dest_offset); | |
| memcpy(data, src + buf_size - dest_offset, | |
| src_len + dest_offset - buf_size); | |
| } | |
| } | |
| /* A "cyclic-to-normal" memcpy. */ | |
| static void shmcb_cyclic_cton_memcpy(unsigned int buf_size, unsigned char *dest, | |
| const unsigned char *data, unsigned int src_offset, | |
| unsigned int src_len) | |
| { | |
| if (src_offset + src_len < buf_size) | |
| /* It be copied all in one go */ | |
| memcpy(dest, data + src_offset, src_len); | |
| else { | |
| /* Copy the two splits */ | |
| memcpy(dest, data + src_offset, buf_size - src_offset); | |
| memcpy(dest + buf_size - src_offset, data, | |
| src_len + src_offset - buf_size); | |
| } | |
| } | |
| /* A memcmp against a cyclic data buffer. Compares SRC of length | |
| * SRC_LEN against the contents of cyclic buffer DATA (which is of | |
| * size BUF_SIZE), starting at offset DEST_OFFSET. Got that? Good. */ | |
| static int shmcb_cyclic_memcmp(unsigned int buf_size, unsigned char *data, | |
| unsigned int dest_offset, | |
| const unsigned char *src, | |
| unsigned int src_len) | |
| { | |
| if (dest_offset + src_len < buf_size) | |
| /* It be compared all in one go */ | |
| return memcmp(data + dest_offset, src, src_len); | |
| else { | |
| /* Compare the two splits */ | |
| int diff; | |
| diff = memcmp(data + dest_offset, src, buf_size - dest_offset); | |
| if (diff) { | |
| return diff; | |
| } | |
| return memcmp(data, src + buf_size - dest_offset, | |
| src_len + dest_offset - buf_size); | |
| } | |
| } | |
| /* Prototypes for low-level subcache operations */ | |
| static void shmcb_subcache_expire(server_rec *, SHMCBHeader *, SHMCBSubcache *, | |
| apr_time_t); | |
| /* Returns zero on success, non-zero on failure. */ | |
| static int shmcb_subcache_store(server_rec *s, SHMCBHeader *header, | |
| SHMCBSubcache *subcache, | |
| unsigned char *data, unsigned int data_len, | |
| const unsigned char *id, unsigned int id_len, | |
| apr_time_t expiry); | |
| /* Returns zero on success, non-zero on failure. */ | |
| static int shmcb_subcache_retrieve(server_rec *, SHMCBHeader *, SHMCBSubcache *, | |
| const unsigned char *id, unsigned int idlen, | |
| unsigned char *data, unsigned int *datalen); | |
| /* Returns zero on success, non-zero on failure. */ | |
| static int shmcb_subcache_remove(server_rec *, SHMCBHeader *, SHMCBSubcache *, | |
| const unsigned char *, unsigned int); | |
| /* Returns result of the (iterator)() call, zero is success (continue) */ | |
| static apr_status_t shmcb_subcache_iterate(ap_socache_instance_t *instance, | |
| server_rec *s, | |
| void *userctx, | |
| SHMCBHeader *header, | |
| SHMCBSubcache *subcache, | |
| ap_socache_iterator_t *iterator, | |
| unsigned char **buf, | |
| apr_size_t *buf_len, | |
| apr_pool_t *pool, | |
| apr_time_t now); | |
| /* | |
| * High-Level "handlers" as per ssl_scache.c | |
| * subcache internals are deferred to shmcb_subcache_*** functions lower down | |
| */ | |
| static const char *socache_shmcb_create(ap_socache_instance_t **context, | |
| const char *arg, | |
| apr_pool_t *tmp, apr_pool_t *p) | |
| { | |
| ap_socache_instance_t *ctx; | |
| char *path, *cp, *cp2; | |
| /* Allocate the context. */ | |
| *context = ctx = apr_pcalloc(p, sizeof *ctx); | |
| ctx->pool = p; | |
| ctx->shm_size = 1024*512; /* 512KB */ | |
| if (!arg || *arg == '\0') { | |
| /* Use defaults. */ | |
| return NULL; | |
| } | |
| ctx->data_file = path = ap_runtime_dir_relative(p, arg); | |
| cp = strrchr(path, '('); | |
| cp2 = path + strlen(path) - 1; | |
| if (cp) { | |
| char *endptr; | |
| if (*cp2 != ')') { | |
| return "Invalid argument: no closing parenthesis or cache size " | |
| "missing after pathname with parenthesis"; | |
| } | |
| *cp++ = '\0'; | |
| *cp2 = '\0'; | |
| ctx->shm_size = strtol(cp, &endptr, 10); | |
| if (endptr != cp2) { | |
| return "Invalid argument: cache size not numerical"; | |
| } | |
| if (ctx->shm_size < 8192) { | |
| return "Invalid argument: size has to be >= 8192 bytes"; | |
| } | |
| if (ctx->shm_size >= SHMCB_MAX_SIZE) { | |
| return apr_psprintf(tmp, "Invalid argument: size has " | |
| "to be < %" APR_SIZE_T_FMT " bytes on this platform", | |
| SHMCB_MAX_SIZE); | |
| } | |
| } | |
| else if (cp2 >= path && *cp2 == ')') { | |
| return "Invalid argument: no opening parenthesis"; | |
| } | |
| return NULL; | |
| } | |
| static apr_status_t socache_shmcb_cleanup(void *arg) | |
| { | |
| ap_socache_instance_t *ctx = arg; | |
| if (ctx->shm) { | |
| apr_shm_destroy(ctx->shm); | |
| ctx->shm = NULL; | |
| } | |
| return APR_SUCCESS; | |
| } | |
| static apr_status_t socache_shmcb_init(ap_socache_instance_t *ctx, | |
| const char *namespace, | |
| const struct ap_socache_hints *hints, | |
| server_rec *s, apr_pool_t *p) | |
| { | |
| void *shm_segment; | |
| apr_size_t shm_segsize; | |
| apr_status_t rv; | |
| SHMCBHeader *header; | |
| unsigned int num_subcache, num_idx, loop; | |
| apr_size_t avg_obj_size, avg_id_len; | |
| /* Create shared memory segment */ | |
| if (ctx->data_file == NULL) { | |
| const char *path = apr_pstrcat(p, DEFAULT_SHMCB_PREFIX, namespace, | |
| DEFAULT_SHMCB_SUFFIX, NULL); | |
| ctx->data_file = ap_runtime_dir_relative(p, path); | |
| } | |
| /* Use anonymous shm by default, fall back on name-based. */ | |
| rv = apr_shm_create(&ctx->shm, ctx->shm_size, NULL, p); | |
| if (APR_STATUS_IS_ENOTIMPL(rv)) { | |
| /* If anon shm isn't supported, fail if no named file was | |
| * configured successfully; the ap_runtime_dir_relative call | |
| * above will return NULL for invalid paths. */ | |
| if (ctx->data_file == NULL) { | |
| ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00818) | |
| "Could not use anonymous shm for '%s' cache", | |
| namespace); | |
| ctx->shm = NULL; | |
| return APR_EINVAL; | |
| } | |
| /* For a name-based segment, remove it first in case of a | |
| * previous unclean shutdown. */ | |
| apr_shm_remove(ctx->data_file, p); | |
| rv = apr_shm_create(&ctx->shm, ctx->shm_size, ctx->data_file, p); | |
| } | |
| if (rv != APR_SUCCESS) { | |
| ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00819) | |
| "Could not allocate shared memory segment for shmcb " | |
| "socache"); | |
| ctx->shm = NULL; | |
| return rv; | |
| } | |
| apr_pool_cleanup_register(ctx->pool, ctx, socache_shmcb_cleanup, | |
| apr_pool_cleanup_null); | |
| shm_segment = apr_shm_baseaddr_get(ctx->shm); | |
| shm_segsize = apr_shm_size_get(ctx->shm); | |
| if (shm_segsize < (5 * ALIGNED_HEADER_SIZE)) { | |
| /* the segment is ridiculously small, bail out */ | |
| ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00820) | |
| "shared memory segment too small"); | |
| return APR_ENOSPC; | |
| } | |
| ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00821) | |
| "shmcb_init allocated %" APR_SIZE_T_FMT | |
| " bytes of shared memory", | |
| shm_segsize); | |
| /* Discount the header */ | |
| shm_segsize -= ALIGNED_HEADER_SIZE; | |
| /* Select index size based on average object size hints, if given. */ | |
| avg_obj_size = hints && hints->avg_obj_size ? hints->avg_obj_size : 150; | |
| avg_id_len = hints && hints->avg_id_len ? hints->avg_id_len : 30; | |
| num_idx = (shm_segsize) / (avg_obj_size + avg_id_len); | |
| num_subcache = 256; | |
| while ((num_idx / num_subcache) < (2 * num_subcache)) | |
| num_subcache /= 2; | |
| num_idx /= num_subcache; | |
| ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00822) | |
| "for %" APR_SIZE_T_FMT " bytes (%" APR_SIZE_T_FMT | |
| " including header), recommending %u subcaches, " | |
| "%u indexes each", shm_segsize, | |
| shm_segsize + ALIGNED_HEADER_SIZE, | |
| num_subcache, num_idx); | |
| if (num_idx < 5) { | |
| /* we're still too small, bail out */ | |
| ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00823) | |
| "shared memory segment too small"); | |
| return APR_ENOSPC; | |
| } | |
| /* OK, we're sorted */ | |
| ctx->header = header = shm_segment; | |
| header->stat_stores = 0; | |
| header->stat_replaced = 0; | |
| header->stat_expiries = 0; | |
| header->stat_scrolled = 0; | |
| header->stat_retrieves_hit = 0; | |
| header->stat_retrieves_miss = 0; | |
| header->stat_removes_hit = 0; | |
| header->stat_removes_miss = 0; | |
| header->subcache_num = num_subcache; | |
| /* Convert the subcache size (in bytes) to a value that is suitable for | |
| * structure alignment on the host platform, by rounding down if necessary. */ | |
| header->subcache_size = (size_t)(shm_segsize / num_subcache); | |
| if (header->subcache_size != APR_ALIGN_DEFAULT(header->subcache_size)) { | |
| header->subcache_size = APR_ALIGN_DEFAULT(header->subcache_size) - | |
| APR_ALIGN_DEFAULT(1); | |
| } | |
| header->subcache_data_offset = ALIGNED_SUBCACHE_SIZE + | |
| num_idx * ALIGNED_INDEX_SIZE; | |
| header->subcache_data_size = header->subcache_size - | |
| header->subcache_data_offset; | |
| header->index_num = num_idx; | |
| /* Output trace info */ | |
| ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00824) | |
| "shmcb_init_memory choices follow"); | |
| ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00825) | |
| "subcache_num = %u", header->subcache_num); | |
| ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00826) | |
| "subcache_size = %u", header->subcache_size); | |
| ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00827) | |
| "subcache_data_offset = %u", header->subcache_data_offset); | |
| ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00828) | |
| "subcache_data_size = %u", header->subcache_data_size); | |
| ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00829) | |
| "index_num = %u", header->index_num); | |
| /* The header is done, make the caches empty */ | |
| for (loop = 0; loop < header->subcache_num; loop++) { | |
| SHMCBSubcache *subcache = SHMCB_SUBCACHE(header, loop); | |
| subcache->idx_pos = subcache->idx_used = 0; | |
| subcache->data_pos = subcache->data_used = 0; | |
| } | |
| ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(00830) | |
| "Shared memory socache initialised"); | |
| /* Success ... */ | |
| return APR_SUCCESS; | |
| } | |
| static void socache_shmcb_destroy(ap_socache_instance_t *ctx, server_rec *s) | |
| { | |
| if (ctx) { | |
| apr_pool_cleanup_run(ctx->pool, ctx, socache_shmcb_cleanup); | |
| } | |
| } | |
| static apr_status_t socache_shmcb_store(ap_socache_instance_t *ctx, | |
| server_rec *s, const unsigned char *id, | |
| unsigned int idlen, apr_time_t expiry, | |
| unsigned char *encoded, | |
| unsigned int len_encoded, | |
| apr_pool_t *p) | |
| { | |
| SHMCBHeader *header = ctx->header; | |
| SHMCBSubcache *subcache = SHMCB_MASK(header, id); | |
| int tryreplace; | |
| ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00831) | |
| "socache_shmcb_store (0x%02x -> subcache %d)", | |
| SHMCB_MASK_DBG(header, id)); | |
| /* XXX: Says who? Why shouldn't this be acceptable, or padded if not? */ | |
| if (idlen < 4) { | |
| ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00832) "unusably short id provided " | |
| "(%u bytes)", idlen); | |
| return APR_EINVAL; | |
| } | |
| tryreplace = shmcb_subcache_remove(s, header, subcache, id, idlen); | |
| if (shmcb_subcache_store(s, header, subcache, encoded, | |
| len_encoded, id, idlen, expiry)) { | |
| ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00833) | |
| "can't store an socache entry!"); | |
| return APR_ENOSPC; | |
| } | |
| if (tryreplace == 0) { | |
| header->stat_replaced++; | |
| } | |
| else { | |
| header->stat_stores++; | |
| } | |
| ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00834) | |
| "leaving socache_shmcb_store successfully"); | |
| return APR_SUCCESS; | |
| } | |
| static apr_status_t socache_shmcb_retrieve(ap_socache_instance_t *ctx, | |
| server_rec *s, | |
| const unsigned char *id, unsigned int idlen, | |
| unsigned char *dest, unsigned int *destlen, | |
| apr_pool_t *p) | |
| { | |
| SHMCBHeader *header = ctx->header; | |
| SHMCBSubcache *subcache = SHMCB_MASK(header, id); | |
| int rv; | |
| ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00835) | |
| "socache_shmcb_retrieve (0x%02x -> subcache %d)", | |
| SHMCB_MASK_DBG(header, id)); | |
| /* Get the entry corresponding to the id, if it exists. */ | |
| rv = shmcb_subcache_retrieve(s, header, subcache, id, idlen, | |
| dest, destlen); | |
| if (rv == 0) | |
| header->stat_retrieves_hit++; | |
| else | |
| header->stat_retrieves_miss++; | |
| ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00836) | |
| "leaving socache_shmcb_retrieve successfully"); | |
| return rv == 0 ? APR_SUCCESS : APR_NOTFOUND; | |
| } | |
| static apr_status_t socache_shmcb_remove(ap_socache_instance_t *ctx, | |
| server_rec *s, const unsigned char *id, | |
| unsigned int idlen, apr_pool_t *p) | |
| { | |
| SHMCBHeader *header = ctx->header; | |
| SHMCBSubcache *subcache = SHMCB_MASK(header, id); | |
| apr_status_t rv; | |
| ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00837) | |
| "socache_shmcb_remove (0x%02x -> subcache %d)", | |
| SHMCB_MASK_DBG(header, id)); | |
| if (idlen < 4) { | |
| ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00838) "unusably short id provided " | |
| "(%u bytes)", idlen); | |
| return APR_EINVAL; | |
| } | |
| if (shmcb_subcache_remove(s, header, subcache, id, idlen) == 0) { | |
| header->stat_removes_hit++; | |
| rv = APR_SUCCESS; | |
| } else { | |
| header->stat_removes_miss++; | |
| rv = APR_NOTFOUND; | |
| } | |
| ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00839) | |
| "leaving socache_shmcb_remove successfully"); | |
| return rv; | |
| } | |
| static void socache_shmcb_status(ap_socache_instance_t *ctx, | |
| request_rec *r, int flags) | |
| { | |
| server_rec *s = r->server; | |
| SHMCBHeader *header = ctx->header; | |
| unsigned int loop, total = 0, cache_total = 0, non_empty_subcaches = 0; | |
| apr_time_t idx_expiry, min_expiry = 0, max_expiry = 0; | |
| apr_time_t now = apr_time_now(); | |
| double expiry_total = 0; | |
| int index_pct, cache_pct; | |
| AP_DEBUG_ASSERT(header->subcache_num > 0); | |
| ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00840) "inside shmcb_status"); | |
| /* Perform the iteration inside the mutex to avoid corruption or invalid | |
| * pointer arithmetic. The rest of our logic uses read-only header data so | |
| * doesn't need the lock. */ | |
| /* Iterate over the subcaches */ | |
| for (loop = 0; loop < header->subcache_num; loop++) { | |
| SHMCBSubcache *subcache = SHMCB_SUBCACHE(header, loop); | |
| shmcb_subcache_expire(s, header, subcache, now); | |
| total += subcache->idx_used; | |
| cache_total += subcache->data_used; | |
| if (subcache->idx_used) { | |
| SHMCBIndex *idx = SHMCB_INDEX(subcache, subcache->idx_pos); | |
| non_empty_subcaches++; | |
| idx_expiry = idx->expires; | |
| expiry_total += (double)idx_expiry; | |
| max_expiry = ((idx_expiry > max_expiry) ? idx_expiry : max_expiry); | |
| if (!min_expiry) | |
| min_expiry = idx_expiry; | |
| else | |
| min_expiry = ((idx_expiry < min_expiry) ? idx_expiry : min_expiry); | |
| } | |
| } | |
| index_pct = (100 * total) / (header->index_num * | |
| header->subcache_num); | |
| cache_pct = (100 * cache_total) / (header->subcache_data_size * | |
| header->subcache_num); | |
| /* Generate Output */ | |
| if (!(flags & AP_STATUS_SHORT)) { | |
| ap_rprintf(r, "cache type: <b>SHMCB</b>, shared memory: <b>%" APR_SIZE_T_FMT "</b> " | |
| "bytes, current entries: <b>%d</b><br>", | |
| ctx->shm_size, total); | |
| ap_rprintf(r, "subcaches: <b>%d</b>, indexes per subcache: <b>%d</b><br>", | |
| header->subcache_num, header->index_num); | |
| if (non_empty_subcaches) { | |
| apr_time_t average_expiry = (apr_time_t)(expiry_total / (double)non_empty_subcaches); | |
| ap_rprintf(r, "time left on oldest entries' objects: "); | |
| if (now < average_expiry) | |
| ap_rprintf(r, "avg: <b>%d</b> seconds, (range: %d...%d)<br>", | |
| (int)apr_time_sec(average_expiry - now), | |
| (int)apr_time_sec(min_expiry - now), | |
| (int)apr_time_sec(max_expiry - now)); | |
| else | |
| ap_rprintf(r, "expiry_threshold: <b>Calculation error!</b><br>"); | |
| } | |
| ap_rprintf(r, "index usage: <b>%d%%</b>, cache usage: <b>%d%%</b><br>", | |
| index_pct, cache_pct); | |
| ap_rprintf(r, "total entries stored since starting: <b>%lu</b><br>", | |
| header->stat_stores); | |
| ap_rprintf(r, "total entries replaced since starting: <b>%lu</b><br>", | |
| header->stat_replaced); | |
| ap_rprintf(r, "total entries expired since starting: <b>%lu</b><br>", | |
| header->stat_expiries); | |
| ap_rprintf(r, "total (pre-expiry) entries scrolled out of the cache: " | |
| "<b>%lu</b><br>", header->stat_scrolled); | |
| ap_rprintf(r, "total retrieves since starting: <b>%lu</b> hit, " | |
| "<b>%lu</b> miss<br>", header->stat_retrieves_hit, | |
| header->stat_retrieves_miss); | |
| ap_rprintf(r, "total removes since starting: <b>%lu</b> hit, " | |
| "<b>%lu</b> miss<br>", header->stat_removes_hit, | |
| header->stat_removes_miss); | |
| } | |
| else { | |
| ap_rputs("CacheType: SHMCB\n", r); | |
| ap_rprintf(r, "CacheSharedMemory: %" APR_SIZE_T_FMT "\n", | |
| ctx->shm_size); | |
| ap_rprintf(r, "CacheCurrentEntries: %d\n", total); | |
| ap_rprintf(r, "CacheSubcaches: %d\n", header->subcache_num); | |
| ap_rprintf(r, "CacheIndexesPerSubcaches: %d\n", header->index_num); | |
| if (non_empty_subcaches) { | |
| apr_time_t average_expiry = (apr_time_t)(expiry_total / (double)non_empty_subcaches); | |
| if (now < average_expiry) { | |
| ap_rprintf(r, "CacheTimeLeftOldestAvg: %d\n", (int)apr_time_sec(average_expiry - now)); | |
| ap_rprintf(r, "CacheTimeLeftOldestMin: %d\n", (int)apr_time_sec(min_expiry - now)); | |
| ap_rprintf(r, "CacheTimeLeftOldestMax: %d\n", (int)apr_time_sec(max_expiry - now)); | |
| } | |
| } | |
| ap_rprintf(r, "CacheIndexUsage: %d%%\n", index_pct); | |
| ap_rprintf(r, "CacheUsage: %d%%\n", cache_pct); | |
| ap_rprintf(r, "CacheStoreCount: %lu\n", header->stat_stores); | |
| ap_rprintf(r, "CacheReplaceCount: %lu\n", header->stat_replaced); | |
| ap_rprintf(r, "CacheExpireCount: %lu\n", header->stat_expiries); | |
| ap_rprintf(r, "CacheDiscardCount: %lu\n", header->stat_scrolled); | |
| ap_rprintf(r, "CacheRetrieveHitCount: %lu\n", header->stat_retrieves_hit); | |
| ap_rprintf(r, "CacheRetrieveMissCount: %lu\n", header->stat_retrieves_miss); | |
| ap_rprintf(r, "CacheRemoveHitCount: %lu\n", header->stat_removes_hit); | |
| ap_rprintf(r, "CacheRemoveMissCount: %lu\n", header->stat_removes_miss); | |
| } | |
| ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00841) "leaving shmcb_status"); | |
| } | |
| static apr_status_t socache_shmcb_iterate(ap_socache_instance_t *instance, | |
| server_rec *s, void *userctx, | |
| ap_socache_iterator_t *iterator, | |
| apr_pool_t *pool) | |
| { | |
| SHMCBHeader *header = instance->header; | |
| unsigned int loop; | |
| apr_time_t now = apr_time_now(); | |
| apr_status_t rv = APR_SUCCESS; | |
| apr_size_t buflen = 0; | |
| unsigned char *buf = NULL; | |
| /* Perform the iteration inside the mutex to avoid corruption or invalid | |
| * pointer arithmetic. The rest of our logic uses read-only header data so | |
| * doesn't need the lock. */ | |
| /* Iterate over the subcaches */ | |
| for (loop = 0; loop < header->subcache_num && rv == APR_SUCCESS; loop++) { | |
| SHMCBSubcache *subcache = SHMCB_SUBCACHE(header, loop); | |
| rv = shmcb_subcache_iterate(instance, s, userctx, header, subcache, | |
| iterator, &buf, &buflen, pool, now); | |
| } | |
| return rv; | |
| } | |
| /* | |
| * Subcache-level cache operations | |
| */ | |
| static void shmcb_subcache_expire(server_rec *s, SHMCBHeader *header, | |
| SHMCBSubcache *subcache, apr_time_t now) | |
| { | |
| unsigned int loop = 0, freed = 0, expired = 0; | |
| unsigned int new_idx_pos = subcache->idx_pos; | |
| SHMCBIndex *idx = NULL; | |
| while (loop < subcache->idx_used) { | |
| idx = SHMCB_INDEX(subcache, new_idx_pos); | |
| if (idx->removed) | |
| freed++; | |
| else if (idx->expires <= now) | |
| expired++; | |
| else | |
| /* not removed and not expired yet, we're done iterating */ | |
| break; | |
| loop++; | |
| new_idx_pos = SHMCB_CYCLIC_INCREMENT(new_idx_pos, 1, header->index_num); | |
| } | |
| if (!loop) | |
| /* Nothing to do */ | |
| return; | |
| ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00842) | |
| "expiring %u and reclaiming %u removed socache entries", | |
| expired, freed); | |
| if (loop == subcache->idx_used) { | |
| /* We're expiring everything, piece of cake */ | |
| subcache->idx_used = 0; | |
| subcache->data_used = 0; | |
| } else { | |
| /* There remain other indexes, so we can use idx to adjust 'data' */ | |
| unsigned int diff = SHMCB_CYCLIC_SPACE(subcache->data_pos, | |
| idx->data_pos, | |
| header->subcache_data_size); | |
| /* Adjust the indexes */ | |
| subcache->idx_used -= loop; | |
| subcache->idx_pos = new_idx_pos; | |
| /* Adjust the data area */ | |
| subcache->data_used -= diff; | |
| subcache->data_pos = idx->data_pos; | |
| } | |
| header->stat_expiries += expired; | |
| ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00843) | |
| "we now have %u socache entries", subcache->idx_used); | |
| } | |
| static int shmcb_subcache_store(server_rec *s, SHMCBHeader *header, | |
| SHMCBSubcache *subcache, | |
| unsigned char *data, unsigned int data_len, | |
| const unsigned char *id, unsigned int id_len, | |
| apr_time_t expiry) | |
| { | |
| unsigned int data_offset, new_idx, id_offset; | |
| SHMCBIndex *idx; | |
| unsigned int total_len = id_len + data_len; | |
| /* Sanity check the input */ | |
| if (total_len > header->subcache_data_size) { | |
| ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00844) | |
| "inserting socache entry larger (%d) than subcache data area (%d)", | |
| total_len, header->subcache_data_size); | |
| return -1; | |
| } | |
| /* First reclaim space from removed and expired records. */ | |
| shmcb_subcache_expire(s, header, subcache, apr_time_now()); | |
| /* Loop until there is enough space to insert | |
| * XXX: This should first compress out-of-order expiries and | |
| * removed records, and then force-remove oldest-first | |
| */ | |
| if (header->subcache_data_size - subcache->data_used < total_len | |
| || subcache->idx_used == header->index_num) { | |
| idx = SHMCB_INDEX(subcache, subcache->idx_pos); | |
| ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00845) | |
| "about to force-expire, subcache: idx_used=%d, " | |
| "data_used=%d", subcache->idx_used, subcache->data_used); | |
| do { | |
| SHMCBIndex *idx2; | |
| /* Adjust the indexes by one */ | |
| subcache->idx_pos = SHMCB_CYCLIC_INCREMENT(subcache->idx_pos, 1, | |
| header->index_num); | |
| subcache->idx_used--; | |
| if (!subcache->idx_used) { | |
| /* There's nothing left */ | |
| subcache->data_used = 0; | |
| break; | |
| } | |
| /* Adjust the data */ | |
| idx2 = SHMCB_INDEX(subcache, subcache->idx_pos); | |
| subcache->data_used -= SHMCB_CYCLIC_SPACE(idx->data_pos, idx2->data_pos, | |
| header->subcache_data_size); | |
| subcache->data_pos = idx2->data_pos; | |
| /* Stats */ | |
| header->stat_scrolled++; | |
| /* Loop admin */ | |
| idx = idx2; | |
| } while (header->subcache_data_size - subcache->data_used < total_len); | |
| ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00846) | |
| "finished force-expire, subcache: idx_used=%d, " | |
| "data_used=%d", subcache->idx_used, subcache->data_used); | |
| } | |
| /* HERE WE ASSUME THAT THE NEW ENTRY SHOULD GO ON THE END! I'M NOT | |
| * CHECKING WHETHER IT SHOULD BE GENUINELY "INSERTED" SOMEWHERE. | |
| * | |
| * We aught to fix that. httpd (never mind third party modules) | |
| * does not promise to perform any processing in date order | |
| * (c.f. FAQ "My log entries are not in date order!") | |
| */ | |
| /* Insert the id */ | |
| id_offset = SHMCB_CYCLIC_INCREMENT(subcache->data_pos, subcache->data_used, | |
| header->subcache_data_size); | |
| shmcb_cyclic_ntoc_memcpy(header->subcache_data_size, | |
| SHMCB_DATA(header, subcache), id_offset, | |
| id, id_len); | |
| subcache->data_used += id_len; | |
| /* Insert the data */ | |
| data_offset = SHMCB_CYCLIC_INCREMENT(subcache->data_pos, subcache->data_used, | |
| header->subcache_data_size); | |
| shmcb_cyclic_ntoc_memcpy(header->subcache_data_size, | |
| SHMCB_DATA(header, subcache), data_offset, | |
| data, data_len); | |
| subcache->data_used += data_len; | |
| /* Insert the index */ | |
| new_idx = SHMCB_CYCLIC_INCREMENT(subcache->idx_pos, subcache->idx_used, | |
| header->index_num); | |
| idx = SHMCB_INDEX(subcache, new_idx); | |
| idx->expires = expiry; | |
| idx->data_pos = id_offset; | |
| idx->data_used = total_len; | |
| idx->id_len = id_len; | |
| idx->removed = 0; | |
| subcache->idx_used++; | |
| ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00847) | |
| "insert happened at idx=%d, data=(%u:%u)", new_idx, | |
| id_offset, data_offset); | |
| ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00848) | |
| "finished insert, subcache: idx_pos/idx_used=%d/%d, " | |
| "data_pos/data_used=%d/%d", | |
| subcache->idx_pos, subcache->idx_used, | |
| subcache->data_pos, subcache->data_used); | |
| return 0; | |
| } | |
| static int shmcb_subcache_retrieve(server_rec *s, SHMCBHeader *header, | |
| SHMCBSubcache *subcache, | |
| const unsigned char *id, unsigned int idlen, | |
| unsigned char *dest, unsigned int *destlen) | |
| { | |
| unsigned int pos; | |
| unsigned int loop = 0; | |
| apr_time_t now = apr_time_now(); | |
| pos = subcache->idx_pos; | |
| while (loop < subcache->idx_used) { | |
| SHMCBIndex *idx = SHMCB_INDEX(subcache, pos); | |
| /* Only consider 'idx' if the id matches, and the "removed" | |
| * flag isn't set, and the record is not expired. | |
| * Check the data length too to avoid a buffer overflow | |
| * in case of corruption, which should be impossible, | |
| * but it's cheap to be safe. */ | |
| if (!idx->removed | |
| && idx->id_len == idlen | |
| && (idx->data_used - idx->id_len) <= *destlen | |
| && shmcb_cyclic_memcmp(header->subcache_data_size, | |
| SHMCB_DATA(header, subcache), | |
| idx->data_pos, id, idx->id_len) == 0) { | |
| ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00849) | |
| "match at idx=%d, data=%d", pos, idx->data_pos); | |
| if (idx->expires > now) { | |
| unsigned int data_offset; | |
| /* Find the offset of the data segment, after the id */ | |
| data_offset = SHMCB_CYCLIC_INCREMENT(idx->data_pos, | |
| idx->id_len, | |
| header->subcache_data_size); | |
| *destlen = idx->data_used - idx->id_len; | |
| /* Copy out the data */ | |
| shmcb_cyclic_cton_memcpy(header->subcache_data_size, | |
| dest, SHMCB_DATA(header, subcache), | |
| data_offset, *destlen); | |
| return 0; | |
| } | |
| else { | |
| /* Already stale, quietly remove and treat as not-found */ | |
| idx->removed = 1; | |
| header->stat_expiries++; | |
| ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00850) | |
| "shmcb_subcache_retrieve discarding expired entry"); | |
| return -1; | |
| } | |
| } | |
| /* Increment */ | |
| loop++; | |
| pos = SHMCB_CYCLIC_INCREMENT(pos, 1, header->index_num); | |
| } | |
| ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00851) | |
| "shmcb_subcache_retrieve found no match"); | |
| return -1; | |
| } | |
| static int shmcb_subcache_remove(server_rec *s, SHMCBHeader *header, | |
| SHMCBSubcache *subcache, | |
| const unsigned char *id, | |
| unsigned int idlen) | |
| { | |
| unsigned int pos; | |
| unsigned int loop = 0; | |
| pos = subcache->idx_pos; | |
| while (loop < subcache->idx_used) { | |
| SHMCBIndex *idx = SHMCB_INDEX(subcache, pos); | |
| /* Only consider 'idx' if the id matches, and the "removed" | |
| * flag isn't set. */ | |
| if (!idx->removed && idx->id_len == idlen | |
| && shmcb_cyclic_memcmp(header->subcache_data_size, | |
| SHMCB_DATA(header, subcache), | |
| idx->data_pos, id, idx->id_len) == 0) { | |
| ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00852) | |
| "possible match at idx=%d, data=%d", pos, idx->data_pos); | |
| /* Found the matching entry, remove it quietly. */ | |
| idx->removed = 1; | |
| ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00853) | |
| "shmcb_subcache_remove removing matching entry"); | |
| return 0; | |
| } | |
| /* Increment */ | |
| loop++; | |
| pos = SHMCB_CYCLIC_INCREMENT(pos, 1, header->index_num); | |
| } | |
| return -1; /* failure */ | |
| } | |
| static apr_status_t shmcb_subcache_iterate(ap_socache_instance_t *instance, | |
| server_rec *s, | |
| void *userctx, | |
| SHMCBHeader *header, | |
| SHMCBSubcache *subcache, | |
| ap_socache_iterator_t *iterator, | |
| unsigned char **buf, | |
| apr_size_t *buf_len, | |
| apr_pool_t *pool, | |
| apr_time_t now) | |
| { | |
| unsigned int pos; | |
| unsigned int loop = 0; | |
| apr_status_t rv; | |
| pos = subcache->idx_pos; | |
| while (loop < subcache->idx_used) { | |
| SHMCBIndex *idx = SHMCB_INDEX(subcache, pos); | |
| /* Only consider 'idx' if the "removed" flag isn't set. */ | |
| if (!idx->removed) { | |
| ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00854) | |
| "iterating idx=%d, data=%d", pos, idx->data_pos); | |
| if (idx->expires > now) { | |
| unsigned char *id = *buf; | |
| unsigned char *dest; | |
| unsigned int data_offset, dest_len; | |
| apr_size_t buf_req; | |
| /* Find the offset of the data segment, after the id */ | |
| data_offset = SHMCB_CYCLIC_INCREMENT(idx->data_pos, | |
| idx->id_len, | |
| header->subcache_data_size); | |
| dest_len = idx->data_used - idx->id_len; | |
| buf_req = APR_ALIGN_DEFAULT(idx->id_len + 1) | |
| + APR_ALIGN_DEFAULT(dest_len + 1); | |
| if (buf_req > *buf_len) { | |
| /* Grow to ~150% of this buffer requirement on resize | |
| * always using APR_ALIGN_DEFAULT sized pages | |
| */ | |
| *buf_len = buf_req + APR_ALIGN_DEFAULT(buf_req / 2); | |
| *buf = apr_palloc(pool, *buf_len); | |
| id = *buf; | |
| } | |
| dest = *buf + APR_ALIGN_DEFAULT(idx->id_len + 1); | |
| /* Copy out the data, because it's potentially cyclic */ | |
| shmcb_cyclic_cton_memcpy(header->subcache_data_size, id, | |
| SHMCB_DATA(header, subcache), | |
| idx->data_pos, idx->id_len); | |
| id[idx->id_len] = '\0'; | |
| shmcb_cyclic_cton_memcpy(header->subcache_data_size, dest, | |
| SHMCB_DATA(header, subcache), | |
| data_offset, dest_len); | |
| dest[dest_len] = '\0'; | |
| rv = iterator(instance, s, userctx, id, idx->id_len, | |
| dest, dest_len, pool); | |
| ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, APLOGNO(00855) | |
| "shmcb entry iterated"); | |
| if (rv != APR_SUCCESS) | |
| return rv; | |
| } | |
| else { | |
| /* Already stale, quietly remove and treat as not-found */ | |
| idx->removed = 1; | |
| header->stat_expiries++; | |
| ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00856) | |
| "shmcb_subcache_iterate discarding expired entry"); | |
| } | |
| } | |
| /* Increment */ | |
| loop++; | |
| pos = SHMCB_CYCLIC_INCREMENT(pos, 1, header->index_num); | |
| } | |
| return APR_SUCCESS; | |
| } | |
| static const ap_socache_provider_t socache_shmcb = { | |
| "shmcb", | |
| AP_SOCACHE_FLAG_NOTMPSAFE, | |
| socache_shmcb_create, | |
| socache_shmcb_init, | |
| socache_shmcb_destroy, | |
| socache_shmcb_store, | |
| socache_shmcb_retrieve, | |
| socache_shmcb_remove, | |
| socache_shmcb_status, | |
| socache_shmcb_iterate | |
| }; | |
| static void register_hooks(apr_pool_t *p) | |
| { | |
| ap_register_provider(p, AP_SOCACHE_PROVIDER_GROUP, "shmcb", | |
| AP_SOCACHE_PROVIDER_VERSION, | |
| &socache_shmcb); | |
| /* Also register shmcb under the default provider name. */ | |
| ap_register_provider(p, AP_SOCACHE_PROVIDER_GROUP, | |
| AP_SOCACHE_DEFAULT_PROVIDER, | |
| AP_SOCACHE_PROVIDER_VERSION, | |
| &socache_shmcb); | |
| } | |
| AP_DECLARE_MODULE(socache_shmcb) = { | |
| STANDARD20_MODULE_STUFF, | |
| NULL, NULL, NULL, NULL, NULL, | |
| register_hooks | |
| }; |