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