/*****************************************************************************
/*
                                   VM.c

Virtual memory support functions for HTTPd using the LIB$*_VM_* routines.

The reason for not using generic memory routines such as calloc() and malloc()
for frequently requested and then disposed of dynamic memory is of course
efficiency. The theory goes like this ... tailoring VMS memory zones against
the expected request profile for it's use will result in faster and more
efficient memory management. An added, and not inconsequential bonus, is the
additional integrity checking the library routines can provide.

A general zone is created for non-specific allocations.

Zones for fixed sized memory blocks are created for the HTTP/2 and request
structures.

An individual zone is created for each HTTP/2 connection's and request's heap. 
The overhead of zone creation will be offset by the lower overhead required for
managing a smaller and short-lived collection of chunks, and the documentation
specifically states that reseting or deleting a memory zone is a more efficient
method of disposing of virtual memory than individually freeing each chunk.

The request zone has the initial allocation "tuned".  After an initial sample
of requests the allocation becomes the running "average" of those requests
(rounded into mutliples of the hard-wired chunk size).  This averaging persists
for a period (number of requests) before being reset to the initial sample
state again.

Logical names may be used to tailor the number of pages allocated for the
initial and extend values of the heap zones.  The logical name value should be
"<integer-initial>[,<integer-extend>]" (extend value is optional and defaults
to the supplied initial value).   Defining an <initial-value> to be -1 disables
LIB$*VM* zone management of the memory and standard DECC functions are used
instead.  Statistics are still collected and reported.  Some memory management
(e.g. request) must always be managed as a zone.  Values are read during server
startup.

Examples:

  $ DEFINE /EXECUTIVE /TABLE=WASD_TABLE WASD_VM_CACHE "4096"
  $ DEFINE /EXECUTIVE /TABLE=WASD_TABLE WASD_VM_DECC "2048,4096"
  $ DEFINE /EXECUTIVE /TABLE=WASD_TABLE WASD_VM_EXPAT "128,128"
  $ DEFINE /EXECUTIVE /TABLE=WASD_TABLE WASD_VM_HTTP2 "80,128"
  $ DEFINE /EXECUTIVE /TABLE=WASD_TABLE WASD_VM_OPENSSL "-1"
  $ DEFINE /EXECUTIVE /TABLE=WASD_TABLE WASD_VM_PERMCACHE "512"
  $ DEFINE /EXECUTIVE /TABLE=WASD_TABLE WASD_VM_REQUEST "96,128"

Request with "tune" parameters (sample of 64, and period 100 times greater):

  $ DEFINE /EXECUTIVE /TABLE=WASD_TABLE WASD_VM_REQUEST "256,512,64,100"


NOTE:  MEMORY MANAGEMENT IS CONSIDERED CRUCIAL TO HTTPD FUNCTIONING
-------------------------------------------------------------------
IF A ZONE ALLOCATION OR FREEING GENERATES AN ERROR (E.G. INSUFFICIENT VIRTUAL,
BAD BLOCK,  ETC.) THEN THIS IS CONSIDERED SERIOUS (ESPECIALLY THE LATTER,
INDICATING STRUCTURE CORRUPTION) AND THE SERVER EXITS REPORTING STATUS
INFORMATION.

Hence calls to memory allocation and freeing do not need to check for success
as only successful operations will return to them!


MEMORY CHUNK PROLOGUE
---------------------
All memory allocations contain a small prologue structure immediately in front
of the location returned to the caller.  This provides some memory management
data (chunk size and guard magic) and 9 bytes of space immediately adjacent to
the first returned byte.  This space is intended to contain an HTTP/2 frame
header immediately preceding data to be written to the network.  It is always
present in allocated memory whether used for HTTP/2 framing or not and its
presence detected through some specific magic stored immediately preceding (and
in this manner "close") to the returned pointer.  See HTTP2.c and HTTP2NET.C
for further background, example usage, etc.


OPENSSL MEMORY STATISTICS
-------------------------
The OpenSSL memory management calls malloc(), realloc() and free() can be
intercepted by this module and used to provide statistics on their use.  The
calls are actually still processed by the DECC run-time equivalent calls.
Compatible with "intercepting DECC calls" but not "OpenSSL memory management".


OPENSSL MEMORY MANAGEMENT
-------------------------
The OpenSSL memory management calls malloc(), realloc() and free() can be
performed by this module using calls associated with dedicated zone "WASD
OpenSSL".  This can be used for investigative purpose or in production.
The OpenSSL CRYPTO_set_mem_function() API is used to enable this.
OpenSSL memory management and the DECC emulation are compatible. 
Compatible with "intercepting DECC calls" but not "OpenSSL memory statistics".


INTERCEPTING DECC CALLS
-----------------------
The void* VmDecc..() functions are designed to provide a DECC emulation for
development and troubleshooting purposes.  Defining VM_DECC macro true will
enable an overloading of the DECC$CALLOC, DECC$MALLOC, DECC$REALLOC and
DECC$FREE functions.

These messages (or CPU equivalent) at link time indicate a successful 
overloading and may safely be ignored.  Bit messy but hey.

%LINK-W-MULDEF, symbol DECC$REALLOC multiply defined
   in module DECC$SHR_EV56 file SYS$COMMON:[SYSLIB]DECC$SHR_EV56.EXE;1
%LINK-W-MULDEF, symbol DECC$MALLOC multiply defined
   in module DECC$SHR_EV56 file SYS$COMMON:[SYSLIB]DECC$SHR_EV56.EXE;1
%LINK-W-MULDEF, symbol DECC$CALLOC multiply defined
   in module DECC$SHR_EV56 file SYS$COMMON:[SYSLIB]DECC$SHR_EV56.EXE;1
%LINK-W-MULDEF, symbol DECC$FREE multiply defined
   in module DECC$SHR_EV56 file SYS$COMMON:[SYSLIB]DECC$SHR_EV56.EXE;1

By default the actual memory management is performed by LIB$*VM*() routines
with a created LIB$ memory zone.  If the macro VM_DECC_LIB_VM macro is true
then the memory management is performed by calls to LIB$VM..() calls, a
slightly incestuous code path implemented to gather DECC statistics.

DECC also seems to free() one or two, perhaps more, right at the beginning of
server initialisation, perhaps from DECC initialisation itself.  This is
detected by the absence of approprite magic on the memory and would normally be
considered a fatal error.  So this code ignores magic-less memory chunks for a
few seconds after server startup.


VAX SUPPORT
-----------
Is limited to the basic functionality of this module and DOES NOT support 
using zones for individual non-WASD memory management or statistic gathering
and reporting.


VERSION HISTORY
---------------
20-JUL-2021  MGD  confine VmReport() items to WASD zones,
                    unless logical name WASD_VM_REPORT_ALL is defined
19-NOV-2020  MGD  VmGetRequest(), VmFreeHeap() provide "tuned" initial pages
                    (yep, tuning again...)
11-AUG-2020  MGD  VmCheckPgFlLimit() and WASD_VM_PGFL_LIMIT logical name
23-JAN-2020  MGD  HTTP/2 heap initial and extend pages 128 to 256
26-JUL-2018  MGD  here we go again - rework to pull more statistics
14-JUL-2018  MGD  Bonne Fête Nationale!
                  eliminate dynamic tuning of heap initial allocation
                    assumes overhead of tuning out-weighs that of area mgmnt
                    (and after a query and some discussion on comp.os.vms)
                  add VmOpenSsl..() routines specifically for OpenSSL
                  add VmDecc..() routines for development and troubleshooting
                  VmReport() add lib$show_vm_zone_64 (perhaps looking ahead)
01-JUL-2018  MGD  VmReport() include WATCH link in heap zone data
04-AUG-2015  MGD  support HTTP/2 specific memory management
                  add magic allowing various allocations to be differentiated
                  HTTP2_STRUCT and REQUEST_STRUCT sizes via external storage
14-MAR-2011  MGD  sizeof(VM_STRUCT) now 8 (natural alignment) instead of 4
04-JUL-2006  MGD  use PercentOf32() for more accurate percentages
16-APR-2005  MGD  modify statistics to a maximum of 1024 pages and granularity
                  of 8 (GZIP significantly increased memory requirements)
26-MAR-2005  MGD  VmRequestTune() set 'VmRequestSizePages' dynamically
22-MAY-2004  MGD  bugfix; VmGetRequest() error exit if zone create fails
27-JAN-2004  MGD  VmCacheInit() and VmPermCacheInit() extend size reduced
09-DEC-2003  MGD  VmGeneralInit() upped from 1024/1024 to 4096/4096
18-JUN-2003  MGD  refine VmRequestTune()
14-JUN-2003  MGD  request heap statistics and VmRequestTune()
24-MAY-2003  MGD  VmPermCache..()
29-SEP-2001  MGD  instance support
04-AUG-2001  MGD  support module WATCHing
26-FEB-2001  MGD  observation indicates VmGeneralInit() extend up to 1024
04-MAR-2000  MGD  use FaolToNet(), et.al.
05-FEB-2000  MGD  add module name and line number to VmFree/Realloc...()
16-JUL-1998  MGD  observation indicates VmGeneralInit() initial up to 1024
07-DEC-1997  MGD  using LIB$*_VM_* routines for virtual memory manipulation
                  (and unbundled from support.c for v5.0)
*/
/*****************************************************************************/

#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 <ctype.h>
#include <errno.h>
#include <stdio.h>

/* VMS related header files */
#include <descrip.h>
#include <jpidef.h>
#include <libdef.h>
#include <libvmdef.h>
#include <ssdef.h>
#include <stsdef.h>

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

#define WASD_MODULE "VM"

/* allocation quantum for the zone */
#define VM_BLOCK_BYTES 64

/*
   Where appropriate allocate an extra few bytes for carriage-control
   in buffers, null string terminators, programming errors ;^) etc.
*/
#define VM_ELBOW_ROOM 8

#define VM_GUARD_SIZE sizeof(ulong)

/* all overhead when allocating even a single byte of heap */
#define VM_ALLOC_OVERHEAD (sizeof(VM_STRUCT) + VM_ELBOW_ROOM + VM_GUARD_SIZE)

/* unconditionally control VM module WATCHing - only for troubleshooting! */
#define WATCHON_WATCHOFF 0

/*****************/
/* GENERAL ZONES */
/*****************/

#define VM_REQUEST_INITIAL_PAGES 256
#define VM_REQUEST_EXTEND_PAGES 256
#define VM_REQUEST_MAX_PAGES 1024
#define VM_REQUEST_MIN_PAGES 32

/* tune after sampling this many requests */
#define VM_REQUEST_TUNE_SAMPLE 128
/* continue to tune for this multiple of the sample */
#define VM_REQUEST_TUNE_PERIOD 32

#define VM_HTTP2_INITIAL_PAGES 256
#define VM_HTTP2_EXTEND_PAGES 256
#define VM_HTTP2_MAX_PAGES 2048
#define VM_HTTP2_MIN_PAGES 128

#define VM_GENERAL_INITIAL_PAGES 4096
#define VM_GENERAL_EXTEND_PAGES 2048
#define VM_GENERAL_MAX_PAGES 16384
#define VM_GENERAL_MIN_PAGES 512

#define VM_CACHE_INITIAL_PAGES 1024
#define VM_CACHE_EXTEND_PAGES 1024
#define VM_CACHE_MAX_PAGES 8192
#define VM_CACHE_MIN_PAGES 256

#define VM_PERMCACHE_INITIAL_PAGES 256
#define VM_PERMCACHE_EXTEND_PAGES 256
#define VM_PERMCACHE_MAX_PAGES 8192
#define VM_PERMCACHE_MIN_PAGES 256

   /*******/
#if VM_DECC
   /*******/

