~ubuntu-branches/ubuntu/feisty/apache2/feisty

« back to all changes in this revision

Viewing changes to support/htcacheclean.c

  • Committer: Bazaar Package Importer
  • Author(s): Andreas Barth
  • Date: 2006-12-09 21:05:45 UTC
  • mfrom: (0.6.1 upstream)
  • Revision ID: james.westby@ubuntu.com-20061209210545-h70s0xaqc2v8vqr2
Tags: 2.2.3-3.2
* Non-maintainer upload.
* 043_ajp_connection_reuse: Patch from upstream Bugzilla, fixing a critical
  issue with regard to connection reuse in mod_proxy_ajp.
  Closes: #396265

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
/*
 
18
 * htcacheclean.c: simple program for cleaning of
 
19
 * the disk cache of the Apache HTTP server
 
20
 *
 
21
 * Contributed by Andreas Steinmetz <ast domdv.de>
 
22
 * 8 Oct 2004
 
23
 */
 
24
 
 
25
#include "apr.h"
 
26
#include "apr_lib.h"
 
27
#include "apr_strings.h"
 
28
#include "apr_file_io.h"
 
29
#include "apr_file_info.h"
 
30
#include "apr_pools.h"
 
31
#include "apr_hash.h"
 
32
#include "apr_thread_proc.h"
 
33
#include "apr_signal.h"
 
34
#include "apr_getopt.h"
 
35
#include "apr_ring.h"
 
36
#include "apr_date.h"
 
37
#include "../modules/cache/mod_disk_cache.h"
 
38
 
 
39
#if APR_HAVE_UNISTD_H
 
40
#include <unistd.h>
 
41
#endif
 
42
#if APR_HAVE_STDLIB_H
 
43
#include <stdlib.h>
 
44
#endif
 
45
 
 
46
/* define the following for debugging */
 
47
#undef DEBUG
 
48
 
 
49
/*
 
50
 * Note: on Linux delays <= 2ms are busy waits without
 
51
 *       scheduling, so never use a delay <= 2ms below
 
52
 */
 
53
 
 
54
#define NICE_DELAY    10000     /* usecs */
 
55
#define DELETE_NICE   10        /* be nice after this amount of delete ops */
 
56
#define STAT_ATTEMPTS 10        /* maximum stat attempts for a file */
 
57
#define STAT_DELAY    5000      /* usecs */
 
58
#define HEADER        1         /* headers file */
 
59
#define DATA          2         /* body file */
 
60
#define TEMP          4         /* temporary file */
 
61
#define HEADERDATA    (HEADER|DATA)
 
62
#define MAXDEVIATION  3600      /* secs */
 
63
#define SECS_PER_MIN  60
 
64
#define KBYTE         1024
 
65
#define MBYTE         1048576
 
66
#define GBYTE         1073741824
 
67
 
 
68
#define DIRINFO (APR_FINFO_MTIME|APR_FINFO_SIZE|APR_FINFO_TYPE|APR_FINFO_LINK)
 
69
 
 
70
typedef struct _direntry {
 
71
    APR_RING_ENTRY(_direntry) link;
 
72
    int type;         /* type of file/fileset: TEMP, HEADER, DATA, HEADERDATA */
 
73
    apr_time_t htime; /* headers file modification time */
 
74
    apr_time_t dtime; /* body file modification time */
 
75
    apr_off_t hsize;  /* headers file size */
 
76
    apr_off_t dsize;  /* body or temporary file size */
 
77
    char *basename;   /* file/fileset base name */
 
78
} DIRENTRY;
 
79
 
 
80
typedef struct _entry {
 
81
    APR_RING_ENTRY(_entry) link;
 
82
    apr_time_t expire;        /* cache entry exiration time */
 
83
    apr_time_t response_time; /* cache entry time of last response to client */
 
84
    apr_time_t htime;         /* headers file modification time */
 
85
    apr_time_t dtime;         /* body file modification time */
 
86
    apr_off_t hsize;          /* headers file size */
 
87
    apr_off_t dsize;          /* body or temporary file size */
 
88
    char *basename;           /* fileset base name */
 
89
} ENTRY;
 
90
 
 
91
 
 
92
static int delcount;    /* file deletion count for nice mode */
 
93
static int interrupted; /* flag: true if SIGINT or SIGTERM occurred */
 
94
static int realclean;   /* flag: true means user said apache is not running */
 
95
static int verbose;     /* flag: true means print statistics */
 
96
static int benice;      /* flag: true means nice mode is activated */
 
97
static int dryrun;      /* flag: true means dry run, don't actually delete
 
98
                                 anything */
 
99
static int deldirs;     /* flag: true means directories should be deleted */
 
100
static int baselen;     /* string length of the path to the proxy directory */
 
101
static apr_time_t now;  /* start time of this processing run */
 
102
 
 
103
static apr_file_t *errfile;   /* stderr file handle */
 
104
static apr_off_t unsolicited; /* file size summary for deleted unsolicited
 
105
                                 files */
 
106
static APR_RING_ENTRY(_entry) root; /* ENTRY ring anchor */
 
107
 
 
108
/* short program name as called */
 
109
static const char *shortname = "htcacheclean";
 
110
 
 
111
#ifdef DEBUG
 
112
/*
 
113
 * fake delete for debug purposes
 
114
 */
 
