httpd-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Andreas Steinmetz <...@domdv.de>
Subject Re: mod_disk_cache directives naming convention
Date Sun, 10 Oct 2004 19:32:45 GMT
Justin Erenkrantz wrote:
> Looks generally fine, but I haven't tested it yet.  I guess we should 
> move the mod_disk_cache header struct to a header file instead of the .c 
> file.
> 
> If you're willing, I'd be happy to see this brought into the tree under 
> support/ and available for 2.1+.  What do you think?  -- justin

Yes, the support directory was my intention. Below is my current version
which is tested and really feature complete. If I don't detect any bugs
in this version it is going to be the final version. Some remarks:

1. Nice mode runs at about 10% to 20% of the speed of standard mode
    which should be fine for loaded systems.

2. Intelligent mode depends on the fact that the aptmpXXXXXX files are
    always created in the disk cache root directory as it uses the
    disk cache root directory modification time as an indicator whether
    to actually run (useful for systems that do e.g. spin down idle disks).

3. A common header file with mod_disk_cache.c for the shared definitions
    would be nice. If you could modify the AP_TEMPFILE definition somehow
    as shown below in the code this definition could be shared, too.
-- 
Andreas Steinmetz                       SPAMmers use robotrap@domdv.de


/* Copyright 2001-2004 The Apache Software Foundation
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
  *     http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */

/*
  * htcacheclean.c: simple program for cleaning of
  * the disk cache of the Apache HTTP server
  *
  * Contributed by Andreas Steinmetz <ast@domdv.de>
  * 8 Oct 2004
  */

#include "apr.h"
#include "apr_lib.h"
#include "apr_strings.h"
#include "apr_file_io.h"
#include "apr_file_info.h"
#include "apr_pools.h"
#include "apr_hash.h"
#include "apr_thread_proc.h"
#include "apr_signal.h"
#include "apr_getopt.h"
#include "apr_ring.h"
#include "apr_date.h"

/* 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[0];
} 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) +
                             strlen(d->basename) + 1);
                         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;
                         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;
}

Mime
View raw message