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>
27 #include "apr_strings.h"
28 #include "apr_file_io.h"
29 #include "apr_file_info.h"
30 #include "apr_pools.h"
32 #include "apr_thread_proc.h"
33 #include "apr_signal.h"
34 #include "apr_getopt.h"
38 #include "apr_buckets.h"
39 #include "../modules/cache/mod_disk_cache.h"
48 /* define the following for debugging */
52 * Note: on Linux delays <= 2ms are busy waits without
53 * scheduling, so never use a delay <= 2ms below
56 #define NICE_DELAY 10000 /* usecs */
57 #define DELETE_NICE 10 /* be nice after this amount of delete ops */
58 #define STAT_ATTEMPTS 10 /* maximum stat attempts for a file */
59 #define STAT_DELAY 5000 /* usecs */
60 #define HEADER 1 /* headers file */
61 #define DATA 2 /* body file */
62 #define TEMP 4 /* temporary file */
63 #define HEADERDATA (HEADER|DATA)
64 #define MAXDEVIATION 3600 /* secs */
65 #define SECS_PER_MIN 60
68 #define GBYTE 1073741824
70 #define DIRINFO (APR_FINFO_MTIME|APR_FINFO_SIZE|APR_FINFO_TYPE|APR_FINFO_LINK)
72 typedef struct _direntry {
73 APR_RING_ENTRY(_direntry) link;
74 int type; /* type of file/fileset: TEMP, HEADER, DATA, HEADERDATA */
75 apr_time_t htime; /* headers file modification time */
76 apr_time_t dtime; /* body file modification time */
77 apr_off_t hsize; /* headers file size */
78 apr_off_t dsize; /* body or temporary file size */
79 char *basename; /* file/fileset base name */
82 typedef struct _entry {
83 APR_RING_ENTRY(_entry) link;
84 apr_time_t expire; /* cache entry exiration time */
85 apr_time_t response_time; /* cache entry time of last response to client */
86 apr_time_t htime; /* headers file modification time */
87 apr_time_t dtime; /* body file modification time */
88 apr_off_t hsize; /* headers file size */
89 apr_off_t dsize; /* body or temporary file size */
90 char *basename; /* fileset base name */
94 static int delcount; /* file deletion count for nice mode */
95 static int interrupted; /* flag: true if SIGINT or SIGTERM occurred */
96 static int realclean; /* flag: true means user said apache is not running */
97 static int verbose; /* flag: true means print statistics */
98 static int benice; /* flag: true means nice mode is activated */
99 static int dryrun; /* flag: true means dry run, don't actually delete
101 static int deldirs; /* flag: true means directories should be deleted */
102 static int baselen; /* string length of the path to the proxy directory */
103 static apr_time_t now; /* start time of this processing run */
105 static apr_file_t *errfile; /* stderr file handle */
106 static apr_off_t unsolicited; /* file size summary for deleted unsolicited
108 static APR_RING_ENTRY(_entry) root; /* ENTRY ring anchor */
110 /* short program name as called */
111 static const char *shortname = "htcacheclean";
113 /* what did we clean? */
128 * fake delete for debug purposes
130 #define apr_file_remove fake_file_remove
131 static void fake_file_remove(char *pathname, apr_pool_t *p)
135 /* stat and printing to simulate some deletion system load and to
136 display what would actually have happened */
137 apr_stat(&info, pathname, DIRINFO, p);
138 apr_file_printf(errfile, "would delete %s" APR_EOL_STR, pathname);
143 * called on SIGINT or SIGTERM
145 static void setterm(int unused)
148 apr_file_printf(errfile, "interrupt" APR_EOL_STR);
154 * called in out of memory condition
156 static int oom(int unused)
158 static int called = 0;
160 /* be careful to call exit() only once */
169 * print purge statistics
171 static void printstats(char *path, struct stats *s)
173 char ttype, stype, mtype, utype;
174 apr_off_t tfrag, sfrag, ufrag;
181 tfrag = ((s->total * 10) / KBYTE) % 10;
183 if (s->total >= KBYTE) {
185 tfrag = ((s->total * 10) / KBYTE) % 10;
190 sfrag = ((s->sum * 10) / KBYTE) % 10;
192 if (s->sum >= KBYTE) {
194 sfrag = ((s->sum * 10) / KBYTE) % 10;
200 if (s->max >= KBYTE) {
205 apr_file_printf(errfile, "Cleaned %s. Statistics:" APR_EOL_STR, path);
208 ufrag = ((unsolicited * 10) / KBYTE) % 10;
209 unsolicited /= KBYTE;
210 if (unsolicited >= KBYTE) {
212 ufrag = ((unsolicited * 10) / KBYTE) % 10;
213 unsolicited /= KBYTE;
215 if (!unsolicited && !ufrag) {
218 apr_file_printf(errfile, "unsolicited size %d.%d%c" APR_EOL_STR,
219 (int)(unsolicited), (int)(ufrag), utype);
221 apr_file_printf(errfile, "size limit %d.0%c" APR_EOL_STR,
222 (int)(s->max), mtype);
223 apr_file_printf(errfile, "total size was %d.%d%c, total size now "
224 "%d.%d%c" APR_EOL_STR,
225 (int)(s->total), (int)(tfrag), ttype,
226 (int)(s->sum), (int)(sfrag), stype);
227 apr_file_printf(errfile, "total entries was %d, total entries now %d"
228 APR_EOL_STR, (int)(s->etotal),
230 apr_file_printf(errfile, "%d entries deleted (%d from future, %d "
231 "expired, %d fresh)" APR_EOL_STR,
232 (int)(s->etotal - s->entries), (int)(s->dfuture),
233 (int)(s->dexpired), (int)(s->dfresh));
237 * delete a single file
239 static void delete_file(char *path, char *basename, apr_pool_t *pool)
248 /* temp pool, otherwise lots of memory could be allocated */
249 apr_pool_create(&p, pool);
250 nextpath = apr_pstrcat(p, path, "/", basename, NULL);
251 apr_file_remove(nextpath, p);
255 if (++delcount >= DELETE_NICE) {
256 apr_sleep(NICE_DELAY);
263 * delete cache file set
265 static void delete_entry(char *path, char *basename, apr_pool_t *pool)
274 /* temp pool, otherwise lots of memory could be allocated */
275 apr_pool_create(&p, pool);
277 nextpath = apr_pstrcat(p, path, "/", basename, CACHE_HEADER_SUFFIX, NULL);
278 apr_file_remove(nextpath, p);
280 nextpath = apr_pstrcat(p, path, "/", basename, CACHE_DATA_SUFFIX, NULL);
281 apr_file_remove(nextpath, p);
287 if (delcount >= DELETE_NICE) {
288 apr_sleep(NICE_DELAY);
295 * walk the cache directory tree
297 static int process_dir(char *path, apr_pool_t *pool)
307 apr_time_t current, deviation;
308 char *nextpath, *base, *ext, *orig_basename;
309 APR_RING_ENTRY(_direntry) anchor;
313 disk_cache_info_t disk_info;
315 APR_RING_INIT(&anchor, _direntry, link);
316 apr_pool_create(&p, pool);
317 h = apr_hash_make(p);
320 deviation = MAXDEVIATION * APR_USEC_PER_SEC;
322 if (apr_dir_open(&dir, path, p) != APR_SUCCESS) {
326 while (apr_dir_read(&info, 0, dir) == APR_SUCCESS && !interrupted) {
327 if (!strcmp(info.name, ".") || !strcmp(info.name, "..")) {
330 d = apr_pcalloc(p, sizeof(DIRENTRY));
331 d->basename = apr_pstrcat(p, path, "/", info.name, NULL);
332 APR_RING_INSERT_TAIL(&anchor, d, _direntry, link);
343 for (d = APR_RING_FIRST(&anchor);
344 !interrupted && d != APR_RING_SENTINEL(&anchor, _direntry, link);
346 n = APR_RING_NEXT(d, link);
347 base = strrchr(d->basename, '/');
351 ext = strchr(base, '.');
353 /* there may be temporary files which may be gone before
354 * processing, always skip these if not in realclean mode
356 if (!ext && !realclean) {
357 if (!strncasecmp(base, AP_TEMPFILE_BASE, AP_TEMPFILE_BASELEN)
358 && strlen(base) == AP_TEMPFILE_NAMELEN) {
363 /* this may look strange but apr_stat() may return an error which
364 * is system dependent and there may be transient failures,
365 * so just blindly retry for a short while
367 retries = STAT_ATTEMPTS;
368 status = APR_SUCCESS;
370 if (status != APR_SUCCESS) {
371 apr_sleep(STAT_DELAY);
373 status = apr_stat(&info, d->basename, DIRINFO, p);
374 } while (status != APR_SUCCESS && !interrupted && --retries);
376 /* what may happen here is that apache did create a file which
377 * we did detect but then does delete the file before we can
378 * get file information, so if we don't get any file information
379 * we will ignore the file in this case
381 if (status != APR_SUCCESS) {
382 if (!realclean && !interrupted) {
388 if (info.filetype == APR_DIR) {
389 /* Make a copy of the basename, as process_dir modifies it */
390 orig_basename = apr_pstrdup(pool, d->basename);
391 if (process_dir(d->basename, pool)) {
395 /* If asked to delete dirs, do so now. We don't care if it fails.
396 * If it fails, it likely means there was something else there.
398 if (deldirs && !dryrun) {
399 apr_dir_remove(orig_basename, pool);
404 if (info.filetype != APR_REG) {
409 if (!strncasecmp(base, AP_TEMPFILE_BASE, AP_TEMPFILE_BASELEN)
410 && strlen(base) == AP_TEMPFILE_NAMELEN) {
413 d->dsize = info.size;
414 apr_hash_set(h, d->basename, APR_HASH_KEY_STRING, d);
419 if (!strcasecmp(ext, CACHE_HEADER_SUFFIX)) {
422 /* if a user manually creates a '.header' file */
423 if (d->basename[0] == '\0') {
426 t = apr_hash_get(h, d->basename, APR_HASH_KEY_STRING);
431 d->htime = info.mtime;
432 d->hsize = info.size;
433 apr_hash_set(h, d->basename, APR_HASH_KEY_STRING, d);
437 if (!strcasecmp(ext, CACHE_DATA_SUFFIX)) {
440 /* if a user manually creates a '.data' file */
441 if (d->basename[0] == '\0') {
444 t = apr_hash_get(h, d->basename, APR_HASH_KEY_STRING);
449 d->dtime = info.mtime;
450 d->dsize = info.size;
451 apr_hash_set(h, d->basename, APR_HASH_KEY_STRING, d);
459 path[baselen] = '\0';
461 for (i = apr_hash_first(p, h); i && !interrupted; i = apr_hash_next(i)) {
465 apr_hash_this(i, NULL, NULL, &hvalue);
470 nextpath = apr_pstrcat(p, path, "/", d->basename,
471 CACHE_HEADER_SUFFIX, NULL);
472 if (apr_file_open(&fd, nextpath, APR_FOPEN_READ | APR_FOPEN_BINARY,
473 APR_OS_DEFAULT, p) == APR_SUCCESS) {
474 len = sizeof(format);
475 if (apr_file_read_full(fd, &format, len,
476 &len) == APR_SUCCESS) {
477 if (format == DISK_FORMAT_VERSION) {
478 apr_off_t offset = 0;
480 apr_file_seek(fd, APR_SET, &offset);
482 len = sizeof(disk_cache_info_t);
484 if (apr_file_read_full(fd, &disk_info, len,
485 &len) == APR_SUCCESS) {
487 e = apr_palloc(pool, sizeof(ENTRY));
488 APR_RING_INSERT_TAIL(&root, e, _entry, link);
489 e->expire = disk_info.expire;
490 e->response_time = disk_info.response_time;
495 e->basename = apr_pstrdup(pool, d->basename);
502 else if (format == VARY_FORMAT_VERSION) {
503 /* This must be a URL that added Vary headers later,
504 * so kill the orphaned .data file
507 apr_file_remove(apr_pstrcat(p, path, "/", d->basename,
508 CACHE_DATA_SUFFIX, NULL),
518 /* we have a somehow unreadable headers file which is associated
519 * with a data file. this may be caused by apache currently
520 * rewriting the headers file. thus we may delete the file set
521 * either in realclean mode or if the headers file modification
522 * timestamp is not within a specified positive or negative offset
523 * to the current time.
525 current = apr_time_now();
526 if (realclean || d->htime < current - deviation
527 || d->htime > current + deviation) {
528 delete_entry(path, d->basename, p);
529 unsolicited += d->hsize;
530 unsolicited += d->dsize;
534 /* single data and header files may be deleted either in realclean
535 * mode or if their modification timestamp is not within a
536 * specified positive or negative offset to the current time.
537 * this handling is necessary due to possible race conditions
538 * between apache and this process
541 current = apr_time_now();
542 nextpath = apr_pstrcat(p, path, "/", d->basename,
543 CACHE_HEADER_SUFFIX, NULL);
544 if (apr_file_open(&fd, nextpath, APR_FOPEN_READ | APR_FOPEN_BINARY,
545 APR_OS_DEFAULT, p) == APR_SUCCESS) {
546 len = sizeof(format);
547 if (apr_file_read_full(fd, &format, len,
548 &len) == APR_SUCCESS) {
549 if (format == VARY_FORMAT_VERSION) {
552 len = sizeof(expires);
554 if (apr_file_read_full(fd, &expires, len,
555 &len) == APR_SUCCESS) {
559 if (expires < current) {
560 delete_entry(path, d->basename, p);
569 if (realclean || d->htime < current - deviation
570 || d->htime > current + deviation) {
571 delete_entry(path, d->basename, p);
572 unsolicited += d->hsize;
577 current = apr_time_now();
578 if (realclean || d->dtime < current - deviation
579 || d->dtime > current + deviation) {
580 delete_entry(path, d->basename, p);
581 unsolicited += d->dsize;
585 /* temp files may only be deleted in realclean mode which
586 * is asserted above if a tempfile is in the hash array
589 delete_file(path, d->basename, p);
590 unsolicited += d->dsize;
602 apr_sleep(NICE_DELAY);
613 * purge cache entries
615 static void purge(char *path, apr_pool_t *pool, apr_off_t max)
617 ENTRY *e, *n, *oldest;
627 for (e = APR_RING_FIRST(&root);
628 e != APR_RING_SENTINEL(&root, _entry, link);
629 e = APR_RING_NEXT(e, link)) {
636 s.etotal = s.entries;
638 if (s.sum <= s.max) {
639 printstats(path, &s);
643 /* process all entries with a timestamp in the future, this may
644 * happen if a wrong system time is corrected
647 for (e = APR_RING_FIRST(&root);
648 e != APR_RING_SENTINEL(&root, _entry, link) && !interrupted;) {
649 n = APR_RING_NEXT(e, link);
650 if (e->response_time > now || e->htime > now || e->dtime > now) {
651 delete_entry(path, e->basename, pool);
656 APR_RING_REMOVE(e, link);
657 if (s.sum <= s.max) {
659 printstats(path, &s);
671 /* process all entries with are expired */
672 for (e = APR_RING_FIRST(&root);
673 e != APR_RING_SENTINEL(&root, _entry, link) && !interrupted;) {
674 n = APR_RING_NEXT(e, link);
675 if (e->expire != APR_DATE_BAD && e->expire < now) {
676 delete_entry(path, e->basename, pool);
681 APR_RING_REMOVE(e, link);
682 if (s.sum <= s.max) {
684 printstats(path, &s);
696 /* process remaining entries oldest to newest, the check for an emtpy
697 * ring actually isn't necessary except when the compiler does
698 * corrupt 64bit arithmetics which happend to me once, so better safe
701 while (s.sum > s.max && !interrupted
702 && !APR_RING_EMPTY(&root, _entry, link)) {
703 oldest = APR_RING_FIRST(&root);
705 for (e = APR_RING_NEXT(oldest, link);
706 e != APR_RING_SENTINEL(&root, _entry, link);
707 e = APR_RING_NEXT(e, link)) {
708 if (e->dtime < oldest->dtime) {
713 delete_entry(path, oldest->basename, pool);
714 s.sum -= oldest->hsize;
715 s.sum -= oldest->dsize;
718 APR_RING_REMOVE(oldest, link);
722 printstats(path, &s);
726 static apr_status_t remove_directory(apr_pool_t *pool, const char *dir)
732 rv = apr_dir_open(&dirp, dir, pool);
733 if (rv == APR_ENOENT) {
736 if (rv != APR_SUCCESS) {
738 apr_file_printf(errfile, "Could not open directory %s: %s" APR_EOL_STR,
739 dir, apr_strerror(rv, errmsg, sizeof errmsg));
743 while (apr_dir_read(&dirent, APR_FINFO_DIRENT | APR_FINFO_TYPE, dirp)
745 if (dirent.filetype == APR_DIR) {
746 if (strcmp(dirent.name, ".") && strcmp(dirent.name, "..")) {
747 rv = remove_directory(pool, apr_pstrcat(pool, dir, "/",
749 /* tolerate the directory not being empty, the cache may have
750 * attempted to recreate the directory in the mean time.
752 if (APR_SUCCESS != rv && APR_ENOTEMPTY != rv) {
757 const char *file = apr_pstrcat(pool, dir, "/", dirent.name, NULL);
758 rv = apr_file_remove(file, pool);
759 if (APR_SUCCESS != rv) {
761 apr_file_printf(errfile,
762 "Could not remove file '%s': %s" APR_EOL_STR, file,
763 apr_strerror(rv, errmsg, sizeof errmsg));
771 if (rv == APR_SUCCESS) {
772 rv = apr_dir_remove(dir, pool);
773 if (APR_ENOTEMPTY == rv) {
776 if (rv != APR_SUCCESS) {
778 apr_file_printf(errfile, "Could not remove directory %s: %s" APR_EOL_STR,
779 dir, apr_strerror(rv, errmsg, sizeof errmsg));
786 static apr_status_t find_directory(apr_pool_t *pool, const char *base,
792 int found = 0, files = 0;
793 const char *header = apr_pstrcat(pool, rest, CACHE_HEADER_SUFFIX, NULL);
794 const char *data = apr_pstrcat(pool, rest, CACHE_DATA_SUFFIX, NULL);
795 const char *vdir = apr_pstrcat(pool, rest, CACHE_HEADER_SUFFIX,
796 CACHE_VDIR_SUFFIX, NULL);
797 const char *dirname = NULL;
799 rv = apr_dir_open(&dirp, base, pool);
800 if (rv != APR_SUCCESS) {
802 apr_file_printf(errfile, "Could not open directory %s: %s" APR_EOL_STR,
803 base, apr_strerror(rv, errmsg, sizeof errmsg));
809 while (apr_dir_read(&dirent, APR_FINFO_DIRENT | APR_FINFO_TYPE, dirp)
811 int len = strlen(dirent.name);
812 int restlen = strlen(rest);
813 if (dirent.filetype == APR_DIR && !strncmp(rest, dirent.name, len)) {
814 dirname = apr_pstrcat(pool, base, "/", dirent.name, NULL);
815 rv = find_directory(pool, dirname, rest + (len < restlen ? len
817 if (APR_SUCCESS == rv) {
821 if (dirent.filetype == APR_DIR) {
822 if (!strcmp(dirent.name, vdir)) {
826 if (dirent.filetype == APR_REG) {
827 if (!strcmp(dirent.name, header) || !strcmp(dirent.name, data)) {
841 remove = apr_pstrcat(pool, base, "/", header, NULL);
842 status = apr_file_remove(remove, pool);
843 if (status != APR_SUCCESS && status != APR_ENOENT) {
845 apr_file_printf(errfile, "Could not remove file %s: %s" APR_EOL_STR,
846 remove, apr_strerror(status, errmsg, sizeof errmsg));
850 remove = apr_pstrcat(pool, base, "/", data, NULL);
851 status = apr_file_remove(remove, pool);
852 if (status != APR_SUCCESS && status != APR_ENOENT) {
854 apr_file_printf(errfile, "Could not remove file %s: %s" APR_EOL_STR,
855 remove, apr_strerror(status, errmsg, sizeof errmsg));
859 status = remove_directory(pool, apr_pstrcat(pool, base, "/", vdir, NULL));
860 if (status != APR_SUCCESS && status != APR_ENOENT) {
866 /* If asked to delete dirs, do so now. We don't care if it fails.
867 * If it fails, it likely means there was something else there.
869 if (dirname && deldirs && !dryrun) {
870 apr_dir_remove(dirname, pool);
881 * Delete a specific URL from the cache.
883 static apr_status_t delete_url(apr_pool_t *pool, const char *proxypath, const char *url)
885 apr_md5_ctx_t context;
886 unsigned char digest[16];
890 static const char enc_table[64] =
891 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_@";
893 apr_md5_init(&context);
894 apr_md5_update(&context, (const unsigned char *) url, strlen(url));
895 apr_md5_final(digest, &context);
897 /* encode 128 bits as 22 characters, using a modified uuencoding
898 * the encoding is 3 bytes -> 4 characters* i.e. 128 bits is
899 * 5 x 3 bytes + 1 byte -> 5 * 4 characters + 2 characters
901 for (i = 0, k = 0; i < 15; i += 3) {
902 x = (digest[i] << 16) | (digest[i + 1] << 8) | digest[i + 2];
903 tmp[k++] = enc_table[x >> 18];
904 tmp[k++] = enc_table[(x >> 12) & 0x3f];
905 tmp[k++] = enc_table[(x >> 6) & 0x3f];
906 tmp[k++] = enc_table[x & 0x3f];
911 tmp[k++] = enc_table[x >> 2]; /* use up 6 bits */
912 tmp[k++] = enc_table[(x << 4) & 0x3f];
915 /* automatically find the directory levels */
916 return find_directory(pool, proxypath, tmp);
922 #define NL APR_EOL_STR
923 static void usage(const char *error)
926 apr_file_printf(errfile, "%s error: %s\n", shortname, error);
928 apr_file_printf(errfile,
929 "%s -- program for cleaning the disk cache." NL
930 "Usage: %s [-Dvtrn] -pPATH -lLIMIT [-PPIDFILE]" NL
931 " %s [-nti] -dINTERVAL -pPATH -lLIMIT [-PPIDFILE]" NL
932 " %s [-Dvt] -pPATH URL ..." NL
935 " -d Daemonize and repeat cache cleaning every INTERVAL minutes." NL
936 " This option is mutually exclusive with the -D, -v and -r" NL
939 " -D Do a dry run and don't delete anything. This option is mutually" NL
940 " exclusive with the -d option." NL
942 " -v Be verbose and print statistics. This option is mutually" NL
943 " exclusive with the -d option." NL
945 " -r Clean thoroughly. This assumes that the Apache web server is " NL
946 " not running. This option is mutually exclusive with the -d" NL
947 " option and implies -t." NL
949 " -n Be nice. This causes slower processing in favour of other" NL
952 " -t Delete all empty directories. By default only cache files are" NL
953 " removed, however with some configurations the large number of" NL
954 " directories created may require attention." NL
956 " -p Specify PATH as the root directory of the disk cache." NL
958 " -P Specify PIDFILE as the file to write the pid to." NL
960 " -l Specify LIMIT as the total disk cache size limit. Attach 'K'" NL
961 " or 'M' to the number for specifying KBytes or MBytes." NL
963 " -i Be intelligent and run only when there was a modification of" NL
964 " the disk cache. This option is only possible together with the" NL
967 "Should an URL be provided on the command line, the URL will be" NL
968 "deleted from the cache. A reverse proxied URL is made up as follows:" NL
969 "http://<hostname>:<port><path>?[query]. So, for the path \"/\" on the" NL
970 "host \"localhost\" and port 80, the URL to delete becomes" NL
971 "\"http://localhost:80/?\". Note the '?' in the URL must always be" NL
972 "specified explicitly, whether a query string is present or not." NL,
983 static void usage_repeated_arg(apr_pool_t *pool, char option) {
984 usage(apr_psprintf(pool,
985 "The option '%c' cannot be specified more than once",
989 static void log_pid(apr_pool_t *pool, const char *pidfilename, apr_file_t **pidfile)
993 pid_t mypid = getpid();
995 if (APR_SUCCESS == (status = apr_file_open(pidfile, pidfilename,
996 APR_FOPEN_WRITE | APR_FOPEN_CREATE | APR_FOPEN_TRUNCATE |
997 APR_FOPEN_DELONCLOSE, APR_FPROT_UREAD | APR_FPROT_UWRITE |
998 APR_FPROT_GREAD | APR_FPROT_WREAD, pool))) {
999 apr_file_printf(*pidfile, "%" APR_PID_T_FMT APR_EOL_STR, mypid);
1003 apr_file_printf(errfile,
1004 "Could not write the pid file '%s': %s" APR_EOL_STR,
1006 apr_strerror(status, errmsg, sizeof errmsg));
1015 int main(int argc, const char * const argv[])
1018 apr_time_t current, repeat, delay, previous;
1019 apr_status_t status;
1020 apr_pool_t *pool, *instance;
1023 apr_file_t *pidfile;
1024 int retries, isdaemon, limit_found, intelligent, dowork;
1027 char *proxypath, *path, *pidfilename;
1041 previous = 0; /* avoid compiler warning */
1045 if (apr_app_initialize(&argc, &argv, NULL) != APR_SUCCESS) {
1048 atexit(apr_terminate);
1051 shortname = apr_filepath_name_get(argv[0]);
1054 if (apr_pool_create(&pool, NULL) != APR_SUCCESS) {
1057 apr_pool_abort_set(oom, pool);
1058 apr_file_open_stderr(&errfile, pool);
1059 apr_signal(SIGINT, setterm);
1060 apr_signal(SIGTERM, setterm);
1062 apr_getopt_init(&o, pool, argc, argv);
1065 status = apr_getopt(o, "iDnvrtd:l:L:p:P:", &opt, &arg);
1066 if (status == APR_EOF) {
1069 else if (status != APR_SUCCESS) {
1076 usage_repeated_arg(pool, opt);
1083 usage_repeated_arg(pool, opt);
1090 usage_repeated_arg(pool, opt);
1097 usage_repeated_arg(pool, opt);
1104 usage_repeated_arg(pool, opt);
1111 usage_repeated_arg(pool, opt);
1119 usage_repeated_arg(pool, opt);
1122 repeat = apr_atoi64(arg);
1123 repeat *= SECS_PER_MIN;
1124 repeat *= APR_USEC_PER_SEC;
1129 usage_repeated_arg(pool, opt);
1137 rv = apr_strtoff(&max, arg, &end, 10);
1138 if (rv == APR_SUCCESS) {
1139 if ((*end == 'K' || *end == 'k') && !end[1]) {
1142 else if ((*end == 'M' || *end == 'm') && !end[1]) {
1145 else if ((*end == 'G' || *end == 'g') && !end[1]) {
1148 else if (*end && /* neither empty nor [Bb] */
1149 ((*end != 'B' && *end != 'b') || end[1])) {
1153 if (rv != APR_SUCCESS) {
1154 usage(apr_psprintf(pool, "Invalid limit: %s"
1155 APR_EOL_STR APR_EOL_STR, arg));
1162 usage_repeated_arg(pool, opt);
1164 proxypath = apr_pstrdup(pool, arg);
1165 if ((status = apr_filepath_set(proxypath, pool)) != APR_SUCCESS) {
1166 usage(apr_psprintf(pool, "Could not set filepath to '%s': %s",
1167 proxypath, apr_strerror(status, errmsg, sizeof errmsg)));
1173 usage_repeated_arg(pool, opt);
1175 pidfilename = apr_pstrdup(pool, arg);
1186 if (o->ind < argc) {
1190 usage("Option -d cannot be used with URL arguments, aborting");
1193 usage("Option -i cannot be used with URL arguments, aborting");
1196 usage("Option -l cannot be used with URL arguments, aborting");
1198 while (o->ind < argc) {
1199 status = delete_url(pool, proxypath, argv[o->ind]);
1200 if (APR_SUCCESS == status) {
1202 apr_file_printf(errfile, "Removed: %s" APR_EOL_STR,
1207 else if (APR_ENOENT == status) {
1209 apr_file_printf(errfile, "Not cached: %s" APR_EOL_STR,
1215 apr_file_printf(errfile, "Error while removed: %s" APR_EOL_STR,
1222 return error ? 1 : deleted ? 0 : 2;
1225 if (isdaemon && repeat <= 0) {
1226 usage("Option -d must be greater than zero");
1229 if (isdaemon && (verbose || realclean || dryrun)) {
1230 usage("Option -d cannot be used with -v, -r or -D");
1233 if (!isdaemon && intelligent) {
1234 usage("Option -i cannot be used without -d");
1238 usage("Option -p must be specified");
1242 usage("Option -l must be greater than zero");
1245 if (apr_filepath_get(&path, 0, pool) != APR_SUCCESS) {
1246 usage(apr_psprintf(pool, "Could not get the filepath: %s",
1247 apr_strerror(status, errmsg, sizeof errmsg)));
1249 baselen = strlen(path);
1252 log_pid(pool, pidfilename, &pidfile); /* before daemonizing, so we
1259 apr_file_close(errfile);
1262 apr_file_close(pidfile); /* delete original pidfile only in parent */
1264 apr_proc_detach(APR_PROC_DETACH_DAEMONIZE);
1266 log_pid(pool, pidfilename, &pidfile);
1272 apr_pool_create(&instance, pool);
1274 now = apr_time_now();
1275 APR_RING_INIT(&root, _entry, link);
1280 switch (intelligent) {
1286 retries = STAT_ATTEMPTS;
1287 status = APR_SUCCESS;
1290 if (status != APR_SUCCESS) {
1291 apr_sleep(STAT_DELAY);
1293 status = apr_stat(&info, path, APR_FINFO_MTIME, instance);
1294 } while (status != APR_SUCCESS && !interrupted && --retries);
1296 if (status == APR_SUCCESS) {
1297 previous = info.mtime;
1304 retries = STAT_ATTEMPTS;
1305 status = APR_SUCCESS;
1308 if (status != APR_SUCCESS) {
1309 apr_sleep(STAT_DELAY);
1311 status = apr_stat(&info, path, APR_FINFO_MTIME, instance);
1312 } while (status != APR_SUCCESS && !interrupted && --retries);
1314 if (status == APR_SUCCESS) {
1315 if (previous != info.mtime) {
1318 previous = info.mtime;
1326 if (dowork && !interrupted) {
1327 if (!process_dir(path, instance) && !interrupted) {
1328 purge(path, instance, max);
1330 else if (!isdaemon && !interrupted) {
1331 apr_file_printf(errfile, "An error occurred, cache cleaning "
1332 "aborted." APR_EOL_STR);
1336 if (intelligent && !interrupted) {
1337 retries = STAT_ATTEMPTS;
1338 status = APR_SUCCESS;
1340 if (status != APR_SUCCESS) {
1341 apr_sleep(STAT_DELAY);
1343 status = apr_stat(&info, path, APR_FINFO_MTIME, instance);
1344 } while (status != APR_SUCCESS && !interrupted && --retries);
1346 if (status == APR_SUCCESS) {
1347 previous = info.mtime;
1356 apr_pool_destroy(instance);
1358 current = apr_time_now();
1359 if (current < now) {
1362 else if (current - now >= repeat) {
1366 delay = now + repeat - current;
1369 /* we can't sleep the whole delay time here apiece as this is racy
1370 * with respect to interrupt delivery - think about what happens
1371 * if we have tested for an interrupt, then get scheduled
1372 * before the apr_sleep() call and while waiting for the cpu
1373 * we do get an interrupt
1376 while (delay && !interrupted) {
1377 if (delay > APR_USEC_PER_SEC) {
1378 apr_sleep(APR_USEC_PER_SEC);
1379 delay -= APR_USEC_PER_SEC;
1387 } while (isdaemon && !interrupted);
1389 if (!isdaemon && interrupted) {
1390 apr_file_printf(errfile, "Cache cleaning aborted due to user "
1391 "request." APR_EOL_STR);