115
#define apr_file_remove fake_file_remove
 
116
static void fake_file_remove(char *pathname, apr_pool_t *p)
 
117
{
 
118
    apr_finfo_t info;
 
119
 
 
120
    /* stat and printing to simulate some deletion system load and to
 
121
       display what would actually have happened */
 
122
    apr_stat(&info, pathname, DIRINFO, p);
 
123
    apr_file_printf(errfile, "would delete %s" APR_EOL_STR, pathname);
 
124
}
 
125
#endif
 
126
 
 
127
/*
 
128
 * called on SIGINT or SIGTERM
 
129
 */
 
130
static void setterm(int unused)
 
131
{
 
132
#ifdef DEBUG
 
133
    apr_file_printf(errfile, "interrupt" APR_EOL_STR);
 
134
#endif
 
135
    interrupted = 1;
 
136
}
 
137
 
 
138
/*
 
139
 * called in out of memory condition
 
140
 */
 
141
static int oom(int unused)
 
142
{
 
143
    static int called = 0;
 
144
 
 
145
    /* be careful to call exit() only once */
 
146
    if (!called) {
 
147
        called = 1;
 
148
        exit(1);
 
149
    }
 
150
    return APR_ENOMEM;
 
151
}
 
152
 
 
153
/*
 
154
 * print purge statistics
 
155
 */
 
156
static void printstats(apr_off_t total, apr_off_t sum, apr_off_t max,
 
157
                       apr_off_t etotal, apr_off_t entries)
 
158
{
 
159
    char ttype, stype, mtype, utype;
 
160
    apr_off_t tfrag, sfrag, ufrag;
 
161
 
 
162
    if (!verbose) {
 
163
        return;
 
164
    }
 
165
 
 
166
    ttype = 'K';
 
167
    tfrag = ((total * 10) / KBYTE) % 10;
 
168
    total /= KBYTE;
 
169
    if (total >= KBYTE) {
 
170
        ttype = 'M';
 
171
        tfrag = ((total * 10) / KBYTE) % 10;
 
172
        total /= KBYTE;
 
173
    }
 
174
 
 
175
    stype = 'K';
 
176
    sfrag = ((sum * 10) / KBYTE) % 10;
 
177
    sum /= KBYTE;
 
178
    if (sum >= KBYTE) {
 
179
        stype = 'M';
 
180
        sfrag = ((sum * 10) / KBYTE) % 10;
 
181
        sum /= KBYTE;
 
182
    }
 
183
 
 
184
    mtype = 'K';
 
185
    max /= KBYTE;
 
186
    if (max >= KBYTE) {
 
187
        mtype = 'M';
 
188
        max /= KBYTE;
 
189
    }
 
190
 
 
191
    apr_file_printf(errfile, "Statistics:" APR_EOL_STR);
 
192
    if (unsolicited) {
 
193
        utype = 'K';
 
194
        ufrag = ((unsolicited * 10) / KBYTE) % 10;
 
195
        unsolicited /= KBYTE;
 
196
        if (unsolicited >= KBYTE) {
 
197
            utype = 'M';
 
198
            ufrag = ((unsolicited * 10) / KBYTE) % 10;
 
199
            unsolicited /= KBYTE;
 
200
        }
 
201
        if (!unsolicited && !ufrag) {
 
202
            ufrag = 1;
 
203
        }
 
204
        apr_file_printf(errfile, "unsolicited size %d.%d%c" APR_EOL_STR,
 
205
                        (int)(unsolicited), (int)(ufrag), utype);
 
206
     }
 
207
     apr_file_printf(errfile, "size limit %d.0%c" APR_EOL_STR,
 
208
                     (int)(max), mtype);
 
209
     apr_file_printf(errfile, "total size was %d.%d%c, total size now "
 
210
                              "%d.%d%c" APR_EOL_STR,
 
211
                     (int)(total), (int)(tfrag), ttype, (int)(sum),
 
212
                     (int)(sfrag), stype);
 
213
     apr_file_printf(errfile, "total entries was %d, total entries now %d"
 
214
                              APR_EOL_STR, (int)(etotal), (int)(entries));
 
215
}
 
216
 
 
217
/*
 
218
 * delete a single file
 
219
 */
 
220
static void delete_file(char *path, char *basename, apr_pool_t *pool)
 
221
{
 
222
    char *nextpath;
 
223
    apr_pool_t *p;
 
224
 
 
225
    if (dryrun) {
 
226
        return;
 
227
    }
 
228
 
 
229
    /* temp pool, otherwise lots of memory could be allocated */
 
230
    apr_pool_create(&p, pool);
 
231
    nextpath = apr_pstrcat(p, path, "/", basename, NULL);
 
232
    apr_file_remove(nextpath, p);
 
233
    apr_pool_destroy(p);
 
234
 
 
235
    if (benice) {
 
236
        if (++delcount >= DELETE_NICE) {
 
237
            apr_sleep(NICE_DELAY);
 
238
            delcount = 0;
 
239
        }
 
240
    }
 
241
}
 
242
 
 
243
/*
 
244
 * delete cache file set
 
245
 */
 
246
static void delete_entry(char *path, char *basename, apr_pool_t *pool)
 
