]> granicus.if.org Git - apache/blob - support/htcacheclean.c
72983ce5584d49655bb3220c6982297b2d9de676
[apache] / support / htcacheclean.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 /*
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_md5.h"
36 #include "apr_ring.h"
37 #include "apr_date.h"
38 #include "apr_buckets.h"
39 #include "../modules/cache/mod_disk_cache.h"
40
41 #if APR_HAVE_UNISTD_H
42 #include <unistd.h>
43 #endif
44 #if APR_HAVE_STDLIB_H
45 #include <stdlib.h>
46 #endif
47
48 /* define the following for debugging */
49 #undef DEBUG
50
51 /*
52  * Note: on Linux delays <= 2ms are busy waits without
53  *       scheduling, so never use a delay <= 2ms below
54  */
55
56 #define NICE_DELAY    10000     /* usecs */
57 #define DELETE_NICE   10        /* be nice after this amount of delete ops */
58 #define STAT_ATTEMPTS 10        /* maximum stat attempts for a file */
59 #define STAT_DELAY    5000      /* usecs */
60 #define HEADER        1         /* headers file */
61 #define DATA          2         /* body file */
62 #define TEMP          4         /* temporary file */
63 #define HEADERDATA    (HEADER|DATA)
64 #define MAXDEVIATION  3600      /* secs */
65 #define SECS_PER_MIN  60
66 #define KBYTE         1024
67 #define MBYTE         1048576
68 #define GBYTE         1073741824
69
70 #define DIRINFO (APR_FINFO_MTIME|APR_FINFO_SIZE|APR_FINFO_TYPE|APR_FINFO_LINK)
71
72 typedef struct _direntry {
73     APR_RING_ENTRY(_direntry) link;
74     int type;         /* type of file/fileset: TEMP, HEADER, DATA, HEADERDATA */
75     apr_time_t htime; /* headers file modification time */
76     apr_time_t dtime; /* body file modification time */
77     apr_off_t hsize;  /* headers file size */
78     apr_off_t dsize;  /* body or temporary file size */
79     char *basename;   /* file/fileset base name */
80 } DIRENTRY;
81
82 typedef struct _entry {
83     APR_RING_ENTRY(_entry) link;
84     apr_time_t expire;        /* cache entry exiration time */
85     apr_time_t response_time; /* cache entry time of last response to client */
86     apr_time_t htime;         /* headers file modification time */
87     apr_time_t dtime;         /* body file modification time */
88     apr_off_t hsize;          /* headers file size */
89     apr_off_t dsize;          /* body or temporary file size */
90     char *basename;           /* fileset base name */
91 } ENTRY;
92
93
94 static int delcount;    /* file deletion count for nice mode */
95 static int interrupted; /* flag: true if SIGINT or SIGTERM occurred */
96 static int realclean;   /* flag: true means user said apache is not running */
97 static int verbose;     /* flag: true means print statistics */
98 static int benice;      /* flag: true means nice mode is activated */
99 static int dryrun;      /* flag: true means dry run, don't actually delete
100                                  anything */
101 static int deldirs;     /* flag: true means directories should be deleted */
102 static int baselen;     /* string length of the path to the proxy directory */
103 static apr_time_t now;  /* start time of this processing run */
104
105 static apr_file_t *errfile;   /* stderr file handle */
106 static apr_off_t unsolicited; /* file size summary for deleted unsolicited
107                                  files */
108 static APR_RING_ENTRY(_entry) root; /* ENTRY ring anchor */
109
110 /* short program name as called */
111 static const char *shortname = "htcacheclean";
112
113 /* what did we clean? */
114 struct stats {
115     apr_off_t total;
116     apr_off_t sum;
117     apr_off_t max;
118     apr_off_t etotal;
119     apr_off_t entries;
120     apr_off_t dfuture;
121     apr_off_t dexpired;
122     apr_off_t dfresh;
123 };
124
125
126 #ifdef DEBUG
127 /*
128  * fake delete for debug purposes
129  */
130 #define apr_file_remove fake_file_remove
131 static void fake_file_remove(char *pathname, apr_pool_t *p)
132 {
133     apr_finfo_t info;
134
135     /* stat and printing to simulate some deletion system load and to
136        display what would actually have happened */
137     apr_stat(&info, pathname, DIRINFO, p);
138     apr_file_printf(errfile, "would delete %s" APR_EOL_STR, pathname);
139 }
140 #endif
141
142 /*
143  * called on SIGINT or SIGTERM
144  */
145 static void setterm(int unused)
146 {
147 #ifdef DEBUG
148     apr_file_printf(errfile, "interrupt" APR_EOL_STR);
149 #endif
150     interrupted = 1;
151 }
152
153 /*
154  * called in out of memory condition
155  */
156 static int oom(int unused)
157 {
158     static int called = 0;
159
160     /* be careful to call exit() only once */
161     if (!called) {
162         called = 1;
163         exit(1);
164     }
165     return APR_ENOMEM;
166 }
167
168 /*
169  * print purge statistics
170  */
171 static void printstats(char *path, struct stats *s)
172 {
173     char ttype, stype, mtype, utype;
174     apr_off_t tfrag, sfrag, ufrag;
175
176     if (!verbose) {
177         return;
178     }
179
180     ttype = 'K';
181     tfrag = ((s->total * 10) / KBYTE) % 10;
182     s->total /= KBYTE;
183     if (s->total >= KBYTE) {
184         ttype = 'M';
185         tfrag = ((s->total * 10) / KBYTE) % 10;
186         s->total /= KBYTE;
187     }
188
189     stype = 'K';
190     sfrag = ((s->sum * 10) / KBYTE) % 10;
191     s->sum /= KBYTE;
192     if (s->sum >= KBYTE) {
193         stype = 'M';
194         sfrag = ((s->sum * 10) / KBYTE) % 10;
195         s->sum /= KBYTE;
196     }
197
198     mtype = 'K';
199     s->max /= KBYTE;
200     if (s->max >= KBYTE) {
201         mtype = 'M';
202         s->max /= KBYTE;
203     }
204
205     apr_file_printf(errfile, "Cleaned %s. Statistics:" APR_EOL_STR, path);
206     if (unsolicited) {
207         utype = 'K';
208         ufrag = ((unsolicited * 10) / KBYTE) % 10;
209         unsolicited /= KBYTE;
210         if (unsolicited >= KBYTE) {
211             utype = 'M';
212             ufrag = ((unsolicited * 10) / KBYTE) % 10;
213             unsolicited /= KBYTE;
214         }
215         if (!unsolicited && !ufrag) {
216             ufrag = 1;
217         }
218         apr_file_printf(errfile, "unsolicited size %d.%d%c" APR_EOL_STR,
219                         (int)(unsolicited), (int)(ufrag), utype);
220      }
221      apr_file_printf(errfile, "size limit %d.0%c" APR_EOL_STR,
222                      (int)(s->max), mtype);
223      apr_file_printf(errfile, "total size was %d.%d%c, total size now "
224                               "%d.%d%c" APR_EOL_STR,
225                      (int)(s->total), (int)(tfrag), ttype,
226                      (int)(s->sum), (int)(sfrag), stype);
227      apr_file_printf(errfile, "total entries was %d, total entries now %d"
228                               APR_EOL_STR, (int)(s->etotal),
229                               (int)(s->entries));
230      apr_file_printf(errfile, "%d entries deleted (%d from future, %d "
231                               "expired, %d fresh)" APR_EOL_STR,
232                      (int)(s->etotal - s->entries), (int)(s->dfuture),
233                      (int)(s->dexpired), (int)(s->dfresh));
234 }
235
236 /*
237  * delete a single file
238  */
239 static void delete_file(char *path, char *basename, apr_pool_t *pool)
240 {
241     char *nextpath;
242     apr_pool_t *p;
243
244     if (dryrun) {
245         return;
246     }
247
248     /* temp pool, otherwise lots of memory could be allocated */
249     apr_pool_create(&p, pool);
250     nextpath = apr_pstrcat(p, path, "/", basename, NULL);
251     apr_file_remove(nextpath, p);
252     apr_pool_destroy(p);
253
254     if (benice) {
255         if (++delcount >= DELETE_NICE) {
256             apr_sleep(NICE_DELAY);
257             delcount = 0;
258         }
259     }
260 }
261
262 /*
263  * delete cache file set
264  */
265 static void delete_entry(char *path, char *basename, apr_pool_t *pool)
266 {
267     char *nextpath;
268     apr_pool_t *p;
269
270     if (dryrun) {
271         return;
272     }
273
274     /* temp pool, otherwise lots of memory could be allocated */
275     apr_pool_create(&p, pool);
276
277     nextpath = apr_pstrcat(p, path, "/", basename, CACHE_HEADER_SUFFIX, NULL);
278     apr_file_remove(nextpath, p);
279
280     nextpath = apr_pstrcat(p, path, "/", basename, CACHE_DATA_SUFFIX, NULL);
281     apr_file_remove(nextpath, p);
282
283     apr_pool_destroy(p);
284
285     if (benice) {
286         delcount += 2;
287         if (delcount >= DELETE_NICE) {
288             apr_sleep(NICE_DELAY);
289             delcount = 0;
290         }
291     }
292 }
293
294 /*
295  * walk the cache directory tree
296  */
297 static int process_dir(char *path, apr_pool_t *pool)
298 {
299     apr_dir_t *dir;
300     apr_pool_t *p;
301     apr_hash_t *h;
302     apr_hash_index_t *i;
303     apr_file_t *fd;
304     apr_status_t status;
305     apr_finfo_t info;
306     apr_size_t len;
307     apr_time_t current, deviation;
308     char *nextpath, *base, *ext, *orig_basename;
309     APR_RING_ENTRY(_direntry) anchor;
310     DIRENTRY *d, *t, *n;
311     ENTRY *e;
312     int skip, retries;
313     disk_cache_info_t disk_info;
314
315     APR_RING_INIT(&anchor, _direntry, link);
316     apr_pool_create(&p, pool);
317     h = apr_hash_make(p);
318     fd = NULL;
319     skip = 0;
320     deviation = MAXDEVIATION * APR_USEC_PER_SEC;
321
322     if (apr_dir_open(&dir, path, p) != APR_SUCCESS) {
323         return 1;
324     }
325
326     while (apr_dir_read(&info, 0, dir) == APR_SUCCESS && !interrupted) {
327         if (!strcmp(info.name, ".") || !strcmp(info.name, "..")) {
328             continue;
329         }
330         d = apr_pcalloc(p, sizeof(DIRENTRY));
331         d->basename = apr_pstrcat(p, path, "/", info.name, NULL);
332         APR_RING_INSERT_TAIL(&anchor, d, _direntry, link);
333     }
334
335     apr_dir_close(dir);
336
337     if (interrupted) {
338         return 1;
339     }
340
341     skip = baselen + 1;
342
343     for (d = APR_RING_FIRST(&anchor);
344          !interrupted && d != APR_RING_SENTINEL(&anchor, _direntry, link);
345          d=n) {
346         n = APR_RING_NEXT(d, link);
347         base = strrchr(d->basename, '/');
348         if (!base++) {
349             base = d->basename;
350         }
351         ext = strchr(base, '.');
352
353         /* there may be temporary files which may be gone before
354          * processing, always skip these if not in realclean mode
355          */
356         if (!ext && !realclean) {
357             if (!strncasecmp(base, AP_TEMPFILE_BASE, AP_TEMPFILE_BASELEN)
358                 && strlen(base) == AP_TEMPFILE_NAMELEN) {
359                 continue;
360             }
361         }
362
363         /* this may look strange but apr_stat() may return an error which
364          * is system dependent and there may be transient failures,
365          * so just blindly retry for a short while
366          */
367         retries = STAT_ATTEMPTS;
368         status = APR_SUCCESS;
369         do {
370             if (status != APR_SUCCESS) {
371                 apr_sleep(STAT_DELAY);
372             }
373             status = apr_stat(&info, d->basename, DIRINFO, p);
374         } while (status != APR_SUCCESS && !interrupted && --retries);
375
376         /* what may happen here is that apache did create a file which
377          * we did detect but then does delete the file before we can
378          * get file information, so if we don't get any file information
379          * we will ignore the file in this case
380          */
381         if (status != APR_SUCCESS) {
382             if (!realclean && !interrupted) {
383                 continue;
384             }
385             return 1;
386         }
387
388         if (info.filetype == APR_DIR) {
389             /* Make a copy of the basename, as process_dir modifies it */
390             orig_basename = apr_pstrdup(pool, d->basename);
391             if (process_dir(d->basename, pool)) {
392                 return 1;
393             }
394
395             /* If asked to delete dirs, do so now. We don't care if it fails.
396              * If it fails, it likely means there was something else there.
397              */
398             if (deldirs && !dryrun) {
399                 apr_dir_remove(orig_basename, pool);
400             }
401             continue;
402         }
403
404         if (info.filetype != APR_REG) {
405             continue;
406         }
407
408         if (!ext) {
409             if (!strncasecmp(base, AP_TEMPFILE_BASE, AP_TEMPFILE_BASELEN)
410                 && strlen(base) == AP_TEMPFILE_NAMELEN) {
411                 d->basename += skip;
412                 d->type = TEMP;
413                 d->dsize = info.size;
414                 apr_hash_set(h, d->basename, APR_HASH_KEY_STRING, d);
415             }
416             continue;
417         }
418
419         if (!strcasecmp(ext, CACHE_HEADER_SUFFIX)) {
420             *ext = '\0';
421             d->basename += skip;
422             /* if a user manually creates a '.header' file */
423             if (d->basename[0] == '\0') {
424                 continue;
425             }
426             t = apr_hash_get(h, d->basename, APR_HASH_KEY_STRING);
427             if (t) {
428                 d = t;
429             }
430             d->type |= HEADER;
431             d->htime = info.mtime;
432             d->hsize = info.size;
433             apr_hash_set(h, d->basename, APR_HASH_KEY_STRING, d);
434             continue;
435         }
436
437         if (!strcasecmp(ext, CACHE_DATA_SUFFIX)) {
438             *ext = '\0';
439             d->basename += skip;
440             /* if a user manually creates a '.data' file */
441             if (d->basename[0] == '\0') {
442                 continue;
443             }
444             t = apr_hash_get(h, d->basename, APR_HASH_KEY_STRING);
445             if (t) {
446                 d = t;
447             }
448             d->type |= DATA;
449             d->dtime = info.mtime;
450             d->dsize = info.size;
451             apr_hash_set(h, d->basename, APR_HASH_KEY_STRING, d);
452         }
453     }
454
455     if (interrupted) {
456         return 1;
457     }
458
459     path[baselen] = '\0';
460
461     for (i = apr_hash_first(p, h); i && !interrupted; i = apr_hash_next(i)) {
462         void *hvalue;
463         apr_uint32_t format;
464
465         apr_hash_this(i, NULL, NULL, &hvalue);
466         d = hvalue;
467
468         switch(d->type) {
469         case HEADERDATA:
470             nextpath = apr_pstrcat(p, path, "/", d->basename,
471                                    CACHE_HEADER_SUFFIX, NULL);
472             if (apr_file_open(&fd, nextpath, APR_FOPEN_READ | APR_FOPEN_BINARY,
473                               APR_OS_DEFAULT, p) == APR_SUCCESS) {
474                 len = sizeof(format);
475                 if (apr_file_read_full(fd, &format, len,
476                                        &len) == APR_SUCCESS) {
477                     if (format == DISK_FORMAT_VERSION) {
478                         apr_off_t offset = 0;
479
480                         apr_file_seek(fd, APR_SET, &offset);
481
482                         len = sizeof(disk_cache_info_t);
483
484                         if (apr_file_read_full(fd, &disk_info, len,
485                                                &len) == APR_SUCCESS) {
486                             apr_file_close(fd);
487                             e = apr_palloc(pool, sizeof(ENTRY));
488                             APR_RING_INSERT_TAIL(&root, e, _entry, link);
489                             e->expire = disk_info.expire;
490                             e->response_time = disk_info.response_time;
491                             e->htime = d->htime;
492                             e->dtime = d->dtime;
493                             e->hsize = d->hsize;
494                             e->dsize = d->dsize;
495                             e->basename = apr_pstrdup(pool, d->basename);
496                             break;
497                         }
498                         else {
499                             apr_file_close(fd);
500                         }
501                     }
502                     else if (format == VARY_FORMAT_VERSION) {
503                         /* This must be a URL that added Vary headers later,
504                          * so kill the orphaned .data file
505                          */
506                         apr_file_close(fd);
507                         apr_file_remove(apr_pstrcat(p, path, "/", d->basename,
508                                                     CACHE_DATA_SUFFIX, NULL),
509                                         p);
510                         break;
511                     }
512                 }
513                 else {
514                     apr_file_close(fd);
515                 }
516
517             }
518             /* we have a somehow unreadable headers file which is associated
519              * with a data file. this may be caused by apache currently
520              * rewriting the headers file. thus we may delete the file set
521              * either in realclean mode or if the headers file modification
522              * timestamp is not within a specified positive or negative offset
523              * to the current time.
524              */
525             current = apr_time_now();
526             if (realclean || d->htime < current - deviation
527                 || d->htime > current + deviation) {
528                 delete_entry(path, d->basename, p);
529                 unsolicited += d->hsize;
530                 unsolicited += d->dsize;
531             }
532             break;
533
534         /* single data and header files may be deleted either in realclean
535          * mode or if their modification timestamp is not within a
536          * specified positive or negative offset to the current time.
537          * this handling is necessary due to possible race conditions
538          * between apache and this process
539          */
540         case HEADER:
541             current = apr_time_now();
542             nextpath = apr_pstrcat(p, path, "/", d->basename,
543                                    CACHE_HEADER_SUFFIX, NULL);
544             if (apr_file_open(&fd, nextpath, APR_FOPEN_READ | APR_FOPEN_BINARY,
545                               APR_OS_DEFAULT, p) == APR_SUCCESS) {
546                 len = sizeof(format);
547                 if (apr_file_read_full(fd, &format, len,
548                                        &len) == APR_SUCCESS) {
549                     if (format == VARY_FORMAT_VERSION) {
550                         apr_time_t expires;
551
552                         len = sizeof(expires);
553
554                         if (apr_file_read_full(fd, &expires, len,
555                                                &len) == APR_SUCCESS) {
556
557                             apr_file_close(fd);
558
559                             if (expires < current) {
560                                 delete_entry(path, d->basename, p);
561                             }
562                             break;
563                         }
564                     }
565                 }
566                 apr_file_close(fd);
567             }
568
569             if (realclean || d->htime < current - deviation
570                 || d->htime > current + deviation) {
571                 delete_entry(path, d->basename, p);
572                 unsolicited += d->hsize;
573             }
574             break;
575
576         case DATA:
577             current = apr_time_now();
578             if (realclean || d->dtime < current - deviation
579                 || d->dtime > current + deviation) {
580                 delete_entry(path, d->basename, p);
581                 unsolicited += d->dsize;
582             }
583             break;
584
585         /* temp files may only be deleted in realclean mode which
586          * is asserted above if a tempfile is in the hash array
587          */
588         case TEMP:
589             delete_file(path, d->basename, p);
590             unsolicited += d->dsize;
591             break;
592         }
593     }
594
595     if (interrupted) {
596         return 1;
597     }
598
599     apr_pool_destroy(p);
600
601     if (benice) {
602         apr_sleep(NICE_DELAY);
603     }
604
605     if (interrupted) {
606         return 1;
607     }
608
609     return 0;
610 }
611
612 /*
613  * purge cache entries
614  */
615 static void purge(char *path, apr_pool_t *pool, apr_off_t max)
616 {
617     ENTRY *e, *n, *oldest;
618
619     struct stats s;
620     s.sum = 0;
621     s.entries = 0;
622     s.dfuture = 0;
623     s.dexpired = 0;
624     s.dfresh = 0;
625     s.max = max;
626
627     for (e = APR_RING_FIRST(&root);
628          e != APR_RING_SENTINEL(&root, _entry, link);
629          e = APR_RING_NEXT(e, link)) {
630         s.sum += e->hsize;
631         s.sum += e->dsize;
632         s.entries++;
633     }
634
635     s.total = s.sum;
636     s.etotal = s.entries;
637
638     if (s.sum <= s.max) {
639         printstats(path, &s);
640         return;
641     }
642
643     /* process all entries with a timestamp in the future, this may
644      * happen if a wrong system time is corrected
645      */
646
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->response_time > now || e->htime > now || e->dtime > now) {
651             delete_entry(path, e->basename, pool);
652             s.sum -= e->hsize;
653             s.sum -= e->dsize;
654             s.entries--;
655             s.dfuture++;
656             APR_RING_REMOVE(e, link);
657             if (s.sum <= s.max) {
658                 if (!interrupted) {
659                     printstats(path, &s);
660                 }
661                 return;
662             }
663         }
664         e = n;
665     }
666
667     if (interrupted) {
668         return;
669     }
670
671     /* process all entries with are expired */
672     for (e = APR_RING_FIRST(&root);
673          e != APR_RING_SENTINEL(&root, _entry, link) && !interrupted;) {
674         n = APR_RING_NEXT(e, link);
675         if (e->expire != APR_DATE_BAD && e->expire < now) {
676             delete_entry(path, e->basename, pool);
677             s.sum -= e->hsize;
678             s.sum -= e->dsize;
679             s.entries--;
680             s.dexpired++;
681             APR_RING_REMOVE(e, link);
682             if (s.sum <= s.max) {
683                 if (!interrupted) {
684                     printstats(path, &s);
685                 }
686                 return;
687             }
688         }
689         e = n;
690     }
691
692     if (interrupted) {
693          return;
694     }
695
696     /* process remaining entries oldest to newest, the check for an emtpy
697      * ring actually isn't necessary except when the compiler does
698      * corrupt 64bit arithmetics which happend to me once, so better safe
699      * than sorry
700      */
701     while (s.sum > s.max && !interrupted
702            && !APR_RING_EMPTY(&root, _entry, link)) {
703         oldest = APR_RING_FIRST(&root);
704
705         for (e = APR_RING_NEXT(oldest, link);
706              e != APR_RING_SENTINEL(&root, _entry, link);
707              e = APR_RING_NEXT(e, link)) {
708             if (e->dtime < oldest->dtime) {
709                 oldest = e;
710             }
711         }
712
713         delete_entry(path, oldest->basename, pool);
714         s.sum -= oldest->hsize;
715         s.sum -= oldest->dsize;
716         s.entries--;
717         s.dfresh++;
718         APR_RING_REMOVE(oldest, link);
719     }
720
721     if (!interrupted) {
722         printstats(path, &s);
723     }
724 }
725
726 static apr_status_t remove_directory(apr_pool_t *pool, const char *dir)
727 {
728     apr_status_t rv;
729     apr_dir_t *dirp;
730     apr_finfo_t dirent;
731
732     rv = apr_dir_open(&dirp, dir, pool);
733     if (rv == APR_ENOENT) {
734         return rv;
735     }
736     if (rv != APR_SUCCESS) {
737         char errmsg[120];
738         apr_file_printf(errfile, "Could not open directory %s: %s" APR_EOL_STR,
739                 dir, apr_strerror(rv, errmsg, sizeof errmsg));
740         return rv;
741     }
742
743     while (apr_dir_read(&dirent, APR_FINFO_DIRENT | APR_FINFO_TYPE, dirp)
744             == APR_SUCCESS) {
745         if (dirent.filetype == APR_DIR) {
746             if (strcmp(dirent.name, ".") && strcmp(dirent.name, "..")) {
747                 rv = remove_directory(pool, apr_pstrcat(pool, dir, "/",
748                         dirent.name, NULL));
749                 /* tolerate the directory not being empty, the cache may have
750                  * attempted to recreate the directory in the mean time.
751                  */
752                 if (APR_SUCCESS != rv && APR_ENOTEMPTY != rv) {
753                     break;
754                 }
755             }
756         } else {
757             const char *file = apr_pstrcat(pool, dir, "/", dirent.name, NULL);
758             rv = apr_file_remove(file, pool);
759             if (APR_SUCCESS != rv) {
760                 char errmsg[120];
761                 apr_file_printf(errfile,
762                         "Could not remove file '%s': %s" APR_EOL_STR, file,
763                         apr_strerror(rv, errmsg, sizeof errmsg));
764                 break;
765             }
766         }
767     }
768
769     apr_dir_close(dirp);
770
771     if (rv == APR_SUCCESS) {
772         rv = apr_dir_remove(dir, pool);
773         if (APR_ENOTEMPTY == rv) {
774             rv = APR_SUCCESS;
775         }
776         if (rv != APR_SUCCESS) {
777             char errmsg[120];
778             apr_file_printf(errfile, "Could not remove directory %s: %s" APR_EOL_STR,
779                     dir, apr_strerror(rv, errmsg, sizeof errmsg));
780         }
781     }
782
783     return rv;
784 }
785
786 static apr_status_t find_directory(apr_pool_t *pool, const char *base,
787         const char *rest)
788 {
789     apr_status_t rv;
790     apr_dir_t *dirp;
791     apr_finfo_t dirent;
792     int found = 0, files = 0;
793     const char *header = apr_pstrcat(pool, rest, CACHE_HEADER_SUFFIX, NULL);
794     const char *data = apr_pstrcat(pool, rest, CACHE_DATA_SUFFIX, NULL);
795     const char *vdir = apr_pstrcat(pool, rest, CACHE_HEADER_SUFFIX,
796             CACHE_VDIR_SUFFIX, NULL);
797     const char *dirname = NULL;
798
799     rv = apr_dir_open(&dirp, base, pool);
800     if (rv != APR_SUCCESS) {
801         char errmsg[120];
802         apr_file_printf(errfile, "Could not open directory %s: %s" APR_EOL_STR,
803                 base, apr_strerror(rv, errmsg, sizeof errmsg));
804         return rv;
805     }
806
807     rv = APR_ENOENT;
808
809     while (apr_dir_read(&dirent, APR_FINFO_DIRENT | APR_FINFO_TYPE, dirp)
810             == APR_SUCCESS) {
811         int len = strlen(dirent.name);
812         int restlen = strlen(rest);
813         if (dirent.filetype == APR_DIR && !strncmp(rest, dirent.name, len)) {
814             dirname = apr_pstrcat(pool, base, "/", dirent.name, NULL);
815             rv = find_directory(pool, dirname, rest + (len < restlen ? len
816                     : restlen));
817             if (APR_SUCCESS == rv) {
818                 found = 1;
819             }
820         }
821         if (dirent.filetype == APR_DIR) {
822             if (!strcmp(dirent.name, vdir)) {
823                 files = 1;
824             }
825         }
826         if (dirent.filetype == APR_REG) {
827             if (!strcmp(dirent.name, header) || !strcmp(dirent.name, data)) {
828                 files = 1;
829             }
830         }
831     }
832
833     apr_dir_close(dirp);
834
835     if (files) {
836         rv = APR_SUCCESS;
837         if (!dryrun) {
838             const char *remove;
839             apr_status_t status;
840
841             remove = apr_pstrcat(pool, base, "/", header, NULL);
842             status = apr_file_remove(remove, pool);
843             if (status != APR_SUCCESS && status != APR_ENOENT) {
844                 char errmsg[120];
845                 apr_file_printf(errfile, "Could not remove file %s: %s" APR_EOL_STR,
846                         remove, apr_strerror(status, errmsg, sizeof errmsg));
847                 rv = status;
848             }
849
850             remove = apr_pstrcat(pool, base, "/", data, NULL);
851             status = apr_file_remove(remove, pool);
852             if (status != APR_SUCCESS && status != APR_ENOENT) {
853                 char errmsg[120];
854                 apr_file_printf(errfile, "Could not remove file %s: %s" APR_EOL_STR,
855                         remove, apr_strerror(status, errmsg, sizeof errmsg));
856                 rv = status;
857             }
858
859             status = remove_directory(pool, apr_pstrcat(pool, base, "/", vdir, NULL));
860             if (status != APR_SUCCESS && status != APR_ENOENT) {
861                 rv = status;
862             }
863         }
864     }
865
866     /* If asked to delete dirs, do so now. We don't care if it fails.
867      * If it fails, it likely means there was something else there.
868      */
869     if (dirname && deldirs && !dryrun) {
870         apr_dir_remove(dirname, pool);
871     }
872
873     if (found) {
874         return APR_SUCCESS;
875     }
876
877     return rv;
878 }
879
880 /**
881  * Delete a specific URL from the cache.
882  */
883 static apr_status_t delete_url(apr_pool_t *pool, const char *proxypath, const char *url)
884 {
885     apr_md5_ctx_t context;
886     unsigned char digest[16];
887     char tmp[23];
888     int i, k;
889     unsigned int x;
890     static const char enc_table[64] =
891             "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_@";
892
893     apr_md5_init(&context);
894     apr_md5_update(&context, (const unsigned char *) url, strlen(url));
895     apr_md5_final(digest, &context);
896
897     /* encode 128 bits as 22 characters, using a modified uuencoding
898      * the encoding is 3 bytes -> 4 characters* i.e. 128 bits is
899      * 5 x 3 bytes + 1 byte -> 5 * 4 characters + 2 characters
900      */
901     for (i = 0, k = 0; i < 15; i += 3) {
902         x = (digest[i] << 16) | (digest[i + 1] << 8) | digest[i + 2];
903         tmp[k++] = enc_table[x >> 18];
904         tmp[k++] = enc_table[(x >> 12) & 0x3f];
905         tmp[k++] = enc_table[(x >> 6) & 0x3f];
906         tmp[k++] = enc_table[x & 0x3f];
907     }
908
909     /* one byte left */
910     x = digest[15];
911     tmp[k++] = enc_table[x >> 2]; /* use up 6 bits */
912     tmp[k++] = enc_table[(x << 4) & 0x3f];
913     tmp[k] = 0;
914
915     /* automatically find the directory levels */
916     return find_directory(pool, proxypath, tmp);
917 }
918
919 /*
920  * usage info
921  */
922 #define NL APR_EOL_STR
923 static void usage(const char *error)
924 {
925     if (error) {
926         apr_file_printf(errfile, "%s error: %s\n", shortname, error);
927     }
928     apr_file_printf(errfile,
929     "%s -- program for cleaning the disk cache."                             NL
930     "Usage: %s [-Dvtrn] -pPATH -lLIMIT [-PPIDFILE]"                          NL
931     "       %s [-nti] -dINTERVAL -pPATH -lLIMIT [-PPIDFILE]"                 NL
932     "       %s [-Dvt] -pPATH URL ..."                                        NL
933                                                                              NL
934     "Options:"                                                               NL
935     "  -d   Daemonize and repeat cache cleaning every INTERVAL minutes."     NL
936     "       This option is mutually exclusive with the -D, -v and -r"        NL
937     "       options."                                                        NL
938                                                                              NL
939     "  -D   Do a dry run and don't delete anything. This option is mutually" NL
940     "       exclusive with the -d option."                                   NL
941                                                                              NL
942     "  -v   Be verbose and print statistics. This option is mutually"        NL
943     "       exclusive with the -d option."                                   NL
944                                                                              NL
945     "  -r   Clean thoroughly. This assumes that the Apache web server is "   NL
946     "       not running. This option is mutually exclusive with the -d"      NL
947     "       option and implies -t."                                          NL
948                                                                              NL
949     "  -n   Be nice. This causes slower processing in favour of other"       NL
950     "       processes."                                                      NL
951                                                                              NL
952     "  -t   Delete all empty directories. By default only cache files are"   NL
953     "       removed, however with some configurations the large number of"   NL
954     "       directories created may require attention."                      NL
955                                                                              NL
956     "  -p   Specify PATH as the root directory of the disk cache."           NL
957                                                                              NL
958     "  -P   Specify PIDFILE as the file to write the pid to."                NL
959                                                                              NL
960     "  -l   Specify LIMIT as the total disk cache size limit. Attach 'K'"    NL
961     "       or 'M' to the number for specifying KBytes or MBytes."           NL
962                                                                              NL
963     "  -i   Be intelligent and run only when there was a modification of"    NL
964     "       the disk cache. This option is only possible together with the"  NL
965     "       -d option."                                                      NL
966                                                                              NL
967     "Should an URL be provided on the command line, the URL will be"         NL
968     "deleted from the cache. A reverse proxied URL is made up as follows:"   NL
969     "http://<hostname>:<port><path>?[query]. So, for the path \"/\" on the"  NL
970     "host \"localhost\" and port 80, the URL to delete becomes"              NL
971     "\"http://localhost:80/?\". Note the '?' in the URL must always be"      NL
972     "specified explicitly, whether a query string is present or not."        NL,
973     shortname,
974     shortname,
975     shortname,
976     shortname
977     );
978
979     exit(1);
980 }
981 #undef NL
982
983 static void usage_repeated_arg(apr_pool_t *pool, char option) {
984     usage(apr_psprintf(pool, 
985                        "The option '%c' cannot be specified more than once",
986                        option));
987 }
988
989 static void log_pid(apr_pool_t *pool, const char *pidfilename, apr_file_t **pidfile)
990 {
991     apr_status_t status;
992     char errmsg[120];
993     pid_t mypid = getpid();
994
995     if (APR_SUCCESS == (status = apr_file_open(pidfile, pidfilename,
996                 APR_FOPEN_WRITE | APR_FOPEN_CREATE | APR_FOPEN_TRUNCATE |
997                 APR_FOPEN_DELONCLOSE, APR_FPROT_UREAD | APR_FPROT_UWRITE |
998                 APR_FPROT_GREAD | APR_FPROT_WREAD, pool))) {
999         apr_file_printf(*pidfile, "%" APR_PID_T_FMT APR_EOL_STR, mypid);
1000     }
1001     else {
1002         if (errfile) {
1003             apr_file_printf(errfile,
1004                             "Could not write the pid file '%s': %s" APR_EOL_STR,
1005                             pidfilename, 
1006                             apr_strerror(status, errmsg, sizeof errmsg));
1007         }
1008         exit(1);
1009     }
1010 }
1011
1012 /*
1013  * main
1014  */
1015 int main(int argc, const char * const argv[])
1016 {
1017     apr_off_t max;
1018     apr_time_t current, repeat, delay, previous;
1019     apr_status_t status;
1020     apr_pool_t *pool, *instance;
1021     apr_getopt_t *o;
1022     apr_finfo_t info;
1023     apr_file_t *pidfile;
1024     int retries, isdaemon, limit_found, intelligent, dowork;
1025     char opt;
1026     const char *arg;
1027     char *proxypath, *path, *pidfilename;
1028     char errmsg[1024];
1029
1030     interrupted = 0;
1031     repeat = 0;
1032     isdaemon = 0;
1033     dryrun = 0;
1034     limit_found = 0;
1035     max = 0;
1036     verbose = 0;
1037     realclean = 0;
1038     benice = 0;
1039     deldirs = 0;
1040     intelligent = 0;
1041     previous = 0; /* avoid compiler warning */
1042     proxypath = NULL;
1043     pidfilename = NULL;
1044
1045     if (apr_app_initialize(&argc, &argv, NULL) != APR_SUCCESS) {
1046         return 1;
1047     }
1048     atexit(apr_terminate);
1049
1050     if (argc) {
1051         shortname = apr_filepath_name_get(argv[0]);
1052     }
1053
1054     if (apr_pool_create(&pool, NULL) != APR_SUCCESS) {
1055         return 1;
1056     }
1057     apr_pool_abort_set(oom, pool);
1058     apr_file_open_stderr(&errfile, pool);
1059     apr_signal(SIGINT, setterm);
1060     apr_signal(SIGTERM, setterm);
1061
1062     apr_getopt_init(&o, pool, argc, argv);
1063
1064     while (1) {
1065         status = apr_getopt(o, "iDnvrtd:l:L:p:P:", &opt, &arg);
1066         if (status == APR_EOF) {
1067             break;
1068         }
1069         else if (status != APR_SUCCESS) {
1070             usage(NULL);
1071         }
1072         else {
1073             switch (opt) {
1074             case 'i':
1075                 if (intelligent) {
1076                     usage_repeated_arg(pool, opt);
1077                 }
1078                 intelligent = 1;
1079                 break;
1080
1081             case 'D':
1082                 if (dryrun) {
1083                     usage_repeated_arg(pool, opt);
1084                 }
1085                 dryrun = 1;
1086                 break;
1087
1088             case 'n':
1089                 if (benice) {
1090                     usage_repeated_arg(pool, opt);
1091                 }
1092                 benice = 1;
1093                 break;
1094
1095             case 't':
1096                 if (deldirs) {
1097                     usage_repeated_arg(pool, opt);
1098                 }
1099                 deldirs = 1;
1100                 break;
1101
1102             case 'v':
1103                 if (verbose) {
1104                     usage_repeated_arg(pool, opt);
1105                 }
1106                 verbose = 1;
1107                 break;
1108
1109             case 'r':
1110                 if (realclean) {
1111                     usage_repeated_arg(pool, opt);
1112                 }
1113                 realclean = 1;
1114                 deldirs = 1;
1115                 break;
1116
1117             case 'd':
1118                 if (isdaemon) {
1119                     usage_repeated_arg(pool, opt);
1120                 }
1121                 isdaemon = 1;
1122                 repeat = apr_atoi64(arg);
1123                 repeat *= SECS_PER_MIN;
1124                 repeat *= APR_USEC_PER_SEC;
1125                 break;
1126
1127             case 'l':
1128                 if (limit_found) {
1129                     usage_repeated_arg(pool, opt);
1130                 }
1131                 limit_found = 1;
1132
1133                 do {
1134                     apr_status_t rv;
1135                     char *end;
1136
1137                     rv = apr_strtoff(&max, arg, &end, 10);
1138                     if (rv == APR_SUCCESS) {
1139                         if ((*end == 'K' || *end == 'k') && !end[1]) {
1140                             max *= KBYTE;
1141                         }
1142                         else if ((*end == 'M' || *end == 'm') && !end[1]) {
1143                             max *= MBYTE;
1144                         }
1145                         else if ((*end == 'G' || *end == 'g') && !end[1]) {
1146                             max *= GBYTE;
1147                         }
1148                         else if (*end &&        /* neither empty nor [Bb] */
1149                                  ((*end != 'B' && *end != 'b') || end[1])) {
1150                             rv = APR_EGENERAL;
1151                         }
1152                     }
1153                     if (rv != APR_SUCCESS) {
1154                         usage(apr_psprintf(pool, "Invalid limit: %s"
1155                                                  APR_EOL_STR APR_EOL_STR, arg));
1156                     }
1157                 } while(0);
1158                 break;
1159
1160             case 'p':
1161                 if (proxypath) {
1162                     usage_repeated_arg(pool, opt);
1163                 }
1164                 proxypath = apr_pstrdup(pool, arg);
1165                 if ((status = apr_filepath_set(proxypath, pool)) != APR_SUCCESS) {
1166                     usage(apr_psprintf(pool, "Could not set filepath to '%s': %s",
1167                                        proxypath, apr_strerror(status, errmsg, sizeof errmsg)));
1168                 }
1169                 break;
1170
1171             case 'P':
1172                 if (pidfilename) {
1173                     usage_repeated_arg(pool, opt);
1174                 }
1175                 pidfilename = apr_pstrdup(pool, arg);
1176                 break;
1177
1178             } /* switch */
1179         } /* else */
1180     } /* while */
1181
1182     if (argc <= 1) {
1183         usage(NULL);
1184     }
1185
1186     if (o->ind < argc) {
1187         int deleted = 0;
1188         int error = 0;
1189         if (isdaemon) {
1190             usage("Option -d cannot be used with URL arguments, aborting");
1191         }
1192         if (intelligent) {
1193             usage("Option -i cannot be used with URL arguments, aborting");
1194         }
1195         if (limit_found) {
1196             usage("Option -l cannot be used with URL arguments, aborting");
1197         }
1198         while (o->ind < argc) {
1199             status = delete_url(pool, proxypath, argv[o->ind]);
1200             if (APR_SUCCESS == status) {
1201                 if (verbose) {
1202                     apr_file_printf(errfile, "Removed: %s" APR_EOL_STR,
1203                             argv[o->ind]);
1204                 }
1205                 deleted = 1;
1206             }
1207             else if (APR_ENOENT == status) {
1208                 if (verbose) {
1209                     apr_file_printf(errfile, "Not cached: %s" APR_EOL_STR,
1210                             argv[o->ind]);
1211                 }
1212             }
1213             else {
1214                 if (verbose) {
1215                     apr_file_printf(errfile, "Error while removed: %s" APR_EOL_STR,
1216                             argv[o->ind]);
1217                 }
1218                 error = 1;
1219             }
1220             o->ind++;
1221         }
1222         return error ? 1 : deleted ? 0 : 2;
1223     }
1224
1225     if (isdaemon && repeat <= 0) {
1226          usage("Option -d must be greater than zero");
1227     }
1228
1229     if (isdaemon && (verbose || realclean || dryrun)) {
1230          usage("Option -d cannot be used with -v, -r or -D");
1231     }
1232
1233     if (!isdaemon && intelligent) {
1234          usage("Option -i cannot be used without -d");
1235     }
1236
1237     if (!proxypath) {
1238          usage("Option -p must be specified");
1239     }
1240
1241     if (max <= 0) {
1242          usage("Option -l must be greater than zero");
1243     }
1244
1245     if (apr_filepath_get(&path, 0, pool) != APR_SUCCESS) {
1246         usage(apr_psprintf(pool, "Could not get the filepath: %s",
1247                            apr_strerror(status, errmsg, sizeof errmsg)));
1248     }
1249     baselen = strlen(path);
1250
1251     if (pidfilename) {
1252         log_pid(pool, pidfilename, &pidfile); /* before daemonizing, so we
1253                                                * can report errors
1254                                                */
1255     }
1256
1257 #ifndef DEBUG
1258     if (isdaemon) {
1259         apr_file_close(errfile);
1260         errfile = NULL;
1261         if (pidfilename) {
1262             apr_file_close(pidfile); /* delete original pidfile only in parent */
1263         }
1264         apr_proc_detach(APR_PROC_DETACH_DAEMONIZE);
1265         if (pidfilename) {
1266             log_pid(pool, pidfilename, &pidfile);
1267         }
1268     }
1269 #endif
1270
1271     do {
1272         apr_pool_create(&instance, pool);
1273
1274         now = apr_time_now();
1275         APR_RING_INIT(&root, _entry, link);
1276         delcount = 0;
1277         unsolicited = 0;
1278         dowork = 0;
1279
1280         switch (intelligent) {
1281         case 0:
1282             dowork = 1;
1283             break;
1284
1285         case 1:
1286             retries = STAT_ATTEMPTS;
1287             status = APR_SUCCESS;
1288
1289             do {
1290                 if (status != APR_SUCCESS) {
1291                     apr_sleep(STAT_DELAY);
1292                 }
1293                 status = apr_stat(&info, path, APR_FINFO_MTIME, instance);
1294             } while (status != APR_SUCCESS && !interrupted && --retries);
1295
1296             if (status == APR_SUCCESS) {
1297                 previous = info.mtime;
1298                 intelligent = 2;
1299             }
1300             dowork = 1;
1301             break;
1302
1303         case 2:
1304             retries = STAT_ATTEMPTS;
1305             status = APR_SUCCESS;
1306
1307             do {
1308                 if (status != APR_SUCCESS) {
1309                     apr_sleep(STAT_DELAY);
1310                 }
1311                 status = apr_stat(&info, path, APR_FINFO_MTIME, instance);
1312             } while (status != APR_SUCCESS && !interrupted && --retries);
1313
1314             if (status == APR_SUCCESS) {
1315                 if (previous != info.mtime) {
1316                     dowork = 1;
1317                 }
1318                 previous = info.mtime;
1319                 break;
1320             }
1321             intelligent = 1;
1322             dowork = 1;
1323             break;
1324         }
1325
1326         if (dowork && !interrupted) {
1327             if (!process_dir(path, instance) && !interrupted) {
1328                 purge(path, instance, max);
1329             }
1330             else if (!isdaemon && !interrupted) {
1331                 apr_file_printf(errfile, "An error occurred, cache cleaning "
1332                                          "aborted." APR_EOL_STR);
1333                 return 1;
1334             }
1335
1336             if (intelligent && !interrupted) {
1337                 retries = STAT_ATTEMPTS;
1338                 status = APR_SUCCESS;
1339                 do {
1340                     if (status != APR_SUCCESS) {
1341                         apr_sleep(STAT_DELAY);
1342                     }
1343                     status = apr_stat(&info, path, APR_FINFO_MTIME, instance);
1344                 } while (status != APR_SUCCESS && !interrupted && --retries);
1345
1346                 if (status == APR_SUCCESS) {
1347                     previous = info.mtime;
1348                     intelligent = 2;
1349                 }
1350                 else {
1351                     intelligent = 1;
1352                 }
1353             }
1354         }
1355
1356         apr_pool_destroy(instance);
1357
1358         current = apr_time_now();
1359         if (current < now) {
1360             delay = repeat;
1361         }
1362         else if (current - now >= repeat) {
1363             delay = repeat;
1364         }
1365         else {
1366             delay = now + repeat - current;
1367         }
1368
1369         /* we can't sleep the whole delay time here apiece as this is racy
1370          * with respect to interrupt delivery - think about what happens
1371          * if we have tested for an interrupt, then get scheduled
1372          * before the apr_sleep() call and while waiting for the cpu
1373          * we do get an interrupt
1374          */
1375         if (isdaemon) {
1376             while (delay && !interrupted) {
1377                 if (delay > APR_USEC_PER_SEC) {
1378                     apr_sleep(APR_USEC_PER_SEC);
1379                     delay -= APR_USEC_PER_SEC;
1380                 }
1381                 else {
1382                     apr_sleep(delay);
1383                     delay = 0;
1384                 }
1385             }
1386         }
1387     } while (isdaemon && !interrupted);
1388
1389     if (!isdaemon && interrupted) {
1390         apr_file_printf(errfile, "Cache cleaning aborted due to user "
1391                                  "request." APR_EOL_STR);
1392         return 1;
1393     }
1394
1395     return 0;
1396 }