--- /dev/null
+/* 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 <ast@domdv.de>
+ * 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 <unistd.h>
+#endif
+#if APR_HAVE_STDLIB_H
+#include <stdlib.h>
+#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;
+}