#if VM_OPENSSL
/* if OpenSSL being VM managed then DECC needs a lot less */
#  define VM_DECC_INITIAL_PAGES 256
#  define VM_DECC_EXTEND_PAGES 256
#  define VM_DECC_MAX_PAGES 4096
#  define VM_DECC_MIN_PAGES 128
#else
/* if OpenSSL *not* being managed then DECC needs to take up the slack */
#  define VM_DECC_INITIAL_PAGES 16384
#  define VM_DECC_EXTEND_PAGES 16384
#  define VM_DECC_MAX_PAGES 131072
#  define VM_DECC_MIN_PAGES 4096
#endif /* VM_OPENSSL */

#  ifndef VM_DECC_LIB_VM
#     define VM_DECC_LIB_VM 0
#  endif

/* overload the local routines onto the DECC routines */
#  define  VmDeccMalloc   DECC$MALLOC
#  define  VmDeccCalloc   DECC$CALLOC
#  define  VmDeccRealloc  DECC$REALLOC
#  define  VmDeccFree     DECC$FREE

#endif /* VM_DECC */

   /********/
#if VM_EXPAT
   /********/

#define VM_EXPAT_INITIAL_PAGES 256
#define VM_EXPAT_EXTEND_PAGES 256
#define VM_EXPAT_MAX_PAGES 2048
#define VM_EXPAT_MIN_PAGES 128

#endif /* VM_EXPAT */

   /**********/
#if VM_OPENSSL
   /**********/

#define VM_OPENSSL_INITIAL_PAGES 16384
#define VM_OPENSSL_EXTEND_PAGES 16384
#define VM_OPENSSL_MAX_PAGES 131072
#define VM_OPENSSL_MIN_PAGES 4096

#endif /* VM_OPENSSL */

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

static BOOL  VmReportPlus,
             VmReportThisOne;

static int VmDeccTickSecond;

static VM_ZONE  VmCacheZone;
static VM_ZONE  VmGeneralZone;
static VM_ZONE  VmHttp2Zone;
static VM_ZONE  VmPermCacheZone;
static VM_ZONE  VmRequestZone;

static VM_ZONE  VmExpatZone;
static VM_ZONE  VmOpenSslZone;

#if VM_DECC
static VM_ZONE  VmDeccZone;
#endif /* VM_DECC */

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

extern BOOL  HttpdServerStartup;

extern int  CacheEntryKBytesMax,
            CacheTotalKBytesMax,
            EfnWait,
            HttpdTickSecond,
            Http2StructSize,
            NetConcurrentMax,
            RequestStructSize;

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

extern CONFIG_STRUCT  Config;
extern HTTPD_GBLSEC  *HttpdGblSecPtr;
extern HTTPD_PROCESS  HttpdProcess;
extern WATCH_STRUCT  Watch;

/*****************************************************************************/
/*
Initialize virtual memory management.
*/ 

void VmInit ()

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

   if (WATCH_MODULE(WATCH_MOD_VM) || WATCHON_WATCHOFF)
   {
      Watch.Module |= WATCH_MOD_VM;
      Watch.LogFile = 1;
      strzcpy (Watch.LogFileName, WATCH_NAME_DEFAULT, sizeof(Watch.LogFileName));
      WatchThis (WATCHALL, WATCH_MOD_VM, "VmInit()");
   }

   VmGeneralInit ();
}

/*****************************************************************************/
/*
Allocate memory for an up-call function.
Malloc is indicated by a chunk number of -1.
*/ 

void* VmGenericCalloc
(
VM_ZONE *ZonePtr,
int NumChunks,
int ChunkSize,
char *ModuleName,
int LineNumber
)
{
   int  status;
   ulong  BaseAddress;
   uchar  *aptr;
   VM_STRUCT  *vmptr;

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

   if (!ZonePtr->ZoneId) ErrorExitVmsStatus (SS$_BUGCHECK, NULL, FI_LI);

   if (WATCH_MODULE(WATCH_MOD_VM) || WATCHON_WATCHOFF)
      WatchThis (WATCHALL, WATCH_MOD_VM,
                 "VmGenericCalloc() 0x!8XL !AZ !SL !SL",
                 ZonePtr->ZoneId, ZonePtr->ReportTitle,
                 NumChunks, ChunkSize);

   if (!ChunkSize) return (NULL);

   if (NumChunks > 1) ChunkSize *= NumChunks;

   ChunkSize += VM_ALLOC_OVERHEAD;
   if (ZonePtr->ZoneId == (ulong)-1)
   {
      vmptr = (VM_STRUCT*)LIB$VM_CALLOC (1, ChunkSize);
      if (!vmptr) ErrorExitVmsStatus (SS$_BUGCHECK, "lib$vm_calloc()", FI_LI);
   }
   else
   {
      status = lib$get_vm (&ChunkSize, &BaseAddress, &ZonePtr->ZoneId);
      if (VMSnok (status)) ErrorExitVmsStatus (status, "lib$get_vm()", FI_LI);
      vmptr = (VM_STRUCT*)BaseAddress;
   }
   ChunkSize -= VM_ALLOC_OVERHEAD;

   vmptr->size = ChunkSize;

   vmptr->magic = ZonePtr->MagicValue;

   /* guard is placed at the end of the memory chunk to detect overflow */
   aptr = (uchar*)vmptr + (sizeof(VM_STRUCT) + VM_ELBOW_ROOM) + ChunkSize;
   *(ULONGPTR)aptr = VM_GUARD;

   if (NumChunks < 0)
      ZonePtr->MallocCount++;
   else
      ZonePtr->CallocCount++;
   ZonePtr->AllocBytes += ChunkSize;
   ZonePtr->InUseBytes = ZonePtr->AllocBytes - ZonePtr->DeallocBytes;
   if (ZonePtr->InUseBytes > ZonePtr->InUseMaxBytes)
      ZonePtr->InUseMaxBytes = ZonePtr->InUseBytes;

   if (WATCH_MODULE(WATCH_MOD_VM) || WATCHON_WATCHOFF)
      WatchThis (WATCHALL, WATCH_MOD_VM, "!UL !UL !8XL !8XL",
                 vmptr, vmptr->size, vmptr->magic, *(ULONGPTR)aptr);

   return ((uchar*)vmptr + sizeof(VM_STRUCT));
}

/*****************************************************************************/
/*
Expand generic chunk.  See VmGenericCalloc().
*/ 

void* VmGenericRealloc
(
VM_ZONE *ZonePtr,
uchar *ChunkPtr,
int ChunkSize,
char *ModuleName,
int LineNumber
)
{
   int  status;
   ulong  BaseAddress,
          TheBaseAddress,
          TheChunkSize;
   uchar  *aptr;
   VM_STRUCT  *vmptr;

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

   if (!ZonePtr->ZoneId) ErrorExitVmsStatus (SS$_BUGCHECK, NULL, FI_LI);

   if (ChunkPtr)
      TheChunkSize = ((VM_STRUCT*)(ChunkPtr-sizeof(VM_STRUCT)))->size;
   else
      TheChunkSize = -1;

   if (WATCH_MODULE(WATCH_MOD_VM) || WATCHON_WATCHOFF)
      WatchThis (WATCHALL, WATCH_MOD_VM,
                 "VmGenericRealloc() 0x!8XL !AZ !SL !SL !AZ:!UL",
                 ZonePtr->ZoneId, ZonePtr->ReportTitle,
                 TheChunkSize, ChunkSize, ModuleName, LineNumber);

   if (!ChunkPtr && ChunkSize)
      return (VmGenericCalloc (ZonePtr, -1, ChunkSize, ModuleName, LineNumber));

   if (ChunkPtr && !ChunkSize)
   {
      VmGenericFree (ZonePtr, ChunkPtr, ModuleName, LineNumber);
      return (NULL);
   }

   if (!ChunkSize) return (NULL);

   vmptr = (VM_STRUCT*)(TheBaseAddress = ChunkPtr - sizeof(VM_STRUCT));
   aptr = (uchar*)vmptr + (sizeof(VM_STRUCT) + VM_ELBOW_ROOM) + TheChunkSize;

   if (WATCH_MODULE(WATCH_MOD_VM) || WATCHON_WATCHOFF)
      WatchThis (WATCHALL, WATCH_MOD_VM, "!UL !UL !8XL !8XL",
                 vmptr, vmptr->size, vmptr->magic, *(ULONGPTR)aptr);

   if (vmptr->magic != ZonePtr->MagicValue)
      ErrorExitVmsStatus (SS$_BUGCHECK, "magic", ModuleName, LineNumber);

   if (*(ULONGPTR)aptr != VM_GUARD)
      ErrorExitVmsStatus (SS$_BUGCHECK, "guard", ModuleName, LineNumber);

   /* if this chunk satisfies the reallocation then just return it */
   TheChunkSize = ((VM_STRUCT*)TheBaseAddress)->size;
   if (TheChunkSize >= ChunkSize) return (ChunkPtr);

   /* allocate a new, larger chunk */
   ChunkSize += VM_ALLOC_OVERHEAD;
   if (ZonePtr->ZoneId == (ulong)-1)
   {
      vmptr = (VM_STRUCT*)LIB$VM_CALLOC (1, ChunkSize);
      if (!vmptr) return (NULL);
   }
   else
   {
      status = lib$get_vm (&ChunkSize, &BaseAddress, &ZonePtr->ZoneId);
      if (VMSnok (status)) ErrorExitVmsStatus (status, "lib$get_vm()", FI_LI);
      vmptr = (VM_STRUCT*)BaseAddress;
   }
   ChunkSize -= VM_ALLOC_OVERHEAD;

   /* copy the existing chunk into the new chunk */
   if (TheChunkSize > 0)
      memcpy (vmptr,
              (uchar*)TheBaseAddress,
              TheChunkSize + (sizeof(VM_STRUCT) + VM_ELBOW_ROOM));

   aptr = (uchar*)vmptr + (sizeof(VM_STRUCT) + VM_ELBOW_ROOM) + ChunkSize;
   *(ULONGPTR)aptr = VM_GUARD;

   /* update the chunk size */
   vmptr->size = ChunkSize;

   /* this is the GROWTH in memory due to reallocation */
   ZonePtr->ReallocCount++;
   ZonePtr->AllocBytes += ChunkSize - TheChunkSize;
   ZonePtr->InUseBytes = ZonePtr->AllocBytes - ZonePtr->DeallocBytes;
   if (ZonePtr->InUseBytes > ZonePtr->InUseMaxBytes)
      ZonePtr->InUseMaxBytes = ZonePtr->InUseBytes;

   /* free the previous chunk */
   if (ZonePtr->ZoneId == (ulong)-1)
      LIB$VM_FREE ((void*)TheBaseAddress);
   else
   {
      status = lib$free_vm (0, &TheBaseAddress, &ZonePtr->ZoneId);
      if (VMSnok (status))
         ErrorExitVmsStatus (status, "lib$free_vm()", ModuleName, LineNumber);
   }

   if (WATCH_MODULE(WATCH_MOD_VM) || WATCHON_WATCHOFF)
      WatchThis (WATCHALL, WATCH_MOD_VM, "!UL !UL !8XL !8XL",
                 vmptr, vmptr->size, vmptr->magic, *(ULONGPTR)aptr);

   return ((uchar*)vmptr + sizeof(VM_STRUCT));
}

/*****************************************************************************/
/*
Release memory allocated for generic use.
*/ 

