Return-Path: Delivered-To: apmail-httpd-cvs-archive@www.apache.org Received: (qmail 22806 invoked from network); 6 Nov 2004 07:45:26 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (209.237.227.199) by minotaur-2.apache.org with SMTP; 6 Nov 2004 07:45:26 -0000 Received: (qmail 98634 invoked by uid 500); 6 Nov 2004 07:45:25 -0000 Delivered-To: apmail-httpd-cvs-archive@httpd.apache.org Received: (qmail 98595 invoked by uid 500); 6 Nov 2004 07:45:24 -0000 Mailing-List: contact cvs-help@httpd.apache.org; run by ezmlm Precedence: bulk Reply-To: dev@httpd.apache.org list-help: list-unsubscribe: list-post: Delivered-To: mailing list cvs@httpd.apache.org Received: (qmail 98582 invoked by uid 500); 6 Nov 2004 07:45:24 -0000 Delivered-To: apmail-httpd-2.0-cvs@apache.org Received: (qmail 98579 invoked by uid 99); 6 Nov 2004 07:45:24 -0000 X-ASF-Spam-Status: No, hits=-10.0 required=10.0 tests=ALL_TRUSTED,NO_REAL_NAME X-Spam-Check-By: apache.org Received: from [209.237.227.194] (HELO minotaur.apache.org) (209.237.227.194) by apache.org (qpsmtpd/0.28) with SMTP; Fri, 05 Nov 2004 23:45:23 -0800 Received: (qmail 22750 invoked by uid 1327); 6 Nov 2004 07:45:21 -0000 Date: 6 Nov 2004 07:45:21 -0000 Message-ID: <20041106074521.22749.qmail@minotaur.apache.org> From: jerenkrantz@apache.org To: httpd-2.0-cvs@apache.org Subject: cvs commit: httpd-2.0/support htcacheclean.c .cvsignore Makefile.in X-Virus-Checked: Checked X-Spam-Rating: minotaur-2.apache.org 1.6.2 0/1000/N jerenkrantz 2004/11/05 23:45:21 Modified: . CHANGES support .cvsignore Makefile.in Added: support htcacheclean.c Log: 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 Revision Changes Path 1.1628 +5 -2 httpd-2.0/CHANGES Index: CHANGES =================================================================== RCS file: /home/cvs/httpd-2.0/CHANGES,v retrieving revision 1.1627 retrieving revision 1.1628 diff -u -u -r1.1627 -r1.1628 --- CHANGES 5 Nov 2004 18:48:15 -0000 1.1627 +++ CHANGES 6 Nov 2004 07:45:21 -0000 1.1628 @@ -2,11 +2,14 @@ [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 @@ 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 1.18 +1 -0 httpd-2.0/support/.cvsignore Index: .cvsignore =================================================================== RCS file: /home/cvs/httpd-2.0/support/.cvsignore,v retrieving revision 1.17 retrieving revision 1.18 diff -u -u -r1.17 -r1.18 --- .cvsignore 5 Jun 2002 14:56:46 -0000 1.17 +++ .cvsignore 6 Nov 2004 07:45:21 -0000 1.18 @@ -7,6 +7,7 @@ htpasswd htdbm htdigest +htcacheclean unescape inc2shtml httpd_monitor 1.41 +4 -1 httpd-2.0/support/Makefile.in Index: Makefile.in =================================================================== RCS file: /home/cvs/httpd-2.0/support/Makefile.in,v retrieving revision 1.40 retrieving revision 1.41 diff -u -u -r1.40 -r1.41 --- Makefile.in 6 Mar 2004 16:47:41 -0000 1.40 +++ Makefile.in 6 Nov 2004 07:45:21 -0000 1.41 @@ -3,7 +3,7 @@ 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: $(suexec_OBJECTS) $(LINK) $(suexec_OBJECTS) +htcacheclean_OBJECTS = htcacheclean.lo +htcacheclean: $(htcacheclean_OBJECTS) + $(LINK) $(htcacheclean_LTFLAGS) $(htcacheclean_OBJECTS) $(PROGRAM_LDADD) 1.1 httpd-2.0/support/htcacheclean.c Index: htcacheclean.c =================================================================== /* 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; }