]> granicus.if.org Git - apache/blob - support/htcacheclean.c
another warning.
[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
152 #ifdef DEBUG
153 /*
154  * fake delete for debug purposes
155  */
156 #define apr_file_remove fake_file_remove
157 static void fake_file_remove(char *pathname, apr_pool_t *p)
158 {
159     apr_finfo_t info;
160
161     /* stat and printing to simulate some deletion system load and to
162        display what would actually have happened */
163     apr_stat(&info, pathname, DIRINFO, p);
164     apr_file_printf(errfile, "would delete %s\n", pathname);
165 }
166 #endif
167
168 /*
169  * called on SIGINT or SIGTERM
170  */
171 static void setterm(int unused)
172 {
173 #ifdef DEBUG
174     apr_file_printf(errfile, "interrupt\n");
175 #endif
176     interrupted = 1;
177 }
178
179 /*
180  * called in out of memory condition
181  */
182 static int oom(int unused)
183 {
184     static int called = 0;
185
186     /* be careful to call exit() only once */
187     if (!called) {
188         called = 1;
189         exit(1);
190     }
191     return APR_ENOMEM;
192 }
193
194 /*
195  * print purge statistics
196  */
197 static void printstats(apr_off_t total, apr_off_t sum, apr_off_t max,
198                        apr_off_t etotal, apr_off_t entries)
199 {
200     char ttype, stype, mtype, utype;
201     apr_off_t tfrag, sfrag, ufrag;
202
203     if (!verbose) {
204         return;
205     }
206
207     ttype = 'K';
208     tfrag = ((total * 10) / KBYTE) % 10;
209     total /= KBYTE;
210     if (total >= KBYTE) {
211         ttype = 'M';
212         tfrag = ((total * 10) / KBYTE) % 10;
213         total /= KBYTE;
214     }
215
216     stype = 'K';
217     sfrag = ((sum * 10) / KBYTE) % 10;
218     sum /= KBYTE;
219     if (sum >= KBYTE) {
220         stype = 'M';
221         sfrag = ((sum * 10) / KBYTE) % 10;
222         sum /= KBYTE;
223     }
224
225     mtype = 'K';
226     max /= KBYTE;
227     if (max >= KBYTE) {
228         mtype = 'M';
229         max /= KBYTE;
230     }
231
232     apr_file_printf(errfile, "Statistics:\n");
233     if (unsolicited) {
234         utype = 'K';
235         ufrag = ((unsolicited * 10) / KBYTE) % 10;
236         unsolicited /= KBYTE;
237         if (unsolicited >= KBYTE) {
238             utype = 'M';
239             ufrag = ((unsolicited * 10) / KBYTE) % 10;
240             unsolicited /= KBYTE;
241         }
242         if (!unsolicited && !ufrag) {
243             ufrag = 1;
244         }
245         apr_file_printf(errfile, "unsolicited size %d.%d%c\n",
246                         (int)(unsolicited), (int)(ufrag), utype);
247      }
248      apr_file_printf(errfile, "size limit %d.0%c\n", (int)(max), mtype);
249      apr_file_printf(errfile, "total size was %d.%d%c, total size now "
250                               "%d.%d%c\n",
251                      (int)(total), (int)(tfrag), ttype, (int)(sum),
252                      (int)(sfrag), stype);
253      apr_file_printf(errfile, "total entries was %d, total entries now %d\n",
254                      (int)(etotal), (int)(entries));
255 }
256
257 /*
258  * delete a single file
259  */
260 static void delete_file(char *path, char *basename, apr_pool_t *pool)
261 {
262     char *nextpath;
263     apr_pool_t *p;
264
265     if (dryrun) {
266         return;
267     }
268
269     /* temp pool, otherwise lots of memory could be allocated */
270     apr_pool_create(&p, pool);
271     nextpath = apr_pstrcat(p, path, "/", basename, NULL);
272     apr_file_remove(nextpath, p);
273     apr_pool_destroy(p);
274
275     if (benice) {
276         if (++delcount >= DELETE_NICE) {
277             apr_sleep(NICE_DELAY);
278             delcount = 0;
279         }
280     }
281 }
282
283 /*
284  * delete cache file set
285  */
286 static void delete_entry(char *path, char *basename, apr_pool_t *pool)
287 {
288     char *nextpath;
289     apr_pool_t *p;
290
291     if (dryrun) {
292         return;
293     }
294
295     /* temp pool, otherwise lots of memory could be allocated */
296     apr_pool_create(&p, pool);
297
298     nextpath = apr_pstrcat(p, path, "/", basename, CACHE_HEADER_SUFFIX, NULL);
299     apr_file_remove(nextpath, p);
300
301     nextpath = apr_pstrcat(p, path, "/", basename, CACHE_DATA_SUFFIX, NULL);
302     apr_file_remove(nextpath, p);
303
304     apr_pool_destroy(p);
305
306     if (benice) {
307         delcount += 2;
308         if (delcount >= DELETE_NICE) {
309             apr_sleep(NICE_DELAY);
310             delcount = 0;
311         }
312     }
313 }
314
315 /*
316  * walk the cache directory tree
317  */
318 static int process_dir(char *path, apr_pool_t *pool)
319 {
320     apr_dir_t *dir;
321     apr_pool_t *p;
322     apr_hash_t *h;
323     apr_hash_index_t *i;
324     apr_file_t *fd;
325     apr_status_t status;
326     apr_finfo_t info;
327     apr_size_t len;
328     apr_time_t current, deviation;
329     char *nextpath, *base, *ext;
330     APR_RING_ENTRY(_direntry) anchor;
331     DIRENTRY *d, *t, *n;
332     ENTRY *e;
333     int skip, retries;
334     disk_cache_info_t disk_info;
335
336     APR_RING_INIT(&anchor, _direntry, link);
337     apr_pool_create(&p, pool);
338     h = apr_hash_make(p);
339     fd = NULL;
340     skip = 0;
341     deviation = MAXDEVIATION * APR_USEC_PER_SEC;
342
343     if (apr_dir_open(&dir, path, p) != APR_SUCCESS) {
344         return 1;
345     }
346
347     while (apr_dir_read(&info, 0, dir) == APR_SUCCESS && !interrupted) {
348         /* skip first two entries which will always be '.' and '..' */
349         if (skip < 2) {
350             skip++;
351             continue;
352         }
353         d = apr_pcalloc(p, sizeof(DIRENTRY));
354         d->basename = apr_pstrcat(p, path, "/", info.name, NULL);
355         APR_RING_INSERT_TAIL(&anchor, d, _direntry, link);
356     }
357
358     apr_dir_close(dir);
359
360     if (interrupted) {
361         return 1;
362     }
363
364     skip = baselen + 1;
365
366     for (d = APR_RING_FIRST(&anchor);
367          !interrupted && d != APR_RING_SENTINEL(&anchor, _direntry, link);
368          d=n) {
369         n = APR_RING_NEXT(d, link);
370         base = strrchr(d->basename, '/');
371         if (!base++) {
372             base = d->basename;
373         }
374         ext = strchr(base, '.');
375
376         /* there may be temporary files which may be gone before
377          * processing, always skip these if not in realclean mode
378          */
379         if (!ext && !realclean) {
380             if (!strncasecmp(base, AP_TEMPFILE_BASE, AP_TEMPFILE_BASELEN)
381                 && strlen(base) == AP_TEMPFILE_NAMELEN) {
382                 continue;
383             }
384         }
385
386         /* this may look strange but apr_stat() may return errno which
387          * is system dependent and there may be transient failures,
388          * so just blindly retry for a short while
389          */
390         retries = STAT_ATTEMPTS;
391         status = APR_SUCCESS;
392         do {
393             if (status != APR_SUCCESS) {
394                 apr_sleep(STAT_DELAY);
395             }
396             status = apr_stat(&info, d->basename, DIRINFO, p);
397         } while (status != APR_SUCCESS && !interrupted && --retries);
398
399         /* what may happen here is that apache did create a file which
400          * we did detect but then does delete the file before we can
401          * get file information, so if we don't get any file information
402          * we will ignore the file in this case
403          */
404         if (status != APR_SUCCESS) {
405             if (!realclean && !interrupted) {
406                 continue;
407             }
408             return 1;
409         }
410
411         if (info.filetype == APR_DIR) {
412             if (process_dir(d->basename, pool)) {
413                 return 1;
414             }
415             continue;
416         }
417
418         if (info.filetype != APR_REG) {
419             continue;
420         }
421
422         if (!ext) {
423             if (!strncasecmp(base, AP_TEMPFILE_BASE, AP_TEMPFILE_BASELEN)
424                 && strlen(base) == AP_TEMPFILE_NAMELEN) {
425                 d->basename += skip;
426                 d->type = TEMP;
427                 d->dsize = info.size;
428                 apr_hash_set(h, d->basename, APR_HASH_KEY_STRING, d);
429             }
430             continue;
431         }
432
433         if (!strcasecmp(ext, CACHE_HEADER_SUFFIX)) {
434             *ext = '\0';
435             d->basename += skip;
436             /* if a user manually creates a '.header' file */
437             if (d->basename[0] == '\0') {
438                 continue;
439             }
440             t = apr_hash_get(h, d->basename, APR_HASH_KEY_STRING);
441             if (t) {
442                 d = t;
443             }
444             d->type |= HEADER;
445             d->htime = info.mtime;
446             d->hsize = info.size;
447             apr_hash_set(h, d->basename, APR_HASH_KEY_STRING, d);
448             continue;
449         }
450
451         if (!strcasecmp(ext, CACHE_DATA_SUFFIX)) {
452             *ext = '\0';
453             d->basename += skip;
454             /* if a user manually creates a '.data' file */
455             if (d->basename[0] == '\0') {
456                 continue;
457             }
458             t = apr_hash_get(h, d->basename, APR_HASH_KEY_STRING);
459             if (t) {
460                 d = t;
461             }
462             d->type |= DATA;
463             d->dtime = info.mtime;
464             d->dsize = info.size;
465             apr_hash_set(h, d->basename, APR_HASH_KEY_STRING, d);
466         }
467     }
468
469     if (interrupted) {
470         return 1;
471     }
472
473     path[baselen] = '\0';
474
475     for (i = apr_hash_first(p, h); i && !interrupted; i = apr_hash_next(i)) {
476         void *hvalue;
477
478         apr_hash_this(i, NULL, NULL, &hvalue);
479         d = hvalue;
480
481         switch(d->type) {
482         case HEADERDATA:
483             nextpath = apr_pstrcat(p, path, "/", d->basename,
484                                    CACHE_HEADER_SUFFIX, NULL);
485             if (apr_file_open(&fd, nextpath, APR_READ, APR_OS_DEFAULT,
486                               p) == APR_SUCCESS) {
487                 len = sizeof(disk_cache_info_t);
488                 if (apr_file_read_full(fd, &disk_info, len,
489                                        &len) == APR_SUCCESS) {
490                     apr_file_close(fd);
491                     if (disk_info.format == DISK_FORMAT_VERSION) {
492                         e = apr_palloc(pool, sizeof(ENTRY));
493                         APR_RING_INSERT_TAIL(&root, e, _entry, link);
494                         e->expire = disk_info.expire;
495                         e->response_time = disk_info.response_time;
496                         e->htime = d->htime;
497                         e->dtime = d->dtime;
498                         e->hsize = d->hsize;
499                         e->dsize = d->dsize;
500                         e->basename = apr_palloc(pool,
501                                                  strlen(d->basename) + 1);
502                         strcpy(e->basename, d->basename);
503                         break;
504                     }
505                 }
506                 else {
507                     apr_file_close(fd);
508                 }
509             }
510             /* we have a somehow unreadable headers file which is associated
511              * with a data file. this may be caused by apache currently
512              * rewriting the headers file. thus we may delete the file set
513              * either in realclean mode or if the headers file modification
514              * timestamp is not within a specified positive or negative offset
515              * to the current time.
516              */
517             current = apr_time_now();
518             if (realclean || d->htime < current - deviation
519                 || d->htime > current + deviation) {
520                 delete_entry(path, d->basename, p);
521                 unsolicited += d->hsize;
522                 unsolicited += d->dsize;
523             }
524             break;
525
526         /* single data and header files may be deleted either in realclean
527          * mode or if their modification timestamp is not within a
528          * specified positive or negative offset to the current time.
529          * this handling is necessary due to possible race conditions
530          * between apache and this process
531          */
532         case HEADER:
533             current = apr_time_now();
534             if (realclean || d->htime < current - deviation
535                 || d->htime > current + deviation) {
536                 delete_entry(path, d->basename, p);
537                 unsolicited += d->hsize;
538             }
539             break;
540
541         case DATA:
542             current = apr_time_now();
543             if (realclean || d->dtime < current - deviation
544                 || d->dtime > current + deviation) {
545                 delete_entry(path, d->basename, p);
546                 unsolicited += d->dsize;
547             }
548             break;
549
550         /* temp files may only be deleted in realclean mode which
551          * is asserted above if a tempfile is in the hash array
552          */
553         case TEMP:
554             delete_file(path, d->basename, p);
555             unsolicited += d->dsize;
556             break;
557         }
558     }
559
560     if (interrupted) {
561         return 1;
562     }
563
564     apr_pool_destroy(p);
565
566     if (benice) {
567         apr_sleep(NICE_DELAY);
568     }
569
570     if (interrupted) {
571         return 1;
572     }
573
574     return 0;
575 }
576
577 /*
578  * purge cache entries
579  */
580 static void purge(char *path, apr_pool_t *pool, apr_off_t max)
581 {
582     apr_off_t sum, total, entries, etotal;
583     ENTRY *e, *n, *oldest;
584
585     sum = 0;
586     entries = 0;
587
588     for (e = APR_RING_FIRST(&root);
589          e != APR_RING_SENTINEL(&root, _entry, link);
590          e = APR_RING_NEXT(e, link)) {
591         sum += e->hsize;
592         sum += e->dsize;
593         entries++;
594     }
595
596     total = sum;
597     etotal = entries;
598
599     if (sum <= max) {
600         printstats(total, sum, max, etotal, entries);
601         return;
602     }
603
604     /* process all entries with a timestamp in the future, this may
605      * happen if a wrong system time is corrected
606      */
607
608     for (e = APR_RING_FIRST(&root);
609          e != APR_RING_SENTINEL(&root, _entry, link) && !interrupted;) {
610         n = APR_RING_NEXT(e, link);
611         if (e->response_time > now || e->htime > now || e->dtime > now) {
612             delete_entry(path, e->basename, pool);
613             sum -= e->hsize;
614             sum -= e->dsize;
615             entries--;
616             APR_RING_REMOVE(e, link);
617             if (sum <= max) {
618                 if (!interrupted) {
619                     printstats(total, sum, max, etotal, entries);
620                 }
621                 return;
622             }
623         }
624         e = n;
625     }
626
627     if (interrupted) {
628         return;
629     }
630
631     /* process all entries with are expired */
632     for (e = APR_RING_FIRST(&root);
633          e != APR_RING_SENTINEL(&root, _entry, link) && !interrupted;) {
634         n = APR_RING_NEXT(e, link);
635         if (e->expire != APR_DATE_BAD && e->expire < now) {
636             delete_entry(path, e->basename, pool);
637             sum -= e->hsize;
638             sum -= e->dsize;
639             entries--;
640             APR_RING_REMOVE(e, link);
641             if (sum <= max) {
642                 if (!interrupted) {
643                     printstats(total, sum, max, etotal, entries);
644                 }
645                 return;
646             }
647         }
648         e = n;
649     }
650
651     if (interrupted) {
652          return;
653     }
654
655     /* process remaining entries oldest to newest, the check for an emtpy
656      * ring actually isn't necessary except when the compiler does
657      * corrupt 64bit arithmetics which happend to me once, so better safe
658      * than sorry
659      */
660     while (sum > max && !interrupted && !APR_RING_EMPTY(&root, _entry, link)) {
661         oldest = APR_RING_FIRST(&root);
662
663         for (e = APR_RING_NEXT(oldest, link);
664              e != APR_RING_SENTINEL(&root, _entry, link);
665              e = APR_RING_NEXT(e, link)) {
666             if (e->dtime < oldest->dtime) {
667                 oldest = e;
668             }
669         }
670
671         delete_entry(path, oldest->basename, pool);
672         sum -= oldest->hsize;
673         sum -= oldest->dsize;
674         entries--;
675         APR_RING_REMOVE(oldest, link);
676     }
677
678     if (!interrupted) {
679         printstats(total, sum, max, etotal, entries);
680     }
681 }
682
683 /*
684  * usage info
685  */
686 static void usage(void)
687 {
688     apr_file_printf(errfile, "htcacheclean -- program for cleaning the "
689                              "disk cache.\n");
690     apr_file_printf(errfile, "Usage: htcacheclean [-Dvrn] -pPATH -lLIMIT\n");
691     apr_file_printf(errfile, "       htcacheclean [-Dvrn] -pPATH -LLIMIT\n");
692     apr_file_printf(errfile, "       htcacheclean [-ni] -dINTERVAL -pPATH "
693                              "-lLIMIT\n");
694     apr_file_printf(errfile, "       htcacheclean [-ni] -dINTERVAL -pPATH "
695                              "-LLIMIT\n");
696     apr_file_printf(errfile, "Options:\n");
697     apr_file_printf(errfile, "   -d   Daemonize and repeat cache cleaning "
698                              "every INTERVAL minutes. This\n"
699                              "        option is mutually exclusive with "
700                              "the -D, -v and -r options.\n");
701     apr_file_printf(errfile, "   -D   Do a dry run and don't delete anything. "
702                              "This option is mutually\n"
703                              "        exclusive with the -d option.\n");
704     apr_file_printf(errfile, "   -v   Be verbose and print statistics. "
705                              "This option is mutually exclusive\n"
706                              "        with the -d option.\n");
707     apr_file_printf(errfile, "   -r   Clean thoroughly. This assumes that "
708                              "the Apache web server\n"
709                              "        is not running. This option is "
710                              "mutually exclusive with the -d option.\n");
711     apr_file_printf(errfile, "   -n   Be nice. This causes slower processing "
712                              "in favour of other processes.\n");
713     apr_file_printf(errfile, "   -p   Specify PATH as the root directory of "
714                              "the disk cache.\n");
715     apr_file_printf(errfile, "   -l   Specify LIMIT as the total disk cache "
716                              "size limit in KBytes.\n");
717     apr_file_printf(errfile, "   -L   Specify LIMIT as the total disk cache "
718                              "size limit in MBytes.\n");
719     apr_file_printf(errfile, "   -i   Be intelligent and run only when there "
720                              "was a modification\n"
721                              "        of the disk cache. This option is only "
722                              "possible together with\n"
723                              "        the -d option.\n");
724     exit(1);
725 }
726
727 /*
728  * main
729  */
730 int main(int argc, const char * const argv[])
731 {
732     apr_off_t max;
733     apr_time_t current, repeat, delay, previous;
734     apr_status_t status;
735     apr_pool_t *pool, *instance;
736     apr_getopt_t *o;
737     apr_finfo_t info;
738     int retries, isdaemon, limit_found, intelligent, dowork;
739     char opt;
740     const char *arg;
741     char *proxypath, *path;
742
743     interrupted = 0;
744     repeat = 0;
745     isdaemon = 0;
746     dryrun = 0;
747     limit_found = 0;
748     max = 0;
749     verbose = 0;
750     realclean = 0;
751     benice = 0;
752     intelligent = 0;
753     previous = 0; /* avoid compiler warning */
754     proxypath = NULL;
755
756     if (apr_app_initialize(&argc, &argv, NULL) != APR_SUCCESS) {
757         return 1;
758     }
759     atexit(apr_terminate);
760
761     if (apr_pool_create(&pool, NULL) != APR_SUCCESS) {
762         return 1;
763     }
764     apr_pool_abort_set(oom, pool);
765     apr_file_open_stderr(&errfile, pool);
766     apr_signal(SIGINT, setterm);
767     apr_signal(SIGTERM, setterm);
768
769     apr_getopt_init(&o, pool, argc, argv);
770
771     while (1) {
772         status = apr_getopt(o, "iDnvrd:l:L:p:", &opt, &arg);
773         if (status == APR_EOF) {
774             break;
775         }
776         else if (status != APR_SUCCESS) {
777             usage();
778         }
779         else {
780             switch (opt) {
781             case 'i':
782                 if (intelligent) {
783                     usage();
784                 }
785                 intelligent = 1;
786                 break;
787
788             case 'D':
789                 if (dryrun) {
790                     usage();
791                 }
792                 dryrun = 1;
793                 break;
794
795             case 'n':
796                 if (benice) {
797                     usage();
798                 }
799                 benice = 1;
800                 break;
801
802             case 'v':
803                 if (verbose) {
804                     usage();
805                 }
806                 verbose = 1;
807                 break;
808
809             case 'r':
810                 if (realclean) {
811                     usage();
812                 }
813                 realclean = 1;
814                 break;
815
816             case 'd':
817                 if (isdaemon) {
818                     usage();
819                 }
820                 isdaemon = 1;
821                 repeat = apr_atoi64(arg);
822                 repeat *= SECS_PER_MIN;
823                 repeat *= APR_USEC_PER_SEC;
824                 break;
825
826             case 'l':
827                 if (limit_found) {
828                     usage();
829                 }
830                 limit_found = 1;
831                 max = apr_atoi64(arg);
832                 max *= KBYTE;
833                 break;
834
835             case 'L':
836                 if (limit_found) {
837                     usage();
838                 }
839                 limit_found = 1;
840                 max = apr_atoi64(arg);
841                 max *= MBYTE;
842                 break;
843
844             case 'p':
845                 if (proxypath) {
846                     usage();
847                 }
848                 proxypath = apr_pstrdup(pool, arg);
849                 if (apr_filepath_set(proxypath, pool) != APR_SUCCESS) {
850                     usage();
851                 }
852                 break;
853             } /* switch */
854         } /* else */
855     } /* while */
856
857     if (o->ind != argc) {
858          usage();
859     }
860
861     if (isdaemon && (repeat <= 0 || verbose || realclean || dryrun)) {
862          usage();
863     }
864
865     if (!isdaemon && intelligent) {
866          usage();
867     }
868
869     if (!proxypath || max <= 0) {
870          usage();
871     }
872
873     if (apr_filepath_get(&path, 0, pool) != APR_SUCCESS) {
874         usage();
875     }
876     baselen = strlen(path);
877
878 #ifndef DEBUG
879     if (isdaemon) {
880         apr_file_close(errfile);
881         apr_proc_detach(APR_PROC_DETACH_DAEMONIZE);
882     }
883 #endif
884
885     do {
886         apr_pool_create(&instance, pool);
887
888         now = apr_time_now();
889         APR_RING_INIT(&root, _entry, link);
890         delcount = 0;
891         unsolicited = 0;
892         dowork = 0;
893
894         switch (intelligent) {
895         case 0:
896             dowork = 1;
897             break;
898
899         case 1:
900             retries = STAT_ATTEMPTS;
901             status = APR_SUCCESS;
902
903             do {
904                 if (status != APR_SUCCESS) {
905                     apr_sleep(STAT_DELAY);
906                 }
907                 status = apr_stat(&info, path, APR_FINFO_MTIME, instance);
908             } while (status != APR_SUCCESS && !interrupted && --retries);
909
910             if (status == APR_SUCCESS) {
911                 previous = info.mtime;
912                 intelligent = 2;
913             }
914             dowork = 1;
915             break;
916
917         case 2:
918             retries = STAT_ATTEMPTS;
919             status = APR_SUCCESS;
920
921             do {
922                 if (status != APR_SUCCESS) {
923                     apr_sleep(STAT_DELAY);
924                 }
925                 status = apr_stat(&info, path, APR_FINFO_MTIME, instance);
926             } while (status != APR_SUCCESS && !interrupted && --retries);
927
928             if (status == APR_SUCCESS) {
929                 if (previous != info.mtime) {
930                     dowork = 1;
931                 }
932                 previous = info.mtime;
933                 break;
934             }
935             intelligent = 1;
936             dowork = 1;
937             break;
938         }
939
940         if (dowork && !interrupted) {
941             if (!process_dir(path, instance) && !interrupted) {
942                 purge(path, instance, max);
943             }
944             else if (!isdaemon && !interrupted) {
945                 apr_file_printf(errfile,
946                      "An error occurred, cache cleaning aborted.\n");
947                 return 1;
948             }
949
950             if (intelligent && !interrupted) {
951                 retries = STAT_ATTEMPTS;
952                 status = APR_SUCCESS;
953                 do {
954                     if (status != APR_SUCCESS) {
955                         apr_sleep(STAT_DELAY);
956                     }
957                     status = apr_stat(&info, path, APR_FINFO_MTIME, instance);
958                 } while (status != APR_SUCCESS && !interrupted && --retries);
959
960                 if (status == APR_SUCCESS) {
961                     previous = info.mtime;
962                     intelligent = 2;
963                 }
964                 else {
965                     intelligent = 1;
966                 }
967             }
968         }
969
970         apr_pool_destroy(instance);
971
972         current = apr_time_now();
973         if (current < now) {
974             delay = repeat;
975         }
976         else if (current - now >= repeat) {
977             delay = repeat;
978         }
979         else {
980             delay = now + repeat - current;
981         }
982
983         /* we can't sleep the whole delay time here apiece as this is racy
984          * with respect to interrupt delivery - think about what happens
985          * if we have tested for an interrupt, then get scheduled
986          * before the apr_sleep() call and while waiting for the cpu
987          * we do get an interrupt
988          */
989         if (isdaemon) {
990             while (delay && !interrupted) {
991                 if (delay > APR_USEC_PER_SEC) {
992                     apr_sleep(APR_USEC_PER_SEC);
993                     delay -= APR_USEC_PER_SEC;
994                 }
995                 else {
996                     apr_sleep(delay);
997                     delay = 0;
998                 }
999             }
1000         }
1001     } while (isdaemon && !interrupted);
1002
1003     if (!isdaemon && interrupted) {
1004         apr_file_printf(errfile,
1005                         "Cache cleaning aborted due to user request.\n");
1006         return 1;
1007     }
1008
1009     return 0;
1010 }