void VmGenericFree
(
VM_ZONE *ZonePtr,
uchar *ChunkPtr,
char *ModuleName,
int LineNumber
)
{
   int  status,
        ChunkSize;
   ulong  BaseAddress;
   uchar  *aptr;
   VM_STRUCT  *vmptr;

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

   if (!ZonePtr->ZoneId) ErrorExitVmsStatus (SS$_BUGCHECK, NULL, FI_LI);

   if (ChunkPtr)
      ChunkSize = ((VM_STRUCT*)(ChunkPtr-sizeof(VM_STRUCT)))->size;
   else
      ChunkSize = 0;

   if (WATCH_MODULE(WATCH_MOD_VM) || WATCHON_WATCHOFF)
      WatchThis (WATCHALL, WATCH_MOD_VM,
                 "VmGenericFree() 0x!8XL !AZ !SL !AZ:!UL",
                 ZonePtr->ZoneId, ZonePtr->ReportTitle, ChunkSize,
                 ModuleName, LineNumber);

   if (!ChunkPtr) return;

   vmptr = (VM_STRUCT*)(BaseAddress = ChunkPtr - sizeof(VM_STRUCT));
   aptr = (uchar*)vmptr + (sizeof(VM_STRUCT) + VM_ELBOW_ROOM) + ChunkSize;

   if (WATCH_MODULE(WATCH_MOD_VM) || WATCHON_WATCHOFF)
      WatchThis (WATCHALL, WATCH_MOD_VM, "!UL !UL !8XL !8XL",
                 vmptr, vmptr->size, vmptr->magic, *(ULONGPTR)aptr);

   /* see module prologue for explanation */
   if (vmptr->magic != ZonePtr->MagicValue)
      ErrorExitVmsStatus (SS$_BUGCHECK, "magic", ModuleName, LineNumber);
   if (*(ULONGPTR)aptr != VM_GUARD)
      ErrorExitVmsStatus (SS$_BUGCHECK, "guard", ModuleName, LineNumber);

   ZonePtr->FreeCount++;
   ZonePtr->DeallocBytes += ChunkSize;
   ZonePtr->InUseBytes = ZonePtr->AllocBytes - ZonePtr->DeallocBytes;
   if (ZonePtr->InUseBytes > ZonePtr->InUseMaxBytes)
      ZonePtr->InUseMaxBytes = ZonePtr->InUseBytes;

   if (ZonePtr->ZoneId == (ulong)-1)
      LIB$VM_FREE ((void*)BaseAddress);
   else
   {
      status = lib$free_vm (0, &BaseAddress, &ZonePtr->ZoneId);
      if (VMSnok (status))
         ErrorExitVmsStatus (status, "lib$free_vm()", ModuleName, LineNumber);
   }
}

/*****************************************************************************/
/*
Initialize the virtual memory zone general memory will be allocated from.
Also take parameters from logical names described above if present.
*/ 

void VmGeneralInit ()

{
   static $DESCRIPTOR (ZoneNameDsc, "WASD General");
   static ulong  Algorithm = LIB$K_VM_QUICK_FIT,
                 AlgorithmArg = 64,
                 Flags = LIB$M_VM_BOUNDARY_TAGS |
                         LIB$M_VM_GET_FILL0 |
                         LIB$M_VM_TAIL_LARGE,
                 ExtendPages = 4096,
                 InitialPages = 4096,
                 BlockBytes = VM_BLOCK_BYTES;

   int  status;
   char  *cptr;
   VM_ZONE  *vzptr = &VmGeneralZone;

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

   if (WATCH_MODULE(WATCH_MOD_VM) || WATCHON_WATCHOFF)
      WatchThis (WATCHALL, WATCH_MOD_VM, "VmGeneralInit()");

   vzptr->MagicValue = VM_MAGIC_GENERAL;
   vzptr->MaxPages = VM_GENERAL_MAX_PAGES;
   vzptr->MinPages = VM_GENERAL_MIN_PAGES;
   vzptr->InitialPages = VM_GENERAL_INITIAL_PAGES;
   vzptr->ExtendPages = VM_GENERAL_EXTEND_PAGES;
   vzptr->LogicalName = WASD_VM_GENERAL;
   vzptr->ReportTitle = "General";
   VmSetPages (vzptr);

   /* create the general virtual memory zone */
   status = lib$create_vm_zone (&vzptr->ZoneId,
                                &Algorithm, &AlgorithmArg, &Flags,
                                &vzptr->ExtendPages,
                                &vzptr->InitialPages,
                                &BlockBytes, 0, 0, 0, &ZoneNameDsc, 0, 0);
   if (VMSnok (status))
      ErrorExitVmsStatus (status, "lib$create_vm_zone()", FI_LI);
}

/*****************************************************************************/
/*
Allocate memory for general use.
*/ 

uchar* VmGet (ulong ChunkSize)

{
   int  status;
   ulong  BaseAddress;

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

   if (WATCH_MODULE(WATCH_MOD_VM) || WATCHON_WATCHOFF)
      WatchThis (WATCHALL, WATCH_MOD_VM, "VmGet() !UL", ChunkSize);

   return (VmGenericCalloc (&VmGeneralZone, 1, ChunkSize, FI_LI));
}

/*****************************************************************************/
/*
Expand (or even contract) an individual a general-use chunk.  See VmGet().
*/ 

uchar* VmRealloc
(
uchar *ChunkPtr,
ulong ChunkSize,
char *ModuleName,
int LineNumber
)
{
   /*********/
   /* begin */
   /*********/

   return (VmGenericRealloc (&VmGeneralZone, ChunkPtr, ChunkSize,
                             ModuleName, LineNumber));
}

/*****************************************************************************/
/*
Release memory allocated for general use.
*/ 

void VmFree
(
uchar *ChunkPtr,
char *ModuleName,
int LineNumber
)
{
   /*********/
   /* begin */
   /*********/

   VmGenericFree (&VmGeneralZone, ChunkPtr, ModuleName, LineNumber);
}

/*****************************************************************************/
/*
Initialize the virtual memory zone for the request structures.
*/ 

void VmRequestInit ()

{
   static $DESCRIPTOR (ZoneNameDsc, "WASD Request Pool");
   static ulong  Algorithm = LIB$K_VM_QUICK_FIT,
                 AlgorithmArg = 32,
                 Flags = LIB$M_VM_BOUNDARY_TAGS |
                         LIB$M_VM_GET_FILL0 |
                         LIB$M_VM_TAIL_LARGE,
                 BlockBytes = VM_BLOCK_BYTES;

   int  status;
   ulong  ExtendPages, InitialPages;
   char  *cptr;
   VM_ZONE  *vzptr = &VmRequestZone;

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

   if (WATCH_MODULE(WATCH_MOD_VM) || WATCHON_WATCHOFF)
      WatchThis (WATCHALL, WATCH_MOD_VM,
                 "VmRequestInit() !UL", NetConcurrentMax);

   /* not used in this function - used in VmGetRequest() */
   vzptr->MagicValue = 0;
   vzptr->MaxPages = VM_REQUEST_MAX_PAGES;
   vzptr->MinPages = VM_REQUEST_MIN_PAGES;
   vzptr->InitialPages = VM_REQUEST_INITIAL_PAGES;
   vzptr->ExtendPages = VM_REQUEST_EXTEND_PAGES;
   vzptr->TuneSample = VM_REQUEST_TUNE_SAMPLE;
   vzptr->TunePeriod = VM_REQUEST_TUNE_PERIOD;
   vzptr->LogicalName = WASD_VM_REQUEST;
   vzptr->ReportTitle = "Request";
   VmSetPages (vzptr);

   ExtendPages = InitialPages =
      ((NetConcurrentMax * RequestStructSize) / 512) + 1;

   /* create the virtual memory zone for the request pool */
   status = lib$create_vm_zone (&vzptr->ZoneId,
                                &Algorithm, &AlgorithmArg, &Flags,
                                &ExtendPages, &InitialPages, &BlockBytes,
                                0, 0, 0, &ZoneNameDsc, 0, 0);
   if (VMSnok (status))
      ErrorExitVmsStatus (status, "lib$create_vm_zone()", FI_LI);

   FaoToStdout ("%HTTPD-I-VM, request zone initialised\n");
}

/*****************************************************************************/
/*
Allocate a request structure with associated virtual memory zone ready for
heap allocation.
*/ 

REQUEST_STRUCT* VmGetRequest (int ConnectNumber)

{
   static $DESCRIPTOR (ProctorZoneNameFaoDsc, "WASD Proctor Heap !UL");
   static $DESCRIPTOR (RequestZoneNameFaoDsc, "WASD Request Heap "
                       "<a href=\"!AZ?at=!UL\">!-!UL</a>");
   static ulong  Algorithm = LIB$K_VM_QUICK_FIT,
                 AlgorithmArg = 16,
                 Flags = LIB$M_VM_BOUNDARY_TAGS |
                         LIB$M_VM_GET_FILL0 |
                         LIB$M_VM_TAIL_LARGE,
                 BlockBytes = VM_BLOCK_BYTES;

   int  status;
   ushort  slen;
   ulong  BaseAddress,
          InitialPages;
   char  ZoneName [128];
   REQUEST_STRUCT  *rqptr;
   $DESCRIPTOR (ZoneNameDsc, ZoneName);

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

   if (WATCH_MODULE(WATCH_MOD_VM) || WATCHON_WATCHOFF)
      WatchThis (WATCHALL, WATCH_MOD_VM, "VmGetRequest()");

   if (ConnectNumber > 0)
      status = sys$fao (&RequestZoneNameFaoDsc, &slen, &ZoneNameDsc,
                        ADMIN_REPORT_WATCH, ConnectNumber);
   else
      status = sys$fao (&ProctorZoneNameFaoDsc, &slen, &ZoneNameDsc,
                        -(ConnectNumber));
   if (VMSnok (status)) ErrorExitVmsStatus (status, "sys$fao()", FI_LI);
   ZoneName[ZoneNameDsc.dsc$w_length = slen] = '\0';

   status = lib$get_vm (&RequestStructSize, &BaseAddress,
                        &VmRequestZone.ZoneId);
   if (VMSnok (status)) ErrorExitVmsStatus (status, "lib$get_vm()", FI_LI);

   rqptr = (REQUEST_STRUCT*)BaseAddress;

   rqptr->ConnectNumber = ConnectNumber;

   if (!(InitialPages = VmRequestZone.StatInitial))
      InitialPages = VmRequestZone.InitialPages;

   /* now create a virtual memory zone for the request's heap */
   rqptr->VmHeapZoneId = 0;
   status = lib$create_vm_zone (&rqptr->VmHeapZoneId,
                                &Algorithm, &AlgorithmArg, &Flags,
                                &VmRequestZone.ExtendPages, &InitialPages,
                                &BlockBytes, 0, 0, 0, &ZoneNameDsc, 0, 0);
   if (VMSnok (status))
      ErrorExitVmsStatus (status, "lib$create_vm_zone()", FI_LI);

   return (rqptr);
}

/*****************************************************************************/
/*
Delete any virtual memory zone created for the request's heap, then return the
request structure to the request virtual memory pool.
*/ 

void VmFreeRequest
(
REQUEST_STRUCT *rqptr,
char *ModuleName,
int LineNumber
)
{
   static ulong  PrevByteCount;

   int  status;
   ulong  PageCount;

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

   if (WATCHMOD (rqptr, WATCH_MOD_VM))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_VM, "VmFreeRequest()");

   if (rqptr->VmHeapZoneId)
   {
      /* delete the request's heap's virtual memory zone */
      status = lib$delete_vm_zone (&rqptr->VmHeapZoneId);
      if (VMSnok (status))
         ErrorExitVmsStatus (status, "lib$delete_vm_zone()", FI_LI);
   }

   status = lib$free_vm (0, &rqptr, &VmRequestZone.ZoneId);
   if (VMSnok (status))
      ErrorExitVmsStatus (status, "VmFreeRequest() lib$free_vm()",
                          ModuleName, LineNumber);
}

/*****************************************************************************/
/*
Allocate dynamic memory to an individual request's heap.  Return a pointer to 
the start of new *usable* memory area if successful, explode if not.
*/ 