247
{
 
248
    char *nextpath;
 
249
    apr_pool_t *p;
 
250
 
 
251
    if (dryrun) {
 
252
        return;
 
253
    }
 
254
 
 
255
    /* temp pool, otherwise lots of memory could be allocated */
 
256
    apr_pool_create(&p, pool);
 
257
 
 
258
    nextpath = apr_pstrcat(p, path, "/", basename, CACHE_HEADER_SUFFIX, NULL);
 
259
    apr_file_remove(nextpath, p);
 
260
 
 
261
    nextpath = apr_pstrcat(p, path, "/", basename, CACHE_DATA_SUFFIX, NULL);
 
262
    apr_file_remove(nextpath, p);
 
263
 
 
264
    apr_pool_destroy(p);
 
265
 
 
266
    if (benice) {
 
267
        delcount += 2;
 
268
        if (delcount >= DELETE_NICE) {
 
269
            apr_sleep(NICE_DELAY);
 
270
            delcount = 0;
 
271
        }
 
272
    }
 
273
}
 
274
 
 
275
/*
 
276
 * walk the cache directory tree
 
277
 */
 
278
static int process_dir(char *path, apr_pool_t *pool)
 
279
{
 
280
    apr_dir_t *dir;
 
281
    apr_pool_t *p;
 
282
    apr_hash_t *h;
 
283
    apr_hash_index_t *i;
 
284
    apr_file_t *fd;
 
285
    apr_status_t status;
 
286
    apr_finfo_t info;
 
287
    apr_size_t len;
 
288
    apr_time_t current, deviation;
 
289
    char *nextpath, *base, *ext, *orig_basename;
 
290
    APR_RING_ENTRY(_direntry) anchor;
 
291
    DIRENTRY *d, *t, *n;
 
292
    ENTRY *e;
 
293
    int skip, retries;
 
294
    disk_cache_info_t disk_info;
 
295
 
 
296
    APR_RING_INIT(&anchor, _direntry, link);
 
297
    apr_pool_create(&p, pool);
 
298
    h = apr_hash_make(p);
 
299
    fd = NULL;
 
300
    skip = 0;
 
301
    deviation = MAXDEVIATION * APR_USEC_PER_SEC;
 
302
 
 
303
    if (apr_dir_open(&dir, path, p) != APR_SUCCESS) {
 
304
        return 1;
 
305
    }
 
306
 
 
307
    while (apr_dir_read(&info, 0, dir) == APR_SUCCESS && !interrupted) {
 
308
        if (!strcmp(info.name, ".") || !strcmp(info.name, "..")) {
 
309
            continue;
 
310
        }
 
311
        d = apr_pcalloc(p, sizeof(DIRENTRY));
 
312
        d->basename = apr_pstrcat(p, path, "/", info.name, NULL);
 
313
        APR_RING_INSERT_TAIL(&anchor, d, _direntry, link);
 
314
    }
 
315
 
 
316
    apr_dir_close(dir);
 
317
 
 
318
    if (interrupted) {
 
319
        return 1;
 
320
    }
 
321
 
 
322
    skip = baselen + 1;
 
323
 
 
324
    for (d = APR_RING_FIRST(&anchor);
 
325
         !interrupted && d != APR_RING_SENTINEL(&anchor, _direntry, link);
 
326
         d=n) {
 
327
        n = APR_RING_NEXT(d, link);
 
328
        base = strrchr(d->basename, '/');
 
329
        if (!base++) {
 
330
            base = d->basename;
 
331
        }
 
332
        ext = strchr(base, '.');
 
333
 
 
334
        /* there may be temporary files which may be gone before
 
335
         * processing, always skip these if not in realclean mode
 
336
         */
 
337
        if (!ext && !realclean) {
 
338
            if (!strncasecmp(base, AP_TEMPFILE_BASE, AP_TEMPFILE_BASELEN)
 
339
                && strlen(base) == AP_TEMPFILE_NAMELEN) {
 
340
                continue;
 
341
            }
 
342
        }
 
343
 
 
344
        /* this may look strange but apr_stat() may return errno which
 
345
         * is system dependent and there may be transient failures,
 
346
         * so just blindly retry for a short while
 
347
         */
 
348
        retries = STAT_ATTEMPTS;
 
349
        status = APR_SUCCESS;
 
350
        do {
 
351
            if (status != APR_SUCCESS) {
 
352
                apr_sleep(STAT_DELAY);
 
353
            }
 
354
            status = apr_stat(&info, d->basename, DIRINFO, p);
 
355
        } while (status != APR_SUCCESS && !interrupted && --retries);
 
356
 
 
357
        /* what may happen here is that apache did create a file which
 
358
         * we did detect but then does delete the file before we can
 
359
         * get file information, so if we don't get any file information
 
360
         * we will ignore the file in this case
 
361
         */
 
362
        if (status != APR_SUCCESS) {
 
363
            if (!realclean && !interrupted) {
 
364
                continue;
 
365
            }
 
366
            return 1;
 
367
        }
 
368
 
 
369
        if (info.filetype == APR_DIR) {
 
370
            /* Make a copy of the basename, as process_dir modifies it */
 
371
            orig_basename = apr_pstrdup(pool, d->basename);
 
372
            if (process_dir(d->basename, pool)) {
 
373
                return 1;
 
374
            }
 
375
 
 
376
            /* If asked to delete dirs, do so now. We don't care if it fails.
 
377
             * If it fails, it likely means there was something else there.
 
378
             */
 
379
            if (deldirs && !dryrun) {
 
380
                apr_dir_remove(orig_basename, pool);
 
381
            }
 
382
            continue;
 
383
        }
 
384
 
 
385
        if (info.filetype != APR_REG) {
 
386
            continue;
 
387
        }
 
388
 
 
389
        if (!ext) {
 
390
            if (!strncasecmp(base, AP_TEMPFILE_BASE, AP_TEMPFILE_BASELEN)
 
391
                && strlen(base) == AP_TEMPFILE_NAMELEN) {
 
392
                d->basename += skip;
 
393
                d->type = TEMP;
 
394
                d->dsize = info.size;
 
395
                apr_hash_set(h, d->basename, APR_HASH_KEY_STRING, d);
 
396
            }
 
397
            continue;
 
398
        }
 
399
 
 
400
        if (!strcasecmp(ext, CACHE_HEADER_SUFFIX)) {
 
401
            *ext = '\0';
 
402
            d->basename += skip;
 
403
            /* if a user manually creates a '.header' file */
 
404
            if (d->basename[0] == '\0') {
 
405
                continue;
 
406
            }
 
407
            t = apr_hash_get(h, d->basename, APR_HASH_KEY_STRING);
 
408
            if (t) {
 
409
                d = t;
 
410
            }
 
411
            d->type |= HEADER;
 
412
            d->htime = info.mtime;
 
413
            d->hsize = info.size;
 
414
            apr_hash_set(h, d->basename, APR_HASH_KEY_STRING, d);
 
415
            continue;
 
416
        }
 
417
 
 
418
        if (!strcasecmp(ext, CACHE_DATA_SUFFIX)) {
 
419
            *ext = '\0';
 
420
            d->basename += skip;
 
421
            /* if a user manually creates a '.data' file */
 
422
            if (d->basename[0] == '\0') {
 
423
                continue;
 
424
            }
 
425
            t = apr_hash_get(h, d->basename, APR_HASH_KEY_STRING);
 
426
            if (t) {
 
427
                d = t;
 
428
            }
 
429
            d->type |= DATA;
 
430
            d->dtime = info.mtime;
 
431
            d->dsize = info.size;
 
432
            apr_hash_set(h, d->basename, APR_HASH_KEY_STRING, d);
 
433
        }
 
434
    }
 
435
 
 
436
    if (interrupted) {
 
437
        return 1;
 
438
    }
 
439
 
 
440
    path[baselen] = '\0';
 
441
 
 
442
    for (i = apr_hash_first(p, h); i && !interrupted; i = apr_hash_next(i)) {
 
443
        void *hvalue;
 
444
        apr_uint32_t format;
 
445
 
 
446
        apr_hash_this(i, NULL, NULL, &hvalue);
 
447
        d = hvalue;
 
448
 
 
449
        switch(d->type) {
 
450
        case HEADERDATA:
 
451
            nextpath = apr_pstrcat(p, path, "/", d->basename,
 
452
                                   CACHE_HEADER_SUFFIX, NULL);
 
453
            if (apr_file_open(&fd, nextpath, APR_FOPEN_READ | APR_FOPEN_BINARY,
 
454
                              APR_OS_DEFAULT, p) == APR_SUCCESS) {
 
455
                len = sizeof(format);
 
456
                if (apr_file_read_full(fd, &format, len,
 
457
                                       &len) == APR_SUCCESS) {
 
458
                    if (format == DISK_FORMAT_VERSION) {
 
459
                        apr_off_t offset = 0;
 
460
 
 
461
                        apr_file_seek(fd, APR_SET, &offset);
 
462
 
 
463
                        len = sizeof(disk_cache_info_t);
 
464
 
 
465
                        if (apr_file_read_full(fd, &disk_info, len,
 
466
                                               &len) == APR_SUCCESS) {
 
467
                            apr_file_close(fd);
 
468
                            e = apr_palloc(pool, sizeof(ENTRY));
 
469
                            APR_RING_INSERT_TAIL(&root, e, _entry, link);
 
470
                            e->expire = disk_info.expire;
 
471
                            e->response_time = disk_info.response_time;
 
472
                            e->htime = d->htime;
 
473
                            e->dtime = d->dtime;
 
474
                            e->hsize = d->hsize;
 
475
                            e->dsize = d->dsize;
 
476
                            e->basename = apr_palloc(pool,
 
477
                                                     strlen(d->basename) + 1);
 
478
                            strcpy(e->basename, d->basename);
 
479
                            break;
 
480
                        }
 
481
                        else {
 
482
                            apr_file_close(fd);
 
483
                        }
 
484
                    }
 
485
                    else if (format == VARY_FORMAT_VERSION) {
 
486
                        /* This must be a URL that added Vary headers later,
 
487
                         * so kill the orphaned .data file
 
488
                         */
 
489
                        apr_file_close(fd);
 
490
                        apr_file_remove(apr_pstrcat(p, path, "/", d->basename,
 
491
                                                    CACHE_DATA_SUFFIX, NULL),
 
492
                                        p);
 
493
                    }
 
494
                }
 
495
                else {
 
496
                    apr_file_close(fd);
 
497
                }
 
498
 
 
499
            }
 
500
            /* we have a somehow unreadable headers file which is associated
 
501
             * with a data file. this may be caused by apache currently
 
502
             * rewriting the headers file. thus we may delete the file set
 
503
             * either in realclean mode or if the headers file modification
 
504
             * timestamp is not within a specified positive or negative offset
 
505
             * to the current time.
 
506
             */
 
507
            current = apr_time_now();
 
508
            if (realclean || d->htime < current - deviation
 
509
                || d->htime > current + deviation) {
 
510
                delete_entry(path, d->basename, p);
 
511
                unsolicited += d->hsize;
 
512
                unsolicited += d->dsize;
 
513
            }
 
514
            break;
 
515
 
 
516
        /* single data and header files may be deleted either in realclean
 
517
         * mode or if their modification timestamp is not within a
 
518
         * specified positive or negative offset to the current time.
 
519
         * this handling is necessary due to possible race conditions
 
520
         * between apache and this process
 
521
         */
 
522
        case HEADER:
 
523
            current = apr_time_now();
 
524
            nextpath = apr_pstrcat(p, path, "/", d->basename,
 
525
                                   CACHE_HEADER_SUFFIX, NULL);
 
526
            if (apr_file_open(&fd, nextpath, APR_FOPEN_READ | APR_FOPEN_BINARY,
 
527
                              APR_OS_DEFAULT, p) == APR_SUCCESS) {
 
528
                len = sizeof(format);
 
529
                if (apr_file_read_full(fd, &format, len,
 
530
                                       &len) == APR_SUCCESS) {
 
531
                    if (format == VARY_FORMAT_VERSION) {
 
532
                        apr_time_t expires;
 
533
 
 
534
                        len = sizeof(expires);
 
535
 
 
536
                        apr_file_read_full(fd, &expires, len, &len);
 
537
 
 
538
                        apr_file_close(fd);
 
539
 
 
540
                        if (expires < current) {
 
541
                            delete_entry(path, d->basename, p);
 
542
                        }
 
543
                        break;
 
544
                    }
 
545
                }
 
546
                apr_file_close(fd);
 
547
            }
 
548
 
 
549
            if (realclean || d->htime < current - deviation
 
550
                || d->htime > current + deviation) {
 
551
                delete_entry(path, d->basename, p);
 
552
                unsolicited += d->hsize;
 
553
            }
 
554
            break;
 
555
 
 
556
        case DATA:
 
557
            current = apr_time_now();
 
558
            if (realclean || d->dtime < current - deviation
 
559
                || d->dtime > current + deviation) {
 
560
                delete_entry(path, d->basename, p);
 
561
                unsolicited += d->dsize;
 
562
            }
 
563
            break;
 
564
 
 
565
        /* temp files may only be deleted in realclean mode which
 
566
         * is asserted above if a tempfile is in the hash array
 
567
         */
 
568
        case TEMP:
 
569
            delete_file(path, d->basename, p);
 
570
            unsolicited += d->dsize;
 
571
            break;
 
572
        }
 
573
    }
 
574
 
 
575
    if (interrupted) {
 
576
        return 1;
 
577
    }
 
578
 
 
579
    apr_pool_destroy(p);
 
580
 
 
581
    if (benice) {
 
582
        apr_sleep(NICE_DELAY);
 
583
    }
 
584
 
 
585
    if (interrupted) {
 
586
        return 1;
 
587
    }
 
588
 
 
589
    return 0;
 
590
}
 
