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