uchar* VmGetHeap
(
REQUEST_STRUCT *rqptr,
ulong ChunkSize
)
{
   int  status;
   ulong  BaseAddress;

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

   if (WATCHMOD (rqptr, WATCH_MOD_VM))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_VM, "VmGetHeap() !UL", ChunkSize);

   ChunkSize += VM_ALLOC_OVERHEAD;
   status = lib$get_vm (&ChunkSize, &BaseAddress, &rqptr->VmHeapZoneId);
   if (VMSnok (status)) ErrorExitVmsStatus (status, "lib$get_vm()", FI_LI);
   ChunkSize -= VM_ALLOC_OVERHEAD;

   ((VM_STRUCT*)BaseAddress)->size = ChunkSize;
   ((VM_STRUCT*)BaseAddress)->magic = VM_MAGIC_REQUEST;

   return ((uchar*)BaseAddress + sizeof(VM_STRUCT));
}

/*****************************************************************************/
/*
Expand (or even contract) an individual chunk.  See VmGetHeap().
*/ 

uchar* VmReallocHeap
(
REQUEST_STRUCT *rqptr,
uchar *ChunkPtr,
ulong ChunkSize,
char *ModuleName,
int LineNumber
)
{
   int  status;
   ulong  BaseAddress,
          TheBaseAddress,
          TheChunkSize;

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

   if (WATCHMOD (rqptr, WATCH_MOD_VM))
   {
      if (ChunkPtr)
         TheChunkSize = ((VM_STRUCT*)(ChunkPtr-sizeof(VM_STRUCT)))->size;
      else
         TheChunkSize = 0;
      WatchThis (WATCHITM(rqptr), WATCH_MOD_VM,
                 "VmReallocHeap() !UL !UL", TheChunkSize, ChunkSize);
   }

   if (!ChunkPtr) return (VmGetHeap (rqptr, ChunkSize));

   TheBaseAddress = ChunkPtr - sizeof(VM_STRUCT);
   if (((VM_STRUCT*)TheBaseAddress)->magic != VM_MAGIC_REQUEST)
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck,
                          ModuleName, LineNumber);

   /* if this chunk satisfies the reallocation then just return it */
   TheChunkSize = ((VM_STRUCT*)TheBaseAddress)->size;
   if (TheChunkSize >= ChunkSize) return (ChunkPtr);

   /* allocate a new, larger chunk */
   ChunkSize += VM_ALLOC_OVERHEAD;
   status = lib$get_vm (&ChunkSize, &BaseAddress, &rqptr->VmHeapZoneId);
   if (VMSnok (status))
      ErrorExitVmsStatus (status, "VmReallocHeap() lib$get_vm()",
                          ModuleName, LineNumber);
   ChunkSize -= VM_ALLOC_OVERHEAD;

   /* copy the existing chunk into the new chunk */
   if (TheChunkSize > 0)
      memcpy ((uchar*)BaseAddress,
              (uchar*)TheBaseAddress,
              TheChunkSize + VM_ALLOC_OVERHEAD);

   /* update the chunk size */
   ((VM_STRUCT*)BaseAddress)->size = ChunkSize;

   /* free the previous chunk */
   status = lib$free_vm (0, &TheBaseAddress, &rqptr->VmHeapZoneId);
   if (VMSnok (status))
      ErrorExitVmsStatus (status, "VmReallocHeap() lib$free_vm()",
                          ModuleName, LineNumber);

   return ((uchar*)BaseAddress+sizeof(VM_STRUCT));
}

/*****************************************************************************/
/*
Release back into the virtual memory zone one chunk of request heap memory.
*/ 

void VmFreeFromHeap
(
REQUEST_STRUCT *rqptr,
uchar *ChunkPtr,
char *SourceModuleName,
int SourceLineNumber
)
{
   int  status,
        ChunkSize;
   ulong  BaseAddress;

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

   BaseAddress = ChunkPtr - sizeof(VM_STRUCT);
   ChunkSize = ((VM_STRUCT*)(BaseAddress))->size;

   if (WATCHMOD (rqptr, WATCH_MOD_VM))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_VM,
                 "VmFreeFromHeap() !UL", ChunkSize);

   if (((VM_STRUCT*)BaseAddress)->magic != VM_MAGIC_REQUEST)
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck,
                          SourceModuleName, SourceLineNumber);

   status = lib$free_vm (0, &BaseAddress, &rqptr->VmHeapZoneId);
   if (VMSnok (status))
      ErrorExitVmsStatus (status, "VmFreeFromHeap() lib$free_vm()",
                          SourceModuleName, SourceLineNumber);
}

/*****************************************************************************/
/*
Release back into the virtual memory zone the individually allocated chunks of
memory (the zone is deleted on request structure release).
*/ 

void VmFreeHeap
(
REQUEST_STRUCT *rqptr,
char *SourceModuleName,
int SourceLineNumber
)
{
   static ulong  ShowVmZoneDetail = 1;

   int  status;
   ulong  pages;

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

   if (WATCHMOD (rqptr, WATCH_MOD_VM))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_VM, "VmFreeHeap()");

   if (VmRequestZone.TuneSample)
   {
      /********/
      /* tune */
      /********/

      status = lib$show_vm_zone (&rqptr->VmHeapZoneId, &ShowVmZoneDetail,
                                 &VmFreeHeapStat, rqptr);
      if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);

      if (rqptr->VmHeapStatPages > 0)
      {
         VmRequestZone.StatCount++;
         VmRequestZone.StatPages += rqptr->VmHeapStatPages;

         if (VmRequestZone.StatCount > VmRequestZone.TuneSample)
         {
            /* after an initial sample of request pages */
            VmRequestZone.StatInitial = VmRequestZone.StatPages /
                                        VmRequestZone.StatCount;
            VmRequestZone.StatInitial /= VmRequestZone.InitialPages;
            VmRequestZone.StatInitial *= VmRequestZone.InitialPages;
            if (VmRequestZone.StatInitial > VmRequestZone.MaxPages)
               VmRequestZone.StatInitial = VmRequestZone.MaxPages;

            /* reset the tuning process after a given number of requests */
            if (VmRequestZone.StatCount > VmRequestZone.TuneSample *
                                          VmRequestZone.TunePeriod)
               VmRequestZone.StatCount = VmRequestZone.StatPages =
                  VmRequestZone.StatInitial = 0; 
         }
         rqptr->VmHeapStatPages = 0;
      }
   }

   /********/
   /* free */
   /********/

   /* well - reset, if we must be pedantic */
   status = lib$reset_vm_zone (&rqptr->VmHeapZoneId);
   if (VMSnok (status))
      ErrorExitVmsStatus (status, "VmFreeHeap() lib$reset_vm_zone()", 
                          SourceModuleName, SourceLineNumber);
}

/*****************************************************************************/
/*
Parse the output from lib$show_vm_zone() to determine how many pages were
allocated to the request heap.  This value can then be used to optimise request
heap initial allocation.  Kludgy but there is (still) no lib$stat_vm_zone().
*/ 

