| /* Licensed to the Apache Software Foundation (ASF) under one or more | |
| * contributor license agreements. See the NOTICE file distributed with | |
| * this work for additional information regarding copyright ownership. | |
| * The ASF licenses this file to You 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_md5.h" | |
| #include "apr_ring.h" | |
| #include "apr_date.h" | |
| #include "apr_buckets.h" | |
| #include "../modules/cache/cache_common.h" | |
| #include "../modules/cache/cache_disk_common.h" | |
| #if APR_HAVE_UNISTD_H | |
| #include <unistd.h> | |
| #endif | |
| #if APR_HAVE_STDLIB_H | |
| #include <stdlib.h> | |
| #endif | |
| /* 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 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; /* type of file/fileset: TEMP, HEADER, DATA, HEADERDATA */ | |
| apr_time_t htime; /* headers file modification time */ | |
| apr_time_t dtime; /* body file modification time */ | |
| apr_off_t hsize; /* headers file size */ | |
| apr_off_t dsize; /* body or temporary file size */ | |
| char *basename; /* file/fileset base name */ | |
| } DIRENTRY; | |
| typedef struct _entry { | |
| APR_RING_ENTRY(_entry) link; | |
| apr_time_t expire; /* cache entry exiration time */ | |
| apr_time_t response_time; /* cache entry time of last response to client */ | |
| apr_time_t htime; /* headers file modification time */ | |
| apr_time_t dtime; /* body file modification time */ | |
| apr_off_t hsize; /* headers file size */ | |
| apr_off_t dsize; /* body or temporary file size */ | |
| char *basename; /* fileset base name */ | |
| } ENTRY; | |
| static int delcount; /* file deletion count for nice mode */ | |
| static int interrupted; /* flag: true if SIGINT or SIGTERM occurred */ | |
| static int realclean; /* flag: true means user said apache is not running */ | |
| static int verbose; /* flag: true means print statistics */ | |
| static int benice; /* flag: true means nice mode is activated */ | |
| static int dryrun; /* flag: true means dry run, don't actually delete | |
| anything */ | |
| static int deldirs; /* flag: true means directories should be deleted */ | |
| static int listurls; /* flag: true means list cached urls */ | |
| static int listextended;/* flag: true means list cached urls */ | |
| static int baselen; /* string length of the path to the proxy directory */ | |
| static apr_time_t now; /* start time of this processing run */ | |
| static apr_file_t *errfile; /* stderr file handle */ | |
| static apr_file_t *outfile; /* stdout file handle */ | |
| static apr_off_t unsolicited; /* file size summary for deleted unsolicited | |
| files */ | |
| static ENTRY root; /* ENTRY ring anchor */ | |
| /* short program name as called */ | |
| static const char *shortname = "htcacheclean"; | |
| /* what did we clean? */ | |
| struct stats { | |
| apr_off_t total; | |
| apr_off_t sum; | |
| apr_off_t max; | |
| apr_off_t ntotal; | |
| apr_off_t nodes; | |
| apr_off_t inodes; | |
| apr_off_t etotal; | |
| apr_off_t entries; | |
| apr_off_t dfuture; | |
| apr_off_t dexpired; | |
| apr_off_t dfresh; | |
| }; | |
| #ifdef DEBUG | |
| /* | |
| * fake delete for debug purposes | |
| */ | |
| #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" APR_EOL_STR, pathname); | |
| } | |
| #endif | |
| /* | |
| * called on SIGINT or SIGTERM | |
| */ | |
| static void setterm(int unused) | |
| { | |
| #ifdef DEBUG | |
| apr_file_printf(errfile, "interrupt" APR_EOL_STR); | |
| #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(char *path, struct stats *s) | |
| { | |
| char ttype, stype, mtype, utype; | |
| apr_off_t tfrag, sfrag, ufrag; | |
| if (!verbose) { | |
| return; | |
| } | |
| ttype = 'K'; | |
| tfrag = ((s->total * 10) / KBYTE) % 10; | |
| s->total /= KBYTE; | |
| if (s->total >= KBYTE) { | |
| ttype = 'M'; | |
| tfrag = ((s->total * 10) / KBYTE) % 10; | |
| s->total /= KBYTE; | |
| } | |
| stype = 'K'; | |
| sfrag = ((s->sum * 10) / KBYTE) % 10; | |
| s->sum /= KBYTE; | |
| if (s->sum >= KBYTE) { | |
| stype = 'M'; | |
| sfrag = ((s->sum * 10) / KBYTE) % 10; | |
| s->sum /= KBYTE; | |
| } | |
| mtype = 'K'; | |
| s->max /= KBYTE; | |
| if (s->max >= KBYTE) { | |
| mtype = 'M'; | |
| s->max /= KBYTE; | |
| } | |
| apr_file_printf(errfile, "Cleaned %s. Statistics:" APR_EOL_STR, path); | |
| 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 %" APR_OFF_T_FMT ".0%c" APR_EOL_STR, | |
| s->max, mtype); | |
| apr_file_printf(errfile, "inodes limit %" APR_OFF_T_FMT APR_EOL_STR, | |
| s->inodes); | |
| apr_file_printf( | |
| errfile, | |
| "total size was %" APR_OFF_T_FMT ".%" APR_OFF_T_FMT "%c, total size now " | |
| "%" APR_OFF_T_FMT ".%" APR_OFF_T_FMT "%c" APR_EOL_STR, s->total, | |
| tfrag, ttype, s->sum, sfrag, stype); | |
| apr_file_printf(errfile, "total inodes was %" APR_OFF_T_FMT | |
| ", total %sinodes now " | |
| "%" APR_OFF_T_FMT APR_EOL_STR, s->ntotal, dryrun && deldirs ? "estimated " | |
| : "", s->nodes); | |
| apr_file_printf( | |
| errfile, | |
| "total entries was %" APR_OFF_T_FMT ", total entries now %" APR_OFF_T_FMT | |
| APR_EOL_STR, s->etotal, s->entries); | |
| apr_file_printf( | |
| errfile, | |
| "%" APR_OFF_T_FMT " entries deleted (%" APR_OFF_T_FMT " from future, %" | |
| APR_OFF_T_FMT " expired, %" APR_OFF_T_FMT " fresh)" APR_EOL_STR, | |
| (s->etotal - s->entries), s->dfuture, s->dexpired, s->dfresh); | |
| } | |
| /** | |
| * Round the value up to the given threshold. | |
| */ | |
| static apr_size_t round_up(apr_size_t val, apr_off_t round) | |
| { | |
| if (round > 1) { | |
| return (apr_size_t)(((val + round - 1) / round) * round); | |
| } | |
| return val; | |
| } | |
| /* | |
| * delete parent directories | |
| */ | |
| static void delete_parent(const char *path, const char *basename, | |
| apr_off_t *nodes, apr_pool_t *pool) | |
| { | |
| char *nextpath, *name; | |
| apr_pool_t *p; | |
| /* temp pool, otherwise lots of memory could be allocated */ | |
| apr_pool_create(&p, pool); | |
| name = apr_pstrdup(p, basename); | |
| /* If asked to delete dirs, do so now. We don't care if it fails. | |
| * If it fails, it likely means there was something else there. | |
| */ | |
| if (deldirs && !dryrun) { | |
| const char *vary; | |
| char *end = strrchr(name, '/'); | |
| while (end) { | |
| *end = 0; | |
| /* remove the directory */ | |
| nextpath = apr_pstrcat(p, path, "/", name, NULL); | |
| if (!apr_dir_remove(nextpath, p)) { | |
| (*nodes)--; | |
| /* vary directory found? */ | |
| vary = strstr(name, CACHE_VDIR_SUFFIX); | |
| if (vary && !vary[sizeof(CACHE_VDIR_SUFFIX) - 1]) { | |
| nextpath = apr_pstrcat(p, path, "/", apr_pstrndup(p, name, vary | |
| - name), NULL); | |
| if (!apr_file_remove(nextpath, p)) { | |
| (*nodes)--; | |
| } | |
| } | |
| } | |
| else { | |
| break; | |
| } | |
| end = strrchr(name, '/'); | |
| } | |
| } | |
| apr_pool_destroy(p); | |
| if (benice) { | |
| if (++delcount >= DELETE_NICE) { | |
| apr_sleep(NICE_DELAY); | |
| delcount = 0; | |
| } | |
| } | |
| } | |
| /* | |
| * delete a single file | |
| */ | |
| static void delete_file(char *path, char *basename, apr_off_t *nodes, | |
| apr_pool_t *pool) | |
| { | |
| char *nextpath; | |
| apr_pool_t *p; | |
| /* temp pool, otherwise lots of memory could be allocated */ | |
| apr_pool_create(&p, pool); | |
| nextpath = apr_pstrcat(p, path, "/", basename, NULL); | |
| if (dryrun) { | |
| apr_finfo_t finfo; | |
| if (!apr_stat(&finfo, nextpath, APR_FINFO_NLINK, p)) { | |
| (*nodes)--; | |
| } | |
| } | |
| else if (!apr_file_remove(nextpath, p)) { | |
| (*nodes)--; | |
| } | |
| apr_pool_destroy(p); | |
| if (benice) { | |
| if (++delcount >= DELETE_NICE) { | |
| apr_sleep(NICE_DELAY); | |
| delcount = 0; | |
| } | |
| } | |
| delete_parent(path, basename, nodes, pool); | |
| } | |
| /* | |
| * delete cache file set | |
| */ | |
| static void delete_entry(char *path, char *basename, apr_off_t *nodes, | |
| apr_pool_t *pool) | |
| { | |
| char *nextpath; | |
| apr_pool_t *p; | |
| /* temp pool, otherwise lots of memory could be allocated */ | |
| apr_pool_create(&p, pool); | |
| nextpath = apr_pstrcat(p, path, "/", basename, CACHE_HEADER_SUFFIX, NULL); | |
| if (dryrun) { | |
| apr_finfo_t finfo; | |
| if (!apr_stat(&finfo, nextpath, APR_FINFO_NLINK, p)) { | |
| (*nodes)--; | |
| } | |
| } | |
| else if (!apr_file_remove(nextpath, p)) { | |
| (*nodes)--; | |
| } | |
| nextpath = apr_pstrcat(p, path, "/", basename, CACHE_DATA_SUFFIX, NULL); | |
| if (dryrun) { | |
| apr_finfo_t finfo; | |
| if (!apr_stat(&finfo, nextpath, APR_FINFO_NLINK, p)) { | |
| (*nodes)--; | |
| } | |
| } | |
| else if (!apr_file_remove(nextpath, p)) { | |
| (*nodes)--; | |
| } | |
| apr_pool_destroy(p); | |
| if (benice) { | |
| delcount += 2; | |
| if (delcount >= DELETE_NICE) { | |
| apr_sleep(NICE_DELAY); | |
| delcount = 0; | |
| } | |
| } | |
| delete_parent(path, basename, nodes, pool); | |
| } | |
| /* | |
| * list the cache directory tree | |
| */ | |
| static int list_urls(char *path, apr_pool_t *pool, apr_off_t round) | |
| { | |
| apr_dir_t *dir; | |
| apr_finfo_t info; | |
| apr_size_t len; | |
| apr_pool_t *p; | |
| apr_file_t *fd; | |
| const char *ext, *nextpath; | |
| char *url; | |
| apr_uint32_t format; | |
| disk_cache_info_t disk_info; | |
| apr_pool_create(&p, pool); | |
| if (apr_dir_open(&dir, path, p) != APR_SUCCESS) { | |
| return 1; | |
| } | |
| while (apr_dir_read(&info, APR_FINFO_TYPE, dir) == APR_SUCCESS && !interrupted) { | |
| if (info.filetype == APR_DIR) { | |
| if (!strcmp(info.name, ".") || !strcmp(info.name, "..")) { | |
| continue; | |
| } | |
| if (list_urls(apr_pstrcat(p, path, "/", info.name, NULL), pool, round)) { | |
| return 1; | |
| } | |
| } | |
| else if (info.filetype == APR_REG) { | |
| ext = strchr(info.name, '.'); | |
| if (ext && !strcasecmp(ext, CACHE_HEADER_SUFFIX)) { | |
| nextpath = apr_pstrcat(p, path, "/", info.name, 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) { | |
| len = disk_info.name_len; | |
| url = apr_palloc(p, len + 1); | |
| url[len] = 0; | |
| if (apr_file_read_full(fd, url, len, &len) | |
| == APR_SUCCESS) { | |
| if (listextended) { | |
| apr_finfo_t hinfo, dinfo; | |
| /* stat the header file */ | |
| if (APR_SUCCESS != apr_file_info_get( | |
| &hinfo, APR_FINFO_SIZE, fd)) { | |
| /* ignore the file */ | |
| } | |
| else if (disk_info.has_body && APR_SUCCESS | |
| != apr_stat( | |
| &dinfo, | |
| apr_pstrcat( | |
| p, | |
| path, | |
| "/", | |
| apr_pstrndup( | |
| p, | |
| info.name, | |
| ext | |
| - info.name), | |
| CACHE_DATA_SUFFIX, | |
| NULL), | |
| APR_FINFO_SIZE | |
| | APR_FINFO_IDENT, | |
| p)) { | |
| /* ignore the file */ | |
| } | |
| else if (disk_info.has_body && (dinfo.device | |
| != disk_info.device | |
| || dinfo.inode | |
| != disk_info.inode)) { | |
| /* ignore the file */ | |
| } | |
| else { | |
| apr_file_printf( | |
| outfile, | |
| "%s %" APR_SIZE_T_FMT | |
| " %" APR_SIZE_T_FMT | |
| " %d %" APR_SIZE_T_FMT | |
| " %" APR_TIME_T_FMT | |
| " %" APR_TIME_T_FMT | |
| " %" APR_TIME_T_FMT | |
| " %" APR_TIME_T_FMT | |
| " %d %d\n", | |
| url, | |
| round_up((apr_size_t)hinfo.size, round), | |
| round_up( | |
| disk_info.has_body ? (apr_size_t)dinfo.size | |
| : 0, round), | |
| disk_info.status, | |
| disk_info.entity_version, | |
| disk_info.date, | |
| disk_info.expire, | |
| disk_info.request_time, | |
| disk_info.response_time, | |
| disk_info.has_body, | |
| disk_info.header_only); | |
| } | |
| } | |
| else { | |
| apr_finfo_t dinfo; | |
| /* stat the data file */ | |
| if (disk_info.has_body && APR_SUCCESS | |
| != apr_stat( | |
| &dinfo, | |
| apr_pstrcat( | |
| p, | |
| path, | |
| "/", | |
| apr_pstrndup( | |
| p, | |
| info.name, | |
| ext | |
| - info.name), | |
| CACHE_DATA_SUFFIX, | |
| NULL), | |
| APR_FINFO_SIZE | |
| | APR_FINFO_IDENT, | |
| p)) { | |
| /* ignore the file */ | |
| } | |
| else if (disk_info.has_body && (dinfo.device | |
| != disk_info.device | |
| || dinfo.inode | |
| != disk_info.inode)) { | |
| /* ignore the file */ | |
| } | |
| else { | |
| apr_file_printf(outfile, "%s\n", | |
| url); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| apr_file_close(fd); | |
| } | |
| } | |
| } | |
| } | |
| apr_dir_close(dir); | |
| if (interrupted) { | |
| return 1; | |
| } | |
| apr_pool_destroy(p); | |
| if (benice) { | |
| apr_sleep(NICE_DELAY); | |
| } | |
| if (interrupted) { | |
| return 1; | |
| } | |
| return 0; | |
| } | |
| /* | |
| * walk the cache directory tree | |
| */ | |
| static int process_dir(char *path, apr_pool_t *pool, apr_off_t *nodes) | |
| { | |
| 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; | |
| DIRENTRY *d, *t, *n, anchor; | |
| ENTRY *e; | |
| int skip, retries; | |
| disk_cache_info_t disk_info; | |
| APR_RING_INIT(&anchor.link, _direntry, link); | |
| apr_pool_create(&p, pool); | |
| h = apr_hash_make(p); | |
| fd = NULL; | |
| 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.link, d, _direntry, link); | |
| (*nodes)++; | |
| } | |
| apr_dir_close(dir); | |
| if (interrupted) { | |
| return 1; | |
| } | |
| skip = baselen + 1; | |
| for (d = APR_RING_FIRST(&anchor.link); | |
| !interrupted && d != APR_RING_SENTINEL(&anchor.link, _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 an error 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) { | |
| char *dirpath = apr_pstrdup(p, d->basename); | |
| if (process_dir(d->basename, pool, nodes)) { | |
| return 1; | |
| } | |
| /* When given the -t option htcacheclean does not | |
| * delete directories that are already empty, so we'll do that here | |
| * since process_dir checks all the directories. | |
| * If it fails, it likely means there was something else there. | |
| */ | |
| if (deldirs && !dryrun) { | |
| apr_dir_remove(dirpath, p); | |
| } | |
| 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)) { | |
| 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.link, 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); | |
| if (!disk_info.has_body) { | |
| delete_file(path, apr_pstrcat(p, path, "/", | |
| d->basename, CACHE_DATA_SUFFIX, NULL), | |
| nodes, p); | |
| } | |
| break; | |
| } | |
| else { | |
| apr_file_close(fd); | |
| } | |
| } | |
| else if (format == VARY_FORMAT_VERSION) { | |
| apr_finfo_t finfo; | |
| /* This must be a URL that added Vary headers later, | |
| * so kill the orphaned .data file | |
| */ | |
| apr_file_close(fd); | |
| if (apr_stat(&finfo, apr_pstrcat(p, nextpath, | |
| CACHE_VDIR_SUFFIX, NULL), APR_FINFO_TYPE, p) | |
| || finfo.filetype != APR_DIR) { | |
| delete_entry(path, d->basename, nodes, p); | |
| } | |
| else { | |
| delete_file(path, apr_pstrcat(p, path, "/", | |
| d->basename, CACHE_DATA_SUFFIX, NULL), | |
| nodes, p); | |
| } | |
| break; | |
| } | |
| else { | |
| /* We didn't recognise the format, kill the files */ | |
| apr_file_close(fd); | |
| delete_entry(path, d->basename, nodes, p); | |
| 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, nodes, 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(); | |
| 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); | |
| if (apr_file_read_full(fd, &expires, len, | |
| &len) == APR_SUCCESS) { | |
| apr_finfo_t finfo; | |
| apr_file_close(fd); | |
| if (apr_stat(&finfo, apr_pstrcat(p, nextpath, | |
| CACHE_VDIR_SUFFIX, NULL), APR_FINFO_TYPE, p) | |
| || finfo.filetype != APR_DIR) { | |
| delete_entry(path, d->basename, nodes, p); | |
| } | |
| else if (expires < current) { | |
| delete_entry(path, d->basename, nodes, p); | |
| } | |
| break; | |
| } | |
| } | |
| else 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.link, 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 { | |
| apr_file_close(fd); | |
| delete_entry(path, d->basename, nodes, p); | |
| break; | |
| } | |
| } | |
| else { | |
| apr_file_close(fd); | |
| } | |
| } | |
| if (realclean || d->htime < current - deviation | |
| || d->htime > current + deviation) { | |
| delete_entry(path, d->basename, nodes, 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, nodes, 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, nodes, 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 inodes, apr_off_t nodes, apr_off_t round) | |
| { | |
| ENTRY *e, *n, *oldest; | |
| struct stats s; | |
| s.sum = 0; | |
| s.entries = 0; | |
| s.dfuture = 0; | |
| s.dexpired = 0; | |
| s.dfresh = 0; | |
| s.max = max; | |
| s.nodes = nodes; | |
| s.inodes = inodes; | |
| s.ntotal = nodes; | |
| for (e = APR_RING_FIRST(&root.link); | |
| e != APR_RING_SENTINEL(&root.link, _entry, link); | |
| e = APR_RING_NEXT(e, link)) { | |
| s.sum += round_up((apr_size_t)e->hsize, round); | |
| s.sum += round_up((apr_size_t)e->dsize, round); | |
| s.entries++; | |
| } | |
| s.total = s.sum; | |
| s.etotal = s.entries; | |
| if ((!s.max || s.sum <= s.max) && (!s.inodes || s.nodes <= s.inodes)) { | |
| printstats(path, &s); | |
| 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.link); | |
| e != APR_RING_SENTINEL(&root.link, _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, &s.nodes, pool); | |
| s.sum -= round_up((apr_size_t)e->hsize, round); | |
| s.sum -= round_up((apr_size_t)e->dsize, round); | |
| s.entries--; | |
| s.dfuture++; | |
| APR_RING_REMOVE(e, link); | |
| if ((!s.max || s.sum <= s.max) && (!s.inodes || s.nodes <= s.inodes)) { | |
| if (!interrupted) { | |
| printstats(path, &s); | |
| } | |
| return; | |
| } | |
| } | |
| e = n; | |
| } | |
| if (interrupted) { | |
| return; | |
| } | |
| /* process all entries which are expired */ | |
| for (e = APR_RING_FIRST(&root.link); | |
| e != APR_RING_SENTINEL(&root.link, _entry, link) && !interrupted;) { | |
| n = APR_RING_NEXT(e, link); | |
| if (e->expire != APR_DATE_BAD && e->expire < now) { | |
| delete_entry(path, e->basename, &s.nodes, pool); | |
| s.sum -= round_up((apr_size_t)e->hsize, round); | |
| s.sum -= round_up((apr_size_t)e->dsize, round); | |
| s.entries--; | |
| s.dexpired++; | |
| APR_RING_REMOVE(e, link); | |
| if ((!s.max || s.sum <= s.max) && (!s.inodes || s.nodes <= s.inodes)) { | |
| if (!interrupted) { | |
| printstats(path, &s); | |
| } | |
| return; | |
| } | |
| } | |
| e = n; | |
| } | |
| if (interrupted) { | |
| return; | |
| } | |
| /* process remaining entries oldest to newest, the check for an empty | |
| * ring actually isn't necessary except when the compiler does | |
| * corrupt 64bit arithmetics which happened to me once, so better safe | |
| * than sorry | |
| */ | |
| while (!((!s.max || s.sum <= s.max) && (!s.inodes || s.nodes <= s.inodes)) | |
| && !interrupted && !APR_RING_EMPTY(&root.link, _entry, link)) { | |
| oldest = APR_RING_FIRST(&root.link); | |
| for (e = APR_RING_NEXT(oldest, link); | |
| e != APR_RING_SENTINEL(&root.link, _entry, link); | |
| e = APR_RING_NEXT(e, link)) { | |
| if (e->dtime < oldest->dtime) { | |
| oldest = e; | |
| } | |
| } | |
| delete_entry(path, oldest->basename, &s.nodes, pool); | |
| s.sum -= round_up((apr_size_t)oldest->hsize, round); | |
| s.sum -= round_up((apr_size_t)oldest->dsize, round); | |
| s.entries--; | |
| s.dfresh++; | |
| APR_RING_REMOVE(oldest, link); | |
| } | |
| if (!interrupted) { | |
| printstats(path, &s); | |
| } | |
| } | |
| static apr_status_t remove_directory(apr_pool_t *pool, const char *dir) | |
| { | |
| apr_status_t rv; | |
| apr_dir_t *dirp; | |
| apr_finfo_t dirent; | |
| rv = apr_dir_open(&dirp, dir, pool); | |
| if (APR_STATUS_IS_ENOENT(rv)) { | |
| return rv; | |
| } | |
| if (rv != APR_SUCCESS) { | |
| apr_file_printf(errfile, "Could not open directory %s: %pm" APR_EOL_STR, | |
| dir, &rv); | |
| return rv; | |
| } | |
| while (apr_dir_read(&dirent, APR_FINFO_DIRENT | APR_FINFO_TYPE, dirp) | |
| == APR_SUCCESS) { | |
| if (dirent.filetype == APR_DIR) { | |
| if (strcmp(dirent.name, ".") && strcmp(dirent.name, "..")) { | |
| rv = remove_directory(pool, apr_pstrcat(pool, dir, "/", | |
| dirent.name, NULL)); | |
| /* tolerate the directory not being empty, the cache may have | |
| * attempted to recreate the directory in the mean time. | |
| */ | |
| if (APR_SUCCESS != rv && APR_ENOTEMPTY != rv) { | |
| break; | |
| } | |
| } | |
| } else { | |
| const char *file = apr_pstrcat(pool, dir, "/", dirent.name, NULL); | |
| rv = apr_file_remove(file, pool); | |
| if (APR_SUCCESS != rv) { | |
| apr_file_printf(errfile, | |
| "Could not remove file '%s': %pm" APR_EOL_STR, file, | |
| &rv); | |
| break; | |
| } | |
| } | |
| } | |
| apr_dir_close(dirp); | |
| if (rv == APR_SUCCESS) { | |
| rv = apr_dir_remove(dir, pool); | |
| if (APR_ENOTEMPTY == rv) { | |
| rv = APR_SUCCESS; | |
| } | |
| if (rv != APR_SUCCESS) { | |
| apr_file_printf(errfile, "Could not remove directory %s: %pm" APR_EOL_STR, | |
| dir, &rv); | |
| } | |
| } | |
| return rv; | |
| } | |
| static apr_status_t find_directory(apr_pool_t *pool, const char *base, | |
| const char *rest) | |
| { | |
| apr_status_t rv; | |
| apr_dir_t *dirp; | |
| apr_finfo_t dirent; | |
| int found = 0, files = 0; | |
| const char *header = apr_pstrcat(pool, rest, CACHE_HEADER_SUFFIX, NULL); | |
| const char *data = apr_pstrcat(pool, rest, CACHE_DATA_SUFFIX, NULL); | |
| const char *vdir = apr_pstrcat(pool, rest, CACHE_HEADER_SUFFIX, | |
| CACHE_VDIR_SUFFIX, NULL); | |
| const char *dirname = NULL; | |
| rv = apr_dir_open(&dirp, base, pool); | |
| if (rv != APR_SUCCESS) { | |
| apr_file_printf(errfile, "Could not open directory %s: %pm" APR_EOL_STR, | |
| base, &rv); | |
| return rv; | |
| } | |
| rv = APR_ENOENT; | |
| while (apr_dir_read(&dirent, APR_FINFO_DIRENT | APR_FINFO_TYPE, dirp) | |
| == APR_SUCCESS) { | |
| int len = strlen(dirent.name); | |
| int restlen = strlen(rest); | |
| if (dirent.filetype == APR_DIR && !strncmp(rest, dirent.name, len)) { | |
| dirname = apr_pstrcat(pool, base, "/", dirent.name, NULL); | |
| rv = find_directory(pool, dirname, rest + (len < restlen ? len | |
| : restlen)); | |
| if (APR_SUCCESS == rv) { | |
| found = 1; | |
| } | |
| } | |
| if (dirent.filetype == APR_DIR) { | |
| if (!strcmp(dirent.name, vdir)) { | |
| files = 1; | |
| } | |
| } | |
| if (dirent.filetype == APR_REG) { | |
| if (!strcmp(dirent.name, header) || !strcmp(dirent.name, data)) { | |
| files = 1; | |
| } | |
| } | |
| } | |
| apr_dir_close(dirp); | |
| if (files) { | |
| rv = APR_SUCCESS; | |
| if (!dryrun) { | |
| const char *remove; | |
| apr_status_t status; | |
| remove = apr_pstrcat(pool, base, "/", header, NULL); | |
| status = apr_file_remove(remove, pool); | |
| if (status != APR_SUCCESS && !APR_STATUS_IS_ENOENT(status)) { | |
| apr_file_printf(errfile, "Could not remove file %s: %pm" APR_EOL_STR, | |
| remove, &status); | |
| rv = status; | |
| } | |
| remove = apr_pstrcat(pool, base, "/", data, NULL); | |
| status = apr_file_remove(remove, pool); | |
| if (status != APR_SUCCESS && !APR_STATUS_IS_ENOENT(status)) { | |
| apr_file_printf(errfile, "Could not remove file %s: %pm" APR_EOL_STR, | |
| remove, &status); | |
| rv = status; | |
| } | |
| status = remove_directory(pool, apr_pstrcat(pool, base, "/", vdir, NULL)); | |
| if (status != APR_SUCCESS && !APR_STATUS_IS_ENOENT(status)) { | |
| rv = status; | |
| } | |
| } | |
| } | |
| /* If asked to delete dirs, do so now. We don't care if it fails. | |
| * If it fails, it likely means there was something else there. | |
| */ | |
| if (dirname && deldirs && !dryrun) { | |
| apr_dir_remove(dirname, pool); | |
| } | |
| if (found) { | |
| return APR_SUCCESS; | |
| } | |
| return rv; | |
| } | |
| /** | |
| * Delete a specific URL from the cache. | |
| */ | |
| static apr_status_t delete_url(apr_pool_t *pool, const char *proxypath, const char *url) | |
| { | |
| apr_md5_ctx_t context; | |
| unsigned char digest[16]; | |
| char tmp[23]; | |
| int i, k; | |
| unsigned int x; | |
| static const char enc_table[64] = | |
| "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_@"; | |
| apr_md5_init(&context); | |
| apr_md5_update(&context, (const unsigned char *) url, strlen(url)); | |
| apr_md5_final(digest, &context); | |
| /* encode 128 bits as 22 characters, using a modified uuencoding | |
| * the encoding is 3 bytes -> 4 characters* i.e. 128 bits is | |
| * 5 x 3 bytes + 1 byte -> 5 * 4 characters + 2 characters | |
| */ | |
| for (i = 0, k = 0; i < 15; i += 3) { | |
| x = (digest[i] << 16) | (digest[i + 1] << 8) | digest[i + 2]; | |
| tmp[k++] = enc_table[x >> 18]; | |
| tmp[k++] = enc_table[(x >> 12) & 0x3f]; | |
| tmp[k++] = enc_table[(x >> 6) & 0x3f]; | |
| tmp[k++] = enc_table[x & 0x3f]; | |
| } | |
| /* one byte left */ | |
| x = digest[15]; | |
| tmp[k++] = enc_table[x >> 2]; /* use up 6 bits */ | |
| tmp[k++] = enc_table[(x << 4) & 0x3f]; | |
| tmp[k] = 0; | |
| /* automatically find the directory levels */ | |
| return find_directory(pool, proxypath, tmp); | |
| } | |
| /* | |
| * usage info | |
| */ | |
| #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] [-LLIMIT] [-PPIDFILE]" NL | |
| " %s [-nti] -dINTERVAL -pPATH [-lLIMIT] [-LLIMIT] [-PPIDFILE]" NL | |
| " %s [-Dvt] -pPATH URL ..." 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. When doing a dry run and deleting" NL | |
| " directories with -t, the inodes reported deleted in the stats" NL | |
| " cannot take into account the directories deleted, and will be" NL | |
| " marked as an estimate." 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 | |
| " -R Specify amount to round sizes up to." NL | |
| NL | |
| " -l Specify LIMIT as the total disk cache size limit. Attach 'K'," NL | |
| " 'M' or 'G' to the number for specifying KBytes, MBytes or" NL | |
| " GBytes." NL | |
| NL | |
| " -L Specify LIMIT as the total disk cache inode limit. 'K', 'M' or" NL | |
| " 'G' suffix can also be used." 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 | |
| NL | |
| " -a List the URLs currently stored in the cache. Variants of the" NL | |
| " same URL will be listed once for each variant." NL | |
| NL | |
| " -A List the URLs currently stored in the cache, along with their" NL | |
| " attributes in the following order: url, header size, body size," NL | |
| " status, entity version, date, expiry, request time," NL | |
| " response time, body present, head request." NL | |
| NL | |
| "Should an URL be provided on the command line, the URL will be" NL | |
| "deleted from the cache. A reverse proxied URL is made up as follows:" NL | |
| "http://<hostname>:<port><path>?[query]. So, for the path \"/\" on the" NL | |
| "host \"localhost\" and port 80, the URL to delete becomes" NL | |
| "\"http://localhost:80/?\". Note the '?' in the URL must always be" NL | |
| "specified explicitly, whether a query string is present or not." NL, | |
| shortname, | |
| shortname, | |
| shortname, | |
| shortname | |
| ); | |
| exit(1); | |
| } | |
| #undef NL | |
| static void usage_repeated_arg(apr_pool_t *pool, char option) | |
| { | |
| usage(apr_psprintf(pool, | |
| "The option '%c' cannot be specified more than once", | |
| option)); | |
| } | |
| static void log_pid(apr_pool_t *pool, const char *pidfilename, apr_file_t **pidfile) | |
| { | |
| apr_status_t status; | |
| 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': %pm" APR_EOL_STR, | |
| pidfilename, &status); | |
| } | |
| exit(1); | |
| } | |
| } | |
| /* | |
| * main | |
| */ | |
| int main(int argc, const char * const argv[]) | |
| { | |
| apr_off_t max, inodes, round; | |
| 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, inodes_found, intelligent, dowork; | |
| char opt; | |
| const char *arg; | |
| char *proxypath, *path, *pidfilename; | |
| interrupted = 0; | |
| repeat = 0; | |
| isdaemon = 0; | |
| dryrun = 0; | |
| limit_found = 0; | |
| inodes_found = 0; | |
| max = 0; | |
| inodes = 0; | |
| round = 0; | |
| verbose = 0; | |
| realclean = 0; | |
| benice = 0; | |
| deldirs = 0; | |
| intelligent = 0; | |
| previous = 0; /* avoid compiler warning */ | |
| 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_file_open_stdout(&outfile, 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:R:aA", &opt, &arg); | |
| if (status == APR_EOF) { | |
| break; | |
| } | |
| else if (status != APR_SUCCESS) { | |
| usage(NULL); | |
| } | |
| else { | |
| char *end; | |
| apr_status_t rv; | |
| switch (opt) { | |
| case 'i': | |
| if (intelligent) { | |
| usage_repeated_arg(pool, opt); | |
| } | |
| intelligent = 1; | |
| break; | |
| case 'D': | |
| if (dryrun) { | |
| usage_repeated_arg(pool, opt); | |
| } | |
| dryrun = 1; | |
| break; | |
| case 'n': | |
| if (benice) { | |
| usage_repeated_arg(pool, opt); | |
| } | |
| benice = 1; | |
| break; | |
| case 't': | |
| if (deldirs) { | |
| usage_repeated_arg(pool, opt); | |
| } | |
| deldirs = 1; | |
| break; | |
| case 'v': | |
| if (verbose) { | |
| usage_repeated_arg(pool, opt); | |
| } | |
| verbose = 1; | |
| break; | |
| case 'r': | |
| if (realclean) { | |
| usage_repeated_arg(pool, opt); | |
| } | |
| realclean = 1; | |
| deldirs = 1; | |
| break; | |
| case 'd': | |
| if (isdaemon) { | |
| usage_repeated_arg(pool, opt); | |
| } | |
| isdaemon = 1; | |
| repeat = apr_atoi64(arg); | |
| repeat *= SECS_PER_MIN; | |
| repeat *= APR_USEC_PER_SEC; | |
| break; | |
| case 'l': | |
| if (limit_found) { | |
| usage_repeated_arg(pool, opt); | |
| } | |
| limit_found = 1; | |
| do { | |
| 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 && /* neither empty nor [Bb] */ | |
| ((*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 'L': | |
| if (inodes_found) { | |
| usage_repeated_arg(pool, opt); | |
| } | |
| inodes_found = 1; | |
| do { | |
| rv = apr_strtoff(&inodes, arg, &end, 10); | |
| if (rv == APR_SUCCESS) { | |
| if ((*end == 'K' || *end == 'k') && !end[1]) { | |
| inodes *= KBYTE; | |
| } | |
| else if ((*end == 'M' || *end == 'm') && !end[1]) { | |
| inodes *= MBYTE; | |
| } | |
| else if ((*end == 'G' || *end == 'g') && !end[1]) { | |
| inodes *= GBYTE; | |
| } | |
| else if (*end && /* neither empty nor [Bb] */ | |
| ((*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 'a': | |
| if (listurls) { | |
| usage_repeated_arg(pool, opt); | |
| } | |
| listurls = 1; | |
| break; | |
| case 'A': | |
| if (listurls) { | |
| usage_repeated_arg(pool, opt); | |
| } | |
| listurls = 1; | |
| listextended = 1; | |
| break; | |
| case 'p': | |
| if (proxypath) { | |
| usage_repeated_arg(pool, 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': %pm", | |
| proxypath, &status)); | |
| } | |
| break; | |
| case 'P': | |
| if (pidfilename) { | |
| usage_repeated_arg(pool, opt); | |
| } | |
| pidfilename = apr_pstrdup(pool, arg); | |
| break; | |
| case 'R': | |
| if (round) { | |
| usage_repeated_arg(pool, opt); | |
| } | |
| rv = apr_strtoff(&round, arg, &end, 10); | |
| if (rv == APR_SUCCESS) { | |
| if (*end) { | |
| usage(apr_psprintf(pool, "Invalid round value: %s" | |
| APR_EOL_STR APR_EOL_STR, arg)); | |
| } | |
| else if (round < 0) { | |
| usage(apr_psprintf(pool, "Round value must be positive: %s" | |
| APR_EOL_STR APR_EOL_STR, arg)); | |
| } | |
| } | |
| if (rv != APR_SUCCESS) { | |
| usage(apr_psprintf(pool, "Invalid round value: %s" | |
| APR_EOL_STR APR_EOL_STR, arg)); | |
| } | |
| break; | |
| } /* switch */ | |
| } /* else */ | |
| } /* while */ | |
| if (argc <= 1) { | |
| usage(NULL); | |
| } | |
| if (!proxypath) { | |
| usage("Option -p must be specified"); | |
| } | |
| if (o->ind < argc) { | |
| int deleted = 0; | |
| int error = 0; | |
| if (isdaemon) { | |
| usage("Option -d cannot be used with URL arguments, aborting"); | |
| } | |
| if (intelligent) { | |
| usage("Option -i cannot be used with URL arguments, aborting"); | |
| } | |
| if (limit_found) { | |
| usage("Option -l and -L cannot be used with URL arguments, aborting"); | |
| } | |
| while (o->ind < argc) { | |
| status = delete_url(pool, proxypath, argv[o->ind]); | |
| if (APR_SUCCESS == status) { | |
| if (verbose) { | |
| apr_file_printf(errfile, "Removed: %s" APR_EOL_STR, | |
| argv[o->ind]); | |
| } | |
| deleted = 1; | |
| } | |
| else if (APR_ENOENT == status) { | |
| if (verbose) { | |
| apr_file_printf(errfile, "Not cached: %s" APR_EOL_STR, | |
| argv[o->ind]); | |
| } | |
| } | |
| else { | |
| if (verbose) { | |
| apr_file_printf(errfile, "Error while removed: %s" APR_EOL_STR, | |
| argv[o->ind]); | |
| } | |
| error = 1; | |
| } | |
| o->ind++; | |
| } | |
| return error ? 1 : deleted ? 0 : 2; | |
| } | |
| if (isdaemon && repeat <= 0) { | |
| usage("Option -d must be greater than zero"); | |
| } | |
| if (isdaemon && (verbose || realclean || dryrun || listurls)) { | |
| usage("Option -d cannot be used with -v, -r, -L or -D"); | |
| } | |
| if (!isdaemon && intelligent) { | |
| usage("Option -i cannot be used without -d"); | |
| } | |
| if (!listurls && max <= 0 && inodes <= 0) { | |
| usage("At least one of option -l or -L must be greater than zero"); | |
| } | |
| if (apr_filepath_get(&path, 0, pool) != APR_SUCCESS) { | |
| usage(apr_psprintf(pool, "Could not get the filepath: %pm", &status)); | |
| } | |
| baselen = strlen(path); | |
| if (pidfilename) { | |
| log_pid(pool, pidfilename, &pidfile); /* before daemonizing, so we | |
| * can report errors | |
| */ | |
| } | |
| if (listurls) { | |
| list_urls(path, pool, round); | |
| return (interrupted != 0); | |
| } | |
| #ifndef DEBUG | |
| if (isdaemon) { | |
| apr_file_close(errfile); | |
| errfile = NULL; | |
| if (pidfilename) { | |
| apr_file_close(pidfile); /* delete original pidfile only in parent */ | |
| } | |
| 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.link, _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) { | |
| apr_off_t nodes = 0; | |
| if (!process_dir(path, instance, &nodes) && !interrupted) { | |
| purge(path, instance, max, inodes, nodes, round); | |
| } | |
| 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; | |
| } | |
| /* 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." APR_EOL_STR); | |
| return 1; | |
| } | |
| return 0; | |
| } |