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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 #include "http_request.h"
20 #include "http_protocol.h"
21 #include "http_config.h"
24 #include "apr_strings.h"
27 #define APR_WANT_STRFUNC
30 #include "ap_socache.h"
32 #define SHMCB_MAX_SIZE (64 * 1024 * 1024)
35 * This shared memory based SSL session cache implementation was
36 * originally written by Geoff Thorpe <geoff geoffthorpe.net> for C2Net
37 * Europe as a contribution to Ralf Engelschall's mod_ssl project.
39 * Since rewritten by GT to not use alignment-fudging memcpys and reduce
44 * Header structure - the start of the shared-mem segment
47 /* Stats for cache operations */
48 unsigned long stat_stores;
49 unsigned long stat_expiries;
50 unsigned long stat_scrolled;
51 unsigned long stat_retrieves_hit;
52 unsigned long stat_retrieves_miss;
53 unsigned long stat_removes_hit;
54 unsigned long stat_removes_miss;
55 /* Number of subcaches */
56 unsigned int subcache_num;
57 /* How many indexes each subcache's queue has */
58 unsigned int index_num;
59 /* How large each subcache is, including the queue and data */
60 unsigned int subcache_size;
61 /* How far into each subcache the data area is (optimisation) */
62 unsigned int subcache_data_offset;
63 /* How large the data area in each subcache is (optimisation) */
64 unsigned int subcache_data_size;
68 * Subcache structure - the start of each subcache, followed by
72 /* The start position and length of the cyclic buffer of indexes */
73 unsigned int idx_pos, idx_used;
74 /* Same for the data area */
75 unsigned int data_pos, data_used;
79 * Index structure - each subcache has an array of these
82 /* absolute time this entry expires */
84 /* location within the subcache's data area */
85 unsigned int data_pos;
86 /* size (most logic ignores this, we keep it only to minimise memcpy) */
87 unsigned int data_used;
88 /* length of the used data which contains the id */
90 /* Used to mark explicitly-removed sessions */
91 unsigned char removed;
94 struct ap_socache_instance_t {
95 const char *data_file;
101 /* The SHM data segment is of fixed size and stores data as follows.
103 * [ SHMCBHeader | Subcaches ]
105 * The SHMCBHeader header structure stores metadata concerning the
106 * cache and the contained subcaches.
108 * Subcaches is a hash table of header->subcache_num SHMCBSubcache
109 * structures. The hash table is indexed by SHMCB_MASK(id). Each
110 * SHMCBSubcache structure has a fixed size (header->subcache_size),
111 * which is determined at creation time, and looks like the following:
113 * [ SHMCBSubcache | Indexes | Data ]
115 * Each subcache is prefixed by the SHMCBSubcache structure.
117 * The subcache's "Data" segment is a single cyclic data buffer, of
118 * total size header->subcache_data_size; data inside is referenced
119 * using byte offsets. The offset marking the beginning of the cyclic
120 * buffer is subcache->data_pos the buffer's length is
121 * subcache->data_used.
123 * "Indexes" is an array of header->index_num SHMCBIndex structures,
124 * which is used as a cyclic queue; subcache->idx_pos gives the array
125 * index of the first in use, subcache->idx_used gives the number in
126 * use. Both ->idx_* values have a range of [0, header->index_num)
128 * Each in-use SHMCBIndex structure represents a single SSL session.
129 * The ID and data segment are stored consecutively in the subcache's
130 * cyclic data buffer. The "Data" segment can thus be seen to
131 * look like this, for example
133 * offset: [ 0 1 2 3 4 5 6 ...
134 * contents:[ ID1 Data1 ID2 Data2 ID3 ...
136 * where the corresponding indices would look like:
138 * idx1 = { data_pos = 0, data_used = 3, id_len = 1, ...}
139 * idx2 = { data_pos = 3, data_used = 3, id_len = 1, ...}
143 /* This macro takes a pointer to the header and a zero-based index and returns
144 * a pointer to the corresponding subcache. */
145 #define SHMCB_SUBCACHE(pHeader, num) \
146 (SHMCBSubcache *)(((unsigned char *)(pHeader)) + \
147 sizeof(SHMCBHeader) + \
148 (num) * ((pHeader)->subcache_size))
150 /* This macro takes a pointer to the header and a session id and returns a
151 * pointer to the corresponding subcache. */
152 #define SHMCB_MASK(pHeader, id) \
153 SHMCB_SUBCACHE((pHeader), *(id) & ((pHeader)->subcache_num - 1))
155 /* This macro takes the same params as the last, generating two outputs for use
156 * in ap_log_error(...). */
157 #define SHMCB_MASK_DBG(pHeader, id) \
158 *(id), (*(id) & ((pHeader)->subcache_num - 1))
160 /* This macro takes a pointer to a subcache and a zero-based index and returns
161 * a pointer to the corresponding SHMCBIndex. */
162 #define SHMCB_INDEX(pSubcache, num) \
163 ((SHMCBIndex *)(((unsigned char *)pSubcache) + \
164 sizeof(SHMCBSubcache)) + num)
166 /* This macro takes a pointer to the header and a subcache and returns a
167 * pointer to the corresponding data area. */
168 #define SHMCB_DATA(pHeader, pSubcache) \
169 ((unsigned char *)(pSubcache) + (pHeader)->subcache_data_offset)
172 * Cyclic functions - assists in "wrap-around"/modulo logic
175 /* Addition modulo 'mod' */
176 #define SHMCB_CYCLIC_INCREMENT(val,inc,mod) \
177 (((val) + (inc)) % (mod))
179 /* Subtraction (or "distance between") modulo 'mod' */
180 #define SHMCB_CYCLIC_SPACE(val1,val2,mod) \
181 ((val2) >= (val1) ? ((val2) - (val1)) : \
182 ((val2) + (mod) - (val1)))
184 /* A "normal-to-cyclic" memcpy. */
185 static void shmcb_cyclic_ntoc_memcpy(unsigned int buf_size, unsigned char *data,
186 unsigned int dest_offset, const unsigned char *src,
187 unsigned int src_len)
189 if (dest_offset + src_len < buf_size)
190 /* It be copied all in one go */
191 memcpy(data + dest_offset, src, src_len);
193 /* Copy the two splits */
194 memcpy(data + dest_offset, src, buf_size - dest_offset);
195 memcpy(data, src + buf_size - dest_offset,
196 src_len + dest_offset - buf_size);
200 /* A "cyclic-to-normal" memcpy. */
201 static void shmcb_cyclic_cton_memcpy(unsigned int buf_size, unsigned char *dest,
202 const unsigned char *data, unsigned int src_offset,
203 unsigned int src_len)
205 if (src_offset + src_len < buf_size)
206 /* It be copied all in one go */
207 memcpy(dest, data + src_offset, src_len);
209 /* Copy the two splits */
210 memcpy(dest, data + src_offset, buf_size - src_offset);
211 memcpy(dest + buf_size - src_offset, data,
212 src_len + src_offset - buf_size);
216 /* A memcmp against a cyclic data buffer. Compares SRC of length
217 * SRC_LEN against the contents of cyclic buffer DATA (which is of
218 * size BUF_SIZE), starting at offset DEST_OFFSET. Got that? Good. */
219 static int shmcb_cyclic_memcmp(unsigned int buf_size, unsigned char *data,
220 unsigned int dest_offset,
221 const unsigned char *src,
222 unsigned int src_len)
224 if (dest_offset + src_len < buf_size)
225 /* It be compared all in one go */
226 return memcmp(data + dest_offset, src, src_len);
228 /* Compare the two splits */
231 diff = memcmp(data + dest_offset, src, buf_size - dest_offset);
235 return memcmp(data, src + buf_size - dest_offset,
236 src_len + dest_offset - buf_size);
241 /* Prototypes for low-level subcache operations */
242 static void shmcb_subcache_expire(server_rec *, SHMCBHeader *, SHMCBSubcache *);
243 /* Returns zero on success, non-zero on failure. */
244 static int shmcb_subcache_store(server_rec *s, SHMCBHeader *header,
245 SHMCBSubcache *subcache,
246 unsigned char *data, unsigned int data_len,
247 const unsigned char *id, unsigned int id_len,
249 /* Returns zero on success, non-zero on failure. */
250 static int shmcb_subcache_retrieve(server_rec *, SHMCBHeader *, SHMCBSubcache *,
251 const unsigned char *id, unsigned int idlen,
252 unsigned char *data, unsigned int *datalen);
253 /* Returns zero on success, non-zero on failure. */
254 static int shmcb_subcache_remove(server_rec *, SHMCBHeader *, SHMCBSubcache *,
255 const unsigned char *, unsigned int);
258 * High-Level "handlers" as per ssl_scache.c
259 * subcache internals are deferred to shmcb_subcache_*** functions lower down
262 static const char *socache_shmcb_create(ap_socache_instance_t **context,
264 apr_pool_t *tmp, apr_pool_t *p)
266 ap_socache_instance_t *ctx;
267 char *path, *cp, *cp2;
269 /* Allocate the context. */
270 *context = ctx = apr_pcalloc(p, sizeof *ctx);
272 ctx->data_file = path = ap_server_root_relative(p, arg);
273 ctx->shm_size = 1024*512; /* 512KB */
275 cp = strchr(path, '(');
279 if (!(cp2 = strchr(cp, ')'))) {
280 return "Invalid argument: no closing parenthesis";
285 ctx->shm_size = atoi(cp);
287 if (ctx->shm_size < 8192) {
288 return "Invalid argument: size has to be >= 8192 bytes";
292 if (ctx->shm_size >= SHMCB_MAX_SIZE) {
293 return apr_psprintf(tmp,
294 "Invalid argument: size has "
295 "to be < %d bytes on this platform",
304 static apr_status_t socache_shmcb_init(ap_socache_instance_t *ctx,
305 const char *namespace,
306 const struct ap_socache_hints *hints,
307 server_rec *s, apr_pool_t *p)
310 apr_size_t shm_segsize;
313 unsigned int num_subcache, num_idx, loop;
314 apr_size_t avg_obj_size, avg_id_len;
316 /* Create shared memory segment */
317 if (ctx->data_file == NULL) {
318 ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
319 "SSLSessionCache required");
323 /* Use anonymous shm by default, fall back on name-based. */
324 rv = apr_shm_create(&ctx->shm, ctx->shm_size, NULL, p);
325 if (APR_STATUS_IS_ENOTIMPL(rv)) {
326 /* For a name-based segment, remove it first in case of a
327 * previous unclean shutdown. */
328 apr_shm_remove(ctx->data_file, p);
330 rv = apr_shm_create(&ctx->shm, ctx->shm_size, ctx->data_file, p);
333 if (rv != APR_SUCCESS) {
334 ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
335 "could not allocate shared memory for shmcb "
340 shm_segment = apr_shm_baseaddr_get(ctx->shm);
341 shm_segsize = apr_shm_size_get(ctx->shm);
342 if (shm_segsize < (5 * sizeof(SHMCBHeader))) {
343 /* the segment is ridiculously small, bail out */
344 ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
345 "shared memory segment too small");
348 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
349 "shmcb_init allocated %" APR_SIZE_T_FMT
350 " bytes of shared memory",
352 /* Discount the header */
353 shm_segsize -= sizeof(SHMCBHeader);
354 /* Select index size based on average object size hints, if given. */
355 avg_obj_size = hints && hints->avg_obj_size ? hints->avg_obj_size : 150;
356 avg_id_len = hints && hints->avg_id_len ? hints->avg_id_len : 30;
357 num_idx = (shm_segsize) / (avg_obj_size + avg_id_len);
359 while ((num_idx / num_subcache) < (2 * num_subcache))
361 num_idx /= num_subcache;
362 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
363 "for %" APR_SIZE_T_FMT " bytes (%" APR_SIZE_T_FMT
364 " including header), recommending %u subcaches, "
365 "%u indexes each", shm_segsize,
366 shm_segsize + sizeof(SHMCBHeader), num_subcache, num_idx);
368 /* we're still too small, bail out */
369 ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
370 "shared memory segment too small");
373 /* OK, we're sorted */
374 ctx->header = header = shm_segment;
375 header->stat_stores = 0;
376 header->stat_expiries = 0;
377 header->stat_scrolled = 0;
378 header->stat_retrieves_hit = 0;
379 header->stat_retrieves_miss = 0;
380 header->stat_removes_hit = 0;
381 header->stat_removes_miss = 0;
382 header->subcache_num = num_subcache;
383 /* Convert the subcache size (in bytes) to a value that is suitable for
384 * structure alignment on the host platform, by rounding down if necessary.
385 * This assumes that sizeof(unsigned long) provides an appropriate
387 header->subcache_size = ((size_t)(shm_segsize / num_subcache) &
388 ~(size_t)(sizeof(unsigned long) - 1));
389 header->subcache_data_offset = sizeof(SHMCBSubcache) +
390 num_idx * sizeof(SHMCBIndex);
391 header->subcache_data_size = header->subcache_size -
392 header->subcache_data_offset;
393 header->index_num = num_idx;
395 /* Output trace info */
396 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
397 "shmcb_init_memory choices follow");
398 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
399 "subcache_num = %u", header->subcache_num);
400 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
401 "subcache_size = %u", header->subcache_size);
402 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
403 "subcache_data_offset = %u", header->subcache_data_offset);
404 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
405 "subcache_data_size = %u", header->subcache_data_size);
406 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
407 "index_num = %u", header->index_num);
408 /* The header is done, make the caches empty */
409 for (loop = 0; loop < header->subcache_num; loop++) {
410 SHMCBSubcache *subcache = SHMCB_SUBCACHE(header, loop);
411 subcache->idx_pos = subcache->idx_used = 0;
412 subcache->data_pos = subcache->data_used = 0;
414 ap_log_error(APLOG_MARK, APLOG_INFO, 0, s,
415 "Shared memory session cache initialised");
421 static void socache_shmcb_kill(ap_socache_instance_t *ctx, server_rec *s)
423 if (ctx && ctx->shm) {
424 apr_shm_destroy(ctx->shm);
429 static apr_status_t socache_shmcb_store(ap_socache_instance_t *ctx,
431 const unsigned char *id, unsigned int idlen,
433 unsigned char *encoded,
434 unsigned int len_encoded)
436 SHMCBHeader *header = ctx->header;
437 SHMCBSubcache *subcache = SHMCB_MASK(header, id);
439 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
440 "socache_shmcb_store (0x%02x -> subcache %d)",
441 SHMCB_MASK_DBG(header, id));
443 ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, "unusably short session_id provided "
444 "(%u bytes)", idlen);
447 if (shmcb_subcache_store(s, header, subcache, encoded,
448 len_encoded, id, idlen, timeout)) {
449 ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
450 "can't store a session!");
453 header->stat_stores++;
454 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
455 "leaving socache_shmcb_store successfully");
459 static apr_status_t socache_shmcb_retrieve(ap_socache_instance_t *ctx,
461 const unsigned char *id, unsigned int idlen,
462 unsigned char *dest, unsigned int *destlen,
465 SHMCBHeader *header = ctx->header;
466 SHMCBSubcache *subcache = SHMCB_MASK(header, id);
469 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
470 "socache_shmcb_retrieve (0x%02x -> subcache %d)",
471 SHMCB_MASK_DBG(header, id));
473 /* Get the session corresponding to the session_id, if it exists. */
474 rv = shmcb_subcache_retrieve(s, header, subcache, id, idlen,
477 header->stat_retrieves_hit++;
479 header->stat_retrieves_miss++;
480 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
481 "leaving socache_shmcb_retrieve successfully");
483 return rv == 0 ? APR_SUCCESS : APR_EGENERAL;
486 static void socache_shmcb_remove(ap_socache_instance_t *ctx, server_rec *s,
487 const unsigned char *id, unsigned int idlen,
490 SHMCBHeader *header = ctx->header;
491 SHMCBSubcache *subcache = SHMCB_MASK(header, id);
493 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
494 "socache_shmcb_remove (0x%02x -> subcache %d)",
495 SHMCB_MASK_DBG(header, id));
497 ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, "unusably short session_id provided "
498 "(%u bytes)", idlen);
501 if (shmcb_subcache_remove(s, header, subcache, id, idlen))
502 header->stat_removes_hit++;
504 header->stat_removes_miss++;
505 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
506 "leaving socache_shmcb_remove successfully");
509 static void socache_shmcb_status(ap_socache_instance_t *ctx,
510 request_rec *r, int flags)
512 server_rec *s = r->server;
513 SHMCBHeader *header = ctx->header;
514 unsigned int loop, total = 0, cache_total = 0, non_empty_subcaches = 0;
515 time_t idx_expiry, min_expiry = 0, max_expiry = 0, average_expiry = 0;
516 time_t now = time(NULL);
517 double expiry_total = 0;
518 int index_pct, cache_pct;
520 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "inside shmcb_status");
521 /* Perform the iteration inside the mutex to avoid corruption or invalid
522 * pointer arithmetic. The rest of our logic uses read-only header data so
523 * doesn't need the lock. */
524 /* Iterate over the subcaches */
525 for (loop = 0; loop < header->subcache_num; loop++) {
526 SHMCBSubcache *subcache = SHMCB_SUBCACHE(header, loop);
527 shmcb_subcache_expire(s, header, subcache);
528 total += subcache->idx_used;
529 cache_total += subcache->data_used;
530 if (subcache->idx_used) {
531 SHMCBIndex *idx = SHMCB_INDEX(subcache, subcache->idx_pos);
532 non_empty_subcaches++;
533 idx_expiry = idx->expires;
534 expiry_total += (double)idx_expiry;
535 max_expiry = ((idx_expiry > max_expiry) ? idx_expiry : max_expiry);
537 min_expiry = idx_expiry;
539 min_expiry = ((idx_expiry < min_expiry) ? idx_expiry : min_expiry);
542 index_pct = (100 * total) / (header->index_num *
543 header->subcache_num);
544 cache_pct = (100 * cache_total) / (header->subcache_data_size *
545 header->subcache_num);
547 ap_rprintf(r, "cache type: <b>SHMCB</b>, shared memory: <b>%" APR_SIZE_T_FMT "</b> "
548 "bytes, current sessions: <b>%d</b><br>",
549 ctx->shm_size, total);
550 ap_rprintf(r, "subcaches: <b>%d</b>, indexes per subcache: <b>%d</b><br>",
551 header->subcache_num, header->index_num);
552 if (non_empty_subcaches) {
553 average_expiry = (time_t)(expiry_total / (double)non_empty_subcaches);
554 ap_rprintf(r, "time left on oldest entries' SSL sessions: ");
555 if (now < average_expiry)
556 ap_rprintf(r, "avg: <b>%d</b> seconds, (range: %d...%d)<br>",
557 (int)(average_expiry - now),
558 (int)(min_expiry - now),
559 (int)(max_expiry - now));
561 ap_rprintf(r, "expiry_threshold: <b>Calculation error!</b><br>");
564 ap_rprintf(r, "index usage: <b>%d%%</b>, cache usage: <b>%d%%</b><br>",
565 index_pct, cache_pct);
566 ap_rprintf(r, "total sessions stored since starting: <b>%lu</b><br>",
567 header->stat_stores);
568 ap_rprintf(r, "total sessions expired since starting: <b>%lu</b><br>",
569 header->stat_expiries);
570 ap_rprintf(r, "total (pre-expiry) sessions scrolled out of the cache: "
571 "<b>%lu</b><br>", header->stat_scrolled);
572 ap_rprintf(r, "total retrieves since starting: <b>%lu</b> hit, "
573 "<b>%lu</b> miss<br>", header->stat_retrieves_hit,
574 header->stat_retrieves_miss);
575 ap_rprintf(r, "total removes since starting: <b>%lu</b> hit, "
576 "<b>%lu</b> miss<br>", header->stat_removes_hit,
577 header->stat_removes_miss);
578 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "leaving shmcb_status");
582 * Subcache-level cache operations
585 static void shmcb_subcache_expire(server_rec *s, SHMCBHeader *header,
586 SHMCBSubcache *subcache)
588 time_t now = time(NULL);
589 unsigned int loop = 0;
590 unsigned int new_idx_pos = subcache->idx_pos;
591 SHMCBIndex *idx = NULL;
593 while (loop < subcache->idx_used) {
594 idx = SHMCB_INDEX(subcache, new_idx_pos);
595 if (idx->expires > now)
596 /* it hasn't expired yet, we're done iterating */
599 new_idx_pos = SHMCB_CYCLIC_INCREMENT(new_idx_pos, 1, header->index_num);
604 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
605 "will be expiring %u sessions", loop);
606 if (loop == subcache->idx_used) {
607 /* We're expiring everything, piece of cake */
608 subcache->idx_used = 0;
609 subcache->data_used = 0;
611 /* There remain other indexes, so we can use idx to adjust 'data' */
612 unsigned int diff = SHMCB_CYCLIC_SPACE(subcache->data_pos,
614 header->subcache_data_size);
615 /* Adjust the indexes */
616 subcache->idx_used -= loop;
617 subcache->idx_pos = new_idx_pos;
618 /* Adjust the data area */
619 subcache->data_used -= diff;
620 subcache->data_pos = idx->data_pos;
622 header->stat_expiries += loop;
623 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
624 "we now have %u sessions", subcache->idx_used);
627 static int shmcb_subcache_store(server_rec *s, SHMCBHeader *header,
628 SHMCBSubcache *subcache,
629 unsigned char *data, unsigned int data_len,
630 const unsigned char *id, unsigned int id_len,
633 unsigned int data_offset, new_idx, id_offset;
635 unsigned int total_len = id_len + data_len;
637 /* Sanity check the input */
638 if (total_len > header->subcache_data_size) {
639 ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
640 "inserting session larger (%d) than subcache data area (%d)",
641 total_len, header->subcache_data_size);
645 /* If there are entries to expire, ditch them first. */
646 shmcb_subcache_expire(s, header, subcache);
648 /* Loop until there is enough space to insert */
649 if (header->subcache_data_size - subcache->data_used < total_len
650 || subcache->idx_used == header->index_num) {
651 unsigned int loop = 0;
653 idx = SHMCB_INDEX(subcache, subcache->idx_pos);
654 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
655 "about to force-expire, subcache: idx_used=%d, "
656 "data_used=%d", subcache->idx_used, subcache->data_used);
660 /* Adjust the indexes by one */
661 subcache->idx_pos = SHMCB_CYCLIC_INCREMENT(subcache->idx_pos, 1,
663 subcache->idx_used--;
664 if (!subcache->idx_used) {
665 /* There's nothing left */
666 subcache->data_used = 0;
669 /* Adjust the data */
670 idx2 = SHMCB_INDEX(subcache, subcache->idx_pos);
671 subcache->data_used -= SHMCB_CYCLIC_SPACE(idx->data_pos, idx2->data_pos,
672 header->subcache_data_size);
673 subcache->data_pos = idx2->data_pos;
675 header->stat_scrolled++;
679 } while (header->subcache_data_size - subcache->data_used < total_len);
681 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
682 "finished force-expire, subcache: idx_used=%d, "
683 "data_used=%d", subcache->idx_used, subcache->data_used);
686 /* HERE WE ASSUME THAT THE NEW SESSION SHOULD GO ON THE END! I'M NOT
687 * CHECKING WHETHER IT SHOULD BE GENUINELY "INSERTED" SOMEWHERE.
689 * We either fix that, or find out at a "higher" (read "mod_ssl")
690 * level whether it is possible to have distinct session caches for
691 * any attempted tomfoolery to do with different session timeouts.
692 * Knowing in advance that we can have a cache-wide constant timeout
693 * would make this stuff *MUCH* more efficient. Mind you, it's very
694 * efficient right now because I'm ignoring this problem!!!
697 id_offset = SHMCB_CYCLIC_INCREMENT(subcache->data_pos, subcache->data_used,
698 header->subcache_data_size);
699 shmcb_cyclic_ntoc_memcpy(header->subcache_data_size,
700 SHMCB_DATA(header, subcache), id_offset,
702 subcache->data_used += id_len;
703 /* Insert the data */
704 data_offset = SHMCB_CYCLIC_INCREMENT(subcache->data_pos, subcache->data_used,
705 header->subcache_data_size);
706 shmcb_cyclic_ntoc_memcpy(header->subcache_data_size,
707 SHMCB_DATA(header, subcache), data_offset,
709 subcache->data_used += data_len;
710 /* Insert the index */
711 new_idx = SHMCB_CYCLIC_INCREMENT(subcache->idx_pos, subcache->idx_used,
713 idx = SHMCB_INDEX(subcache, new_idx);
714 idx->expires = expiry;
715 idx->data_pos = id_offset;
716 idx->data_used = total_len;
717 idx->id_len = id_len;
719 subcache->idx_used++;
720 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
721 "insert happened at idx=%d, data=(%u:%u)", new_idx,
722 id_offset, data_offset);
723 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
724 "finished insert, subcache: idx_pos/idx_used=%d/%d, "
725 "data_pos/data_used=%d/%d",
726 subcache->idx_pos, subcache->idx_used,
727 subcache->data_pos, subcache->data_used);
731 static int shmcb_subcache_retrieve(server_rec *s, SHMCBHeader *header,
732 SHMCBSubcache *subcache,
733 const unsigned char *id, unsigned int idlen,
734 unsigned char *dest, unsigned int *destlen)
737 unsigned int loop = 0;
739 /* If there are entries to expire, ditch them first. */
740 shmcb_subcache_expire(s, header, subcache);
741 pos = subcache->idx_pos;
743 while (loop < subcache->idx_used) {
744 SHMCBIndex *idx = SHMCB_INDEX(subcache, pos);
746 /* Only consider 'idx' if the id matches, and the "removed"
747 * flag isn't set; check the data length too to avoid a buffer
748 * overflow in case of corruption, which should be impossible,
749 * but it's cheap to be safe. */
751 && idx->id_len == idlen && (idx->data_used - idx->id_len) < *destlen
752 && shmcb_cyclic_memcmp(header->subcache_data_size,
753 SHMCB_DATA(header, subcache),
754 idx->data_pos, id, idx->id_len) == 0) {
755 unsigned int data_offset;
756 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
757 "match at idx=%d, data=%d", pos, idx->data_pos);
759 /* Find the offset of the data segment, after the id */
760 data_offset = SHMCB_CYCLIC_INCREMENT(idx->data_pos,
762 header->subcache_data_size);
764 *destlen = idx->data_used - idx->id_len;
766 /* Copy out the data */
767 shmcb_cyclic_cton_memcpy(header->subcache_data_size,
768 dest, SHMCB_DATA(header, subcache),
769 data_offset, *destlen);
775 pos = SHMCB_CYCLIC_INCREMENT(pos, 1, header->index_num);
778 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
779 "shmcb_subcache_retrieve found no match");
784 static int shmcb_subcache_remove(server_rec *s, SHMCBHeader *header,
785 SHMCBSubcache *subcache,
786 const unsigned char *id, unsigned int idlen)
789 unsigned int loop = 0;
791 /* Unlike the others, we don't do an expire-run first. This is to keep
792 * consistent statistics where a "remove" operation may actually be the
793 * higher layer spotting an expiry issue prior to us. Our caller is
794 * handling stats, so a failure return would be inconsistent if the
795 * intended session was in fact removed by an expiry run. */
797 pos = subcache->idx_pos;
798 while (loop < subcache->idx_used) {
799 SHMCBIndex *idx = SHMCB_INDEX(subcache, pos);
801 /* Only consider 'idx' if the id matches, and the "removed"
803 if (!idx->removed && idx->id_len == idlen
804 && shmcb_cyclic_memcmp(header->subcache_data_size,
805 SHMCB_DATA(header, subcache),
806 idx->data_pos, id, idx->id_len) == 0) {
807 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
808 "possible match at idx=%d, data=%d", pos, idx->data_pos);
809 /* Found the matching session, remove it quietly. */
811 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
812 "shmcb_subcache_remove removing matching session");
817 pos = SHMCB_CYCLIC_INCREMENT(pos, 1, header->index_num);
820 return -1; /* failure */
823 static const ap_socache_provider_t socache_shmcb = {
825 AP_SOCACHE_FLAG_NOTMPSAFE,
826 socache_shmcb_create,
830 socache_shmcb_retrieve,
831 socache_shmcb_remove,
835 static void register_hooks(apr_pool_t *p)
837 ap_register_provider(p, AP_SOCACHE_PROVIDER_GROUP, "shmcb",
838 AP_SOCACHE_PROVIDER_VERSION,
842 module AP_MODULE_DECLARE_DATA socache_shmcb_module = {
843 STANDARD20_MODULE_STUFF,
844 NULL, NULL, NULL, NULL, NULL,