/*****************************************************************************/
/*
                                ProxyCache.c

*************
** CAUTION **
*************

THIS MODULE IS TASK-ORIENTED, NOT REQUEST-ORIENTED.

That is, most of the functions take a pointer to proxy task rather than a
pointer to request as do other modules. The reason is simple. Many of these
functions are designed for use independently of a request.


INTRODUCTION
------------
Implements a basic HTTP proxy disk caching service.  The design always trades
simplicity off against efficiency and elegance.  Cache management is performed
by the PROXYMAINT.C module.  It work intimately with this module to provide
routine and reactive purging (deletion) of cache files to maintain device free
space and currency of cache content.


CACHE CRITERIA
--------------
Proxied requests COULD ONLY have been cached if THE REQUEST ...

  o  uses the GET method
  o  does not contain a query string
  o  is HTTP/1.n compliant (i.e. not HTTP/0.9)

Proxied request responses WILL ONLY be cached if THE RESPONSE ...

  o  status code is 200 (success)
  o  contains a "Last-Modified:" header field
  o  one or more hours since the last modification
  o  does not contain a "Pragma: no-cache" field
  o  does not exceed a configuration parameter in size
  o  is HTTP/1.n compliant (i.e. not HTTP/0.9)

"Content-Length:" is only used (if available) to check load integrity.

Configuration parameter for maximum cachable response size.  This applies to
response without content-length header fields, the in-progress caching is just
aborted if the response grows to exceed this size.

  [ProxyCacheFileKbytesMax] ... maximum number of Kbytes for any one response


FILE CACHE
----------
The file cache is supported using ODS-2 conventions.  This does not mean that
it cannot be located on an ODS-5 device, just that the directory depth,
structure and file naming must be ODS-2 compliant (which it is by default). 
There seems little advantage in supporting extended file specifications for
this functionality so I'm spending the time elsewhere!

One file is used for each unique request.  A request is made unique by creating
a request identifier using the request host name, the port, and the request
path (excluding any query string of course).  This is essentially the URL.  A
unique file name is generated for each of these unique requests by generating
an MD5 digest using the identifier.  This, for all intents and purpose (at
least according to RSA), generates a unique "fingerprint" of the identifier's
text.  No other checking is required to ensure the file represents the request!

A quotation from RFC1321 (April 1992):

  The algorithm takes as input a message of arbitrary length and produces
  as output a 128-bit "fingerprint" or "message digest" of the input. It
  is conjectured that it is computationally infeasible to produce two
  messages having the same message digest ...

The digest is 16 bytes (128 bits) represented as 32 hexadecimal digits and
looks like this:

  2BF641901B1661D3345C4D058A0F390F

Use of the MD5 algorithm has been shown in practice to produce a *very* even
distribution of file names.

To help reduce per-directory congestion the cached files are distributed
throughout a number of subdirectories.

Two cache directory organizations are currently available.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
These are selected using the [ProxyCacheDeviceDirOrg] configuration directive.

The first (and default) provides a single level structure with a possible 256
directories at the top level and files organized immediate below these. The
directory name for any file is generated using the first two hexadecimal
digits, hence subdirectory names range from [00], [01], [02] to [FF]! The other
30 digits comprise the file name, with an extension of ".HTC". Subirectories
are created by the server as required. Hence a proxy cache file name looks
something like: 

  HT_CACHE_ROOT:[2B]F641901B1661D3345C4D058A0F390F.HTC

This is the original structure and works well where files do not exceed 256 per
directory for a total of approximately 65,000 files.  For versions of VMS prior
to V7.2 exceeding 256 files per directory incurs a significant performance
penalty for some directory operations. 

The second organization involves two levels of directory, each with a maximum
of 64 directories.  This is accomplished by using the leading three hexadecimal
digits (12 bits), the first becomes the first character of the top-level
directory name, the upper two bits of the second represented byte the
hexadecimal equivalent of the second character, the lower two bits of the
hexadecimal equivalent of the first character of the second directory and the
third character the second character of the second directory.  A file in this
structure cache might look like:

  HT_CACHE_ROOT:[22.3F]641901B1661D3345C4D058A0F390F.HTC

Here the first three characters, "2BF" or 0010+1011+1111, as a bit-pattern,
become "22", or 0010+10, for the first directory name, and "3F", or 11+1111,
for the second directory name.  Hence the first three hexdecimal characters of
the MD5 digest become the directory path [22.3F].  Directory names will begin
at [00.00], [00.01], [00.02] and range through to [F3.3D], [F3.3E], [F3.3F].
This allows for approximately 1,000,000 files before encountering the 256 file
per directory issue.

No "database" or other record is maintained of the files in the cache. All
access is via the unique MD5 "fingerprint" of the request. Hence it is easy to
reclaim disk space by manually deleting files. Cache files should not of
course be renamed to other subdirectories.


CACHE FILE STRUCTURE
--------------------
Three 32 bit integers prefix the cache file; cache version number, request URL
length (length of "http://node.domain:port/path"), response header length (that
received from the remote server).  Following these three integers is a null
terminated string, the URL string (length, including terminating null in second
integer). Then the response header (not null-terminated), immediately followed
by the response body (not null-terminated) to the end-of-file (which delimits
the body ... and response header for that matter).  The following diagram
illustrates this layout.

   16       (URL)12             8             4             0   VBN 1
    +-------------+-------------+-------------+-------------+
    | uu uu uu uu |  HEADER LEN |     URL LEN |     VERSION |
    +-------------+-------------+-------------+-------------+
    | uu uu uu uu | uu uu uu uu | uu uu uu uu | uu uu uu uu |
    +-------------+-------------+-------------+-------------+
    .
    .                           (HEADER)
    +-------------+-------------+-------------+-------------+
    | hh hh hh hh | hh hh hh hh | hh hh \0 uu | uu uu uu uu |
    +-------------+-------------+-------------+-------------+
    | hh hh hh hh | hh hh hh hh | hh hh hh hh | hh hh hh hh |
    +-------------+-------------+-------------+-------------+
    .
    .               (BODY)
    +-------------+-------------+-------------+-------------+
    | bb bb bb bb | bb bb hh hh | hh hh hh hh | hh hh hh hh |
    +-------------+-------------+-------------+-------------+
    | bb bb bb bb | bb bb bb bb | bb bb bb bb | bb bb bb bb |
    +-------------+-------------+-------------+-------------+
    .
    .
    +-------------+-------------+-------------+-------------+
    |             |             |       bb bb | bb bb bb bb |
    +-------------+-------------+-------------+-------------+
                                    EOF^                        VBN n


DISK SPACE USAGE
----------------
To simplify cache management and processing overhead the proxy cache is allowed
to use up to a configuration maximum percentage of the particular device it is
located on.  That is, it is allowed to continue to use free space until the
device reaches a specified percentage used.  At that point it deletes cache
files until another, lower device usage percentage is reached.  Hence the cache
will grow using available free space, unless that free space is called-on for
some other purpose, at which time it reduces it's own usage.  The following
configuration parameters control this behaviour.

  [ProxyCacheDeviceMaxPercent] ...... maximum device percentage in use 
  [ProxyCacheDevicePurgePercent] ... when purging usage reduce by this


CACHE FILE MANAGEMENT
---------------------
Cache file deletion takes two forms.  The first is ROUTINE, where files that
have not been accessed within specified limits are deleted.  The second is
remedial, a PURGE, where cache space usage is reaching it's limit and files
need to be deleted.  The following two configuration parameters control the
minutes between each of these events.

  [ProxyCacheRoutineHourOfDay] ..... routine delete of non-accessed files
                                     (if 24 done constantly in the background)
  [ProxyCacheDeviceCheckMinutes] ... check if the cache device needs purging

In the first, ROUTINE form the cache files are scanned looking for those that
exceed the configuration parameters for maximum period since last access, which
are then deleted (see [ProxyCachePurgeList] described below).

The second form, a PURGE of cache space, the files are scanned using the
[ProxyCachePurgeList] parameter above, working from the greatest to least
number of hours in the steps provided.  At each scan files not accessed within
that period are deleted.  Each few files deleted the device free space is
checked for having reached the lower purge percentage limit, at which point the
scan terminates.

  [ProxyCachePurgeList] ... series of comma-separated integers

This parameter has as it's input a series of comma-separated integers
representing a series of purges to the hour a file was last accessed. In this
way the cache can be progressively reduced until percentage usage targets are
realized.  (Note that this parameter specifies hours in the reverse order to
[ProxyCacheReloadList].)  Such a parameter would be specified as follows,

  [ProxyCachePurgeList] 168,48,24,8,0

meaning the purge would first delete files not accessed in the last week, then
not for the last two days, then the last twenty-four hours, then eight, then
finally all files.  The largest of the specified periods (in this case 168) is
also used as the limit for the ROUTINE scan and file delete.

Once the target reduction percentage is reached the purge stops. During the
purge operation, and until the target reduction is reached, cache files are not
created.  Unless the target reduction percentage can be reached no more cache
files will be created.  Even when cache file cannot be created for any reason
proxy serving still continues transparently to the clients.

CACHE FILES CAN BE MANUALLY DELETED AT ANY TIME (FROM THE COMMAND LINE, AND
PROVIDED THEY ARE NOT LOCKED FOR ANY REASON) WITHOUT DISTURBING THE
PROXY-CACHING SERVER AND WITHOUT REBUILDING ANY DATABASES.  WHEN DELETING, THE
/BEFORE= QUALIFIER CAN BE USED, WITH /CREATED BEING THE DOCUMENT'S
LAST-MODIFIED DATE, /REVISED BEING THE LAST TIME IT WAS LOADED, AND /EXPIRED
THE LAST TIME THE FILE WAS ACCESSED (USED TO SUPPLY A REQUEST).


CACHE MAINTENANCE COMMENT
-------------------------
This cache design is obviously very file system intensive (not necesarily an
optimal choice with the notoriously slow VMS file system).  The trade-off is of
course simplicity of implementation and management.  Let the file system do the
database management for it, with nothing to go wrong, maintain or rebuild
outside of that ;^)  The use of the MD5 algorithm to generate unique cache file
names makes all this straight-forward.  Let's hope it works!


CACHE INVALIDATION
------------------
For this purpose, cache invalidation is defined as the determination when a
cache file's data is no longer valid and needs to be reloaded.

The method used to for cache validation is deliberately quite simple in
algorithm and implementation. In this first attempt at a proxy server the
overriding criteria have been efficiency, simplicity of implementation, and
reliability. Wishing to avoid complicated revalidation using behind-the-scenes
HEAD requests the basic approach has been to just invalidate the cache item
upon exiry of a period related to it's "Last-Modified:" age or upon a
"no-cache" request, both described further below.

If a "Pragma: no-cache" request header field is present (as is generated by
Netscape Navigator when using the "reload" function) then the server should
completely reload the response from the remote server. (Too often the author
seems to have received incomplete responses where the proxy server caches only
part of a response and has seemed to refuse to explicitly re-request.) OK it's
a a bit more expensive but who's to say the proxy server is right all the
time! The response is still cached ... the next request may not have the
"no-cache" parameter.

When a response is cached the file creation date is set to the local equivalent
of the "Last-Modified:" GMT date and time supplied with the response. In this
manner the file's absolute age can be determined solely from the file header.

Also, when a file is cached, the "revision" and "expires" date/times is set to
current.  The "revision" date/time is used when assessing when the file was
last loaded/validated/reloaded.  The "expires" is used as described below.

Once a file is cached the RMS "expiration" date/time is updated every time it
is subsequently accessed. In this way recency of usage of the item can be
easily tracked.

The revision count (automatically updated by VMS) tracks the absolute number of
accesses since the file was created (actually a maximum of 65535, or an
unsigned short, but that should be enough).


AGE ALGORITHM
-------------
The following configuration parameter is used to control when a file being
accessed is forceably reloaded from source.

  [ProxyCacheReloadList] ... series of comma-separated integers

This parameter supplied a series of integers representing the hours after which
an access to a cache file causes the file to be invalidated and reloaded from
it's source during the proxied request.  (Note that this parameter specifies
hours in the reverse order to [ProxyCachePurgeList].) Each number in the
series represents the lower boundary of the range between it and the next
number of hours.  A file with a last-loaded age falling within a range is
reloaded at the lower boundary of that particular range.  The following example

  [ProxyCacheReloadList] 1,2,4,8,12,24,48,96,168

would result in a file 1.5 hours old being reloaded every hour, 3.25 hours old
every 2 hours, 4-8 hours old every 4 hours, etc.  Here "old" means since last
(or of course first) loaded.  Files not reloaded since the final integer, in
this example 168, are always reloaded.


VERSION HISTORY
---------------
19-SEP-2004  MGD  bugfix; even number of bytes on a disk $QIO READVBLK
30-APR-2004  MGD  significant changes to eliminate RMS from cache read
                  access by using ACP/QIOs (saves a few cycles, reduces
                  latency a little - RMS still abstracts the complexity
                  of creating and populating the cache files)
18-AUG-2001  MGD  prevent (pragma) reload within the specified period,
                  bugfix; a bugfix in VMS V7.2 has broken the previously
                  working usage of IO$_MODIFY in ProxyCacheSetLastAccessed()
04-AUG-2001  MGD  support module WATCHing
20-DEC-2000  MGD  routine proxy maintainence optionally disabled/external
28-AUG-2000  MGD  flat256 and 64x64 cache directory organizations
01-JUN-2000  MGD  bugfix; use 'rqHeader.RequestUriPtr' as '->RequestUriPtr'
                  (non-URL-decoded path plus query string)
03-JAN-2000  MGD  no changes required for ODS-5 compliance ... it's not
                  (see not above)
10-NOV-1999  MGD  cookie constraint relaxed (improved cache perform by 100%)
19-AUG-1998  MGD  initial development (recommenced DEC 1998)
*/
/*****************************************************************************/

