/*****************************************************************************/ /* 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 works intimately with this module to provide routine and reactive purging (deletion) of cache files to maintain device free space and currency of cache content. HTTP/1.1 behaviour is based on descriptions in RFC2616 (June, 1999). Although RFC2616 has been extensively referenced the WASD proxy cache should not be considered fully HTTP/1.1 compliant. It is more of an effort to not flagrantly flout HTTP/1.1 caching requirements. Simplicity has generally been chosen over RFC SHALLs (largely due to development time available and the size of the potential user base). CACHE CRITERIA -------------- HTTP/1.1 cache behaviour is based on descriptions in RFC2616 (June, 1999). The Squid FAQ (http://www.squid-cache.org/ - "How come some objects do not get cached?") has also been used as a guide to what can and can't be cached under given circumstances. 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) o does not contain an "Authorization:" header field (and the response does not contain a "Cache-Control: public" field) Successful responses WILL ONLY be cached if THE RESPONSE ... o status code 200 (success) 203 (non-authoritative) 300 (multiple choice) 301 (moved permanently) 410 (gone) o contains a "Last-Modified:" header field o one or more hours since the last modification o does not contain restrictive cache control "Pragma: no-cache" field (HTTP/1.0) "Cache-Control: no-cache, no-store, private" (/1.1) o "Vary:" header field does not contain a "*" or "accept[-...]" o does not exceed a configuration parameter in size o is HTTP/1.n compliant (i.e. not HTTP/0.9) Unsuccessful (negative) responses will be cached if ... o [ProxyCacheNegativeSeconds] is non-zero o status code 305 (use proxy) 400 (bad request) 403 (forbidden) 404 (not found) 405 (method not allowed) 414 (request URI too large) 500 (internal server error) 501 (not implemented) 502 (bad gateway) 503 (service unavailable) 504 (gateway timeout) o does not contain restrictive cache control "Pragma: no-cache" field (HTTP/1.0) "Cache-Control: no-cache, no-store, private" (/1.1) "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: WASD_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: WASD_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, /BACKUP being the last time it was loaded, /REVISED the last time the file was accessed (used to supply a request) and /EXPIRES any expiry date/time. 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. FILE HEADER DT FIELDS --------------------- (for v9.0.0ff cache files, different to the previous version - v6.0.0ff) BDT - backup contains the loaded date/time CDT - creation is used to hold a responses last-modified date/time RDT - revision contains the date/time the file was last accessed EDT - for cached responses, set to any supplied "Expires:" date/time for cached "negative" responses, the load time plus "negative" period FILE HEADER RAT FIELD --------------------- The record attributes field is used to flag whether a cached response contains a GZIP compressed response. If bit 1 (FAT$M_FORTRANCC, FAB$M_FTN) has been set during cache load then this file contains a GZIP encoded response and the client capacity to accept that cached content determines whether the file is used or not. Very simple, very cheap, a little inefficient, because if a non-GZIP client accesses the cached response it will be invalidated and reloaded. For the cost of this approach this is acceptable, and considering most proxy client populations will either be able to accept GZIP encoding or not it shouldn't be a significant issue either. 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 an HTTP/1.1 "Connection: max-age=0" or HTTP/1.0 "Pragma: no-cache" request header field is present (as is generated by many browsers 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 (CDT) date/time 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 backup (BDT) and revision (RDT) date/times are set to current. The BDT is used when assessing when the file was last loaded/validated/reloaded. The RDT provides the date/time the cache file was last accessed (read). For "negative" responses the expires (EDT) date/time is set to the current time plus the configuration directive [ProxyCacheNegativeSeconds] number of seconds. If a positve response the EDT is set to the local equivalent of any "Expires:" supplied date/time. In this way both "negative" responses can naturally expire and explicitly expired "positive" responses using the same mechanism. 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 --------------- 26-MAR-2014 MGD ProxyCacheInit() reflect cache enbled in accounting data 08-JUN-2012 JPP allow ResponseBufferSize to be >= 64k 02-JUN-2007 MGD remove 204 (no content) cached negative response (WebDAV) 03-MAY-2005 MGD "Cache-Control: max-age=0" applied only to proxy cache 26-MAR-2005 MGD allow caching of GZIP compressed responses 14-NOV-2004 MGD suppress caching for content-encoded (GZIPed) responses 19-SEP-2004 MGD bugfix; even number of bytes on a disk $QIO READVBLK 04-SEP-2004 MGD significant modifications to caching capabilities some with respect to increased HTTP/1.1 compatibility others to increase the finesse with what can and can't be cached (see ProxCacheLoadBegin()) NOTE: significant changes in file header date usage! 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 ProxyCacheSetFileDateTime() 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_V7 #undef _VMS__V6__SOURCE #define _VMS__V6__SOURCE #undef __VMS_VER #define __VMS_VER 70000000 #undef __CRTL_VER #define __CRTL_VER 70000000 #endif /* standard C header files */ #include #include #include #include /* VMS related header files */ #include #include #include #include #include #include #include #include #include #include /* application-related header files */ #include "wasd.h" #define WASD_MODULE "PROXYCACHE" #define RAT_GZIP 0x01 /* (FAT$M_FORTRANCC, FAB$M_FTN) */ /******************/ /* 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, ProxyCacheNegativeSeconds, ProxyCacheNoReloadSeconds, ProxyCacheRoutineHourOfDay; unsigned long ProxyCacheNegativeSecondsDelta [2]; char *ProxyCachePurgeListPtr, *ProxyCacheReloadListPtr, *ProxyCacheRoot; #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 int ToLowerCase[], ToUpperCase[]; extern unsigned long HttpdTime64[]; extern char ErrorSanityCheck[], HttpProtocol[], SoftwareID[]; 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 (WATCHALL, WATCH_MOD_PROXY, "ProxyCacheInit() !&B", CacheEnabled); ProxyCacheEnabled = ProxyAccountingPtr->CacheEnabled = CacheEnabled; ProxyCacheRoot = v10orPrev10(PROXY_CACHE_ROOT,-1); 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; ProxyCacheNegativeSeconds = Config.cfProxy.CacheNegativeSeconds; 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 (WATCHALL, 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 less than zero or greater than fifty-nine minutes */ if (ProxyCacheNegativeSeconds < 0 || ProxyCacheNegativeSeconds >= 59 * 60) ProxyCacheNegativeSeconds = 5 * 60; /* create a delta time of that many seconds */ ProxyCacheNegativeSecondsDelta[0] = -10000000 * ProxyCacheNegativeSeconds; ProxyCacheNegativeSecondsDelta[1] = -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 (WATCHALL, 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 (WATCHALL, 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 (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), 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; } if (tkptr->LoadFab.fab$w_ifi) { /* this function ASTs to ProxyEnd() */ ProxyCacheLoadEnd (tkptr); return; } 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 (NULL, status, NULL, FI_LI); sys$dassgn (cfqptr->QioChannel); cfqptr->QioChannel = 0; } 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 CannotCache, SetPathNoCache; int status, UrlLength; char *cptr, *sptr, *zptr, *UrlPtr; PROXY_CACHE_DESCR *pcdptr; /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), 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; CannotCache = false; if (tkptr->RequestHttpMethod != HTTP_METHOD_GET) CannotCache = true; else if (tkptr->RequestUriQueryStringPtr[0]) CannotCache = true; else if (tkptr->RequestHttpVersion == HTTP_VERSION_0_9) CannotCache = true; else if (SetPathNoCache) CannotCache = true; if (CannotCache) { if (WATCHING (tkptr, 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]) strcat (String, ", query string"); if (tkptr->RequestHttpVersion == HTTP_VERSION_0_9) strcat (String, ", HTTP/0.9"); if (SetPathNoCache) strcat (String, ", path set NOCACHE"); if (!String[0]) strcpy (String, ", ?"); WatchThis (WATCHITM(tkptr), WATCH_PROXY_CACHE, "CACHE READ not cachable: !AZ", String+2); } tkptr->CannotCache = true; InstanceGblSecIncrLong (&ProxyAccountingPtr->RequestCannotCacheCount); 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_PROXY_CACHE)) WatchThis (WATCHITM(tkptr), 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 = ProxyCacheRoot; *cptr; *sptr++ = *cptr++); if (ProxyCacheDeviceDirOrg == PROXY_CACHE_DIR_ORG_FLAT256) { *sptr++ = TOUP(tkptr->ProxyCacheMd5HexDigest[0]); *sptr++ = TOUP(tkptr->ProxyCacheMd5HexDigest[1]); cptr = tkptr->ProxyCacheMd5HexDigest + 2; } else { /* PROXY_CACHE_DIR_ORG_64X64 */ *sptr++ = TOUP(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++ = TOUP(tkptr->ProxyCacheMd5HexDigest[2]); cptr = tkptr->ProxyCacheMd5HexDigest + 3; } *sptr++ = ']'; while (*cptr) *sptr++ = TOUP(*cptr++); for (cptr = ".HTC;1"; *cptr; *sptr++ = *cptr++); *sptr = '\0'; tkptr->ProxyCacheFileNameLength = sptr - tkptr->ProxyCacheFileName; if (WATCHING (tkptr, WATCH_PROXY_CACHE)) WatchThis (WATCHITM(tkptr), 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 (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyCacheReadAcpInfo() !&F !&S !&S", &ProxyCacheReadAcpInfo, FabPtr->fab$l_sts, FabPtr->fab$l_stv); 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; if (VMSnok (status = tkptr->ParseFab.fab$l_sts)) { if (WATCHING (tkptr, WATCH_PROXY_CACHE)) WatchThis (WATCHITM(tkptr), WATCH_PROXY_CACHE, "CACHE READ parse fail !AZ !&S", tkptr->ProxyCacheFileName, status); if (ProxyReportCacheLog) FaoToStdout ("%HTTPD-W-PROXY, \ !20%D, file parse error (!AZ !UL)\n-!&M\n \\!AZ\\\n", 0, FI_LI, status, tkptr->ProxyCacheFileName); ProxyReadCacheFailed (tkptr); return; } /* copy in the parsed directory ID */ 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->BdtTime64); atptr->atr$w_type = ATR$C_BAKDATE; atptr->atr$l_addr = &cfqptr->BdtTime64; atptr++; atptr->atr$w_size = sizeof(cfqptr->CdtTime64); atptr->atr$w_type = ATR$C_CREDATE; atptr->atr$l_addr = &cfqptr->CdtTime64; atptr++; atptr->atr$w_size = sizeof(cfqptr->EdtTime64); atptr->atr$w_type = ATR$C_EXPDATE; atptr->atr$l_addr = &cfqptr->EdtTime64; atptr++; atptr->atr$w_size = sizeof(cfqptr->RdtTime64); atptr->atr$w_type = ATR$C_REVDATE; atptr->atr$l_addr = &cfqptr->RdtTime64; 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, ExpiresHours, ExpiresSeconds, LastAccessHours, LastAccessSeconds, LastLoadHours, LastLoadSeconds, LastModifiedHours, LastModifiedSeconds; unsigned long ResultTime64 [2]; PROXY_CACHE_FILE_QIO *cfqptr; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyCacheReadAcpInfoAst() !&F !&S", &ProxyCacheReadAcpInfoAst, tkptr->CacheFileQio.IOsb.Status); rqptr = tkptr->RequestPtr; /* get the file ACP pointer from the task structure */ cfqptr = &tkptr->CacheFileQio; if (VMSnok (status = cfqptr->IOsb.Status)) { /*************/ /* ACP error */ /*************/ if (WATCHING (tkptr, WATCH_PROXY_CACHE)) { if (status == SS$_NOSUCHFILE) WatchThis (WATCHITM(tkptr), WATCH_PROXY_CACHE, "CACHE READ not found !AZ", tkptr->ProxyCacheFileName); else WatchThis (WATCHITM(tkptr), WATCH_PROXY_CACHE, "CACHE ACP fail !AZ !&S", tkptr->ProxyCacheFileName, status); } if (ProxyReportCacheLog) if (status != SS$_NOSUCHFILE) FaoToStdout ( "%HTTPD-W-PROXY, !20%D, file ACP error (!AZ !UL)\n-!&M\n \\!AZ\\\n", 0, FI_LI, status, tkptr->ProxyCacheFileName); ProxyReadCacheFailed (tkptr); return; } /* note that the cache file at least existed at the time it was checked! */ tkptr->ProxyCacheFileExisted = true; /* record attributes used to indicate the content is GZIP compressed */ if (cfqptr->RecAttr.fat$b_rattrib & RAT_GZIP) { /* cache file content is GZIP encoded */ if (!rqptr->rqHeader.AcceptEncodingGzip) { /* client does not support that */ ProxyReadCacheFailed (tkptr); return; } rqptr->rqResponse.ContentIsEncodedGzip = 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 (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), 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; } LastLoadSeconds = ProxyCacheDeltaSeconds (&HttpdTime64, &cfqptr->BdtTime64); LastLoadHours = LastLoadSeconds / 3600; LastModifiedSeconds = ProxyCacheDeltaSeconds (&HttpdTime64, &cfqptr->CdtTime64); LastModifiedHours = LastModifiedSeconds / 3600; if (WATCHING (tkptr, WATCH_PROXY_CACHE)) { LastAccessSeconds = ProxyCacheDeltaSeconds (&HttpdTime64, &cfqptr->RdtTime64); LastAccessHours = LastAccessSeconds / 3600; if (QUAD_ZERO(cfqptr->EdtTime64)) ExpiresHours = ExpiresSeconds = 0; else { ExpiresSeconds = ProxyCacheDeltaSeconds (&cfqptr->EdtTime64, &HttpdTime64); ExpiresHours = ExpiresSeconds / 3600; } WatchThis (tkptr->RequestPtr, WATCH_PROXY_CACHE, "CACHE READ gzip:!AZ noreload:!UL/!UL negative:!SL/!UL modified:!UL(!UL) \ loaded:!UL(!UL) expires:!UL(!UL) accessed:!UL(!UL) (!UL time!%s) !AZ", (cfqptr->RecAttr.fat$b_rattrib & RAT_GZIP) ? "yes" : "no", LastLoadSeconds, ProxyCacheNoReloadSeconds, ExpiresSeconds, ProxyCacheNegativeSeconds, LastModifiedHours, LastModifiedHours/24, LastLoadHours, LastLoadHours/24, ExpiresHours, ExpiresHours/24, LastAccessHours, LastAccessHours/24, cfqptr->AscDates.RevisionCount, tkptr->ProxyCacheFileName); } if (QUAD_ZERO(cfqptr->BdtTime64)) { /********************/ /* from v6.0 cache! */ /********************/ if (WATCHING (tkptr, WATCH_PROXY_CACHE)) WatchThis (WATCHITM(tkptr), WATCH_PROXY_CACHE, "CACHE READ v6.0 RELOAD"); ProxyReadCacheFailed (tkptr); return; } /******************/ /* check currency */ /******************/ if (ProxyCacheAllowReload && LastLoadSeconds >= ProxyCacheNoReloadSeconds) { if (tkptr->RequestNotFromCache) { /******************/ /* not-from-cache */ /******************/ if (WATCHING (tkptr, WATCH_PROXY_CACHE)) WatchThis (WATCHITM(tkptr), WATCH_PROXY_CACHE, "CACHE READ not-from-cache RELOAD"); ProxyReadCacheFailed (tkptr); return; } if (rqptr->rqHeader.CacheControlMaxAge && (LastLoadSeconds > rqptr->rqHeader.CacheControlMaxAge || LastModifiedSeconds > rqptr->rqHeader.CacheControlMaxAge)) { /***********/ /* max-age */ /***********/ if (WATCHING (tkptr, WATCH_PROXY_CACHE)) WatchThis (WATCHITM(tkptr), WATCH_PROXY_CACHE, "CACHE READ max-age RELOAD"); ProxyReadCacheFailed (tkptr); return; } } if (QUAD_NOT_ZERO(cfqptr->EdtTime64)) { /************/ /* expired? */ /************/ status = lib$sub_times (&cfqptr->EdtTime64, &HttpdTime64, &ResultTime64); if (VMSnok(status)) { /* status will be LIB$_NEGTIM if expired */ if (WATCHING (tkptr, WATCH_PROXY_CACHE)) WatchThis (WATCHITM(tkptr), WATCH_PROXY_CACHE, "CACHE READ expired RELOAD"); ProxyReadCacheFailed (tkptr); return; } } if (ProxyCacheReloadLastModified (LastModifiedHours, LastLoadHours)) { /***********************/ /* last-modified limit */ /***********************/ if (WATCHING (tkptr, WATCH_PROXY_CACHE)) WatchThis (WATCHITM(tkptr), WATCH_PROXY_CACHE, "CACHE READ last-modified limit RELOAD"); ProxyReadCacheFailed (tkptr); return; } /* now it's a read! */ InstanceGblSecIncrLong (&ProxyAccountingPtr->CacheReadCount); if (QUAD_NOT_ZERO(rqptr->rqTime.IfModifiedSinceTime64) && !tkptr->RequestNotFromCache) { /*********************/ /* if modified since */ /*********************/ DICT_ENTRY_STRUCT *denptr; status = HttpIfModifiedSince (rqptr, &cfqptr->CdtTime64, -1); if (VMSnok (status)) { /* status will be LIB$_NEGTIM if not modified/sent */ if (WATCHING (tkptr, WATCH_PROXY_CACHE)) WatchThis (WATCHITM(tkptr), WATCH_PROXY_CACHE, "CACHE READ not modified"); /* update the last-accessed (RDT) time */ ProxyCacheSetFileDateTime (&tkptr->CacheFileQio, &cfqptr->BdtTime64, &cfqptr->CdtTime64, &cfqptr->EdtTime64, &HttpdTime64); tkptr->ResponseStatusCode = 304; InstanceGblSecIncrLong (&ProxyAccountingPtr->CacheRead304Count); /* the 304 header length */ if (denptr = ResponseDictHeader (rqptr)) tkptr->ProxyCacheReadBytes = DICT_GET_VALUE_LEN(denptr); else tkptr->ProxyCacheReadBytes = 0; 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 (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), 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_PROXY_CACHE)) WatchThis (WATCHITM(tkptr), WATCH_PROXY_CACHE, "CACHE READ access fail !AZ !&S", tkptr->ProxyCacheFileName, status); if (ProxyReportCacheLog) FaoToStdout ("%HTTPD-W-PROXY, \ !20%D, file open error (!AZ !UL)\n-!&M\n \\!AZ\\\n", 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; tkptr->ProxyCacheReadBytes = 0; /* fudge the first network write status */ tkptr->RequestPtr->NetIoPtr->WriteStatus = 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, BodyLength, BufferSize, DataLength; char *BodyPtr, *DataPtr; PROXY_CACHE_DESCR *pcdptr; PROXY_CACHE_FILE_QIO *cfqptr; PROXY_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(rqptr), WATCH_MOD_PROXY, "ProxyCacheReadNext()"); tkptr = rqptr->ProxyTaskPtr; if (VMSnok (status = rqptr->NetIoPtr->WriteStatus)) { /******************************/ /* client network write error */ /******************************/ if (WATCHING (tkptr, WATCH_PROXY_CACHE)) WatchThis (WATCHITM(tkptr), WATCH_PROXY_CACHE, "CACHE READ client write fail !&S", status); ProxyCacheEnd (tkptr); return; } /*********************/ /* first/next blocks */ /*********************/ cfqptr = &tkptr->CacheFileQio; if (cfqptr->BlockNumber) { if (!tkptr->ResponseHeaderSent) { tkptr->ResponseHeaderSent = true; DataPtr = cfqptr->BufferPtr; DataLength = cfqptr->BufferCount; pcdptr = (PROXY_CACHE_DESCR*)DataPtr; BodyPtr = DataPtr + sizeof(PROXY_CACHE_DESCR) + pcdptr->UrlLength + pcdptr->HeaderLength; BodyLength = DataLength - sizeof(PROXY_CACHE_DESCR) - pcdptr->UrlLength - pcdptr->HeaderLength; if (BodyLength) { tkptr->ProxyCacheReadBytes += BodyLength; NetWrite (rqptr, &ProxyCacheReadNext, BodyPtr, BodyLength); return; } } if (cfqptr->EndOfFile) { cfqptr->EndOfFile = false; cfqptr->IOsb.Status = SS$_ENDOFFILE; ProxyCacheReadNextAst (tkptr); return; } /* 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 & 0x7ffffe00; 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 (WATCHMOD (rqptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(rqptr), 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, BodyLength, DataLength; char *BodyPtr, *DataPtr, *HeaderPtr, *UrlPtr; REQUEST_STRUCT *rqptr; PROXY_CACHE_DESCR *pcdptr; PROXY_CACHE_FILE_QIO *cfqptr; /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), 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_PROXY_CACHE)) WatchThis (WATCHITM(tkptr), WATCH_PROXY_CACHE, "CACHE READ eof !UL bytes", tkptr->ProxyCacheReadBytes - tkptr->RebuiltResponseLength); /* update the last-accessed (RDT) time */ ProxyCacheSetFileDateTime (&tkptr->CacheFileQio, &cfqptr->BdtTime64, &cfqptr->CdtTime64, &cfqptr->EdtTime64, &HttpdTime64); ProxyCacheEnd (tkptr); return; } if (WATCHING (tkptr, WATCH_PROXY_CACHE)) WatchThis (WATCHITM(tkptr), WATCH_PROXY_CACHE, "CACHE READ fail !&S", 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; DataPtr = cfqptr->BufferPtr; DataLength = cfqptr->BufferCount; if (cfqptr->BlockNumber == 1) { /****************/ /* initial read */ /****************/ if (!cfqptr->BufferCount) { /* in between the ACP-QIO and opening it has started reloading */ ErrorNoticed (rqptr, 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) */ pcdptr = (PROXY_CACHE_DESCR*)DataPtr; if (pcdptr->CacheVersion != PROXY_CACHE_FILE_VERSION) { /***************/ /* delete file */ /***************/ if (WATCHING (tkptr, WATCH_PROXY_CACHE)) WatchThis (WATCHITM(tkptr), 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 (rqptr, status, NULL, FI_LI); rqptr->rqResponse.HttpStatus = 500; ErrorGeneral (rqptr, ErrorProxyCacheVersion, FI_LI); ProxyCacheEnd (tkptr); return; } /******************/ /* rebuild header */ /******************/ /* cache file description components */ UrlPtr = DataPtr + sizeof(PROXY_CACHE_DESCR); HeaderPtr = UrlPtr + pcdptr->UrlLength; status = ProxyResponseRebuild (tkptr, HeaderPtr, pcdptr->HeaderLength); if (VMSnok (status)) { ProxyReadCacheFailed (tkptr); return; } if (tkptr->RequestHttpVersion == HTTP_VERSION_1_0 && tkptr->ResponseHttpVersion == HTTP_VERSION_1_1 && tkptr->ResponseTransferEncodingChunked) { /* probably won't understand it */ if (WATCHING (tkptr, WATCH_PROXY_CACHE)) WatchThis (WATCHITM(tkptr), WATCH_PROXY_CACHE, "CACHE READ downgrade HTTP protocol RELOAD"); ProxyReadCacheFailed (tkptr); return; } if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) { BodyPtr = HeaderPtr + pcdptr->HeaderLength; BodyLength = DataLength - sizeof(PROXY_CACHE_DESCR) - pcdptr->UrlLength - pcdptr->HeaderLength; WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ver:!XL url:!UL hdr:!UL http:!UL len:!SL bod:!UL eof:!&B", pcdptr->CacheVersion, pcdptr->UrlLength, pcdptr->HeaderLength, tkptr->ResponseStatusCode, tkptr->ResponseContentLength, BodyLength, cfqptr->EndOfFile); WatchDataFormatted ("{!UL}!-!#AZ\n{!UL}!-!#AZ\n{!UL}!-!#AZ\n", pcdptr->UrlLength, UrlPtr, pcdptr->HeaderLength, HeaderPtr, tkptr->RebuiltResponseLength, tkptr->RebuiltResponsePtr); } DataPtr = tkptr->RebuiltResponsePtr; DataLength = tkptr->RebuiltResponseLength; } 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 }; BOOL CannotCache, CacheNegative, CachePositive; int status, ExpiresHours, ExpiresSeconds, LastModifiedHours, LastModifiedSeconds, MaxAge; unsigned int AllocationQuantity; PROXY_CACHE_DESCR *pcdptr; REQUEST_STRUCT *rqptr; /*********/ /* begin */ /*********/ if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyCacheLoadBegin()"); rqptr = tkptr->RequestPtr; if (!ProxyCacheFreeSpaceAvailable) { /* no file system space available for caching purposes */ if (WATCHING (tkptr, WATCH_PROXY_CACHE)) { WatchThis (WATCHITM(tkptr), WATCH_PROXY_CACHE, "CACHE LOAD insufficient device space (!UL% max)", ProxyCacheDeviceMaxPercent); ProxyMaintWatchDeviceStats (); } return (STS$K_ERROR); } if (QUAD_ZERO(tkptr->ResponseLastModified)) tkptr->ProxyCacheLastModifiedHours = LastModifiedHours = LastModifiedSeconds = 0; else { LastModifiedSeconds = ProxyCacheDeltaSeconds (&HttpdTime64, &tkptr->ResponseLastModifiedTime64); tkptr->ProxyCacheLastModifiedHours = LastModifiedHours = LastModifiedSeconds / 3600; } /* 's-maxage' takes precedence over 'max-age', and over any 'expires' */ if ((MaxAge = tkptr->ResponseCacheControlSMaxAge) || (MaxAge = tkptr->ResponseCacheControlMaxAge)) { static long AddendZero = 0, OneSecondDelta = -10000000; unsigned long DeltaTime64 [2]; /* ensure it doesn't appear negative, still plenty of range left */ MaxAge &= 0x7fffffff; status = lib$emul (&MaxAge, &OneSecondDelta, &AddendZero, &DeltaTime64); if (VMSnok (status)) { ErrorNoticed (rqptr, status, NULL, FI_LI); PUT_ZERO_QUAD (DeltaTime64); } else { status = lib$add_times (&HttpdTime64, &DeltaTime64, &tkptr->ResponseExpiresTime64); if (VMSnok (status)) { ErrorNoticed (rqptr, status, NULL, FI_LI); PUT_ZERO_QUAD (tkptr->ResponseExpiresTime64); } } if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "!UL !%D", MaxAge, &tkptr->ResponseExpiresTime64); } if (QUAD_ZERO(tkptr->ResponseExpiresTime64)) ExpiresHours = ExpiresSeconds = 0; else { ExpiresSeconds = ProxyCacheDeltaSeconds (&tkptr->ResponseExpiresTime64, &HttpdTime64); ExpiresHours = ExpiresSeconds / 3600; } /**************/ /* cacheable? */ /**************/ CacheNegative = CachePositive = false; if (tkptr->ResponseStatusCode == 200 || tkptr->ResponseStatusCode == 203 || tkptr->ResponseStatusCode == 300 || tkptr->ResponseStatusCode == 301 || tkptr->ResponseStatusCode == 410) { /* can cache this success response */ CachePositive = true; } else if (ProxyCacheNegativeSeconds && (tkptr->ResponseStatusCode == 305 || tkptr->ResponseStatusCode == 400 || tkptr->ResponseStatusCode == 403 || tkptr->ResponseStatusCode == 404 || tkptr->ResponseStatusCode == 405 || tkptr->ResponseStatusCode == 414 || tkptr->ResponseStatusCode == 500 || tkptr->ResponseStatusCode == 502 || tkptr->ResponseStatusCode == 503 || tkptr->ResponseStatusCode == 504)) { /* can cache this non-success (negative) response */ CacheNegative = true; if (QUAD_ZERO(tkptr->ResponseExpiresTime64)) { /* negative response did not explicitly specify an expiry/max-age */ status = lib$add_times (&HttpdTime64, &ProxyCacheNegativeSecondsDelta, &tkptr->ResponseExpiresTime64); if (VMSnok (status)) { ErrorNoticed (rqptr, status, NULL, FI_LI); PUT_ZERO_QUAD (tkptr->ResponseExpiresTime64); } } } if (CachePositive || CacheNegative) { /* slightly Japanese but never mind */ CannotCache = false; if (tkptr->ResponseNoCache) CannotCache = true; else if (CachePositive && LastModifiedHours < 1) CannotCache = true; else if (CachePositive && QUAD_NOT_ZERO(tkptr->ResponseExpires) && ExpiresHours < 1) CannotCache = true; else if (tkptr->ResponseCacheControlMaxAgeZero) CannotCache = true; else if (tkptr->ResponseContentEncodingUnknown) CannotCache = true; else if (tkptr->ResponseContentLength >= 0 && (tkptr->ResponseContentLength >> 10) + 1 > ProxyCacheFileKBytesMax) CannotCache = true; else if (tkptr->RequestAuthorization && !tkptr->ResponseCacheControlPublic) CannotCache = true; else if (tkptr->ResponseVaryUnsupported) CannotCache = true; else if (tkptr->ResponseContentTypeMultipart) CannotCache = true; else if (tkptr->ResponseHttpVersion == HTTP_VERSION_0_9) CannotCache = true; } else CannotCache = true; if (CannotCache) { /******************/ /* not cache-able */ /******************/ if (WATCHING (tkptr, WATCH_PROXY_CACHE)) { char String [256]; String[0] = '\0'; if (!(CachePositive || CacheNegative)) sprintf (String, ", status %d", tkptr->ResponseStatusCode); if (tkptr->ResponseNoCache) strcat (String, ", no-cache"); if (CachePositive) { if (QUAD_ZERO(tkptr->ResponseLastModified)) strcat (String, ", no last-modified"); else if (LastModifiedHours < 1) strcat (String, ", last-modified < 1hr"); if (QUAD_NOT_ZERO(tkptr->ResponseExpires) && ExpiresHours < 1) strcat (String, ", expires < 1hr"); } if (tkptr->ResponseCacheControlMaxAgeZero) strcat (String, ", max-age=0"); if (tkptr->ResponseContentEncodingUnknown) strcat (String, ", content-encoding"); if (tkptr->ResponseContentLength >= 0 && (tkptr->ResponseContentLength >> 10) + 1 > ProxyCacheFileKBytesMax) { char *cptr; for (cptr = String; *cptr; cptr++); sprintf (cptr, ", content-length %d > %d kB", (tkptr->ResponseContentLength >> 10) + 1, ProxyCacheFileKBytesMax); } if (tkptr->RequestAuthorization && !tkptr->ResponseCacheControlPublic) strcat (String, ", request authorization"); if (tkptr->ResponseVaryUnsupported) strcat (String, ", unsupported vary"); if (tkptr->ResponseContentTypeMultipart) strcat (String, ", multipart"); if (tkptr->ResponseHttpVersion == HTTP_VERSION_0_9) strcat (String, ", HTTP/0.9"); if (!String[0]) strcpy (String, ", ?"); WatchThis (WATCHITM(tkptr), WATCH_PROXY_CACHE, "CACHE LOAD not cachable: !AZ", String + 2); } /* 'tkptr->ProxyCacheSuitable' checked method and query string */ if (tkptr->ResponseStatusCode == 304) { if (tkptr->ProxyCacheFileExisted) { /*****************************/ /* not modified, leave as-is */ /*****************************/ if (WATCHING (tkptr, WATCH_PROXY_CACHE)) WatchThis (WATCHITM(tkptr), WATCH_PROXY_CACHE, "CACHE LOAD not modified !AZ", tkptr->ProxyCacheFileName); /* update loaded (BDT) and last accessed (RDT) times to current */ ProxyCacheSetFileDateTime (&tkptr->CacheFileQio, &HttpdTime64, &tkptr->CacheFileQio.CdtTime64, &tkptr->CacheFileQio.EdtTime64, &HttpdTime64); 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_PROXY_CACHE)) WatchThis (WATCHITM(tkptr), 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)) FaoToStdout ("%HTTPD-W-PROXY, \ !20%D, file erase error (!AZ !UL)\n-!&M\n \\!AZ\\\n", 0, FI_LI, status, tkptr->ProxyCacheFileName); } tkptr->CannotCache = true; InstanceGblSecIncrLong (&ProxyAccountingPtr->ResponseCannotCacheCount); return (STS$K_ERROR); } if (MATCH6 (&tkptr->ParseNam.nam$w_did, &EmptyDid)) { /**********************************/ /* 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 there was no last-modified supplied with the (negative) response */ if (QUAD_ZERO(tkptr->ResponseLastModifiedTime64)) PUT_QUAD_QUAD (HttpdTime64, tkptr->ResponseLastModifiedTime64); if (WATCHING (tkptr, WATCH_PROXY_CACHE)) { if (tkptr->ResponseContentLength >= 0) WatchThis (WATCHITM(tkptr), WATCH_PROXY_CACHE, "CACHE !&?RE\r\rLOAD !AZ content !UL bytes (!UL blocks)", tkptr->ProxyCacheFileExisted, tkptr->ProxyCacheFileName, tkptr->ResponseContentLength, AllocationQuantity); else WatchThis (WATCHITM(tkptr), 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; /* record attributes used to indicate the content is GZIP compressed */ if (tkptr->ResponseContentEncodingGzip) tkptr->LoadFab.fab$b_rat = RAT_GZIP; else 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; /* last loaded (BDT) */ PUT_QUAD_QUAD (HttpdTime64, &tkptr->LoadXabDat.xab$q_bdt); /* last modified (CDT) */ PUT_QUAD_QUAD (tkptr->ResponseLastModifiedTime64, &tkptr->LoadXabDat.xab$q_cdt); /* expires at (EDT) */ PUT_QUAD_QUAD (tkptr->ResponseExpiresTime64, &tkptr->LoadXabDat.xab$q_edt); /* last accessed (RDT) will be set at $CLOSE */ /* 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 (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), 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_PROXY_CACHE)) WatchThis (WATCHITM(tkptr), WATCH_PROXY_CACHE, "CACHE CREATE fail !&S", status); if (ProxyReportCacheLog) FaoToStdout ("%HTTPD-W-PROXY, \ !20%D, file create error (!AZ !UL)\n-!&M\n \\!AZ\\\n", 0, FI_LI, status, tkptr->ProxyCacheFileName); /* explicitly call the function to continue transaction with client */ ProxyResponseNetWrite (tkptr); return; } /* copy the directory and file IDs returned from the file load $CREATE */ memcpy (&tkptr->CacheFileQio.Fib.fib$w_did, &tkptr->ParseNam.nam$w_did, 6); memcpy (&tkptr->CacheFileQio.Fib.fib$w_fid, &tkptr->ParseNam.nam$w_fid, 6); 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_PROXY_CACHE)) WatchThis (WATCHITM(tkptr), WATCH_PROXY_CACHE, "CACHE CONNECT fail !&S", status); if (ProxyReportCacheLog) FaoToStdout ("%HTTPD-W-PROXY, \ !20%D, file connect error (!AZ !UL)\n-!&M\n \\!AZ\\\n", 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 (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), 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_PROXY_CACHE)) WatchThis (WATCHITM(tkptr), 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)) FaoToStdout ("%HTTPD-W-PROXY, \ !20%D, file delete-on-close error (!AZ !UL)\n-!&M\n \\!AZ\\\n", 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 (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), 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_PROXY_CACHE)) WatchThis (WATCHITM(tkptr), WATCH_PROXY_CACHE, "CACHE WRITE fail !&S", status); if (ProxyReportCacheLog) FaoToStdout ("%HTTPD-W-PROXY, \ !20%D, file write error (!AZ !UL)\n-!&M\n \\!AZ\\\n", 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 (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), WATCH_MOD_PROXY, "ProxyCacheLoadEnd()"); ClientWriteStatus = SS$_NORMAL; if (tkptr->RequestPtr) if (VMSnok (tkptr->RequestPtr->NetIoPtr->WriteStatus)) ClientWriteStatus = tkptr->RequestPtr->NetIoPtr->WriteStatus; ProxyReadStatus = tkptr->NetIoPtr->ReadStatus; /* 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_PROXY_CACHE)) { if (VMSnok (ProxyReadStatus)) WatchThis (WATCHITM(tkptr), WATCH_PROXY_CACHE, "CACHE LOAD FAIL server read !&S", ProxyReadStatus); else if (VMSnok (ClientWriteStatus)) WatchThis (WATCHITM(tkptr), WATCH_PROXY_CACHE, "CACHE LOAD FAIL client write !&S", ClientWriteStatus); else if (tkptr->ResponseContentLength >= 0) WatchThis (WATCHITM(tkptr), WATCH_PROXY_CACHE, "CACHE LOAD FAIL received:!UL expecting:!UL", tkptr->ResponseBodyLength, tkptr->ResponseContentLength); else WatchThis (WATCHITM(tkptr), WATCH_PROXY_CACHE, "CACHE LOAD FAIL reason unknown"); } if (ProxyReportCacheLog) { if (VMSnok (ProxyReadStatus)) FaoToStdout ("%HTTPD-W-PROXY, \ !20%D, cache load read fail (!AZ !UL)\n-!&M\n \\!AZ\\\n", 0, FI_LI, ProxyReadStatus, tkptr->ProxyCacheFileName); else if (VMSnok (ClientWriteStatus)) FaoToStdout ("%HTTPD-W-PROXY, \ !20%D, cache load write fail (!AZ !UL)\n-!&M\n \\!AZ\\\n", 0, FI_LI, ClientWriteStatus, tkptr->ProxyCacheFileName); else if (tkptr->ResponseContentLength >= 0) FaoToStdout ("%HTTPD-W-PROXY, \ !20%D, cache load fail, received:!UL expecting:!UL (!AZ !UL)\n\ \\!AZ\\\n", 0, tkptr->ResponseBodyLength, tkptr->ResponseContentLength, FI_LI, tkptr->ProxyCacheFileName); else FaoToStdout ("%HTTPD-W-PROXY, \ !20%D, cache load fail (!AZ !UL)\n \\!AZ\\\n", 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_PROXY_CACHE)) { char ExpiresAt [64]; if (QUAD_ZERO(tkptr->ResponseExpiresTime64)) strcpy (ExpiresAt, "no expiry"); else FaoToBuffer (ExpiresAt, sizeof(ExpiresAt), NULL, "expires !20%D", &tkptr->ResponseExpiresTime64); WatchThis (WATCHITM(tkptr), WATCH_PROXY_CACHE, "CACHE LOAD OK !AZ, !UL bytes (!UL blocks), \ last-modified !UL/!UL (hrs/days), !AZ", tkptr->ResponseContentEncodingGzip ? "gzip encoded" : "unencoded", tkptr->ResponseBodyLength, ((tkptr->ResponseHeaderLength + tkptr->ResponseBodyLength) >> 9) + 1, tkptr->ProxyCacheLastModifiedHours, tkptr->ProxyCacheLastModifiedHours/24, ExpiresAt); } /* 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; char *cptr; PROXY_TASK *tkptr; /*********/ /* begin */ /*********/ tkptr = FabPtr->fab$l_ctx; if (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), 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)) FaoToStdout ("%HTTPD-W-PROXY, \ !20%D, file delete-on-close error (!AZ !UL)\n-!&M\n \\!AZ\\\n", 0, FI_LI, status, tkptr->ProxyCacheFileName); /* if there was no load write error and so not set delete-on-close */ if (!(tkptr->ParseFab.fab$l_fop & FAB$M_DLT)) { /* set file header date/times the way *we* want! */ ProxyCacheSetFileDateTime (&tkptr->CacheFileQio, &HttpdTime64, &tkptr->ResponseLastModifiedTime64, &tkptr->ResponseExpiresTime64, &HttpdTime64); } 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 (WATCHMOD (tkptr, WATCH_MOD_PROXY)) WatchThis (WATCHITM(tkptr), 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)) FaoToStdout ("%HTTPD-W-PROXY, \ !20%D, file delete-on-close error (!AZ !UL)\n-!&M\n \\!AZ\\\n", 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 (WATCHALL, 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 (WATCHALL, 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_PROXY_CACHE)) { if (VMSnok (status)) WatchThis (WATCHITM(tkptr), WATCH_PROXY_CACHE, "CACHE DIRECTORY create fail !AZ !&S", DirectoryName, status); else WatchThis (WATCHITM(tkptr), WATCH_PROXY_CACHE, "CACHE DIRECTORY created !AZ", DirectoryName); } if (ProxyReportCacheLog) if (VMSnok (status)) FaoToStdout ("%HTTPD-W-PROXY, \ !20%D, directory create error (!AZ !UL)\n-!&M\n \\!AZ\\\n", 0, FI_LI, status, DirectoryName); return (status); } /****************************************************************************/ /* Return the age in seconds relative to the current time. */ int ProxyCacheDeltaSeconds ( unsigned long *CurrentTime6464Ptr, unsigned long *AgeTime64Ptr ) { static unsigned long LibDeltaSeconds = LIB$K_DELTA_SECONDS; int status; unsigned long DeltaSeconds; unsigned long ResultTime64 [2]; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_PROXY)) WatchThis (WATCHALL, WATCH_MOD_PROXY, "ProxyCacheDeltaSeconds()"); if (QUAD_ZERO(AgeTime64Ptr)) return (999999999); if (!CurrentTime6464Ptr) CurrentTime6464Ptr = &HttpdTime64; status = lib$sub_times (CurrentTime6464Ptr, AgeTime64Ptr, &ResultTime64); if (status == LIB$_NEGTIM) return (0); status = lib$cvt_from_internal_time (&LibDeltaSeconds, &DeltaSeconds, &ResultTime64); if (WATCH_MODULE(WATCH_MOD_PROXY)) WatchThis (WATCHALL, WATCH_MOD_PROXY, "!UL", DeltaSeconds); return (DeltaSeconds); } /****************************************************************************/ /* Returns true if 'TheseHours' exceed the limit implemented by the age algorithm as applied to 'LastModHours'. See "AGE ALGORITHM" in the description of this module. */ int ProxyCacheReloadLastModified ( unsigned long LastModHours, unsigned long TheseHours ) { int idx; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_PROXY)) WatchThis (WATCHALL, WATCH_MOD_PROXY, "ProxyCacheReloadLastModified() !UL !UL", LastModHours, TheseHours); /* 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 (WATCHALL, WATCH_MOD_PROXY, "!UL !UL !UL !UL", idx, ProxyCacheReloadList[idx], LastModHours, TheseHours); if (LastModHours > 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); } /*****************************************************************************/ /* Update a file header's BDT, CDT, ETD and RDT date/time fields. 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. 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 date/times are modified! This function uses the ACP-QIO interface detailed in the "OpenVMS I/O User's Reference Manual". */ ProxyCacheSetFileDateTime ( PROXY_CACHE_FILE_QIO *ProxyCacheFileQioPtr, unsigned long *BdtTime64Ptr, /* last loaded at */ unsigned long *CdtTime64Ptr, /* last modified at */ unsigned long *EdtTime64Ptr, /* expires at */ unsigned long *RdtTime64Ptr /* last modified at */ ) { int status; char *cptr, *sptr, *zptr; ATRDEF *atptr; PROXY_CACHE_FILE_QIO *cfqptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_PROXY)) WatchThis (WATCHALL, WATCH_MOD_PROXY, "ProxyCacheSetFileDateTime() acp:!&B qio:!&B bdt:!%D cdt:!%D rdt:!%D edt:!%D", ProxyCacheFileQioPtr->AcpChannel, ProxyCacheFileQioPtr->QioChannel, BdtTime64Ptr, CdtTime64Ptr, RdtTime64Ptr, EdtTime64Ptr); if (!ProxyCacheFileQioPtr->AcpChannel && !ProxyCacheFileQioPtr->QioChannel) { ErrorNoticed (NULL, 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; PUT_QUAD_QUAD (BdtTime64Ptr, cfqptr->BdtTime64); atptr->atr$w_size = sizeof(cfqptr->BdtTime64); atptr->atr$w_type = ATR$C_BAKDATE; atptr->atr$l_addr = &cfqptr->BdtTime64; atptr++; PUT_QUAD_QUAD (CdtTime64Ptr, cfqptr->CdtTime64); atptr->atr$w_size = sizeof(cfqptr->CdtTime64); atptr->atr$w_type = ATR$C_CREDATE; atptr->atr$l_addr = &cfqptr->CdtTime64; atptr++; PUT_QUAD_QUAD (EdtTime64Ptr, cfqptr->EdtTime64); atptr->atr$w_size = sizeof(cfqptr->EdtTime64); atptr->atr$w_type = ATR$C_EXPDATE; atptr->atr$l_addr = &cfqptr->EdtTime64; atptr++; PUT_QUAD_QUAD (RdtTime64Ptr, cfqptr->RdtTime64); atptr->atr$w_size = sizeof(cfqptr->RdtTime64); atptr->atr$w_type = ATR$C_REVDATE; atptr->atr$l_addr = &cfqptr->RdtTime64; atptr++; atptr->atr$w_size = atptr->atr$w_type = atptr->atr$l_addr = 0; if (cfqptr->AcpChannel) { /* ProxyCacheSetFileDateTimeAst() expects it accessed on 'QioChannel' */ cfqptr->QioChannel = cfqptr->AcpChannel; cfqptr->AcpChannel = 0; status = sys$qio (EfnNoWait, cfqptr->QioChannel, IO$_MODIFY, &cfqptr->IOsb, &ProxyCacheSetFileDateTimeAst, cfqptr, &cfqptr->FibDsc, &cfqptr->FileNameDsc, 0, 0, &cfqptr->FileAtr, 0); if (VMSnok (status)) { /* let the AST routine handle it! */ cfqptr->IOsb.Status = status; SysDclAst (&ProxyCacheSetFileDateTimeAst, cfqptr); } } else if (cfqptr->QioChannel) { /* this channel has the file 'open' for read, deaccess then modify */ status = sys$qio (EfnNoWait, cfqptr->QioChannel, IO$_DEACCESS, &cfqptr->IOsb, &ProxyCacheSetFileDateTime2, cfqptr, &cfqptr->FibDsc, 0, 0, 0, 0, 0); if (VMSnok (status)) { /* let the AST routine handle it! */ cfqptr->IOsb.Status = status; SysDclAst (&ProxyCacheSetFileDateTime2, cfqptr); } } else { /* hmmm, no channel assigned from ProxyCacheReadAcpInfo() */ cfqptr->IOsb.Status = SS$_BUGCHECK; SysDclAst (&ProxyCacheSetFileDateTimeAst, cfqptr); } } /*****************************************************************************/ /* AST from IO$_DEACCESS above. Now do the IO$_MODIFY. */ ProxyCacheSetFileDateTime2 (PROXY_CACHE_FILE_QIO *cfqptr) { int status; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_PROXY)) WatchThis (WATCHALL, WATCH_MOD_PROXY, "ProxyCacheSetFileDateTime2() !&F !&S", &ProxyCacheSetFileDateTime2, cfqptr->IOsb.Status); if (VMSok (status = cfqptr->IOsb.Status)) status = sys$qio (EfnNoWait, cfqptr->QioChannel, IO$_MODIFY, &cfqptr->IOsb, &ProxyCacheSetFileDateTimeAst, cfqptr, &cfqptr->FibDsc, 0, 0, 0, &cfqptr->FileAtr, 0); if (VMSnok (status)) { /* let the AST routine handle it! */ cfqptr->IOsb.Status = status; SysDclAst (&ProxyCacheSetFileDateTimeAst, cfqptr); } } /****************************************************************************/ /* AST delivered from IO$_MODIFY above. Just check and if required report error status and deassign the channel. */ ProxyCacheSetFileDateTimeAst (PROXY_CACHE_FILE_QIO *cfqptr) { int status; /*********/ /* begin */ /*********/ if (WATCH_MODULE(WATCH_MOD_PROXY)) WatchThis (WATCHALL, WATCH_MOD_PROXY, "ProxyCacheSetFileDateTimeAst() !&F !&S", &ProxyCacheSetFileDateTimeAst, cfqptr->IOsb.Status); if (ProxyReportCacheLog) if (VMSnok (cfqptr->IOsb.Status)) FaoToStdout ("%HTTPD-W-PROXY, \ !20%D, expired timestamp update error (!AZ !UL)\n-!&M\n \ \\!#AZ\\\n", 0, FI_LI, cfqptr->IOsb.Status, cfqptr->FileNameDsc.dsc$w_length, cfqptr->FileNameDsc.dsc$a_pointer); sys$dassgn (cfqptr->QioChannel); VmFree (cfqptr, FI_LI); } /****************************************************************************/