591
 
 
592
/*
 
593
 * purge cache entries
 
594
 */
 
595
static void purge(char *path, apr_pool_t *pool, apr_off_t max)
 
596
{
 
597
    apr_off_t sum, total, entries, etotal;
 
598
    ENTRY *e, *n, *oldest;
 
599
 
 
600
    sum = 0;
 
601
    entries = 0;
 
602
 
 
603
    for (e = APR_RING_FIRST(&root);
 
604
         e != APR_RING_SENTINEL(&root, _entry, link);
 
605
         e = APR_RING_NEXT(e, link)) {
 
606
        sum += e->hsize;
 
607
        sum += e->dsize;
 
608
        entries++;
 
609
    }
 
610
 
 
611
    total = sum;
 
612
    etotal = entries;
 
613
 
 
614
    if (sum <= max) {
 
615
        printstats(total, sum, max, etotal, entries);
 
616
        return;
 
617
    }
 
618
 
 
619
    /* process all entries with a timestamp in the future, this may
 
620
     * happen if a wrong system time is corrected
 
621
     */
 
622
 
 
623
    for (e = APR_RING_FIRST(&root);
 
624
         e != APR_RING_SENTINEL(&root, _entry, link) && !interrupted;) {
 
625
        n = APR_RING_NEXT(e, link);
 
626
        if (e->response_time > now || e->htime > now || e->dtime > now) {
 
627
            delete_entry(path, e->basename, pool);
 
628
            sum -= e->hsize;
 
629
            sum -= e->dsize;
 
630
            entries--;
 
631
            APR_RING_REMOVE(e, link);
 
632
            if (sum <= max) {
 
633
                if (!interrupted) {
 
634
                    printstats(total, sum, max, etotal, entries);
 
635
                }
 
636
                return;
 
637
            }
 
638
        }
 
639
        e = n;
 
640
    }
 
641
 
 
642
    if (interrupted) {
 
643
        return;
 
644
    }
 
645
 
 
646
    /* process all entries with are expired */
 
647
    for (e = APR_RING_FIRST(&root);
 
648
         e != APR_RING_SENTINEL(&root, _entry, link) && !interrupted;) {
 
649
        n = APR_RING_NEXT(e, link);
 
650
        if (e->expire != APR_DATE_BAD && e->expire < now) {
 
651
            delete_entry(path, e->basename, pool);
 
652
            sum -= e->hsize;
 
653
            sum -= e->dsize;
 
654
            entries--;
 
655
            APR_RING_REMOVE(e, link);
 
656
            if (sum <= max) {
 
657
                if (!interrupted) {
 
658
                    printstats(total, sum, max, etotal, entries);
 
659
                }
 
660
                return;
 
661
            }
 
662
        }
 
663
        e = n;
 
664
    }
 
665
 
 
666
    if (interrupted) {
 
667
         return;
 
668
    }
 
669
 
 
670
    /* process remaining entries oldest to newest, the check for an emtpy
 
671
     * ring actually isn't necessary except when the compiler does
 
672
     * corrupt 64bit arithmetics which happend to me once, so better safe
 
673
     * than sorry
 
674
     */
 
675
    while (sum > max && !interrupted && !APR_RING_EMPTY(&root, _entry, link)) {
 
676
        oldest = APR_RING_FIRST(&root);
 
677
 
 
678
        for (e = APR_RING_NEXT(oldest, link);
 
679
             e != APR_RING_SENTINEL(&root, _entry, link);
 
680
             e = APR_RING_NEXT(e, link)) {
 
681
            if (e->dtime < oldest->dtime) {
 
682
                oldest = e;
 
683
            }
 
684
        }
 
685
 
 
686
        delete_entry(path, oldest->basename, pool);
 
687
        sum -= oldest->hsize;
 
688
        sum -= oldest->dsize;
 
689
        entries--;
 
690
        APR_RING_REMOVE(oldest, link);
 
691
    }
 
692
 
 
693
    if (!interrupted) {
 
694
        printstats(total, sum, max, etotal, entries);
 
695
    }
 
696
}
 
