htcacheclean.c revision 9fe74ffcdea85800f04a7222f716f78ae60cce51
/* 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;
}