#ifdef WASD_VMS_V6
#undef _VMS_V6_SOURCE
#define _VMS_V6_SOURCE
#undef __VMS_VER
#define __VMS_VER 60000000
#undef __CRTL_VER
#define __CRTL_VER 60000000
#endif

/* standard C header files */
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>

/* VMS related header files */
#include <atrdef.h>
#include <descrip.h>
#include <dvidef.h>
#include <fibdef.h>
#include <iodef.h>
#include <libdef.h>
#include <libdtdef.h>
#include <psldef.h>
#include <ssdef.h>
#include <stsdef.h>

/* application-related header files */
#include "wasd.h"

#define WASD_MODULE "PROXYCACHE"

/******************/
/* global storage */
/******************/

#define PROXY_CACHE_DEFAULT_PURGE_LIST "168,48,24,8,0"
#define PROXY_CACHE_DEFAULT_RELOAD_LIST "1,2,4,8,12,24,48,96,168"
#define PROXY_CACHE_DIR_ORG_DEFAULT PROXY_CACHE_DIR_ORG_FLAT256

char  ErrorProxyCacheVersion [] = "Proxy cache file version mismatch!";

BOOL  ProxyCacheAllowReload = true,
      ProxyCacheEnabled,
      ProxyCacheFreeSpaceAvailable,
      ProxyReportCacheLog,
      ProxyReportLog;

int  ProxyCacheAllocationQuantityMax,
     ProxyCacheDeviceCheckMinutes,
     ProxyCacheDeviceDirOrg,
     ProxyCacheDeviceMaxPercent,
     ProxyCacheDevicePurgePercent,
     ProxyCacheDeviceTargetPercent,
     ProxyCacheFileKBytesMax,
     ProxyCacheNoReloadSeconds,
     ProxyCacheRoutineHourOfDay;

char  *ProxyCachePurgeListPtr,
      *ProxyCacheReloadListPtr;

#define PROXY_CACHE_PURGE_HOURS_MAX 32
int  ProxyCachePurgeList [PROXY_CACHE_PURGE_HOURS_MAX];
int  ProxyCachePurgeListCount;
char  ProxyCachePurgeListString [64];

#define PROXY_CACHE_RELOAD_HOURS_MAX 32
int  ProxyCacheReloadList [PROXY_CACHE_RELOAD_HOURS_MAX];
int  ProxyCacheReloadListCount;
char  ProxyCacheReloadListString [64];

char  *ProxyCache64x64 [] = {
   "0.0", "0.1", "0.2", "0.3", "1.0", "1.1", "1.2", "1.3",
   "2.0", "2.1", "2.2", "2.3", "3.0", "3.1", "3.2", "3.3",
};

/********************/
/* external storage */
/********************/

extern int  EfnWait,
            EfnNoWait,
            ProxyReadBufferSize;

extern char  ErrorSanityCheck[],
             HttpProtocol[],
             SoftwareID[],
             Utility[];

extern ACCOUNTING_STRUCT  *AccountingPtr;
extern CONFIG_STRUCT  Config;
extern MSG_STRUCT  Msgs;
extern PROXY_ACCOUNTING_STRUCT  *ProxyAccountingPtr;
extern WATCH_STRUCT  Watch;

/****************************************************************************/
/*
*/

ProxyCacheInit (BOOL CacheEnabled)

{
   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (NULL, FI_LI, WATCH_MOD_PROXY,
                 "ProxyCacheInit() !&B", CacheEnabled);
 
   ProxyCacheEnabled = CacheEnabled;

   if (!(ProxyCacheDeviceDirOrg = Config.cfProxy.CacheDeviceDirOrg))
      ProxyCacheDeviceDirOrg = PROXY_CACHE_DIR_ORG_DEFAULT;
   ProxyCacheDeviceCheckMinutes = Config.cfProxy.CacheDeviceCheckMinutes;
   ProxyCacheDeviceMaxPercent = Config.cfProxy.CacheDeviceMaxPercent;
   ProxyCacheDevicePurgePercent = Config.cfProxy.CacheDevicePurgePercent;
   ProxyCacheFileKBytesMax = Config.cfProxy.CacheFileKBytesMax;
   ProxyCacheNoReloadSeconds = Config.cfProxy.CacheNoReloadSeconds;
   if (isdigit(Config.cfProxy.CacheRoutineHourOfDayString[0]))
      ProxyCacheRoutineHourOfDay =
         atoi(Config.cfProxy.CacheRoutineHourOfDayString);
   else
      ProxyCacheRoutineHourOfDay = -1;
   ProxyCachePurgeListPtr = Config.cfProxy.CachePurgeList;
   ProxyCacheReloadListPtr = Config.cfProxy.CachePurgeList;
   ProxyReportCacheLog = Config.cfProxy.ReportCacheLog;
   ProxyReportLog = Config.cfProxy.ReportLog;

   ProxyCacheInitValues ();

   if (ProxyCacheEnabled) ProxyMaintInit ();
}

/****************************************************************************/
/*
Make sure the configuration parameters are reasonable.  Build the purge and
reload arrays.
*/

ProxyCacheInitValues ()

{
   int  Number,
        LastNumber;
   char  *cptr;

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (NULL, FI_LI, WATCH_MOD_PROXY, "ProxyCacheInitValues()");

   if (ProxyCacheDeviceCheckMinutes < 1)
      ProxyCacheDeviceCheckMinutes = 15;
   if (ProxyCacheDeviceCheckMinutes > 60)
      ProxyCacheDeviceCheckMinutes = 60;

   if (ProxyCacheDeviceMaxPercent < 10)
      ProxyCacheDeviceMaxPercent = 85;
   if (ProxyCacheDeviceMaxPercent > 85)
      ProxyCacheDeviceMaxPercent = 85;

   if (ProxyCacheDevicePurgePercent < 1)
      ProxyCacheDevicePurgePercent = 1;
   if (ProxyCacheDevicePurgePercent > 25)
      ProxyCacheDevicePurgePercent = 25;

   ProxyCacheDeviceTargetPercent = ProxyCacheDeviceMaxPercent -
                                   ProxyCacheDevicePurgePercent;
   if (ProxyCacheDeviceTargetPercent < 0) ProxyCacheDeviceTargetPercent = 0;

   if (ProxyCacheFileKBytesMax < 1)
      ProxyCacheFileKBytesMax = 256;
   if (ProxyCacheFileKBytesMax > 1024)
      ProxyCacheFileKBytesMax = 1024;
   /* number of KBytes * 2 (2x512bytes) plus 1 block for URL, etc. */
   ProxyCacheAllocationQuantityMax = (ProxyCacheFileKBytesMax * 2) + 1;

   if (ProxyCacheRoutineHourOfDay < 0)
      ProxyCacheRoutineHourOfDay = 0;
   if (ProxyCacheRoutineHourOfDay > 24)
      ProxyCacheRoutineHourOfDay = 24;

   ProxyCacheInitPurgeList ();

   ProxyCacheInitReloadList ();
}

/****************************************************************************/
/*
Initialize the proxy maintainance purge list of hours.
*/

ProxyCacheInitPurgeList ()

{
   static $DESCRIPTOR (ListFaoDsc, "!AZ!UL");

   int  idx,
        status,
        Number,
        LastNumber;
   unsigned long  *vecptr;
   char  *cptr;
   unsigned long  FaoVector [2];
   unsigned short  Length;
   $DESCRIPTOR (ListDsc, ProxyCachePurgeListString);

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (NULL, FI_LI, WATCH_MOD_PROXY,
                 "ProxyCacheInitPurgeList() !&Z", ProxyCachePurgeListPtr);

   if (!ProxyCachePurgeListPtr || !ProxyCachePurgeListPtr[0])
      ProxyCachePurgeListPtr = ProxyCachePurgeListString;

   for (;;)
   {
      LastNumber = 999999999;
      ProxyCachePurgeListCount = 0;
      if (!*ProxyCachePurgeListPtr)
         ProxyCachePurgeListPtr = PROXY_CACHE_DEFAULT_PURGE_LIST;
      cptr = ProxyCachePurgeListPtr;
      while (*cptr && *ProxyCachePurgeListPtr)
      {
         while (*cptr && !isdigit(*cptr)) cptr++;
         if (!*cptr) break;
         Number = atoi(cptr);
         while (isdigit(*cptr)) cptr++;
         if (Number >= LastNumber ||
             ProxyCachePurgeListCount >= PROXY_CACHE_PURGE_HOURS_MAX)
         {
            strcpy (ProxyCachePurgeListString, "*ERROR*");
            break;
         }
         ProxyCachePurgeList[ProxyCachePurgeListCount++] = LastNumber = Number;
      }

      /* exit, having built the array of hours */
      if (*ProxyCachePurgeListPtr) break;
   }

   memset (&ProxyCachePurgeListString, 0, sizeof(ProxyCachePurgeListString));
   Length = 0;
   for (idx = 0; idx < ProxyCachePurgeListCount; idx++)
   {
      ListDsc.dsc$w_length += Length;
      ListDsc.dsc$a_pointer += Length;
      vecptr = &FaoVector;
      if (idx) *vecptr++ = ","; else *vecptr++ = "";
      *vecptr++ = ProxyCachePurgeList[idx];
      status = sys$faol (&ListFaoDsc, &Length, &ListDsc, &FaoVector);
      if (VMSnok (status))
      {
         strcpy (ProxyCachePurgeListString, "*ERROR*");
         break;
      }
   }
   ProxyCachePurgeListPtr = ProxyCachePurgeListString;

   if (WATCH_MODULE(WATCH_MOD_PROXY))
      WatchDataFormatted ("!&Z\n", ProxyCachePurgeListPtr);
}

/****************************************************************************/
/*
*/

ProxyCacheInitReloadList ()

{
   static $DESCRIPTOR (ListFaoDsc, "!AZ!UL");

   int  idx,
        status,
        Number,
        LastNumber;
   unsigned long  FaoVector [2];
   unsigned short  Length;
   unsigned long  *vecptr;
   char  *cptr;
   $DESCRIPTOR (ListDsc, ProxyCacheReloadListString);

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (NULL, FI_LI, WATCH_MOD_PROXY,
                  "ProxyCacheInitReloadList() !&Z", ProxyCacheReloadListPtr);

   for (;;)
   {
      LastNumber = 0;
      ProxyCacheReloadListCount = 0;
      if (!*ProxyCacheReloadListPtr)
         ProxyCacheReloadListPtr = PROXY_CACHE_DEFAULT_RELOAD_LIST;
      cptr = ProxyCacheReloadListPtr;
      while (*cptr)
      {
         while (*cptr && !isdigit(*cptr)) cptr++;
         if (!*cptr) break;
         Number = atoi(cptr);
         while (isdigit(*cptr)) cptr++;
         if (Number <= LastNumber ||
             ProxyCacheReloadListCount >= PROXY_CACHE_RELOAD_HOURS_MAX)
         {
            ProxyCacheReloadListPtr = "";
            break;
         }
         ProxyCacheReloadList[ProxyCacheReloadListCount++] =
            LastNumber = Number;
      }

      /* exit, having built the array of hours */
      if (*ProxyCacheReloadListPtr) break;
   }

   memset (&ProxyCacheReloadListString, 0, sizeof(ProxyCacheReloadListString));
   Length = 0;
   for (idx = 0; idx < ProxyCacheReloadListCount; idx++)
   {
      ListDsc.dsc$w_length += Length;
      ListDsc.dsc$a_pointer += Length;
      vecptr = &FaoVector;
      if (idx) *vecptr++ = ","; else *vecptr++ = "";
      *vecptr++ = ProxyCacheReloadList[idx];
      status = sys$faol (&ListFaoDsc, &Length, &ListDsc, &FaoVector);
      if (VMSnok (status))
      {
         strcpy (ProxyCacheReloadListString, "*ERROR*");
         break;
      }
   }
   ProxyCacheReloadListPtr = ProxyCacheReloadListString;

   if (WATCH_MODULE(WATCH_MOD_PROXY))
      WatchDataFormatted ("!&Z\n", ProxyCacheReloadListPtr);
}