int VmFreeHeapStat
(
struct dsc$descriptor *DscPtr,
REQUEST_STRUCT *rqptr
)
{
   char  *cptr, *czptr;

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

   if (WATCH_MODULE(WATCH_MOD_VM) || WATCHON_WATCHOFF)
      WatchThis (WATCHALL, WATCH_MOD_VM, "VmFreeHeapStat() !UL !-!#AZ",
                 DscPtr->dsc$w_length, DscPtr->dsc$a_pointer);

   if (rqptr->VmHeapStatPages ||
       DscPtr->dsc$w_length < 60 ||
       DscPtr->dsc$w_length > 90) return (SS$_NORMAL);

   for (czptr = (cptr = DscPtr->dsc$a_pointer) + DscPtr->dsc$w_length;
        cptr < czptr;
        cptr++)
      if (*cptr == 'I' && MATCH12(cptr,"Initial size")) break;

   if (cptr < czptr)
      for (cptr += 12; cptr < czptr; cptr++)
         if (*cptr == 'C' && MATCH12(cptr,"Current size"))
            break;

   if (cptr < czptr)
   {
      cptr += 15;
      rqptr->VmHeapStatPages = atol(cptr);
      if (WATCH_MODULE(WATCH_MOD_VM) || WATCHON_WATCHOFF)
         WatchThis (WATCHALL, WATCH_MOD_VM, "!SL", rqptr->VmHeapStatPages);
   }

   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
Initialize the virtual memory zone for the pool of HTTP/2 structures.
*/ 

void VmHttp2Init ()

{
   static $DESCRIPTOR (ZoneNameDsc, "WASD HTTP/2 Pool");
   static ulong  Algorithm = LIB$K_VM_QUICK_FIT,
                 AlgorithmArg = 32,
                 Flags = LIB$M_VM_BOUNDARY_TAGS |
                         LIB$M_VM_GET_FILL0 |
                         LIB$M_VM_TAIL_LARGE,
                 BlockBytes = VM_BLOCK_BYTES;

   int  status;
   ulong  ExtendPages, InitialPages;
   char  *cptr;
   VM_ZONE  *vzptr = &VmHttp2Zone;

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

   if (WATCH_MODULE(WATCH_MOD_VM) || WATCHON_WATCHOFF)
      WatchThis (WATCHALL, WATCH_MOD_VM,
                 "VmHttp2Init() !UL", NetConcurrentMax);

   /* not used in this function - used in VmHttp2Get() */
   vzptr->MagicValue = VM_MAGIC_HTTP2;
   vzptr->MaxPages = VM_HTTP2_MAX_PAGES;
   vzptr->MinPages = VM_HTTP2_MIN_PAGES;
   vzptr->InitialPages = VM_HTTP2_INITIAL_PAGES;
   vzptr->ExtendPages = VM_HTTP2_EXTEND_PAGES;
   vzptr->LogicalName = WASD_VM_HTTP2;
   vzptr->ReportTitle = "HTTP/2";
   VmSetPages (vzptr);

   ExtendPages = InitialPages = (NetConcurrentMax / 10) * 16;

   /* create the HTTP/2 virtual memory zone */
   status = lib$create_vm_zone (&vzptr->ZoneId,
                                &Algorithm, &AlgorithmArg, &Flags,
                                &ExtendPages, &InitialPages,
                                &BlockBytes, 0, 0, 0, &ZoneNameDsc, 0, 0);
   if (VMSnok (status))
      ErrorExitVmsStatus (status, "lib$create_vm_zone()", FI_LI);

   FaoToStdout ("%HTTPD-I-VM, HTTP/2 zone initialised\n");
}

/*****************************************************************************/
/*
Allocate an HTTP/2 structure with associated virtual memory zone ready for
HTTP/2 heap allocation.   This is used almost exclusively for write buffers.
*/ 

HTTP2_STRUCT* VmHttp2Get (int ConnectNumber)

{
   static $DESCRIPTOR (ZoneNameFaoDsc,
"WASD HTTP/2 Heap <a href=\"!AZ?at=!UL\">!-!UL</a>");
   static ulong  Algorithm = LIB$K_VM_QUICK_FIT,
                 AlgorithmArg = 16,
                 Flags = LIB$M_VM_BOUNDARY_TAGS |
                         LIB$M_VM_GET_FILL0 |
                         LIB$M_VM_TAIL_LARGE,
                 BlockBytes = VM_BLOCK_BYTES;

   int  status;
   ushort  slen;
   ulong  BaseAddress;
   char  ZoneName [128];
   HTTP2_STRUCT  *h2ptr;
   $DESCRIPTOR (ZoneNameDsc, ZoneName);

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

   if (WATCH_MODULE(WATCH_MOD_VM) || WATCHON_WATCHOFF)
      WatchThis (WATCHALL, WATCH_MOD_VM, "VmHttp2Get()");

   status = sys$fao (&ZoneNameFaoDsc, &slen, &ZoneNameDsc,
                     ADMIN_REPORT_WATCH, ConnectNumber);
   if (VMSnok (status)) ErrorExitVmsStatus (status, "sys$fao()", FI_LI);
   ZoneName[ZoneNameDsc.dsc$w_length = slen] = '\0';

   status = lib$get_vm (&Http2StructSize, &BaseAddress, &VmHttp2Zone.ZoneId);
   if (VMSnok (status)) ErrorExitVmsStatus (status, "lib$get_vm()", FI_LI);

   h2ptr = (HTTP2_STRUCT*)BaseAddress;

   h2ptr->ConnectNumber = ConnectNumber;

   /* now create a virtual memory zone for the request's heap */
   h2ptr->VmHeapZoneId = 0;
   status = lib$create_vm_zone (&h2ptr->VmHeapZoneId,
                                &Algorithm, &AlgorithmArg, &Flags,
                                &VmHttp2Zone.ExtendPages,
                                &VmHttp2Zone.InitialPages,
                                &BlockBytes, 0, 0, 0, &ZoneNameDsc, 0, 0);
   if (VMSnok (status))
      ErrorExitVmsStatus (status, "lib$create_vm_zone()", FI_LI);

   return (h2ptr);
}

/*****************************************************************************/
/*
Delete any virtual memory zone created for the HTTP/2 heap, then return the
HTTP/2 structure to the request virtual memory pool.
*/ 

void VmHttp2Free
(
HTTP2_STRUCT *h2ptr,
char *SourceModuleName,
int SourceLineNumber
)
{
   static ulong  PrevByteCount;

   int  status;
   ulong  PageCount;

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

   if (WATCHMOD (h2ptr, WATCH_MOD_VM))
      WatchThis (WATCHITM(h2ptr), WATCH_MOD_VM, "VmHttp2Free()");

   if (h2ptr->VmHeapZoneId)
   {
      /* delete the request's heap's virtual memory zone */
      status = lib$delete_vm_zone (&h2ptr->VmHeapZoneId);
      if (VMSnok (status))
         ErrorExitVmsStatus (status, "lib$delete_vm_zone()", FI_LI);
   }

   status = lib$free_vm (0, &h2ptr, &VmHttp2Zone.ZoneId);
   if (VMSnok (status))
      ErrorExitVmsStatus (status, "VmHttp2Free() lib$free_vm()",
                          SourceModuleName, SourceLineNumber);
}

/*****************************************************************************/
/*
Allocate dynamic memory to an individual HTTP/2 heap.  Return a pointer to 
the start of new *usable* memory area if successful, explode if not.
*/ 

uchar* VmGet2Heap
(
HTTP2_STRUCT *h2ptr,
ulong ChunkSize
)
{
   int  status;
   ulong  BaseAddress;

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

   if (WATCHMOD (h2ptr, WATCH_MOD_VM))
      WatchThis (WATCHITM(h2ptr), WATCH_MOD_VM, "VmGet2Heap() !UL", ChunkSize);

   ChunkSize += VM_ALLOC_OVERHEAD;
   status = lib$get_vm (&ChunkSize, &BaseAddress, &h2ptr->VmHeapZoneId);
   if (VMSnok (status)) ErrorExitVmsStatus (status, "lib$get_vm()", FI_LI);
   ChunkSize -= VM_ALLOC_OVERHEAD;

   ((VM_STRUCT*)BaseAddress)->size = ChunkSize;
   ((VM_STRUCT*)BaseAddress)->magic = VM_MAGIC_HTTP2;

   return ((uchar*)BaseAddress+sizeof(VM_STRUCT));
}

/*****************************************************************************/
/*
Release back into the virtual memory zone one chunk of HTTP/2 heap memory.
*/ 

void VmFreeFrom2Heap
(
HTTP2_STRUCT *h2ptr,
uchar *ChunkPtr,
char *SourceModuleName,
int SourceLineNumber
)
{
   int  status,
        ChunkSize;
   ulong  BaseAddress;

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

   BaseAddress = ChunkPtr - sizeof(VM_STRUCT);
   ChunkSize = ((VM_STRUCT*)(BaseAddress))->size;

   if (WATCHMOD (h2ptr, WATCH_MOD_VM))
      WatchThis (WATCHITM(h2ptr), WATCH_MOD_VM,
                 "VmFreeFrom2Heap() !UL", ChunkSize);

   if (((VM_STRUCT*)BaseAddress)->magic != VM_MAGIC_HTTP2)
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck,
                          SourceModuleName, SourceLineNumber);

   status = lib$free_vm (0, &BaseAddress, &h2ptr->VmHeapZoneId);
   if (VMSnok (status))
      ErrorExitVmsStatus (status, "VmFreeFrom2Heap() lib$free_vm()",
                          SourceModuleName, SourceLineNumber);
}

/*****************************************************************************/
/*
Release back into the HTTP/2 virtual memory zone the individually allocated
chunks of memory (the zone is deleted on request structure release).
*/ 

void VmFree2Heap
(
HTTP2_STRUCT *h2ptr,
char *SourceModuleName,
int SourceLineNumber
)
{
   int  status;

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

   if (WATCHMOD (h2ptr, WATCH_MOD_VM))
      WatchThis (WATCHITM(h2ptr), WATCH_MOD_VM, "VmFree2Heap()");

   status = lib$reset_vm_zone (&h2ptr->VmHeapZoneId);
   if (VMSnok (status))
      ErrorExitVmsStatus (status, "VmFree2Heap() lib$reset_vm_zone()", 
                          SourceModuleName, SourceLineNumber);
}

/*****************************************************************************/
/*
Initialize the virtual memory zone cache memory will be allocated from.
*/ 

void VmCacheInit (int TotalKBytesMax)

{
   static $DESCRIPTOR (ZoneNameDsc, "WASD Cache");
   static ulong  Algorithm = LIB$K_VM_QUICK_FIT,
                 AlgorithmArg = 128,
                 Flags = LIB$M_VM_BOUNDARY_TAGS |
                         LIB$M_VM_GET_FILL0 |
                         LIB$M_VM_TAIL_LARGE,
                 ExtendPages,
                 InitialPages,
                 BlockBytes = VM_BLOCK_BYTES;

   int  status;
   VM_ZONE  *vzptr = &VmCacheZone;

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

   if (WATCH_MODULE(WATCH_MOD_VM) || WATCHON_WATCHOFF)
      WatchThis (WATCHALL, WATCH_MOD_VM,
                 "VmCacheInit() !UL", TotalKBytesMax);

   vzptr->MagicValue = VM_MAGIC_CACHE;
   vzptr->MaxPages = VM_CACHE_MAX_PAGES;
   vzptr->MinPages = VM_CACHE_MIN_PAGES;
   vzptr->InitialPages = VM_CACHE_INITIAL_PAGES;
   vzptr->ExtendPages = VM_CACHE_EXTEND_PAGES;
   vzptr->LogicalName = WASD_VM_CACHE;
   vzptr->ReportTitle = "Cache";
   VmSetPages (vzptr);

   if ((InitialPages = TotalKBytesMax * 2) <= 0) InitialPages = 32;
   ExtendPages = InitialPages / 2;

   /* create the cache virtual memory zone */
   status = lib$create_vm_zone (&vzptr->ZoneId,
                                &Algorithm, &AlgorithmArg, &Flags,
                                &ExtendPages, &InitialPages, &BlockBytes,
                                0, 0, 0, &ZoneNameDsc, 0, 0);
   if (VMSnok (status))
      ErrorExitVmsStatus (status, "lib$create_vm_zone()", FI_LI);

   FaoToStdout ("%HTTPD-I-VM, cache zone initialised\n");
}

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

uchar* VmGetCache (ulong ChunkSize)

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

   if (WATCH_MODULE(WATCH_MOD_VM) || WATCHON_WATCHOFF)
      WatchThis (WATCHALL, WATCH_MOD_VM,
                 "VmGetCache() !UL", ChunkSize);

   if (!VmCacheZone.ZoneId) VmCacheInit (CacheTotalKBytesMax);

   return (VmGenericCalloc (&VmCacheZone, 1, ChunkSize, FI_LI));
}

/*****************************************************************************/
/*
Release back into the virtual memory zone the individually allocated chunks of
cache memory.
*/ 

void VmFreeCache
(
uchar *ChunkPtr,
char *ModuleName,
int LineNumber
)
{
   /*********/
   /* begin */
   /*********/

   VmGenericFree (&VmCacheZone, ChunkPtr, ModuleName, LineNumber);
}

/*****************************************************************************/
/*
Initialize the virtual memory zone permanent cache memory will be allocated
from.
*/ 

void VmPermCacheInit (int TotalKBytesMax)

{
   static $DESCRIPTOR (ZoneNameDsc, "WASD Perm-Cache");
   static ulong  Algorithm = LIB$K_VM_QUICK_FIT,
                 AlgorithmArg = 128,
                 Flags = LIB$M_VM_BOUNDARY_TAGS |
                         LIB$M_VM_GET_FILL0 |
                         LIB$M_VM_TAIL_LARGE,
                 ExtendPages,
                 InitialPages,
                 BlockBytes = VM_BLOCK_BYTES;

   int  status;
   VM_ZONE  *vzptr = &VmPermCacheZone;

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

   if (WATCH_MODULE(WATCH_MOD_VM) || WATCHON_WATCHOFF)
      WatchThis (WATCHALL, WATCH_MOD_VM,
                 "VmPermCacheInit() !UL", TotalKBytesMax);

   vzptr->MagicValue = VM_MAGIC_PERMCACHE;
   vzptr->MaxPages = VM_PERMCACHE_MAX_PAGES;
   vzptr->MinPages = VM_PERMCACHE_MIN_PAGES;
   vzptr->InitialPages = VM_PERMCACHE_INITIAL_PAGES;
   vzptr->ExtendPages = VM_PERMCACHE_EXTEND_PAGES;
   vzptr->LogicalName = WASD_VM_PERMCACHE;
   vzptr->ReportTitle = "Permanent Cache";
   VmSetPages (vzptr);

   if ((InitialPages = TotalKBytesMax * 2) <= 0) InitialPages = 32;
   ExtendPages = InitialPages / 2;

   /* create the cache virtual memory zone */
   status = lib$create_vm_zone (&vzptr->ZoneId,
                                &Algorithm, &AlgorithmArg, &Flags,
                                &ExtendPages, &InitialPages, &BlockBytes,
                                0, 0, 0, &ZoneNameDsc, 0, 0);
   if (VMSnok (status))
      ErrorExitVmsStatus (status, "lib$create_vm_zone()", FI_LI);

   FaoToStdout ("%HTTPD-I-VM, permanent cache zone initialised\n");
}

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

uchar* VmGetPermCache (ulong ChunkSize)

{
   int  status;

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

   if (WATCH_MODULE(WATCH_MOD_VM) || WATCHON_WATCHOFF)
      WatchThis (WATCHALL, WATCH_MOD_VM,
                 "VmGetPermCache() !UL", ChunkSize);

   if (!VmPermCacheZone.ZoneId) VmPermCacheInit (CacheTotalKBytesMax);

   return (VmGenericCalloc (&VmPermCacheZone, 1, ChunkSize, FI_LI));
}

/*****************************************************************************/
/*
Release back into the virtual memory zone the individually allocated chunks of
permanent cache memory.
*/ 

void VmFreePermCache
(
uchar *ChunkPtr,
char *ModuleName,
int LineNumber
)
{
   /*********/
   /* begin */
   /*********/

   VmGenericFree (&VmPermCacheZone, ChunkPtr, ModuleName, LineNumber);
}

