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