/*****************************************************************************/
/*
Cleanup the RMS parse structure, ACP-QIO channel is still assigned, deaccess
any QIO-accessed read cache file and then deassign the associated channel,
finalize any cache file load.
*/

ProxyCacheEnd (PROXY_TASK *tkptr)

{
   int  status;
   PROXY_CACHE_FILE_QIO  *cfqptr;

   /*********/
   /* begin */
   /*********/

   if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (WATCHTK(tkptr), FI_LI, WATCH_MOD_PROXY, "ProxyCacheEnd()");

   if (tkptr->ParseInUse)
   {
      /* ensure parse internal data structures are released */
      tkptr->ParseFab.fab$l_fna = "a:[b]c.d;";
      tkptr->ParseFab.fab$b_fns = 9;
      tkptr->ParseFab.fab$b_dns = tkptr->ParseNam.nam$l_rlf = 0;
      tkptr->ParseNam.nam$b_nop = NAM$M_SYNCHK;

      /* synchronous service */
      tkptr->ParseFab.fab$l_fop &= ~FAB$M_ASY;
      status = sys$parse (&tkptr->ParseFab, 0, 0);

      tkptr->ParseInUse = false;
   }

   cfqptr = &tkptr->CacheFileQio;

   if (cfqptr->AcpChannel)
   {
      /* no file has been accessed, clean up after the ACP-QIO */
      sys$dassgn (cfqptr->AcpChannel);
      cfqptr->AcpChannel = 0;
   }

   if (cfqptr->QioChannel)
   {
      /* cache file has been accessed for read */
      status = sys$qiow (EfnWait, cfqptr->QioChannel, IO$_DEACCESS,
                         &cfqptr->IOsb, 0, 0,
                         &cfqptr->FibDsc, 0, 0, 0, 0, 0);
      if (VMSok (status)) status = cfqptr->IOsb.Status;
      if (VMSnok (status)) ErrorNoticed (status, "IO$_DEACCESS", FI_LI);

      sys$dassgn (cfqptr->QioChannel);
      cfqptr->QioChannel = 0;
   }

   if (tkptr->LoadFab.fab$w_ifi)
   {
      /* this function ASTs to ProxyEnd() */
      ProxyCacheLoadEnd (tkptr);
      return;
   }

   ProxyEnd (tkptr);
}

/*****************************************************************************/
/*
Check the obvious and early criteria on whether a proxied request could be
cached.  If not just return an error status.  If it's possible to have cached
this request then construct the cache file ident block from the request
host-name[:port] and the request path.  Use this to generate the MD5 digest
string of that ident block and generate a unique cache file name using that.
Create a file FAB and NAM and attempt to parse the cache file name.
*/

int ProxyCacheReadBegin (PROXY_TASK *tkptr)

{
   BOOL  SetPathNoCache;
   int  status,
        UrlLength;
   char  *cptr, *sptr, *zptr,
         *UrlPtr;
   PROXY_CACHE_DESCR  *pcdptr;

   /*********/
   /* begin */
   /*********/

   if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (WATCHTK(tkptr), FI_LI, WATCH_MOD_PROXY,
                 "ProxyCacheReadBegin()");

   tkptr->ProxyCacheSuitable = tkptr->ProxyCacheFileExisted = false;

   if (!ProxyCacheEnabled) return (STS$K_ERROR);

   if (!tkptr->RequestPtr)
      SetPathNoCache = false;
   else
      SetPathNoCache = tkptr->RequestPtr->rqPathSet.NoCache;
         
   if (tkptr->RequestHttpMethod != HTTP_METHOD_GET ||
       tkptr->RequestUriQueryStringPtr[0] ||
       tkptr->RequestHttpVersion == HTTP_VERSION_0_9 ||
       SetPathNoCache)
   {
      if (WATCHING(tkptr) &&
          WATCH_CATEGORY(WATCH_PROXY_CACHE))
      {
         char  String [256];

         String[0] = '\0';
         if (tkptr->RequestHttpMethod != HTTP_METHOD_GET)
         {
            strcpy (String, "method ");
            strcat (String, tkptr->RequestHttpMethodNamePtr);
         }
         if (tkptr->RequestUriQueryStringPtr[0])
         {
            if (String[0]) strcat (String, ", ");
            strcat (String, "query string");
         }
         if (tkptr->RequestHttpVersion == HTTP_VERSION_0_9)
         {
            if (String[0]) strcat (String, ", ");
            strcat (String, "HTTP/0.9");
         }
         if (SetPathNoCache)
         {
            if (String[0]) strcat (String, ", ");
            strcat (String, "path set NOCACHE");
         }
         if (!String[0]) strcpy (String, "?");

         WatchThis (WATCHTK(tkptr), FI_LI, WATCH_PROXY_CACHE,
                    "CACHE READ not cachable: !AZ", String);
      }

      tkptr->NotCacheable = true;
      InstanceGblSecIncrLong (&ProxyAccountingPtr->RequestNotCacheableCount);

      return (STS$K_ERROR);
   }

   /*************************/
   /* generate cache header */
   /*************************/

   pcdptr = (PROXY_CACHE_DESCR*)tkptr->ResponseBufferPtr;
   pcdptr->CacheVersion = PROXY_CACHE_FILE_VERSION;

   zptr = (sptr = tkptr->ResponseBufferPtr) + tkptr->ResponseBufferSize;
   sptr += sizeof(PROXY_CACHE_DESCR);
   UrlPtr = sptr;
   for (cptr = "http://";
        *cptr && sptr < zptr;
        *sptr++ = *cptr++);
   for (cptr = tkptr->RequestHostPort;
        *cptr && sptr < zptr;
        *sptr++ = *cptr++);
   /* copy in the path portion, stopping at any query string */
   for (cptr = tkptr->RequestUriPtr;
        *cptr && *cptr != '?' && sptr < zptr;
        *sptr++ = *cptr++);

   /* if cache information block overflowed then just forget it! */
   if (sptr >= zptr)
   {
      if (WATCHING(tkptr) && WATCH_CATEGORY(WATCH_PROXY_CACHE))
         WatchThis (WATCHTK(tkptr), FI_LI, WATCH_PROXY_CACHE,
                    "CACHE READ URI overflow");
      return (STS$K_ERROR);
   }

   /* the terminating null is included in this length */
   *sptr++ = '\0';
   pcdptr->UrlLength = UrlLength = sptr - UrlPtr;

   /* now the currently free space in the buffer follows the description */
   tkptr->ResponseBufferCurrentPtr = sptr;
   tkptr->ResponseBufferRemaining = tkptr->ResponseBufferSize -
                                    (tkptr->ResponseBufferCurrentPtr -
                                     tkptr->ResponseBufferPtr);

   /* flag that this request is suitable for caching */
   tkptr->ProxyCacheSuitable = true;

   /****************************/
   /* generate cache file name */
   /****************************/

   /* generate an MD5 digest representing the "http://host-name:port/path" */
   Md5HexString (UrlPtr, UrlLength, tkptr->ProxyCacheMd5HexDigest);

   /* using the MD5 digest generate a unique cache file name */
   sptr = tkptr->ProxyCacheFileName;
   for (cptr = PROXY_CACHE_ROOT; *cptr; *sptr++ = *cptr++);
   if (ProxyCacheDeviceDirOrg == PROXY_CACHE_DIR_ORG_FLAT256)
   {
      *sptr++ = toupper(tkptr->ProxyCacheMd5HexDigest[0]);
      *sptr++ = toupper(tkptr->ProxyCacheMd5HexDigest[1]);
      cptr = tkptr->ProxyCacheMd5HexDigest + 2;
   }
   else
   {
      /* PROXY_CACHE_DIR_ORG_64X64 */
      *sptr++ = toupper(tkptr->ProxyCacheMd5HexDigest[0]);
      if (tkptr->ProxyCacheMd5HexDigest[1] >= '0' &&
          tkptr->ProxyCacheMd5HexDigest[1] <= '9')
         cptr = ProxyCache64x64[tkptr->ProxyCacheMd5HexDigest[1]-'0'];
      else
         cptr = ProxyCache64x64[tkptr->ProxyCacheMd5HexDigest[1]-'a'+10];
      while (*cptr) *sptr++ = *cptr++;
      *sptr++ = toupper(tkptr->ProxyCacheMd5HexDigest[2]);
      cptr = tkptr->ProxyCacheMd5HexDigest + 3;
   }
   *sptr++ = ']';
   while (*cptr) *sptr++ = toupper(*cptr++);
   for (cptr = ".HTC;1"; *cptr; *sptr++ = *cptr++);
   *sptr = '\0';
   tkptr->ProxyCacheFileNameLength = sptr - tkptr->ProxyCacheFileName;

   if (WATCHING(tkptr) && WATCH_CATEGORY(WATCH_PROXY_CACHE))
      WatchThis (WATCHTK(tkptr), FI_LI, WATCH_PROXY_CACHE,
                 "!AZ !AZ !AZ", UrlPtr,
                 tkptr->ProxyCacheMd5HexDigest,
                 tkptr->ProxyCacheFileName);

   /*******************/
   /* parse file name */
   /*******************/

   tkptr->ParseInUse = true;

   tkptr->ParseFab = cc$rms_fab;
   tkptr->ParseFab.fab$l_ctx = tkptr;
   tkptr->ParseFab.fab$l_fna = tkptr->ProxyCacheFileName;
   tkptr->ParseFab.fab$b_fns = tkptr->ProxyCacheFileNameLength;
   tkptr->ParseFab.fab$l_nam = &tkptr->ParseNam;

   /* initialize the NAM block */
   tkptr->ParseNam = cc$rms_nam;
   tkptr->ParseNam.nam$l_esa = tkptr->ExpandedFileName;
   tkptr->ParseNam.nam$b_ess = sizeof(tkptr->ExpandedFileName)-1;

   /* asynchronous service */
   tkptr->ParseFab.fab$l_fop |= FAB$M_ASY;
   status = sys$parse (&tkptr->ParseFab, &ProxyCacheReadAcpInfo,
                                         &ProxyCacheReadAcpInfo);

   return (SS$_NORMAL);
} 

/*****************************************************************************/
/*
Check the result of the RMS parse of the cache file name.

Get the file size and creation date/time (document last modified) of the file.

This function uses the ACP-QIO interface detailed in the "OpenVMS I/O User's 
Reference Manual", and is probably as fast as we can get for this type of file
system functionality!
*/ 

ProxyCacheReadAcpInfo (struct FAB *FabPtr)

