home *** CD-ROM | disk | FTP | other *** search
- /* ====================================================================
- * Copyright (c) 1996-1999 The Apache Group. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in
- * the documentation and/or other materials provided with the
- * distribution.
- *
- * 3. All advertising materials mentioning features or use of this
- * software must display the following acknowledgment:
- * "This product includes software developed by the Apache Group
- * for use in the Apache HTTP server project (http://www.apache.org/)."
- *
- * 4. The names "Apache Server" and "Apache Group" must not be used to
- * endorse or promote products derived from this software without
- * prior written permission. For written permission, please contact
- * apache@apache.org.
- *
- * 5. Products derived from this software may not be called "Apache"
- * nor may "Apache" appear in their names without prior written
- * permission of the Apache Group.
- *
- * 6. Redistributions of any form whatsoever must retain the following
- * acknowledgment:
- * "This product includes software developed by the Apache Group
- * for use in the Apache HTTP server project (http://www.apache.org/)."
- *
- * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY
- * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE APACHE GROUP OR
- * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
- * OF THE POSSIBILITY OF SUCH DAMAGE.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Group and was originally based
- * on public domain software written at the National Center for
- * Supercomputing Applications, University of Illinois, Urbana-Champaign.
- * For more information on the Apache Group and the Apache HTTP server
- * project, please see <http://www.apache.org/>.
- *
- */
-
- /* Cache and garbage collection routines for Apache proxy */
-
- #include "mod_proxy.h"
- #include "http_conf_globals.h"
- #include "http_log.h"
- #include "http_main.h"
- #include "util_date.h"
- #ifdef WIN32
- #include <sys/utime.h>
- #else
- #include <utime.h>
- #endif /* WIN32 */
- #include "multithread.h"
- #include "ap_md5.h"
-
- DEF_Explain
-
- struct gc_ent {
- unsigned long int len;
- time_t expire;
- char file[HASH_LEN + 1];
- };
-
- /* Poor man's 61 bit arithmetic */
- typedef struct {
- long lower; /* lower 30 bits of result */
- long upper; /* upper 31 bits of result */
- } long61_t;
-
- /* FIXME: The block size can be different on a `per file system' base.
- * This would make automatic detection highly OS specific.
- * In the GNU fileutils code for du(1), you can see how complicated it can
- * become to detect the block size. And, with BSD-4.x fragments, it
- * it even more difficult to get precise results.
- * As a compromise (and to improve on the incorrect counting of cache
- * size on byte level, omitting directory sizes entirely, which was
- * used up to apache-1.3b7) we're rounding to multiples of 512 here.
- * Your file system may be using larger blocks (I certainly hope so!)
- * but it will hardly use smaller blocks.
- * (So this approximation is still closer to reality than the old behavior).
- * The best solution would be automatic detection, the next best solution
- * IMHO is a sensible default and the possibility to override it.
- */
-
- #define ROUNDUP2BLOCKS(_bytes) (((_bytes)+block_size-1) & ~(block_size-1))
- static long block_size = 512; /* this must be a power of 2 */
- static long61_t curbytes, cachesize;
- static time_t every, garbage_now, garbage_expire;
- static char *filename;
- static mutex *garbage_mutex = NULL;
-
-
- int ap_proxy_garbage_init(server_rec *r, pool *p)
- {
- if (!garbage_mutex)
- garbage_mutex = ap_create_mutex(NULL);
-
- return (0);
- }
-
-
- static int sub_garbage_coll(request_rec *r, array_header *files,
- const char *cachedir, const char *cachesubdir);
- static void help_proxy_garbage_coll(request_rec *r);
- #if !defined(WIN32) && !defined(MPE) && !defined(OS2)
- static void detached_proxy_garbage_coll(request_rec *r);
- #endif
-
-
- void ap_proxy_garbage_coll(request_rec *r)
- {
- static int inside = 0;
-
- (void) ap_acquire_mutex(garbage_mutex);
- if (inside == 1) {
- (void) ap_release_mutex(garbage_mutex);
- return;
- }
- else
- inside = 1;
- (void) ap_release_mutex(garbage_mutex);
-
- ap_block_alarms(); /* avoid SIGALRM on big cache cleanup */
- #if !defined(WIN32) && !defined(MPE) && !defined(OS2)
- detached_proxy_garbage_coll(r);
- #else
- help_proxy_garbage_coll(r);
- #endif
- ap_unblock_alarms();
-
- (void) ap_acquire_mutex(garbage_mutex);
- inside = 0;
- (void) ap_release_mutex(garbage_mutex);
- }
-
-
- static void
- add_long61 (long61_t *accu, long val)
- {
- /* Add in lower 30 bits */
- accu->lower += (val & 0x3FFFFFFFL);
- /* add in upper bits, and carry */
- accu->upper += (val >> 30) + ((accu->lower & ~0x3FFFFFFFL) != 0L);
- /* Clear carry */
- accu->lower &= 0x3FFFFFFFL;
- }
-
- static void
- sub_long61 (long61_t *accu, long val)
- {
- int carry = (val & 0x3FFFFFFFL) > accu->lower;
- /* Subtract lower 30 bits */
- accu->lower = accu->lower - (val & 0x3FFFFFFFL) + ((carry) ? 0x40000000 : 0);
- /* add in upper bits, and carry */
- accu->upper -= (val >> 30) + carry;
- }
-
- /* Compare two long61's:
- * return <0 when left < right
- * return 0 when left == right
- * return >0 when left > right
- */
- static long
- cmp_long61 (long61_t *left, long61_t *right)
- {
- return (left->upper == right->upper) ? (left->lower - right->lower)
- : (left->upper - right->upper);
- }
-
- /* Compare two gc_ent's, sort them by expiration date */
- static int gcdiff(const void *ap, const void *bp)
- {
- const struct gc_ent *a = (const struct gc_ent * const) ap;
- const struct gc_ent *b = (const struct gc_ent * const) bp;
-
- if (a->expire > b->expire)
- return 1;
- else if (a->expire < b->expire)
- return -1;
- else
- return 0;
- }
-
- #if !defined(WIN32) && !defined(MPE) && !defined(OS2)
- static void detached_proxy_garbage_coll(request_rec *r)
- {
- pid_t pid;
- int status;
- pid_t pgrp;
-
- switch (pid = fork()) {
- case -1:
- ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
- "proxy: fork() for cache cleanup failed");
- return;
-
- case 0: /* Child */
-
- /* close all sorts of things, including the socket fd */
- ap_cleanup_for_exec();
-
- /* Fork twice to disassociate from the child */
- switch (pid = fork()) {
- case -1:
- ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
- "proxy: fork(2nd) for cache cleanup failed");
- exit(1);
-
- case 0: /* Child */
- /* The setpgrp() stuff was snarfed from http_main.c */
- #ifndef NO_SETSID
- if ((pgrp = setsid()) == -1) {
- perror("setsid");
- fprintf(stderr, "%s: setsid failed\n",
- ap_server_argv0);
- exit(1);
- }
- #elif defined(NEXT) || defined(NEWSOS)
- if (setpgrp(0, getpid()) == -1 || (pgrp = getpgrp(0)) == -1) {
- perror("setpgrp");
- fprintf(stderr, "%S: setpgrp or getpgrp failed\n",
- ap_server_argv0);
- exit(1);
- }
- #else
- if ((pgrp = setpgrp(getpid(), 0)) == -1) {
- perror("setpgrp");
- fprintf(stderr, "%s: setpgrp failed\n",
- ap_server_argv0);
- exit(1);
- }
- #endif
- help_proxy_garbage_coll(r);
- exit(0);
-
- default: /* Father */
- /* After grandson has been forked off, */
- /* there's nothing else to do. */
- exit(0);
- }
- default:
- /* Wait until grandson has been forked off */
- /* (without wait we'd leave a zombie) */
- waitpid(pid, &status, 0);
- return;
- }
- }
- #endif /* ndef WIN32 */
-
- static void help_proxy_garbage_coll(request_rec *r)
- {
- const char *cachedir;
- void *sconf = r->server->module_config;
- proxy_server_conf *pconf =
- (proxy_server_conf *) ap_get_module_config(sconf, &proxy_module);
- const struct cache_conf *conf = &pconf->cache;
- array_header *files;
- struct stat buf;
- struct gc_ent *fent;
- int i, timefd;
- static time_t lastcheck = BAD_DATE; /* static (per-process) data!!! */
-
- cachedir = conf->root;
- /* configured size is given in kB. Make it bytes, convert to long61_t: */
- cachesize.lower = cachesize.upper = 0;
- add_long61(&cachesize, conf->space << 10);
- every = conf->gcinterval;
-
- if (cachedir == NULL || every == -1)
- return;
- garbage_now = time(NULL);
- /* Usually, the modification time of <cachedir>/.time can only increase.
- * Thus, even with several child processes having their own copy of
- * lastcheck, if time(NULL) still < lastcheck then it's not time
- * for GC yet.
- */
- if (garbage_now != -1 && lastcheck != BAD_DATE && garbage_now < lastcheck + every)
- return;
-
- ap_block_alarms(); /* avoid SIGALRM on big cache cleanup */
-
- filename = ap_palloc(r->pool, strlen(cachedir) + HASH_LEN + 2);
- strcpy(filename, cachedir);
- strcat(filename, "/.time");
- if (stat(filename, &buf) == -1) { /* does not exist */
- if (errno != ENOENT) {
- ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
- "proxy: stat(%s)", filename);
- ap_unblock_alarms();
- return;
- }
- if ((timefd = creat(filename, 0666)) == -1) {
- if (errno != EEXIST)
- ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
- "proxy: creat(%s)", filename);
- else
- lastcheck = garbage_now; /* someone else got in there */
- ap_unblock_alarms();
- return;
- }
- close(timefd);
- }
- else {
- lastcheck = buf.st_mtime; /* save the time */
- if (garbage_now < lastcheck + every) {
- ap_unblock_alarms();
- return;
- }
- if (utime(filename, NULL) == -1)
- ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
- "proxy: utimes(%s)", filename);
- }
- files = ap_make_array(r->pool, 100, sizeof(struct gc_ent));
- curbytes.upper = curbytes.lower = 0L;
-
- sub_garbage_coll(r, files, cachedir, "/");
-
- if (cmp_long61(&curbytes, &cachesize) < 0L) {
- ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r->server,
- "proxy GC: Cache is %ld%% full (nothing deleted)",
- (long)(((curbytes.upper<<20)|(curbytes.lower>>10))*100/conf->space));
- ap_unblock_alarms();
- return;
- }
-
- /* sort the files we found by expiration date */
- qsort(files->elts, files->nelts, sizeof(struct gc_ent), gcdiff);
-
- for (i = 0; i < files->nelts; i++) {
- fent = &((struct gc_ent *) files->elts)[i];
- sprintf(filename, "%s%s", cachedir, fent->file);
- Explain3("GC Unlinking %s (expiry %ld, garbage_now %ld)", filename, fent->expire, garbage_now);
- #if TESTING
- fprintf(stderr, "Would unlink %s\n", filename);
- #else
- if (unlink(filename) == -1) {
- if (errno != ENOENT)
- ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
- "proxy gc: unlink(%s)", filename);
- }
- else
- #endif
- {
- sub_long61(&curbytes, ROUNDUP2BLOCKS(fent->len));
- if (cmp_long61(&curbytes, &cachesize) < 0)
- break;
- }
- }
-
- ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r->server,
- "proxy GC: Cache is %ld%% full (%d deleted)",
- (long)(((curbytes.upper<<20)|(curbytes.lower>>10))*100/conf->space), i);
- ap_unblock_alarms();
- }
-
- static int sub_garbage_coll(request_rec *r, array_header *files,
- const char *cachebasedir, const char *cachesubdir)
- {
- char line[27];
- char cachedir[HUGE_STRING_LEN];
- struct stat buf;
- int fd, i;
- DIR *dir;
- #if defined(NEXT) || defined(WIN32)
- struct DIR_TYPE *ent;
- #else
- struct dirent *ent;
- #endif
- struct gc_ent *fent;
- int nfiles = 0;
-
- ap_snprintf(cachedir, sizeof(cachedir), "%s%s", cachebasedir, cachesubdir);
- Explain1("GC Examining directory %s", cachedir);
- dir = opendir(cachedir);
- if (dir == NULL) {
- ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
- "proxy gc: opendir(%s)", cachedir);
- return 0;
- }
-
- while ((ent = readdir(dir)) != NULL) {
- if (ent->d_name[0] == '.')
- continue;
- sprintf(filename, "%s%s", cachedir, ent->d_name);
- Explain1("GC Examining file %s", filename);
- /* is it a temporary file? */
- if (strncmp(ent->d_name, "tmp", 3) == 0) {
- /* then stat it to see how old it is; delete temporary files > 1 day old */
- if (stat(filename, &buf) == -1) {
- if (errno != ENOENT)
- ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
- "proxy gc: stat(%s)", filename);
- }
- else if (garbage_now != -1 && buf.st_atime < garbage_now - SEC_ONE_DAY &&
- buf.st_mtime < garbage_now - SEC_ONE_DAY) {
- Explain1("GC unlink %s", filename);
- ap_log_error(APLOG_MARK, APLOG_INFO|APLOG_NOERRNO, r->server,
- "proxy gc: deleting orphaned cache file %s", filename);
- #if TESTING
- fprintf(stderr, "Would unlink %s\n", filename);
- #else
- unlink(filename);
- #endif
- }
- continue;
- }
- ++nfiles;
- /* is it another file? */
- /* FIXME: Shouldn't any unexpected files be deleted? */
- /* if (strlen(ent->d_name) != HASH_LEN) continue; */
-
- /* under OS/2 use dirent's d_attr to identify a diretory */
- #ifdef OS2
- /* is it a directory? */
- if (ent->d_attr & A_DIR) {
- char newcachedir[HUGE_STRING_LEN];
- ap_snprintf(newcachedir, sizeof(newcachedir),
- "%s%s/", cachesubdir, ent->d_name);
- if (!sub_garbage_coll(r, files, cachebasedir, newcachedir)) {
- ap_snprintf(newcachedir, sizeof(newcachedir),
- "%s%s", cachedir, ent->d_name);
- #if TESTING
- fprintf(stderr, "Would remove directory %s\n", newcachedir);
- #else
- rmdir(newcachedir);
- #endif
- --nfiles;
- }
- continue;
- }
- #endif
-
- /* read the file */
- fd = open(filename, O_RDONLY | O_BINARY);
- if (fd == -1) {
- if (errno != ENOENT)
- ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
- "proxy gc: open(%s)", filename);
- continue;
- }
- if (fstat(fd, &buf) == -1) {
- ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
- "proxy gc: fstat(%s)", filename);
- close(fd);
- continue;
- }
-
- /* In OS/2 this has already been done above */
- #ifndef OS2
- if (S_ISDIR(buf.st_mode)) {
- char newcachedir[HUGE_STRING_LEN];
- close(fd);
- ap_snprintf(newcachedir, sizeof(newcachedir),
- "%s%s/", cachesubdir, ent->d_name);
- if (!sub_garbage_coll(r, files, cachebasedir, newcachedir)) {
- ap_snprintf(newcachedir, sizeof(newcachedir),
- "%s%s", cachedir, ent->d_name);
- #if TESTING
- fprintf(stderr, "Would remove directory %s\n", newcachedir);
- #else
- rmdir(newcachedir);
- #endif
- --nfiles;
- } else {
- /* Directory is not empty. Account for its size: */
- add_long61(&curbytes, ROUNDUP2BLOCKS(buf.st_size));
- }
- continue;
- }
- #endif
-
- i = read(fd, line, 26);
- close(fd);
- if (i == -1) {
- ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
- "proxy gc: read(%s)", filename);
- continue;
- }
- line[i] = '\0';
- garbage_expire = ap_proxy_hex2sec(line + 18);
- if (!ap_checkmask(line, "&&&&&&&& &&&&&&&& &&&&&&&&") ||
- garbage_expire == BAD_DATE) {
- /* bad file */
- if (garbage_now != -1 && buf.st_atime > garbage_now + SEC_ONE_DAY &&
- buf.st_mtime > garbage_now + SEC_ONE_DAY) {
- ap_log_error(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, r->server,
- "proxy: deleting bad cache file with future date: %s", filename);
- #if TESTING
- fprintf(stderr, "Would unlink bad file %s\n", filename);
- #else
- unlink(filename);
- #endif
- }
- continue;
- }
-
- /*
- * we need to calculate an 'old' factor, and remove the 'oldest' files
- * so that the space requirement is met; sort by the expires date of the
- * file.
- *
- */
- fent = (struct gc_ent *) ap_push_array(files);
- fent->len = buf.st_size;
- fent->expire = garbage_expire;
- strcpy(fent->file, cachesubdir);
- strcat(fent->file, ent->d_name);
-
- /* accumulate in blocks, to cope with directories > 4Gb */
- add_long61(&curbytes, ROUNDUP2BLOCKS(buf.st_size));
- }
-
- closedir(dir);
-
- return nfiles;
-
- }
-
- /*
- * read a cache file;
- * returns 1 on success,
- * 0 on failure (bad file or wrong URL)
- * -1 on UNIX error
- */
- static int rdcache(request_rec *r, BUFF *cachefp, cache_req *c)
- {
- char urlbuff[1034], *strp;
- int len;
- /* read the data from the cache file */
- /* format
- * date SP lastmod SP expire SP count SP content-length CRLF
- * dates are stored as hex seconds since 1970
- */
- len = ap_bgets(urlbuff, sizeof urlbuff, cachefp);
- if (len == -1)
- return -1;
- if (len == 0 || urlbuff[len - 1] != '\n')
- return 0;
- urlbuff[len - 1] = '\0';
-
- if (!ap_checkmask(urlbuff,
- "&&&&&&&& &&&&&&&& &&&&&&&& &&&&&&&& &&&&&&&&"))
- return 0;
-
- c->date = ap_proxy_hex2sec(urlbuff);
- c->lmod = ap_proxy_hex2sec(urlbuff + 9);
- c->expire = ap_proxy_hex2sec(urlbuff + 18);
- c->version = ap_proxy_hex2sec(urlbuff + 27);
- c->len = ap_proxy_hex2sec(urlbuff + 36);
-
- /* check that we have the same URL */
- len = ap_bgets(urlbuff, sizeof urlbuff, cachefp);
- if (len == -1)
- return -1;
- if (len == 0 || strncmp(urlbuff, "X-URL: ", 7) != 0 ||
- urlbuff[len - 1] != '\n')
- return 0;
- urlbuff[len - 1] = '\0';
- if (strcmp(urlbuff + 7, c->url) != 0)
- return 0;
-
- /* What follows is the message */
- len = ap_bgets(urlbuff, sizeof urlbuff, cachefp);
- if (len == -1)
- return -1;
- if (len == 0 || urlbuff[len - 1] != '\n')
- return 0;
- urlbuff[--len] = '\0';
-
- c->resp_line = ap_pstrdup(r->pool, urlbuff);
- strp = strchr(urlbuff, ' ');
- if (strp == NULL)
- return 0;
-
- c->status = atoi(strp);
- c->hdrs = ap_proxy_read_headers(r, urlbuff, sizeof urlbuff, cachefp);
- if (c->hdrs == NULL)
- return -1;
- if (c->len != -1) { /* add a content-length header */
- if (ap_table_get(c->hdrs, "Content-Length") == NULL) {
- ap_table_set(c->hdrs, "Content-Length",
- ap_psprintf(r->pool, "%lu", (unsigned long)c->len));
- }
- }
- return 1;
- }
-
-
- /*
- * Call this to test for a resource in the cache
- * Returns DECLINED if we need to check the remote host
- * or an HTTP status code if successful
- *
- * Functions:
- * if URL is cached then
- * if cached file is not expired then
- * if last modified after if-modified-since then send body
- * else send 304 Not modified
- * else
- * if last modified after if-modified-since then add
- * last modified date to request
- */
- int ap_proxy_cache_check(request_rec *r, char *url, struct cache_conf *conf,
- cache_req **cr)
- {
- char hashfile[66];
- const char *imstr, *pragma, *auth;
- cache_req *c;
- time_t now;
- BUFF *cachefp;
- int cfd, i;
- const long int zero = 0L;
- void *sconf = r->server->module_config;
- proxy_server_conf *pconf =
- (proxy_server_conf *) ap_get_module_config(sconf, &proxy_module);
-
- c = ap_pcalloc(r->pool, sizeof(cache_req));
- *cr = c;
- c->req = r;
- c->url = ap_pstrdup(r->pool, url);
-
- /* get the If-Modified-Since date of the request */
- c->ims = BAD_DATE;
- imstr = ap_table_get(r->headers_in, "If-Modified-Since");
- if (imstr != NULL) {
- /* this may modify the value in the original table */
- imstr = ap_proxy_date_canon(r->pool, imstr);
- c->ims = ap_parseHTTPdate(imstr);
- if (c->ims == BAD_DATE) /* bad or out of range date; remove it */
- ap_table_unset(r->headers_in, "If-Modified-Since");
- }
-
- /* find the filename for this cache entry */
- ap_proxy_hash(url, hashfile, pconf->cache.dirlevels, pconf->cache.dirlength);
- if (conf->root != NULL)
- c->filename = ap_pstrcat(r->pool, conf->root, "/", hashfile, NULL);
- else
- c->filename = NULL;
-
- cachefp = NULL;
- /* find out about whether the request can access the cache */
- pragma = ap_table_get(r->headers_in, "Pragma");
- auth = ap_table_get(r->headers_in, "Authorization");
- Explain5("Request for %s, pragma=%s, auth=%s, ims=%ld, imstr=%s", url,
- pragma, auth, c->ims, imstr);
- if (c->filename != NULL && r->method_number == M_GET &&
- strlen(url) < 1024 && !ap_proxy_liststr(pragma, "no-cache") &&
- auth == NULL) {
- Explain1("Check file %s", c->filename);
- cfd = open(c->filename, O_RDWR | O_BINARY);
- if (cfd != -1) {
- ap_note_cleanups_for_fd(r->pool, cfd);
- cachefp = ap_bcreate(r->pool, B_RD | B_WR);
- ap_bpushfd(cachefp, cfd, cfd);
- }
- else if (errno != ENOENT)
- ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
- "proxy: error opening cache file %s",
- c->filename);
- #ifdef EXPLAIN
- else
- Explain1("File %s not found", c->filename);
- #endif
- }
-
- if (cachefp != NULL) {
- i = rdcache(r, cachefp, c);
- if (i == -1)
- ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
- "proxy: error reading cache file %s",
- c->filename);
- else if (i == 0)
- ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, r,
- "proxy: bad (short?) cache file: %s", c->filename);
- if (i != 1) {
- ap_pclosef(r->pool, cachefp->fd);
- cachefp = NULL;
- }
- }
- /* fixed? in this case, we want to get the headers from the remote server
- it will be handled later if we don't do this (I hope ;-)
- if (cachefp == NULL)
- c->hdrs = ap_make_table(r->pool, 20);
- */
- /* FIXME: Shouldn't we check the URL somewhere? */
- now = time(NULL);
- /* Ok, have we got some un-expired data? */
- if (cachefp != NULL && c->expire != BAD_DATE && now < c->expire) {
- Explain0("Unexpired data available");
- /* check IMS */
- if (c->lmod != BAD_DATE && c->ims != BAD_DATE && c->ims >= c->lmod) {
- /* has the cached file changed since this request? */
- if (c->date == BAD_DATE || c->date > c->ims) {
- /* No, but these header values may have changed, so we send them with the
- * 304 HTTP_NOT_MODIFIED response
- */
- const char *q;
-
- if ((q = ap_table_get(c->hdrs, "Expires")) != NULL)
- ap_table_set(r->headers_out, "Expires", q);
- }
- ap_pclosef(r->pool, cachefp->fd);
- Explain0("Use local copy, cached file hasn't changed");
- return HTTP_NOT_MODIFIED;
- }
-
- /* Ok, has been modified */
- Explain0("Local copy modified, send it");
- r->status_line = strchr(c->resp_line, ' ') + 1;
- r->status = c->status;
- if (!r->assbackwards) {
- ap_soft_timeout("proxy send headers", r);
- ap_proxy_send_headers(r, c->resp_line, c->hdrs);
- ap_kill_timeout(r);
- }
- ap_bsetopt(r->connection->client, BO_BYTECT, &zero);
- r->sent_bodyct = 1;
- if (!r->header_only)
- ap_proxy_send_fb(cachefp, r, NULL);
- ap_pclosef(r->pool, cachefp->fd);
- return OK;
- }
-
- /* if we already have data and a last-modified date, and it is not a head
- * request, then add an If-Modified-Since
- */
-
- if (cachefp != NULL && c->lmod != BAD_DATE && !r->header_only) {
- /*
- * use the later of the one from the request and the last-modified date
- * from the cache
- */
- if (c->ims == BAD_DATE || c->ims < c->lmod) {
- const char *q;
-
- if ((q = ap_table_get(c->hdrs, "Last-Modified")) != NULL)
- ap_table_set(r->headers_in, "If-Modified-Since",
- (char *) q);
- }
- }
- c->fp = cachefp;
-
- Explain0("Local copy not present or expired. Declining.");
-
- return DECLINED;
- }
-
- /*
- * Having read the response from the client, decide what to do
- * If the response is not cachable, then delete any previously cached
- * response, and copy data from remote server to client.
- * Functions:
- * parse dates
- * check for an uncachable response
- * calculate an expiry date, if one is not provided
- * if the remote file has not been modified, then return the document
- * from the cache, maybe updating the header line
- * otherwise, delete the old cached file and open a new temporary file
- */
- int ap_proxy_cache_update(cache_req *c, table *resp_hdrs,
- const int is_HTTP1, int nocache)
- {
- #ifdef ULTRIX_BRAIN_DEATH
- extern char *mktemp(char *template);
- #endif
- request_rec *r = c->req;
- char *p;
- int i;
- const char *expire, *lmods, *dates, *clen;
- time_t expc, date, lmod, now;
- char buff[46];
- void *sconf = r->server->module_config;
- proxy_server_conf *conf =
- (proxy_server_conf *) ap_get_module_config(sconf, &proxy_module);
- const long int zero = 0L;
-
- c->tempfile = NULL;
-
- /* we've received the response */
- /* read expiry date; if a bad date, then leave it so the client can
- * read it
- */
- expire = ap_table_get(resp_hdrs, "Expires");
- if (expire != NULL)
- expc = ap_parseHTTPdate(expire);
- else
- expc = BAD_DATE;
-
- /*
- * read the last-modified date; if the date is bad, then delete it
- */
- lmods = ap_table_get(resp_hdrs, "Last-Modified");
- if (lmods != NULL) {
- lmod = ap_parseHTTPdate(lmods);
- if (lmod == BAD_DATE) {
- /* kill last modified date */
- lmods = NULL;
- }
- }
- else
- lmod = BAD_DATE;
-
- /*
- * what responses should we not cache?
- * Unknown status responses and those known to be uncacheable
- * 304 HTTP_NOT_MODIFIED response when we have no valid cache file, or
- * 200 HTTP_OK response from HTTP/1.0 and up without a Last-Modified header, or
- * HEAD requests, or
- * requests with an Authorization header, or
- * protocol requests nocache (e.g. ftp with user/password)
- */
- /* @@@ XXX FIXME: is the test "r->status != HTTP_MOVED_PERMANENTLY" corerct?
- * or shouldn't it be "ap_is_HTTP_REDIRECT(r->status)" ? -MnKr */
- if ((r->status != HTTP_OK && r->status != HTTP_MOVED_PERMANENTLY && r->status != HTTP_NOT_MODIFIED) ||
- (expire != NULL && expc == BAD_DATE) ||
- (r->status == HTTP_NOT_MODIFIED && (c == NULL || c->fp == NULL)) ||
- (r->status == HTTP_OK && lmods == NULL && is_HTTP1) ||
- r->header_only ||
- ap_table_get(r->headers_in, "Authorization") != NULL ||
- nocache) {
- Explain1("Response is not cacheable, unlinking %s", c->filename);
- /* close the file */
- if (c->fp != NULL) {
- ap_pclosef(r->pool, c->fp->fd);
- c->fp = NULL;
- }
- /* delete the previously cached file */
- if (c->filename)
- unlink(c->filename);
- return DECLINED; /* send data to client but not cache */
- }
-
- /* otherwise, we are going to cache the response */
- /*
- * Read the date. Generate one if one is not supplied
- */
- dates = ap_table_get(resp_hdrs, "Date");
- if (dates != NULL)
- date = ap_parseHTTPdate(dates);
- else
- date = BAD_DATE;
-
- now = time(NULL);
-
- if (date == BAD_DATE) { /* No, or bad date */
- /* no date header! */
- /* add one; N.B. use the time _now_ rather than when we were checking the cache
- */
- date = now;
- dates = ap_gm_timestr_822(r->pool, now);
- ap_table_set(resp_hdrs, "Date", dates);
- Explain0("Added date header");
- }
-
- /* check last-modified date */
- if (lmod != BAD_DATE && lmod > date)
- /* if its in the future, then replace by date */
- {
- lmod = date;
- lmods = dates;
- Explain0("Last modified is in the future, replacing with now");
- }
- /* if the response did not contain the header, then use the cached version */
- if (lmod == BAD_DATE && c->fp != NULL) {
- lmod = c->lmod;
- Explain0("Reusing cached last modified");
- }
-
- /* we now need to calculate the expire data for the object. */
- if (expire == NULL && c->fp != NULL) { /* no expiry data sent in response */
- expire = ap_table_get(c->hdrs, "Expires");
- if (expire != NULL)
- expc = ap_parseHTTPdate(expire);
- }
- /* so we now have the expiry date */
- /* if no expiry date then
- * if lastmod
- * expiry date = now + min((date - lastmod) * factor, maxexpire)
- * else
- * expire date = now + defaultexpire
- */
- Explain1("Expiry date is %ld", expc);
- if (expc == BAD_DATE) {
- if (lmod != BAD_DATE) {
- double x = (double) (date - lmod) * conf->cache.lmfactor;
- double maxex = conf->cache.maxexpire;
- if (x > maxex)
- x = maxex;
- expc = now + (int) x;
- }
- else
- expc = now + conf->cache.defaultexpire;
- Explain1("Expiry date calculated %ld", expc);
- }
-
- /* get the content-length header */
- clen = ap_table_get(resp_hdrs, "Content-Length");
- if (clen == NULL)
- c->len = -1;
- else
- c->len = atoi(clen);
-
- ap_proxy_sec2hex(date, buff);
- buff[8] = ' ';
- ap_proxy_sec2hex(lmod, buff + 9);
- buff[17] = ' ';
- ap_proxy_sec2hex(expc, buff + 18);
- buff[26] = ' ';
- ap_proxy_sec2hex(c->version++, buff + 27);
- buff[35] = ' ';
- ap_proxy_sec2hex(c->len, buff + 36);
- buff[44] = '\n';
- buff[45] = '\0';
-
- /* if file not modified */
- if (r->status == HTTP_NOT_MODIFIED) {
- if (c->ims != BAD_DATE && lmod != BAD_DATE && lmod <= c->ims) {
- /* set any changed headers somehow */
- /* update dates and version, but not content-length */
- if (lmod != c->lmod || expc != c->expire || date != c->date) {
- off_t curpos = lseek(c->fp->fd, 0, SEEK_SET);
- if (curpos == -1)
- ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
- "proxy: error seeking on cache file %s",
- c->filename);
- else if (write(c->fp->fd, buff, 35) == -1)
- ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
- "proxy: error updating cache file %s",
- c->filename);
- }
- ap_pclosef(r->pool, c->fp->fd);
- Explain0("Remote document not modified, use local copy");
- /* CHECKME: Is this right? Shouldn't we check IMS again here? */
- return HTTP_NOT_MODIFIED;
- }
- else {
- /* return the whole document */
- Explain0("Remote document updated, sending");
- r->status_line = strchr(c->resp_line, ' ') + 1;
- r->status = c->status;
- if (!r->assbackwards) {
- ap_soft_timeout("proxy send headers", r);
- ap_proxy_send_headers(r, c->resp_line, c->hdrs);
- ap_kill_timeout(r);
- }
- ap_bsetopt(r->connection->client, BO_BYTECT, &zero);
- r->sent_bodyct = 1;
- if (!r->header_only)
- ap_proxy_send_fb(c->fp, r, NULL);
- /* set any changed headers somehow */
- /* update dates and version, but not content-length */
- if (lmod != c->lmod || expc != c->expire || date != c->date) {
- off_t curpos = lseek(c->fp->fd, 0, SEEK_SET);
-
- if (curpos == -1)
- ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
- "proxy: error seeking on cache file %s",
- c->filename);
- else if (write(c->fp->fd, buff, 35) == -1)
- ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
- "proxy: error updating cache file %s",
- c->filename);
- }
- ap_pclosef(r->pool, c->fp->fd);
- return OK;
- }
- }
- /* new or modified file */
- if (c->fp != NULL) {
- ap_pclosef(r->pool, c->fp->fd);
- c->fp->fd = -1;
- }
- c->version = 0;
- ap_proxy_sec2hex(0, buff + 27);
- buff[35] = ' ';
-
- /* open temporary file */
- #define TMPFILESTR "/tmpXXXXXX"
- if (conf->cache.root == NULL)
- return DECLINED;
- c->tempfile = ap_palloc(r->pool, strlen(conf->cache.root) + sizeof(TMPFILESTR));
- strcpy(c->tempfile, conf->cache.root);
- strcat(c->tempfile, TMPFILESTR);
- #undef TMPFILESTR
- p = mktemp(c->tempfile);
- if (p == NULL)
- return DECLINED;
-
- Explain1("Create temporary file %s", c->tempfile);
-
- i = open(c->tempfile, O_WRONLY | O_CREAT | O_EXCL | O_BINARY, 0622);
- if (i == -1) {
- ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
- "proxy: error creating cache file %s",
- c->tempfile);
- return DECLINED;
- }
- ap_note_cleanups_for_fd(r->pool, i);
- c->fp = ap_bcreate(r->pool, B_WR);
- ap_bpushfd(c->fp, -1, i);
-
- if (ap_bvputs(c->fp, buff, "X-URL: ", c->url, "\n", NULL) == -1) {
- ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
- "proxy: error writing cache file(%s)", c->tempfile);
- ap_pclosef(r->pool, c->fp->fd);
- unlink(c->tempfile);
- c->fp = NULL;
- }
- return DECLINED;
- }
-
- void ap_proxy_cache_tidy(cache_req *c)
- {
- server_rec *s;
- long int bc;
-
- if (c == NULL || c->fp == NULL)
- return;
-
- s = c->req->server;
-
- /* don't care how much was sent, but rather how much was written to cache
- ap_bgetopt(c->req->connection->client, BO_BYTECT, &bc);
- */
- bc = c->written;
-
- if (c->len != -1) {
- /* file lengths don't match; don't cache it */
- if (bc != c->len) {
- ap_pclosef(c->req->pool, c->fp->fd); /* no need to flush */
- unlink(c->tempfile);
- return;
- }
- }
- /* don't care if aborted, cache it if fully retrieved from host!
- else if (c->req->connection->aborted) {
- ap_pclosef(c->req->pool, c->fp->fd); / no need to flush /
- unlink(c->tempfile);
- return;
- }
- */
- else {
- /* update content-length of file */
- char buff[9];
- off_t curpos;
-
- c->len = bc;
- ap_bflush(c->fp);
- ap_proxy_sec2hex(c->len, buff);
- curpos = lseek(c->fp->fd, 36, SEEK_SET);
- if (curpos == -1)
- ap_log_error(APLOG_MARK, APLOG_ERR, s,
- "proxy: error seeking on cache file %s", c->tempfile);
- else if (write(c->fp->fd, buff, 8) == -1)
- ap_log_error(APLOG_MARK, APLOG_ERR, s,
- "proxy: error updating cache file %s", c->tempfile);
- }
-
- if (ap_bflush(c->fp) == -1) {
- ap_log_error(APLOG_MARK, APLOG_ERR, s,
- "proxy: error writing to cache file %s",
- c->tempfile);
- ap_pclosef(c->req->pool, c->fp->fd);
- unlink(c->tempfile);
- return;
- }
-
- if (ap_pclosef(c->req->pool, c->fp->fd) == -1) {
- ap_log_error(APLOG_MARK, APLOG_ERR, s,
- "proxy: error closing cache file %s", c->tempfile);
- unlink(c->tempfile);
- return;
- }
-
- if (unlink(c->filename) == -1 && errno != ENOENT) {
- ap_log_error(APLOG_MARK, APLOG_ERR, s,
- "proxy: error deleting old cache file %s",
- c->tempfile);
- }
- else {
- char *p;
- proxy_server_conf *conf =
- (proxy_server_conf *) ap_get_module_config(s->module_config, &proxy_module);
-
- for (p = c->filename + strlen(conf->cache.root) + 1;;) {
- p = strchr(p, '/');
- if (!p)
- break;
- *p = '\0';
- #ifdef WIN32
- if (mkdir(c->filename) < 0 && errno != EEXIST)
- #else
- if (mkdir(c->filename, S_IREAD | S_IWRITE | S_IEXEC) < 0 && errno != EEXIST)
- #endif /* WIN32 */
- ap_log_error(APLOG_MARK, APLOG_ERR, s,
- "proxy: error creating cache directory %s",
- c->filename);
- *p = '/';
- ++p;
- }
- #if defined(OS2) || defined(WIN32)
- /* Under OS/2 use rename. */
- if (rename(c->tempfile, c->filename) == -1)
- ap_log_error(APLOG_MARK, APLOG_ERR, s,
- "proxy: error renaming cache file %s to %s",
- c->tempfile, c->filename);
- }
- #else
-
- if (link(c->tempfile, c->filename) == -1)
- ap_log_error(APLOG_MARK, APLOG_ERR, s,
- "proxy: error linking cache file %s to %s",
- c->tempfile, c->filename);
- }
-
- if (unlink(c->tempfile) == -1)
- ap_log_error(APLOG_MARK, APLOG_ERR, s,
- "proxy: error deleting temp file %s", c->tempfile);
- #endif
-
- }
-