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