697
 
 
698
/*
 
699
 * usage info
 
700
 */
 
701
#define NL APR_EOL_STR
 
702
static void usage(void)
 
703
{
 
704
    apr_file_printf(errfile,
 
705
    "%s -- program for cleaning the disk cache."                             NL
 
706
    "Usage: %s [-Dvtrn] -pPATH -lLIMIT"                                      NL
 
707
    "       %s [-nti] -dINTERVAL -pPATH -lLIMIT"                             NL
 
708
                                                                             NL
 
709
    "Options:"                                                               NL
 
710
    "  -d   Daemonize and repeat cache cleaning every INTERVAL minutes."     NL
 
711
    "       This option is mutually exclusive with the -D, -v and -r"        NL
 
712
    "       options."                                                        NL
 
713
                                                                             NL
 
714
    "  -D   Do a dry run and don't delete anything. This option is mutually" NL
 
715
    "       exclusive with the -d option."                                   NL
 
716
                                                                             NL
 
717
    "  -v   Be verbose and print statistics. This option is mutually"        NL
 
718
    "       exclusive with the -d option."                                   NL
 
719
                                                                             NL
 
720
    "  -r   Clean thoroughly. This assumes that the Apache web server is "   NL
 
721
    "       not running. This option is mutually exclusive with the -d"      NL
 
722
    "       option and implies -t."                                          NL
 
723
                                                                             NL
 
724
    "  -n   Be nice. This causes slower processing in favour of other"       NL
 
725
    "       processes."                                                      NL
 
726
                                                                             NL
 
727
    "  -t   Delete all empty directories. By default only cache files are"   NL
 
728
    "       removed, however with some configurations the large number of"   NL
 
729
    "       directories created may require attention."                      NL
 
730
                                                                             NL
 
731
    "  -p   Specify PATH as the root directory of the disk cache."           NL
 
732
                                                                             NL
 
733
    "  -l   Specify LIMIT as the total disk cache size limit. Attach 'K'"    NL
 
734
    "       or 'M' to the number for specifying KBytes or MBytes."           NL
 
735
                                                                             NL
 
736
    "  -i   Be intelligent and run only when there was a modification of"    NL
 
737
    "       the disk cache. This option is only possible together with the"  NL
 
738
    "       -d option."                                                      NL,
 
739
    shortname,
 
740
    shortname,
 
741
    shortname
 
742
    );
 
743
 
 
744
    exit(1);
 
745
}
 
