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