/*****************************************************************************/
/*
Once a minute HttpdTick() calls this function to check how much page file (and
hence virtual memory) has been consumed.  When the default limit has been
exceeded the server proactively exits with an explanatory message.  The logical
name WASD_VM_PGFL_LIMIT defined as an integer 20..99 can change the default
percentage.  The rationale is that it's better to elegantly exit for a known
reason than spit somewhere less obvious because memory wasn't available.
*/ 

void VmCheckPgFlLimit ()

{
   static ulong  JpiPagFilCnt,
                 JpiPgFlQuo;
   static VMS_ITEM_LIST3
   JpiItems [] =
   {
      { sizeof(JpiPagFilCnt), JPI$_PAGFILCNT, &JpiPagFilCnt, 0 },
      { sizeof(JpiPgFlQuo), JPI$_PGFLQUOTA, &JpiPgFlQuo, 0 },
      { 0,0,0,0 }
   };

   int  limit, percent, status;
   char  *cptr;
   char  buf [64];
   IO_SB  IOsb;

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

   limit = VM_DEFAULT_PGFL_LIMIT;
   if (cptr = SysTrnLnm (WASD_VM_PGFL_LIMIT))
   {
      limit = atoi(cptr);
      if (limit < 20 || limit > 99) limit = VM_DEFAULT_PGFL_LIMIT;
   }

   status = sys$getjpiw (EfnWait, 0, 0, &JpiItems, &IOsb, 0, 0);
   if (VMSok (status)) status = IOsb.Status;
   if (VMSnok (status)) ErrorExitVmsStatus (status, NULL, FI_LI);

   percent = 0;
   if (JpiPagFilCnt) percent = 100 - PercentOf32(JpiPagFilCnt, JpiPgFlQuo);
   if (percent > limit)
   {
      sprintf (buf, "current page file usage %d%% exceeds %d%% limit",
               percent, limit);
      ErrorExitVmsStatus (SS$_ABORT, buf, FI_LI);
   }
}

/*****************************************************************************/
/*
Initialize the virtual memory zone DECC malloc(), calloc(), realloc() (and
free()) memory will be allocated from.
*/ 

#if VM_DECC

void VmDeccInit ()

{
   static $DESCRIPTOR (ZoneNameDsc, "WASD DECC");
   static ulong  Algorithm = LIB$K_VM_QUICK_FIT,
                 AlgorithmArg = 64,
                 Flags = LIB$M_VM_BOUNDARY_TAGS |
                         LIB$M_VM_GET_FILL0 |
                         LIB$M_VM_TAIL_LARGE,
                 BlockBytes = VM_BLOCK_BYTES;

   int  status;
   char  *cptr;
   int64  Time64;
   VM_ZONE  *vzptr = &VmDeccZone;

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

   if (WATCH_MODULE(WATCH_MOD_VM) || WATCHON_WATCHOFF)
      WatchThis (WATCHALL, WATCH_MOD_VM, "VmDeccInit()");

   /* we may have to conjure up our own time */
   if (!HttpdTickSecond) HttpdTickSecond = HttpdGetTickSecond();
   /* for the next 10 seconds DECC has a get-out-of-jail-free card */
   VmDeccTickSecond = HttpdTickSecond + 10;

   if (vzptr->ZoneId) return;

   vzptr->MagicValue = VM_MAGIC_DECC;
   vzptr->MaxPages = VM_DECC_MAX_PAGES;
   vzptr->MinPages = VM_DECC_MIN_PAGES;
   vzptr->InitialPages = VM_DECC_INITIAL_PAGES;
   vzptr->ExtendPages = VM_DECC_EXTEND_PAGES;
   vzptr->LogicalName = WASD_VM_DECC;
   vzptr->ReportTitle = "DECC";
   VmSetPages (vzptr);

#if !VM_DECC_LIB_VM
   /* forced disable of zone-based memory management */
   vzptr->ZoneId = (int)-1;
#endif

   /* if zone-based memory management disabled */
   if ((int)vzptr->ZoneId < 0) return; 

   /* create the general virtual memory zone */
   status = lib$create_vm_zone (&vzptr->ZoneId,
                                &Algorithm, &AlgorithmArg, &Flags,
                                &vzptr->ExtendPages,
                                &vzptr->InitialPages,
                                &BlockBytes,  0, 0, 0, &ZoneNameDsc, 0, 0);
   if (VMSnok (status))
      ErrorExitVmsStatus (status, "lib$create_vm_zone()", FI_LI);

   if (HttpdServerStartup)
      FaoToStdout ("%HTTPD-I-VM, DECC zone initialised\n");
}

#endif /* VM_DECC */

/*****************************************************************************/
/*
Allocate memory for DECC use.
*/ 

#if VM_DECC

void* VmDeccCalloc
(
int NumChunks,
int ChunkSize
)
{
   /*********/
   /* begin */
   /*********/

   if (!VmDeccZone.ZoneId) VmDeccInit ();

   return (VmGenericCalloc (&VmDeccZone, NumChunks, ChunkSize, FI_LI));
}

#endif /* VM_DECC */

/*****************************************************************************/
/*
A wrapper for malloc() which just uses calloc().
*/ 

#if VM_DECC

void* VmDeccMalloc (int ChunkSize)

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

   if (!VmDeccZone.ZoneId) VmDeccInit ();

   return (VmGenericCalloc (&VmDeccZone, -1, ChunkSize, FI_LI));
}

#endif /* VM_DECC */

/*****************************************************************************/
/*
Expand an individual a DECC chunk.
See VmDeccCalloc().
*/ 

#if VM_DECC

void* VmDeccRealloc
(
uchar *ChunkPtr,
int ChunkSize
)
{
   /*********/
   /* begin */
   /*********/

   if (!VmDeccZone.ZoneId) VmDeccInit ();

   return (VmGenericRealloc (&VmDeccZone, ChunkPtr, ChunkSize, FI_LI));
}

#endif /* VM_DECC */

/*****************************************************************************/
/*
Release memory allocated for DECC use.
*/ 

#if VM_DECC

void VmDeccFree (uchar *ChunkPtr)

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

   if (!VmDeccZone.ZoneId) VmDeccInit ();

   VmGenericFree (&VmDeccZone, ChunkPtr, FI_LI);
}

#endif /* VM_DECC */

/*****************************************************************************/
/*
Initialise virtual memory management for EXPAT.
*/ 

#if VM_EXPAT

BOOL VmExpatInit ()

{
   static $DESCRIPTOR (ZoneNameDsc, "WASD expat");
   static ulong  Algorithm = LIB$K_VM_QUICK_FIT,
                 AlgorithmArg = 64,
                 Flags = LIB$M_VM_BOUNDARY_TAGS |
                         LIB$M_VM_TAIL_LARGE,
                 BlockBytes = VM_BLOCK_BYTES;

   int  status;
   char  *cptr;
   VM_ZONE  *vzptr = &VmExpatZone;

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

   if (WATCH_MODULE(WATCH_MOD_VM) || WATCHON_WATCHOFF)
      WatchThis (WATCHALL, WATCH_MOD_VM, "VmExpatInit()");

   if ((int)vzptr->ZoneId < 0) return (false);

   vzptr->MagicValue = VM_MAGIC_EXPAT;
   vzptr->MaxPages = VM_EXPAT_MAX_PAGES;
   vzptr->MinPages = VM_EXPAT_MIN_PAGES;
   vzptr->InitialPages = VM_EXPAT_INITIAL_PAGES;
   vzptr->ExtendPages = VM_EXPAT_EXTEND_PAGES;
   vzptr->LogicalName = WASD_VM_EXPAT;
   vzptr->ReportTitle = "EXPAT";
   VmSetPages (vzptr);

   /* if zone-based memory management disabled */
   if (vzptr->ZoneId == (ulong)-1) return (false); 

   /* create the expat virtual memory zone */
   status = lib$create_vm_zone (&vzptr->ZoneId,
                                &Algorithm, &AlgorithmArg, &Flags,
                                &vzptr->ExtendPages,
                                &vzptr->InitialPages,
                                &BlockBytes, 0, 0, 0, &ZoneNameDsc, 0, 0);
   if (VMSnok (status))
      ErrorExitVmsStatus (status, "lib$create_vm_zone()", FI_LI);

   FaoToStdout ("%HTTPD-I-VM, EXPAT zone initialised\n");

   return (true);
}

#else /* VM_EXPAT */

/******************/
/* NOT VM_EXPAT */
/******************/

BOOL VmExpatInit () { return (false); }

/* allow ExpatInit() linkages without recompile */
void* VmExpatMalloc (int ChunkSize) { return (NULL); }
void* VmExpatRealloc (uchar *ChunkPtr, int ChunkSize ) { return (NULL); }
void VmExpatFree (uchar *ChunkPtr) { return; }

#endif /* VM_EXPAT */

/*****************************************************************************/
/*
Allocate memory for EXPAT use.
*/ 

#if VM_EXPAT

void* VmExpatMalloc (int ChunkSize)

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

   return (VmGenericCalloc (&VmExpatZone, -1, ChunkSize, FI_LI));
}

#endif /* VM_EXPAT */

/*****************************************************************************/
/*
Expand an individual EXPAT chunk.  See VmGenericCalloc().
*/ 

#if VM_EXPAT

void* VmExpatRealloc
(
uchar *ChunkPtr,
int ChunkSize
)
{
   /*********/
   /* begin */
   /*********/

   return (VmGenericRealloc (&VmExpatZone, ChunkPtr, ChunkSize, FI_LI));
}

#endif /* VM_EXPAT */

/*****************************************************************************/
/*
Release memory allocated for EXPAT use.
*/ 

#if VM_EXPAT

void VmExpatFree (uchar *ChunkPtr)

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

   VmGenericFree (&VmExpatZone, ChunkPtr, FI_LI);
}

#endif /* VM_EXPAT */

/*****************************************************************************/
/*
Initialise virtual memory management for OpenSSL.
*/ 

#if VM_OPENSSL

BOOL VmOpenSslInit ()

{
   static $DESCRIPTOR (ZoneNameDsc, "WASD OpenSSL");
   static ulong  Algorithm = LIB$K_VM_QUICK_FIT,
                 AlgorithmArg = 128,
                 Flags = LIB$M_VM_BOUNDARY_TAGS |
                         LIB$M_VM_TAIL_LARGE,
                 BlockBytes = VM_BLOCK_BYTES;

   int  status;
   char  *cptr;
   VM_ZONE  *vzptr = &VmOpenSslZone;

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

   if (WATCH_MODULE(WATCH_MOD_VM) || WATCHON_WATCHOFF)
      WatchThis (WATCHALL, WATCH_MOD_VM, "VmOpenSslInit()");

   if ((int)vzptr->ZoneId < 0) return (false);

   vzptr->MagicValue = VM_MAGIC_OPENSSL;
   vzptr->MaxPages = VM_OPENSSL_MAX_PAGES;
   vzptr->MinPages = VM_OPENSSL_MIN_PAGES;
   vzptr->InitialPages = VM_OPENSSL_INITIAL_PAGES;
   vzptr->ExtendPages = VM_OPENSSL_EXTEND_PAGES;
   vzptr->LogicalName = WASD_VM_OPENSSL;
   vzptr->ReportTitle = "OpenSSL";
   VmSetPages (vzptr);

   /* if zone-based memory management disabled */
   if (vzptr->ZoneId == (ulong)-1) return (false); 

   /* create the OpenSSL virtual memory zone */
   status = lib$create_vm_zone (&vzptr->ZoneId,
                                &Algorithm, &AlgorithmArg, &Flags,
                                &vzptr->ExtendPages,
                                &vzptr->InitialPages,
                                &BlockBytes, 0, 0, 0, &ZoneNameDsc, 0, 0);
   if (VMSnok (status))
      ErrorExitVmsStatus (status, "lib$create_vm_zone()", FI_LI);

   FaoToStdout ("%HTTPD-I-VM, OpenSSL zone initialised\n");

   return (true);
}

