]> granicus.if.org Git - apache/blob - support/htcacheclean.c
More splitting as suggested by minfrin.
[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
40 #include "../modules/cache/cache_common.h"
41 #include "../modules/cache/disk_cache_common.h"
42
43 #if APR_HAVE_UNISTD_H
44 #include <unistd.h>
45 #endif
46 #if APR_HAVE_STDLIB_H
47 #include <stdlib.h>
48 #endif
49
50 #ifdef AP_DEBUG
51 /* XXX: Maybe we should link util_debug into htcacheclean? */
52 #undef strchr
53 #undef strrchr
54 #undef strstr
55 #endif
56
57 /* define the following for debugging */
58 #undef DEBUG
59
60 /*
61  * Note: on Linux delays <= 2ms are busy waits without
62  *       scheduling, so never use a delay <= 2ms below
63  */
64
65 #define NICE_DELAY    10000     /* usecs */
66 #define DELETE_NICE   10        /* be nice after this amount of delete ops */
67 #define STAT_ATTEMPTS 10        /* maximum stat attempts for a file */
68 #define STAT_DELAY    5000      /* usecs */
69 #define HEADER        1         /* headers file */
70 #define DATA          2         /* body file */
71 #define TEMP          4         /* temporary file */
72 #define HEADERDATA    (HEADER|DATA)
73 #define MAXDEVIATION  3600      /* secs */
74 #define SECS_PER_MIN  60
75 #define KBYTE         1024
76 #define MBYTE         1048576
77 #define GBYTE         1073741824
78
79 #define DIRINFO (APR_FINFO_MTIME|APR_FINFO_SIZE|APR_FINFO_TYPE|APR_FINFO_LINK)
80
81 typedef struct _direntry {
82     APR_RING_ENTRY(_direntry) link;
83     int type;         /* type of file/fileset: TEMP, HEADER, DATA, HEADERDATA */
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;   /* file/fileset base name */
89 } DIRENTRY;
90
91 typedef struct _entry {
92     APR_RING_ENTRY(_entry) link;
93     apr_time_t expire;        /* cache entry exiration time */
94     apr_time_t response_time; /* cache entry time of last response to client */
95     apr_time_t htime;         /* headers file modification time */
96     apr_time_t dtime;         /* body file modification time */
97     apr_off_t hsize;          /* headers file size */
98     apr_off_t dsize;          /* body or temporary file size */
99     char *basename;           /* fileset base name */
100 } ENTRY;
101
102
103 static int delcount;    /* file deletion count for nice mode */
104 static int interrupted; /* flag: true if SIGINT or SIGTERM occurred */
105 static int realclean;   /* flag: true means user said apache is not running */
106 static int verbose;     /* flag: true means print statistics */
107 static int benice;      /* flag: true means nice mode is activated */
108 static int dryrun;      /* flag: true means dry run, don't actually delete
109                                  anything */
110 static int deldirs;     /* flag: true means directories should be deleted */
111 static int listurls;    /* flag: true means list cached urls */
112 static int listextended;/* flag: true means list cached urls */
113 static int baselen;     /* string length of the path to the proxy directory */
114 static apr_time_t now;  /* start time of this processing run */
115
116 static apr_file_t *errfile;   /* stderr file handle */
117 static apr_file_t *outfile;   /* stdout file handle */
118 static apr_off_t unsolicited; /* file size summary for deleted unsolicited
119                                  files */
120 static APR_RING_ENTRY(_entry) root; /* ENTRY ring anchor */
121
122 /* short program name as called */
123 static const char *shortname = "htcacheclean";
124
125 /* what did we clean? */
126 struct stats {
127     apr_off_t total;
128     apr_off_t sum;
129     apr_off_t max;
130     apr_off_t ntotal;
131     apr_off_t nodes;
132     apr_off_t inodes;
133     apr_off_t etotal;
134     apr_off_t entries;
135     apr_off_t dfuture;
136     apr_off_t dexpired;
137     apr_off_t dfresh;
138 };
139
140
141 #ifdef DEBUG
142 /*
143  * fake delete for debug purposes
144  */
145 #define apr_file_remove fake_file_remove
146 static void fake_file_remove(char *pathname, apr_pool_t *p)
147 {
148     apr_finfo_t info;
149
150     /* stat and printing to simulate some deletion system load and to
151        display what would actually have happened */
152     apr_stat(&info, pathname, DIRINFO, p);
153     apr_file_printf(errfile, "would delete %s" APR_EOL_STR, pathname);
154 }
155 #endif
156
157 /*
158  * called on SIGINT or SIGTERM
159  */
160 static void setterm(int unused)
161 {
162 #ifdef DEBUG
163     apr_file_printf(errfile, "interrupt" APR_EOL_STR);
164 #endif
165     interrupted = 1;
166 }
167
168 /*
169  * called in out of memory condition
170  */
171 static int oom(int unused)
172 {
173     static int called = 0;
174
175     /* be careful to call exit() only once */
176     if (!called) {
177         called = 1;
178         exit(1);
179     }
180     return APR_ENOMEM;
181 }
182
183 /*
184  * print purge statistics
185  */
186 static void printstats(char *path, struct stats *s)
187 {
188     char ttype, stype, mtype, utype;
189     apr_off_t tfrag, sfrag, ufrag;
190
191     if (!verbose) {
192         return;
193     }
194
195     ttype = 'K';
196     tfrag = ((s->total * 10) / KBYTE) % 10;
197     s->total /= KBYTE;
198     if (s->total >= KBYTE) {
199         ttype = 'M';
200         tfrag = ((s->total * 10) / KBYTE) % 10;
201         s->total /= KBYTE;
202     }
203
204     stype = 'K';
205     sfrag = ((s->sum * 10) / KBYTE) % 10;
206     s->sum /= KBYTE;
207     if (s->sum >= KBYTE) {
208         stype = 'M';
209         sfrag = ((s->sum * 10) / KBYTE) % 10;
210         s->sum /= KBYTE;
211     }
212
213     mtype = 'K';
214     s->max /= KBYTE;
215     if (s->max >= KBYTE) {
216         mtype = 'M';
217         s->max /= KBYTE;
218     }
219
220     apr_file_printf(errfile, "Cleaned %s. Statistics:" APR_EOL_STR, path);
221     if (unsolicited) {
222         utype = 'K';
223         ufrag = ((unsolicited * 10) / KBYTE) % 10;
224         unsolicited /= KBYTE;
225         if (unsolicited >= KBYTE) {
226             utype = 'M';
227             ufrag = ((unsolicited * 10) / KBYTE) % 10;
228             unsolicited /= KBYTE;
229         }
230         if (!unsolicited && !ufrag) {
231             ufrag = 1;
232         }
233         apr_file_printf(errfile, "unsolicited size %d.%d%c" APR_EOL_STR,
234                         (int)(unsolicited), (int)(ufrag), utype);
235     }
236     apr_file_printf(errfile, "size limit %" APR_OFF_T_FMT ".0%c" APR_EOL_STR,
237             s->max, mtype);
238     apr_file_printf(errfile, "inodes limit %" APR_OFF_T_FMT APR_EOL_STR,
239             s->inodes);
240     apr_file_printf(
241             errfile,
242             "total size was %" APR_OFF_T_FMT ".%" APR_OFF_T_FMT "%c, total size now "
243             "%" APR_OFF_T_FMT ".%" APR_OFF_T_FMT "%c" APR_EOL_STR, s->total,
244             tfrag, ttype, s->sum, sfrag, stype);
245     apr_file_printf(errfile, "total inodes was %" APR_OFF_T_FMT
246             ", total %sinodes now "
247             "%" APR_OFF_T_FMT APR_EOL_STR, s->ntotal, dryrun && deldirs ? "estimated "
248             : "", s->nodes);
249     apr_file_printf(
250             errfile,
251             "total entries was %" APR_OFF_T_FMT ", total entries now %" APR_OFF_T_FMT
252             APR_EOL_STR, s->etotal, s->entries);
253     apr_file_printf(
254             errfile,
255             "%" APR_OFF_T_FMT " entries deleted (%" APR_OFF_T_FMT " from future, %"
256             APR_OFF_T_FMT " expired, %" APR_OFF_T_FMT " fresh)" APR_EOL_STR,
257             (s->etotal - s->entries), s->dfuture, s->dexpired, s->dfresh);
258 }
259
260 /**
261  * Round the value up to the given threshold.
262  */
263 static apr_size_t round_up(apr_size_t val, apr_off_t round) {
264     if (round > 1) {
265         return ((val + round - 1) / round) * round;
266     }
267     return val;
268 }
269
270 /*
271  * delete parent directories
272  */
273 static void delete_parent(const char *path, const char *basename,
274         apr_off_t *nodes, apr_pool_t *pool)
275 {
276     char *nextpath, *name;
277     apr_pool_t *p;
278
279     /* temp pool, otherwise lots of memory could be allocated */
280     apr_pool_create(&p, pool);
281     name = apr_pstrdup(p, basename);
282
283     /* If asked to delete dirs, do so now. We don't care if it fails.
284      * If it fails, it likely means there was something else there.
285      */
286     if (deldirs && !dryrun) {
287         const char *vary;
288         char *end = strrchr(name, '/');
289         while (end) {
290             *end = 0;
291
292             /* remove the directory */
293             nextpath = apr_pstrcat(p, path, "/", name, NULL);
294             if (!apr_dir_remove(nextpath, p)) {
295                 (*nodes)--;
296
297                 /* vary directory found? */
298                 vary = strstr(name, CACHE_VDIR_SUFFIX);
299                 if (vary && !vary[sizeof(CACHE_VDIR_SUFFIX) - 1]) {
300                     nextpath = apr_pstrcat(p, path, "/", apr_pstrndup(p, name, vary
301                             - name), NULL);
302                     if (!apr_file_remove(nextpath, p)) {
303                         (*nodes)--;
304                     }
305                 }
306
307             }
308             else {
309                 break;
310             }
311             end = strrchr(name, '/');
312         }
313     }
314
315     apr_pool_destroy(p);
316
317     if (benice) {
318         if (++delcount >= DELETE_NICE) {
319             apr_sleep(NICE_DELAY);
320             delcount = 0;
321         }
322     }
323
324 }
325
326 /*
327  * delete a single file
328  */
329 static void delete_file(char *path, char *basename, apr_off_t *nodes,
330         apr_pool_t *pool)
331 {
332     char *nextpath;
333     apr_pool_t *p;
334
335     /* temp pool, otherwise lots of memory could be allocated */
336     apr_pool_create(&p, pool);
337     nextpath = apr_pstrcat(p, path, "/", basename, NULL);
338
339     if (dryrun) {
340         apr_finfo_t finfo;
341         if (!apr_stat(&finfo, nextpath, APR_FINFO_NLINK, p)) {
342             (*nodes)--;
343         }
344     }
345     else if (!apr_file_remove(nextpath, p)) {
346         (*nodes)--;
347     }
348
349     apr_pool_destroy(p);
350
351     if (benice) {
352         if (++delcount >= DELETE_NICE) {
353             apr_sleep(NICE_DELAY);
354             delcount = 0;
355         }
356     }
357
358     delete_parent(path, basename, nodes, pool);
359
360 }
361
362 /*
363  * delete cache file set
364  */
365 static void delete_entry(char *path, char *basename, apr_off_t *nodes,
366         apr_pool_t *pool)
367 {
368     char *nextpath;
369     apr_pool_t *p;
370
371     /* temp pool, otherwise lots of memory could be allocated */
372     apr_pool_create(&p, pool);
373
374     nextpath = apr_pstrcat(p, path, "/", basename, CACHE_HEADER_SUFFIX, NULL);
375     if (dryrun) {
376         apr_finfo_t finfo;
377         if (!apr_stat(&finfo, nextpath, APR_FINFO_NLINK, p)) {
378             (*nodes)--;
379         }
380     }
381     else if (!apr_file_remove(nextpath, p)) {
382         (*nodes)--;
383     }
384
385     nextpath = apr_pstrcat(p, path, "/", basename, CACHE_DATA_SUFFIX, NULL);
386     if (dryrun) {
387         apr_finfo_t finfo;
388         if (!apr_stat(&finfo, nextpath, APR_FINFO_NLINK, p)) {
389             (*nodes)--;
390         }
391     }
392     else if (!apr_file_remove(nextpath, p)) {
393         (*nodes)--;
394     }
395
396     apr_pool_destroy(p);
397
398     if (benice) {
399         delcount += 2;
400         if (delcount >= DELETE_NICE) {
401             apr_sleep(NICE_DELAY);
402             delcount = 0;
403         }
404     }
405
406     delete_parent(path, basename, nodes, pool);
407
408 }
409
410 /*
411  * list the cache directory tree
412  */
413 static int list_urls(char *path, apr_pool_t *pool, apr_off_t round)
414 {
415     apr_dir_t *dir;
416     apr_finfo_t info;
417     apr_size_t len;
418     apr_pool_t *p;
419     apr_file_t *fd;
420     const char *ext, *nextpath;
421     char *url;
422     apr_uint32_t format;
423     disk_cache_info_t disk_info;
424
425     apr_pool_create(&p, pool);
426
427     if (apr_dir_open(&dir, path, p) != APR_SUCCESS) {
428         return 1;
429     }
430
431     while (apr_dir_read(&info, 0, dir) == APR_SUCCESS && !interrupted) {
432
433         if (info.filetype == APR_DIR) {
434             if (!strcmp(info.name, ".") || !strcmp(info.name, "..")) {
435                 continue;
436             }
437
438             if (list_urls(apr_pstrcat(p, path, "/", info.name, NULL), pool, round)) {
439                 return 1;
440             }
441         }
442
443         else if (info.filetype == APR_REG) {
444
445             ext = strchr(info.name, '.');
446
447             if (ext && !strcasecmp(ext, CACHE_HEADER_SUFFIX)) {
448
449                 nextpath = apr_pstrcat(p, path, "/", info.name, NULL);
450
451                 if (apr_file_open(&fd, nextpath, APR_FOPEN_READ
452                         | APR_FOPEN_BINARY, APR_OS_DEFAULT, p) == APR_SUCCESS) {
453                     len = sizeof(format);
454                     if (apr_file_read_full(fd, &format, len, &len)
455                             == APR_SUCCESS) {
456                         if (format == DISK_FORMAT_VERSION) {
457                             apr_off_t offset = 0;
458
459                             apr_file_seek(fd, APR_SET, &offset);
460
461                             len = sizeof(disk_cache_info_t);
462
463                             if (apr_file_read_full(fd, &disk_info, len, &len)
464                                     == APR_SUCCESS) {
465                                 len = disk_info.name_len;
466                                 url = apr_palloc(p, len + 1);
467                                 url[len] = 0;
468
469                                 if (apr_file_read_full(fd, url, len, &len)
470                                         == APR_SUCCESS) {
471
472                                     if (listextended) {
473                                         apr_finfo_t hinfo, dinfo;
474
475                                         /* stat the header file */
476                                         if (APR_SUCCESS != apr_file_info_get(
477                                                 &hinfo, APR_FINFO_SIZE, fd)) {
478                                             /* ignore the file */
479                                         }
480                                         else if (disk_info.has_body && APR_SUCCESS
481                                                 != apr_stat(
482                                                         &dinfo,
483                                                         apr_pstrcat(
484                                                                 p,
485                                                                 path,
486                                                                 "/",
487                                                                 apr_pstrndup(
488                                                                         p,
489                                                                         info.name,
490                                                                         ext
491                                                                                 - info.name),
492                                                                 CACHE_DATA_SUFFIX,
493                                                                 NULL),
494                                                         APR_FINFO_SIZE
495                                                                 | APR_FINFO_IDENT,
496                                                         p)) {
497                                             /* ignore the file */
498                                         }
499                                         else if (disk_info.has_body && (dinfo.device
500                                                 != disk_info.device
501                                                 || dinfo.inode
502                                                         != disk_info.inode)) {
503                                             /* ignore the file */
504                                         }
505                                         else {
506
507                                             apr_file_printf(
508                                                     outfile,
509                                                     "%s %" APR_SIZE_T_FMT
510                                                     " %" APR_SIZE_T_FMT
511                                                     " %d %" APR_SIZE_T_FMT
512                                                     " %" APR_TIME_T_FMT
513                                                     " %" APR_TIME_T_FMT
514                                                     " %" APR_TIME_T_FMT
515                                                     " %" APR_TIME_T_FMT
516                                                     " %d %d\n",
517                                                     url,
518                                                     round_up(hinfo.size, round),
519                                                     round_up(
520                                                             disk_info.has_body ? dinfo.size
521                                                                     : 0, round),
522                                                     disk_info.status,
523                                                     disk_info.entity_version,
524                                                     disk_info.date,
525                                                     disk_info.expire,
526                                                     disk_info.request_time,
527                                                     disk_info.response_time,
528                                                     disk_info.has_body,
529                                                     disk_info.header_only);
530                                         }
531                                     }
532                                     else {
533                                         apr_finfo_t dinfo;
534
535                                         /* stat the data file */
536                                         if (disk_info.has_body && APR_SUCCESS
537                                                 != apr_stat(
538                                                         &dinfo,
539                                                         apr_pstrcat(
540                                                                 p,
541                                                                 path,
542                                                                 "/",
543                                                                 apr_pstrndup(
544                                                                         p,
545                                                                         info.name,
546                                                                         ext
547                                                                                 - info.name),
548                                                                 CACHE_DATA_SUFFIX,
549                                                                 NULL),
550                                                         APR_FINFO_SIZE
551                                                                 | APR_FINFO_IDENT,
552                                                         p)) {
553                                             /* ignore the file */
554                                         }
555                                         else if (disk_info.has_body && (dinfo.device
556                                                 != disk_info.device
557                                                 || dinfo.inode
558                                                         != disk_info.inode)) {
559                                             /* ignore the file */
560                                         }
561                                         else {
562                                             apr_file_printf(outfile, "%s\n",
563                                                     url);
564                                         }
565                                     }
566                                 }
567
568                                 break;
569                             }
570                         }
571                     }
572                     apr_file_close(fd);
573
574                 }
575             }
576         }
577
578     }
579
580     apr_dir_close(dir);
581
582     if (interrupted) {
583         return 1;
584     }
585
586     apr_pool_destroy(p);
587
588     if (benice) {
589         apr_sleep(NICE_DELAY);
590     }
591
592     if (interrupted) {
593         return 1;
594     }
595
596     return 0;
597 }
598
599 /*
600  * walk the cache directory tree
601  */
602 static int process_dir(char *path, apr_pool_t *pool, apr_off_t *nodes)
603 {
604     apr_dir_t *dir;
605     apr_pool_t *p;
606     apr_hash_t *h;
607     apr_hash_index_t *i;
608     apr_file_t *fd;
609     apr_status_t status;
610     apr_finfo_t info;
611     apr_size_t len;
612     apr_time_t current, deviation;
613     char *nextpath, *base, *ext;
614     APR_RING_ENTRY(_direntry) anchor;
615     DIRENTRY *d, *t, *n;
616     ENTRY *e;
617     int skip, retries;
618     disk_cache_info_t disk_info;
619
620     APR_RING_INIT(&anchor, _direntry, link);
621     apr_pool_create(&p, pool);
622     h = apr_hash_make(p);
623     fd = NULL;
624     skip = 0;
625     deviation = MAXDEVIATION * APR_USEC_PER_SEC;
626
627     if (apr_dir_open(&dir, path, p) != APR_SUCCESS) {
628         return 1;
629     }
630
631     while (apr_dir_read(&info, 0, dir) == APR_SUCCESS && !interrupted) {
632         if (!strcmp(info.name, ".") || !strcmp(info.name, "..")) {
633             continue;
634         }
635         d = apr_pcalloc(p, sizeof(DIRENTRY));
636         d->basename = apr_pstrcat(p, path, "/", info.name, NULL);
637         APR_RING_INSERT_TAIL(&anchor, d, _direntry, link);
638         (*nodes)++;
639     }
640
641     apr_dir_close(dir);
642
643     if (interrupted) {
644         return 1;
645     }
646
647     skip = baselen + 1;
648
649     for (d = APR_RING_FIRST(&anchor);
650          !interrupted && d != APR_RING_SENTINEL(&anchor, _direntry, link);
651          d=n) {
652         n = APR_RING_NEXT(d, link);
653         base = strrchr(d->basename, '/');
654         if (!base++) {
655             base = d->basename;
656         }
657         ext = strchr(base, '.');
658
659         /* there may be temporary files which may be gone before
660          * processing, always skip these if not in realclean mode
661          */
662         if (!ext && !realclean) {
663             if (!strncasecmp(base, AP_TEMPFILE_BASE, AP_TEMPFILE_BASELEN)
664                 && strlen(base) == AP_TEMPFILE_NAMELEN) {
665                 continue;
666             }
667         }
668
669         /* this may look strange but apr_stat() may return an error which
670          * is system dependent and there may be transient failures,
671          * so just blindly retry for a short while
672          */
673         retries = STAT_ATTEMPTS;
674         status = APR_SUCCESS;
675         do {
676             if (status != APR_SUCCESS) {
677                 apr_sleep(STAT_DELAY);
678             }
679             status = apr_stat(&info, d->basename, DIRINFO, p);
680         } while (status != APR_SUCCESS && !interrupted && --retries);
681
682         /* what may happen here is that apache did create a file which
683          * we did detect but then does delete the file before we can
684          * get file information, so if we don't get any file information
685          * we will ignore the file in this case
686          */
687         if (status != APR_SUCCESS) {
688             if (!realclean && !interrupted) {
689                 continue;
690             }
691             return 1;
692         }
693
694         if (info.filetype == APR_DIR) {
695             if (process_dir(d->basename, pool, nodes)) {
696                 return 1;
697             }
698             continue;
699         }
700
701         if (info.filetype != APR_REG) {
702             continue;
703         }
704
705         if (!ext) {
706             if (!strncasecmp(base, AP_TEMPFILE_BASE, AP_TEMPFILE_BASELEN)
707                 && strlen(base) == AP_TEMPFILE_NAMELEN) {
708                 d->basename += skip;
709                 d->type = TEMP;
710                 d->dsize = info.size;
711                 apr_hash_set(h, d->basename, APR_HASH_KEY_STRING, d);
712             }
713             continue;
714         }
715
716         if (!strcasecmp(ext, CACHE_HEADER_SUFFIX)) {
717             *ext = '\0';
718             d->basename += skip;
719             /* if a user manually creates a '.header' file */
720             if (d->basename[0] == '\0') {
721                 continue;
722             }
723             t = apr_hash_get(h, d->basename, APR_HASH_KEY_STRING);
724             if (t) {
725                 d = t;
726             }
727             d->type |= HEADER;
728             d->htime = info.mtime;
729             d->hsize = info.size;
730             apr_hash_set(h, d->basename, APR_HASH_KEY_STRING, d);
731             continue;
732         }
733
734         if (!strcasecmp(ext, CACHE_DATA_SUFFIX)) {
735             *ext = '\0';
736             d->basename += skip;
737             /* if a user manually creates a '.data' file */
738             if (d->basename[0] == '\0') {
739                 continue;
740             }
741             t = apr_hash_get(h, d->basename, APR_HASH_KEY_STRING);
742             if (t) {
743                 d = t;
744             }
745             d->type |= DATA;
746             d->dtime = info.mtime;
747             d->dsize = info.size;
748             apr_hash_set(h, d->basename, APR_HASH_KEY_STRING, d);
749         }
750     }
751
752     if (interrupted) {
753         return 1;
754     }
755
756     path[baselen] = '\0';
757
758     for (i = apr_hash_first(p, h); i && !interrupted; i = apr_hash_next(i)) {
759         void *hvalue;
760         apr_uint32_t format;
761
762         apr_hash_this(i, NULL, NULL, &hvalue);
763         d = hvalue;
764
765         switch(d->type) {
766         case HEADERDATA:
767             nextpath = apr_pstrcat(p, path, "/", d->basename,
768                                    CACHE_HEADER_SUFFIX, NULL);
769             if (apr_file_open(&fd, nextpath, APR_FOPEN_READ | APR_FOPEN_BINARY,
770                               APR_OS_DEFAULT, p) == APR_SUCCESS) {
771                 len = sizeof(format);
772                 if (apr_file_read_full(fd, &format, len,
773                                        &len) == APR_SUCCESS) {
774                     if (format == DISK_FORMAT_VERSION) {
775                         apr_off_t offset = 0;
776
777                         apr_file_seek(fd, APR_SET, &offset);
778
779                         len = sizeof(disk_cache_info_t);
780
781                         if (apr_file_read_full(fd, &disk_info, len,
782                                                &len) == APR_SUCCESS) {
783                             apr_file_close(fd);
784                             e = apr_palloc(pool, sizeof(ENTRY));
785                             APR_RING_INSERT_TAIL(&root, e, _entry, link);
786                             e->expire = disk_info.expire;
787                             e->response_time = disk_info.response_time;
788                             e->htime = d->htime;
789                             e->dtime = d->dtime;
790                             e->hsize = d->hsize;
791                             e->dsize = d->dsize;
792                             e->basename = apr_pstrdup(pool, d->basename);
793                             if (!disk_info.has_body) {
794                                 delete_file(path, apr_pstrcat(p, path, "/",
795                                         d->basename, CACHE_DATA_SUFFIX, NULL),
796                                         nodes, p);
797                             }
798                             break;
799                         }
800                         else {
801                             apr_file_close(fd);
802                         }
803                     }
804                     else if (format == VARY_FORMAT_VERSION) {
805                         apr_finfo_t finfo;
806
807                         /* This must be a URL that added Vary headers later,
808                          * so kill the orphaned .data file
809                          */
810                         apr_file_close(fd);
811
812                         if (apr_stat(&finfo, apr_pstrcat(p, nextpath,
813                                 CACHE_VDIR_SUFFIX, NULL), APR_FINFO_TYPE, p)
814                                 || finfo.filetype != APR_DIR) {
815                             delete_entry(path, d->basename, nodes, p);
816                         }
817                         else {
818                             delete_file(path, apr_pstrcat(p, path, "/",
819                                     d->basename, CACHE_DATA_SUFFIX, NULL),
820                                     nodes, p);
821                         }
822                         break;
823                     }
824                     else {
825                         /* We didn't recognise the format, kill the files */
826                         apr_file_close(fd);
827                         delete_entry(path, d->basename, nodes, p);
828                         break;
829                     }
830                 }
831                 else {
832                     apr_file_close(fd);
833                 }
834
835             }
836             /* we have a somehow unreadable headers file which is associated
837              * with a data file. this may be caused by apache currently
838              * rewriting the headers file. thus we may delete the file set
839              * either in realclean mode or if the headers file modification
840              * timestamp is not within a specified positive or negative offset
841              * to the current time.
842              */
843             current = apr_time_now();
844             if (realclean || d->htime < current - deviation
845                 || d->htime > current + deviation) {
846                 delete_entry(path, d->basename, nodes, p);
847                 unsolicited += d->hsize;
848                 unsolicited += d->dsize;
849             }
850             break;
851
852         /* single data and header files may be deleted either in realclean
853          * mode or if their modification timestamp is not within a
854          * specified positive or negative offset to the current time.
855          * this handling is necessary due to possible race conditions
856          * between apache and this process
857          */
858         case HEADER:
859             current = apr_time_now();
860             nextpath = apr_pstrcat(p, path, "/", d->basename,
861                                    CACHE_HEADER_SUFFIX, NULL);
862             if (apr_file_open(&fd, nextpath, APR_FOPEN_READ | APR_FOPEN_BINARY,
863                               APR_OS_DEFAULT, p) == APR_SUCCESS) {
864                 len = sizeof(format);
865                 if (apr_file_read_full(fd, &format, len,
866                                        &len) == APR_SUCCESS) {
867                     if (format == VARY_FORMAT_VERSION) {
868                         apr_time_t expires;
869
870                         len = sizeof(expires);
871
872                         if (apr_file_read_full(fd, &expires, len,
873                                                &len) == APR_SUCCESS) {
874                             apr_finfo_t finfo;
875
876                             apr_file_close(fd);
877
878                             if (apr_stat(&finfo, apr_pstrcat(p, nextpath,
879                                     CACHE_VDIR_SUFFIX, NULL), APR_FINFO_TYPE, p)
880                                     || finfo.filetype != APR_DIR) {
881                                 delete_entry(path, d->basename, nodes, p);
882                             }
883                             else if (expires < current) {
884                                 delete_entry(path, d->basename, nodes, p);
885                             }
886
887                             break;
888                         }
889                     }
890                     else if (format == DISK_FORMAT_VERSION) {
891                         apr_off_t offset = 0;
892
893                         apr_file_seek(fd, APR_SET, &offset);
894
895                         len = sizeof(disk_cache_info_t);
896
897                         if (apr_file_read_full(fd, &disk_info, len,
898                                                &len) == APR_SUCCESS) {
899                             apr_file_close(fd);
900                             e = apr_palloc(pool, sizeof(ENTRY));
901                             APR_RING_INSERT_TAIL(&root, e, _entry, link);
902                             e->expire = disk_info.expire;
903                             e->response_time = disk_info.response_time;
904                             e->htime = d->htime;
905                             e->dtime = d->dtime;
906                             e->hsize = d->hsize;
907                             e->dsize = d->dsize;
908                             e->basename = apr_pstrdup(pool, d->basename);
909                             break;
910                         }
911                         else {
912                             apr_file_close(fd);
913                         }
914                     }
915                     else {
916                         apr_file_close(fd);
917                         delete_entry(path, d->basename, nodes, p);
918                         break;
919                     }
920                 }
921                 else {
922                     apr_file_close(fd);
923                 }
924             }
925
926             if (realclean || d->htime < current - deviation
927                 || d->htime > current + deviation) {
928                 delete_entry(path, d->basename, nodes, p);
929                 unsolicited += d->hsize;
930             }
931             break;
932
933         case DATA:
934             current = apr_time_now();
935             if (realclean || d->dtime < current - deviation
936                 || d->dtime > current + deviation) {
937                 delete_entry(path, d->basename, nodes, p);
938                 unsolicited += d->dsize;
939             }
940             break;
941
942         /* temp files may only be deleted in realclean mode which
943          * is asserted above if a tempfile is in the hash array
944          */
945         case TEMP:
946             delete_file(path, d->basename, nodes, p);
947             unsolicited += d->dsize;
948             break;
949         }
950     }
951
952     if (interrupted) {
953         return 1;
954     }
955
956     apr_pool_destroy(p);
957
958     if (benice) {
959         apr_sleep(NICE_DELAY);
960     }
961
962     if (interrupted) {
963         return 1;
964     }
965
966     return 0;
967 }
968
969 /*
970  * purge cache entries
971  */
972 static void purge(char *path, apr_pool_t *pool, apr_off_t max,
973         apr_off_t inodes, apr_off_t nodes, apr_off_t round)
974 {
975     ENTRY *e, *n, *oldest;
976
977     struct stats s;
978     s.sum = 0;
979     s.entries = 0;
980     s.dfuture = 0;
981     s.dexpired = 0;
982     s.dfresh = 0;
983     s.max = max;
984     s.nodes = nodes;
985     s.inodes = inodes;
986     s.ntotal = nodes;
987
988     for (e = APR_RING_FIRST(&root);
989          e != APR_RING_SENTINEL(&root, _entry, link);
990          e = APR_RING_NEXT(e, link)) {
991         s.sum += round_up(e->hsize, round);
992         s.sum += round_up(e->dsize, round);
993         s.entries++;
994     }
995
996     s.total = s.sum;
997     s.etotal = s.entries;
998
999     if ((!s.max || s.sum <= s.max) && (!s.inodes || s.nodes <= s.inodes)) {
1000         printstats(path, &s);
1001         return;
1002     }
1003
1004     /* process all entries with a timestamp in the future, this may
1005      * happen if a wrong system time is corrected
1006      */
1007
1008     for (e = APR_RING_FIRST(&root);
1009          e != APR_RING_SENTINEL(&root, _entry, link) && !interrupted;) {
1010         n = APR_RING_NEXT(e, link);
1011         if (e->response_time > now || e->htime > now || e->dtime > now) {
1012             delete_entry(path, e->basename, &s.nodes, pool);
1013             s.sum -= round_up(e->hsize, round);
1014             s.sum -= round_up(e->dsize, round);
1015             s.entries--;
1016             s.dfuture++;
1017             APR_RING_REMOVE(e, link);
1018             if ((!s.max || s.sum <= s.max) && (!s.inodes || s.nodes <= s.inodes)) {
1019                 if (!interrupted) {
1020                     printstats(path, &s);
1021                 }
1022                 return;
1023             }
1024         }
1025         e = n;
1026     }
1027
1028     if (interrupted) {
1029         return;
1030     }
1031
1032     /* process all entries with are expired */
1033     for (e = APR_RING_FIRST(&root);
1034          e != APR_RING_SENTINEL(&root, _entry, link) && !interrupted;) {
1035         n = APR_RING_NEXT(e, link);
1036         if (e->expire != APR_DATE_BAD && e->expire < now) {
1037             delete_entry(path, e->basename, &s.nodes, pool);
1038             s.sum -= round_up(e->hsize, round);
1039             s.sum -= round_up(e->dsize, round);
1040             s.entries--;
1041             s.dexpired++;
1042             APR_RING_REMOVE(e, link);
1043             if ((!s.max || s.sum <= s.max) && (!s.inodes || s.nodes <= s.inodes)) {
1044                 if (!interrupted) {
1045                     printstats(path, &s);
1046                 }
1047                 return;
1048             }
1049         }
1050         e = n;
1051     }
1052
1053     if (interrupted) {
1054          return;
1055     }
1056
1057     /* process remaining entries oldest to newest, the check for an emtpy
1058      * ring actually isn't necessary except when the compiler does
1059      * corrupt 64bit arithmetics which happend to me once, so better safe
1060      * than sorry
1061      */
1062     while (!((!s.max || s.sum <= s.max) && (!s.inodes || s.nodes <= s.inodes))
1063             && !interrupted && !APR_RING_EMPTY(&root, _entry, link)) {
1064         oldest = APR_RING_FIRST(&root);
1065
1066         for (e = APR_RING_NEXT(oldest, link);
1067              e != APR_RING_SENTINEL(&root, _entry, link);
1068              e = APR_RING_NEXT(e, link)) {
1069             if (e->dtime < oldest->dtime) {
1070                 oldest = e;
1071             }
1072         }
1073
1074         delete_entry(path, oldest->basename, &s.nodes, pool);
1075         s.sum -= round_up(oldest->hsize, round);
1076         s.sum -= round_up(oldest->dsize, round);
1077         s.entries--;
1078         s.dfresh++;
1079         APR_RING_REMOVE(oldest, link);
1080     }
1081
1082     if (!interrupted) {
1083         printstats(path, &s);
1084     }
1085 }
1086
1087 static apr_status_t remove_directory(apr_pool_t *pool, const char *dir)
1088 {
1089     apr_status_t rv;
1090     apr_dir_t *dirp;
1091     apr_finfo_t dirent;
1092
1093     rv = apr_dir_open(&dirp, dir, pool);
1094     if (rv == APR_ENOENT) {
1095         return rv;
1096     }
1097     if (rv != APR_SUCCESS) {
1098         char errmsg[120];
1099         apr_file_printf(errfile, "Could not open directory %s: %s" APR_EOL_STR,
1100                 dir, apr_strerror(rv, errmsg, sizeof errmsg));
1101         return rv;
1102     }
1103
1104     while (apr_dir_read(&dirent, APR_FINFO_DIRENT | APR_FINFO_TYPE, dirp)
1105             == APR_SUCCESS) {
1106         if (dirent.filetype == APR_DIR) {
1107             if (strcmp(dirent.name, ".") && strcmp(dirent.name, "..")) {
1108                 rv = remove_directory(pool, apr_pstrcat(pool, dir, "/",
1109                         dirent.name, NULL));
1110                 /* tolerate the directory not being empty, the cache may have
1111                  * attempted to recreate the directory in the mean time.
1112                  */
1113                 if (APR_SUCCESS != rv && APR_ENOTEMPTY != rv) {
1114                     break;
1115                 }
1116             }
1117         } else {
1118             const char *file = apr_pstrcat(pool, dir, "/", dirent.name, NULL);
1119             rv = apr_file_remove(file, pool);
1120             if (APR_SUCCESS != rv) {
1121                 char errmsg[120];
1122                 apr_file_printf(errfile,
1123                         "Could not remove file '%s': %s" APR_EOL_STR, file,
1124                         apr_strerror(rv, errmsg, sizeof errmsg));
1125                 break;
1126             }
1127         }
1128     }
1129
1130     apr_dir_close(dirp);
1131
1132     if (rv == APR_SUCCESS) {
1133         rv = apr_dir_remove(dir, pool);
1134         if (APR_ENOTEMPTY == rv) {
1135             rv = APR_SUCCESS;
1136         }
1137         if (rv != APR_SUCCESS) {
1138             char errmsg[120];
1139             apr_file_printf(errfile, "Could not remove directory %s: %s" APR_EOL_STR,
1140                     dir, apr_strerror(rv, errmsg, sizeof errmsg));
1141         }
1142     }
1143
1144     return rv;
1145 }
1146
1147 static apr_status_t find_directory(apr_pool_t *pool, const char *base,
1148         const char *rest)
1149 {
1150     apr_status_t rv;
1151     apr_dir_t *dirp;
1152     apr_finfo_t dirent;
1153     int found = 0, files = 0;
1154     const char *header = apr_pstrcat(pool, rest, CACHE_HEADER_SUFFIX, NULL);
1155     const char *data = apr_pstrcat(pool, rest, CACHE_DATA_SUFFIX, NULL);
1156     const char *vdir = apr_pstrcat(pool, rest, CACHE_HEADER_SUFFIX,
1157             CACHE_VDIR_SUFFIX, NULL);
1158     const char *dirname = NULL;
1159
1160     rv = apr_dir_open(&dirp, base, pool);
1161     if (rv != APR_SUCCESS) {
1162         char errmsg[120];
1163         apr_file_printf(errfile, "Could not open directory %s: %s" APR_EOL_STR,
1164                 base, apr_strerror(rv, errmsg, sizeof errmsg));
1165         return rv;
1166     }
1167
1168     rv = APR_ENOENT;
1169
1170     while (apr_dir_read(&dirent, APR_FINFO_DIRENT | APR_FINFO_TYPE, dirp)
1171             == APR_SUCCESS) {
1172         int len = strlen(dirent.name);
1173         int restlen = strlen(rest);
1174         if (dirent.filetype == APR_DIR && !strncmp(rest, dirent.name, len)) {
1175             dirname = apr_pstrcat(pool, base, "/", dirent.name, NULL);
1176             rv = find_directory(pool, dirname, rest + (len < restlen ? len
1177                     : restlen));
1178             if (APR_SUCCESS == rv) {
1179                 found = 1;
1180             }
1181         }
1182         if (dirent.filetype == APR_DIR) {
1183             if (!strcmp(dirent.name, vdir)) {
1184                 files = 1;
1185             }
1186         }
1187         if (dirent.filetype == APR_REG) {
1188             if (!strcmp(dirent.name, header) || !strcmp(dirent.name, data)) {
1189                 files = 1;
1190             }
1191         }
1192     }
1193
1194     apr_dir_close(dirp);
1195
1196     if (files) {
1197         rv = APR_SUCCESS;
1198         if (!dryrun) {
1199             const char *remove;
1200             apr_status_t status;
1201
1202             remove = apr_pstrcat(pool, base, "/", header, NULL);
1203             status = apr_file_remove(remove, pool);
1204             if (status != APR_SUCCESS && status != APR_ENOENT) {
1205                 char errmsg[120];
1206                 apr_file_printf(errfile, "Could not remove file %s: %s" APR_EOL_STR,
1207                         remove, apr_strerror(status, errmsg, sizeof errmsg));
1208                 rv = status;
1209             }
1210
1211             remove = apr_pstrcat(pool, base, "/", data, NULL);
1212             status = apr_file_remove(remove, pool);
1213             if (status != APR_SUCCESS && status != APR_ENOENT) {
1214                 char errmsg[120];
1215                 apr_file_printf(errfile, "Could not remove file %s: %s" APR_EOL_STR,
1216                         remove, apr_strerror(status, errmsg, sizeof errmsg));
1217                 rv = status;
1218             }
1219
1220             status = remove_directory(pool, apr_pstrcat(pool, base, "/", vdir, NULL));
1221             if (status != APR_SUCCESS && status != APR_ENOENT) {
1222                 rv = status;
1223             }
1224         }
1225     }
1226
1227     /* If asked to delete dirs, do so now. We don't care if it fails.
1228      * If it fails, it likely means there was something else there.
1229      */
1230     if (dirname && deldirs && !dryrun) {
1231         apr_dir_remove(dirname, pool);
1232     }
1233
1234     if (found) {
1235         return APR_SUCCESS;
1236     }
1237
1238     return rv;
1239 }
1240
1241 /**
1242  * Delete a specific URL from the cache.
1243  */
1244 static apr_status_t delete_url(apr_pool_t *pool, const char *proxypath, const char *url)
1245 {
1246     apr_md5_ctx_t context;
1247     unsigned char digest[16];
1248     char tmp[23];
1249     int i, k;
1250     unsigned int x;
1251     static const char enc_table[64] =
1252             "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_@";
1253
1254     apr_md5_init(&context);
1255     apr_md5_update(&context, (const unsigned char *) url, strlen(url));
1256     apr_md5_final(digest, &context);
1257
1258     /* encode 128 bits as 22 characters, using a modified uuencoding
1259      * the encoding is 3 bytes -> 4 characters* i.e. 128 bits is
1260      * 5 x 3 bytes + 1 byte -> 5 * 4 characters + 2 characters
1261      */
1262     for (i = 0, k = 0; i < 15; i += 3) {
1263         x = (digest[i] << 16) | (digest[i + 1] << 8) | digest[i + 2];
1264         tmp[k++] = enc_table[x >> 18];
1265         tmp[k++] = enc_table[(x >> 12) & 0x3f];
1266         tmp[k++] = enc_table[(x >> 6) & 0x3f];
1267         tmp[k++] = enc_table[x & 0x3f];
1268     }
1269
1270     /* one byte left */
1271     x = digest[15];
1272     tmp[k++] = enc_table[x >> 2]; /* use up 6 bits */
1273     tmp[k++] = enc_table[(x << 4) & 0x3f];
1274     tmp[k] = 0;
1275
1276     /* automatically find the directory levels */
1277     return find_directory(pool, proxypath, tmp);
1278 }
1279
1280 /*
1281  * usage info
1282  */
1283 #define NL APR_EOL_STR
1284 static void usage(const char *error)
1285 {
1286     if (error) {
1287         apr_file_printf(errfile, "%s error: %s\n", shortname, error);
1288     }
1289     apr_file_printf(errfile,
1290     "%s -- program for cleaning the disk cache."                             NL
1291     "Usage: %s [-Dvtrn] -pPATH [-lLIMIT|-LLIMIT] [-PPIDFILE]"                NL
1292     "       %s [-nti] -dINTERVAL -pPATH [-lLIMIT|-LLIMIT] [-PPIDFILE]"       NL
1293     "       %s [-Dvt] -pPATH URL ..."                                        NL
1294                                                                              NL
1295     "Options:"                                                               NL
1296     "  -d   Daemonize and repeat cache cleaning every INTERVAL minutes."     NL
1297     "       This option is mutually exclusive with the -D, -v and -r"        NL
1298     "       options."                                                        NL
1299                                                                              NL
1300     "  -D   Do a dry run and don't delete anything. This option is mutually" NL
1301     "       exclusive with the -d option. When doing a dry run and deleting" NL
1302     "       directories with -t, the inodes reported deleted in the stats"   NL
1303     "       cannot take into account the directories deleted, and will be"   NL
1304     "       marked as an estimate."                                          NL
1305                                                                              NL
1306     "  -v   Be verbose and print statistics. This option is mutually"        NL
1307     "       exclusive with the -d option."                                   NL
1308                                                                              NL
1309     "  -r   Clean thoroughly. This assumes that the Apache web server is "   NL
1310     "       not running. This option is mutually exclusive with the -d"      NL
1311     "       option and implies -t."                                          NL
1312                                                                              NL
1313     "  -n   Be nice. This causes slower processing in favour of other"       NL
1314     "       processes."                                                      NL
1315                                                                              NL
1316     "  -t   Delete all empty directories. By default only cache files are"   NL
1317     "       removed, however with some configurations the large number of"   NL
1318     "       directories created may require attention."                      NL
1319                                                                              NL
1320     "  -p   Specify PATH as the root directory of the disk cache."           NL
1321                                                                              NL
1322     "  -P   Specify PIDFILE as the file to write the pid to."                NL
1323                                                                              NL
1324     "  -R   Specify amount to round sizes up to."                            NL
1325                                                                              NL
1326     "  -l   Specify LIMIT as the total disk cache size limit. Attach 'K'"    NL
1327     "       or 'M' to the number for specifying KBytes or MBytes."           NL
1328                                                                              NL
1329     "  -L   Specify LIMIT as the total disk cache inode limit."              NL
1330                                                                              NL
1331     "  -i   Be intelligent and run only when there was a modification of"    NL
1332     "       the disk cache. This option is only possible together with the"  NL
1333     "       -d option."                                                      NL
1334                                                                              NL
1335     "  -a   List the URLs currently stored in the cache. Variants of the"    NL
1336     "       same URL will be listed once for each variant."                  NL
1337                                                                              NL
1338     "  -A   List the URLs currently stored in the cache, along with their"   NL
1339     "       attributes in the following order: url, header size, body size," NL
1340     "       status, entity version, date, expiry, request time,"             NL
1341     "       response time, body present, head request."                      NL
1342                                                                              NL
1343     "Should an URL be provided on the command line, the URL will be"         NL
1344     "deleted from the cache. A reverse proxied URL is made up as follows:"   NL
1345     "http://<hostname>:<port><path>?[query]. So, for the path \"/\" on the"  NL
1346     "host \"localhost\" and port 80, the URL to delete becomes"              NL
1347     "\"http://localhost:80/?\". Note the '?' in the URL must always be"      NL
1348     "specified explicitly, whether a query string is present or not."        NL,
1349     shortname,
1350     shortname,
1351     shortname,
1352     shortname
1353     );
1354
1355     exit(1);
1356 }
1357 #undef NL
1358
1359 static void usage_repeated_arg(apr_pool_t *pool, char option) {
1360     usage(apr_psprintf(pool, 
1361                        "The option '%c' cannot be specified more than once",
1362                        option));
1363 }
1364
1365 static void log_pid(apr_pool_t *pool, const char *pidfilename, apr_file_t **pidfile)
1366 {
1367     apr_status_t status;
1368     char errmsg[120];
1369     pid_t mypid = getpid();
1370
1371     if (APR_SUCCESS == (status = apr_file_open(pidfile, pidfilename,
1372                 APR_FOPEN_WRITE | APR_FOPEN_CREATE | APR_FOPEN_TRUNCATE |
1373                 APR_FOPEN_DELONCLOSE, APR_FPROT_UREAD | APR_FPROT_UWRITE |
1374                 APR_FPROT_GREAD | APR_FPROT_WREAD, pool))) {
1375         apr_file_printf(*pidfile, "%" APR_PID_T_FMT APR_EOL_STR, mypid);
1376     }
1377     else {
1378         if (errfile) {
1379             apr_file_printf(errfile,
1380                             "Could not write the pid file '%s': %s" APR_EOL_STR,
1381                             pidfilename, 
1382                             apr_strerror(status, errmsg, sizeof errmsg));
1383         }
1384         exit(1);
1385     }
1386 }
1387
1388 /*
1389  * main
1390  */
1391 int main(int argc, const char * const argv[])
1392 {
1393     apr_off_t max, inodes, round;
1394     apr_time_t current, repeat, delay, previous;
1395     apr_status_t status;
1396     apr_pool_t *pool, *instance;
1397     apr_getopt_t *o;
1398     apr_finfo_t info;
1399     apr_file_t *pidfile;
1400     int retries, isdaemon, limit_found, inodes_found, intelligent, dowork;
1401     char opt;
1402     const char *arg;
1403     char *proxypath, *path, *pidfilename;
1404     char errmsg[1024];
1405
1406     interrupted = 0;
1407     repeat = 0;
1408     isdaemon = 0;
1409     dryrun = 0;
1410     limit_found = 0;
1411     inodes_found = 0;
1412     max = 0;
1413     inodes = 0;
1414     round = 0;
1415     verbose = 0;
1416     realclean = 0;
1417     benice = 0;
1418     deldirs = 0;
1419     intelligent = 0;
1420     previous = 0; /* avoid compiler warning */
1421     proxypath = NULL;
1422     pidfilename = NULL;
1423
1424     if (apr_app_initialize(&argc, &argv, NULL) != APR_SUCCESS) {
1425         return 1;
1426     }
1427     atexit(apr_terminate);
1428
1429     if (argc) {
1430         shortname = apr_filepath_name_get(argv[0]);
1431     }
1432
1433     if (apr_pool_create(&pool, NULL) != APR_SUCCESS) {
1434         return 1;
1435     }
1436     apr_pool_abort_set(oom, pool);
1437     apr_file_open_stderr(&errfile, pool);
1438     apr_file_open_stdout(&outfile, pool);
1439     apr_signal(SIGINT, setterm);
1440     apr_signal(SIGTERM, setterm);
1441
1442     apr_getopt_init(&o, pool, argc, argv);
1443
1444     while (1) {
1445         status = apr_getopt(o, "iDnvrtd:l:L:p:P:R:aA", &opt, &arg);
1446         if (status == APR_EOF) {
1447             break;
1448         }
1449         else if (status != APR_SUCCESS) {
1450             usage(NULL);
1451         }
1452         else {
1453             char *end;
1454             apr_status_t rv;
1455             switch (opt) {
1456             case 'i':
1457                 if (intelligent) {
1458                     usage_repeated_arg(pool, opt);
1459                 }
1460                 intelligent = 1;
1461                 break;
1462
1463             case 'D':
1464                 if (dryrun) {
1465                     usage_repeated_arg(pool, opt);
1466                 }
1467                 dryrun = 1;
1468                 break;
1469
1470             case 'n':
1471                 if (benice) {
1472                     usage_repeated_arg(pool, opt);
1473                 }
1474                 benice = 1;
1475                 break;
1476
1477             case 't':
1478                 if (deldirs) {
1479                     usage_repeated_arg(pool, opt);
1480                 }
1481                 deldirs = 1;
1482                 break;
1483
1484             case 'v':
1485                 if (verbose) {
1486                     usage_repeated_arg(pool, opt);
1487                 }
1488                 verbose = 1;
1489                 break;
1490
1491             case 'r':
1492                 if (realclean) {
1493                     usage_repeated_arg(pool, opt);
1494                 }
1495                 realclean = 1;
1496                 deldirs = 1;
1497                 break;
1498
1499             case 'd':
1500                 if (isdaemon) {
1501                     usage_repeated_arg(pool, opt);
1502                 }
1503                 isdaemon = 1;
1504                 repeat = apr_atoi64(arg);
1505                 repeat *= SECS_PER_MIN;
1506                 repeat *= APR_USEC_PER_SEC;
1507                 break;
1508
1509             case 'l':
1510                 if (limit_found) {
1511                     usage_repeated_arg(pool, opt);
1512                 }
1513                 limit_found = 1;
1514
1515                 do {
1516                     rv = apr_strtoff(&max, arg, &end, 10);
1517                     if (rv == APR_SUCCESS) {
1518                         if ((*end == 'K' || *end == 'k') && !end[1]) {
1519                             max *= KBYTE;
1520                         }
1521                         else if ((*end == 'M' || *end == 'm') && !end[1]) {
1522                             max *= MBYTE;
1523                         }
1524                         else if ((*end == 'G' || *end == 'g') && !end[1]) {
1525                             max *= GBYTE;
1526                         }
1527                         else if (*end &&        /* neither empty nor [Bb] */
1528                                  ((*end != 'B' && *end != 'b') || end[1])) {
1529                             rv = APR_EGENERAL;
1530                         }
1531                     }
1532                     if (rv != APR_SUCCESS) {
1533                         usage(apr_psprintf(pool, "Invalid limit: %s"
1534                                                  APR_EOL_STR APR_EOL_STR, arg));
1535                     }
1536                 } while(0);
1537                 break;
1538
1539             case 'L':
1540                 if (inodes_found) {
1541                     usage_repeated_arg(pool, opt);
1542                 }
1543                 inodes_found = 1;
1544
1545                 do {
1546                     rv = apr_strtoff(&inodes, arg, &end, 10);
1547                     if (rv == APR_SUCCESS) {
1548                         if ((*end == 'K' || *end == 'k') && !end[1]) {
1549                             inodes *= KBYTE;
1550                         }
1551                         else if ((*end == 'M' || *end == 'm') && !end[1]) {
1552                             inodes *= MBYTE;
1553                         }
1554                         else if ((*end == 'G' || *end == 'g') && !end[1]) {
1555                             inodes *= GBYTE;
1556                         }
1557                         else if (*end &&        /* neither empty nor [Bb] */
1558                                  ((*end != 'B' && *end != 'b') || end[1])) {
1559                             rv = APR_EGENERAL;
1560                         }
1561                     }
1562                     if (rv != APR_SUCCESS) {
1563                         usage(apr_psprintf(pool, "Invalid limit: %s"
1564                                                  APR_EOL_STR APR_EOL_STR, arg));
1565                     }
1566                 } while(0);
1567                 break;
1568
1569             case 'a':
1570                 if (listurls) {
1571                     usage_repeated_arg(pool, opt);
1572                 }
1573                 listurls = 1;
1574                 break;
1575
1576             case 'A':
1577                 if (listurls) {
1578                     usage_repeated_arg(pool, opt);
1579                 }
1580                 listurls = 1;
1581                 listextended = 1;
1582                 break;
1583
1584             case 'p':
1585                 if (proxypath) {
1586                     usage_repeated_arg(pool, opt);
1587                 }
1588                 proxypath = apr_pstrdup(pool, arg);
1589                 if ((status = apr_filepath_set(proxypath, pool)) != APR_SUCCESS) {
1590                     usage(apr_psprintf(pool, "Could not set filepath to '%s': %s",
1591                                        proxypath, apr_strerror(status, errmsg, sizeof errmsg)));
1592                 }
1593                 break;
1594
1595             case 'P':
1596                 if (pidfilename) {
1597                     usage_repeated_arg(pool, opt);
1598                 }
1599                 pidfilename = apr_pstrdup(pool, arg);
1600                 break;
1601
1602             case 'R':
1603                 if (round) {
1604                     usage_repeated_arg(pool, opt);
1605                 }
1606                 rv = apr_strtoff(&round, arg, &end, 10);
1607                 if (rv == APR_SUCCESS) {
1608                     if (*end) {
1609                         usage(apr_psprintf(pool, "Invalid round value: %s"
1610                                                  APR_EOL_STR APR_EOL_STR, arg));
1611                     }
1612                     else if (round < 0) {
1613                         usage(apr_psprintf(pool, "Round value must be positive: %s"
1614                                                  APR_EOL_STR APR_EOL_STR, arg));
1615                     }
1616                 }
1617                 if (rv != APR_SUCCESS) {
1618                     usage(apr_psprintf(pool, "Invalid round value: %s"
1619                                              APR_EOL_STR APR_EOL_STR, arg));
1620                 }
1621                 break;
1622
1623             } /* switch */
1624         } /* else */
1625     } /* while */
1626
1627     if (argc <= 1) {
1628         usage(NULL);
1629     }
1630
1631     if (o->ind < argc) {
1632         int deleted = 0;
1633         int error = 0;
1634         if (isdaemon) {
1635             usage("Option -d cannot be used with URL arguments, aborting");
1636         }
1637         if (intelligent) {
1638             usage("Option -i cannot be used with URL arguments, aborting");
1639         }
1640         if (limit_found) {
1641             usage("Option -l cannot be used with URL arguments, aborting");
1642         }
1643         while (o->ind < argc) {
1644             status = delete_url(pool, proxypath, argv[o->ind]);
1645             if (APR_SUCCESS == status) {
1646                 if (verbose) {
1647                     apr_file_printf(errfile, "Removed: %s" APR_EOL_STR,
1648                             argv[o->ind]);
1649                 }
1650                 deleted = 1;
1651             }
1652             else if (APR_ENOENT == status) {
1653                 if (verbose) {
1654                     apr_file_printf(errfile, "Not cached: %s" APR_EOL_STR,
1655                             argv[o->ind]);
1656                 }
1657             }
1658             else {
1659                 if (verbose) {
1660                     apr_file_printf(errfile, "Error while removed: %s" APR_EOL_STR,
1661                             argv[o->ind]);
1662                 }
1663                 error = 1;
1664             }
1665             o->ind++;
1666         }
1667         return error ? 1 : deleted ? 0 : 2;
1668     }
1669
1670     if (isdaemon && repeat <= 0) {
1671          usage("Option -d must be greater than zero");
1672     }
1673
1674     if (isdaemon && (verbose || realclean || dryrun || listurls)) {
1675          usage("Option -d cannot be used with -v, -r, -L or -D");
1676     }
1677
1678     if (!isdaemon && intelligent) {
1679          usage("Option -i cannot be used without -d");
1680     }
1681
1682     if (!proxypath) {
1683          usage("Option -p must be specified");
1684     }
1685
1686     if (!listurls && max <= 0 && inodes <= 0) {
1687          usage("At least one of option -l or -L must be greater than zero");
1688     }
1689
1690     if (apr_filepath_get(&path, 0, pool) != APR_SUCCESS) {
1691         usage(apr_psprintf(pool, "Could not get the filepath: %s",
1692                            apr_strerror(status, errmsg, sizeof errmsg)));
1693     }
1694     baselen = strlen(path);
1695
1696     if (pidfilename) {
1697         log_pid(pool, pidfilename, &pidfile); /* before daemonizing, so we
1698                                                * can report errors
1699                                                */
1700     }
1701
1702     if (listurls) {
1703         list_urls(path, pool, round);
1704         return (interrupted != 0);
1705     }
1706
1707 #ifndef DEBUG
1708     if (isdaemon) {
1709         apr_file_close(errfile);
1710         errfile = NULL;
1711         if (pidfilename) {
1712             apr_file_close(pidfile); /* delete original pidfile only in parent */
1713         }
1714         apr_proc_detach(APR_PROC_DETACH_DAEMONIZE);
1715         if (pidfilename) {
1716             log_pid(pool, pidfilename, &pidfile);
1717         }
1718     }
1719 #endif
1720
1721     do {
1722         apr_pool_create(&instance, pool);
1723
1724         now = apr_time_now();
1725         APR_RING_INIT(&root, _entry, link);
1726         delcount = 0;
1727         unsolicited = 0;
1728         dowork = 0;
1729
1730         switch (intelligent) {
1731         case 0:
1732             dowork = 1;
1733             break;
1734
1735         case 1:
1736             retries = STAT_ATTEMPTS;
1737             status = APR_SUCCESS;
1738
1739             do {
1740                 if (status != APR_SUCCESS) {
1741                     apr_sleep(STAT_DELAY);
1742                 }
1743                 status = apr_stat(&info, path, APR_FINFO_MTIME, instance);
1744             } while (status != APR_SUCCESS && !interrupted && --retries);
1745
1746             if (status == APR_SUCCESS) {
1747                 previous = info.mtime;
1748                 intelligent = 2;
1749             }
1750             dowork = 1;
1751             break;
1752
1753         case 2:
1754             retries = STAT_ATTEMPTS;
1755             status = APR_SUCCESS;
1756
1757             do {
1758                 if (status != APR_SUCCESS) {
1759                     apr_sleep(STAT_DELAY);
1760                 }
1761                 status = apr_stat(&info, path, APR_FINFO_MTIME, instance);
1762             } while (status != APR_SUCCESS && !interrupted && --retries);
1763
1764             if (status == APR_SUCCESS) {
1765                 if (previous != info.mtime) {
1766                     dowork = 1;
1767                 }
1768                 previous = info.mtime;
1769                 break;
1770             }
1771             intelligent = 1;
1772             dowork = 1;
1773             break;
1774         }
1775
1776         if (dowork && !interrupted) {
1777             apr_off_t nodes = 0;
1778             if (!process_dir(path, instance, &nodes) && !interrupted) {
1779                 purge(path, instance, max, inodes, nodes, round);
1780             }
1781             else if (!isdaemon && !interrupted) {
1782                 apr_file_printf(errfile, "An error occurred, cache cleaning "
1783                                          "aborted." APR_EOL_STR);
1784                 return 1;
1785             }
1786
1787             if (intelligent && !interrupted) {
1788                 retries = STAT_ATTEMPTS;
1789                 status = APR_SUCCESS;
1790                 do {
1791                     if (status != APR_SUCCESS) {
1792                         apr_sleep(STAT_DELAY);
1793                     }
1794                     status = apr_stat(&info, path, APR_FINFO_MTIME, instance);
1795                 } while (status != APR_SUCCESS && !interrupted && --retries);
1796
1797                 if (status == APR_SUCCESS) {
1798                     previous = info.mtime;
1799                     intelligent = 2;
1800                 }
1801                 else {
1802                     intelligent = 1;
1803                 }
1804             }
1805         }
1806
1807         apr_pool_destroy(instance);
1808
1809         current = apr_time_now();
1810         if (current < now) {
1811             delay = repeat;
1812         }
1813         else if (current - now >= repeat) {
1814             delay = repeat;
1815         }
1816         else {
1817             delay = now + repeat - current;
1818         }
1819
1820         /* we can't sleep the whole delay time here apiece as this is racy
1821          * with respect to interrupt delivery - think about what happens
1822          * if we have tested for an interrupt, then get scheduled
1823          * before the apr_sleep() call and while waiting for the cpu
1824          * we do get an interrupt
1825          */
1826         if (isdaemon) {
1827             while (delay && !interrupted) {
1828                 if (delay > APR_USEC_PER_SEC) {
1829                     apr_sleep(APR_USEC_PER_SEC);
1830                     delay -= APR_USEC_PER_SEC;
1831                 }
1832                 else {
1833                     apr_sleep(delay);
1834                     delay = 0;
1835                 }
1836             }
1837         }
1838     } while (isdaemon && !interrupted);
1839
1840     if (!isdaemon && interrupted) {
1841         apr_file_printf(errfile, "Cache cleaning aborted due to user "
1842                                  "request." APR_EOL_STR);
1843         return 1;
1844     }
1845
1846     return 0;
1847 }