-/* 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
+/* 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
*
* htcacheclean.c: simple program for cleaning of
* the disk cache of the Apache HTTP server
*
- * Contributed by Andreas Steinmetz <ast@domdv.de>
+ * Contributed by Andreas Steinmetz <ast domdv.de>
* 8 Oct 2004
*/
#include "apr_getopt.h"
#include "apr_ring.h"
#include "apr_date.h"
+#include "apr_buckets.h"
+#include "../modules/cache/mod_disk_cache.h"
#if APR_HAVE_UNISTD_H
#include <unistd.h>
#include <stdlib.h>
#endif
-/* mod_disk_cache.c extract start */
-
-#define DISK_FORMAT_VERSION 0
-typedef struct {
- /* Indicates the format of the header struct stored on-disk. */
- int format;
- /* The HTTP status code returned for this response. */
- int status;
- /* The size of the entity name that follows. */
- apr_size_t name_len;
- /* The number of times we've cached this entity. */
- apr_size_t entity_version;
- /* Miscellaneous time values. */
- apr_time_t date;
- apr_time_t expire;
- apr_time_t request_time;
- apr_time_t response_time;
-} disk_cache_info_t;
-
-#define CACHE_HEADER_SUFFIX ".header"
-#define CACHE_DATA_SUFFIX ".data"
-/* mod_disk_cache.c extract end */
-
-/* mod_disk_cache.c related definitions start */
-
-/*
- * this is based on #define AP_TEMPFILE "/aptmpXXXXXX"
- *
- * the above definition could be reworked into the following:
- *
- * #define AP_TEMPFILE_PREFIX "/"
- * #define AP_TEMPFILE_BASE "aptmp"
- * #define AP_TEMPFILE_SUFFIX "XXXXXX"
- * #define AP_TEMPFILE_BASELEN strlen(AP_TEMPFILE_BASE)
- * #define AP_TEMPFILE_NAMELEN strlen(AP_TEMPFILE_BASE AP_TEMPFILE_SUFFIX)
- * #define AP_TEMPFILE AP_TEMPFILE_PREFIX AP_TEMPFILE_BASE AP_TEMPFILE_SUFFIX
- *
- * these definitions would then match the definitions below:
- */
-
-#define AP_TEMPFILE_BASE "aptmp"
-#define AP_TEMPFILE_SUFFIX "XXXXXX"
-#define AP_TEMPFILE_BASELEN strlen(AP_TEMPFILE_BASE)
-#define AP_TEMPFILE_NAMELEN strlen(AP_TEMPFILE_BASE AP_TEMPFILE_SUFFIX)
-
-/* mod_disk_cache.c related definitions end */
-
/* define the following for debugging */
#undef DEBUG
#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)
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 baselen; /* string length of the path to the proxy directory */
static apr_time_t now; /* start time of this processing run */
files */
static APR_RING_ENTRY(_entry) root; /* ENTRY ring anchor */
+/* short program name as called */
+static const char *shortname = "htcacheclean";
#ifdef DEBUG
/*
/* 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);
+ apr_file_printf(errfile, "would delete %s" APR_EOL_STR, pathname);
}
#endif
static void setterm(int unused)
{
#ifdef DEBUG
- apr_file_printf(errfile, "interrupt\n");
+ apr_file_printf(errfile, "interrupt" APR_EOL_STR);
#endif
interrupted = 1;
}
max /= KBYTE;
}
- apr_file_printf(errfile, "Statistics:\n");
+ apr_file_printf(errfile, "Statistics:" APR_EOL_STR);
if (unsolicited) {
utype = 'K';
ufrag = ((unsolicited * 10) / KBYTE) % 10;
if (!unsolicited && !ufrag) {
ufrag = 1;
}
- apr_file_printf(errfile, "unsolicited size %d.%d%c\n",
+ 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\n", (int)(max), mtype);
+ 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\n",
+ "%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\n",
- (int)(etotal), (int)(entries));
+ apr_file_printf(errfile, "total entries was %d, total entries now %d"
+ APR_EOL_STR, (int)(etotal), (int)(entries));
}
/*
apr_finfo_t info;
apr_size_t len;
apr_time_t current, deviation;
- char *nextpath, *base, *ext;
+ char *nextpath, *base, *ext, *orig_basename;
APR_RING_ENTRY(_direntry) anchor;
DIRENTRY *d, *t, *n;
ENTRY *e;
}
while (apr_dir_read(&info, 0, dir) == APR_SUCCESS && !interrupted) {
- /* skip first two entries which will always be '.' and '..' */
- if (skip < 2) {
- skip++;
+ if (!strcmp(info.name, ".") || !strcmp(info.name, "..")) {
continue;
}
d = apr_pcalloc(p, sizeof(DIRENTRY));
}
}
- /* this may look strange but apr_stat() may return errno which
+ /* 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
*/
}
if (info.filetype == APR_DIR) {
+ /* Make a copy of the basename, as process_dir modifies it */
+ orig_basename = apr_pstrdup(pool, d->basename);
if (process_dir(d->basename, pool)) {
return 1;
}
+
+ /* 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) {
+ apr_dir_remove(orig_basename, pool);
+ }
continue;
}
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));
+ 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_READ, APR_OS_DEFAULT,
- p) == APR_SUCCESS) {
- len = sizeof(disk_cache_info_t);
- if (apr_file_read_full(fd, &disk_info, len,
+ 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) {
- 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);
+ 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) {
+ /* This must be a URL that added Vary headers later,
+ * so kill the orphaned .data file
+ */
+ apr_file_close(fd);
+ apr_file_remove(apr_pstrcat(p, path, "/", d->basename,
+ CACHE_DATA_SUFFIX, NULL),
+ 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
*/
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_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);
/*
* usage info
*/
-static void usage(void)
+#define NL APR_EOL_STR
+static void usage(const char *error)
{
- 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");
+ 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 usage_repeated_arg(apr_pool_t *pool, char option) {
+ usage(apr_psprintf(pool,
+ "The option '%c' cannot be specified more than once",
+ option));
+}
/*
* main
int retries, isdaemon, limit_found, intelligent, dowork;
char opt;
const char *arg;
- char *proxypath, *path;
+ char *proxypath, *path, *pidfile;
+ char errmsg[1024];
interrupted = 0;
repeat = 0;
verbose = 0;
realclean = 0;
benice = 0;
+ deldirs = 0;
intelligent = 0;
previous = 0; /* avoid compiler warning */
proxypath = NULL;
+ pidfile = 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_getopt_init(&o, pool, argc, argv);
while (1) {
- status = apr_getopt(o, "iDnvrd:l:L:p:", &opt, &arg);
+ status = apr_getopt(o, "iDnvrtd:l:L:p:P:", &opt, &arg);
if (status == APR_EOF) {
break;
}
else if (status != APR_SUCCESS) {
- usage();
+ usage(NULL);
}
else {
switch (opt) {
case 'i':
if (intelligent) {
- usage();
+ usage_repeated_arg(pool, opt);
}
intelligent = 1;
break;
case 'D':
if (dryrun) {
- usage();
+ usage_repeated_arg(pool, opt);
}
dryrun = 1;
break;
case 'n':
if (benice) {
- usage();
+ 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();
+ usage_repeated_arg(pool, opt);
}
verbose = 1;
break;
case 'r':
if (realclean) {
- usage();
+ usage_repeated_arg(pool, opt);
}
realclean = 1;
+ deldirs = 1;
break;
case 'd':
if (isdaemon) {
- usage();
+ usage_repeated_arg(pool, opt);
}
isdaemon = 1;
repeat = apr_atoi64(arg);
case 'l':
if (limit_found) {
- usage();
+ usage_repeated_arg(pool, opt);
}
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;
+ 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 && /* 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 'p':
if (proxypath) {
- usage();
+ usage_repeated_arg(pool, opt);
}
proxypath = apr_pstrdup(pool, arg);
- if (apr_filepath_set(proxypath, pool) != APR_SUCCESS) {
- usage();
+ 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 (pidfile) {
+ usage_repeated_arg(pool, opt);
+ }
+ pidfile = apr_pstrdup(pool, arg);
+ break;
+
} /* switch */
} /* else */
} /* while */
+ if (argc <= 1) {
+ usage(NULL);
+ }
+
if (o->ind != argc) {
- usage();
+ usage("Additional parameters specified on the command line, aborting");
+ }
+
+ if (isdaemon && repeat <= 0) {
+ usage("Option -d must be greater than zero");
}
- if (isdaemon && (repeat <= 0 || verbose || realclean || dryrun)) {
- usage();
+ if (isdaemon && (verbose || realclean || dryrun)) {
+ usage("Option -d cannot be used with -v, -r or -D");
}
if (!isdaemon && intelligent) {
- usage();
+ usage("Option -i cannot be used without -d");
+ }
+
+ if (!proxypath) {
+ usage("Option -p must be specified");
}
- if (!proxypath || max <= 0) {
- usage();
+ if (max <= 0) {
+ usage("Option -l must be greater than zero");
}
if (apr_filepath_get(&path, 0, pool) != APR_SUCCESS) {
- usage();
+ usage(apr_psprintf(pool, "Could not get the filepath: %s",
+ apr_strerror(status, errmsg, sizeof errmsg)));
}
baselen = strlen(path);
}
#endif
+ if (pidfile) {
+ apr_file_t *file;
+ pid_t mypid = getpid();
+ if (APR_SUCCESS == (status = apr_file_open(&file, pidfile, APR_WRITE
+ | APR_CREATE | APR_TRUNCATE | APR_DELONCLOSE,
+ APR_UREAD | APR_UWRITE | APR_GREAD, pool))) {
+ apr_file_printf(file, "%" APR_PID_T_FMT APR_EOL_STR, mypid);
+ }
+ else if (!isdaemon) {
+ apr_file_printf(errfile,
+ "Could not write the pid file '%s': %s" APR_EOL_STR,
+ pidfile, apr_strerror(status, errmsg, sizeof errmsg));
+ }
+ }
+
do {
apr_pool_create(&instance, pool);
purge(path, instance, max);
}
else if (!isdaemon && !interrupted) {
- apr_file_printf(errfile,
- "An error occurred, cache cleaning aborted.\n");
+ apr_file_printf(errfile, "An error occurred, cache cleaning "
+ "aborted." APR_EOL_STR);
return 1;
}
} while (isdaemon && !interrupted);
if (!isdaemon && interrupted) {
- apr_file_printf(errfile,
- "Cache cleaning aborted due to user request.\n");
+ apr_file_printf(errfile, "Cache cleaning aborted due to user "
+ "request." APR_EOL_STR);
return 1;
}