#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"
#include "../modules/cache/mod_disk_cache.h"
#if APR_HAVE_UNISTD_H
#include <unistd.h>
#endif
#if APR_HAVE_STDLIB_H
#include <stdlib.h>
#endif
#undef DEBUG
#define NICE_DELAY 10000
#define DELETE_NICE 10
#define STAT_ATTEMPTS 10
#define STAT_DELAY 5000
#define HEADER 1
#define DATA 2
#define TEMP 4
#define HEADERDATA (HEADER|DATA)
#define MAXDEVIATION 3600
#define SECS_PER_MIN 60
#define KBYTE 1024
#define MBYTE 1048576
#define GBYTE 1073741824
#define DIRINFO (APR_FINFO_MTIME|APR_FINFO_SIZE|APR_FINFO_TYPE|APR_FINFO_LINK)
typedef struct _direntry {
APR_RING_ENTRY(_direntry) link;
int type;
apr_time_t htime;
apr_time_t dtime;
apr_off_t hsize;
apr_off_t dsize;
char *basename;
} DIRENTRY;
typedef struct _entry {
APR_RING_ENTRY(_entry) link;
apr_time_t expire;
apr_time_t response_time;
apr_time_t htime;
apr_time_t dtime;
apr_off_t hsize;
apr_off_t dsize;
char *basename;
} ENTRY;
static int delcount;
static int interrupted;
static int realclean;
static int verbose;
static int benice;
static int dryrun;
static int deldirs;
static int baselen;
static apr_time_t now;
static apr_file_t *errfile;
static apr_off_t unsolicited;
static APR_RING_ENTRY(_entry) root;
static const char *shortname = "htcacheclean";
#ifdef DEBUG
#define apr_file_remove fake_file_remove
static void fake_file_remove(char *pathname, apr_pool_t *p)
{
apr_finfo_t info;
apr_stat(&info, pathname, DIRINFO, p);
apr_file_printf(errfile, "would delete %s" APR_EOL_STR, pathname);
}
#endif
static void setterm(int unused)
{
#ifdef DEBUG
apr_file_printf(errfile, "interrupt" APR_EOL_STR);
#endif
interrupted = 1;
}
static int oom(int unused)
{
static int called = 0;
if (!called) {
called = 1;
exit(1);
}
return APR_ENOMEM;
}
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, stype, mtype, utype;
apr_off_t tfrag, sfrag, 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:" APR_EOL_STR);
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" APR_EOL_STR,
(int)(unsolicited), (int)(ufrag), utype);
}
apr_file_printf(errfile, "size limit %d.0%c" APR_EOL_STR,
(int)(max), mtype);
apr_file_printf(errfile, "total size was %d.%d%c, total size now "
"%d.%d%c" APR_EOL_STR,
(int)(total), (int)(tfrag), ttype, (int)(sum),
(int)(sfrag), stype);
apr_file_printf(errfile, "total entries was %d, total entries now %d"
APR_EOL_STR, (int)(etotal), (int)(entries));
}
static void delete_file(char *path, char *basename, apr_pool_t *pool)
{
char *nextpath;
apr_pool_t *p;
if (dryrun) {
return;
}
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;
}
}
}
static void delete_entry(char *path, char *basename, apr_pool_t *pool)
{
char *nextpath;
apr_pool_t *p;
if (dryrun) {
return;
}
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;
}
}
}
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, deviation;
char *nextpath, *base, *ext, *orig_basename;
APR_RING_ENTRY(_direntry) anchor;
DIRENTRY *d, *t, *n;
ENTRY *e;
int skip, 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) {
if (!strcmp(info.name, ".") || !strcmp(info.name, "..")) {
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, '.');
if (!ext && !realclean) {
if (!strncasecmp(base, AP_TEMPFILE_BASE, AP_TEMPFILE_BASELEN)
&& strlen(base) == AP_TEMPFILE_NAMELEN) {
continue;
}
}
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);
if (status != APR_SUCCESS) {
if (!realclean && !interrupted) {
continue;
}
return 1;
}
if (info.filetype == APR_DIR) {
orig_basename = apr_pstrdup(pool, d->basename);
if (process_dir(d->basename, pool)) {
return 1;
}
if (deldirs && !dryrun) {
apr_dir_remove(orig_basename, pool);
}
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 (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 (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)) {
void *hvalue;
apr_uint32_t format;
apr_hash_this(i, NULL, NULL, &hvalue);
d = hvalue;
switch(d->type) {
case HEADERDATA:
nextpath = apr_pstrcat(p, path, "/", d->basename,
CACHE_HEADER_SUFFIX, NULL);
if (apr_file_open(&fd, nextpath, APR_FOPEN_READ | APR_FOPEN_BINARY,
APR_OS_DEFAULT, p) == APR_SUCCESS) {
len = sizeof(format);
if (apr_file_read_full(fd, &format, len,
&len) == APR_SUCCESS) {
if (format == DISK_FORMAT_VERSION) {
apr_off_t offset = 0;
apr_file_seek(fd, APR_SET, &offset);
len = sizeof(disk_cache_info_t);
if (apr_file_read_full(fd, &disk_info, len,
&len) == APR_SUCCESS) {
apr_file_close(fd);
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_pstrdup(pool, d->basename);
break;
}
else {
apr_file_close(fd);
}
}
else if (format == VARY_FORMAT_VERSION) {
apr_file_close(fd);
apr_file_remove(apr_pstrcat(p, path, "/", d->basename,
CACHE_DATA_SUFFIX, NULL),
p);
}
}
else {
apr_file_close(fd);
}
}
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;
case HEADER:
current = apr_time_now();
nextpath = apr_pstrcat(p, path, "/", d->basename,
CACHE_HEADER_SUFFIX, NULL);
if (apr_file_open(&fd, nextpath, APR_FOPEN_READ | APR_FOPEN_BINARY,
APR_OS_DEFAULT, p) == APR_SUCCESS) {
len = sizeof(format);
if (apr_file_read_full(fd, &format, len,
&len) == APR_SUCCESS) {
if (format == VARY_FORMAT_VERSION) {
apr_time_t expires;
len = sizeof(expires);
apr_file_read_full(fd, &expires, len, &len);
apr_file_close(fd);
if (expires < current) {
delete_entry(path, d->basename, p);
}
break;
}
}
apr_file_close(fd);
}
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;
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;
}
static void purge(char *path, apr_pool_t *pool, apr_off_t max)
{
apr_off_t sum, total, entries, etotal;
ENTRY *e, *n, *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;
}
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;
}
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;
}
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);
}
}
#define NL APR_EOL_STR
static void usage(const char *error)
{
if (error) {
apr_file_printf(errfile, "%s error: %s\n", shortname, error);
}
apr_file_printf(errfile,
"%s -- program for cleaning the disk cache." NL
"Usage: %s [-Dvtrn] -pPATH -lLIMIT [-PPIDFILE]" NL
" %s [-nti] -dINTERVAL -pPATH -lLIMIT [-PPIDFILE]" NL
NL
"Options:" NL
" -d Daemonize and repeat cache cleaning every INTERVAL minutes." NL
" This option is mutually exclusive with the -D, -v and -r" NL
" options." NL
NL
" -D Do a dry run and don't delete anything. This option is mutually" NL
" exclusive with the -d option." NL
NL
" -v Be verbose and print statistics. This option is mutually" NL
" exclusive with the -d option." NL
NL
" -r Clean thoroughly. This assumes that the Apache web server is " NL
" not running. This option is mutually exclusive with the -d" NL
" option and implies -t." NL
NL
" -n Be nice. This causes slower processing in favour of other" NL
" processes." NL
NL
" -t Delete all empty directories. By default only cache files are" NL
" removed, however with some configurations the large number of" NL
" directories created may require attention." NL
NL
" -p Specify PATH as the root directory of the disk cache." NL
NL
" -P Specify PIDFILE as the file to write the pid to." NL
NL
" -l Specify LIMIT as the total disk cache size limit. Attach 'K'" NL
" or 'M' to the number for specifying KBytes or MBytes." NL
NL
" -i Be intelligent and run only when there was a modification of" NL
" the disk cache. This option is only possible together with the" NL
" -d option." NL,
shortname,
shortname,
shortname
);
exit(1);
}
#undef NL
static void log_pid(apr_pool_t *pool, const char *pidfilename, apr_file_t **pidfile)
{
apr_status_t status;
char errmsg[120];
pid_t mypid = getpid();
if (APR_SUCCESS == (status = apr_file_open(pidfile, pidfilename,
APR_FOPEN_WRITE | APR_FOPEN_CREATE | APR_FOPEN_TRUNCATE |
APR_FOPEN_DELONCLOSE, APR_FPROT_UREAD | APR_FPROT_UWRITE |
APR_FPROT_GREAD | APR_FPROT_WREAD, pool))) {
apr_file_printf(*pidfile, "%" APR_PID_T_FMT APR_EOL_STR, mypid);
}
else {
if (errfile) {
apr_file_printf(errfile,
"Could not write the pid file '%s': %s" APR_EOL_STR,
pidfilename,
apr_strerror(status, errmsg, sizeof errmsg));
}
exit(1);
}
}
int main(int argc, const char * const argv[])
{
apr_off_t max;
apr_time_t current, repeat, delay, previous;
apr_status_t status;
apr_pool_t *pool, *instance;
apr_getopt_t *o;
apr_finfo_t info;
apr_file_t *pidfile;
int retries, isdaemon, limit_found, intelligent, dowork;
char opt;
const char *arg;
char *proxypath, *path, *pidfilename;
char errmsg[1024];
interrupted = 0;
repeat = 0;
isdaemon = 0;
dryrun = 0;
limit_found = 0;
max = 0;
verbose = 0;
realclean = 0;
benice = 0;
deldirs = 0;
intelligent = 0;
previous = 0;
proxypath = NULL;
pidfilename = NULL;
if (apr_app_initialize(&argc, &argv, NULL) != APR_SUCCESS) {
return 1;
}
atexit(apr_terminate);
if (argc) {
shortname = apr_filepath_name_get(argv[0]);
}
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, "iDnvrtd:l:L:p:P:", &opt, &arg);
if (status == APR_EOF) {
break;
}
else if (status != APR_SUCCESS) {
usage(NULL);
}
else {
switch (opt) {
case 'i':
if (intelligent) {
usage(apr_psprintf(pool, "The option '%c' cannot be specified more than once", (int)opt));
}
intelligent = 1;
break;
case 'D':
if (dryrun) {
usage(apr_psprintf(pool, "The option '%c' cannot be specified more than once", (int)opt));
}
dryrun = 1;
break;
case 'n':
if (benice) {
usage(apr_psprintf(pool, "The option '%c' cannot be specified more than once", (int)opt));
}
benice = 1;
break;
case 't':
if (deldirs) {
usage(apr_psprintf(pool, "The option '%c' cannot be specified more than once", (int)opt));
}
deldirs = 1;
break;
case 'v':
if (verbose) {
usage(apr_psprintf(pool, "The option '%c' cannot be specified more than once", (int)opt));
}
verbose = 1;
break;
case 'r':
if (realclean) {
usage(apr_psprintf(pool, "The option '%c' cannot be specified more than once", (int)opt));
}
realclean = 1;
deldirs = 1;
break;
case 'd':
if (isdaemon) {
usage(apr_psprintf(pool, "The option '%c' cannot be specified more than once", (int)opt));
}
isdaemon = 1;
repeat = apr_atoi64(arg);
repeat *= SECS_PER_MIN;
repeat *= APR_USEC_PER_SEC;
break;
case 'l':
if (limit_found) {
usage(apr_psprintf(pool, "The option '%c' cannot be specified more than once", (int)opt));
}
limit_found = 1;
do {
apr_status_t rv;
char *end;
rv = apr_strtoff(&max, arg, &end, 10);
if (rv == APR_SUCCESS) {
if ((*end == 'K' || *end == 'k') && !end[1]) {
max *= KBYTE;
}
else if ((*end == 'M' || *end == 'm') && !end[1]) {
max *= MBYTE;
}
else if ((*end == 'G' || *end == 'g') && !end[1]) {
max *= GBYTE;
}
else if (*end &&
((*end != 'B' && *end != 'b') || end[1])) {
rv = APR_EGENERAL;
}
}
if (rv != APR_SUCCESS) {
usage(apr_psprintf(pool, "Invalid limit: %s"
APR_EOL_STR APR_EOL_STR, arg));
}
} while(0);
break;
case 'p':
if (proxypath) {
usage(apr_psprintf(pool, "The option '%c' cannot be specified more than once", (int)opt));
}
proxypath = apr_pstrdup(pool, arg);
if ((status = apr_filepath_set(proxypath, pool)) != APR_SUCCESS) {
usage(apr_psprintf(pool, "Could not set filepath to '%s': %s",
proxypath, apr_strerror(status, errmsg, sizeof errmsg)));
}
break;
case 'P':
if (pidfilename) {
usage(apr_psprintf(pool, "The option '%c' cannot be specified more than once", (int)opt));
}
pidfilename = apr_pstrdup(pool, arg);
break;
}
}
}
if (argc <= 1) {
usage(NULL);
}
if (o->ind != argc) {
usage("Additional parameters specified on the command line, aborting");
}
if (isdaemon && repeat <= 0) {
usage("Option -d must be greater than zero");
}
if (isdaemon && (verbose || realclean || dryrun)) {
usage("Option -d cannot be used with -v, -r or -D");
}
if (!isdaemon && intelligent) {
usage("Option -i cannot be used without -d");
}
if (!proxypath) {
usage("Option -p must be specified");
}
if (max <= 0) {
usage("Option -l must be greater than zero");
}
if (apr_filepath_get(&path, 0, pool) != APR_SUCCESS) {
usage(apr_psprintf(pool, "Could not get the filepath: %s",
apr_strerror(status, errmsg, sizeof errmsg)));
}
baselen = strlen(path);
if (pidfilename) {
log_pid(pool, pidfilename, &pidfile);
}
#ifndef DEBUG
if (isdaemon) {
apr_file_close(errfile);
errfile = NULL;
if (pidfilename) {
apr_file_close(pidfile);
}
apr_proc_detach(APR_PROC_DETACH_DAEMONIZE);
if (pidfilename) {
log_pid(pool, pidfilename, &pidfile);
}
}
#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." APR_EOL_STR);
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;
}
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." APR_EOL_STR);
return 1;
}
return 0;
}