#else /* VM_OPENSSL */

/******************/
/* NOT VM_OPENSSL */
/******************/

BOOL VmOpenSslInit () { return (false); }

/* allow SesolaInit() linkages without recompile */
void* VmOpenSslMalloc (int ChunkSize) { return (NULL); }
void* VmOpenSslRealloc (uchar *ChunkPtr, int ChunkSize ) { return (NULL); }
void VmOpenSslFree (uchar *ChunkPtr) { return; }

#endif /* VM_OPENSSL */

/*****************************************************************************/
/*
Allocate memory for OpenSSL use.
NOTE: OpenSSL does not employ calloc().
*/ 

#if VM_OPENSSL

void* VmOpenSslMalloc (int ChunkSize)

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

   return (VmGenericCalloc (&VmOpenSslZone, -1, ChunkSize, FI_LI));
}

#endif /* VM_OPENSSL */

/*****************************************************************************/
/*
Expand an individual OpenSSL chunk.  See VmGenericCalloc().
*/ 

#if VM_OPENSSL

void* VmOpenSslRealloc
(
uchar *ChunkPtr,
int ChunkSize
)
{
   /*********/
   /* begin */
   /*********/

   return (VmGenericRealloc (&VmOpenSslZone, ChunkPtr, ChunkSize, FI_LI));
}

#endif /* VM_OPENSSL */

/*****************************************************************************/
/*
Release memory allocated for OpenSSL use.
*/ 

#if VM_OPENSSL

void VmOpenSslFree (uchar *ChunkPtr)

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

   VmGenericFree (&VmOpenSslZone, ChunkPtr, FI_LI);
}

#endif /* VM_OPENSSL */

/*****************************************************************************/
/*
Set initial and extend pages for the zone.
Set initial to -1 to disable *some* VM zones.
Set initial to 0 to enable *some* VM zones and have default values.
Default <integer> values are pages, and if a trailing 'p'.  If a trailing 'b'
then indicates bytes, or if 'm' megabytes (or mebibytes if you prefer), and
pages are calculated using <bytes> / 512 plus 5%.
Some zones allow tuning (dynamic adjustment to initial allocation) and will
accept an additional two integers, the initial sample size, and how long that
tune is maintained before reset.
*/ 

void VmSetPages (VM_ZONE *ZonePtr)

{
   long  ExtendPages = 0,
         InitialPages = 0,
         TuneSample = -1,
         TunePeriod = -1;
   char  *cptr;

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

   sys$gettim (&ZonePtr->LastLookTime64); 

   /* disabled by default - only some callers pay attention to disabled */
   ZonePtr->ZoneId = (ulong)-1;

   if (!(cptr = SysTrnLnm (ZonePtr->LogicalName))) return;

   /* logical name value should be "<initial>[,extend>]" integers */
   while (*cptr && !isdigit(*cptr) && *cptr != '-') cptr++;
   InitialPages = atol(cptr);
   if (*cptr == '-') cptr++;
   while (*cptr && isdigit(*cptr)) cptr++;
   if (to_lower(*cptr) == 'b' || to_lower(*cptr) == 'm')
   {
      if (InitialPages > 0)
      {
         if (to_lower(*cptr) == 'm') InitialPages *= 1048576;
         InitialPages /= 512;
         InitialPages += (InitialPages * 5) / 100;
      }
      while (*cptr && isalpha(*cptr)) cptr++;
   }

   while (*cptr && !isdigit(*cptr) && *cptr != '-') cptr++;
   ExtendPages = atol(cptr);
   if (*cptr == '-') cptr++;
   while (*cptr && isdigit(*cptr)) cptr++;
   if (to_lower(*cptr) == 'b' || to_lower(*cptr) == 'm')
   {
      if (ExtendPages > 0)
      {
         if (to_lower(*cptr) == 'm') ExtendPages *= 1048576;
         ExtendPages /= 512;
         ExtendPages += (ExtendPages * 5) / 100;
      }
      while (*cptr && isalpha(*cptr)) cptr++;
   }

   /* value can have optional "[,<tune-sample>[,<tune-period>]" integers */
   while (*cptr && !isdigit(*cptr)) cptr++;
   if (*cptr) TuneSample = atol(cptr);
   while (*cptr && isdigit(*cptr)) cptr++;
   while (*cptr && !isdigit(*cptr)) cptr++;
   if (*cptr) TunePeriod = atol(cptr);
   while (*cptr && isdigit(*cptr)) cptr++;


   /* only some callers pay attention to disabled zones */
   if (InitialPages >= 0)
   {
      /* zone-based management enabled */
      ZonePtr->ZoneId = 0;
      if (!InitialPages) InitialPages = ZonePtr->InitialPages;
      if (!ExtendPages) ExtendPages = ZonePtr->ExtendPages;

      if (InitialPages > ZonePtr->MaxPages)
         InitialPages = ZonePtr->MaxPages;
      else
      if (InitialPages <= ZonePtr->MinPages)
         InitialPages = ZonePtr->MinPages;
      ZonePtr->InitialPages = InitialPages;

      if (ExtendPages > ZonePtr->MaxPages)
         ExtendPages = ZonePtr->MaxPages;
      else
      if (ExtendPages <= ZonePtr->MinPages)
         ExtendPages = ZonePtr->MinPages;
      ZonePtr->ExtendPages = ExtendPages;

      /* not all zones pay attention to these settings */
      if (TuneSample >= 0)
         ZonePtr->TuneSample = TuneSample;
      else
         ZonePtr->TuneSample = VM_REQUEST_TUNE_SAMPLE;
      if (TunePeriod >= 0)
         ZonePtr->TunePeriod = TunePeriod;
      else
         ZonePtr->TunePeriod = VM_REQUEST_TUNE_PERIOD;
   }
}

/*****************************************************************************/
/*
Return a report on processes' virtual memory usage.  This function blocks while
executing.
*/ 

#define SHOW_VM_64 0

#ifndef SHOW_VM_64
#  define SHOW_VM_64 1
#endif

#if SHOW_VM_64
/* prototype */
int VmWrite64 (struct dsc$descriptor*, int64);
#endif

#if VM_BACKPORT_FOR_DTAG  /* see VM.H */
void Vm__Report (REQUEST_STRUCT*);
void VmReport (REQUEST_STRUCT *rqptr, REQUEST_AST NextTaskFunction)
{
   Vm__Report (rqptr);
   SysDclAst (NextTaskFunction, rqptr);
}
void Vm__Report (REQUEST_STRUCT *rqptr)
#else
void VmReport (REQUEST_STRUCT *rqptr)
#endif
{
   static ulong  ShowVmCode123 = 0,
                 ShowVmCode567 = 4,
                 ShowVmZoneDetail = 3;

#if SHOW_VM_64
   static int64  ShowVm64Code123 = 0,
                   ShowVm64Code567 = 4,
                   ShowVm64ZoneDetail = 3;
#endif /* SHOW_VM_64 */

   static char  BeginPageFao [] =
"<p><table class=\"ctgry\">\n\
<tr><td>\n\
<table class=\"lftlft\">\n\
<tr><td><pre>\
Request Heap Initial Pages: !UL (!ULkB) <!!-- !UL !UL !&,@SQ/!&,@SQ -->\n\
              Extend Pages: !UL (!ULkB)\n\
\n\
 HTTP/2 Heap Initial Pages: !UL (!ULkB)\n\
              Extend Pages: !UL (!ULkB)\n\
\n\
WsSize: !UL (!ULMB)  WsPeak: !UL (!ULMB)  VirtPeak: !UL (!ULMB)\n\
\n\
  PgFl: !UL/!UL (!UL/!ULMB !UL%)\n\
\n";

   static char  EndPageFao [] =
"</pre></td></tr>\n\
</table>\n\
</td></tr>\n\
</table>\n\
</body>\n\
</html>\n";

   static ulong  JpiPageFlts,
                 JpiPagFilCnt,
                 JpiVirtPeak,
                 JpiWsSize,
                 JpiWsPeak,
                 Pid = 0;

   static VMS_ITEM_LIST3  JpiItems [] =
   {
      { sizeof(JpiPageFlts), JPI$_PAGEFLTS, &JpiPageFlts, 0 },
      { sizeof(JpiPagFilCnt), JPI$_PAGFILCNT, &JpiPagFilCnt, 0 },
      { sizeof(JpiVirtPeak), JPI$_VIRTPEAK, &JpiVirtPeak, 0 },
      { sizeof(JpiWsSize), JPI$_WSSIZE, &JpiWsSize, 0 },
      { sizeof(JpiWsPeak), JPI$_WSPEAK, &JpiWsPeak, 0 },
      {0,0,0,0}
   };

   int  cnt, status;
   ushort  Length;
   ulong  Context,
          ZoneId;
   char  Buffer [4096];
   IO_SB  IOsb;
   $DESCRIPTOR (BufferDsc, Buffer);

#if SHOW_VM_64
   int64  Context64,
          ZoneId64;
#endif /* SHOW_VM_64 */

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

   if (WATCH_MODULE(WATCH_MOD_VM) || WATCHON_WATCHOFF)
      WatchThis (WATCHALL, WATCH_MOD_VM, "VmReport()");

   status = sys$getjpiw (EfnWait, &Pid, 0, &JpiItems, &IOsb, 0, 0);
   if (VMSok (status)) status = IOsb.Status;
   if (VMSnok (status))
   {
      rqptr->rqResponse.ErrorTextPtr = "sys$getjpiw()";
      ErrorVmsStatus (rqptr, status, FI_LI);

#if !VM_BACKPORT_FOR_DTAG
      AdminEnd (rqptr);
#endif
      return;
   }

   VmReportPlus = strsame (rqptr->rqHeader.RequestUriPtr,
                           ADMIN_REPORT_MEMORY_PLUS, -1);

   if (VmReportPlus)
      AdminPageTitle (rqptr, "Virtual Memory+ Report");
   else
      AdminPageTitle (rqptr, "Virtual Memory Report");

   status = FaoToNet (rqptr, BeginPageFao,
                      VmRequestZone.StatInitial ?
                         VmRequestZone.StatInitial :
                         VmRequestZone.InitialPages,
                      VmRequestZone.StatInitial ?
                         VmRequestZone.StatInitial * 512 / 1024 :
                         VmRequestZone.InitialPages * 512 / 1024,
                      VmRequestZone.TuneSample,
                      VmRequestZone.TunePeriod,
                      &VmRequestZone.StatPages,
                      &VmRequestZone.StatCount,
                      VmRequestZone.ExtendPages,
                      VmRequestZone.ExtendPages * 512 / 1024,
                      VmHttp2Zone.InitialPages, VmHttp2Zone.InitialPages * 512 / 1024,
                      VmHttp2Zone.ExtendPages, VmHttp2Zone.ExtendPages * 512 / 1024,
                      JpiWsSize, JpiWsSize * 512 / 1048576,
                      JpiWsPeak, JpiWsPeak * 512 / 1048576,
                      JpiVirtPeak, JpiVirtPeak * 512 / 1048576,
                      JpiPagFilCnt, HttpdProcess.PgFlQuo,
                      ((ulong)JpiPagFilCnt * 512) / 1048576,
                      ((ulong)HttpdProcess.PgFlQuo * 512) / 1048576,
                      100 - PercentOf32((ulong)JpiPagFilCnt,(ulong)HttpdProcess.PgFlQuo));
   if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);

#if VM_DECC
   VmReportZone (rqptr, &VmDeccZone);
   VmReportZone (rqptr, &VmGeneralZone);
   VmReportZone (rqptr, &VmCacheZone);
   VmReportZone (rqptr, &VmPermCacheZone);
