1/* Licensed to the Apache Software Foundation (ASF) under one or more 2 * contributor license agreements. See the NOTICE file distributed with 3 * this work for additional information regarding copyright ownership. 4 * The ASF licenses this file to You under the Apache License, Version 2.0 5 * (the "License"); you may not use this file except in compliance with 6 * the License. You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17#include "httpd.h" 18#include "http_log.h" 19#include "http_request.h" 20#include "http_protocol.h" 21#include "http_config.h" 22 23#include "apr.h" 24#include "apr_strings.h" 25#include "apr_time.h" 26#include "apr_shm.h" 27#define APR_WANT_STRFUNC 28#include "apr_want.h" 29#include "apr_general.h" 30 31#include "ap_socache.h" 32 33/* XXX Unfortunately, there are still many unsigned ints in use here, so we 34 * XXX cannot allow more than UINT_MAX. Since some of the ints are exposed in 35 * XXX public interfaces, a simple search and replace is not enough. 36 * XXX It should be possible to extend that so that the total cache size can 37 * XXX be APR_SIZE_MAX and only the object size needs to be smaller than 38 * XXX UINT_MAX. 39 */ 40#define SHMCB_MAX_SIZE (UINT_MAX<APR_SIZE_MAX ? UINT_MAX : APR_SIZE_MAX) 41 42#define DEFAULT_SHMCB_PREFIX "socache-shmcb-" 43 44#define DEFAULT_SHMCB_SUFFIX ".cache" 45 46#define ALIGNED_HEADER_SIZE APR_ALIGN_DEFAULT(sizeof(SHMCBHeader)) 47#define ALIGNED_SUBCACHE_SIZE APR_ALIGN_DEFAULT(sizeof(SHMCBSubcache)) 48#define ALIGNED_INDEX_SIZE APR_ALIGN_DEFAULT(sizeof(SHMCBIndex)) 49 50/* 51 * Header structure - the start of the shared-mem segment 52 */ 53typedef struct { 54 /* Stats for cache operations */ 55 unsigned long stat_stores; 56 unsigned long stat_replaced; 57 unsigned long stat_expiries; 58 unsigned long stat_scrolled; 59 unsigned long stat_retrieves_hit; 60 unsigned long stat_retrieves_miss; 61 unsigned long stat_removes_hit; 62 unsigned long stat_removes_miss; 63 /* Number of subcaches */ 64 unsigned int subcache_num; 65 /* How many indexes each subcache's queue has */ 66 unsigned int index_num; 67 /* How large each subcache is, including the queue and data */ 68 unsigned int subcache_size; 69 /* How far into each subcache the data area is (optimisation) */ 70 unsigned int subcache_data_offset; 71 /* How large the data area in each subcache is (optimisation) */ 72 unsigned int subcache_data_size; 73} SHMCBHeader; 74 75/* 76 * Subcache structure - the start of each subcache, followed by 77 * indexes then data 78 */ 79typedef struct { 80 /* The start position and length of the cyclic buffer of indexes */ 81 unsigned int idx_pos, idx_used; 82 /* Same for the data area */ 83 unsigned int data_pos, data_used; 84} SHMCBSubcache; 85 86/* 87 * Index structure - each subcache has an array of these 88 */ 89typedef struct { 90 /* absolute time this entry expires */ 91 apr_time_t expires; 92 /* location within the subcache's data area */ 93 unsigned int data_pos; 94 /* size (most logic ignores this, we keep it only to minimise memcpy) */ 95 unsigned int data_used; 96 /* length of the used data which contains the id */ 97 unsigned int id_len; 98 /* Used to mark explicitly-removed socache entries */ 99 unsigned char removed; 100} SHMCBIndex; 101 102struct ap_socache_instance_t { 103 const char *data_file; 104 apr_size_t shm_size; 105 apr_shm_t *shm; 106 SHMCBHeader *header; 107}; 108 109/* The SHM data segment is of fixed size and stores data as follows. 110 * 111 * [ SHMCBHeader | Subcaches ] 112 * 113 * The SHMCBHeader header structure stores metadata concerning the 114 * cache and the contained subcaches. 115 * 116 * Subcaches is a hash table of header->subcache_num SHMCBSubcache 117 * structures. The hash table is indexed by SHMCB_MASK(id). Each 118 * SHMCBSubcache structure has a fixed size (header->subcache_size), 119 * which is determined at creation time, and looks like the following: 120 * 121 * [ SHMCBSubcache | Indexes | Data ] 122 * 123 * Each subcache is prefixed by the SHMCBSubcache structure. 124 * 125 * The subcache's "Data" segment is a single cyclic data buffer, of 126 * total size header->subcache_data_size; data inside is referenced 127 * using byte offsets. The offset marking the beginning of the cyclic 128 * buffer is subcache->data_pos; the buffer's length is 129 * subcache->data_used. 130 * 131 * "Indexes" is an array of header->index_num SHMCBIndex structures, 132 * which is used as a cyclic queue; subcache->idx_pos gives the array 133 * index of the first in use, subcache->idx_used gives the number in 134 * use. Both ->idx_* values have a range of [0, header->index_num) 135 * 136 * Each in-use SHMCBIndex structure represents a single cached object. 137 * The ID and data segment are stored consecutively in the subcache's 138 * cyclic data buffer. The "Data" segment can thus be seen to 139 * look like this, for example 140 * 141 * offset: [ 0 1 2 3 4 5 6 ... 142 * contents:[ ID1 Data1 ID2 Data2 ID3 ... 143 * 144 * where the corresponding indices would look like: 145 * 146 * idx1 = { data_pos = 0, data_used = 3, id_len = 1, ...} 147 * idx2 = { data_pos = 3, data_used = 3, id_len = 1, ...} 148 * ... 149 */ 150 151/* This macro takes a pointer to the header and a zero-based index and returns 152 * a pointer to the corresponding subcache. */ 153#define SHMCB_SUBCACHE(pHeader, num) \ 154 (SHMCBSubcache *)(((unsigned char *)(pHeader)) + \ 155 ALIGNED_HEADER_SIZE + \ 156 (num) * ((pHeader)->subcache_size)) 157 158/* This macro takes a pointer to the header and an id and returns a 159 * pointer to the corresponding subcache. */ 160#define SHMCB_MASK(pHeader, id) \ 161 SHMCB_SUBCACHE((pHeader), *(id) & ((pHeader)->subcache_num - 1)) 162 163/* This macro takes the same params as the last, generating two outputs for use 164 * in ap_log_error(...). */ 165#define SHMCB_MASK_DBG(pHeader, id) \ 166 *(id), (*(id) & ((pHeader)->subcache_num - 1)) 167 168/* This macro takes a pointer to a subcache and a zero-based index and returns 169 * a pointer to the corresponding SHMCBIndex. */ 170#define SHMCB_INDEX(pSubcache, num) \ 171 (SHMCBIndex *)(((unsigned char *)pSubcache) + \ 172 ALIGNED_SUBCACHE_SIZE + \ 173 (num) * ALIGNED_INDEX_SIZE) 174 175/* This macro takes a pointer to the header and a subcache and returns a 176 * pointer to the corresponding data area. */ 177#define SHMCB_DATA(pHeader, pSubcache) \ 178 ((unsigned char *)(pSubcache) + (pHeader)->subcache_data_offset) 179 180/* 181 * Cyclic functions - assists in "wrap-around"/modulo logic 182 */ 183 184/* Addition modulo 'mod' */ 185#define SHMCB_CYCLIC_INCREMENT(val,inc,mod) \ 186 (((val) + (inc)) % (mod)) 187 188/* Subtraction (or "distance between") modulo 'mod' */ 189#define SHMCB_CYCLIC_SPACE(val1,val2,mod) \ 190 ((val2) >= (val1) ? ((val2) - (val1)) : \ 191 ((val2) + (mod) - (val1))) 192 193/* A "normal-to-cyclic" memcpy. */ 194static void shmcb_cyclic_ntoc_memcpy(unsigned int buf_size, unsigned char *data, 195 unsigned int dest_offset, const unsigned char *src, 196 unsigned int src_len) 197{ 198 if (dest_offset + src_len < buf_size) 199 /* It be copied all in one go */ 200 memcpy(data + dest_offset, src, src_len); 201 else { 202 /* Copy the two splits */ 203 memcpy(data + dest_offset, src, buf_size - dest_offset); 204 memcpy(data, src + buf_size - dest_offset, 205 src_len + dest_offset - buf_size); 206 } 207} 208 209/* A "cyclic-to-normal" memcpy. */ 210static void shmcb_cyclic_cton_memcpy(unsigned int buf_size, unsigned char *dest, 211 const unsigned char *data, unsigned int src_offset, 212 unsigned int src_len) 213{ 214 if (src_offset + src_len < buf_size) 215 /* It be copied all in one go */ 216 memcpy(dest, data + src_offset, src_len); 217 else { 218 /* Copy the two splits */ 219 memcpy(dest, data + src_offset, buf_size - src_offset); 220 memcpy(dest + buf_size - src_offset, data, 221 src_len + src_offset - buf_size); 222 } 223} 224 225/* A memcmp against a cyclic data buffer. Compares SRC of length 226 * SRC_LEN against the contents of cyclic buffer DATA (which is of 227 * size BUF_SIZE), starting at offset DEST_OFFSET. Got that? Good. */ 228static int shmcb_cyclic_memcmp(unsigned int buf_size, unsigned char *data, 229 unsigned int dest_offset, 230 const unsigned char *src, 231 unsigned int src_len) 232{ 233 if (dest_offset + src_len < buf_size) 234 /* It be compared all in one go */ 235 return memcmp(data + dest_offset, src, src_len); 236 else { 237 /* Compare the two splits */ 238 int diff; 239 240 diff = memcmp(data + dest_offset, src, buf_size - dest_offset); 241 if (diff) { 242 return diff; 243 } 244 return memcmp(data, src + buf_size - dest_offset, 245 src_len + dest_offset - buf_size); 246 } 247} 248 249 250/* Prototypes for low-level subcache operations */ 251static void shmcb_subcache_expire(server_rec *, SHMCBHeader *, SHMCBSubcache *, 252 apr_time_t); 253/* Returns zero on success, non-zero on failure. */ 254static int shmcb_subcache_store(server_rec *s, SHMCBHeader *header, 255 SHMCBSubcache *subcache, 256 unsigned char *data, unsigned int data_len, 257 const unsigned char *id, unsigned int id_len, 258 apr_time_t expiry); 259/* Returns zero on success, non-zero on failure. */ 260static int shmcb_subcache_retrieve(server_rec *, SHMCBHeader *, SHMCBSubcache *, 261 const unsigned char *id, unsigned int idlen, 262 unsigned char *data, unsigned int *datalen); 263/* Returns zero on success, non-zero on failure. */ 264static int shmcb_subcache_remove(server_rec *, SHMCBHeader *, SHMCBSubcache *, 265 const unsigned char *, unsigned int); 266 267/* Returns result of the (iterator)() call, zero is success (continue) */ 268static apr_status_t shmcb_subcache_iterate(ap_socache_instance_t *instance, 269 server_rec *s, 270 void *userctx, 271 SHMCBHeader *header, 272 SHMCBSubcache *subcache, 273 ap_socache_iterator_t *iterator, 274 unsigned char **buf, 275 apr_size_t *buf_len, 276 apr_pool_t *pool, 277 apr_time_t now); 278 279/* 280 * High-Level "handlers" as per ssl_scache.c 281 * subcache internals are deferred to shmcb_subcache_*** functions lower down 282 */ 283 284static const char *socache_shmcb_create(ap_socache_instance_t **context, 285 const char *arg, 286 apr_pool_t *tmp, apr_pool_t *p) 287{ 288 ap_socache_instance_t *ctx; 289 char *path, *cp, *cp2; 290 291 /* Allocate the context. */ 292 *context = ctx = apr_pcalloc(p, sizeof *ctx); 293 294 ctx->shm_size = 1024*512; /* 512KB */ 295 296 if (!arg || *arg == '\0') { 297 /* Use defaults. */ 298 return NULL; 299 } 300 301 ctx->data_file = path = ap_server_root_relative(p, arg); 302 303 cp = strrchr(path, '('); 304 cp2 = path + strlen(path) - 1; 305 if (cp) { 306 char *endptr; 307 if (*cp2 != ')') { 308 return "Invalid argument: no closing parenthesis or cache size " 309 "missing after pathname with parenthesis"; 310 } 311 *cp++ = '\0'; 312 *cp2 = '\0'; 313 314 315 ctx->shm_size = strtol(cp, &endptr, 10); 316 if (endptr != cp2) { 317 return "Invalid argument: cache size not numerical"; 318 } 319 320 if (ctx->shm_size < 8192) { 321 return "Invalid argument: size has to be >= 8192 bytes"; 322 323 } 324 325 if (ctx->shm_size >= SHMCB_MAX_SIZE) { 326 return apr_psprintf(tmp, "Invalid argument: size has " 327 "to be < %" APR_SIZE_T_FMT " bytes on this platform", 328 SHMCB_MAX_SIZE); 329 } 330 } 331 else if (cp2 >= path && *cp2 == ')') { 332 return "Invalid argument: no opening parenthesis"; 333 } 334 335 return NULL; 336} 337 338static apr_status_t socache_shmcb_init(ap_socache_instance_t *ctx, 339 const char *namespace, 340 const struct ap_socache_hints *hints, 341 server_rec *s, apr_pool_t *p) 342{ 343 void *shm_segment; 344 apr_size_t shm_segsize; 345 apr_status_t rv; 346 SHMCBHeader *header; 347 unsigned int num_subcache, num_idx, loop; 348 apr_size_t avg_obj_size, avg_id_len; 349 350 /* Create shared memory segment */ 351 if (ctx->data_file == NULL) { 352 const char *path = apr_pstrcat(p, DEFAULT_SHMCB_PREFIX, namespace, 353 DEFAULT_SHMCB_SUFFIX, NULL); 354 355 ctx->data_file = ap_runtime_dir_relative(p, path); 356 } 357 358 /* Use anonymous shm by default, fall back on name-based. */ 359 rv = apr_shm_create(&ctx->shm, ctx->shm_size, NULL, p); 360 if (APR_STATUS_IS_ENOTIMPL(rv)) { 361 /* If anon shm isn't supported, fail if no named file was 362 * configured successfully; the ap_server_root_relative call 363 * above will return NULL for invalid paths. */ 364 if (ctx->data_file == NULL) { 365 ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00818) 366 "Could not use default path '%s' for shmcb socache", 367 ctx->data_file); 368 return APR_EINVAL; 369 } 370 371 /* For a name-based segment, remove it first in case of a 372 * previous unclean shutdown. */ 373 apr_shm_remove(ctx->data_file, p); 374 375 rv = apr_shm_create(&ctx->shm, ctx->shm_size, ctx->data_file, p); 376 } 377 378 if (rv != APR_SUCCESS) { 379 ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00819) 380 "Could not allocate shared memory segment for shmcb " 381 "socache"); 382 return rv; 383 } 384 385 shm_segment = apr_shm_baseaddr_get(ctx->shm); 386 shm_segsize = apr_shm_size_get(ctx->shm); 387 if (shm_segsize < (5 * ALIGNED_HEADER_SIZE)) { 388 /* the segment is ridiculously small, bail out */ 389 ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00820) 390 "shared memory segment too small"); 391 return APR_ENOSPC; 392 } 393 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00821) 394 "shmcb_init allocated %" APR_SIZE_T_FMT 395 " bytes of shared memory", 396 shm_segsize); 397 /* Discount the header */ 398 shm_segsize -= ALIGNED_HEADER_SIZE; 399 /* Select index size based on average object size hints, if given. */ 400 avg_obj_size = hints && hints->avg_obj_size ? hints->avg_obj_size : 150; 401 avg_id_len = hints && hints->avg_id_len ? hints->avg_id_len : 30; 402 num_idx = (shm_segsize) / (avg_obj_size + avg_id_len); 403 num_subcache = 256; 404 while ((num_idx / num_subcache) < (2 * num_subcache)) 405 num_subcache /= 2; 406 num_idx /= num_subcache; 407 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00822) 408 "for %" APR_SIZE_T_FMT " bytes (%" APR_SIZE_T_FMT 409 " including header), recommending %u subcaches, " 410 "%u indexes each", shm_segsize, 411 shm_segsize + ALIGNED_HEADER_SIZE, 412 num_subcache, num_idx); 413 if (num_idx < 5) { 414 /* we're still too small, bail out */ 415 ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00823) 416 "shared memory segment too small"); 417 return APR_ENOSPC; 418 } 419 /* OK, we're sorted */ 420 ctx->header = header = shm_segment; 421 header->stat_stores = 0; 422 header->stat_replaced = 0; 423 header->stat_expiries = 0; 424 header->stat_scrolled = 0; 425 header->stat_retrieves_hit = 0; 426 header->stat_retrieves_miss = 0; 427 header->stat_removes_hit = 0; 428 header->stat_removes_miss = 0; 429 header->subcache_num = num_subcache; 430 /* Convert the subcache size (in bytes) to a value that is suitable for 431 * structure alignment on the host platform, by rounding down if necessary. */ 432 header->subcache_size = (size_t)(shm_segsize / num_subcache); 433 if (header->subcache_size != APR_ALIGN_DEFAULT(header->subcache_size)) { 434 header->subcache_size = APR_ALIGN_DEFAULT(header->subcache_size) - 435 APR_ALIGN_DEFAULT(1); 436 } 437 header->subcache_data_offset = ALIGNED_SUBCACHE_SIZE + 438 num_idx * ALIGNED_INDEX_SIZE; 439 header->subcache_data_size = header->subcache_size - 440 header->subcache_data_offset; 441 header->index_num = num_idx; 442 443 /* Output trace info */ 444 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00824) 445 "shmcb_init_memory choices follow"); 446 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00825) 447 "subcache_num = %u", header->subcache_num); 448 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00826) 449 "subcache_size = %u", header->subcache_size); 450 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00827) 451 "subcache_data_offset = %u", header->subcache_data_offset); 452 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00828) 453 "subcache_data_size = %u", header->subcache_data_size); 454 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00829) 455 "index_num = %u", header->index_num); 456 /* The header is done, make the caches empty */ 457 for (loop = 0; loop < header->subcache_num; loop++) { 458 SHMCBSubcache *subcache = SHMCB_SUBCACHE(header, loop); 459 subcache->idx_pos = subcache->idx_used = 0; 460 subcache->data_pos = subcache->data_used = 0; 461 } 462 ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(00830) 463 "Shared memory socache initialised"); 464 /* Success ... */ 465 466 return APR_SUCCESS; 467} 468 469static void socache_shmcb_destroy(ap_socache_instance_t *ctx, server_rec *s) 470{ 471 if (ctx && ctx->shm) { 472 apr_shm_destroy(ctx->shm); 473 ctx->shm = NULL; 474 } 475} 476 477static apr_status_t socache_shmcb_store(ap_socache_instance_t *ctx, 478 server_rec *s, const unsigned char *id, 479 unsigned int idlen, apr_time_t expiry, 480 unsigned char *encoded, 481 unsigned int len_encoded, 482 apr_pool_t *p) 483{ 484 SHMCBHeader *header = ctx->header; 485 SHMCBSubcache *subcache = SHMCB_MASK(header, id); 486 int tryreplace; 487 488 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00831) 489 "socache_shmcb_store (0x%02x -> subcache %d)", 490 SHMCB_MASK_DBG(header, id)); 491 /* XXX: Says who? Why shouldn't this be acceptable, or padded if not? */ 492 if (idlen < 4) { 493 ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00832) "unusably short id provided " 494 "(%u bytes)", idlen); 495 return APR_EINVAL; 496 } 497 tryreplace = shmcb_subcache_remove(s, header, subcache, id, idlen); 498 if (shmcb_subcache_store(s, header, subcache, encoded, 499 len_encoded, id, idlen, expiry)) { 500 ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00833) 501 "can't store an socache entry!"); 502 return APR_ENOSPC; 503 } 504 if (tryreplace == 0) { 505 header->stat_replaced++; 506 } 507 else { 508 header->stat_stores++; 509 } 510 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00834) 511 "leaving socache_shmcb_store successfully"); 512 return APR_SUCCESS; 513} 514 515static apr_status_t socache_shmcb_retrieve(ap_socache_instance_t *ctx, 516 server_rec *s, 517 const unsigned char *id, unsigned int idlen, 518 unsigned char *dest, unsigned int *destlen, 519 apr_pool_t *p) 520{ 521 SHMCBHeader *header = ctx->header; 522 SHMCBSubcache *subcache = SHMCB_MASK(header, id); 523 int rv; 524 525 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00835) 526 "socache_shmcb_retrieve (0x%02x -> subcache %d)", 527 SHMCB_MASK_DBG(header, id)); 528 529 /* Get the entry corresponding to the id, if it exists. */ 530 rv = shmcb_subcache_retrieve(s, header, subcache, id, idlen, 531 dest, destlen); 532 if (rv == 0) 533 header->stat_retrieves_hit++; 534 else 535 header->stat_retrieves_miss++; 536 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00836) 537 "leaving socache_shmcb_retrieve successfully"); 538 539 return rv == 0 ? APR_SUCCESS : APR_NOTFOUND; 540} 541 542static apr_status_t socache_shmcb_remove(ap_socache_instance_t *ctx, 543 server_rec *s, const unsigned char *id, 544 unsigned int idlen, apr_pool_t *p) 545{ 546 SHMCBHeader *header = ctx->header; 547 SHMCBSubcache *subcache = SHMCB_MASK(header, id); 548 apr_status_t rv; 549 550 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00837) 551 "socache_shmcb_remove (0x%02x -> subcache %d)", 552 SHMCB_MASK_DBG(header, id)); 553 if (idlen < 4) { 554 ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00838) "unusably short id provided " 555 "(%u bytes)", idlen); 556 return APR_EINVAL; 557 } 558 if (shmcb_subcache_remove(s, header, subcache, id, idlen) == 0) { 559 header->stat_removes_hit++; 560 rv = APR_SUCCESS; 561 } else { 562 header->stat_removes_miss++; 563 rv = APR_NOTFOUND; 564 } 565 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00839) 566 "leaving socache_shmcb_remove successfully"); 567 568 return rv; 569} 570 571static void socache_shmcb_status(ap_socache_instance_t *ctx, 572 request_rec *r, int flags) 573{ 574 server_rec *s = r->server; 575 SHMCBHeader *header = ctx->header; 576 unsigned int loop, total = 0, cache_total = 0, non_empty_subcaches = 0; 577 apr_time_t idx_expiry, min_expiry = 0, max_expiry = 0; 578 apr_time_t now = apr_time_now(); 579 double expiry_total = 0; 580 int index_pct, cache_pct; 581 582 AP_DEBUG_ASSERT(header->subcache_num > 0); 583 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00840) "inside shmcb_status"); 584 /* Perform the iteration inside the mutex to avoid corruption or invalid 585 * pointer arithmetic. The rest of our logic uses read-only header data so 586 * doesn't need the lock. */ 587 /* Iterate over the subcaches */ 588 for (loop = 0; loop < header->subcache_num; loop++) { 589 SHMCBSubcache *subcache = SHMCB_SUBCACHE(header, loop); 590 shmcb_subcache_expire(s, header, subcache, now); 591 total += subcache->idx_used; 592 cache_total += subcache->data_used; 593 if (subcache->idx_used) { 594 SHMCBIndex *idx = SHMCB_INDEX(subcache, subcache->idx_pos); 595 non_empty_subcaches++; 596 idx_expiry = idx->expires; 597 expiry_total += (double)idx_expiry; 598 max_expiry = ((idx_expiry > max_expiry) ? idx_expiry : max_expiry); 599 if (!min_expiry) 600 min_expiry = idx_expiry; 601 else 602 min_expiry = ((idx_expiry < min_expiry) ? idx_expiry : min_expiry); 603 } 604 } 605 index_pct = (100 * total) / (header->index_num * 606 header->subcache_num); 607 cache_pct = (100 * cache_total) / (header->subcache_data_size * 608 header->subcache_num); 609 /* Generate HTML */ 610 ap_rprintf(r, "cache type: <b>SHMCB</b>, shared memory: <b>%" APR_SIZE_T_FMT "</b> " 611 "bytes, current entries: <b>%d</b><br>", 612 ctx->shm_size, total); 613 ap_rprintf(r, "subcaches: <b>%d</b>, indexes per subcache: <b>%d</b><br>", 614 header->subcache_num, header->index_num); 615 if (non_empty_subcaches) { 616 apr_time_t average_expiry = (apr_time_t)(expiry_total / (double)non_empty_subcaches); 617 ap_rprintf(r, "time left on oldest entries' objects: "); 618 if (now < average_expiry) 619 ap_rprintf(r, "avg: <b>%d</b> seconds, (range: %d...%d)<br>", 620 (int)apr_time_sec(average_expiry - now), 621 (int)apr_time_sec(min_expiry - now), 622 (int)apr_time_sec(max_expiry - now)); 623 else 624 ap_rprintf(r, "expiry_threshold: <b>Calculation error!</b><br>"); 625 } 626 627 ap_rprintf(r, "index usage: <b>%d%%</b>, cache usage: <b>%d%%</b><br>", 628 index_pct, cache_pct); 629 ap_rprintf(r, "total entries stored since starting: <b>%lu</b><br>", 630 header->stat_stores); 631 ap_rprintf(r, "total entries replaced since starting: <b>%lu</b><br>", 632 header->stat_replaced); 633 ap_rprintf(r, "total entries expired since starting: <b>%lu</b><br>", 634 header->stat_expiries); 635 ap_rprintf(r, "total (pre-expiry) entries scrolled out of the cache: " 636 "<b>%lu</b><br>", header->stat_scrolled); 637 ap_rprintf(r, "total retrieves since starting: <b>%lu</b> hit, " 638 "<b>%lu</b> miss<br>", header->stat_retrieves_hit, 639 header->stat_retrieves_miss); 640 ap_rprintf(r, "total removes since starting: <b>%lu</b> hit, " 641 "<b>%lu</b> miss<br>", header->stat_removes_hit, 642 header->stat_removes_miss); 643 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00841) "leaving shmcb_status"); 644} 645 646static apr_status_t socache_shmcb_iterate(ap_socache_instance_t *instance, 647 server_rec *s, void *userctx, 648 ap_socache_iterator_t *iterator, 649 apr_pool_t *pool) 650{ 651 SHMCBHeader *header = instance->header; 652 unsigned int loop; 653 apr_time_t now = apr_time_now(); 654 apr_status_t rv = APR_SUCCESS; 655 apr_size_t buflen = 0; 656 unsigned char *buf = NULL; 657 658 /* Perform the iteration inside the mutex to avoid corruption or invalid 659 * pointer arithmetic. The rest of our logic uses read-only header data so 660 * doesn't need the lock. */ 661 /* Iterate over the subcaches */ 662 for (loop = 0; loop < header->subcache_num && rv == APR_SUCCESS; loop++) { 663 SHMCBSubcache *subcache = SHMCB_SUBCACHE(header, loop); 664 rv = shmcb_subcache_iterate(instance, s, userctx, header, subcache, 665 iterator, &buf, &buflen, pool, now); 666 } 667 return rv; 668} 669 670/* 671 * Subcache-level cache operations 672 */ 673 674static void shmcb_subcache_expire(server_rec *s, SHMCBHeader *header, 675 SHMCBSubcache *subcache, apr_time_t now) 676{ 677 unsigned int loop = 0, freed = 0, expired = 0; 678 unsigned int new_idx_pos = subcache->idx_pos; 679 SHMCBIndex *idx = NULL; 680 681 while (loop < subcache->idx_used) { 682 idx = SHMCB_INDEX(subcache, new_idx_pos); 683 if (idx->removed) 684 freed++; 685 else if (idx->expires <= now) 686 expired++; 687 else 688 /* not removed and not expired yet, we're done iterating */ 689 break; 690 loop++; 691 new_idx_pos = SHMCB_CYCLIC_INCREMENT(new_idx_pos, 1, header->index_num); 692 } 693 if (!loop) 694 /* Nothing to do */ 695 return; 696 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00842) 697 "expiring %u and reclaiming %u removed socache entries", 698 expired, freed); 699 if (loop == subcache->idx_used) { 700 /* We're expiring everything, piece of cake */ 701 subcache->idx_used = 0; 702 subcache->data_used = 0; 703 } else { 704 /* There remain other indexes, so we can use idx to adjust 'data' */ 705 unsigned int diff = SHMCB_CYCLIC_SPACE(subcache->data_pos, 706 idx->data_pos, 707 header->subcache_data_size); 708 /* Adjust the indexes */ 709 subcache->idx_used -= loop; 710 subcache->idx_pos = new_idx_pos; 711 /* Adjust the data area */ 712 subcache->data_used -= diff; 713 subcache->data_pos = idx->data_pos; 714 } 715 header->stat_expiries += expired; 716 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00843) 717 "we now have %u socache entries", subcache->idx_used); 718} 719 720static int shmcb_subcache_store(server_rec *s, SHMCBHeader *header, 721 SHMCBSubcache *subcache, 722 unsigned char *data, unsigned int data_len, 723 const unsigned char *id, unsigned int id_len, 724 apr_time_t expiry) 725{ 726 unsigned int data_offset, new_idx, id_offset; 727 SHMCBIndex *idx; 728 unsigned int total_len = id_len + data_len; 729 730 /* Sanity check the input */ 731 if (total_len > header->subcache_data_size) { 732 ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00844) 733 "inserting socache entry larger (%d) than subcache data area (%d)", 734 total_len, header->subcache_data_size); 735 return -1; 736 } 737 738 /* First reclaim space from removed and expired records. */ 739 shmcb_subcache_expire(s, header, subcache, apr_time_now()); 740 741 /* Loop until there is enough space to insert 742 * XXX: This should first compress out-of-order expiries and 743 * removed records, and then force-remove oldest-first 744 */ 745 if (header->subcache_data_size - subcache->data_used < total_len 746 || subcache->idx_used == header->index_num) { 747 unsigned int loop = 0; 748 749 idx = SHMCB_INDEX(subcache, subcache->idx_pos); 750 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00845) 751 "about to force-expire, subcache: idx_used=%d, " 752 "data_used=%d", subcache->idx_used, subcache->data_used); 753 do { 754 SHMCBIndex *idx2; 755 756 /* Adjust the indexes by one */ 757 subcache->idx_pos = SHMCB_CYCLIC_INCREMENT(subcache->idx_pos, 1, 758 header->index_num); 759 subcache->idx_used--; 760 if (!subcache->idx_used) { 761 /* There's nothing left */ 762 subcache->data_used = 0; 763 break; 764 } 765 /* Adjust the data */ 766 idx2 = SHMCB_INDEX(subcache, subcache->idx_pos); 767 subcache->data_used -= SHMCB_CYCLIC_SPACE(idx->data_pos, idx2->data_pos, 768 header->subcache_data_size); 769 subcache->data_pos = idx2->data_pos; 770 /* Stats */ 771 header->stat_scrolled++; 772 /* Loop admin */ 773 idx = idx2; 774 loop++; 775 } while (header->subcache_data_size - subcache->data_used < total_len); 776 777 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00846) 778 "finished force-expire, subcache: idx_used=%d, " 779 "data_used=%d", subcache->idx_used, subcache->data_used); 780 } 781 782 /* HERE WE ASSUME THAT THE NEW ENTRY SHOULD GO ON THE END! I'M NOT 783 * CHECKING WHETHER IT SHOULD BE GENUINELY "INSERTED" SOMEWHERE. 784 * 785 * We aught to fix that. httpd (never mind third party modules) 786 * does not promise to perform any processing in date order 787 * (c.f. FAQ "My log entries are not in date order!") 788 */ 789 /* Insert the id */ 790 id_offset = SHMCB_CYCLIC_INCREMENT(subcache->data_pos, subcache->data_used, 791 header->subcache_data_size); 792 shmcb_cyclic_ntoc_memcpy(header->subcache_data_size, 793 SHMCB_DATA(header, subcache), id_offset, 794 id, id_len); 795 subcache->data_used += id_len; 796 /* Insert the data */ 797 data_offset = SHMCB_CYCLIC_INCREMENT(subcache->data_pos, subcache->data_used, 798 header->subcache_data_size); 799 shmcb_cyclic_ntoc_memcpy(header->subcache_data_size, 800 SHMCB_DATA(header, subcache), data_offset, 801 data, data_len); 802 subcache->data_used += data_len; 803 /* Insert the index */ 804 new_idx = SHMCB_CYCLIC_INCREMENT(subcache->idx_pos, subcache->idx_used, 805 header->index_num); 806 idx = SHMCB_INDEX(subcache, new_idx); 807 idx->expires = expiry; 808 idx->data_pos = id_offset; 809 idx->data_used = total_len; 810 idx->id_len = id_len; 811 idx->removed = 0; 812 subcache->idx_used++; 813 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00847) 814 "insert happened at idx=%d, data=(%u:%u)", new_idx, 815 id_offset, data_offset); 816 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00848) 817 "finished insert, subcache: idx_pos/idx_used=%d/%d, " 818 "data_pos/data_used=%d/%d", 819 subcache->idx_pos, subcache->idx_used, 820 subcache->data_pos, subcache->data_used); 821 return 0; 822} 823 824static int shmcb_subcache_retrieve(server_rec *s, SHMCBHeader *header, 825 SHMCBSubcache *subcache, 826 const unsigned char *id, unsigned int idlen, 827 unsigned char *dest, unsigned int *destlen) 828{ 829 unsigned int pos; 830 unsigned int loop = 0; 831 apr_time_t now = apr_time_now(); 832 833 pos = subcache->idx_pos; 834 835 while (loop < subcache->idx_used) { 836 SHMCBIndex *idx = SHMCB_INDEX(subcache, pos); 837 838 /* Only consider 'idx' if the id matches, and the "removed" 839 * flag isn't set, and the record is not expired. 840 * Check the data length too to avoid a buffer overflow 841 * in case of corruption, which should be impossible, 842 * but it's cheap to be safe. */ 843 if (!idx->removed 844 && idx->id_len == idlen 845 && (idx->data_used - idx->id_len) <= *destlen 846 && shmcb_cyclic_memcmp(header->subcache_data_size, 847 SHMCB_DATA(header, subcache), 848 idx->data_pos, id, idx->id_len) == 0) { 849 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00849) 850 "match at idx=%d, data=%d", pos, idx->data_pos); 851 if (idx->expires > now) { 852 unsigned int data_offset; 853 854 /* Find the offset of the data segment, after the id */ 855 data_offset = SHMCB_CYCLIC_INCREMENT(idx->data_pos, 856 idx->id_len, 857 header->subcache_data_size); 858 859 *destlen = idx->data_used - idx->id_len; 860 861 /* Copy out the data */ 862 shmcb_cyclic_cton_memcpy(header->subcache_data_size, 863 dest, SHMCB_DATA(header, subcache), 864 data_offset, *destlen); 865 866 return 0; 867 } 868 else { 869 /* Already stale, quietly remove and treat as not-found */ 870 idx->removed = 1; 871 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00850) 872 "shmcb_subcache_retrieve discarding expired entry"); 873 return -1; 874 } 875 } 876 /* Increment */ 877 loop++; 878 pos = SHMCB_CYCLIC_INCREMENT(pos, 1, header->index_num); 879 } 880 881 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00851) 882 "shmcb_subcache_retrieve found no match"); 883 return -1; 884} 885 886static int shmcb_subcache_remove(server_rec *s, SHMCBHeader *header, 887 SHMCBSubcache *subcache, 888 const unsigned char *id, 889 unsigned int idlen) 890{ 891 unsigned int pos; 892 unsigned int loop = 0; 893 894 pos = subcache->idx_pos; 895 while (loop < subcache->idx_used) { 896 SHMCBIndex *idx = SHMCB_INDEX(subcache, pos); 897 898 /* Only consider 'idx' if the id matches, and the "removed" 899 * flag isn't set. */ 900 if (!idx->removed && idx->id_len == idlen 901 && shmcb_cyclic_memcmp(header->subcache_data_size, 902 SHMCB_DATA(header, subcache), 903 idx->data_pos, id, idx->id_len) == 0) { 904 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00852) 905 "possible match at idx=%d, data=%d", pos, idx->data_pos); 906 907 /* Found the matching entry, remove it quietly. */ 908 idx->removed = 1; 909 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00853) 910 "shmcb_subcache_remove removing matching entry"); 911 return 0; 912 } 913 /* Increment */ 914 loop++; 915 pos = SHMCB_CYCLIC_INCREMENT(pos, 1, header->index_num); 916 } 917 918 return -1; /* failure */ 919} 920 921 922static apr_status_t shmcb_subcache_iterate(ap_socache_instance_t *instance, 923 server_rec *s, 924 void *userctx, 925 SHMCBHeader *header, 926 SHMCBSubcache *subcache, 927 ap_socache_iterator_t *iterator, 928 unsigned char **buf, 929 apr_size_t *buf_len, 930 apr_pool_t *pool, 931 apr_time_t now) 932{ 933 unsigned int pos; 934 unsigned int loop = 0; 935 apr_status_t rv; 936 937 pos = subcache->idx_pos; 938 while (loop < subcache->idx_used) { 939 SHMCBIndex *idx = SHMCB_INDEX(subcache, pos); 940 941 /* Only consider 'idx' if the "removed" flag isn't set. */ 942 if (!idx->removed) { 943 944 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00854) 945 "iterating idx=%d, data=%d", pos, idx->data_pos); 946 if (idx->expires > now) { 947 unsigned char *id = *buf; 948 unsigned char *dest; 949 unsigned int data_offset, dest_len; 950 apr_size_t buf_req; 951 952 /* Find the offset of the data segment, after the id */ 953 data_offset = SHMCB_CYCLIC_INCREMENT(idx->data_pos, 954 idx->id_len, 955 header->subcache_data_size); 956 957 dest_len = idx->data_used - idx->id_len; 958 959 buf_req = APR_ALIGN_DEFAULT(idx->id_len + 1) 960 + APR_ALIGN_DEFAULT(dest_len + 1); 961 962 if (buf_req > *buf_len) { 963 /* Grow to ~150% of this buffer requirement on resize 964 * always using APR_ALIGN_DEFAULT sized pages 965 */ 966 *buf_len = buf_req + APR_ALIGN_DEFAULT(buf_req / 2); 967 *buf = apr_palloc(pool, *buf_len); 968 id = *buf; 969 } 970 971 dest = *buf + APR_ALIGN_DEFAULT(idx->id_len + 1); 972 973 /* Copy out the data, because it's potentially cyclic */ 974 shmcb_cyclic_cton_memcpy(header->subcache_data_size, id, 975 SHMCB_DATA(header, subcache), 976 idx->data_pos, idx->id_len); 977 id[idx->id_len] = '\0'; 978 979 shmcb_cyclic_cton_memcpy(header->subcache_data_size, dest, 980 SHMCB_DATA(header, subcache), 981 data_offset, dest_len); 982 dest[dest_len] = '\0'; 983 984 rv = iterator(instance, s, userctx, id, idx->id_len, 985 dest, dest_len, pool); 986 ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, APLOGNO(00855) 987 "shmcb entry iterated"); 988 if (rv != APR_SUCCESS) 989 return rv; 990 } 991 else { 992 /* Already stale, quietly remove and treat as not-found */ 993 idx->removed = 1; 994 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00856) 995 "shmcb_subcache_iterate discarding expired entry"); 996 } 997 } 998 /* Increment */ 999 loop++; 1000 pos = SHMCB_CYCLIC_INCREMENT(pos, 1, header->index_num); 1001 } 1002 1003 return APR_SUCCESS; 1004} 1005 1006static const ap_socache_provider_t socache_shmcb = { 1007 "shmcb", 1008 AP_SOCACHE_FLAG_NOTMPSAFE, 1009 socache_shmcb_create, 1010 socache_shmcb_init, 1011 socache_shmcb_destroy, 1012 socache_shmcb_store, 1013 socache_shmcb_retrieve, 1014 socache_shmcb_remove, 1015 socache_shmcb_status, 1016 socache_shmcb_iterate 1017}; 1018 1019static void register_hooks(apr_pool_t *p) 1020{ 1021 ap_register_provider(p, AP_SOCACHE_PROVIDER_GROUP, "shmcb", 1022 AP_SOCACHE_PROVIDER_VERSION, 1023 &socache_shmcb); 1024 1025 /* Also register shmcb under the default provider name. */ 1026 ap_register_provider(p, AP_SOCACHE_PROVIDER_GROUP, 1027 AP_SOCACHE_DEFAULT_PROVIDER, 1028 AP_SOCACHE_PROVIDER_VERSION, 1029 &socache_shmcb); 1030} 1031 1032AP_DECLARE_MODULE(socache_shmcb) = { 1033 STANDARD20_MODULE_STUFF, 1034 NULL, NULL, NULL, NULL, NULL, 1035 register_hooks 1036}; 1037