]> granicus.if.org Git - apache/blob - modules/cache/mod_socache_shmcb.c
remove const on socache module declarations to avoid segfault in
[apache] / modules / cache / mod_socache_shmcb.c
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
30 #include "ap_socache.h"
31
32 #define SHMCB_MAX_SIZE (64 * 1024 * 1024)
33
34 /* 
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.
38  *
39  * Since rewritten by GT to not use alignment-fudging memcpys and reduce
40  * complexity.
41  */
42
43 /*
44  * Header structure - the start of the shared-mem segment
45  */
46 typedef struct {
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;
65 } SHMCBHeader;
66
67 /* 
68  * Subcache structure - the start of each subcache, followed by
69  * indexes then data
70  */
71 typedef struct {
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;
76 } SHMCBSubcache;
77
78 /* 
79  * Index structure - each subcache has an array of these
80  */
81 typedef struct {
82     /* absolute time this entry expires */
83     time_t 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 */
89     unsigned int id_len;
90     /* Used to mark explicitly-removed sessions */
91     unsigned char removed;
92 } SHMCBIndex;
93
94 struct ap_socache_instance_t {
95     const char *data_file;
96     apr_size_t shm_size;
97     apr_shm_t *shm;
98     SHMCBHeader *header;
99 };
100
101 /* The SHM data segment is of fixed size and stores data as follows.
102  *
103  *   [ SHMCBHeader | Subcaches ]
104  *
105  * The SHMCBHeader header structure stores metadata concerning the
106  * cache and the contained subcaches.
107  *
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:
112  *
113  *   [ SHMCBSubcache | Indexes | Data ]
114  *
115  * Each subcache is prefixed by the SHMCBSubcache structure.
116  *
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.
122  *
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)
127  *
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
132  *
133  * offset:  [ 0     1     2     3     4     5     6    ...
134  * contents:[ ID1   Data1       ID2   Data2       ID3  ...
135  *
136  * where the corresponding indices would look like:
137  *
138  * idx1 = { data_pos = 0, data_used = 3, id_len = 1, ...}
139  * idx2 = { data_pos = 3, data_used = 3, id_len = 1, ...}
140  * ...
141  */
142
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))
149
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))
154
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))
159
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)
165
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)
170
171 /*
172  * Cyclic functions - assists in "wrap-around"/modulo logic
173  */
174
175 /* Addition modulo 'mod' */
176 #define SHMCB_CYCLIC_INCREMENT(val,inc,mod) \
177                 (((val) + (inc)) % (mod))
178
179 /* Subtraction (or "distance between") modulo 'mod' */
180 #define SHMCB_CYCLIC_SPACE(val1,val2,mod) \
181                 ((val2) >= (val1) ? ((val2) - (val1)) : \
182                         ((val2) + (mod) - (val1)))
183
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)
188 {
189     if (dest_offset + src_len < buf_size)
190         /* It be copied all in one go */
191         memcpy(data + dest_offset, src, src_len);
192     else {
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);
197     }
198 }
199
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)
204 {
205     if (src_offset + src_len < buf_size)
206         /* It be copied all in one go */
207         memcpy(dest, data + src_offset, src_len);
208     else {
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);
213     }
214 }
215
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)
223 {
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);
227     else {
228         /* Compare the two splits */
229         int diff;
230         
231         diff = memcmp(data + dest_offset, src, buf_size - dest_offset);
232         if (diff) {
233             return diff;
234         }
235         return memcmp(data, src + buf_size - dest_offset,
236                       src_len + dest_offset - buf_size);
237     }
238 }
239
240
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,
248                                 time_t expiry);
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);
256
257 /*
258  * High-Level "handlers" as per ssl_scache.c
259  * subcache internals are deferred to shmcb_subcache_*** functions lower down
260  */
261
262 static const char *socache_shmcb_create(ap_socache_instance_t **context,
263                                         const char *arg, 
264                                         apr_pool_t *tmp, apr_pool_t *p)
265 {
266     ap_socache_instance_t *ctx;
267     char *path, *cp, *cp2;
268
269     /* Allocate the context. */
270     *context = ctx = apr_pcalloc(p, sizeof *ctx);
271     
272     ctx->data_file = path = ap_server_root_relative(p, arg);
273     ctx->shm_size  = 1024*512; /* 512KB */
274
275     cp = strchr(path, '(');
276     if (cp) {
277         *cp++ = '\0';
278
279         if (!(cp2 = strchr(cp, ')'))) {
280             return "Invalid argument: no closing parenthesis";
281         }
282             
283         *cp2 = '\0';
284         
285         ctx->shm_size = atoi(cp);
286         
287         if (ctx->shm_size < 8192) {
288             return "Invalid argument: size has to be >= 8192 bytes";
289             
290         }
291         
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", 
296                                 SHMCB_MAX_SIZE);
297             
298         }
299     }
300
301     return NULL;
302 }
303
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)
308 {
309     void *shm_segment;
310     apr_size_t shm_segsize;
311     apr_status_t rv;
312     SHMCBHeader *header;
313     unsigned int num_subcache, num_idx, loop;
314     apr_size_t avg_obj_size, avg_id_len;
315
316     /* Create shared memory segment */
317     if (ctx->data_file == NULL) {
318         ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
319                      "SSLSessionCache required");
320         return APR_EINVAL;
321     }
322
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);
329
330         rv = apr_shm_create(&ctx->shm, ctx->shm_size, ctx->data_file, p);
331     }
332
333     if (rv != APR_SUCCESS) {
334         ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
335                      "could not allocate shared memory for shmcb "
336                      "session cache");
337         return rv;
338     }
339
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");
346         return APR_ENOSPC;
347     }
348     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
349                  "shmcb_init allocated %" APR_SIZE_T_FMT
350                  " bytes of shared memory",
351                  shm_segsize);
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);
358     num_subcache = 256;
359     while ((num_idx / num_subcache) < (2 * num_subcache))
360         num_subcache /= 2;
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);
367     if (num_idx < 5) {
368         /* we're still too small, bail out */
369         ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
370                      "shared memory segment too small");
371         return APR_ENOSPC;
372     }
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
386      * alignment unit.  */
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;
394
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;
413     }
414     ap_log_error(APLOG_MARK, APLOG_INFO, 0, s,
415                  "Shared memory session cache initialised");
416     /* Success ... */
417
418     return APR_SUCCESS;
419 }
420
421 static void socache_shmcb_kill(ap_socache_instance_t *ctx, server_rec *s)
422 {
423     if (ctx && ctx->shm) {
424         apr_shm_destroy(ctx->shm);
425         ctx->shm = NULL;
426     }
427 }
428
429 static apr_status_t socache_shmcb_store(ap_socache_instance_t *ctx, 
430                                         server_rec *s, 
431                                         const unsigned char *id, unsigned int idlen,
432                                         time_t timeout, 
433                                         unsigned char *encoded,
434                                         unsigned int len_encoded)
435 {
436     SHMCBHeader *header = ctx->header;
437     SHMCBSubcache *subcache = SHMCB_MASK(header, id);
438
439     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
440                  "socache_shmcb_store (0x%02x -> subcache %d)",
441                  SHMCB_MASK_DBG(header, id));
442     if (idlen < 4) {
443         ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, "unusably short session_id provided "
444                 "(%u bytes)", idlen);
445         return APR_EINVAL;
446     }
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!");
451         return APR_ENOSPC;
452     }
453     header->stat_stores++;
454     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
455                  "leaving socache_shmcb_store successfully");
456     return APR_SUCCESS;
457 }
458
459 static apr_status_t socache_shmcb_retrieve(ap_socache_instance_t *ctx, 
460                                            server_rec *s, 
461                                            const unsigned char *id, unsigned int idlen,
462                                            unsigned char *dest, unsigned int *destlen,
463                                            apr_pool_t *p)
464 {
465     SHMCBHeader *header = ctx->header;
466     SHMCBSubcache *subcache = SHMCB_MASK(header, id);
467     int rv;
468
469     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
470                  "socache_shmcb_retrieve (0x%02x -> subcache %d)",
471                  SHMCB_MASK_DBG(header, id));
472
473     /* Get the session corresponding to the session_id, if it exists. */
474     rv = shmcb_subcache_retrieve(s, header, subcache, id, idlen,
475                                  dest, destlen);
476     if (rv == 0)
477         header->stat_retrieves_hit++;
478     else
479         header->stat_retrieves_miss++;
480     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
481                  "leaving socache_shmcb_retrieve successfully");
482
483     return rv == 0 ? APR_SUCCESS : APR_EGENERAL;
484 }
485
486 static void socache_shmcb_remove(ap_socache_instance_t *ctx, server_rec *s, 
487                                  const unsigned char *id, unsigned int idlen,
488                                  apr_pool_t *p)
489 {
490     SHMCBHeader *header = ctx->header;
491     SHMCBSubcache *subcache = SHMCB_MASK(header, id);
492
493     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
494                  "socache_shmcb_remove (0x%02x -> subcache %d)",
495                  SHMCB_MASK_DBG(header, id));
496     if (idlen < 4) {
497         ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, "unusably short session_id provided "
498                 "(%u bytes)", idlen);
499         return;
500     }
501     if (shmcb_subcache_remove(s, header, subcache, id, idlen))
502         header->stat_removes_hit++;
503     else
504         header->stat_removes_miss++;
505     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
506                  "leaving socache_shmcb_remove successfully");
507 }
508
509 static void socache_shmcb_status(ap_socache_instance_t *ctx, 
510                                  request_rec *r, int flags)
511 {
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;
519
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);
536             if (!min_expiry)
537                 min_expiry = idx_expiry;
538             else
539                 min_expiry = ((idx_expiry < min_expiry) ? idx_expiry : min_expiry);
540         }
541     }
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);
546     /* Generate HTML */
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));
560         else
561             ap_rprintf(r, "expiry_threshold: <b>Calculation error!</b><br>");
562     }
563
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");
579 }
580
581 /*
582  * Subcache-level cache operations 
583  */
584
585 static void shmcb_subcache_expire(server_rec *s, SHMCBHeader *header,
586                                   SHMCBSubcache *subcache)
587 {
588     time_t now = time(NULL);
589     unsigned int loop = 0;
590     unsigned int new_idx_pos = subcache->idx_pos;
591     SHMCBIndex *idx = NULL;
592
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 */
597             break;
598         loop++;
599         new_idx_pos = SHMCB_CYCLIC_INCREMENT(new_idx_pos, 1, header->index_num);
600     }
601     if (!loop)
602         /* Nothing to do */
603         return;
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;
610     } else {
611         /* There remain other indexes, so we can use idx to adjust 'data' */
612         unsigned int diff = SHMCB_CYCLIC_SPACE(subcache->data_pos,
613                                                idx->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;
621     }
622     header->stat_expiries += loop;
623     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
624                  "we now have %u sessions", subcache->idx_used);
625 }
626
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,
631                                 time_t expiry)
632 {
633     unsigned int data_offset, new_idx, id_offset;
634     SHMCBIndex *idx;
635     unsigned int total_len = id_len + data_len;
636
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);
642         return -1;
643     }
644
645     /* If there are entries to expire, ditch them first. */
646     shmcb_subcache_expire(s, header, subcache);
647
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;
652
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);
657         do {
658             SHMCBIndex *idx2;
659
660             /* Adjust the indexes by one */
661             subcache->idx_pos = SHMCB_CYCLIC_INCREMENT(subcache->idx_pos, 1,
662                                                        header->index_num);
663             subcache->idx_used--;
664             if (!subcache->idx_used) {
665                 /* There's nothing left */
666                 subcache->data_used = 0;
667                 break;
668             }
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;
674             /* Stats */
675             header->stat_scrolled++;
676             /* Loop admin */
677             idx = idx2;
678             loop++;
679         } while (header->subcache_data_size - subcache->data_used < total_len);
680
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);
684     }
685
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.
688      *
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!!!
695      */
696     /* Insert the id */
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,
701                              id, id_len);
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,
708                              data, data_len);
709     subcache->data_used += data_len;
710     /* Insert the index */
711     new_idx = SHMCB_CYCLIC_INCREMENT(subcache->idx_pos, subcache->idx_used,
712                                      header->index_num);
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;
718     idx->removed = 0;
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);
728     return 0;
729 }
730
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)
735 {
736     unsigned int pos;
737     unsigned int loop = 0;
738
739     /* If there are entries to expire, ditch them first. */
740     shmcb_subcache_expire(s, header, subcache);
741     pos = subcache->idx_pos;
742
743     while (loop < subcache->idx_used) {
744         SHMCBIndex *idx = SHMCB_INDEX(subcache, pos);
745
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. */
750         if (!idx->removed
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);
758
759             /* Find the offset of the data segment, after the id */
760             data_offset = SHMCB_CYCLIC_INCREMENT(idx->data_pos, 
761                                                  idx->id_len,
762                                                  header->subcache_data_size);
763
764             *destlen = idx->data_used - idx->id_len;
765
766             /* Copy out the data */
767             shmcb_cyclic_cton_memcpy(header->subcache_data_size,
768                                      dest, SHMCB_DATA(header, subcache),
769                                      data_offset, *destlen);
770
771             return 0;
772         }
773         /* Increment */
774         loop++;
775         pos = SHMCB_CYCLIC_INCREMENT(pos, 1, header->index_num);
776     }
777
778     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
779                  "shmcb_subcache_retrieve found no match");
780     return -1;
781
782 }
783
784 static int shmcb_subcache_remove(server_rec *s, SHMCBHeader *header,
785                                  SHMCBSubcache *subcache,
786                                  const unsigned char *id, unsigned int idlen)
787 {
788     unsigned int pos;
789     unsigned int loop = 0;
790
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. */
796
797     pos = subcache->idx_pos;
798     while (loop < subcache->idx_used) {
799         SHMCBIndex *idx = SHMCB_INDEX(subcache, pos);
800
801         /* Only consider 'idx' if the id matches, and the "removed"
802          * flag isn't set. */
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. */
810             idx->removed = 1;
811             ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
812                              "shmcb_subcache_remove removing matching session");
813             return 0;
814         }
815         /* Increment */
816         loop++;
817         pos = SHMCB_CYCLIC_INCREMENT(pos, 1, header->index_num);
818     }
819
820     return -1; /* failure */
821 }
822
823 static const ap_socache_provider_t socache_shmcb = {
824     "shmcb",
825     AP_SOCACHE_FLAG_NOTMPSAFE,
826     socache_shmcb_create,
827     socache_shmcb_init,
828     socache_shmcb_kill,
829     socache_shmcb_store,
830     socache_shmcb_retrieve,
831     socache_shmcb_remove,
832     socache_shmcb_status
833 };
834
835 static void register_hooks(apr_pool_t *p)
836 {
837     ap_register_provider(p, AP_SOCACHE_PROVIDER_GROUP, "shmcb", 
838                          AP_SOCACHE_PROVIDER_VERSION,
839                          &socache_shmcb);
840 }
841
842 module AP_MODULE_DECLARE_DATA socache_shmcb_module = {
843     STANDARD20_MODULE_STUFF,
844     NULL, NULL, NULL, NULL, NULL,
845     register_hooks
846 };