/* ====================================================================
* 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 .
*
*/
/* 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
#else
#include
#endif /* WIN32 */
#include "multithread.h"
#include "ap_md5.h"
#ifdef __TANDEM
#include
#include
#endif
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 garbage_now, garbage_expire;
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);
static int should_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 (should_proxy_garbage_coll(r))
#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;
#if 0
ap_log_error(APLOG_MARK, APLOG_DEBUG, r->server,
"proxy: Guess what; we fork() again...");
#endif
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 */
#define DOT_TIME "/.time" /* marker */
static int should_proxy_garbage_coll(request_rec *r)
{
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;
const char *cachedir = conf->root;
char *filename;
struct stat buf;
int timefd;
time_t every = conf->gcinterval;
static time_t lastcheck = BAD_DATE; /* static (per-process) data!!! */
if (cachedir == NULL || every == -1)
return 0;
filename = ap_palloc(r->pool, strlen(cachedir) + strlen( DOT_TIME ) +1);
garbage_now = time(NULL);
/* Usually, the modification time of /.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 0;
strcpy(filename,cachedir);
strcat(filename,DOT_TIME);
/* At this point we have a bit of an engineering compromise. We could either
* create and/or mark the .time file (prior to the fork which might
* fail on a resource issue) or wait until we are safely forked. The
* advantage of doing it now in this process is that we get some
* usefull live out of the global last check variable. (XXX which
* should go scoreboard IMHO.) Note that the actual counting is
* at a later moment.
*/
if (stat(filename, &buf) == -1) { /* does not exist */
if (errno != ENOENT) {
ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
"proxy: stat(%s)", filename);
return 0;
}
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 */
return 0;
}
close(timefd);
}
else {
lastcheck = buf.st_mtime; /* save the time */
if (garbage_now < lastcheck + every) {
return 0;
}
if (utime(filename, NULL) == -1)
ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
"proxy: utimes(%s)", filename);
}
return 1;
}
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 gc_ent *fent;
char *filename;
int i;
cachedir = conf->root;
filename = ap_palloc(r->pool, strlen(cachedir) + HASH_LEN + 2);
/* configured size is given in kB. Make it bytes, convert to long61_t: */
cachesize.lower = cachesize.upper = 0;
add_long61(&cachesize, conf->space << 10);
ap_block_alarms(); /* avoid SIGALRM on big cache cleanup */
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;
char *filename;
ap_snprintf(cachedir, sizeof(cachedir), "%s%s", cachebasedir, cachesubdir);
filename = ap_palloc(r->pool, strlen(cachedir) + HASH_LEN + 2);
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)
{
#if defined(ULTRIX_BRAIN_DEATH) || defined(SINIX_D_RESOLVER_BUG)
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" correct?
* 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)
#elif defined(__TANDEM)
if (mkdir(c->filename, S_IRWXU | S_IRWXG | S_IRWXO) < 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
}