]> granicus.if.org Git - apache/blob - modules/cache/mod_socache_dbm.c
mod_cache: Make CacheEnable and CacheDisable configurable per
[apache] / modules / cache / mod_socache_dbm.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 #include "mpm_common.h"
23
24 #include "apr.h"
25 #include "apr_strings.h"
26 #include "apr_time.h"
27 #define APR_WANT_STRFUNC
28 #include "apr_want.h"
29 #include "apr_dbm.h"
30
31 #if APR_HAVE_UNISTD_H
32 #include <unistd.h>
33 #endif
34
35 #include "ap_socache.h"
36
37 #if AP_NEED_SET_MUTEX_PERMS
38 #include "unixd.h"
39 #endif
40
41 /* Use of the context structure must be thread-safe after the initial
42  * create/init; callers must hold the mutex. */
43 struct ap_socache_instance_t {
44     const char *data_file;
45     /* Pool must only be used with the mutex held. */
46     apr_pool_t *pool;
47     apr_time_t last_expiry;
48     apr_interval_time_t expiry_interval;
49 };
50
51 /**
52  * Support for DBM library
53  */
54 #define DBM_FILE_MODE ( APR_UREAD | APR_UWRITE | APR_GREAD | APR_WREAD )
55
56 /* Check for definition of DEFAULT_REL_RUNTIMEDIR */
57 #ifndef DEFAULT_REL_RUNTIMEDIR
58 #define DEFAULT_DBM_PREFIX "logs/socache-dbm-"
59 #else
60 #define DEFAULT_DBM_PREFIX DEFAULT_REL_RUNTIMEDIR "/socache-dbm-"
61 #endif
62
63 /* ### this should use apr_dbm_usednames. */
64 #if !defined(DBM_FILE_SUFFIX_DIR) && !defined(DBM_FILE_SUFFIX_PAG)
65 #if defined(DBM_SUFFIX)
66 #define DBM_FILE_SUFFIX_DIR DBM_SUFFIX
67 #define DBM_FILE_SUFFIX_PAG DBM_SUFFIX
68 #elif defined(__FreeBSD__) || (defined(DB_LOCK) && defined(DB_SHMEM))
69 #define DBM_FILE_SUFFIX_DIR ".db"
70 #define DBM_FILE_SUFFIX_PAG ".db"
71 #else
72 #define DBM_FILE_SUFFIX_DIR ".dir"
73 #define DBM_FILE_SUFFIX_PAG ".pag"
74 #endif
75 #endif
76
77 static void socache_dbm_expire(ap_socache_instance_t *ctx, server_rec *s);
78
79 static apr_status_t socache_dbm_remove(ap_socache_instance_t *ctx, 
80                                        server_rec *s, const unsigned char *id, 
81                                        unsigned int idlen, apr_pool_t *p);
82
83 static const char *socache_dbm_create(ap_socache_instance_t **context, 
84                                       const char *arg, 
85                                       apr_pool_t *tmp, apr_pool_t *p)
86 {
87     ap_socache_instance_t *ctx;
88
89     *context = ctx = apr_pcalloc(p, sizeof *ctx);
90
91     if (arg && *arg) {
92         ctx->data_file = ap_server_root_relative(p, arg);
93         if (!ctx->data_file) {
94             return apr_psprintf(tmp, "Invalid cache file path %s", arg);
95         }
96     }
97
98     apr_pool_create(&ctx->pool, p);
99
100     return NULL;
101 }
102
103 static apr_status_t socache_dbm_init(ap_socache_instance_t *ctx, 
104                                      const char *namespace, 
105                                      const struct ap_socache_hints *hints,
106                                      server_rec *s, apr_pool_t *p)
107 {
108     apr_dbm_t *dbm;
109     apr_status_t rv;
110
111     /* for the DBM we need the data file */
112     if (ctx->data_file == NULL) {
113         const char *path = apr_pstrcat(p, DEFAULT_DBM_PREFIX, namespace,
114                                        NULL);
115
116         ctx->data_file = ap_server_root_relative(p, path);
117
118         if (ctx->data_file == NULL) {
119             ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
120                          "could not use default path '%s' for DBM socache",
121                          path);
122             return APR_EINVAL;
123         }
124     }
125
126     /* open it once to create it and to make sure it _can_ be created */
127     apr_pool_clear(ctx->pool);
128
129     if ((rv = apr_dbm_open(&dbm, ctx->data_file,
130             APR_DBM_RWCREATE, DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) {
131         ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
132                      "Cannot create socache DBM file `%s'",
133                      ctx->data_file);
134         return rv;
135     }
136     apr_dbm_close(dbm);
137
138     ctx->expiry_interval = (hints && hints->expiry_interval 
139                             ? hints->expiry_interval : apr_time_from_sec(30));
140
141 #if AP_NEED_SET_MUTEX_PERMS
142     /*
143      * We have to make sure the Apache child processes have access to
144      * the DBM file. But because there are brain-dead platforms where we
145      * cannot exactly determine the suffixes we try all possibilities.
146      */
147     if (geteuid() == 0 /* is superuser */) {
148         chown(ctx->data_file, ap_unixd_config.user_id, -1 /* no gid change */);
149         if (chown(apr_pstrcat(p, ctx->data_file, DBM_FILE_SUFFIX_DIR, NULL),
150                   ap_unixd_config.user_id, -1) == -1) {
151             if (chown(apr_pstrcat(p, ctx->data_file, ".db", NULL),
152                       ap_unixd_config.user_id, -1) == -1)
153                 chown(apr_pstrcat(p, ctx->data_file, ".dir", NULL),
154                       ap_unixd_config.user_id, -1);
155         }
156         if (chown(apr_pstrcat(p, ctx->data_file, DBM_FILE_SUFFIX_PAG, NULL),
157                   ap_unixd_config.user_id, -1) == -1) {
158             if (chown(apr_pstrcat(p, ctx->data_file, ".db", NULL),
159                       ap_unixd_config.user_id, -1) == -1)
160                 chown(apr_pstrcat(p, ctx->data_file, ".pag", NULL),
161                       ap_unixd_config.user_id, -1);
162         }
163     }
164 #endif
165     socache_dbm_expire(ctx, s);
166
167     return APR_SUCCESS;
168 }
169
170 static void socache_dbm_destroy(ap_socache_instance_t *ctx, server_rec *s)
171 {
172     /* the correct way */
173     unlink(apr_pstrcat(ctx->pool, ctx->data_file, DBM_FILE_SUFFIX_DIR, NULL));
174     unlink(apr_pstrcat(ctx->pool, ctx->data_file, DBM_FILE_SUFFIX_PAG, NULL));
175     /* the additional ways to be sure */
176     unlink(apr_pstrcat(ctx->pool, ctx->data_file, ".dir", NULL));
177     unlink(apr_pstrcat(ctx->pool, ctx->data_file, ".pag", NULL));
178     unlink(apr_pstrcat(ctx->pool, ctx->data_file, ".db", NULL));
179     unlink(ctx->data_file);
180
181     return;
182 }
183
184 static apr_status_t socache_dbm_store(ap_socache_instance_t *ctx, 
185                                       server_rec *s, const unsigned char *id, 
186                                       unsigned int idlen, apr_time_t expiry, 
187                                       unsigned char *ucaData, 
188                                       unsigned int nData, apr_pool_t *pool)
189 {
190     apr_dbm_t *dbm;
191     apr_datum_t dbmkey;
192     apr_datum_t dbmval;
193     apr_status_t rv;
194
195     /* be careful: do not try to store too much bytes in a DBM file! */
196 #ifdef PAIRMAX
197     if ((idlen + nData) >= PAIRMAX) {
198         ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
199                  "data size too large for DBM socache: %d >= %d",
200                  (idlen + nData), PAIRMAX);
201         return APR_ENOSPC;
202     }
203 #else
204     if ((idlen + nData) >= 950 /* at least less than approx. 1KB */) {
205         ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
206                  "data size too large for DBM socache: %d >= %d",
207                  (idlen + nData), 950);
208         return APR_ENOSPC;
209     }
210 #endif
211
212     /* create DBM key */
213     dbmkey.dptr  = (char *)id;
214     dbmkey.dsize = idlen;
215
216     /* create DBM value */
217     dbmval.dsize = sizeof(apr_time_t) + nData;
218     dbmval.dptr  = (char *)malloc(dbmval.dsize);
219     if (dbmval.dptr == NULL) {
220         ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
221                  "malloc error creating DBM value");
222         return APR_ENOMEM;
223     }
224     memcpy((char *)dbmval.dptr, &expiry, sizeof(apr_time_t));
225     memcpy((char *)dbmval.dptr+sizeof(apr_time_t), ucaData, nData);
226
227     /* and store it to the DBM file */
228     apr_pool_clear(ctx->pool);
229
230     if ((rv = apr_dbm_open(&dbm, ctx->data_file,
231                            APR_DBM_RWCREATE, DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) {
232         ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
233                      "Cannot open socache DBM file `%s' for writing "
234                      "(store)",
235                      ctx->data_file);
236         free(dbmval.dptr);
237         return rv;
238     }
239     if ((rv = apr_dbm_store(dbm, dbmkey, dbmval)) != APR_SUCCESS) {
240         ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
241                      "Cannot store socache object to DBM file `%s'",
242                      ctx->data_file);
243         apr_dbm_close(dbm);
244         free(dbmval.dptr);
245         return rv;
246     }
247     apr_dbm_close(dbm);
248
249     /* free temporary buffers */
250     free(dbmval.dptr);
251
252     /* allow the regular expiring to occur */
253     socache_dbm_expire(ctx, s);
254
255     return APR_SUCCESS;
256 }
257
258 static apr_status_t socache_dbm_retrieve(ap_socache_instance_t *ctx, server_rec *s, 
259                                          const unsigned char *id, unsigned int idlen,
260                                          unsigned char *dest, unsigned int *destlen,
261                                          apr_pool_t *p)
262 {
263     apr_dbm_t *dbm;
264     apr_datum_t dbmkey;
265     apr_datum_t dbmval;
266     unsigned int nData;
267     apr_time_t expiry;
268     apr_time_t now;
269     apr_status_t rc;
270
271     /* allow the regular expiring to occur */
272     socache_dbm_expire(ctx, s);
273
274     /* create DBM key and values */
275     dbmkey.dptr  = (char *)id;
276     dbmkey.dsize = idlen;
277
278     /* and fetch it from the DBM file
279      * XXX: Should we open the dbm against r->pool so the cleanup will
280      * do the apr_dbm_close? This would make the code a bit cleaner.
281      */
282     apr_pool_clear(ctx->pool);
283     if ((rc = apr_dbm_open(&dbm, ctx->data_file, APR_DBM_RWCREATE, 
284                            DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) {
285         ap_log_error(APLOG_MARK, APLOG_ERR, rc, s,
286                      "Cannot open socache DBM file `%s' for reading "
287                      "(fetch)",
288                      ctx->data_file);
289         return rc;
290     }
291     rc = apr_dbm_fetch(dbm, dbmkey, &dbmval);
292     if (rc != APR_SUCCESS) {
293         apr_dbm_close(dbm);
294         return APR_NOTFOUND;
295     }
296     if (dbmval.dptr == NULL || dbmval.dsize <= sizeof(apr_time_t)) {
297         apr_dbm_close(dbm);
298         return APR_EGENERAL;
299     }
300
301     /* parse resulting data */
302     nData = dbmval.dsize-sizeof(apr_time_t);
303     if (nData > *destlen) {
304         apr_dbm_close(dbm);
305         return APR_ENOSPC;
306     }    
307
308     *destlen = nData;
309     memcpy(&expiry, dbmval.dptr, sizeof(apr_time_t));
310     memcpy(dest, (char *)dbmval.dptr + sizeof(apr_time_t), nData);
311
312     apr_dbm_close(dbm);
313
314     /* make sure the stuff is still not expired */
315     now = apr_time_now();
316     if (expiry <= now) {
317         socache_dbm_remove(ctx, s, id, idlen, p);
318         return APR_NOTFOUND;
319     }
320
321     return APR_SUCCESS;
322 }
323
324 static apr_status_t socache_dbm_remove(ap_socache_instance_t *ctx, 
325                                        server_rec *s, const unsigned char *id,
326                                        unsigned int idlen, apr_pool_t *p)
327 {
328     apr_dbm_t *dbm;
329     apr_datum_t dbmkey;
330     apr_status_t rv;
331
332     /* create DBM key and values */
333     dbmkey.dptr  = (char *)id;
334     dbmkey.dsize = idlen;
335
336     /* and delete it from the DBM file */
337     apr_pool_clear(ctx->pool);
338
339     if ((rv = apr_dbm_open(&dbm, ctx->data_file, APR_DBM_RWCREATE, 
340                            DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) {
341         ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
342                      "Cannot open socache DBM file `%s' for writing "
343                      "(delete)",
344                      ctx->data_file);
345         return rv;
346     }
347     apr_dbm_delete(dbm, dbmkey);
348     apr_dbm_close(dbm);
349
350     return APR_SUCCESS;
351 }
352
353 static void socache_dbm_expire(ap_socache_instance_t *ctx, server_rec *s)
354 {
355     apr_dbm_t *dbm;
356     apr_datum_t dbmkey;
357     apr_datum_t dbmval;
358     apr_time_t expiry;
359     int elts = 0;
360     int deleted = 0;
361     int expired;
362     apr_datum_t *keylist;
363     int keyidx;
364     int i;
365     apr_time_t now;
366     apr_status_t rv;
367
368     /*
369      * make sure the expiration for still not-accessed
370      * socache entries is done only from time to time
371      */
372     now = apr_time_now();
373
374     if (now < ctx->last_expiry + ctx->expiry_interval) {
375         return;
376     }
377
378     ctx->last_expiry = now;
379
380     /*
381      * Here we have to be very carefully: Not all DBM libraries are
382      * smart enough to allow one to iterate over the elements and at the
383      * same time delete expired ones. Some of them get totally crazy
384      * while others have no problems. So we have to do it the slower but
385      * more safe way: we first iterate over all elements and remember
386      * those which have to be expired. Then in a second pass we delete
387      * all those expired elements. Additionally we reopen the DBM file
388      * to be really safe in state.
389      */
390
391 #define KEYMAX 1024
392
393     for (;;) {
394         /* allocate the key array in a memory sub pool */
395         apr_pool_clear(ctx->pool);
396
397         if ((keylist = apr_palloc(ctx->pool, sizeof(dbmkey)*KEYMAX)) == NULL) {
398             break;
399         }
400
401         /* pass 1: scan DBM database */
402         keyidx = 0;
403         if ((rv = apr_dbm_open(&dbm, ctx->data_file, APR_DBM_RWCREATE,
404                                DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) {
405             ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
406                          "Cannot open socache DBM file `%s' for "
407                          "scanning",
408                          ctx->data_file);
409             break;
410         }
411         apr_dbm_firstkey(dbm, &dbmkey);
412         while (dbmkey.dptr != NULL) {
413             elts++;
414             expired = FALSE;
415             apr_dbm_fetch(dbm, dbmkey, &dbmval);
416             if (dbmval.dsize <= sizeof(apr_time_t) || dbmval.dptr == NULL)
417                 expired = TRUE;
418             else {
419                 memcpy(&expiry, dbmval.dptr, sizeof(apr_time_t));
420                 if (expiry <= now)
421                     expired = TRUE;
422             }
423             if (expired) {
424                 if ((keylist[keyidx].dptr = apr_pmemdup(ctx->pool, dbmkey.dptr, dbmkey.dsize)) != NULL) {
425                     keylist[keyidx].dsize = dbmkey.dsize;
426                     keyidx++;
427                     if (keyidx == KEYMAX)
428                         break;
429                 }
430             }
431             apr_dbm_nextkey(dbm, &dbmkey);
432         }
433         apr_dbm_close(dbm);
434
435         /* pass 2: delete expired elements */
436         if (apr_dbm_open(&dbm, ctx->data_file, APR_DBM_RWCREATE,
437                          DBM_FILE_MODE, ctx->pool) != APR_SUCCESS) {
438             ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
439                          "Cannot re-open socache DBM file `%s' for "
440                          "expiring",
441                          ctx->data_file);
442             break;
443         }
444         for (i = 0; i < keyidx; i++) {
445             apr_dbm_delete(dbm, keylist[i]);
446             deleted++;
447         }
448         apr_dbm_close(dbm);
449
450         if (keyidx < KEYMAX)
451             break;
452     }
453
454     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
455                  "DBM socache expiry: "
456                  "old: %d, new: %d, removed: %d",
457                  elts, elts-deleted, deleted);
458 }
459
460 static void socache_dbm_status(ap_socache_instance_t *ctx, request_rec *r, 
461                                int flags)
462 {
463     apr_dbm_t *dbm;
464     apr_datum_t dbmkey;
465     apr_datum_t dbmval;
466     int elts;
467     long size;
468     int avg;
469     apr_status_t rv;
470
471     elts = 0;
472     size = 0;
473
474     apr_pool_clear(ctx->pool);
475     if ((rv = apr_dbm_open(&dbm, ctx->data_file, APR_DBM_RWCREATE, 
476                            DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) {
477         ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
478                      "Cannot open socache DBM file `%s' for status "
479                      "retrival",
480                      ctx->data_file);
481         return;
482     }
483     /*
484      * XXX - Check the return value of apr_dbm_firstkey, apr_dbm_fetch - TBD
485      */
486     apr_dbm_firstkey(dbm, &dbmkey);
487     for ( ; dbmkey.dptr != NULL; apr_dbm_nextkey(dbm, &dbmkey)) {
488         apr_dbm_fetch(dbm, dbmkey, &dbmval);
489         if (dbmval.dptr == NULL)
490             continue;
491         elts += 1;
492         size += dbmval.dsize;
493     }
494     apr_dbm_close(dbm);
495     if (size > 0 && elts > 0)
496         avg = (int)(size / (long)elts);
497     else
498         avg = 0;
499     ap_rprintf(r, "cache type: <b>DBM</b>, maximum size: <b>unlimited</b><br>");
500     ap_rprintf(r, "current entries: <b>%d</b>, current size: <b>%ld</b> bytes<br>", elts, size);
501     ap_rprintf(r, "average entry size: <b>%d</b> bytes<br>", avg);
502     return;
503 }
504
505 static apr_status_t socache_dbm_iterate(ap_socache_instance_t *ctx,
506                                         server_rec *s, void *userctx,
507                                         ap_socache_iterator_t *iterator,
508                                         apr_pool_t *pool)
509 {
510     apr_dbm_t *dbm;
511     apr_datum_t dbmkey;
512     apr_datum_t dbmval;
513     apr_time_t expiry;
514     int expired;
515     apr_time_t now;
516     apr_status_t rv;
517
518     /*
519      * make sure the expired records are omitted
520      */
521     now = apr_time_now();
522     if ((rv = apr_dbm_open(&dbm, ctx->data_file, APR_DBM_RWCREATE,
523                            DBM_FILE_MODE, ctx->pool)) != APR_SUCCESS) {
524         ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
525                      "Cannot open socache DBM file `%s' for "
526                      "iterating", ctx->data_file);
527         return rv;
528     }
529     rv = apr_dbm_firstkey(dbm, &dbmkey);
530     while (rv == APR_SUCCESS && dbmkey.dptr != NULL) {
531         expired = FALSE;
532         apr_dbm_fetch(dbm, dbmkey, &dbmval);
533         if (dbmval.dsize <= sizeof(apr_time_t) || dbmval.dptr == NULL)
534             expired = TRUE;
535         else {
536             memcpy(&expiry, dbmval.dptr, sizeof(apr_time_t));
537             if (expiry <= now)
538                 expired = TRUE;
539         }
540         if (!expired) {
541             rv = iterator(ctx, s, userctx,
542                              (unsigned char *)dbmkey.dptr, dbmkey.dsize,
543                              (unsigned char *)dbmval.dptr + sizeof(apr_time_t),
544                              dbmval.dsize - sizeof(apr_time_t), pool);
545             ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s,
546                          "dbm `%s' entry iterated", ctx->data_file);
547             if (rv != APR_SUCCESS)
548                 return rv;
549         }
550         rv = apr_dbm_nextkey(dbm, &dbmkey);
551     }
552     apr_dbm_close(dbm);
553
554     if (rv != APR_SUCCESS && rv != APR_EOF) {
555         ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
556                      "Failure reading first/next socache DBM file `%s' record",
557                      ctx->data_file);
558         return rv;
559     }
560     return APR_SUCCESS;
561 }
562
563 static const ap_socache_provider_t socache_dbm = {
564     "dbm",
565     AP_SOCACHE_FLAG_NOTMPSAFE,
566     socache_dbm_create,
567     socache_dbm_init,
568     socache_dbm_destroy,
569     socache_dbm_store,
570     socache_dbm_retrieve,
571     socache_dbm_remove,
572     socache_dbm_status,
573     socache_dbm_iterate
574 };
575
576 static void register_hooks(apr_pool_t *p)
577 {
578     ap_register_provider(p, AP_SOCACHE_PROVIDER_GROUP, "dbm", 
579                          AP_SOCACHE_PROVIDER_VERSION,
580                          &socache_dbm);
581 }
582
583 AP_DECLARE_MODULE(socache_dbm) = {
584     STANDARD20_MODULE_STUFF,
585     NULL, NULL, NULL, NULL, NULL,
586     register_hooks
587 };