{
   static $DESCRIPTOR (DeviceDsc, "");

   int  status;
   ATRDEF  *atptr;
   PROXY_TASK  *tkptr;
   PROXY_CACHE_FILE_QIO  *cfqptr;

   /*********/
   /* begin */
   /*********/

   tkptr = FabPtr->fab$l_ctx;

   if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (WATCHTK(tkptr), FI_LI, WATCH_MOD_PROXY,
                 "ProxyCacheReadAcpInfo() !&F !&S !&S",
                 &ProxyCacheReadAcpInfo,
                 FabPtr->fab$l_sts, FabPtr->fab$l_stv);

   if (VMSnok (status = tkptr->ParseFab.fab$l_sts))
   {
      if (WATCHING(tkptr) && WATCH_CATEGORY(WATCH_PROXY_CACHE))
         WatchThis (WATCHTK(tkptr), FI_LI, WATCH_PROXY_CACHE,
                    "CACHE READ parse fail !AZ !&S %!&M",
                    tkptr->ProxyCacheFileName, status, status);

      if (ProxyReportCacheLog)
         WriteFaoStdout (
"%!AZ-W-PROXY, !20%D, file parse error (!AZ !UL)\n-!&M\n \\!AZ\\\n",
            Utility, 0, FI_LI, status, tkptr->ProxyCacheFileName);

      ProxyReadCacheFailed (tkptr);
      return;
   }

   cfqptr = &tkptr->CacheFileQio;

   /* assign a channel to the disk device containing the file */
   DeviceDsc.dsc$a_pointer = tkptr->ParseNam.nam$l_dev;
   DeviceDsc.dsc$w_length = tkptr->ParseNam.nam$b_dev;

   status = sys$assign (&DeviceDsc, &cfqptr->AcpChannel, 0, 0, 0);
   if (VMSnok (status))
   {
      ProxyReadCacheFailed (tkptr);
      return;
   }

   /* set up the File Information Block for the ACP interface */
   cfqptr->FibDsc.dsc$w_length = sizeof(cfqptr->Fib);
   cfqptr->FibDsc.dsc$a_pointer = &cfqptr->Fib;

   memcpy (&cfqptr->Fib.fib$w_did, &tkptr->ParseNam.nam$w_did, 6);

   cfqptr->FileNameDsc.dsc$a_pointer = tkptr->ParseNam.nam$l_name;
   cfqptr->FileNameDsc.dsc$w_length = tkptr->ParseNam.nam$b_name +
                                      tkptr->ParseNam.nam$b_type +
                                      tkptr->ParseNam.nam$b_ver;

   atptr = &cfqptr->FileAtr;
   atptr->atr$w_size = sizeof(cfqptr->CdtBinTime);
   atptr->atr$w_type = ATR$C_CREDATE;
   atptr->atr$l_addr = &cfqptr->CdtBinTime;
   atptr++;
   atptr->atr$w_size = sizeof(cfqptr->RdtBinTime);
   atptr->atr$w_type = ATR$C_REVDATE;
   atptr->atr$l_addr = &cfqptr->RdtBinTime;
   atptr++;
   atptr->atr$w_size = sizeof(cfqptr->EdtBinTime);
   atptr->atr$w_type = ATR$C_EXPDATE;
   atptr->atr$l_addr = &cfqptr->EdtBinTime;
   atptr++;
   atptr->atr$w_size = sizeof(cfqptr->AscDates);
   atptr->atr$w_type = ATR$C_ASCDATES;
   atptr->atr$l_addr = &cfqptr->AscDates;
   atptr++;
   atptr->atr$w_size = sizeof(cfqptr->RecAttr);
   atptr->atr$w_type = ATR$C_RECATTR;
   atptr->atr$l_addr = &cfqptr->RecAttr;
   atptr++;
   atptr->atr$w_size = atptr->atr$w_type = atptr->atr$l_addr = 0;

   status = sys$qio (EfnNoWait, cfqptr->AcpChannel, IO$_ACCESS,
                     &cfqptr->IOsb, &ProxyCacheReadAcpInfoAst, tkptr, 
                     &cfqptr->FibDsc, &cfqptr->FileNameDsc, 0, 0,
                     &cfqptr->FileAtr, 0);
   if (VMSnok (status))
   {
      /* let the AST routine handle it! */
      cfqptr->IOsb.Status = status;
      SysDclAst (&ProxyCacheReadAcpInfoAst, tkptr);
   }
}

/****************************************************************************/
/*
The ACP-QIO has concluded delivering this AST routine.
*/

ProxyCacheReadAcpInfoAst (PROXY_TASK *tkptr)

{
   int  status,
        AccessHours,
        AgeHours,
        LoadHours,
        RdtSeconds;
   unsigned long  CurrentBinTime[2];
   PROXY_CACHE_FILE_QIO  *cfqptr;
   REQUEST_STRUCT  *rqptr;

   /*********/
   /* begin */
   /*********/

   if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (WATCHTK(tkptr), FI_LI, WATCH_MOD_PROXY,
                 "ProxyCacheReadAcpInfoAst() !&F !&S",
                 &ProxyCacheReadAcpInfoAst, tkptr->CacheFileQio.IOsb.Status);

   /* get the file ACP pointer from the task structure */
   cfqptr = &tkptr->CacheFileQio;

   if (VMSnok (status = cfqptr->IOsb.Status))
   {
      /*************/
      /* ACP error */
      /*************/

      if (WATCHING(tkptr) && WATCH_CATEGORY(WATCH_PROXY_CACHE))
      {
         if (status == SS$_NOSUCHFILE)
            WatchThis (WATCHTK(tkptr), FI_LI, WATCH_PROXY_CACHE,
                       "CACHE READ not found !AZ",
                       tkptr->ProxyCacheFileName);
         else
            WatchThis (WATCHTK(tkptr), FI_LI, WATCH_PROXY_CACHE,
                       "CACHE ACP fail !AZ !&S %!&M",
                       tkptr->ProxyCacheFileName, status, status);
      }

      if (ProxyReportCacheLog)
         if (status != SS$_NOSUCHFILE)
            WriteFaoStdout (
"%!AZ-W-PROXY, !20%D, file ACP error (!AZ !UL)\n-!&M\n \\!AZ\\\n",
               Utility, 0, FI_LI, status, tkptr->ProxyCacheFileName);

      ProxyReadCacheFailed (tkptr);
      return;
   }

   sys$gettim (&CurrentBinTime);

   InstanceGblSecIncrLong (&ProxyAccountingPtr->CacheReadCount);

   /* note that the cache file at least existed at the time it was checked! */
   tkptr->ProxyCacheFileExisted = true;

   cfqptr->EndOfFileVbn = ((cfqptr->RecAttr.fat$l_efblk & 0xffff) << 16) |
                          ((cfqptr->RecAttr.fat$l_efblk & 0xffff0000) >> 16);

   cfqptr->FirstFreeByte = cfqptr->RecAttr.fat$w_ffbyte;

   if (cfqptr->EndOfFileVbn <= 1)
      cfqptr->SizeInBytes = cfqptr->RecAttr.fat$w_ffbyte;
   else
      cfqptr->SizeInBytes = ((cfqptr->EndOfFileVbn-1) << 9) +
                            cfqptr->RecAttr.fat$w_ffbyte;

   if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (WATCHTK(tkptr), FI_LI, WATCH_MOD_PROXY,
                 "EndOfFileVbn:!UL FirstFreeByte:!UL SizeInBytes:!UL",
                  cfqptr->EndOfFileVbn, cfqptr->FirstFreeByte,
                  cfqptr->SizeInBytes);

   if (!cfqptr->SizeInBytes)
   {
      /* file header has just created during cache load - no content yet */
      ProxyReadCacheFailed (tkptr);
      return;
   }

   if (WATCHING(tkptr) && WATCH_CATEGORY(WATCH_PROXY_CACHE))
   {
      RdtSeconds = ProxyCacheAgeSeconds (&CurrentBinTime, &cfqptr->RdtBinTime);
      AgeHours = ProxyCacheAgeHours (&CurrentBinTime, &cfqptr->CdtBinTime);
      LoadHours = ProxyCacheAgeHours (&CurrentBinTime, &cfqptr->RdtBinTime);
      AccessHours = ProxyCacheAgeHours (&CurrentBinTime, &cfqptr->EdtBinTime);
      WatchThis (tkptr->RequestPtr, FI_LI, WATCH_PROXY_CACHE,
"CACHE READ noreload:!UL/!UL age:!UL/!UL \
load:!UL/!UL access:!UL/!UL (!UL time!%s) !AZ",
         RdtSeconds, ProxyCacheNoReloadSeconds,
         AgeHours, AgeHours/24,
         LoadHours, LoadHours/24,
         AccessHours, AccessHours/24,
         cfqptr->AscDates.RevisionCount,
         tkptr->ProxyCacheFileName);
   }

   /******************/
   /* check currency */
   /******************/

   if (ProxyCacheNoReloadSeconds)
      RdtSeconds = ProxyCacheAgeSeconds (&CurrentBinTime, &cfqptr->RdtBinTime);
   else
      RdtSeconds = 0;

   if (ProxyCacheAllowReload &&
       RdtSeconds >= ProxyCacheNoReloadSeconds &&
       tkptr->RequestPragmaNoCache)
   {
      /*******************/
      /* pragma no-cache */
      /*******************/

      if (WATCHING(tkptr) && WATCH_CATEGORY(WATCH_PROXY_CACHE))
         WatchThis (WATCHTK(tkptr), FI_LI, WATCH_PROXY_CACHE,
                    "CACHE READ pragma no-cache RELOAD");

      ProxyReadCacheFailed (tkptr);
      return;
   }

   AgeHours = ProxyCacheAgeHours (&CurrentBinTime, &cfqptr->CdtBinTime);
   LoadHours = ProxyCacheAgeHours (&CurrentBinTime, &cfqptr->RdtBinTime);

   if (ProxyCacheReloadAge (AgeHours, LoadHours))
   {
      /*************/
      /* age limit */
      /*************/

      if (WATCHING(tkptr) && WATCH_CATEGORY(WATCH_PROXY_CACHE))
         WatchThis (WATCHTK(tkptr), FI_LI, WATCH_PROXY_CACHE,
                    "CACHE READ age limit reload");

      ProxyReadCacheFailed (tkptr);
      return;
   }

   /***************************************/
   /* check any request if-modified-since */
   /***************************************/

   if ((rqptr = tkptr->RequestPtr))
   {
      if ((rqptr->rqTime.IfModifiedSinceVMS64bit[0] ||
           rqptr->rqTime.IfModifiedSinceVMS64bit[1]) &&
          !rqptr->PragmaNoCache)
      {
         /*********************/
         /* if modified since */
         /*********************/

         if (VMSnok (status =
             HttpIfModifiedSince (rqptr, &cfqptr->CdtBinTime, -1)))
         {
            /* task status LIB$_NEGTIM if not modified/sent */

            if (WATCHING(tkptr) && WATCH_CATEGORY(WATCH_PROXY_CACHE))
               WatchThis (WATCHTK(tkptr), FI_LI, WATCH_PROXY_CACHE,
                          "CACHE READ not modified");

            /* update the last-accessed time even for not-modified! */
            ProxyCacheSetLastAccessed (&tkptr->CacheFileQio,
                                       &cfqptr->RdtBinTime,
                                       &CurrentBinTime);

            InstanceGblSecIncrLong (&ProxyAccountingPtr->CacheRead304Count);
            /* the 304 header length */
            tkptr->ProxyCacheReadBytes = rqptr->rqResponse.HeaderLength;

            ProxyEnd (tkptr);
            return;
         }
      }
   }

   /*******************/
   /* 'open' the file */
   /*******************/

   cfqptr->Fib.fib$l_acctl = FIB$M_SEQONLY | FIB$M_NOWRITE;
   cfqptr->Fib.fib$w_nmctl = FIB$M_FINDFID;

   status = sys$qio (EfnNoWait, cfqptr->AcpChannel,
                     IO$_ACCESS | IO$M_ACCESS,
                     &cfqptr->IOsb, &ProxyCacheReadAccessAst, tkptr,
                     &cfqptr->FibDsc, 0, 0, 0, 0, 0);
   if (VMSnok (status))
   {
      /* let the AST routine handle it! */
      cfqptr->IOsb.Status = status;
      SysDclAst (ProxyCacheReadAccessAst, tkptr);
   }
}

/*****************************************************************************/
/*
The file access $QIO (the 'open') has completed.  Check status.
*/

ProxyCacheReadAccessAst (PROXY_TASK *tkptr)

{
   int  status;
   PROXY_CACHE_FILE_QIO  *cfqptr;

   /*********/
   /* begin */
   /*********/

   if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (WATCHTK(tkptr), FI_LI, WATCH_MOD_PROXY,
                 "ProxyCacheReadAccessAst() !&F !&S",
                 &ProxyCacheReadAccessAst, tkptr->CacheFileQio.IOsb.Status);

   cfqptr = &tkptr->CacheFileQio;

   if (VMSnok (status = cfqptr->IOsb.Status))
   {
      /****************/
      /* 'open' error */
      /****************/

      if (WATCHING(tkptr) && WATCH_CATEGORY(WATCH_PROXY_CACHE))
         WatchThis (WATCHTK(tkptr), FI_LI, WATCH_PROXY_CACHE,
                    "CACHE READ access fail !AZ !&S %!&M",
                    tkptr->ProxyCacheFileName, status, status);

      if (ProxyReportCacheLog)
         WriteFaoStdout (
"%!AZ-W-PROXY, !20%D, file open error (!AZ !UL)\n-!&M\n \\!AZ\\\n",
            Utility, 0, FI_LI, status, tkptr->ProxyCacheFileName);

      ProxyReadCacheFailed (tkptr);
      return;
   }

   /***********/
   /* success */
   /***********/

   /* now a file is being accessed on the original ACP-QIO channel */
   cfqptr->QioChannel = cfqptr->AcpChannel;
   cfqptr->AcpChannel = 0;

   /* only successful responses are ever cached, hence ... */
   tkptr->RequestPtr->rqResponse.HttpStatus =
      tkptr->ResponseStatusCode = 200;
   memcpy (tkptr->ResponseStatusCodeString, "200", 4);

   tkptr->ProxyCacheReadBytes = 0;

   /* fudge the first network write status */
   tkptr->RequestPtr->rqNet.WriteIOsb.Status = SS$_NORMAL;

   ProxyCacheReadNext (tkptr->RequestPtr);
}

