home *** CD-ROM | disk | FTP | other *** search
/ PC Online 1999 April / PCO0499.ISO / filesbbs / os2 / apach134.arj / APACH134.ZIP / src / modules / proxy / proxy_cache.c < prev    next >
Encoding:
C/C++ Source or Header  |  1999-01-01  |  33.7 KB  |  1,139 lines

  1. /* ====================================================================
  2.  * Copyright (c) 1996-1999 The Apache Group.  All rights reserved.
  3.  *
  4.  * Redistribution and use in source and binary forms, with or without
  5.  * modification, are permitted provided that the following conditions
  6.  * are met:
  7.  *
  8.  * 1. Redistributions of source code must retain the above copyright
  9.  *    notice, this list of conditions and the following disclaimer. 
  10.  *
  11.  * 2. Redistributions in binary form must reproduce the above copyright
  12.  *    notice, this list of conditions and the following disclaimer in
  13.  *    the documentation and/or other materials provided with the
  14.  *    distribution.
  15.  *
  16.  * 3. All advertising materials mentioning features or use of this
  17.  *    software must display the following acknowledgment:
  18.  *    "This product includes software developed by the Apache Group
  19.  *    for use in the Apache HTTP server project (http://www.apache.org/)."
  20.  *
  21.  * 4. The names "Apache Server" and "Apache Group" must not be used to
  22.  *    endorse or promote products derived from this software without
  23.  *    prior written permission. For written permission, please contact
  24.  *    apache@apache.org.
  25.  *
  26.  * 5. Products derived from this software may not be called "Apache"
  27.  *    nor may "Apache" appear in their names without prior written
  28.  *    permission of the Apache Group.
  29.  *
  30.  * 6. Redistributions of any form whatsoever must retain the following
  31.  *    acknowledgment:
  32.  *    "This product includes software developed by the Apache Group
  33.  *    for use in the Apache HTTP server project (http://www.apache.org/)."
  34.  *
  35.  * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY
  36.  * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  37.  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  38.  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE APACHE GROUP OR
  39.  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  40.  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  41.  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  42.  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  43.  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  44.  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  45.  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
  46.  * OF THE POSSIBILITY OF SUCH DAMAGE.
  47.  * ====================================================================
  48.  *
  49.  * This software consists of voluntary contributions made by many
  50.  * individuals on behalf of the Apache Group and was originally based
  51.  * on public domain software written at the National Center for
  52.  * Supercomputing Applications, University of Illinois, Urbana-Champaign.
  53.  * For more information on the Apache Group and the Apache HTTP server
  54.  * project, please see <http://www.apache.org/>.
  55.  *
  56.  */
  57.  
  58. /* Cache and garbage collection routines for Apache proxy */
  59.  
  60. #include "mod_proxy.h"
  61. #include "http_conf_globals.h"
  62. #include "http_log.h"
  63. #include "http_main.h"
  64. #include "util_date.h"
  65. #ifdef WIN32
  66. #include <sys/utime.h>
  67. #else
  68. #include <utime.h>
  69. #endif /* WIN32 */
  70. #include "multithread.h"
  71. #include "ap_md5.h"
  72.  
  73. DEF_Explain
  74.  
  75. struct gc_ent {
  76.     unsigned long int len;
  77.     time_t expire;
  78.     char file[HASH_LEN + 1];
  79. };
  80.  
  81. /* Poor man's 61 bit arithmetic */
  82. typedef struct {
  83.     long lower;    /* lower 30 bits of result */
  84.     long upper; /* upper 31 bits of result */
  85. } long61_t;
  86.  
  87. /* FIXME: The block size can be different on a `per file system' base.
  88.  * This would make automatic detection highly OS specific.
  89.  * In the GNU fileutils code for du(1), you can see how complicated it can
  90.  * become to detect the block size. And, with BSD-4.x fragments, it
  91.  * it even more difficult to get precise results.
  92.  * As a compromise (and to improve on the incorrect counting of cache
  93.  * size on byte level, omitting directory sizes entirely, which was
  94.  * used up to apache-1.3b7) we're rounding to multiples of 512 here.
  95.  * Your file system may be using larger blocks (I certainly hope so!)
  96.  * but it will hardly use smaller blocks.
  97.  * (So this approximation is still closer to reality than the old behavior).
  98.  * The best solution would be automatic detection, the next best solution
  99.  * IMHO is a sensible default and the possibility to override it.
  100.  */
  101.  
  102. #define ROUNDUP2BLOCKS(_bytes) (((_bytes)+block_size-1) & ~(block_size-1))
  103. static long block_size = 512;    /* this must be a power of 2 */
  104. static long61_t curbytes, cachesize;
  105. static time_t every, garbage_now, garbage_expire;
  106. static char *filename;
  107. static mutex *garbage_mutex = NULL;
  108.  
  109.  
  110. int ap_proxy_garbage_init(server_rec *r, pool *p)
  111. {
  112.     if (!garbage_mutex)
  113.     garbage_mutex = ap_create_mutex(NULL);
  114.  
  115.     return (0);
  116. }
  117.  
  118.  
  119. static int sub_garbage_coll(request_rec *r, array_header *files,
  120.                 const char *cachedir, const char *cachesubdir);
  121. static void help_proxy_garbage_coll(request_rec *r);
  122. #if !defined(WIN32) && !defined(MPE) && !defined(OS2)
  123. static void detached_proxy_garbage_coll(request_rec *r);
  124. #endif
  125.  
  126.  
  127. void ap_proxy_garbage_coll(request_rec *r)
  128. {
  129.     static int inside = 0;
  130.  
  131.     (void) ap_acquire_mutex(garbage_mutex);
  132.     if (inside == 1) {
  133.     (void) ap_release_mutex(garbage_mutex);
  134.     return;
  135.     }
  136.     else
  137.     inside = 1;
  138.     (void) ap_release_mutex(garbage_mutex);
  139.  
  140.     ap_block_alarms();        /* avoid SIGALRM on big cache cleanup */
  141. #if !defined(WIN32) && !defined(MPE) && !defined(OS2)
  142.     detached_proxy_garbage_coll(r);
  143. #else
  144.     help_proxy_garbage_coll(r);
  145. #endif
  146.     ap_unblock_alarms();
  147.  
  148.     (void) ap_acquire_mutex(garbage_mutex);
  149.     inside = 0;
  150.     (void) ap_release_mutex(garbage_mutex);
  151. }
  152.  
  153.  
  154. static void
  155. add_long61 (long61_t *accu, long val)
  156. {
  157.     /* Add in lower 30 bits */
  158.     accu->lower += (val & 0x3FFFFFFFL);
  159.     /* add in upper bits, and carry */
  160.     accu->upper += (val >> 30) + ((accu->lower & ~0x3FFFFFFFL) != 0L);
  161.     /* Clear carry */
  162.     accu->lower &= 0x3FFFFFFFL;
  163. }
  164.  
  165. static void
  166. sub_long61 (long61_t *accu, long val)
  167. {
  168.     int carry = (val & 0x3FFFFFFFL) > accu->lower;
  169.     /* Subtract lower 30 bits */
  170.     accu->lower = accu->lower - (val & 0x3FFFFFFFL) + ((carry) ? 0x40000000 : 0);
  171.     /* add in upper bits, and carry */
  172.     accu->upper -= (val >> 30) + carry;
  173. }
  174.  
  175. /* Compare two long61's:
  176.  * return <0 when left < right
  177.  * return  0 when left == right
  178.  * return >0 when left > right
  179.  */
  180. static long
  181. cmp_long61 (long61_t *left, long61_t *right)
  182. {
  183.     return (left->upper == right->upper) ? (left->lower - right->lower)
  184.                      : (left->upper - right->upper);
  185. }
  186.  
  187. /* Compare two gc_ent's, sort them by expiration date */
  188. static int gcdiff(const void *ap, const void *bp)
  189. {
  190.     const struct gc_ent *a = (const struct gc_ent * const) ap;
  191.     const struct gc_ent *b = (const struct gc_ent * const) bp;
  192.  
  193.     if (a->expire > b->expire)
  194.     return 1;
  195.     else if (a->expire < b->expire)
  196.     return -1;
  197.     else
  198.     return 0;
  199. }
  200.  
  201. #if !defined(WIN32) && !defined(MPE) && !defined(OS2)
  202. static void detached_proxy_garbage_coll(request_rec *r)
  203. {
  204.     pid_t pid;
  205.     int status;
  206.     pid_t pgrp;
  207.  
  208.     switch (pid = fork()) {
  209.     case -1:
  210.         ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
  211.              "proxy: fork() for cache cleanup failed");
  212.         return;
  213.  
  214.     case 0:    /* Child */
  215.  
  216.         /* close all sorts of things, including the socket fd */
  217.         ap_cleanup_for_exec();
  218.  
  219.         /* Fork twice to disassociate from the child */
  220.         switch (pid = fork()) {
  221.         case -1:
  222.             ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
  223.              "proxy: fork(2nd) for cache cleanup failed");
  224.             exit(1);
  225.  
  226.         case 0:    /* Child */
  227.             /* The setpgrp() stuff was snarfed from http_main.c */
  228. #ifndef NO_SETSID
  229.             if ((pgrp = setsid()) == -1) {
  230.             perror("setsid");
  231.             fprintf(stderr, "%s: setsid failed\n",
  232.                 ap_server_argv0);
  233.             exit(1);
  234.             }
  235. #elif defined(NEXT) || defined(NEWSOS)
  236.             if (setpgrp(0, getpid()) == -1 || (pgrp = getpgrp(0)) == -1) {
  237.             perror("setpgrp");
  238.             fprintf(stderr, "%S: setpgrp or getpgrp failed\n",
  239.                 ap_server_argv0);
  240.             exit(1);
  241.             }
  242. #else
  243.             if ((pgrp = setpgrp(getpid(), 0)) == -1) {
  244.             perror("setpgrp");
  245.             fprintf(stderr, "%s: setpgrp failed\n",
  246.                 ap_server_argv0);
  247.             exit(1);
  248.             }
  249. #endif
  250.             help_proxy_garbage_coll(r);
  251.             exit(0);
  252.  
  253.         default:    /* Father */
  254.             /* After grandson has been forked off, */
  255.             /* there's nothing else to do. */
  256.             exit(0);            
  257.         }
  258.     default:
  259.         /* Wait until grandson has been forked off */
  260.         /* (without wait we'd leave a zombie) */
  261.         waitpid(pid, &status, 0);
  262.         return;
  263.     }
  264. }
  265. #endif /* ndef WIN32 */
  266.  
  267. static void help_proxy_garbage_coll(request_rec *r)
  268. {
  269.     const char *cachedir;
  270.     void *sconf = r->server->module_config;
  271.     proxy_server_conf *pconf =
  272.     (proxy_server_conf *) ap_get_module_config(sconf, &proxy_module);
  273.     const struct cache_conf *conf = &pconf->cache;
  274.     array_header *files;
  275.     struct stat buf;
  276.     struct gc_ent *fent;
  277.     int i, timefd;
  278.     static time_t lastcheck = BAD_DATE;        /* static (per-process) data!!! */
  279.  
  280.     cachedir = conf->root;
  281.     /* configured size is given in kB. Make it bytes, convert to long61_t: */
  282.     cachesize.lower = cachesize.upper = 0;
  283.     add_long61(&cachesize, conf->space << 10);
  284.     every = conf->gcinterval;
  285.  
  286.     if (cachedir == NULL || every == -1)
  287.     return;
  288.     garbage_now = time(NULL);
  289.     /* Usually, the modification time of <cachedir>/.time can only increase.
  290.      * Thus, even with several child processes having their own copy of
  291.      * lastcheck, if time(NULL) still < lastcheck then it's not time
  292.      * for GC yet.
  293.      */
  294.     if (garbage_now != -1 && lastcheck != BAD_DATE && garbage_now < lastcheck + every)
  295.     return;
  296.  
  297.     ap_block_alarms();        /* avoid SIGALRM on big cache cleanup */
  298.  
  299.     filename = ap_palloc(r->pool, strlen(cachedir) + HASH_LEN + 2);
  300.     strcpy(filename, cachedir);
  301.     strcat(filename, "/.time");
  302.     if (stat(filename, &buf) == -1) {    /* does not exist */
  303.     if (errno != ENOENT) {
  304.         ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
  305.              "proxy: stat(%s)", filename);
  306.         ap_unblock_alarms();
  307.         return;
  308.     }
  309.     if ((timefd = creat(filename, 0666)) == -1) {
  310.         if (errno != EEXIST)
  311.         ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
  312.                  "proxy: creat(%s)", filename);
  313.         else
  314.         lastcheck = garbage_now;    /* someone else got in there */
  315.         ap_unblock_alarms();
  316.         return;
  317.     }
  318.     close(timefd);
  319.     }
  320.     else {
  321.     lastcheck = buf.st_mtime;    /* save the time */
  322.     if (garbage_now < lastcheck + every) {
  323.         ap_unblock_alarms();
  324.         return;
  325.     }
  326.     if (utime(filename, NULL) == -1)
  327.         ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
  328.              "proxy: utimes(%s)", filename);
  329.     }
  330.     files = ap_make_array(r->pool, 100, sizeof(struct gc_ent));
  331.     curbytes.upper = curbytes.lower = 0L;
  332.  
  333.     sub_garbage_coll(r, files, cachedir, "/");
  334.  
  335.     if (cmp_long61(&curbytes, &cachesize) < 0L) {
  336.     ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r->server,
  337.              "proxy GC: Cache is %ld%% full (nothing deleted)",
  338.              (long)(((curbytes.upper<<20)|(curbytes.lower>>10))*100/conf->space));
  339.     ap_unblock_alarms();
  340.     return;
  341.     }
  342.  
  343.     /* sort the files we found by expiration date */
  344.     qsort(files->elts, files->nelts, sizeof(struct gc_ent), gcdiff);
  345.  
  346.     for (i = 0; i < files->nelts; i++) {
  347.     fent = &((struct gc_ent *) files->elts)[i];
  348.     sprintf(filename, "%s%s", cachedir, fent->file);
  349.     Explain3("GC Unlinking %s (expiry %ld, garbage_now %ld)", filename, fent->expire, garbage_now);
  350. #if TESTING
  351.     fprintf(stderr, "Would unlink %s\n", filename);
  352. #else
  353.     if (unlink(filename) == -1) {
  354.         if (errno != ENOENT)
  355.         ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
  356.                  "proxy gc: unlink(%s)", filename);
  357.     }
  358.     else
  359. #endif
  360.     {
  361.         sub_long61(&curbytes, ROUNDUP2BLOCKS(fent->len));
  362.         if (cmp_long61(&curbytes, &cachesize) < 0)
  363.         break;
  364.     }
  365.     }
  366.  
  367.     ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r->server,
  368.              "proxy GC: Cache is %ld%% full (%d deleted)",
  369.              (long)(((curbytes.upper<<20)|(curbytes.lower>>10))*100/conf->space), i);
  370.     ap_unblock_alarms();
  371. }
  372.  
  373. static int sub_garbage_coll(request_rec *r, array_header *files,
  374.               const char *cachebasedir, const char *cachesubdir)
  375. {
  376.     char line[27];
  377.     char cachedir[HUGE_STRING_LEN];
  378.     struct stat buf;
  379.     int fd, i;
  380.     DIR *dir;
  381. #if defined(NEXT) || defined(WIN32)
  382.     struct DIR_TYPE *ent;
  383. #else
  384.     struct dirent *ent;
  385. #endif
  386.     struct gc_ent *fent;
  387.     int nfiles = 0;
  388.  
  389.     ap_snprintf(cachedir, sizeof(cachedir), "%s%s", cachebasedir, cachesubdir);
  390.     Explain1("GC Examining directory %s", cachedir);
  391.     dir = opendir(cachedir);
  392.     if (dir == NULL) {
  393.     ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
  394.              "proxy gc: opendir(%s)", cachedir);
  395.     return 0;
  396.     }
  397.  
  398.     while ((ent = readdir(dir)) != NULL) {
  399.     if (ent->d_name[0] == '.')
  400.         continue;
  401.     sprintf(filename, "%s%s", cachedir, ent->d_name);
  402.     Explain1("GC Examining file %s", filename);
  403. /* is it a temporary file? */
  404.     if (strncmp(ent->d_name, "tmp", 3) == 0) {
  405. /* then stat it to see how old it is; delete temporary files > 1 day old */
  406.         if (stat(filename, &buf) == -1) {
  407.         if (errno != ENOENT)
  408.             ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
  409.                  "proxy gc: stat(%s)", filename);
  410.         }
  411.         else if (garbage_now != -1 && buf.st_atime < garbage_now - SEC_ONE_DAY &&
  412.              buf.st_mtime < garbage_now - SEC_ONE_DAY) {
  413.         Explain1("GC unlink %s", filename);
  414.         ap_log_error(APLOG_MARK, APLOG_INFO|APLOG_NOERRNO, r->server,
  415.                  "proxy gc: deleting orphaned cache file %s", filename);
  416. #if TESTING
  417.         fprintf(stderr, "Would unlink %s\n", filename);
  418. #else
  419.         unlink(filename);
  420. #endif
  421.         }
  422.         continue;
  423.     }
  424.     ++nfiles;
  425. /* is it another file? */
  426.     /* FIXME: Shouldn't any unexpected files be deleted? */
  427.     /*      if (strlen(ent->d_name) != HASH_LEN) continue; */
  428.  
  429. /* under OS/2 use dirent's d_attr to identify a diretory */
  430. #ifdef OS2
  431. /* is it a directory? */
  432.     if (ent->d_attr & A_DIR) {
  433.         char newcachedir[HUGE_STRING_LEN];
  434.         ap_snprintf(newcachedir, sizeof(newcachedir),
  435.             "%s%s/", cachesubdir, ent->d_name);
  436.         if (!sub_garbage_coll(r, files, cachebasedir, newcachedir)) {
  437.         ap_snprintf(newcachedir, sizeof(newcachedir),
  438.                 "%s%s", cachedir, ent->d_name);
  439. #if TESTING
  440.         fprintf(stderr, "Would remove directory %s\n", newcachedir);
  441. #else
  442.         rmdir(newcachedir);
  443. #endif
  444.         --nfiles;
  445.         }
  446.         continue;
  447.     }
  448. #endif
  449.  
  450. /* read the file */
  451.     fd = open(filename, O_RDONLY | O_BINARY);
  452.     if (fd == -1) {
  453.         if (errno != ENOENT)
  454.         ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
  455.                  "proxy gc: open(%s)", filename);
  456.         continue;
  457.     }
  458.     if (fstat(fd, &buf) == -1) {
  459.         ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
  460.              "proxy gc: fstat(%s)", filename);
  461.         close(fd);
  462.         continue;
  463.     }
  464.  
  465. /* In OS/2 this has already been done above */
  466. #ifndef OS2
  467.     if (S_ISDIR(buf.st_mode)) {
  468.         char newcachedir[HUGE_STRING_LEN];
  469.         close(fd);
  470.         ap_snprintf(newcachedir, sizeof(newcachedir),
  471.             "%s%s/", cachesubdir, ent->d_name);
  472.         if (!sub_garbage_coll(r, files, cachebasedir, newcachedir)) {
  473.         ap_snprintf(newcachedir, sizeof(newcachedir),
  474.                 "%s%s", cachedir, ent->d_name);
  475. #if TESTING
  476.         fprintf(stderr, "Would remove directory %s\n", newcachedir);
  477. #else
  478.         rmdir(newcachedir);
  479. #endif
  480.         --nfiles;
  481.         } else {
  482.         /* Directory is not empty. Account for its size: */
  483.         add_long61(&curbytes, ROUNDUP2BLOCKS(buf.st_size));
  484.         }
  485.         continue;
  486.     }
  487. #endif
  488.  
  489.     i = read(fd, line, 26);
  490.     close(fd);
  491.     if (i == -1) {
  492.         ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
  493.              "proxy gc: read(%s)", filename);
  494.         continue;
  495.     }
  496.     line[i] = '\0';
  497.     garbage_expire = ap_proxy_hex2sec(line + 18);
  498.     if (!ap_checkmask(line, "&&&&&&&& &&&&&&&& &&&&&&&&") ||
  499.         garbage_expire == BAD_DATE) {
  500.         /* bad file */
  501.         if (garbage_now != -1 && buf.st_atime > garbage_now + SEC_ONE_DAY &&
  502.         buf.st_mtime > garbage_now + SEC_ONE_DAY) {
  503.         ap_log_error(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, r->server,
  504.                  "proxy: deleting bad cache file with future date: %s", filename);
  505. #if TESTING
  506.         fprintf(stderr, "Would unlink bad file %s\n", filename);
  507. #else
  508.         unlink(filename);
  509. #endif
  510.         }
  511.         continue;
  512.     }
  513.  
  514. /*
  515.  * we need to calculate an 'old' factor, and remove the 'oldest' files
  516.  * so that the space requirement is met; sort by the expires date of the
  517.  * file.
  518.  *
  519.  */
  520.     fent = (struct gc_ent *) ap_push_array(files);
  521.     fent->len = buf.st_size;
  522.     fent->expire = garbage_expire;
  523.     strcpy(fent->file, cachesubdir);
  524.     strcat(fent->file, ent->d_name);
  525.  
  526. /* accumulate in blocks, to cope with directories > 4Gb */
  527.     add_long61(&curbytes, ROUNDUP2BLOCKS(buf.st_size));
  528.     }
  529.  
  530.     closedir(dir);
  531.  
  532.     return nfiles;
  533.  
  534. }
  535.  
  536. /*
  537.  * read a cache file;
  538.  * returns 1 on success,
  539.  *         0 on failure (bad file or wrong URL)
  540.  *        -1 on UNIX error
  541.  */
  542. static int rdcache(request_rec *r, BUFF *cachefp, cache_req *c)
  543. {
  544.     char urlbuff[1034], *strp;
  545.     int len;
  546. /* read the data from the cache file */
  547. /* format
  548.  * date SP lastmod SP expire SP count SP content-length CRLF
  549.  * dates are stored as hex seconds since 1970
  550.  */
  551.     len = ap_bgets(urlbuff, sizeof urlbuff, cachefp);
  552.     if (len == -1)
  553.     return -1;
  554.     if (len == 0 || urlbuff[len - 1] != '\n')
  555.     return 0;
  556.     urlbuff[len - 1] = '\0';
  557.  
  558.     if (!ap_checkmask(urlbuff,
  559.            "&&&&&&&& &&&&&&&& &&&&&&&& &&&&&&&& &&&&&&&&"))
  560.     return 0;
  561.  
  562.     c->date = ap_proxy_hex2sec(urlbuff);
  563.     c->lmod = ap_proxy_hex2sec(urlbuff + 9);
  564.     c->expire = ap_proxy_hex2sec(urlbuff + 18);
  565.     c->version = ap_proxy_hex2sec(urlbuff + 27);
  566.     c->len = ap_proxy_hex2sec(urlbuff + 36);
  567.  
  568. /* check that we have the same URL */
  569.     len = ap_bgets(urlbuff, sizeof urlbuff, cachefp);
  570.     if (len == -1)
  571.     return -1;
  572.     if (len == 0 || strncmp(urlbuff, "X-URL: ", 7) != 0 ||
  573.     urlbuff[len - 1] != '\n')
  574.     return 0;
  575.     urlbuff[len - 1] = '\0';
  576.     if (strcmp(urlbuff + 7, c->url) != 0)
  577.     return 0;
  578.  
  579. /* What follows is the message */
  580.     len = ap_bgets(urlbuff, sizeof urlbuff, cachefp);
  581.     if (len == -1)
  582.     return -1;
  583.     if (len == 0 || urlbuff[len - 1] != '\n')
  584.     return 0;
  585.     urlbuff[--len] = '\0';
  586.  
  587.     c->resp_line = ap_pstrdup(r->pool, urlbuff);
  588.     strp = strchr(urlbuff, ' ');
  589.     if (strp == NULL)
  590.     return 0;
  591.  
  592.     c->status = atoi(strp);
  593.     c->hdrs = ap_proxy_read_headers(r, urlbuff, sizeof urlbuff, cachefp);
  594.     if (c->hdrs == NULL)
  595.     return -1;
  596.     if (c->len != -1) {        /* add a content-length header */
  597.     if (ap_table_get(c->hdrs, "Content-Length") == NULL) {
  598.         ap_table_set(c->hdrs, "Content-Length",
  599.              ap_psprintf(r->pool, "%lu", (unsigned long)c->len));
  600.     }
  601.     }
  602.     return 1;
  603. }
  604.  
  605.  
  606. /*
  607.  * Call this to test for a resource in the cache
  608.  * Returns DECLINED if we need to check the remote host
  609.  * or an HTTP status code if successful
  610.  *
  611.  * Functions:
  612.  *   if URL is cached then
  613.  *      if cached file is not expired then
  614.  *         if last modified after if-modified-since then send body
  615.  *         else send 304 Not modified
  616.  *      else
  617.  *         if last modified after if-modified-since then add
  618.  *            last modified date to request
  619.  */
  620. int ap_proxy_cache_check(request_rec *r, char *url, struct cache_conf *conf,
  621.               cache_req **cr)
  622. {
  623.     char hashfile[66];
  624.     const char *imstr, *pragma, *auth;
  625.     cache_req *c;
  626.     time_t now;
  627.     BUFF *cachefp;
  628.     int cfd, i;
  629.     const long int zero = 0L;
  630.     void *sconf = r->server->module_config;
  631.     proxy_server_conf *pconf =
  632.     (proxy_server_conf *) ap_get_module_config(sconf, &proxy_module);
  633.  
  634.     c = ap_pcalloc(r->pool, sizeof(cache_req));
  635.     *cr = c;
  636.     c->req = r;
  637.     c->url = ap_pstrdup(r->pool, url);
  638.  
  639. /* get the If-Modified-Since date of the request */
  640.     c->ims = BAD_DATE;
  641.     imstr = ap_table_get(r->headers_in, "If-Modified-Since");
  642.     if (imstr != NULL) {
  643. /* this may modify the value in the original table */
  644.     imstr = ap_proxy_date_canon(r->pool, imstr);
  645.     c->ims = ap_parseHTTPdate(imstr);
  646.     if (c->ims == BAD_DATE)    /* bad or out of range date; remove it */
  647.         ap_table_unset(r->headers_in, "If-Modified-Since");
  648.     }
  649.  
  650. /* find the filename for this cache entry */
  651.     ap_proxy_hash(url, hashfile, pconf->cache.dirlevels, pconf->cache.dirlength);
  652.     if (conf->root != NULL)
  653.     c->filename = ap_pstrcat(r->pool, conf->root, "/", hashfile, NULL);
  654.     else
  655.     c->filename = NULL;
  656.  
  657.     cachefp = NULL;
  658. /* find out about whether the request can access the cache */
  659.     pragma = ap_table_get(r->headers_in, "Pragma");
  660.     auth = ap_table_get(r->headers_in, "Authorization");
  661.     Explain5("Request for %s, pragma=%s, auth=%s, ims=%ld, imstr=%s", url,
  662.          pragma, auth, c->ims, imstr);
  663.     if (c->filename != NULL && r->method_number == M_GET &&
  664.     strlen(url) < 1024 && !ap_proxy_liststr(pragma, "no-cache") &&
  665.     auth == NULL) {
  666.     Explain1("Check file %s", c->filename);
  667.     cfd = open(c->filename, O_RDWR | O_BINARY);
  668.     if (cfd != -1) {
  669.         ap_note_cleanups_for_fd(r->pool, cfd);
  670.         cachefp = ap_bcreate(r->pool, B_RD | B_WR);
  671.         ap_bpushfd(cachefp, cfd, cfd);
  672.     }
  673.     else if (errno != ENOENT)
  674.         ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
  675.              "proxy: error opening cache file %s",
  676.              c->filename);
  677. #ifdef EXPLAIN
  678.     else
  679.         Explain1("File %s not found", c->filename);
  680. #endif
  681.     }
  682.  
  683.     if (cachefp != NULL) {
  684.     i = rdcache(r, cachefp, c);
  685.     if (i == -1)
  686.         ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
  687.              "proxy: error reading cache file %s", 
  688.              c->filename);
  689.     else if (i == 0)
  690.         ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, r,
  691.              "proxy: bad (short?) cache file: %s", c->filename);
  692.     if (i != 1) {
  693.         ap_pclosef(r->pool, cachefp->fd);
  694.         cachefp = NULL;
  695.     }
  696.     }
  697. /* fixed?  in this case, we want to get the headers from the remote server
  698.    it will be handled later if we don't do this (I hope ;-)
  699.     if (cachefp == NULL)
  700.     c->hdrs = ap_make_table(r->pool, 20);
  701. */
  702.     /* FIXME: Shouldn't we check the URL somewhere? */
  703.     now = time(NULL);
  704. /* Ok, have we got some un-expired data? */
  705.     if (cachefp != NULL && c->expire != BAD_DATE && now < c->expire) {
  706.     Explain0("Unexpired data available");
  707. /* check IMS */
  708.     if (c->lmod != BAD_DATE && c->ims != BAD_DATE && c->ims >= c->lmod) {
  709. /* has the cached file changed since this request? */
  710.         if (c->date == BAD_DATE || c->date > c->ims) {
  711. /* No, but these header values may have changed, so we send them with the
  712.  * 304 HTTP_NOT_MODIFIED response
  713.  */
  714.         const char *q;
  715.  
  716.         if ((q = ap_table_get(c->hdrs, "Expires")) != NULL)
  717.             ap_table_set(r->headers_out, "Expires", q);
  718.         }
  719.         ap_pclosef(r->pool, cachefp->fd);
  720.         Explain0("Use local copy, cached file hasn't changed");
  721.         return HTTP_NOT_MODIFIED;
  722.     }
  723.  
  724. /* Ok, has been modified */
  725.     Explain0("Local copy modified, send it");
  726.     r->status_line = strchr(c->resp_line, ' ') + 1;
  727.     r->status = c->status;
  728.     if (!r->assbackwards) {
  729.         ap_soft_timeout("proxy send headers", r);
  730.         ap_proxy_send_headers(r, c->resp_line, c->hdrs);
  731.         ap_kill_timeout(r);
  732.     }
  733.     ap_bsetopt(r->connection->client, BO_BYTECT, &zero);
  734.     r->sent_bodyct = 1;
  735.     if (!r->header_only)
  736.         ap_proxy_send_fb(cachefp, r, NULL);
  737.     ap_pclosef(r->pool, cachefp->fd);
  738.     return OK;
  739.     }
  740.  
  741. /* if we already have data and a last-modified date, and it is not a head
  742.  * request, then add an If-Modified-Since
  743.  */
  744.  
  745.     if (cachefp != NULL && c->lmod != BAD_DATE && !r->header_only) {
  746. /*
  747.  * use the later of the one from the request and the last-modified date
  748.  * from the cache
  749.  */
  750.     if (c->ims == BAD_DATE || c->ims < c->lmod) {
  751.         const char *q;
  752.  
  753.         if ((q = ap_table_get(c->hdrs, "Last-Modified")) != NULL)
  754.         ap_table_set(r->headers_in, "If-Modified-Since",
  755.               (char *) q);
  756.     }
  757.     }
  758.     c->fp = cachefp;
  759.  
  760.     Explain0("Local copy not present or expired. Declining.");
  761.  
  762.     return DECLINED;
  763. }
  764.  
  765. /*
  766.  * Having read the response from the client, decide what to do
  767.  * If the response is not cachable, then delete any previously cached
  768.  * response, and copy data from remote server to client.
  769.  * Functions:
  770.  *  parse dates
  771.  *  check for an uncachable response
  772.  *  calculate an expiry date, if one is not provided
  773.  *  if the remote file has not been modified, then return the document
  774.  *  from the cache, maybe updating the header line
  775.  *  otherwise, delete the old cached file and open a new temporary file
  776.  */
  777. int ap_proxy_cache_update(cache_req *c, table *resp_hdrs,
  778.                const int is_HTTP1, int nocache)
  779. {
  780. #ifdef ULTRIX_BRAIN_DEATH
  781.   extern char *mktemp(char *template);
  782. #endif 
  783.     request_rec *r = c->req;
  784.     char *p;
  785.     int i;
  786.     const char *expire, *lmods, *dates, *clen;
  787.     time_t expc, date, lmod, now;
  788.     char buff[46];
  789.     void *sconf = r->server->module_config;
  790.     proxy_server_conf *conf =
  791.     (proxy_server_conf *) ap_get_module_config(sconf, &proxy_module);
  792.     const long int zero = 0L;
  793.  
  794.     c->tempfile = NULL;
  795.  
  796. /* we've received the response */
  797. /* read expiry date; if a bad date, then leave it so the client can
  798.  * read it
  799.  */
  800.     expire = ap_table_get(resp_hdrs, "Expires");
  801.     if (expire != NULL)
  802.     expc = ap_parseHTTPdate(expire);
  803.     else
  804.     expc = BAD_DATE;
  805.  
  806. /*
  807.  * read the last-modified date; if the date is bad, then delete it
  808.  */
  809.     lmods = ap_table_get(resp_hdrs, "Last-Modified");
  810.     if (lmods != NULL) {
  811.     lmod = ap_parseHTTPdate(lmods);
  812.     if (lmod == BAD_DATE) {
  813. /* kill last modified date */
  814.         lmods = NULL;
  815.     }
  816.     }
  817.     else
  818.     lmod = BAD_DATE;
  819.  
  820. /*
  821.  * what responses should we not cache?
  822.  * Unknown status responses and those known to be uncacheable
  823.  * 304 HTTP_NOT_MODIFIED response when we have no valid cache file, or
  824.  * 200 HTTP_OK response from HTTP/1.0 and up without a Last-Modified header, or
  825.  * HEAD requests, or
  826.  * requests with an Authorization header, or
  827.  * protocol requests nocache (e.g. ftp with user/password)
  828.  */
  829. /* @@@ XXX FIXME: is the test "r->status != HTTP_MOVED_PERMANENTLY" corerct?
  830.  * or shouldn't it be "ap_is_HTTP_REDIRECT(r->status)" ? -MnKr */
  831.     if ((r->status != HTTP_OK && r->status != HTTP_MOVED_PERMANENTLY && r->status != HTTP_NOT_MODIFIED) ||
  832.     (expire != NULL && expc == BAD_DATE) ||
  833.     (r->status == HTTP_NOT_MODIFIED && (c == NULL || c->fp == NULL)) ||
  834.     (r->status == HTTP_OK && lmods == NULL && is_HTTP1) ||
  835.     r->header_only ||
  836.     ap_table_get(r->headers_in, "Authorization") != NULL ||
  837.     nocache) {
  838.     Explain1("Response is not cacheable, unlinking %s", c->filename);
  839. /* close the file */
  840.     if (c->fp != NULL) {
  841.         ap_pclosef(r->pool, c->fp->fd);
  842.         c->fp = NULL;
  843.     }
  844. /* delete the previously cached file */
  845.         if (c->filename)
  846.             unlink(c->filename);
  847.     return DECLINED;    /* send data to client but not cache */
  848.     }
  849.  
  850. /* otherwise, we are going to cache the response */
  851. /*
  852.  * Read the date. Generate one if one is not supplied
  853.  */
  854.     dates = ap_table_get(resp_hdrs, "Date");
  855.     if (dates != NULL)
  856.     date = ap_parseHTTPdate(dates);
  857.     else
  858.     date = BAD_DATE;
  859.  
  860.     now = time(NULL);
  861.  
  862.     if (date == BAD_DATE) {    /* No, or bad date */
  863. /* no date header! */
  864. /* add one; N.B. use the time _now_ rather than when we were checking the cache
  865.  */
  866.     date = now;
  867.     dates = ap_gm_timestr_822(r->pool, now);
  868.     ap_table_set(resp_hdrs, "Date", dates);
  869.     Explain0("Added date header");
  870.     }
  871.  
  872. /* check last-modified date */
  873.     if (lmod != BAD_DATE && lmod > date)
  874. /* if its in the future, then replace by date */
  875.     {
  876.     lmod = date;
  877.     lmods = dates;
  878.     Explain0("Last modified is in the future, replacing with now");
  879.     }
  880. /* if the response did not contain the header, then use the cached version */
  881.     if (lmod == BAD_DATE && c->fp != NULL) {
  882.     lmod = c->lmod;
  883.     Explain0("Reusing cached last modified");
  884.     }
  885.  
  886. /* we now need to calculate the expire data for the object. */
  887.     if (expire == NULL && c->fp != NULL) {    /* no expiry data sent in response */
  888.     expire = ap_table_get(c->hdrs, "Expires");
  889.     if (expire != NULL)
  890.         expc = ap_parseHTTPdate(expire);
  891.     }
  892. /* so we now have the expiry date */
  893. /* if no expiry date then
  894.  *   if lastmod
  895.  *      expiry date = now + min((date - lastmod) * factor, maxexpire)
  896.  *   else
  897.  *      expire date = now + defaultexpire
  898.  */
  899.     Explain1("Expiry date is %ld", expc);
  900.     if (expc == BAD_DATE) {
  901.     if (lmod != BAD_DATE) {
  902.         double x = (double) (date - lmod) * conf->cache.lmfactor;
  903.         double maxex = conf->cache.maxexpire;
  904.         if (x > maxex)
  905.         x = maxex;
  906.         expc = now + (int) x;
  907.     }
  908.     else
  909.         expc = now + conf->cache.defaultexpire;
  910.     Explain1("Expiry date calculated %ld", expc);
  911.     }
  912.  
  913. /* get the content-length header */
  914.     clen = ap_table_get(resp_hdrs, "Content-Length");
  915.     if (clen == NULL)
  916.     c->len = -1;
  917.     else
  918.     c->len = atoi(clen);
  919.  
  920.     ap_proxy_sec2hex(date, buff);
  921.     buff[8] = ' ';
  922.     ap_proxy_sec2hex(lmod, buff + 9);
  923.     buff[17] = ' ';
  924.     ap_proxy_sec2hex(expc, buff + 18);
  925.     buff[26] = ' ';
  926.     ap_proxy_sec2hex(c->version++, buff + 27);
  927.     buff[35] = ' ';
  928.     ap_proxy_sec2hex(c->len, buff + 36);
  929.     buff[44] = '\n';
  930.     buff[45] = '\0';
  931.  
  932. /* if file not modified */
  933.     if (r->status == HTTP_NOT_MODIFIED) {
  934.     if (c->ims != BAD_DATE && lmod != BAD_DATE && lmod <= c->ims) {
  935. /* set any changed headers somehow */
  936. /* update dates and version, but not content-length */
  937.         if (lmod != c->lmod || expc != c->expire || date != c->date) {
  938.         off_t curpos = lseek(c->fp->fd, 0, SEEK_SET);
  939.         if (curpos == -1)
  940.             ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
  941.                  "proxy: error seeking on cache file %s",
  942.                  c->filename);
  943.         else if (write(c->fp->fd, buff, 35) == -1)
  944.             ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
  945.                  "proxy: error updating cache file %s",
  946.                  c->filename);
  947.         }
  948.         ap_pclosef(r->pool, c->fp->fd);
  949.         Explain0("Remote document not modified, use local copy");
  950.         /* CHECKME: Is this right? Shouldn't we check IMS again here? */
  951.         return HTTP_NOT_MODIFIED;
  952.     }
  953.     else {
  954. /* return the whole document */
  955.         Explain0("Remote document updated, sending");
  956.         r->status_line = strchr(c->resp_line, ' ') + 1;
  957.         r->status = c->status;
  958.         if (!r->assbackwards) {
  959.         ap_soft_timeout("proxy send headers", r);
  960.         ap_proxy_send_headers(r, c->resp_line, c->hdrs);
  961.         ap_kill_timeout(r);
  962.         }
  963.         ap_bsetopt(r->connection->client, BO_BYTECT, &zero);
  964.         r->sent_bodyct = 1;
  965.         if (!r->header_only)
  966.         ap_proxy_send_fb(c->fp, r, NULL);
  967. /* set any changed headers somehow */
  968. /* update dates and version, but not content-length */
  969.         if (lmod != c->lmod || expc != c->expire || date != c->date) {
  970.         off_t curpos = lseek(c->fp->fd, 0, SEEK_SET);
  971.  
  972.         if (curpos == -1)
  973.             ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
  974.                  "proxy: error seeking on cache file %s",
  975.                  c->filename);
  976.         else if (write(c->fp->fd, buff, 35) == -1)
  977.             ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
  978.                  "proxy: error updating cache file %s",
  979.                  c->filename);
  980.         }
  981.         ap_pclosef(r->pool, c->fp->fd);
  982.         return OK;
  983.     }
  984.     }
  985. /* new or modified file */
  986.     if (c->fp != NULL) {
  987.     ap_pclosef(r->pool, c->fp->fd);
  988.     c->fp->fd = -1;
  989.     }
  990.     c->version = 0;
  991.     ap_proxy_sec2hex(0, buff + 27);
  992.     buff[35] = ' ';
  993.  
  994. /* open temporary file */
  995. #define TMPFILESTR    "/tmpXXXXXX"
  996.     if (conf->cache.root == NULL)
  997.     return DECLINED;
  998.     c->tempfile = ap_palloc(r->pool, strlen(conf->cache.root) + sizeof(TMPFILESTR));
  999.     strcpy(c->tempfile, conf->cache.root);
  1000.     strcat(c->tempfile, TMPFILESTR);
  1001. #undef TMPFILESTR
  1002.     p = mktemp(c->tempfile);
  1003.     if (p == NULL)
  1004.     return DECLINED;
  1005.  
  1006.     Explain1("Create temporary file %s", c->tempfile);
  1007.  
  1008.     i = open(c->tempfile, O_WRONLY | O_CREAT | O_EXCL | O_BINARY, 0622);
  1009.     if (i == -1) {
  1010.     ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
  1011.              "proxy: error creating cache file %s",
  1012.              c->tempfile);
  1013.     return DECLINED;
  1014.     }
  1015.     ap_note_cleanups_for_fd(r->pool, i);
  1016.     c->fp = ap_bcreate(r->pool, B_WR);
  1017.     ap_bpushfd(c->fp, -1, i);
  1018.  
  1019.     if (ap_bvputs(c->fp, buff, "X-URL: ", c->url, "\n", NULL) == -1) {
  1020.     ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
  1021.              "proxy: error writing cache file(%s)", c->tempfile);
  1022.     ap_pclosef(r->pool, c->fp->fd);
  1023.     unlink(c->tempfile);
  1024.     c->fp = NULL;
  1025.     }
  1026.     return DECLINED;
  1027. }
  1028.  
  1029. void ap_proxy_cache_tidy(cache_req *c)
  1030. {
  1031.     server_rec *s;
  1032.     long int bc;
  1033.  
  1034.     if (c == NULL || c->fp == NULL)
  1035.     return;
  1036.  
  1037.     s = c->req->server;
  1038.  
  1039. /* don't care how much was sent, but rather how much was written to cache
  1040.     ap_bgetopt(c->req->connection->client, BO_BYTECT, &bc);
  1041.  */
  1042.     bc = c->written;
  1043.  
  1044.     if (c->len != -1) {
  1045. /* file lengths don't match; don't cache it */
  1046.     if (bc != c->len) {
  1047.         ap_pclosef(c->req->pool, c->fp->fd);    /* no need to flush */
  1048.         unlink(c->tempfile);
  1049.         return;
  1050.     }
  1051.     }
  1052. /* don't care if aborted, cache it if fully retrieved from host!
  1053.     else if (c->req->connection->aborted) {
  1054.     ap_pclosef(c->req->pool, c->fp->fd);    / no need to flush /
  1055.     unlink(c->tempfile);
  1056.     return;
  1057.     }
  1058. */
  1059.     else {
  1060. /* update content-length of file */
  1061.     char buff[9];
  1062.     off_t curpos;
  1063.  
  1064.     c->len = bc;
  1065.     ap_bflush(c->fp);
  1066.     ap_proxy_sec2hex(c->len, buff);
  1067.     curpos = lseek(c->fp->fd, 36, SEEK_SET);
  1068.     if (curpos == -1)
  1069.         ap_log_error(APLOG_MARK, APLOG_ERR, s,
  1070.              "proxy: error seeking on cache file %s", c->tempfile);
  1071.     else if (write(c->fp->fd, buff, 8) == -1)
  1072.         ap_log_error(APLOG_MARK, APLOG_ERR, s,
  1073.              "proxy: error updating cache file %s", c->tempfile);
  1074.     }
  1075.  
  1076.     if (ap_bflush(c->fp) == -1) {
  1077.     ap_log_error(APLOG_MARK, APLOG_ERR, s,
  1078.              "proxy: error writing to cache file %s",
  1079.              c->tempfile);
  1080.     ap_pclosef(c->req->pool, c->fp->fd);
  1081.     unlink(c->tempfile);
  1082.     return;
  1083.     }
  1084.  
  1085.     if (ap_pclosef(c->req->pool, c->fp->fd) == -1) {
  1086.     ap_log_error(APLOG_MARK, APLOG_ERR, s,
  1087.              "proxy: error closing cache file %s", c->tempfile);
  1088.     unlink(c->tempfile);
  1089.     return;
  1090.     }
  1091.  
  1092.     if (unlink(c->filename) == -1 && errno != ENOENT) {
  1093.     ap_log_error(APLOG_MARK, APLOG_ERR, s,
  1094.              "proxy: error deleting old cache file %s",
  1095.              c->tempfile);
  1096.     }
  1097.     else {
  1098.     char *p;
  1099.     proxy_server_conf *conf =
  1100.     (proxy_server_conf *) ap_get_module_config(s->module_config, &proxy_module);
  1101.  
  1102.     for (p = c->filename + strlen(conf->cache.root) + 1;;) {
  1103.         p = strchr(p, '/');
  1104.         if (!p)
  1105.         break;
  1106.         *p = '\0';
  1107. #ifdef WIN32
  1108.         if (mkdir(c->filename) < 0 && errno != EEXIST)
  1109. #else
  1110.         if (mkdir(c->filename, S_IREAD | S_IWRITE | S_IEXEC) < 0 && errno != EEXIST)
  1111. #endif /* WIN32 */
  1112.         ap_log_error(APLOG_MARK, APLOG_ERR, s,
  1113.                  "proxy: error creating cache directory %s",
  1114.                  c->filename);
  1115.         *p = '/';
  1116.         ++p;
  1117.     }
  1118. #if defined(OS2) || defined(WIN32)
  1119.     /* Under OS/2 use rename. */
  1120.     if (rename(c->tempfile, c->filename) == -1)
  1121.         ap_log_error(APLOG_MARK, APLOG_ERR, s,
  1122.              "proxy: error renaming cache file %s to %s",
  1123.              c->tempfile, c->filename);
  1124.     }
  1125. #else
  1126.  
  1127.     if (link(c->tempfile, c->filename) == -1)
  1128.         ap_log_error(APLOG_MARK, APLOG_ERR, s,
  1129.              "proxy: error linking cache file %s to %s",
  1130.              c->tempfile, c->filename);
  1131.     }
  1132.  
  1133.     if (unlink(c->tempfile) == -1)
  1134.     ap_log_error(APLOG_MARK, APLOG_ERR, s,
  1135.              "proxy: error deleting temp file %s", c->tempfile);
  1136. #endif
  1137.  
  1138. }
  1139.