#endif /* VM_DECC */

#if VM_OPENSSL
   if (VmOpenSslZone.ZoneId != (ulong)-1) VmReportZone (rqptr, &VmOpenSslZone);
#endif

#if VM_EXPAT
   if (VmExpatZone.ZoneId != (ulong)-1) VmReportZone (rqptr, &VmExpatZone);
#endif

   if (VMSnok (status = lib$show_vm (&ShowVmCode123, &VmWrite, rqptr)))
      FaoToNet (rqptr, "lib$show_vm() !&S\n", status);

   if (VMSnok (status = lib$show_vm (&ShowVmCode567, &VmWrite, rqptr)))
      FaoToNet (rqptr, "lib$show_vm() !&S\n", status);

   FaoToNet (rqptr, "\n");

   Context = 0;
   while ((status = lib$find_vm_zone (&Context, &ZoneId)) == SS$_NORMAL)
   {
      status = lib$show_vm_zone (&ZoneId, &ShowVmZoneDetail, &VmWrite, rqptr);
      if (VMSnok (status)) FaoToNet (rqptr, "lib$show_vm_zone() !&S\n", status);
   }
   if (status != LIB$_NOTFOU)
      FaoToNet (rqptr, "lib$find_vm_zone() !&S\n", status);

   /**********/
#if SHOW_VM_64
   /**********/

   if (VMSnok (status = lib$show_vm_64 (&ShowVm64Code123, &VmWrite,
                                        (int64)rqptr)))
      FaoToNet (rqptr, "lib$show_vm_64() !&S\n", status);

   if (VMSnok (status = lib$show_vm_64 (&ShowVm64Code567, &VmWrite,
                                        (int64)rqptr)))
      FaoToNet (rqptr, "lib$show_vm_64() !&S\n", status);

   FaoToNet (rqptr, "\n");

   Context64 = 0;
   while ((status = lib$find_vm_zone_64 (&Context64, &ZoneId64)) == SS$_NORMAL)
   {
      status = lib$show_vm_zone_64 (&ZoneId64, &ShowVm64ZoneDetail, &VmWrite64,
                                    (int64)rqptr);
      if (VMSnok (status))
         FaoToNet (rqptr, "lib$show_vm_zone_64() !&S\n", status);
   }
   if (status != LIB$_NOTFOU)
      FaoToNet (rqptr, "lib$find_vm_zone_64() !&S\n", status);

#endif /* SHOW_VM_64 */

   FaolToNet (rqptr, EndPageFao, NULL);

   rqptr->rqResponse.PreExpired = PRE_EXPIRE_ADMIN;
   ResponseHeader200 (rqptr, "text/html", &rqptr->NetWriteBufferDsc);

#if !VM_BACKPORT_FOR_DTAG
   AdminEnd (rqptr);
#endif
}

/*****************************************************************************/
/*
See VmReportZone().
*/ 

void VmDebugOpenSslZone (char *module, int lnumber)

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

   fprintf (stdout, "***** %s:%d *****\n", module, lnumber);
   VmReportZone (NULL, &VmOpenSslZone);
}

/*****************************************************************************/
/*
If |rqptr| is NULL this is written to <stdout> allowing it to be used for
inline code debugging.   Suggest something like the immediately above.
*/ 

void VmReportZone
(
REQUEST_STRUCT *rqptr,
VM_ZONE *ZonePtr
)
{
   static char  HeapFao [] =
"!AZ Heap Initial Pages: !UL (!ULkB)\n\
!#AZ       Extend Pages: !UL (!ULkB)\n";

   static char  StatsFao [] =
"!AZ Statistics (<span style=\"font-size:90%\">LIB$VM..</span>)\n";

   static char  DataFao [] =
"    calloc()  !&,UL\n\
    malloc()  !&,UL\n\
   realloc()  !&,UL\n\
      free()  !&,UL\n\
   Allocated  !&,@SQ bytes\n\
 Deallocated  !&,@SQ bytes\n\
Still In Use  !&,@SQ bytes  (!AZ!AZ&#8710; !AZ!&,@SQ !SL%)\n\
         Max  !&,@SQ bytes\n";

   static char  PlusMinus [2] = { 0, 0 };

   int  blen, status, percentage;
   ushort  slen;
   int64  Delta64;
   char  *bptr;
   char LastLookAgo [64],
        ReportBuffer [1024];

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

   if (WATCH_MODULE(WATCH_MOD_VM) || WATCHON_WATCHOFF)
      WatchThis (WATCHALL, WATCH_MOD_VM, "VmReportZone()");

   if (!ZonePtr->ZoneId) return;

   ThisLongAgo (&ZonePtr->LastLookTime64, LastLookAgo);

   if (!ZonePtr->LastLookBytes)
      Delta64 = 0;
   else
      Delta64 = ZonePtr->InUseBytes - ZonePtr->LastLookBytes;

   if (Delta64 > 0)
      PlusMinus[0] = '+';
   else
      PlusMinus[0] = '\0';

   if (Delta64)
      percentage = (int)((Delta64 * 100) / ZonePtr->InUseBytes);
   else
      percentage = 0;

   bptr = ReportBuffer;
   blen = sizeof(ReportBuffer)-1;

   /* if zone-based memory management disabled */
   if (ZonePtr->ZoneId == (ulong)-1)
      status = FaoToBuffer (bptr, blen, &slen, StatsFao,
                            ZonePtr->ReportTitle);
   else
      status = FaoToBuffer (bptr, blen, &slen, HeapFao,
                            ZonePtr->ReportTitle,
                            ZonePtr->InitialPages,
                            ZonePtr->InitialPages * 512 / 1024,
                            strlen(ZonePtr->ReportTitle), "                 ",
                            ZonePtr->ExtendPages,
                            ZonePtr->ExtendPages * 512 / 1024);
   bptr += slen;
   blen -= slen;

   if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);

   if (rqptr && slen)
   {
      *bptr++ = '\n';
      blen--;
   }

   status = FaoToBuffer (bptr, blen, &slen, DataFao,
                         ZonePtr->CallocCount,
                         ZonePtr->MallocCount,
                         ZonePtr->ReallocCount,
                         ZonePtr->FreeCount,
                         &ZonePtr->AllocBytes,
                         &ZonePtr->DeallocBytes,
                         &ZonePtr->InUseBytes,
                         LastLookAgo, LastLookAgo[0] ? " " : "",
                         PlusMinus, &Delta64, percentage,
                         &ZonePtr->InUseMaxBytes);
   bptr += slen;
   blen -= slen;

   if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);

   if (rqptr && slen)
   {
      *bptr++ = '\n';
      blen--;
   }
   *bptr = '\0';

   if (rqptr)
      FaoToNet (rqptr, "!AZ", ReportBuffer);
   else
      fwrite (ReportBuffer, bptr-ReportBuffer, 1, stdout);

   ZonePtr->LastLookBytes = ZonePtr->InUseBytes;
   sys$gettim (&ZonePtr->LastLookTime64); 
}

/*****************************************************************************/
/*
Action routine for lib$show_vm*() routines.  Simply write the contents of the
parameter descriptor to the client plus a newline character - using some
specially crafted HTML forbidden character escapes allowing an anchor to shine
through.
*/ 

int VmWrite
(
struct dsc$descriptor *DscPtr,
REQUEST_STRUCT *rqptr
)
{
   static int  EmptyLineCount;

   int  status;
   char  buf [1024];
   char  *cptr, *czptr, *sptr, *zptr;

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

   if (WATCH_MODULE(WATCH_MOD_VM) || WATCHON_WATCHOFF)
      WatchThis (WATCHALL, WATCH_MOD_VM, "VmWrite()");

#if 0
   FaoToNet (rqptr, "!UL |!-!#AZ|\n", DscPtr->dsc$w_length,
                                      DscPtr->dsc$a_pointer);
#endif

   czptr = (cptr = DscPtr->dsc$a_pointer) + DscPtr->dsc$w_length;

   /* Zone Id = 7B65E290,  Zone name = "DEFAULT_ZONE" */
   if (cptr < czptr && MATCH8 (cptr, "Zone Id "))
   {
      if (!VmReportPlus)
      {
         for (sptr = cptr + 20; sptr < czptr; sptr++)
         {
            if (MATCH8 (sptr, "Zone nam"))
            {
               sptr += 13;
               if (!MATCH4 (sptr, "WASD"))
               {
                  VmReportThisOne = false;
                  return (SS$_NORMAL);
               }
               break;
            }
         }
      }
      VmReportThisOne = true;
      EmptyLineCount = 0;

      FaoToNet (rqptr, "!90*-\n\n");
      zptr = (sptr = buf) + sizeof(buf)-8;
      while (cptr < czptr && sptr < zptr)
      {
         if (*cptr == '<')
         {
            if (SAME3 (cptr, '<a '))
               *sptr++ = *cptr++;
            else
            if (SAME4 (cptr, '</a>'))
            {
               SET4 (sptr, '</a>');
               sptr += 4;
               cptr += 4;
            }
            else
            {
               SET4 (sptr, '&lt;');
               sptr += 4;
               cptr++;
            }
         }
         else
         if (SAME2 (cptr, '\">'))
         {
            SET2 (sptr, '\">');
            sptr += 2;
            cptr += 2;
         }
         else
         if (*cptr == '>')
         {
            SET4 (sptr, '&gt;');
            sptr += 4;
            cptr++;
         }
         else
         if (*cptr == '&')
         {
            SET4 (sptr, '&amp');
            sptr += 4;
            *sptr++ = ';';
            cptr++;
         }
         else
            *sptr++ = *cptr++;
      }
   }
   else
   if (VmReportThisOne)
   {
      if (cptr < czptr)
         EmptyLineCount = 0;
      else
      if (EmptyLineCount++)
         return (SS$_NORMAL);

      zptr = (sptr = buf) + sizeof(buf)-8;
      while (cptr < czptr && sptr < zptr)
         *sptr++ = *cptr++;
   }
   else
      return (SS$_NORMAL);

   *sptr++ = '\n';
   *sptr = '\0';

   NetWriteBuffered (rqptr, NULL, buf, sptr-buf);

   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
Wrapper for VmWrite().
*/ 

#if SHOW_VM_64

int VmWrite64
(
struct dsc$descriptor *DscPtr,
int64 UserParam  /* 32 bit pointer to the request */
)
{
   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD_VM) || WATCHON_WATCHOFF)
      WatchThis (WATCHALL, WATCH_MOD_VM, "VmWrite64()");

   VmWrite (DscPtr, (ulong)UserParam);

   return (SS$_NORMAL);
}

#endif /* SHOW_VM_64 */

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

void VmDebug (ulong ZoneId)

{
#ifdef DBUG

   static ulong  ShowVmZoneDetail = 3;

   int status;
   ulong Context;

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

   if (WATCH_MODULE(WATCH_MOD_VM) || WATCHON_WATCHOFF)
      WatchThis (WATCHALL, WATCH_MOD_VM, "VmDebug() !UL !60*-", ZoneId);

   if (ZoneId)
   {
      status = lib$show_vm_zone (&ZoneId, &ShowVmZoneDetail, 0, 0);
      if (VMSnok (status)) exit (status);
   }
   else
   {
      Context = 0;
      while ((status = lib$find_vm_zone (&Context, &ZoneId)) == SS$_NORMAL)
      {
         status = lib$show_vm_zone (&ZoneId, &ShowVmZoneDetail, 0, 0);
         if (VMSnok (status)) exit (status);
      }
   }
   if (WATCH_MODULE(WATCH_MOD_VM) || WATCHON_WATCHOFF)
      WatchDataFormatted ("----------\n");

#endif
}

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