Moved apache code into a folder to help prepare for packaging where we dont want...
[httpd-crcsyncproxy.git] / apache / support / htcacheclean.c
blobd449b6cfae3475a9a7074bffcb6107fc56041bde
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 * htcacheclean.c: simple program for cleaning of
19 * the disk cache of the Apache HTTP server
21 * Contributed by Andreas Steinmetz <ast domdv.de>
22 * 8 Oct 2004
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"
40 #if APR_HAVE_UNISTD_H
41 #include <unistd.h>
42 #endif
43 #if APR_HAVE_STDLIB_H
44 #include <stdlib.h>
45 #endif
47 /* define the following for debugging */
48 #undef DEBUG
51 * Note: on Linux delays <= 2ms are busy waits without
52 * scheduling, so never use a delay <= 2ms below
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
69 #define DIRINFO (APR_FINFO_MTIME|APR_FINFO_SIZE|APR_FINFO_TYPE|APR_FINFO_LINK)
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;
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;
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 */
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 */
109 /* short program name as called */
110 static const char *shortname = "htcacheclean";
112 #ifdef DEBUG
114 * fake delete for debug purposes
116 #define apr_file_remove fake_file_remove
117 static void fake_file_remove(char *pathname, apr_pool_t *p)
119 apr_finfo_t info;
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);
126 #endif
129 * called on SIGINT or SIGTERM
131 static void setterm(int unused)
133 #ifdef DEBUG
134 apr_file_printf(errfile, "interrupt" APR_EOL_STR);
135 #endif
136 interrupted = 1;
140 * called in out of memory condition
142 static int oom(int unused)
144 static int called = 0;
146 /* be careful to call exit() only once */
147 if (!called) {
148 called = 1;
149 exit(1);
151 return APR_ENOMEM;
155 * print purge statistics
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)
160 char ttype, stype, mtype, utype;
161 apr_off_t tfrag, sfrag, ufrag;
163 if (!verbose) {
164 return;
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;
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;
185 mtype = 'K';
186 max /= KBYTE;
187 if (max >= KBYTE) {
188 mtype = 'M';
189 max /= KBYTE;
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;
202 if (!unsolicited && !ufrag) {
203 ufrag = 1;
205 apr_file_printf(errfile, "unsolicited size %d.%d%c" APR_EOL_STR,
206 (int)(unsolicited), (int)(ufrag), utype);
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));
219 * delete a single file
221 static void delete_file(char *path, char *basename, apr_pool_t *pool)
223 char *nextpath;
224 apr_pool_t *p;
226 if (dryrun) {
227 return;
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);
236 if (benice) {
237 if (++delcount >= DELETE_NICE) {
238 apr_sleep(NICE_DELAY);
239 delcount = 0;
245 * delete cache file set
247 static void delete_entry(char *path, char *basename, apr_pool_t *pool)
249 char *nextpath;
250 apr_pool_t *p;
252 if (dryrun) {
253 return;
256 /* temp pool, otherwise lots of memory could be allocated */
257 apr_pool_create(&p, pool);
259 nextpath = apr_pstrcat(p, path, "/", basename, CACHE_HEADER_SUFFIX, NULL);
260 apr_file_remove(nextpath, p);
262 nextpath = apr_pstrcat(p, path, "/", basename, CACHE_DATA_SUFFIX, NULL);
263 apr_file_remove(nextpath, p);
265 apr_pool_destroy(p);
267 if (benice) {
268 delcount += 2;
269 if (delcount >= DELETE_NICE) {
270 apr_sleep(NICE_DELAY);
271 delcount = 0;
277 * walk the cache directory tree
279 static int process_dir(char *path, apr_pool_t *pool)
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;
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;
304 if (apr_dir_open(&dir, path, p) != APR_SUCCESS) {
305 return 1;
308 while (apr_dir_read(&info, 0, dir) == APR_SUCCESS && !interrupted) {
309 if (!strcmp(info.name, ".") || !strcmp(info.name, "..")) {
310 continue;
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);
317 apr_dir_close(dir);
319 if (interrupted) {
320 return 1;
323 skip = baselen + 1;
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;
333 ext = strchr(base, '.');
335 /* there may be temporary files which may be gone before
336 * processing, always skip these if not in realclean mode
338 if (!ext && !realclean) {
339 if (!strncasecmp(base, AP_TEMPFILE_BASE, AP_TEMPFILE_BASELEN)
340 && strlen(base) == AP_TEMPFILE_NAMELEN) {
341 continue;
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
349 retries = STAT_ATTEMPTS;
350 status = APR_SUCCESS;
351 do {
352 if (status != APR_SUCCESS) {
353 apr_sleep(STAT_DELAY);
355 status = apr_stat(&info, d->basename, DIRINFO, p);
356 } while (status != APR_SUCCESS && !interrupted && --retries);
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
363 if (status != APR_SUCCESS) {
364 if (!realclean && !interrupted) {
365 continue;
367 return 1;
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;
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.
380 if (deldirs && !dryrun) {
381 apr_dir_remove(orig_basename, pool);
383 continue;
386 if (info.filetype != APR_REG) {
387 continue;
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);
398 continue;
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;
408 t = apr_hash_get(h, d->basename, APR_HASH_KEY_STRING);
409 if (t) {
410 d = t;
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;
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;
426 t = apr_hash_get(h, d->basename, APR_HASH_KEY_STRING);
427 if (t) {
428 d = t;
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);
437 if (interrupted) {
438 return 1;
441 path[baselen] = '\0';
443 for (i = apr_hash_first(p, h); i && !interrupted; i = apr_hash_next(i)) {
444 void *hvalue;
445 apr_uint32_t format;
447 apr_hash_this(i, NULL, NULL, &hvalue);
448 d = hvalue;
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;
462 apr_file_seek(fd, APR_SET, &offset);
464 len = sizeof(disk_cache_info_t);
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;
480 else {
481 apr_file_close(fd);
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
488 apr_file_close(fd);
489 apr_file_remove(apr_pstrcat(p, path, "/", d->basename,
490 CACHE_DATA_SUFFIX, NULL),
492 break;
495 else {
496 apr_file_close(fd);
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.
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;
514 break;
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
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;
534 len = sizeof(expires);
536 if (apr_file_read_full(fd, &expires, len,
537 &len) == APR_SUCCESS) {
539 apr_file_close(fd);
541 if (expires < current) {
542 delete_entry(path, d->basename, p);
544 break;
548 apr_file_close(fd);
551 if (realclean || d->htime < current - deviation
552 || d->htime > current + deviation) {
553 delete_entry(path, d->basename, p);
554 unsolicited += d->hsize;
556 break;
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;
565 break;
567 /* temp files may only be deleted in realclean mode which
568 * is asserted above if a tempfile is in the hash array
570 case TEMP:
571 delete_file(path, d->basename, p);
572 unsolicited += d->dsize;
573 break;
577 if (interrupted) {
578 return 1;
581 apr_pool_destroy(p);
583 if (benice) {
584 apr_sleep(NICE_DELAY);
587 if (interrupted) {
588 return 1;
591 return 0;
595 * purge cache entries
597 static void purge(char *path, apr_pool_t *pool, apr_off_t max)
599 apr_off_t sum, total, entries, etotal;
600 ENTRY *e, *n, *oldest;
602 sum = 0;
603 entries = 0;
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++;
613 total = sum;
614 etotal = entries;
616 if (sum <= max) {
617 printstats(total, sum, max, etotal, entries);
618 return;
621 /* process all entries with a timestamp in the future, this may
622 * happen if a wrong system time is corrected
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);
638 return;
641 e = n;
644 if (interrupted) {
645 return;
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);
662 return;
665 e = n;
668 if (interrupted) {
669 return;
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
677 while (sum > max && !interrupted && !APR_RING_EMPTY(&root, _entry, link)) {
678 oldest = APR_RING_FIRST(&root);
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;
688 delete_entry(path, oldest->basename, pool);
689 sum -= oldest->hsize;
690 sum -= oldest->dsize;
691 entries--;
692 APR_RING_REMOVE(oldest, link);
695 if (!interrupted) {
696 printstats(total, sum, max, etotal, entries);
701 * usage info
703 #define NL APR_EOL_STR
704 static void usage(void)
706 apr_file_printf(errfile,
707 "%s -- program for cleaning the disk cache." NL
708 "Usage: %s [-Dvtrn] -pPATH -lLIMIT" NL
709 " %s [-nti] -dINTERVAL -pPATH -lLIMIT" NL
711 "Options:" NL
712 " -d Daemonize and repeat cache cleaning every INTERVAL minutes." NL
713 " This option is mutually exclusive with the -D, -v and -r" NL
714 " options." NL
716 " -D Do a dry run and don't delete anything. This option is mutually" NL
717 " exclusive with the -d option." NL
719 " -v Be verbose and print statistics. This option is mutually" NL
720 " exclusive with the -d option." NL
722 " -r Clean thoroughly. This assumes that the Apache web server is " NL
723 " not running. This option is mutually exclusive with the -d" NL
724 " option and implies -t." NL
726 " -n Be nice. This causes slower processing in favour of other" NL
727 " processes." NL
729 " -t Delete all empty directories. By default only cache files are" NL
730 " removed, however with some configurations the large number of" NL
731 " directories created may require attention." NL
733 " -p Specify PATH as the root directory of the disk cache." NL
735 " -l Specify LIMIT as the total disk cache size limit. Attach 'K'" NL
736 " or 'M' to the number for specifying KBytes or MBytes." NL
738 " -i Be intelligent and run only when there was a modification of" NL
739 " the disk cache. This option is only possible together with the" NL
740 " -d option." NL,
741 shortname,
742 shortname,
743 shortname
746 exit(1);
748 #undef NL
751 * main
753 int main(int argc, const char * const argv[])
755 apr_off_t max;
756 apr_time_t current, repeat, delay, previous;
757 apr_status_t status;
758 apr_pool_t *pool, *instance;
759 apr_getopt_t *o;
760 apr_finfo_t info;
761 int retries, isdaemon, limit_found, intelligent, dowork;
762 char opt;
763 const char *arg;
764 char *proxypath, *path;
766 interrupted = 0;
767 repeat = 0;
768 isdaemon = 0;
769 dryrun = 0;
770 limit_found = 0;
771 max = 0;
772 verbose = 0;
773 realclean = 0;
774 benice = 0;
775 deldirs = 0;
776 intelligent = 0;
777 previous = 0; /* avoid compiler warning */
778 proxypath = NULL;
780 if (apr_app_initialize(&argc, &argv, NULL) != APR_SUCCESS) {
781 return 1;
783 atexit(apr_terminate);
785 if (argc) {
786 shortname = apr_filepath_name_get(argv[0]);
789 if (apr_pool_create(&pool, NULL) != APR_SUCCESS) {
790 return 1;
792 apr_pool_abort_set(oom, pool);
793 apr_file_open_stderr(&errfile, pool);
794 apr_signal(SIGINT, setterm);
795 apr_signal(SIGTERM, setterm);
797 apr_getopt_init(&o, pool, argc, argv);
799 while (1) {
800 status = apr_getopt(o, "iDnvrtd:l:L:p:", &opt, &arg);
801 if (status == APR_EOF) {
802 break;
804 else if (status != APR_SUCCESS) {
805 usage();
807 else {
808 switch (opt) {
809 case 'i':
810 if (intelligent) {
811 usage();
813 intelligent = 1;
814 break;
816 case 'D':
817 if (dryrun) {
818 usage();
820 dryrun = 1;
821 break;
823 case 'n':
824 if (benice) {
825 usage();
827 benice = 1;
828 break;
830 case 't':
831 if (deldirs) {
832 usage();
834 deldirs = 1;
835 break;
837 case 'v':
838 if (verbose) {
839 usage();
841 verbose = 1;
842 break;
844 case 'r':
845 if (realclean) {
846 usage();
848 realclean = 1;
849 deldirs = 1;
850 break;
852 case 'd':
853 if (isdaemon) {
854 usage();
856 isdaemon = 1;
857 repeat = apr_atoi64(arg);
858 repeat *= SECS_PER_MIN;
859 repeat *= APR_USEC_PER_SEC;
860 break;
862 case 'l':
863 if (limit_found) {
864 usage();
866 limit_found = 1;
868 do {
869 apr_status_t rv;
870 char *end;
872 rv = apr_strtoff(&max, arg, &end, 10);
873 if (rv == APR_SUCCESS) {
874 if ((*end == 'K' || *end == 'k') && !end[1]) {
875 max *= KBYTE;
877 else if ((*end == 'M' || *end == 'm') && !end[1]) {
878 max *= MBYTE;
880 else if ((*end == 'G' || *end == 'g') && !end[1]) {
881 max *= GBYTE;
883 else if (*end && /* neither empty nor [Bb] */
884 ((*end != 'B' && *end != 'b') || end[1])) {
885 rv = APR_EGENERAL;
888 if (rv != APR_SUCCESS) {
889 apr_file_printf(errfile, "Invalid limit: %s"
890 APR_EOL_STR APR_EOL_STR, arg);
891 usage();
893 } while(0);
894 break;
896 case 'p':
897 if (proxypath) {
898 usage();
900 proxypath = apr_pstrdup(pool, arg);
901 if (apr_filepath_set(proxypath, pool) != APR_SUCCESS) {
902 usage();
904 break;
905 } /* switch */
906 } /* else */
907 } /* while */
909 if (o->ind != argc) {
910 usage();
913 if (isdaemon && (repeat <= 0 || verbose || realclean || dryrun)) {
914 usage();
917 if (!isdaemon && intelligent) {
918 usage();
921 if (!proxypath || max <= 0) {
922 usage();
925 if (apr_filepath_get(&path, 0, pool) != APR_SUCCESS) {
926 usage();
928 baselen = strlen(path);
930 #ifndef DEBUG
931 if (isdaemon) {
932 apr_file_close(errfile);
933 apr_proc_detach(APR_PROC_DETACH_DAEMONIZE);
935 #endif
937 do {
938 apr_pool_create(&instance, pool);
940 now = apr_time_now();
941 APR_RING_INIT(&root, _entry, link);
942 delcount = 0;
943 unsolicited = 0;
944 dowork = 0;
946 switch (intelligent) {
947 case 0:
948 dowork = 1;
949 break;
951 case 1:
952 retries = STAT_ATTEMPTS;
953 status = APR_SUCCESS;
955 do {
956 if (status != APR_SUCCESS) {
957 apr_sleep(STAT_DELAY);
959 status = apr_stat(&info, path, APR_FINFO_MTIME, instance);
960 } while (status != APR_SUCCESS && !interrupted && --retries);
962 if (status == APR_SUCCESS) {
963 previous = info.mtime;
964 intelligent = 2;
966 dowork = 1;
967 break;
969 case 2:
970 retries = STAT_ATTEMPTS;
971 status = APR_SUCCESS;
973 do {
974 if (status != APR_SUCCESS) {
975 apr_sleep(STAT_DELAY);
977 status = apr_stat(&info, path, APR_FINFO_MTIME, instance);
978 } while (status != APR_SUCCESS && !interrupted && --retries);
980 if (status == APR_SUCCESS) {
981 if (previous != info.mtime) {
982 dowork = 1;
984 previous = info.mtime;
985 break;
987 intelligent = 1;
988 dowork = 1;
989 break;
992 if (dowork && !interrupted) {
993 if (!process_dir(path, instance) && !interrupted) {
994 purge(path, instance, max);
996 else if (!isdaemon && !interrupted) {
997 apr_file_printf(errfile, "An error occurred, cache cleaning "
998 "aborted." APR_EOL_STR);
999 return 1;
1002 if (intelligent && !interrupted) {
1003 retries = STAT_ATTEMPTS;
1004 status = APR_SUCCESS;
1005 do {
1006 if (status != APR_SUCCESS) {
1007 apr_sleep(STAT_DELAY);
1009 status = apr_stat(&info, path, APR_FINFO_MTIME, instance);
1010 } while (status != APR_SUCCESS && !interrupted && --retries);
1012 if (status == APR_SUCCESS) {
1013 previous = info.mtime;
1014 intelligent = 2;
1016 else {
1017 intelligent = 1;
1022 apr_pool_destroy(instance);
1024 current = apr_time_now();
1025 if (current < now) {
1026 delay = repeat;
1028 else if (current - now >= repeat) {
1029 delay = repeat;
1031 else {
1032 delay = now + repeat - current;
1035 /* we can't sleep the whole delay time here apiece as this is racy
1036 * with respect to interrupt delivery - think about what happens
1037 * if we have tested for an interrupt, then get scheduled
1038 * before the apr_sleep() call and while waiting for the cpu
1039 * we do get an interrupt
1041 if (isdaemon) {
1042 while (delay && !interrupted) {
1043 if (delay > APR_USEC_PER_SEC) {
1044 apr_sleep(APR_USEC_PER_SEC);
1045 delay -= APR_USEC_PER_SEC;
1047 else {
1048 apr_sleep(delay);
1049 delay = 0;
1053 } while (isdaemon && !interrupted);
1055 if (!isdaemon && interrupted) {
1056 apr_file_printf(errfile, "Cache cleaning aborted due to user "
1057 "request." APR_EOL_STR);
1058 return 1;
1061 return 0;