From: Justin Erenkrantz Date: Sat, 6 Nov 2004 07:45:21 +0000 (+0000) Subject: Add htcacheclean to function as cleanup utility (daemonizable) for X-Git-Tag: 2.1.1~57 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=53119755575d7fcfaec429f375ecc7d521379419;p=apache Add htcacheclean to function as cleanup utility (daemonizable) for mod_disk_cache. (Justin did a whole bunch of style tweaks, and some minor functionality tweaks to get it to function on Solaris.) Submitted by: Andreas Steinmetz Reviewed by: Justin Erenkrantz git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@105698 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/CHANGES b/CHANGES index 376b31188c..03a73def80 100644 --- a/CHANGES +++ b/CHANGES @@ -2,11 +2,14 @@ Changes with Apache 2.1.0-dev [Remove entries to the current 2.0 section below, when backported] + *) Add htcacheclean to support/ for assistance with mod_disk_cache. + [Andreas Steinmetz] + *) mod_authnz_ldap: Added the directive "Requires ldap-filter" that allows the module to authorize a user based on a complex LDAP search filter. [Brad Nicholes] - + *) SECURITY: CAN-2004-0942, Fix for memory consumption DoS. [Joe Orton] @@ -17,7 +20,7 @@ Changes with Apache 2.1.0-dev allows the module to only authorize a user if the attribute value specified matches the value of the user object. PR 31913 [Ryan Morgan ] - + *) Allow mod_authnz_ldap authorization functionality to be used without requiring the user to also be authenticated through mod_authnz_ldap. This allows other authentication modules to diff --git a/support/.cvsignore b/support/.cvsignore index e4018d42be..69916d9286 100644 --- a/support/.cvsignore +++ b/support/.cvsignore @@ -7,6 +7,7 @@ rotatelogs htpasswd htdbm htdigest +htcacheclean unescape inc2shtml httpd_monitor diff --git a/support/Makefile.in b/support/Makefile.in index 8fca31c52b..25dda638c1 100644 --- a/support/Makefile.in +++ b/support/Makefile.in @@ -3,7 +3,7 @@ DISTCLEAN_TARGETS = apxs apachectl dbmmanage log_server_status \ CLEAN_TARGETS = suexec -PROGRAMS = htpasswd htdigest rotatelogs logresolve ab checkgid htdbm +PROGRAMS = htpasswd htdigest rotatelogs logresolve ab checkgid htdbm htcacheclean TARGETS = $(PROGRAMS) PROGRAM_LDADD = $(UTIL_LDFLAGS) $(PROGRAM_DEPENDENCIES) $(EXTRA_LIBS) $(AP_LIBS) @@ -62,3 +62,6 @@ suexec_OBJECTS = suexec.lo suexec: $(suexec_OBJECTS) $(LINK) $(suexec_OBJECTS) +htcacheclean_OBJECTS = htcacheclean.lo +htcacheclean: $(htcacheclean_OBJECTS) + $(LINK) $(htcacheclean_LTFLAGS) $(htcacheclean_OBJECTS) $(PROGRAM_LDADD) diff --git a/support/htcacheclean.c b/support/htcacheclean.c new file mode 100644 index 0000000000..7a8227b61a --- /dev/null +++ b/support/htcacheclean.c @@ -0,0 +1,1013 @@ +/* Copyright 2001-2004 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * htcacheclean.c: simple program for cleaning of + * the disk cache of the Apache HTTP server + * + * Contributed by Andreas Steinmetz + * 8 Oct 2004 + */ + +#include "apr.h" +#include "apr_lib.h" +#include "apr_strings.h" +#include "apr_file_io.h" +#include "apr_file_info.h" +#include "apr_pools.h" +#include "apr_hash.h" +#include "apr_thread_proc.h" +#include "apr_signal.h" +#include "apr_getopt.h" +#include "apr_ring.h" +#include "apr_date.h" + +#if APR_HAVE_UNISTD_H +#include +#endif +#if APR_HAVE_STDLIB_H +#include +#endif + +/* mod_disk_cache.c extract start */ + +#define DISK_FORMAT_VERSION 0 +typedef struct { + /* Indicates the format of the header struct stored on-disk. */ + int format; + /* The HTTP status code returned for this response. */ + int status; + /* The size of the entity name that follows. */ + apr_size_t name_len; + /* The number of times we've cached this entity. */ + apr_size_t entity_version; + /* Miscellaneous time values. */ + apr_time_t date; + apr_time_t expire; + apr_time_t request_time; + apr_time_t response_time; +} disk_cache_info_t; + +#define CACHE_HEADER_SUFFIX ".header" +#define CACHE_DATA_SUFFIX ".data" +/* mod_disk_cache.c extract end */ + +/* mod_disk_cache.c related definitions start */ + +/* + * this is based on #define AP_TEMPFILE "/aptmpXXXXXX" + * + * the above definition could be reworked into the following: + * + * #define AP_TEMPFILE_PREFIX "/" + * #define AP_TEMPFILE_BASE "aptmp" + * #define AP_TEMPFILE_SUFFIX "XXXXXX" + * #define AP_TEMPFILE_BASELEN strlen(AP_TEMPFILE_BASE) + * #define AP_TEMPFILE_NAMELEN strlen(AP_TEMPFILE_BASE AP_TEMPFILE_SUFFIX) + * #define AP_TEMPFILE AP_TEMPFILE_PREFIX AP_TEMPFILE_BASE AP_TEMPFILE_SUFFIX + * + * these definitions would then match the definitions below: + */ + +#define AP_TEMPFILE_BASE "aptmp" +#define AP_TEMPFILE_SUFFIX "XXXXXX" +#define AP_TEMPFILE_BASELEN strlen(AP_TEMPFILE_BASE) +#define AP_TEMPFILE_NAMELEN strlen(AP_TEMPFILE_BASE AP_TEMPFILE_SUFFIX) + +/* mod_disk_cache.c related definitions end */ + +/* define the following for debugging */ +#undef DEBUG + +/* + * Note: on Linux delays <= 2ms are busy waits without + * scheduling, so never use a delay <= 2ms below + */ + +#define NICE_DELAY 10000 /* usecs */ +#define DELETE_NICE 10 /* be nice after this amount of delete ops */ +#define STAT_ATTEMPTS 10 /* maximum stat attempts for a file */ +#define STAT_DELAY 5000 /* usecs */ +#define HEADER 1 /* headers file */ +#define DATA 2 /* body file */ +#define TEMP 4 /* temporary file */ +#define HEADERDATA (HEADER|DATA) +#define MAXDEVIATION 3600 /* secs */ +#define SECS_PER_MIN 60 +#define KBYTE 1024 +#define MBYTE 1048576 + +#define DIRINFO (APR_FINFO_MTIME|APR_FINFO_SIZE|APR_FINFO_TYPE|APR_FINFO_LINK) + +typedef struct _direntry +{ + APR_RING_ENTRY(_direntry) link; + /* type of file/fileset: TEMP, HEADER, DATA, HEADERDATA */ + int type; + /* headers file modification time */ + apr_time_t htime; + /* body file modification time */ + apr_time_t dtime; + /* headers file size */ + apr_off_t hsize; + /* body or temporary file size */ + apr_off_t dsize; + /* file/fileset base name */ + char *basename; +} DIRENTRY; + +typedef struct _entry +{ + APR_RING_ENTRY(_entry) link; + /* cache entry exiration time */ + apr_time_t expire; + /* cache entry time of last response to client */ + apr_time_t response_time; + /* headers file modification time */ + apr_time_t htime; + /* body file modification time */ + apr_time_t dtime; + /* headers file size */ + apr_off_t hsize; + /* body or temporary file size */ + apr_off_t dsize; + /* fileset base name */ + char *basename; +} ENTRY; + +/* file deletion count for nice mode */ +static int delcount; +/* flag: true if SIGINT or SIGTERM occurred */ +static int interrupted; +/* flag: true means user said apache is not running */ +static int realclean; +/* flag: true means print statistics */ +static int verbose; +/* flag: true means nice mode is activated */ +static int benice; +/* flag: true means dry run, don't actually delete anything */ +static int dryrun; +/* string length of the path to the proxy directory */ +static int baselen; +/* start time of this processing run */ +static apr_time_t now; +/* stderr file handle */ +static apr_file_t *errfile; +/* file size summary for deleted unsolicited files */ +static apr_off_t unsolicited; +/* ENTRY ring anchor */ +static APR_RING_ENTRY(_entry) root; + +/* + * fake delete for debug purposes + */ + +#ifdef DEBUG +#define apr_file_remove fake_file_remove +static void fake_file_remove(char *pathname, apr_pool_t *p) +{ + apr_finfo_t info; + + /* stat and printing to simulate some deletion system load and to + display what would actually have happened */ + apr_stat(&info, pathname, DIRINFO, p); + apr_file_printf(errfile, "would delete %s\n", pathname); +} +#endif + +/* + * called on SIGINT or SIGTERM + */ + +static void setterm(int unused) +{ +#ifdef DEBUG + apr_file_printf(errfile, "interrupt\n"); +#endif + interrupted = 1; +} + +/* + * called in out of memory condition + */ +static int oom(int unused) +{ + static int called = 0; + + /* be careful to call exit() only once */ + if (!called) { + called = 1; + exit(1); + } + return APR_ENOMEM; +} + +/* + * print purge statistics + */ +static void printstats(apr_off_t total, apr_off_t sum, apr_off_t max, + apr_off_t etotal, apr_off_t entries) +{ + char ttype; + char stype; + char mtype; + char utype; + apr_off_t tfrag; + apr_off_t sfrag; + apr_off_t ufrag; + + if (!verbose) { + return; + } + + ttype = 'K'; + tfrag = ((total * 10) / KBYTE) % 10; + total /= KBYTE; + if (total >= KBYTE) { + ttype = 'M'; + tfrag = ((total * 10) / KBYTE) % 10; + total /= KBYTE; + } + + stype = 'K'; + sfrag = ((sum * 10) / KBYTE) % 10; + sum /= KBYTE; + if (sum >= KBYTE) { + stype = 'M'; + sfrag = ((sum * 10) / KBYTE) % 10; + sum /= KBYTE; + } + + mtype = 'K'; + max /= KBYTE; + if (max >= KBYTE) { + mtype = 'M'; + max /= KBYTE; + } + + apr_file_printf(errfile, "Statistics:\n"); + if (unsolicited) { + utype = 'K'; + ufrag = ((unsolicited * 10) / KBYTE) % 10; + unsolicited /= KBYTE; + if (unsolicited >= KBYTE) { + utype = 'M'; + ufrag = ((unsolicited * 10) / KBYTE) % 10; + unsolicited /= KBYTE; + } + if (!unsolicited && !ufrag) { + ufrag = 1; + } + apr_file_printf(errfile, "unsolicited size %d.%d%c\n", + (int)(unsolicited), (int)(ufrag), utype); + } + apr_file_printf(errfile, "size limit %d.0%c\n", (int)(max), mtype); + apr_file_printf(errfile, + "total size was %d.%d%c, total size now %d.%d%c\n", + (int)(total), (int)(tfrag), ttype, (int)(sum), (int)(sfrag), stype); + apr_file_printf(errfile, "total entries was %d, total entries now %d\n", + (int)(etotal), (int)(entries)); +} + +/* + * delete a single file + */ +static void delete_file(char *path, char *basename, apr_pool_t *pool) +{ + char *nextpath; + apr_pool_t *p; + + if (dryrun) { + return; + } + + /* temp pool, otherwise lots of memory could be allocated */ + apr_pool_create(&p, pool); + nextpath = apr_pstrcat(p, path, "/", basename, NULL); + apr_file_remove(nextpath, p); + apr_pool_destroy(p); + + if (benice) { + if (++delcount >= DELETE_NICE) { + apr_sleep(NICE_DELAY); + delcount = 0; + } + } +} + +/* + * delete cache file set + */ +static void delete_entry(char *path, char *basename, apr_pool_t *pool) +{ + char *nextpath; + apr_pool_t *p; + + if (dryrun) { + return; + } + + /* temp pool, otherwise lots of memory could be allocated */ + apr_pool_create(&p, pool); + + nextpath = apr_pstrcat(p, path, "/", basename, CACHE_HEADER_SUFFIX, NULL); + apr_file_remove(nextpath, p); + + nextpath = apr_pstrcat(p, path, "/", basename, CACHE_DATA_SUFFIX, NULL); + apr_file_remove(nextpath, p); + + apr_pool_destroy(p); + + if (benice) { + delcount += 2; + if (delcount >= DELETE_NICE) { + apr_sleep(NICE_DELAY); + delcount = 0; + } + } +} + +/* + * walk the cache directory tree + */ +static int process_dir(char *path, apr_pool_t *pool) +{ + apr_dir_t *dir; + apr_pool_t *p; + apr_hash_t *h; + apr_hash_index_t *i; + apr_file_t *fd; + apr_status_t status; + apr_finfo_t info; + apr_size_t len; + apr_time_t current; + apr_time_t deviation; + char *nextpath; + char *base; + char *ext; + APR_RING_ENTRY(_direntry) anchor; + DIRENTRY *d; + DIRENTRY *t; + DIRENTRY *n; + ENTRY *e; + int skip; + int retries; + disk_cache_info_t disk_info; + + APR_RING_INIT(&anchor, _direntry, link); + apr_pool_create(&p, pool); + h = apr_hash_make(p); + fd = NULL; + skip = 0; + deviation = MAXDEVIATION * APR_USEC_PER_SEC; + + if (apr_dir_open(&dir, path, p) != APR_SUCCESS) { + return 1; + } + + while (apr_dir_read(&info, 0, dir) == APR_SUCCESS && !interrupted) { + /* skip first two entries which will always be '.' and '..' */ + if (skip < 2) { + skip++; + continue; + } + d = apr_pcalloc(p, sizeof(DIRENTRY)); + d->basename = apr_pstrcat(p, path, "/", info.name, NULL); + APR_RING_INSERT_TAIL(&anchor, d, _direntry, link); + } + + apr_dir_close(dir); + + if (interrupted) { + return 1; + } + + skip = baselen + 1; + + for(d = APR_RING_FIRST(&anchor); + !interrupted && d != APR_RING_SENTINEL(&anchor, _direntry, link); + d=n) { + n = APR_RING_NEXT(d, link); + base = strrchr(d->basename, '/'); + if (!base++) { + base = d->basename; + } + ext = strchr(base, '.'); + + /* there may be temporary files which may be gone before + processing, always skip these if not in realclean mode */ + if (!ext && !realclean) { + if (!strncasecmp(base, AP_TEMPFILE_BASE, AP_TEMPFILE_BASELEN) && + strlen(base) == AP_TEMPFILE_NAMELEN) + continue; + } + + /* this may look strange but apr_stat() may return errno which + is system dependent and there may be transient failures, + so just blindly retry for a short while */ + retries = STAT_ATTEMPTS; + status = APR_SUCCESS; + do + { + if (status != APR_SUCCESS) { + apr_sleep(STAT_DELAY); + } + status = apr_stat(&info, d->basename, DIRINFO, p); + } while (status != APR_SUCCESS && !interrupted && --retries); + + /* what may happen here is that apache did create a file which + we did detect but then does delete the file before we can + get file information, so if we don't get any file information + we will ignore the file in this case */ + if (status != APR_SUCCESS) { + if (!realclean && !interrupted) { + continue; + } + return 1; + } + + if (info.filetype == APR_DIR) { + if (process_dir(d->basename, pool)) { + return 1; + } + continue; + } + + if (info.filetype != APR_REG) { + continue; + } + + if (!ext) { + if (!strncasecmp(base, AP_TEMPFILE_BASE, AP_TEMPFILE_BASELEN) && + strlen(base) == AP_TEMPFILE_NAMELEN) { + d->basename += skip; + d->type = TEMP; + d->dsize = info.size; + apr_hash_set(h, d->basename, APR_HASH_KEY_STRING, d); + } + continue; + } + + if (!strcasecmp(ext, CACHE_HEADER_SUFFIX)) { + *ext = '\0'; + d->basename += skip; + /* if a user manually creates a '.header' file */ + if (d->basename[0] == '\0') + continue; + t = apr_hash_get(h, d->basename, APR_HASH_KEY_STRING); + if (t) + d = t; + d->type |= HEADER; + d->htime = info.mtime; + d->hsize = info.size; + apr_hash_set(h, d->basename, APR_HASH_KEY_STRING, d); + continue; + } + + if (!strcasecmp(ext, CACHE_DATA_SUFFIX)) { + *ext = '\0'; + d->basename += skip; + /* if a user manually creates a '.data' file */ + if (d->basename[0] == '\0') + continue; + t = apr_hash_get(h, d->basename, APR_HASH_KEY_STRING); + if (t) { + d = t; + } + d->type |= DATA; + d->dtime = info.mtime; + d->dsize = info.size; + apr_hash_set(h, d->basename, APR_HASH_KEY_STRING, d); + } + } + + if (interrupted) { + return 1; + } + + path[baselen] = '\0'; + + for (i = apr_hash_first(p, h); i && !interrupted; i = apr_hash_next(i)) { + apr_hash_this(i, NULL, NULL, (void **)(&d)); + switch(d->type) { + case HEADERDATA: + nextpath = apr_pstrcat(p, path, "/", d->basename, + CACHE_HEADER_SUFFIX, NULL); + if (apr_file_open(&fd, nextpath, APR_READ, APR_OS_DEFAULT, p) + == APR_SUCCESS) { + len = sizeof(disk_cache_info_t); + if (apr_file_read_full(fd, &disk_info, len, &len) + == APR_SUCCESS) { + apr_file_close(fd); + if (disk_info.format == DISK_FORMAT_VERSION) { + e = apr_palloc(pool, sizeof(ENTRY)); + APR_RING_INSERT_TAIL(&root, e, _entry, link); + e->expire = disk_info.expire; + e->response_time = disk_info.response_time; + e->htime = d->htime; + e->dtime = d->dtime; + e->hsize = d->hsize; + e->dsize = d->dsize; + e->basename = apr_palloc(pool, + strlen(d->basename) + 1); + strcpy(e->basename, d->basename); + break; + } + } + else { + apr_file_close(fd); + } + } + /* we have a somehow unreadable headers file which is associated + * with a data file. this may be caused by apache currently + * rewriting the headers file. thus we may delete the file set + * either in realclean mode or if the headers file modification + * timestamp is not within a specified positive or negative offset + * to the current time. + */ + current = apr_time_now(); + if (realclean || d->htime < current - deviation || + d->htime > current + deviation) { + delete_entry(path, d->basename, p); + unsolicited += d->hsize; + unsolicited += d->dsize; + } + break; + + /* single data and header files may be deleted either in realclean + * mode or if their modification timestamp is not within a + * specified positive or negative offset to the current time. + * this handling is necessary due to possible race conditions + * between apache and this process + */ + case HEADER: + current = apr_time_now(); + if (realclean || d->htime < current - deviation || + d->htime > current + deviation) { + delete_entry(path, d->basename, p); + unsolicited += d->hsize; + } + break; + + case DATA: + current = apr_time_now(); + if (realclean || d->dtime < current - deviation || + d->dtime > current + deviation) { + delete_entry(path, d->basename, p); + unsolicited += d->dsize; + } + break; + + /* temp files may only be deleted in realclean mode which + * is asserted above if a tempfile is in the hash array + */ + case TEMP: + delete_file(path, d->basename, p); + unsolicited += d->dsize; + break; + } + } + + if (interrupted) { + return 1; + } + + apr_pool_destroy(p); + + if (benice) { + apr_sleep(NICE_DELAY); + } + + if (interrupted) { + return 1; + } + + return 0; +} + +/* + * purge cache entries + */ +static void purge(char *path, apr_pool_t *pool, apr_off_t max) +{ + apr_off_t sum; + apr_off_t total; + apr_off_t entries; + apr_off_t etotal; + ENTRY *e; + ENTRY *n; + ENTRY *oldest; + + sum = 0; + entries = 0; + + for (e = APR_RING_FIRST(&root); + e != APR_RING_SENTINEL(&root, _entry, link); + e = APR_RING_NEXT(e, link)) { + sum += e->hsize; + sum += e->dsize; + entries++; + } + + total = sum; + etotal = entries; + + if (sum <= max) { + printstats(total, sum, max, etotal, entries); + return; + } + + /* process all entries with a timestamp in the future, this may + * happen if a wrong system time is corrected + */ + + for (e = APR_RING_FIRST(&root); + e != APR_RING_SENTINEL(&root, _entry, link) && !interrupted;) { + n = APR_RING_NEXT(e, link); + if (e->response_time > now || e->htime > now || e->dtime > now) { + delete_entry(path, e->basename, pool); + sum -= e->hsize; + sum -= e->dsize; + entries--; + APR_RING_REMOVE(e, link); + if (sum <= max) { + if (!interrupted) { + printstats(total, sum, max, etotal, entries); + } + return; + } + } + e = n; + } + + if (interrupted) { + return; + } + + /* process all entries with are expired */ + for (e = APR_RING_FIRST(&root); + e != APR_RING_SENTINEL(&root, _entry, link) && !interrupted;) { + n = APR_RING_NEXT(e, link); + if (e->expire != APR_DATE_BAD && e->expire < now) { + delete_entry(path, e->basename, pool); + sum -= e->hsize; + sum -= e->dsize; + entries--; + APR_RING_REMOVE(e, link); + if (sum <= max) { + if (!interrupted) + printstats(total, sum, max, etotal, entries); + return; + } + } + e = n; + } + + if (interrupted) { + return; + } + + /* process remaining entries oldest to newest, the check for an emtpy + * ring actually isn't necessary except when the compiler does + * corrupt 64bit arithmetics which happend to me once, so better safe + * than sorry + */ + while (sum > max && !interrupted && !APR_RING_EMPTY(&root, _entry, link)) { + oldest = APR_RING_FIRST(&root); + + for(e = APR_RING_NEXT(oldest, link); + e != APR_RING_SENTINEL(&root, _entry, link); + e = APR_RING_NEXT(e, link)) { + if (e->dtime < oldest->dtime) { + oldest = e; + } + } + + delete_entry(path, oldest->basename, pool); + sum -= oldest->hsize; + sum -= oldest->dsize; + entries--; + APR_RING_REMOVE(oldest, link); + } + + if (!interrupted) { + printstats(total, sum, max, etotal, entries); + } +} + +/* + * usage info + */ +static void usage(void) +{ + apr_file_printf(errfile, "htcacheclean -- program for cleaning the " + "disk cache.\n"); + apr_file_printf(errfile, "Usage: htcacheclean [-Dvrn] -pPATH -lLIMIT\n"); + apr_file_printf(errfile, " htcacheclean [-Dvrn] -pPATH -LLIMIT\n"); + apr_file_printf(errfile, " htcacheclean [-ni] -dINTERVAL -pPATH " + "-lLIMIT\n"); + apr_file_printf(errfile, " htcacheclean [-ni] -dINTERVAL -pPATH " + "-LLIMIT\n"); + apr_file_printf(errfile, "Options:\n"); + apr_file_printf(errfile, " -d Daemonize and repeat cache cleaning " + "every INTERVAL minutes. This\n" + " option is mutually exclusive with " + "the -D, -v and -r options.\n"); + apr_file_printf(errfile, " -D Do a dry run and don't delete anything. " + "This option is mutually\n" + " exclusive with the -d option.\n"); + apr_file_printf(errfile, " -v Be verbose and print statistics. " + "This option is mutually exclusive\n" + " with the -d option.\n"); + apr_file_printf(errfile, " -r Clean thoroughly. This assumes that " + "the Apache web server\n" + " is not running. This option is " + "mutually exclusive with the -d option.\n"); + apr_file_printf(errfile, " -n Be nice. This causes slower processing " + "in favour of other processes.\n"); + apr_file_printf(errfile, " -p Specify PATH as the root directory of " + "the disk cache.\n"); + apr_file_printf(errfile, " -l Specify LIMIT as the total disk cache " + "size limit in KBytes.\n"); + apr_file_printf(errfile, " -L Specify LIMIT as the total disk cache " + "size limit in MBytes.\n"); + apr_file_printf(errfile, " -i Be intelligent and run only when there " + "was a modification\n" + " of the disk cache. This option is only " + "possible together with\n" + " the -d option.\n"); + exit(1); +} + +/* + * main + */ +int main(int argc, const char * const argv[]) +{ + apr_off_t max; + apr_time_t current; + apr_time_t repeat; + apr_time_t delay; + apr_time_t previous; + apr_status_t status; + apr_pool_t *pool; + apr_pool_t *instance; + apr_getopt_t *o; + apr_finfo_t info; + int retries; + int isdaemon; + int limit_found; + int intelligent; + int dowork; + char opt; + const char *arg; + char *proxypath; + char *path; + + interrupted = 0; + repeat = 0; + isdaemon = 0; + dryrun = 0; + limit_found = 0; + max = 0; + verbose = 0; + realclean = 0; + benice = 0; + intelligent = 0; + proxypath = NULL; + + if (apr_app_initialize(&argc, &argv, NULL) != APR_SUCCESS) { + return 1; + } + atexit(apr_terminate); + + if (apr_pool_create(&pool, NULL) != APR_SUCCESS) { + return 1; + } + apr_pool_abort_set(oom, pool); + apr_file_open_stderr(&errfile, pool); + apr_signal(SIGINT, setterm); + apr_signal(SIGTERM, setterm); + + apr_getopt_init(&o, pool, argc, argv); + + while (1) { + status = apr_getopt(o, "iDnvrd:l:L:p:", &opt, &arg); + if (status == APR_EOF) + break; + else if (status == APR_SUCCESS) + switch (opt) { + case 'i': + if (intelligent) + usage(); + intelligent = 1; + break; + case 'D': + if (dryrun) + usage(); + dryrun = 1; + break; + case 'n': + if (benice) + usage(); + benice = 1; + break; + case 'v': + if (verbose) + usage(); + verbose = 1; + break; + case 'r': + if (realclean) + usage(); + realclean = 1; + break; + case 'd': + if (isdaemon) + usage(); + isdaemon = 1; + repeat = apr_atoi64(arg); + repeat *= SECS_PER_MIN; + repeat *= APR_USEC_PER_SEC; + break; + case 'l': + if (limit_found) + usage(); + limit_found = 1; + max = apr_atoi64(arg); + max *= KBYTE; + break; + case 'L': + if (limit_found) + usage(); + limit_found = 1; + max = apr_atoi64(arg); + max *= MBYTE; + break; + case 'p': + if (proxypath) + usage(); + proxypath = apr_pstrdup(pool, arg); + if (apr_filepath_set(proxypath, pool) != APR_SUCCESS) + usage(); + break; + } + else usage(); + } + + if (o->ind != argc) { + usage(); + } + + if (isdaemon && (repeat <= 0 || verbose || realclean || dryrun)) { + usage(); + } + + if (!isdaemon && intelligent) { + usage(); + } + + if (!proxypath || max <= 0) { + usage(); + } + + if (apr_filepath_get(&path, 0, pool) != APR_SUCCESS) { + usage(); + } + baselen = strlen(path); + +#ifndef DEBUG + if (isdaemon) { + apr_file_close(errfile); + apr_proc_detach(APR_PROC_DETACH_DAEMONIZE); + } +#endif + + do + { + apr_pool_create(&instance, pool); + + now = apr_time_now(); + APR_RING_INIT(&root, _entry, link); + delcount = 0; + unsolicited = 0; + dowork = 0; + + switch (intelligent) { + case 0: + dowork = 1; + break; + + case 1: + retries = STAT_ATTEMPTS; + status = APR_SUCCESS; + do + { + if (status != APR_SUCCESS) + apr_sleep(STAT_DELAY); + status = apr_stat(&info, path, APR_FINFO_MTIME, instance); + } while (status != APR_SUCCESS && !interrupted && --retries); + if (status == APR_SUCCESS) { + previous = info.mtime; + intelligent = 2; + } + dowork = 1; + break; + + case 2: + retries = STAT_ATTEMPTS; + status = APR_SUCCESS; + do + { + if (status != APR_SUCCESS) + apr_sleep(STAT_DELAY); + status = apr_stat(&info, path, APR_FINFO_MTIME, instance); + } while (status != APR_SUCCESS && !interrupted && --retries); + if (status == APR_SUCCESS) { + if (previous != info.mtime) + dowork = 1; + previous = info.mtime; + break; + } + intelligent = 1; + dowork = 1; + break; + } + + if (dowork && !interrupted) { + if (!process_dir(path, instance) && !interrupted) { + purge(path, instance, max); + } else if (!isdaemon && !interrupted) { + apr_file_printf(errfile, + "An error occurred, cache cleaning aborted.\n"); + return 1; + } + + if (intelligent && !interrupted) { + retries = STAT_ATTEMPTS; + status = APR_SUCCESS; + do + { + if (status != APR_SUCCESS) + apr_sleep(STAT_DELAY); + status = apr_stat(&info, path, APR_FINFO_MTIME, instance); + } while (status != APR_SUCCESS && !interrupted && --retries); + if (status == APR_SUCCESS) { + previous = info.mtime; + intelligent = 2; + } + else + intelligent = 1; + } + } + + apr_pool_destroy(instance); + + current = apr_time_now(); + if (current < now) { + delay = repeat; + } else if (current - now >= repeat) { + delay = repeat; + } else { + delay = now + repeat - current; + } + + /* we can't sleep the whole delay time here apiece as this is racy + * with respect to interrupt delivery - think about what happens + * if we have tested for an interrupt, then get scheduled + * before the apr_sleep() call and while waiting for the cpu + * we do get an interrupt + */ + if (isdaemon) { + while (delay && !interrupted) { + if (delay > APR_USEC_PER_SEC) { + apr_sleep(APR_USEC_PER_SEC); + delay -= APR_USEC_PER_SEC; + } else { + apr_sleep(delay); + delay = 0; + } + } + } + } while (isdaemon && !interrupted); + + if (!isdaemon && interrupted) { + apr_file_printf(errfile, + "Cache cleaning aborted due to user request.\n"); + return 1; + } + + return 0; +}