746
#undef NL
 
747
 
 
748
/*
 
749
 * main
 
750
 */
 
751
int main(int argc, const char * const argv[])
 
752
{
 
753
    apr_off_t max;
 
754
    apr_time_t current, repeat, delay, previous;
 
755
    apr_status_t status;
 
756
    apr_pool_t *pool, *instance;
 
757
    apr_getopt_t *o;
 
758
    apr_finfo_t info;
 
759
    int retries, isdaemon, limit_found, intelligent, dowork;
 
760
    char opt;
 
761
    const char *arg;
 
762
    char *proxypath, *path;
 
763
 
 
764
    interrupted = 0;
 
765
    repeat = 0;
 
766
    isdaemon = 0;
 
767
    dryrun = 0;
 
768
    limit_found = 0;
 
769
    max = 0;
 
770
    verbose = 0;
 
771
    realclean = 0;
 
772
    benice = 0;
 
773
    deldirs = 0;
 
774
    intelligent = 0;
 
775
    previous = 0; /* avoid compiler warning */
 
776
    proxypath = NULL;
 
777
 
 
778
    if (apr_app_initialize(&argc, &argv, NULL) != APR_SUCCESS) {
 
779
        return 1;
 
780
    }
 
781
    atexit(apr_terminate);
 
782
 
 
783
    if (argc) {
 
784
        shortname = apr_filepath_name_get(argv[0]);
 
785
    }
 
786
 
 
787
    if (apr_pool_create(&pool, NULL) != APR_SUCCESS) {
 
788
        return 1;
 
789
    }
 
790
    apr_pool_abort_set(oom, pool);
 
791
    apr_file_open_stderr(&errfile, pool);
 
792
    apr_signal(SIGINT, setterm);
 
793
    apr_signal(SIGTERM, setterm);
 
794
 
 
795
    apr_getopt_init(&o, pool, argc, argv);
 
796
 
 
797
    while (1) {
 
798
        status = apr_getopt(o, "iDnvrtd:l:L:p:", &opt, &arg);
 
799
        if (status == APR_EOF) {
 
800
            break;
 
801
        }
 
802
        else if (status != APR_SUCCESS) {
 
803
            usage();
 
804
        }
 
805
        else {
 
806
            switch (opt) {
 
807
            case 'i':
 
808
                if (intelligent) {
 
809
                    usage();
 
810
                }
 
811
                intelligent = 1;
 
812
                break;
 
813
 
 
814
            case 'D':
 
815
                if (dryrun) {
 
816
                    usage();
 
817
                }
 
818
                dryrun = 1;
 
819
                break;
 
820
 
 
821
            case 'n':
 
822
                if (benice) {
 
823
                    usage();
 
824
                }
 
825
                benice = 1;
 
826
                break;
 
827
 
 
828
            case 't':
 
829
                if (deldirs) {
 
830
                    usage();
 
831
                }
 
832
                deldirs = 1;
 
833
                break;
 
834
 
 
835
            case 'v':
 
836
                if (verbose) {
 
837
                    usage();
 
838
                }
 
839
                verbose = 1;
 
840
                break;
 
841
 
 
842
            case 'r':
 
843
                if (realclean) {
 
844
                    usage();
 
845
                }
 
846
                realclean = 1;
 
847
                deldirs = 1;
 
848
                break;
 
849
 
 
850
            case 'd':
 
851
                if (isdaemon) {
 
852
                    usage();
 
853
                }
 
854
                isdaemon = 1;
 
855
                repeat = apr_atoi64(arg);
 
856
                repeat *= SECS_PER_MIN;
 
857
                repeat *= APR_USEC_PER_SEC;
 
858
                break;
 
859
 
 
860
            case 'l':
 
861
                if (limit_found) {
 
862
                    usage();
 
863
                }
 
864
                limit_found = 1;
 
865
 
 
866
                do {
 
867
                    apr_status_t rv;
 
868
                    char *end;
 
869
 
 
870
                    rv = apr_strtoff(&max, arg, &end, 10);
 
871
                    if (rv == APR_SUCCESS) {
 
872
                        if ((*end == 'K' || *end == 'k') && !end[1]) {
 
873
                            max *= KBYTE;
 
874
                        }
 
875
                        else if ((*end == 'M' || *end == 'm') && !end[1]) {
 
876
                            max *= MBYTE;
 
877
                        }
 
878
                        else if ((*end == 'G' || *end == 'g') && !end[1]) {
 
879
                            max *= GBYTE;
 
880
                        }
 
881
                        else if (*end &&        /* neither empty nor [Bb] */
 
882
                                 ((*end != 'B' && *end != 'b') || end[1])) {
 
883
                            rv = APR_EGENERAL;
 
884
                        }
 
885
                    }
 
886
                    if (rv != APR_SUCCESS) {
 
887
                        apr_file_printf(errfile, "Invalid limit: %s"
 
888
                                                 APR_EOL_STR APR_EOL_STR, arg);
 
889
                        usage();
 
890
                    }
 
891
                } while(0);
 
892
                break;
 
893
 
 
894
            case 'p':
 
895
                if (proxypath) {
 
896
                    usage();
 
897
                }
 
898
                proxypath = apr_pstrdup(pool, arg);
 
899
                if (apr_filepath_set(proxypath, pool) != APR_SUCCESS) {
 
900
                    usage();
 
901
                }
 
902
                break;
 
903
            } /* switch */
 
904
        } /* else */
 
905
    } /* while */
 
906
 
 
907
    if (o->ind != argc) {
 
908
         usage();
 
909
    }
 
910
 
 
911
    if (isdaemon && (repeat <= 0 || verbose || realclean || dryrun)) {
 
912
         usage();
 
913
    }
 
914
 
 
915
    if (!isdaemon && intelligent) {
 
916
         usage();
 
917
    }
 
918
 
 
919
    if (!proxypath || max <= 0) {
 
920
         usage();
 
921
    }
 
922
 
 
923
    if (apr_filepath_get(&path, 0, pool) != APR_SUCCESS) {
 
924
        usage();
 
925
    }
 
926
    baselen = strlen(path);
 
927
 
 
928
#ifndef DEBUG
 
929
    if (isdaemon) {
 
930
        apr_file_close(errfile);
 
931
        apr_proc_detach(APR_PROC_DETACH_DAEMONIZE);
 
932
    }
 
933
#endif
 
934
 
 
935
    do {
 
936
        apr_pool_create(&instance, pool);
 
937
 
 
938
        now = apr_time_now();
 
939
        APR_RING_INIT(&root, _entry, link);
 
940
        delcount = 0;
 
941
        unsolicited = 0;
 
942
        dowork = 0;
 
943
 
 
944
        switch (intelligent) {
 
945
        case 0:
 
946
            dowork = 1;
 
947
            break;
 
948
 
 
949
        case 1:
 
950
            retries = STAT_ATTEMPTS;
 
951
            status = APR_SUCCESS;
 
952
 
 
953
            do {
 
954
                if (status != APR_SUCCESS) {
 
955
                    apr_sleep(STAT_DELAY);
 
956
                }
 
957
                status = apr_stat(&info, path, APR_FINFO_MTIME, instance);
 
958
            } while (status != APR_SUCCESS && !interrupted && --retries);
 
959
 
 
960
            if (status == APR_SUCCESS) {
 
961
                previous = info.mtime;
 
962
                intelligent = 2;
 
963
            }
 
964
            dowork = 1;
 
965
            break;
 
966
 
 
967
        case 2:
 
968
            retries = STAT_ATTEMPTS;
 
969
            status = APR_SUCCESS;
 
970
 
 
971
            do {
 
972
                if (status != APR_SUCCESS) {
 
973
                    apr_sleep(STAT_DELAY);
 
974
                }
 
975
                status = apr_stat(&info, path, APR_FINFO_MTIME, instance);
 
976
            } while (status != APR_SUCCESS && !interrupted && --retries);
 
977
 
 
978
            if (status == APR_SUCCESS) {
 
979
                if (previous != info.mtime) {
 
980
                    dowork = 1;
 
981
                }
 
982
                previous = info.mtime;
 
983
                break;
 
984
            }
 
985
            intelligent = 1;
 
986
            dowork = 1;
 
987
            break;
 
988
        }
 
989
 
 
990
        if (dowork && !interrupted) {
 
991
            if (!process_dir(path, instance) && !interrupted) {
 
992
                purge(path, instance, max);
 
993
            }
 
994
            else if (!isdaemon && !interrupted) {
 
995
                apr_file_printf(errfile, "An error occurred, cache cleaning "
 
996
                                         "aborted." APR_EOL_STR);
 
997
                return 1;
 
998
            }
 
999
 
 
1000
            if (intelligent && !interrupted) {
 
1001
                retries = STAT_ATTEMPTS;
 
1002
                status = APR_SUCCESS;
 
1003
                do {
 
1004
                    if (status != APR_SUCCESS) {
 
1005
                        apr_sleep(STAT_DELAY);
 
1006
                    }
 
1007
                    status = apr_stat(&info, path, APR_FINFO_MTIME, instance);
 
1008
                } while (status != APR_SUCCESS && !interrupted && --retries);
 
1009
 
 
1010
                if (status == APR_SUCCESS) {
 
1011
                    previous = info.mtime;
 
1012
                    intelligent = 2;
 
1013
                }
 
1014
                else {
 
1015
                    intelligent = 1;
 
1016
                }
 
1017
            }
 
1018
        }
 
1019
 
 
1020
        apr_pool_destroy(instance);
 
1021
 
 
1022
        current = apr_time_now();
 
1023
        if (current < now) {
 
1024
            delay = repeat;
 
1025
        }
 
1026
        else if (current - now >= repeat) {
 
1027
            delay = repeat;
 
1028
        }
 
1029
        else {
 
1030
            delay = now + repeat - current;
 
1031
        }
 
1032
 
 
1033
        /* we can't sleep the whole delay time here apiece as this is racy
 
1034
         * with respect to interrupt delivery - think about what happens
 
1035
         * if we have tested for an interrupt, then get scheduled
 
1036
         * before the apr_sleep() call and while waiting for the cpu
 
1037
         * we do get an interrupt
 
1038
         */
 
1039
        if (isdaemon) {
 
1040
            while (delay && !interrupted) {
 
1041
                if (delay > APR_USEC_PER_SEC) {
 
1042
                    apr_sleep(APR_USEC_PER_SEC);
 
1043
                    delay -= APR_USEC_PER_SEC;
 
1044
                }
 
1045
                else {
 
1046
                    apr_sleep(delay);
 
1047
                    delay = 0;
 
1048
                }
 
1049
            }
 
1050
        }
 
1051
    } while (isdaemon && !interrupted);
 
1052
 
 
1053
    if (!isdaemon && interrupted) {
 
1054
        apr_file_printf(errfile, "Cache cleaning aborted due to user "
 
1055
                                 "request." APR_EOL_STR);
 
1056
        return 1;
 
1057
    }
 
1058
 
 
1059
    return 0;
 
1060
}