/*****************************************************************************/
/*
************
*** NOTE ***  This function takes a pointer to a request!!!
************

Queue a read of the next series of Virtual Blocks from the file.  When the 
read completes call ProxyCacheNextAST() function to send the data to the
client.  Don't bother to test any status here, the AST routine will do that!
*/ 

ProxyCacheReadNext (REQUEST_STRUCT *rqptr)

{
   int  status,
        BufferSize;
   PROXY_CACHE_FILE_QIO  *cfqptr;
   PROXY_TASK  *tkptr;

   /*********/
   /* begin */
   /*********/

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (rqptr, FI_LI, WATCH_MOD_PROXY, "ProxyCacheReadNext()");

   tkptr = rqptr->ProxyTaskPtr; 

   if (VMSnok (status = rqptr->rqNet.WriteIOsb.Status))
   {
      /******************************/
      /* client network write error */
      /******************************/

      if (WATCHING(tkptr) && WATCH_CATEGORY(WATCH_PROXY_CACHE))
         WatchThis (WATCHTK(tkptr), FI_LI, WATCH_PROXY_CACHE,
                    "CACHE READ client write fail !&S %!&M", status, status);

      ProxyCacheEnd (tkptr);
      return;
   }

   /*********************/
   /* first/next blocks */
   /*********************/

   cfqptr = &tkptr->CacheFileQio;

   if (cfqptr->EndOfFile)
   {
      cfqptr->EndOfFile = false;
      cfqptr->IOsb.Status = SS$_ENDOFFILE;
      SysDclAst (&ProxyCacheReadNextAst, tkptr);
      return;
   }

   if (cfqptr->BlockNumber)
   {
      /* get next lot of virtual blocks */
      cfqptr->BlockNumber += cfqptr->BufferSize >> 9;
   }
   else
   {
      cfqptr->BlockNumber = 1;
      cfqptr->BufferPtr = tkptr->ResponseBufferPtr;
      /* make buffer size an even number of 512 byte blocks */
      cfqptr->BufferSize = tkptr->ResponseBufferSize & 0xfe00;
      tkptr->ResponseBufferSize = cfqptr->BufferSize;
   }

   BufferSize = cfqptr->BufferSize;
   if ((cfqptr->BlockNumber-1) * 512 + BufferSize >= cfqptr->SizeInBytes)
   {
      BufferSize = cfqptr->SizeInBytes - (cfqptr->BlockNumber-1) * 512;
      /* it's also a calculated EOF! */
      cfqptr->EndOfFile = true;
   }

   if (WATCHING(rqptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (rqptr, FI_LI, WATCH_MOD_PROXY, "vbn:!UL buf:!UL size:!UL",
                 cfqptr->BlockNumber, cfqptr->BufferSize, BufferSize);

   /*
      Documented in the VMS I/O Users Guide section entitled "Disk Function
      Codes" ... "P2--The number of bytes that are to be read from the disk,
      or written from memory to the disk. An even number must be specified if
      the controller is an RK611, RL11, RX211, or UDA50".
      Well, make sure we ask for an even number of bytes!
      Thanks to Dave Holland for demonstrating this bug on SIMH, and to
      Mark Pizzolato from the SIMH mailing list for pointing out the above.
   */
   if (BufferSize & 1)
   {
      BufferSize++;
      cfqptr->AdjustBuffer = 1;
   }
   else
      cfqptr->AdjustBuffer = 0;

   status = sys$qio (EfnNoWait, cfqptr->QioChannel,
                     IO$_READVBLK, &cfqptr->IOsb,
                     &ProxyCacheReadNextAst, tkptr,
                     cfqptr->BufferPtr, BufferSize, cfqptr->BlockNumber,
                     0, 0, 0);
   if (VMSnok (status))
   {
      /* let the AST routine handle it! */
      cfqptr->IOsb.Status = status;
      SysDclAst (&ProxyCacheReadNextAst, tkptr);
   }
}

/*****************************************************************************/
/*
The read of the next series of Virtual Blocks from the file has completed.  
Post-process and/or queue a network write to the client.  When the network 
write completes it will call the function ProxyCacheReadNextAst() to queue a
read of the next series of blocks.
*/ 

ProxyCacheReadNextAst (PROXY_TASK *tkptr)

{
   int  status,
        DataLength;
   unsigned long  CurrentBinTime [2];
   char  *BodyPtr,
         *DataPtr,
         *HeaderPtr,
         *UrlPtr;
   REQUEST_STRUCT  *rqptr;
   PROXY_CACHE_DESCR  *pcdptr;
   PROXY_CACHE_FILE_QIO  *cfqptr;

   /*********/
   /* begin */
   /*********/

   if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (WATCHTK(tkptr), FI_LI, WATCH_MOD_PROXY,
                 "ProxyCacheReadNextAst() !&F !&S",
                 &ProxyCacheReadNextAst, tkptr->CacheFileQio.IOsb.Status);

   rqptr = tkptr->RequestPtr;
   cfqptr = &tkptr->CacheFileQio;

   if (VMSnok (status = cfqptr->IOsb.Status))
   {
      /*************************/
      /* cache file read error */
      /*************************/

      if (status == SS$_ENDOFFILE)
      {
         if (WATCHING(tkptr) && WATCH_CATEGORY(WATCH_PROXY_CACHE))
            WatchThis (WATCHTK(tkptr), FI_LI, WATCH_PROXY_CACHE,
                       "CACHE READ eof !UL bytes",
                       tkptr->ProxyCacheReadBytes);

         sys$gettim (&CurrentBinTime);
         ProxyCacheSetLastAccessed (&tkptr->CacheFileQio,
                                    NULL, &CurrentBinTime);
         ProxyCacheEnd (tkptr);
         return;
      }

      if (WATCHING(tkptr) && WATCH_CATEGORY(WATCH_PROXY_CACHE))
         WatchThis (WATCHTK(tkptr), FI_LI, WATCH_PROXY_CACHE,
                    "CACHE READ fail !&S %!&M", status, status);

      rqptr->rqResponse.HttpStatus = 500;
      rqptr->rqResponse.ErrorTextPtr = tkptr->RequestHostPort;
      ErrorVmsStatus (rqptr, status, FI_LI);
      ProxyCacheEnd (tkptr);
      return;
   }

   /* get the count from the I/O status block (adjusted as necessary) */
   cfqptr->IOsb.Count -= cfqptr->AdjustBuffer;
   cfqptr->BufferCount = cfqptr->IOsb.Count;

   if (cfqptr->BlockNumber == 1)
   {
      /***************/
      /* first block */
      /***************/

      if (!cfqptr->BufferCount)
      {
         /* in between the ACP-QIO and opening it has started reloading */
         ErrorNoticed (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
         /* just continue on, closing the cache file in ProxyCacheEnd() */
         ProxyReadCacheFailed (tkptr);
         return;
      }

      /* contains cache file description (lengths and request URL) */
      DataPtr = cfqptr->BufferPtr;
      DataLength = cfqptr->BufferCount;
      pcdptr = (PROXY_CACHE_DESCR*)DataPtr;
      UrlPtr = DataPtr + sizeof(PROXY_CACHE_DESCR);
      HeaderPtr = UrlPtr + pcdptr->UrlLength;
      BodyPtr = HeaderPtr + pcdptr->HeaderLength;

      if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
         WatchThis (WATCHTK(tkptr), FI_LI, WATCH_MOD_PROXY,
                    "ver:!XL req:!&X/!UL hdr:!&X/!UL bdy:!&X",
                    pcdptr->CacheVersion, UrlPtr, pcdptr->UrlLength,
                    HeaderPtr, pcdptr->HeaderLength, BodyPtr);

      DataPtr = HeaderPtr;
      DataLength -= sizeof(PROXY_CACHE_DESCR) + pcdptr->UrlLength;

      if (pcdptr->CacheVersion != PROXY_CACHE_FILE_VERSION)
      {
         /***************/
         /* delete file */
         /***************/

         if (WATCHING(tkptr) && WATCH_CATEGORY(WATCH_PROXY_CACHE))
            WatchThis (WATCHTK(tkptr), FI_LI, WATCH_PROXY_CACHE,
                       "CACHE READ cache VERSION MISMATCH");

         /* this will happen very infrequently - never I should imagine! */
         status = sys$qiow (EfnWait, cfqptr->QioChannel, 
                            IO$_DELETE | IO$M_DELETE,
                            &cfqptr->IOsb, 0, 0, 
                            &cfqptr->FibDsc, 0, 0, 0, 0, 0);
         if (VMSok (status)) status = cfqptr->IOsb.Status;
         if (VMSnok (status)) ErrorNoticed (status, "IO$_DELETE", FI_LI);

         rqptr->rqResponse.HttpStatus = 500;
         ErrorGeneral (rqptr, ErrorProxyCacheVersion, FI_LI);
         ProxyCacheEnd (tkptr);
         return;
      }
   }
   else
   {
      /* this just shouldn't happen! */
      if (!cfqptr->BufferCount)
         ErrorNoticed (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

      DataPtr = cfqptr->BufferPtr;
      DataLength = cfqptr->BufferCount;
   }

   tkptr->ProxyCacheReadBytes += DataLength;

   NetWrite (rqptr, &ProxyCacheReadNext, DataPtr, DataLength);
}

/*****************************************************************************/
/*
Begin to load a file to cache the contents of a proxied request.  First assess
whether the request meets the criteria for caching.  If it does not. If it does
then initialize the RMS structures for the file and call the create service,
which ASTs to ProxyCacheLoadConnect().  Check the success there.
*/

int ProxyCacheLoadBegin (PROXY_TASK *tkptr)

{
   static unsigned short  EmptyDid [3] = { 0, 0, 0 };

   int  status,
        ExpiresHours,
        LastModifiedHours;
   unsigned int  AllocationQuantity;
   unsigned long  CurrentBinTime [2];
   PROXY_CACHE_DESCR  *pcdptr;

   /*********/
   /* begin */
   /*********/

   if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (WATCHTK(tkptr), FI_LI, WATCH_MOD_PROXY,
                 "ProxyCacheLoadBegin()");

   if (!ProxyCacheFreeSpaceAvailable)
   {
      /* no file system space available for caching purposes */
      if (WATCHING(tkptr) && WATCH_CATEGORY(WATCH_PROXY_CACHE))
      {
         WatchThis (WATCHTK(tkptr), FI_LI, WATCH_PROXY_CACHE,
                    "CACHE LOAD insufficient device space (!UL% max)",
                    ProxyCacheDeviceMaxPercent);
         ProxyMaintWatchDeviceStats ();
      }
      return (STS$K_ERROR);
   }

   sys$gettim (&CurrentBinTime);

   if (tkptr->ResponseLastModified[0])
      tkptr->ProxyCacheLastModifiedHours = LastModifiedHours =
         ProxyCacheAgeHours (&CurrentBinTime,
                             &tkptr->ResponseLastModifiedBinaryTime);

   if (tkptr->ResponseExpires[0])
      ExpiresHours = ProxyCacheAgeHours (&tkptr->ResponseExpiresBinaryTime,
                                         &CurrentBinTime);

   if (tkptr->ResponseStatusCode != 200 ||
       tkptr->ResponsePragmaNoCache ||
       tkptr->ResponseContentRange ||
       tkptr->ResponseContentTypeMultipart ||
       tkptr->ResponseHttpVersion == HTTP_VERSION_0_9 ||
       !tkptr->ResponseLastModified[0] ||
       (tkptr->ResponseLastModified[0] && LastModifiedHours < 1) ||
       (tkptr->ResponseExpires[0] && ExpiresHours < 1) ||
       (tkptr->ResponseContentLength >= 0 &&
        (tkptr->ResponseContentLength >> 10) + 1 > ProxyCacheFileKBytesMax))
   {
      /****************/
      /* not cachable */
      /****************/

      if (WATCHING(tkptr) && WATCH_CATEGORY(WATCH_PROXY_CACHE))
      {
         char  String [256];

         String[0] = '\0';
         if (tkptr->ResponseStatusCodeString[0] != '2')
         {
            strcpy (String, "status ");
            strcat (String, tkptr->ResponseStatusCodeString);
         }
         if (tkptr->ResponsePragmaNoCache)
         {
            if (String[0]) strcat (String, ", ");
            strcat (String, "pragma no-cache");
         }
         if (tkptr->ResponseHttpVersion == HTTP_VERSION_0_9)
         {
            if (String[0]) strcat (String, ", ");
            strcat (String, "HTTP/0.9");
         }
         if (!tkptr->ResponseLastModified[0])
         {
            if (String[0]) strcat (String, ", ");
            strcat (String, "no last-modified");
         }
         if (tkptr->ResponseLastModified[0] && LastModifiedHours < 1)
         {
            if (String[0]) strcat (String, ", ");
            strcat (String, "last-modified < 1");
         }
         if (tkptr->ResponseExpires[0] && ExpiresHours < 1)
         {
            if (String[0]) strcat (String, ", ");
            strcat (String, "expires < 1");
         }
         if (tkptr->ResponseContentLength >= 0 &&
             (tkptr->ResponseContentLength >> 10) + 1 > ProxyCacheFileKBytesMax)
         {
            char  *cptr;

            for (cptr = String; *cptr; cptr++);
            if (String[0]) *(unsigned short*)cptr++ = ', ';
            sprintf (cptr, "content-length %d > %d kB",
                     (tkptr->ResponseContentLength >> 10) + 1,
                     ProxyCacheFileKBytesMax);
                     
         }
         if (!String[0]) strcpy (String, "?");

         WatchThis (WATCHTK(tkptr), FI_LI, WATCH_PROXY_CACHE,
                    "CACHE LOAD not cachable: !AZ", String);
      }

      /* 'tkptr->ProxyCacheSuitable' checked method and query string */
      if (tkptr->ResponseStatusCode == 304)
      {
         if (tkptr->ProxyCacheFileExisted)
         {
            /*****************************/
            /* not modified, leave as-is */
            /*****************************/

            if (WATCHING(tkptr) && WATCH_CATEGORY(WATCH_PROXY_CACHE))
               WatchThis (WATCHTK(tkptr), FI_LI, WATCH_PROXY_CACHE,
                          "CACHE LOAD not modified !AZ",
                          tkptr->ProxyCacheFileName);

            /* update both the load and access times to current */
            ProxyCacheSetLastAccessed (&tkptr->CacheFileQio,
                                       &CurrentBinTime,
                                       &CurrentBinTime);

            return (STS$K_ERROR);
         }
      }

      /* it's not cacheable but already existed so delete it! */
      if (tkptr->ProxyCacheFileExisted)
      {
         /******************************/
         /* delete original cache file */
         /******************************/

         if (WATCHING(tkptr) && WATCH_CATEGORY(WATCH_PROXY_CACHE))
            WatchThis (WATCHTK(tkptr), FI_LI, WATCH_PROXY_CACHE,
                       "CACHE LOAD delete !AZ", tkptr->ProxyCacheFileName);

         tkptr->ParseFab.fab$l_fop |= FAB$M_DLT;
         /* synchronous service */
         tkptr->ParseFab.fab$l_fop &= ~FAB$M_ASY;
         status = sys$erase (&tkptr->ParseFab, 0, 0);

         if (ProxyReportCacheLog)
            if (VMSnok (status))
               WriteFaoStdout (
"%!AZ-W-PROXY, !20%D, file erase error (!AZ !UL)\n-!&M\n \\!AZ\\\n",
                  Utility, 0, FI_LI, status, tkptr->ProxyCacheFileName);
      }

      tkptr->NotCacheable = true;
      InstanceGblSecIncrLong (&ProxyAccountingPtr->ResponseNotCacheableCount);

      return (STS$K_ERROR);
   }

   if (!memcmp (&tkptr->ParseNam.nam$w_did, &EmptyDid, 6))
   {
      /**********************************/
      /* cache directory does not exist */
      /**********************************/

      status = ProxyCacheCreateDirectory (tkptr);

      /* can't create the file if there's no directory to create it in! */
      if (VMSnok (status)) return (STS$K_ERROR);
   }

   /**************/
   /* begin load */
   /**************/

   InstanceGblSecIncrLong (&ProxyAccountingPtr->CacheWriteCount);

   /* slip the header length into the cache file description block */
   pcdptr = (PROXY_CACHE_DESCR*)tkptr->ResponseBufferPtr;
   pcdptr->HeaderLength = tkptr->ResponseHeaderLength;

   AllocationQuantity = sizeof(PROXY_CACHE_DESCR) +
                        pcdptr->UrlLength +
                        tkptr->ResponseHeaderLength;
   if (tkptr->ResponseContentLength > 0)
      AllocationQuantity += tkptr->ResponseContentLength;
   AllocationQuantity = (AllocationQuantity >> 9) + 1;
   /* bit of a sanity check ... don't want to chew up the whole disk */
   if (AllocationQuantity > ProxyCacheAllocationQuantityMax)
      AllocationQuantity = ProxyCacheAllocationQuantityMax;

   if (WATCHING(tkptr) && WATCH_CATEGORY(WATCH_PROXY_CACHE))
   {
      if (tkptr->ResponseContentLength >= 0)
         WatchThis (WATCHTK(tkptr), FI_LI, WATCH_PROXY_CACHE,
                    "CACHE !&?RE\r\rLOAD !AZ content !UL bytes (!UL blocks)",
                    tkptr->ProxyCacheFileExisted,
                    tkptr->ProxyCacheFileName,
                    tkptr->ResponseContentLength,
                    AllocationQuantity);
      else
         WatchThis (WATCHTK(tkptr), FI_LI, WATCH_PROXY_CACHE,
                    "CACHE !&?RE\r\rLOAD !AZ content ? bytes (!UL blocks)",
                    tkptr->ProxyCacheFileExisted,
                    tkptr->ProxyCacheFileName,
                    AllocationQuantity);
   }

   tkptr->LoadFab = cc$rms_fab;
   tkptr->LoadFab.fab$l_alq = AllocationQuantity;
   tkptr->LoadFab.fab$l_ctx = tkptr;
   tkptr->LoadFab.fab$b_fac = FAB$M_PUT | FAB$M_TRN;
   tkptr->LoadFab.fab$l_fna = tkptr->ProxyCacheFileName;
   tkptr->LoadFab.fab$b_fns = tkptr->ProxyCacheFileNameLength;
   tkptr->LoadFab.fab$l_fop = FAB$M_CIF | FAB$M_SQO |
                              FAB$M_DFW | FAB$M_TEF;
   tkptr->LoadFab.fab$l_nam = &tkptr->ParseNam;
   tkptr->LoadFab.fab$b_rat = 0;
   tkptr->LoadFab.fab$b_rfm = FAB$C_UDF;

   tkptr->LoadFab.fab$b_shr = FAB$M_NIL;
   tkptr->LoadFab.fab$l_xab = &tkptr->LoadXabDat;

   /* initialize the name block */
   tkptr->ParseNam = cc$rms_nam;
   tkptr->ParseNam.nam$l_esa = tkptr->ExpandedFileName;
   tkptr->ParseNam.nam$b_ess = sizeof(tkptr->ExpandedFileName)-1;

   /* date/time extended attribute block */
   tkptr->LoadXabDat = cc$rms_xabdat;
   tkptr->LoadXabDat.xab$l_nxt = &tkptr->LoadXabPro;
   memcpy (&tkptr->LoadXabDat.xab$q_cdt,
           &tkptr->ResponseLastModifiedBinaryTime,
           8);
   memcpy (&tkptr->LoadXabDat.xab$q_edt, &CurrentBinTime, 8);

   /* protection extended attribute block */
   tkptr->LoadXabPro = cc$rms_xabpro;
   /* W:RE,G:RE,O:RWED,S:RWED */
   tkptr->LoadXabPro.xab$w_pro = 0xaa00;

   /* asynchronous service */
   tkptr->LoadFab.fab$l_fop |= FAB$M_ASY;
   status = sys$create (&tkptr->LoadFab, ProxyCacheLoadConnect,
                                         ProxyCacheLoadConnect);

   return (SS$_NORMAL);
} 

/*****************************************************************************/
/*
AST function delivered from ProxyCacheLoadBegin(). Check the success of the
file create. If not report it to the process log and forget about caching the
file. If OK initialize a RAB structure and call the connect service, which
ASTs to ProxyCacheLoadConnectAst(). Check the status there.
*/

ProxyCacheLoadConnect (struct FAB *FabPtr)

{
   PROXY_TASK *tkptr;

   int  status;

   /*********/
   /* begin */
   /*********/

   tkptr = FabPtr->fab$l_ctx;

   if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (WATCHTK(tkptr), FI_LI, WATCH_MOD_PROXY,
                 "ProxyCacheLoadConnect() !&F !&X !&X",
                 &ProxyCacheLoadConnect,
                 FabPtr->fab$l_sts, FabPtr->fab$l_stv);

   if (VMSnok (status = tkptr->LoadFab.fab$l_sts))
   {
      /**********************/
      /* sys$create() error */
      /**********************/

      if (WATCHING(tkptr) && WATCH_CATEGORY(WATCH_PROXY_CACHE))
         WatchThis (WATCHTK(tkptr), FI_LI, WATCH_PROXY_CACHE,
                    "CACHE CREATE fail !&S !-%!&M", status);

      if (ProxyReportCacheLog)
         WriteFaoStdout (
"%!AZ-W-PROXY, !20%D, file create error (!AZ !UL)\n-!&M\n \\!AZ\\\n",
            Utility, 0, FI_LI, status, tkptr->ProxyCacheFileName);

      /* explicitly call the function to continue transaction with client */
      ProxyResponseNetWrite (tkptr);
      return;
   }

   tkptr->ParseNam.nam$l_ver[tkptr->ParseNam.nam$b_ver] = '\0';

   tkptr->LoadRab = cc$rms_rab;
   tkptr->LoadRab.rab$l_fab = &tkptr->LoadFab;
   tkptr->LoadRab.rab$l_ctx = tkptr;
   tkptr->LoadRab.rab$b_rac = RAB$C_SEQ;

   status = sys$connect (&tkptr->LoadRab, 0, 0);
   if (VMSok (status)) status = tkptr->LoadRab.rab$l_sts;

   if (VMSnok (status))
   {
      /***********************/
      /* sys$connect() error */
      /***********************/

      if (WATCHING(tkptr) && WATCH_CATEGORY(WATCH_PROXY_CACHE))
         WatchThis (WATCHTK(tkptr), FI_LI, WATCH_PROXY_CACHE,
                    "CACHE CONNECT fail !&S %!&M", status, status);

      if (ProxyReportCacheLog)
         WriteFaoStdout (
"%!AZ-W-PROXY, !20%D, file connect error (!AZ !UL)\n-!&M\n \\!AZ\\\n",
            Utility, 0, FI_LI, status, tkptr->ProxyCacheFileName);

      /* delete file as closed, ensure an asynchronous close service */
      tkptr->LoadFab.fab$l_fop |= FAB$M_ASY | FAB$M_DLT;
      /* AST the function to continue the transaction with client */
      status = sys$close (&tkptr->LoadFab, &ProxyCacheCloseAst_NetWrite,
                                           &ProxyCacheCloseAst_NetWrite);
      return;
   }

   /* asynchronous truncate on put, in case file existed and we're reloading */
   tkptr->LoadRab.rab$l_rop = RAB$M_ASY | RAB$M_TPT;

   ProxyCacheLoadWrite (tkptr);
} 

/*****************************************************************************/
/*
Called after a chunk of data has been read from the remote server to write
that chunk into the cache file.  AST to ProxyCacheLoadWriteAst() to check the
success of the write.
*/

ProxyCacheLoadWrite (PROXY_TASK *tkptr)

{
   int  status;

   /*********/
   /* begin */
   /*********/

   if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (WATCHTK(tkptr), FI_LI, WATCH_MOD_PROXY,
                 "ProxyCacheLoadWrite()");

   if (tkptr->ResponseContentLength < 0)
   {
      /* response had no content-length, check the quantity received so far */
      if (((tkptr->ResponseBytes - tkptr->ResponseHeaderLength) >> 10) + 1 >
          ProxyCacheFileKBytesMax)
      {
         /*********************************/
         /* exceeds configuration maximum */
         /*********************************/

         if (WATCHING(tkptr) && WATCH_CATEGORY(WATCH_PROXY_CACHE))
            WatchThis (WATCHTK(tkptr), FI_LI, WATCH_PROXY_CACHE,
               "CACHE LOAD abort !UL exceeds !UL kB",
               ((tkptr->ResponseBytes - tkptr->ResponseHeaderLength) >> 10) + 1,
               ProxyCacheFileKBytesMax);

         /* close/delete file, fab$w_ifi will then indicate no cache file */
         tkptr->LoadFab.fab$l_fop |= FAB$M_DLT;
         /* synchronous service */
         tkptr->LoadFab.fab$l_fop &= ~FAB$M_ASY;
         status = sys$close (&tkptr->LoadFab, 0, 0);

         if (ProxyReportCacheLog)
            if (VMSnok (status))
               WriteFaoStdout (
"%!AZ-W-PROXY, !20%D, file delete-on-close error (!AZ !UL)\n-!&M\n \\!AZ\\\n",
                  Utility, 0, FI_LI, status, tkptr->ProxyCacheFileName);

         /* explicitly call the function to continue transaction with client */
         ProxyResponseNetWrite (tkptr);
         return;
      }
   }

   tkptr->LoadRab.rab$l_rbf = tkptr->ResponseBufferCachePtr;
   tkptr->LoadRab.rab$w_rsz = tkptr->ResponseBufferCacheCount;

   sys$put (&tkptr->LoadRab, &ProxyCacheLoadWriteAst,
                             &ProxyCacheLoadWriteAst);
} 

/*****************************************************************************/
/*
AST delivered from ProxyCacheLoadWrite().  Check the status of the write and
if successful declare the AST originally passed to that routine to continue
retrieving the request from the remote server.  If an error was encountered
delete as the file is closed and forget about further caching the response
(although in all these cases continuing to retrieve it via the network!)
*/

ProxyCacheLoadWriteAst (struct RAB *RabPtr)

{
   int  status;
   PROXY_TASK *tkptr;

   /*********/
   /* begin */
   /*********/

   tkptr = RabPtr->rab$l_ctx;

   if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (WATCHTK(tkptr), FI_LI, WATCH_MOD_PROXY,
                 "ProxyCacheLoadWriteAst() !&F !&X !&X !UL",
                 &ProxyCacheLoadWriteAst,
                 RabPtr->rab$l_sts, RabPtr->rab$l_stv, RabPtr->rab$w_rsz);

   if (VMSnok (status = tkptr->LoadRab.rab$l_sts))
   {
      /**************************/
      /* cache file write error */
      /**************************/

      if (WATCHING(tkptr) && WATCH_CATEGORY(WATCH_PROXY_CACHE))
         WatchThis (WATCHTK(tkptr), FI_LI, WATCH_PROXY_CACHE,
                    "CACHE WRITE fail !&S %!&M", status, status);

      if (ProxyReportCacheLog)
         WriteFaoStdout (
"%!AZ-W-PROXY, !20%D, file write error (!AZ !UL)\n-!&M\n \\!AZ\\\n",
            Utility, 0, FI_LI, status, tkptr->ProxyCacheFileName);

      /* delete file as closed, ensure an asynchronous close service */
      tkptr->LoadFab.fab$l_fop |= FAB$M_ASY | FAB$M_DLT;
      status = sys$close (&tkptr->LoadFab, &ProxyCacheCloseAst_NetWrite,
                                           &ProxyCacheCloseAst_NetWrite);
      return;
   }

   ProxyResponseNetWrite (tkptr);
} 

/*****************************************************************************/
/*
Called by ProxyCacheEnd() when the response is complete.  Check if it meets the
criteria to be retained (i.e. essentially a completely successful transaction). 
If it does then just close the file.  If not then delete it as it is closed. 
The sys$close() are asynchronous, AST to ProxyEnd() to complete the
transaction.
*/

ProxyCacheLoadEnd (PROXY_TASK *tkptr)

{
   int  status,
        ClientWriteStatus,
        ProxyReadStatus;

   /*********/
   /* begin */
   /*********/

   if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (WATCHTK(tkptr), FI_LI, WATCH_MOD_PROXY,
                 "ProxyCacheLoadEnd()");

   ClientWriteStatus = SS$_NORMAL;
   if (tkptr->RequestPtr)
      if (VMSnok (tkptr->RequestPtr->rqNet.WriteIOsb.Status))
         ClientWriteStatus = tkptr->RequestPtr->rqNet.WriteIOsb.Status;
   ProxyReadStatus = tkptr->ProxyReadIOsb.Status;
   /* normal for servers to abruptly disconnect at the end of a request */
   if (ProxyReadStatus == SS$_LINKDISCON) ProxyReadStatus = SS$_NORMAL;

   if ((tkptr->ResponseContentLength >= 0 &&
        tkptr->ResponseBodyLength != tkptr->ResponseContentLength) ||
       VMSnok (ClientWriteStatus) ||
       VMSnok (ProxyReadStatus))
   {
      if (WATCHING(tkptr) && WATCH_CATEGORY(WATCH_PROXY_CACHE))
      {
         if (VMSnok (ProxyReadStatus))
            WatchThis (WATCHTK(tkptr), FI_LI, WATCH_PROXY_CACHE,
                       "CACHE LOAD FAIL server read !&S %!&M",
                       ProxyReadStatus, ProxyReadStatus);
         else
         if (VMSnok (ClientWriteStatus))
            WatchThis (WATCHTK(tkptr), FI_LI, WATCH_PROXY_CACHE,
                       "CACHE LOAD FAIL client write !&S %!&M",
                       ClientWriteStatus, ClientWriteStatus);
         else
         if (tkptr->ResponseContentLength >= 0)
            WatchThis (WATCHTK(tkptr), FI_LI, WATCH_PROXY_CACHE,
                       "CACHE LOAD FAIL received:!UL expecting:!UL",
                        tkptr->ResponseBodyLength,
                        tkptr->ResponseContentLength);
         else
            WatchThis (WATCHTK(tkptr), FI_LI, WATCH_PROXY_CACHE,
                       "CACHE LOAD FAIL reason unknown");
      }

      if (ProxyReportCacheLog)
      {
         if (VMSnok (ProxyReadStatus))
            WriteFaoStdout (
"%!AZ-W-PROXY, !20%D, cache load read fail (!AZ !UL)\n-!&M\n \\!AZ\\\n",
               Utility, 0, FI_LI, ProxyReadStatus, tkptr->ProxyCacheFileName);
         else 
         if (VMSnok (ClientWriteStatus))
            WriteFaoStdout (
"%!AZ-W-PROXY, !20%D, cache load write fail (!AZ !UL)\n-!&M\n \\!AZ\\\n",
               Utility, 0, FI_LI, ClientWriteStatus, tkptr->ProxyCacheFileName);
         else 
         if (tkptr->ResponseContentLength >= 0)
            WriteFaoStdout (
"%!AZ-W-PROXY, !20%D, cache load fail, received:!UL expecting:!UL (!AZ !UL)\n\
 \\!AZ\\\n",
               Utility, 0,
               tkptr->ResponseBodyLength, tkptr->ResponseContentLength,
               FI_LI, tkptr->ProxyCacheFileName);
         else
            WriteFaoStdout (
"%!AZ-W-PROXY, !20%D, cache load fail (!AZ !UL)\n \\!AZ\\\n",
               Utility, 0, FI_LI, tkptr->ProxyCacheFileName);
      }

      /* close/delete file asynchronously AST calling ProxyEnd() */
      tkptr->LoadFab.fab$l_fop |= FAB$M_DLT | FAB$M_ASY;
      status = sys$close (&tkptr->LoadFab, &ProxyCacheCloseAst_ProxyEnd,
                                           &ProxyCacheCloseAst_ProxyEnd);
      return;
   }
   else
   {    
      if (WATCHING(tkptr) && WATCH_CATEGORY(WATCH_PROXY_CACHE))
         WatchThis (WATCHTK(tkptr), FI_LI, WATCH_PROXY_CACHE,
"CACHE LOAD OK content !UL bytes (!UL blocks), age !UL/!UL (hrs/days)",
            tkptr->ResponseBodyLength,
          ((tkptr->ResponseHeaderLength + tkptr->ResponseBodyLength) >> 9) + 1,
            tkptr->ProxyCacheLastModifiedHours,
            tkptr->ProxyCacheLastModifiedHours/24);

      /* close file asynchronously AST calling ProxyEnd() */
      tkptr->LoadFab.fab$l_fop |= FAB$M_ASY;
      status = sys$close (&tkptr->LoadFab, &ProxyCacheCloseAst_ProxyEnd,
                                           &ProxyCacheCloseAst_ProxyEnd);
      return;
   }
} 

/*****************************************************************************/
/*
After an asynchronous sys$close() call ProxyEnd() with the 'tkptr' parameter
stored in the FAB context.
*/

ProxyCacheCloseAst_ProxyEnd (struct FAB *FabPtr)

{
   int  status;
   PROXY_TASK  *tkptr;

   /*********/
   /* begin */
   /*********/

   tkptr = FabPtr->fab$l_ctx;

   if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (WATCHTK(tkptr), FI_LI, WATCH_MOD_PROXY,
                 "ProxyCacheCloseAst_ProxyEnd() !&F !&X !&X",
                 &ProxyCacheCloseAst_ProxyEnd,
                 FabPtr->fab$l_sts, FabPtr->fab$l_stv);

   if (ProxyReportCacheLog)
      if (VMSnok (status = FabPtr->fab$l_sts))
         WriteFaoStdout (
"%!AZ-W-PROXY, !20%D, file delete-on-close error (!AZ !UL)\n-!&M\n \\!AZ\\\n",
            Utility, 0, FI_LI, status, tkptr->ProxyCacheFileName);

   ProxyEnd (tkptr);
} 

/*****************************************************************************/
/*
After an asynchronous sys$close() call ProxyResponseNetWrite() with the 'tkptr'
parameter stored in the FAB context. 
*/

ProxyCacheCloseAst_NetWrite (struct FAB *FabPtr)

{
   int  status;
   PROXY_TASK  *tkptr;

   /*********/
   /* begin */
   /*********/

   tkptr = FabPtr->fab$l_ctx;

   if (WATCHING(tkptr) && WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (WATCHTK(tkptr), FI_LI, WATCH_MOD_PROXY,
                 "ProxyCacheCloseAst_NetWrite() !&F !&X !&X",
                 &ProxyCacheCloseAst_NetWrite,
                 FabPtr->fab$l_sts, FabPtr->fab$l_stv);

   if (ProxyReportCacheLog)
      if (VMSnok (status = FabPtr->fab$l_sts))
         WriteFaoStdout (
"%!AZ-W-PROXY, !20%D, file delete-on-close error (!AZ !UL)\n-!&M\n \\!AZ\\\n",
            Utility, 0, FI_LI, status, tkptr->ProxyCacheFileName);

   ProxyResponseNetWrite (tkptr);
} 

/*****************************************************************************/
/*
Create a cache directory.
*/ 
 
ProxyCacheCreateDirectory (PROXY_TASK *tkptr)

{
   static unsigned short  ProtectionEnable = 0xffff, /* alter all S,O,G,W */ 
                          ProtectionMask = 0xaa00;   /* S:RWED */
   static $DESCRIPTOR (DirectoryDsc, "");

   int  status;
   char  *cptr, *sptr;
   char  DirectoryName [64];

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (NULL, FI_LI, WATCH_MOD_PROXY, "ProxyCacheCreateDirectory()");

   sptr = DirectoryName;
   for (cptr = tkptr->ProxyCacheFileName;
        *cptr && *cptr != ']';
        *sptr++ = *cptr++);
   *sptr++ = ']';
   *sptr = '\0';

   DirectoryDsc.dsc$a_pointer = DirectoryName;
   DirectoryDsc.dsc$w_length = sptr - DirectoryName;

   status = lib$create_dir (&DirectoryDsc, 0, &ProtectionEnable,
                            &ProtectionMask, 0, 0);
   if (WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (NULL, FI_LI, WATCH_MOD_PROXY, "!&Z !&S",
                 DirectoryName, status);

   if (status == SS$_CREATED)
      status = SS$_NORMAL;
   else
   if (status == SS$_NORMAL)
      status = RMS$_DNF;

   if (WATCHING(tkptr) && WATCH_CATEGORY(WATCH_PROXY_CACHE))
   {
      if (VMSnok (status))
         WatchThis (WATCHTK(tkptr), FI_LI,
                    WATCH_PROXY_CACHE,
                    "CACHE DIRECTORY create fail !AZ !&S %!&M",
                    DirectoryName, status, status);
      else
         WatchThis (WATCHTK(tkptr), FI_LI,
                    WATCH_PROXY_CACHE,
                    "CACHE DIRECTORY created !AZ",
                    DirectoryName);
   }

   if (ProxyReportCacheLog)
      if (VMSnok (status))
         WriteFaoStdout (
"%!AZ-W-PROXY, !20%D, directory create error (!AZ !UL)\n-!&M\n \\!AZ\\\n",
            Utility, 0, FI_LI, status, DirectoryName);

   return (status);
}

/****************************************************************************/
/*
Return the age in seconds relative to the current time.
*/

int ProxyCacheAgeSeconds
(
unsigned long *CurrentBinaryTimePtr,
unsigned long *AgeBinaryTimePtr
)
{
   static unsigned long  LibDeltaSeconds = LIB$K_DELTA_SECONDS;

   int  status;
   unsigned long  AgeSeconds;
   unsigned long  BinTime[2],
                  ResultBinTime [2];

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (NULL, FI_LI, WATCH_MOD_PROXY, "ProxyCacheAgeSeconds()");

   if (!AgeBinaryTimePtr[0] && !AgeBinaryTimePtr[1]) return (999999999);

   if (!CurrentBinaryTimePtr) sys$gettim (CurrentBinaryTimePtr = &BinTime);

   status = lib$sub_times (CurrentBinaryTimePtr,
                           AgeBinaryTimePtr,
                           &ResultBinTime);
   if (status == LIB$_NEGTIM) return (0);

   status = lib$cvt_from_internal_time (&LibDeltaSeconds,
                                        &AgeSeconds,
                                        &ResultBinTime);

   if (WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (NULL, FI_LI, WATCH_MOD_PROXY, "!UL", AgeSeconds);

   return (AgeSeconds);
}

/****************************************************************************/
/*
Return the age in hours relative to the current time.
*/

int ProxyCacheAgeHours
(
unsigned long *CurrentBinaryTimePtr,
unsigned long *AgeBinaryTimePtr
)
{
   static unsigned long  LibDeltaHours = LIB$K_DELTA_HOURS;

   int  status;
   unsigned long  AgeHours;
   unsigned long  BinTime[2],
                  ResultBinTime [2];

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (NULL, FI_LI, WATCH_MOD_PROXY, "ProxyCacheAgeHours()");

   if (!AgeBinaryTimePtr[0] && !AgeBinaryTimePtr[1]) return (999999999);

   if (!CurrentBinaryTimePtr) sys$gettim (CurrentBinaryTimePtr = &BinTime);

   status = lib$sub_times (CurrentBinaryTimePtr,
                           AgeBinaryTimePtr,
                           &ResultBinTime);
   if (status == LIB$_NEGTIM) return (0);

   status = lib$cvt_from_internal_time (&LibDeltaHours,
                                        &AgeHours,
                                        &ResultBinTime);

   if (WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (NULL, FI_LI, WATCH_MOD_PROXY, "!UL", AgeHours);

   return (AgeHours);
}

/****************************************************************************/
/*
Returns true if 'TheseHours' exceed the limit implemented by the age
algorithm as applied to 'AgeHours'. See "AGE ALGORITHM" in the description of
this module.
*/

int ProxyCacheReloadAge
(
unsigned long AgeHours,
unsigned long TheseHours
)
{
   int  idx;

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (NULL, FI_LI, WATCH_MOD_PROXY,
                 "ProxyCacheReloadAge() !UL !UL", AgeHours, TheseHours);

   /* always reload if less than one hour (should never occur!) */
   if (AgeHours < 1) return (true);

   /* element one is always a default bottom-of-the-range zero */
   for (idx = 1; idx < ProxyCacheReloadListCount; idx++)
   {
      if (WATCH_MODULE(WATCH_MOD_PROXY) && WATCH_MODULE(WATCH_MOD__DETAIL))
         WatchThis (NULL, FI_LI, WATCH_MOD_PROXY, "!UL !UL !UL !UL",
                    idx, ProxyCacheReloadList[idx], AgeHours, TheseHours);
      if (AgeHours > ProxyCacheReloadList[idx]) continue;
      /* if these hours exceed the lower limit of the range then reload */
      if (TheseHours >= ProxyCacheReloadList[idx-1]) return (true);
      /* does not exceed the lower limit of the range therefore no reload */
      return (false);
   }

   /* same criteria as above */
   if (TheseHours >= ProxyCacheReloadList[idx-1]) return (true);
   return (false);
}

/*****************************************************************************/
/*
Three functions to update a cache file's "revised" and "expired" date/times. 
This function executes completely independently of any other processing, uses
it's own dynamically allocated data structure, and delivers an AST to check the
success of the operation and then deallocate the structure.  Note that both
revision and expired date/time must be updated.  The ACP action appears to
update the revision date/time to be the same as the expired if the revision is
not explicitly supplied!

This function works by 'stealing' the contents of the PROXY_CACHE_FILE_QIO
structure from the calling routine and setting the two possible channels of
that structure to zero resulting in ProxyCacheEnd() not IO$_DEACCESS and then
deassigning the channel.  That's done here after the EDT/RDT are modified!

This function uses the ACP-QIO interface detailed in the "OpenVMS I/O User's 
Reference Manual", and is probably as fast as we can get for this type of file
system functionality!
*/ 

ProxyCacheSetLastAccessed
(
PROXY_CACHE_FILE_QIO *ProxyCacheFileQioPtr,
unsigned long *RdtBinaryTimePtr,
unsigned long *EdtBinaryTimePtr
)
{
   int  status;
   char  *cptr, *sptr, *zptr;
   ATRDEF  *atptr;
   PROXY_CACHE_FILE_QIO  *cfqptr;

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (NULL, FI_LI, WATCH_MOD_PROXY,
                 "ProxyCacheSetLastAccessed() acp:!&B qio:!&B rdt:!%D edt:!%D",
                 ProxyCacheFileQioPtr->AcpChannel,
                 ProxyCacheFileQioPtr->QioChannel,
                 RdtBinaryTimePtr, EdtBinaryTimePtr);

   if (!ProxyCacheFileQioPtr->AcpChannel &&
       !ProxyCacheFileQioPtr->QioChannel)
   {
      ErrorNoticed (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
      return;
   }

   cfqptr = VmGet (sizeof(PROXY_CACHE_FILE_QIO));

   memcpy (cfqptr, ProxyCacheFileQioPtr, sizeof(PROXY_CACHE_FILE_QIO));

   cfqptr->FibDsc.dsc$w_length = sizeof(cfqptr->Fib);
   cfqptr->FibDsc.dsc$a_pointer = &cfqptr->Fib;

   /* this will prevent ProxyCacheEnd() from doing it's thing */
   ProxyCacheFileQioPtr->AcpChannel = ProxyCacheFileQioPtr->QioChannel = 0;

   atptr = &cfqptr->FileAtr;

   memset (&cfqptr->BdtBinTime, 0, 8);
   atptr->atr$w_size = sizeof(cfqptr->BdtBinTime);
   atptr->atr$w_type = ATR$C_BAKDATE;
   atptr->atr$l_addr = &cfqptr->BdtBinTime;
   atptr++;
   if (EdtBinaryTimePtr)
   {
      memcpy (&cfqptr->EdtBinTime, EdtBinaryTimePtr, 8);
      atptr->atr$w_size = sizeof(cfqptr->EdtBinTime);
      atptr->atr$w_type = ATR$C_EXPDATE;
      atptr->atr$l_addr = &cfqptr->EdtBinTime;
      atptr++;
   }
   if (RdtBinaryTimePtr)
   {
      memcpy (&cfqptr->RdtBinTime, RdtBinaryTimePtr, 8);
      atptr->atr$w_size = sizeof(cfqptr->RdtBinTime);
      atptr->atr$w_type = ATR$C_REVDATE;
      atptr->atr$l_addr = &cfqptr->RdtBinTime;
      atptr++;
   }
   atptr->atr$w_size = atptr->atr$w_type = atptr->atr$l_addr = 0;

   if (cfqptr->AcpChannel)
   {
      /* no need to deaccess this channel - but we do need to lookup the FID */
      zptr = (sptr = cfqptr->FileName) + sizeof(cfqptr->FileName)-1;
      for (cptr = ProxyCacheFileQioPtr->FileNameDsc.dsc$a_pointer;
           cptr < ProxyCacheFileQioPtr->FileNameDsc.dsc$a_pointer +
                  ProxyCacheFileQioPtr->FileNameDsc.dsc$w_length &&
           sptr < zptr;
           *sptr++ = *cptr++);
      *sptr = '\0';
      cfqptr->FileNameDsc.dsc$a_pointer = cfqptr->FileName;
      cfqptr->FileNameDsc.dsc$w_length = sptr - cfqptr->FileName;

      /* ProxyCacheSetLastAccessedAst() expects it accessed on 'QioChannel' */
      cfqptr->QioChannel = cfqptr->AcpChannel;
      cfqptr->AcpChannel = 0;

      status = sys$qio (EfnNoWait, cfqptr->QioChannel, IO$_MODIFY,
                        &cfqptr->IOsb, &ProxyCacheSetLastAccessedAst, cfqptr,
                        &cfqptr->FibDsc, &cfqptr->FileNameDsc,
                        0, 0, &cfqptr->FileAtr, 0);
      if (VMSnok (status))
      {
         /* let the AST routine handle it! */
         cfqptr->IOsb.Status = status;
         SysDclAst (&ProxyCacheSetLastAccessedAst, cfqptr);
      }
   }
   else
   {
      /* this channel has the file 'open' for read, deaccess then modify */
      status = sys$qio (EfnNoWait, cfqptr->QioChannel, IO$_DEACCESS,
                        &cfqptr->IOsb, &ProxyCacheSetLastAccessed2, cfqptr, 
                        &cfqptr->FibDsc, 0, 0, 0, 0, 0);
      if (VMSnok (status))
      {
         /* let the AST routine handle it! */
         cfqptr->IOsb.Status = status;
         SysDclAst (&ProxyCacheSetLastAccessed2, cfqptr);
      }
   }
}

/*****************************************************************************/
/*
AST from IO$_DEACCESS above.  Now do the IO$_MODIFY.
*/ 

ProxyCacheSetLastAccessed2 (PROXY_CACHE_FILE_QIO *cfqptr)

{
   int  status;

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (NULL, FI_LI, WATCH_MOD_PROXY,
                 "ProxyCacheSetLastAccessed2() !&F !&S",
                 &ProxyCacheSetLastAccessed2, cfqptr->IOsb.Status);

   if (VMSok (status = cfqptr->IOsb.Status))
      status = sys$qio (EfnNoWait, cfqptr->QioChannel, IO$_MODIFY,
                        &cfqptr->IOsb, &ProxyCacheSetLastAccessedAst, cfqptr,
                        &cfqptr->FibDsc, 0, 0, 0, &cfqptr->FileAtr, 0);
   if (VMSnok (status))
   {
      /* let the AST routine handle it! */
      cfqptr->IOsb.Status = status;
      SysDclAst (&ProxyCacheSetLastAccessedAst, cfqptr);
   }
}

/****************************************************************************/
/*
AST delivered from IO$_MODIFY above.  Just check and if required report error
status and deassign the channel.
*/

ProxyCacheSetLastAccessedAst (PROXY_CACHE_FILE_QIO *cfqptr)

{
   int  status;

   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD_PROXY))
      WatchThis (NULL, FI_LI, WATCH_MOD_PROXY,
                 "ProxyCacheSetLastAccessedAst() !&F !&S",
                 &ProxyCacheSetLastAccessedAst, cfqptr->IOsb.Status);

   if (ProxyReportCacheLog)
      if (VMSnok (cfqptr->IOsb.Status))
         WriteFaoStdout (
"%!AZ-W-PROXY, !20%D, expired timestamp update error \
(!AZ !UL)\n-!&M\n \\!#AZ\\\n",
            Utility, 0, FI_LI, cfqptr->IOsb.Status,
            cfqptr->FileNameDsc.dsc$w_length,
            cfqptr->FileNameDsc.dsc$a_pointer);

   sys$dassgn (cfqptr->QioChannel);
   VmFree (cfqptr, FI_LI);
}

/****************************************************************************/

