/*****************************************************************************
/*
                                   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.

A zone for fixed sized memory blocks is created for the request structures.

An individual zone is created for each request's heap.  Hopefully the overhead
of creating zone will be offset by the lower overhead probably 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.

A separate zone is created for each of volatile and permanent cache memory.

MEMORY MANAGEMENT IS CONSIDERED CRUCIAL TO HTTPD FUNCTIONING. IF AN 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!


VERSION HISTORY
---------------
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 NetWriteFaol(), 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_V6
#undef _VMS_V6_SOURCE
#define _VMS_V6_SOURCE
#undef __VMS_VER
#define __VMS_VER 60000000
#undef __CRTL_VER
#define __CRTL_VER 60000000
#endif

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

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

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

#define WASD_MODULE "VM"

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

/* number of pages in initial and extend request virtual memory zone */
#define VM_REQUEST_SIZE_PAGES 40
#define VM_REQUEST_SIZE_PAGES_MIN  24
#define VM_REQUEST_SIZE_PAGES_MAX 128
#define VM_REQUEST_SIZE_PAGES_STAT_MAX 256

/* align on an octaword boundary (the default anyway!) */
#define VM_ALIGNMENT 8

/* for zone with realloc functions use the first longword to store the size */
#define VM_OFFSET sizeof(unsigned long)

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

unsigned long  VmCacheVmZoneId,
               VmGeneralVmZoneId,
               VmPermCacheVmZoneId,
               VmRequestSizePages,
               VmRequestVmZoneId;

/* statistics are accumulated in a combination of ints and the structure */
#define VM_STATS_MAXMIN 0xffffffff

unsigned long  VmHeapStatsCount,
               VmHeapStatsGetCountMax,
               VmHeapStatsGetCountMin,
               VmHeapStatsFreeCountMax,
               VmHeapStatsFreeCountMin,
               VmHeapStatsReallocCountMax,
               VmHeapStatsReallocCountMin,
               VmHeapStatsResetCount;

unsigned long  VmHeapStatsPageCount [(VM_REQUEST_SIZE_PAGES_STAT_MAX/4)+1];

struct  RequestHeapStatsStruct  VmHeapStats;

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

extern int  CacheEntryKBytesMax,
            CacheTotalKBytesMax;

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

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

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

VmGeneralInit ()

{
   static $DESCRIPTOR (ZoneNameDsc, "HTTPd General");
   static unsigned long  Algorithm = LIB$K_VM_QUICK_FIT,
                         AlgorithmArg = 64,
                         Flags = LIB$M_VM_BOUNDARY_TAGS |
                                 LIB$M_VM_GET_FILL0 |
                                 LIB$M_VM_TAIL_LARGE,
                         ExtendSize = 4096,
                         InitialSize = 4096,
                         BlockSize = 64;

   int  status;

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

   if (WATCH_MODULE(WATCH_MOD_VM))
      WatchThis (NULL, FI_LI, WATCH_MOD_VM, "VmGeneralInit()");

   /* create the cache virtual memory zone */
   status = lib$create_vm_zone (&VmGeneralVmZoneId,
                                &Algorithm, &AlgorithmArg, &Flags,
                                &ExtendSize, &InitialSize, &BlockSize,
                                0, 0, 0, &ZoneNameDsc, 0, 0);
   if (VMSnok (status))
      ErrorExitVmsStatus (status, "lib$create_vm_zone()", FI_LI);
}

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

char* VmGet (unsigned long ChunkSize)

{
   int  status;
   unsigned long  BaseAddress;

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

   if (WATCH_MODULE(WATCH_MOD_VM))
      WatchThis (NULL, FI_LI, WATCH_MOD_VM, "VmGet() !UL", ChunkSize);

   ChunkSize += VM_OFFSET + VM_ELBOW_ROOM;
   status = lib$get_vm (&ChunkSize, &BaseAddress, &VmGeneralVmZoneId);
   if (VMSnok (status)) ErrorExitVmsStatus (status, "lib$get_vm()", FI_LI);

   *(unsigned long*)BaseAddress = ChunkSize-VM_OFFSET;
   return ((unsigned char*)BaseAddress+VM_OFFSET);
}

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

char* VmRealloc
(
char *ChunkPtr,
unsigned long ChunkSize,
char *SourceModuleName,
int SourceLineNumber
)
{
   int  status,
        CurrentChunkSize;
   unsigned long  BaseAddress,
                  FreeBaseAddress;

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

   if (WATCH_MODULE(WATCH_MOD_VM))
   {
      if (ChunkPtr)
         CurrentChunkSize = *(unsigned long*)(ChunkPtr-VM_OFFSET);
      else
         CurrentChunkSize = 0;
      WatchThis (NULL, FI_LI, WATCH_MOD_VM,
                 "VmRealloc() !UL !UL", CurrentChunkSize, ChunkSize);
   }

   if (!ChunkPtr) return (VmGet (ChunkSize));

   /* if this chunk satisfies the reallocation then just return it */
   CurrentChunkSize = *(unsigned long*)(ChunkPtr-VM_OFFSET);
   if (CurrentChunkSize >= ChunkSize) return (ChunkPtr);

   /* allocate a new, larger chunk */
   ChunkSize += VM_OFFSET + VM_ELBOW_ROOM;
   status = lib$get_vm (&ChunkSize, &BaseAddress, &VmGeneralVmZoneId);
   if (VMSnok (status))
      ErrorExitVmsStatus (status, "lib$get_vm()",
                          SourceModuleName, SourceLineNumber);

   /* copy the existing chunk into the new chunk */
   memcpy ((unsigned char*)BaseAddress+VM_OFFSET, ChunkPtr, CurrentChunkSize);

   /* free the previous chunk */
   FreeBaseAddress = ChunkPtr-VM_OFFSET;
   status = lib$free_vm (0, &FreeBaseAddress, &VmGeneralVmZoneId);
   if (VMSnok (status))
      ErrorExitVmsStatus (status, "lib$free_vm()", 
                          SourceModuleName, SourceLineNumber);

   *(unsigned long*)BaseAddress = ChunkSize-VM_OFFSET;
   return ((unsigned char*)BaseAddress+VM_OFFSET);
}

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

VmFree
(
char *ChunkPtr,
char *SourceModuleName,
int SourceLineNumber
)
{
   int  status,
        CurrentChunkSize;
   unsigned long  BaseAddress;

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

   if (WATCH_MODULE(WATCH_MOD_VM))
   {
      if (ChunkPtr)
         CurrentChunkSize = *(unsigned long*)(ChunkPtr-VM_OFFSET);
      else
         CurrentChunkSize = 0;
      WatchThis (NULL, FI_LI, WATCH_MOD_VM, "VmFree() !UL", CurrentChunkSize);
   }

   BaseAddress = ChunkPtr-VM_OFFSET;
   status = lib$free_vm (0, &BaseAddress, &VmGeneralVmZoneId);
   if (VMSnok (status))
      ErrorExitVmsStatus (status, "lib$free_vm()",
                          SourceModuleName, SourceLineNumber);
}

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

VmRequestInit (int MaxConnections)

{
   static $DESCRIPTOR (ZoneNameDsc, "HTTPd Request");
   static unsigned long  Algorithm = LIB$K_VM_QUICK_FIT,
                         AlgorithmArg = 32,
                         Flags = LIB$M_VM_BOUNDARY_TAGS |
                                 LIB$M_VM_GET_FILL0 |
                                 LIB$M_VM_TAIL_LARGE,
                         InitialSize,
                         BlockSize = 64;
   int  status;
   char  *cptr;

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

   if (WATCH_MODULE(WATCH_MOD_VM))
      WatchThis (NULL, FI_LI, WATCH_MOD_VM,
                 "VmRequestInit() !UL", MaxConnections);

   cptr = getenv("WASD_VM_REQUEST_SIZE_PAGES");
   if (cptr)
      VmRequestSizePages = atol(cptr);
   else
   {
      InstanceMutexLock (INSTANCE_MUTEX_HTTPD);
      VmRequestSizePages = HttpdGblSecPtr->VmRequestSizePages;
      InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);
   }
   if (VmRequestSizePages < 24 || VmRequestSizePages > 128)
      VmRequestSizePages = VM_REQUEST_SIZE_PAGES;

   InitialSize = (MaxConnections *
                  sizeof(REQUEST_STRUCT)) / 512 + 1;

   /* create the cache virtual memory zone */
   status = lib$create_vm_zone (&VmRequestVmZoneId,
                                &Algorithm, &AlgorithmArg, &Flags,
                                0, &InitialSize, &BlockSize,
                                0, 0, 0, &ZoneNameDsc, 0, 0);
   if (VMSnok (status))
      ErrorExitVmsStatus (status, "lib$create_vm_zone()", FI_LI);

   VmHeapStatsGetCountMin =
      VmHeapStatsFreeCountMin =
      VmHeapStatsReallocCountMin =
      VmHeapStats.FreeByteMin =
      VmHeapStats.GetByteMin =
      VmHeapStats.ReallocByteMin = VM_STATS_MAXMIN;
}

/*****************************************************************************/
/*
Called periodically by HttpTick() and also on an ad hoc basis when VmReport()
is used.  Check most commonly required request heap pages and store it away for
the next startup.  I know the way the pages are calculated before being stored
into the array (VmFreeRequest()) is not 100% percent accurate with what may
have actually been used by LIB$VM but as this statistic is not available from
anywhere I know of and it's not worth fooling around EXEC code to get it this
will be close enough for jazz.
*/ 

VmRequestTune ()

{
   int  idx;
   unsigned long  PageCount,
                  RequestCount,
                  RequestCountTotal;

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

   if (WATCH_MODULE(WATCH_MOD_VM))
      WatchThis (NULL, FI_LI, WATCH_MOD_VM, "VmRequestTune()");

   PageCount = RequestCount = RequestCountTotal = 0;

   for (idx = 0; idx < VM_REQUEST_SIZE_PAGES_MAX / 4; idx++)
      RequestCountTotal += VmHeapStatsPageCount[idx];

   /* only take any notice of this if there's been at least 1,000 requests */
   if (RequestCountTotal < 1000) return;

   for (idx = 0; idx < VM_REQUEST_SIZE_PAGES_MAX / 4; idx++)
   {
      if (!VmHeapStatsPageCount[idx]) continue;
      RequestCount += VmHeapStatsPageCount[idx];
      /* if the percentage calculation would overflow forget it! */
      if (RequestCount * 100 < RequestCount) return;
      /* if this still accounts for less than eighty percent of requests */
      if (RequestCount * 100 / RequestCountTotal < 80) continue;
      break;
   }
   PageCount = (idx + 1) * 4;
   if (WATCH_MODULE(WATCH_MOD_VM))
      WatchDataFormatted ("!UL !UL !UL% !UL\n",
                          RequestCountTotal, RequestCount,
                          RequestCount * 100 / RequestCountTotal, PageCount);
   if (PageCount)
   {
      InstanceMutexLock (INSTANCE_MUTEX_HTTPD);
      HttpdGblSecPtr->VmRequestSizePages = PageCount;
      InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);
   }
}

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

REQUEST_STRUCT* VmGetRequest ()

{
   static $DESCRIPTOR (ZoneNameDsc, "HTTPd Heap");
   static unsigned long  Algorithm = LIB$K_VM_QUICK_FIT,
                         AlgorithmArg = 16,
                         Flags = LIB$M_VM_BOUNDARY_TAGS |
                                 LIB$M_VM_GET_FILL0 |
                                 LIB$M_VM_TAIL_LARGE,
                         BlockSize = 64;

   static unsigned long RequestStructSize = sizeof(REQUEST_STRUCT);

   int  status;
   unsigned long  BaseAddress;
   REQUEST_STRUCT  *rqptr;

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

   if (WATCH_MODULE(WATCH_MOD_VM))
      WatchThis (NULL, FI_LI, WATCH_MOD_VM, "VmGetRequest()");

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

   rqptr = (REQUEST_STRUCT*)BaseAddress;

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

   rqptr->rqHeapStats.FreeByteMin =
      rqptr->rqHeapStats.GetByteMin =
      rqptr->rqHeapStats.ReallocByteMin = VM_STATS_MAXMIN;

   return (rqptr);
}

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

VmFreeRequest
(
REQUEST_STRUCT *rqptr,
char *SourceModuleName,
int SourceLineNumber
)
{
   static unsigned long  RequestStructSize = sizeof(REQUEST_STRUCT);
   static unsigned long  PrevByteCount;

   int  status;
   unsigned long  PageCount;

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

   if (WATCHING(rqptr) && (WATCH_MODULE(WATCH_MOD_VM)))
   {
      PageCount = rqptr->rqHeapStats.ByteCount >> 9;
      if (rqptr->rqHeapStats.ByteCount & 0x1ff) PageCount++;
      WatchThis (rqptr, FI_LI, WATCH_MOD_VM, "VmFreeRequest()");
      WatchDataFormatted (
"ByteCount: !SL PageCount: !SL VmRequestSizePages: !SL\n\
GetCount: !SL GetByteCount: !SL GetByteMax: !SL GetByteMin: !SL\n\
FreeCount: !SL FreeByteCount: !SL FreeByteMax: !SL FreeByteMin: !SL\n\
ReallocCount: !SL ReallocByteCount: !SL \
ReallocByteMax: !SL ReallocByteMin: !SL\n",
         rqptr->rqHeapStats.ByteCount,
         PageCount, VmRequestSizePages,
         rqptr->rqHeapStats.GetCount,
         rqptr->rqHeapStats.GetByteCount,
         rqptr->rqHeapStats.GetByteMax,
         rqptr->rqHeapStats.GetByteMin,
         rqptr->rqHeapStats.FreeCount,
         rqptr->rqHeapStats.FreeByteCount,
         rqptr->rqHeapStats.FreeByteMax,
         rqptr->rqHeapStats.FreeByteMin,
         rqptr->rqHeapStats.ReallocCount,
         rqptr->rqHeapStats.ReallocByteCount,
         rqptr->rqHeapStats.ReallocByteMax,
         rqptr->rqHeapStats.ReallocByteMin);
   }

   VmHeapStats.ByteCount += rqptr->rqHeapStats.ByteCount;
   if (VmHeapStats.ByteCount < PrevByteCount)
   {
      /* no nicker knotting here, just detect and reset */
      memset (&VmHeapStats, 0, sizeof(VmHeapStats));
      memset (&VmHeapStatsPageCount, 0, sizeof(VmHeapStatsPageCount));
      VmHeapStatsGetCountMax =
         VmHeapStatsFreeCountMax =
         VmHeapStatsReallocCountMax =
         VmHeapStatsCount = 0;
      VmHeapStatsGetCountMin =
         VmHeapStatsFreeCountMin =
         VmHeapStatsReallocCountMin =
         VmHeapStats.FreeByteMin =
         VmHeapStats.GetByteMin =
         VmHeapStats.ReallocByteMin = VM_STATS_MAXMIN;
      VmHeapStatsResetCount++;
      VmHeapStats.ByteCount = rqptr->rqHeapStats.ByteCount;
   }
   PrevByteCount = VmHeapStats.ByteCount;
   VmHeapStatsCount++;

   PageCount = rqptr->rqHeapStats.ByteCount >> 9;
   if (rqptr->rqHeapStats.ByteCount & 0x1ff) PageCount++;
   if (PageCount >= VM_REQUEST_SIZE_PAGES_STAT_MAX)
      VmHeapStatsPageCount[VM_REQUEST_SIZE_PAGES_STAT_MAX/4]++;
   else
      VmHeapStatsPageCount[PageCount/4]++;

   if (rqptr->rqHeapStats.GetCount)
   {
      VmHeapStats.GetCount += rqptr->rqHeapStats.GetCount;
      if (rqptr->rqHeapStats.GetCount > VmHeapStatsGetCountMax)
         VmHeapStatsGetCountMax = rqptr->rqHeapStats.GetCount;
      if (rqptr->rqHeapStats.GetCount < VmHeapStatsGetCountMin)
         VmHeapStatsGetCountMin = rqptr->rqHeapStats.GetCount;
      if (rqptr->rqHeapStats.GetByteMax > VmHeapStats.GetByteMax)
         VmHeapStats.GetByteMax = rqptr->rqHeapStats.GetByteMax;
      if (rqptr->rqHeapStats.GetByteMin != VM_STATS_MAXMIN &&
          rqptr->rqHeapStats.GetByteMin < VmHeapStats.GetByteMin)
         VmHeapStats.GetByteMin = rqptr->rqHeapStats.GetByteMin;
   }
   if (rqptr->rqHeapStats.FreeCount)
   {
      VmHeapStats.FreeCount += rqptr->rqHeapStats.FreeCount;
      if (rqptr->rqHeapStats.FreeCount > VmHeapStatsFreeCountMax)
         VmHeapStatsFreeCountMax = rqptr->rqHeapStats.FreeCount;
      if (rqptr->rqHeapStats.FreeCount < VmHeapStatsFreeCountMin)
         VmHeapStatsFreeCountMin = rqptr->rqHeapStats.FreeCount;
      if (rqptr->rqHeapStats.FreeByteMax > VmHeapStats.FreeByteMax)
         VmHeapStats.FreeByteMax = rqptr->rqHeapStats.FreeByteMax;
      if (rqptr->rqHeapStats.FreeByteMin != VM_STATS_MAXMIN &&
          rqptr->rqHeapStats.FreeByteMin < VmHeapStats.FreeByteMin)
         VmHeapStats.FreeByteMin = rqptr->rqHeapStats.FreeByteMin;
   }
   if (rqptr->rqHeapStats.ReallocCount)
   {
      VmHeapStats.ReallocCount += rqptr->rqHeapStats.ReallocCount;
      if (rqptr->rqHeapStats.ReallocCount > VmHeapStatsReallocCountMax)
         VmHeapStatsReallocCountMax = rqptr->rqHeapStats.ReallocCount;
      if (rqptr->rqHeapStats.ReallocCount < VmHeapStatsReallocCountMin)
         VmHeapStatsReallocCountMin = rqptr->rqHeapStats.ReallocCount;
      if (rqptr->rqHeapStats.ReallocByteMax > VmHeapStats.ReallocByteMax)
         VmHeapStats.ReallocByteMax = rqptr->rqHeapStats.ReallocByteMax;
      if (rqptr->rqHeapStats.ReallocByteMin != VM_STATS_MAXMIN &&
          rqptr->rqHeapStats.ReallocByteMin < VmHeapStats.ReallocByteMin)
         VmHeapStats.ReallocByteMin = rqptr->rqHeapStats.ReallocByteMin;
   }

   /* free the request structure's virtual memory */

   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, &VmRequestVmZoneId);
   if (VMSnok (status))
      ErrorExitVmsStatus (status, "lib$free_vm()",
                          SourceModuleName, SourceLineNumber);
}

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

char* VmGetHeap
(
REQUEST_STRUCT *rqptr,
unsigned long ChunkSize
)
{
   int  status;
   unsigned long  BaseAddress;

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

   if (WATCHING(rqptr) && (WATCH_MODULE(WATCH_MOD_VM)))
      WatchThis (rqptr, FI_LI, WATCH_MOD_VM, "VmGetHeap() !UL", ChunkSize);

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

   rqptr->rqHeapStats.GetCount++;
   rqptr->rqHeapStats.GetByteCount += ChunkSize;
   rqptr->rqHeapStats.ByteCount += ChunkSize;
   if (ChunkSize > rqptr->rqHeapStats.GetByteMax)
      rqptr->rqHeapStats.GetByteMax = ChunkSize;
   if (ChunkSize < rqptr->rqHeapStats.GetByteMin)
      rqptr->rqHeapStats.GetByteMin = ChunkSize;
   *(unsigned long*)BaseAddress = ChunkSize-VM_OFFSET;
   return ((unsigned char*)BaseAddress+VM_OFFSET);
}

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

char* VmReallocHeap
(
REQUEST_STRUCT *rqptr,
char *ChunkPtr,
unsigned long ChunkSize,
char *SourceModuleName,
int SourceLineNumber
)
{
   int  status,
        CurrentChunkSize;
   unsigned long  BaseAddress,
                  FreeBaseAddress;

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

   if (WATCHING(rqptr) && (WATCH_MODULE(WATCH_MOD_VM)))
   {
      if (ChunkPtr)
         CurrentChunkSize = *(unsigned long*)(ChunkPtr-VM_OFFSET);
      else
         CurrentChunkSize = 0;
      WatchThis (rqptr, FI_LI, WATCH_MOD_VM,
                 "VmReallocHeap() !UL !UL", CurrentChunkSize, ChunkSize);
   }

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

   /* if this chunk satisfies the reallocation then just return it */
   CurrentChunkSize = *(unsigned long*)(ChunkPtr-VM_OFFSET);
   if (CurrentChunkSize >= ChunkSize) return (ChunkPtr);

   /* allocate a new, larger chunk */
   ChunkSize += VM_OFFSET + VM_ELBOW_ROOM;
   status = lib$get_vm (&ChunkSize, &BaseAddress, &rqptr->VmHeapZoneId);
   if (VMSnok (status))
      ErrorExitVmsStatus (status, "lib$get_vm()",
                          SourceModuleName, SourceLineNumber);

   /* copy the existing chunk into the new chunk */
   memcpy ((unsigned char*)BaseAddress+VM_OFFSET, ChunkPtr, CurrentChunkSize);

   /* free the previous chunk */
   FreeBaseAddress = ChunkPtr-VM_OFFSET;
   status = lib$free_vm (0, &FreeBaseAddress, &rqptr->VmHeapZoneId);
   if (VMSnok (status))
      ErrorExitVmsStatus (status, "lib$free_vm()",
                          SourceModuleName, SourceLineNumber);

   rqptr->rqHeapStats.ReallocCount++;
   rqptr->rqHeapStats.ReallocByteCount += ChunkSize;
   if (ChunkSize > rqptr->rqHeapStats.ReallocByteMax)
      rqptr->rqHeapStats.ReallocByteMax = ChunkSize;
   if (ChunkSize < rqptr->rqHeapStats.ReallocByteMin)
      rqptr->rqHeapStats.ReallocByteMin = ChunkSize;
   rqptr->rqHeapStats.ByteCount -= CurrentChunkSize;
   rqptr->rqHeapStats.ByteCount += ChunkSize;
   *(unsigned long*)BaseAddress = ChunkSize-VM_OFFSET;
   return ((unsigned char*)BaseAddress+VM_OFFSET);
}

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

VmFreeFromHeap
(
REQUEST_STRUCT *rqptr,
char *ChunkPtr,
char *SourceModuleName,
int SourceLineNumber
)
{
   int  status,
        CurrentChunkSize;
   unsigned long  FreeBaseAddress;

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

   if (ChunkPtr)
      CurrentChunkSize = *(unsigned long*)(ChunkPtr-VM_OFFSET);
   else
      CurrentChunkSize = 0;

   if (WATCHING(rqptr) && (WATCH_MODULE(WATCH_MOD_VM)))
      WatchThis (rqptr, FI_LI, WATCH_MOD_VM,
                 "VmFreeFromHeap() !UL", CurrentChunkSize);

   /* free the previous chunk */
   FreeBaseAddress = ChunkPtr-VM_OFFSET;
   status = lib$free_vm (0, &FreeBaseAddress, &rqptr->VmHeapZoneId);
   if (VMSnok (status))
      ErrorExitVmsStatus (status, "lib$free_vm()",
                          SourceModuleName, SourceLineNumber);

   rqptr->rqHeapStats.FreeCount++;
   rqptr->rqHeapStats.FreeByteCount += CurrentChunkSize;
   if (CurrentChunkSize > rqptr->rqHeapStats.FreeByteMax)
      rqptr->rqHeapStats.FreeByteMax = CurrentChunkSize;
   if (CurrentChunkSize < rqptr->rqHeapStats.FreeByteMin)
      rqptr->rqHeapStats.FreeByteMin = CurrentChunkSize;
   rqptr->rqHeapStats.ByteCount -= CurrentChunkSize;
}

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

VmFreeHeap
(
REQUEST_STRUCT *rqptr,
char *SourceModuleName,
int SourceLineNumber
)
{
   int  status,
        CurrentChunkSize;

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

   if (WATCHING(rqptr) && (WATCH_MODULE(WATCH_MOD_VM)))
      WatchThis (rqptr, FI_LI, WATCH_MOD_VM, "VmFreeHeap()");

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

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

VmCacheInit (int TotalKBytesMax)

{
   static $DESCRIPTOR (ZoneNameDsc, "HTTPd Cache");
   static unsigned long  Algorithm = LIB$K_VM_QUICK_FIT,
                         AlgorithmArg = 128,
                         Flags = LIB$M_VM_BOUNDARY_TAGS |
                                 LIB$M_VM_GET_FILL0 |
                                 LIB$M_VM_TAIL_LARGE,
                         ExtendSize,
                         InitialSize,
                         BlockSize = CACHE_CHUNK_SIZE;

   int  status;

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

   if (WATCH_MODULE(WATCH_MOD_VM))
      WatchThis (NULL, FI_LI, WATCH_MOD_VM,
                 "VmCacheInit() !UL", TotalKBytesMax);

   if ((InitialSize = TotalKBytesMax * 2) <= 0) InitialSize = 32;
   ExtendSize = InitialSize / 2;

   /* create the cache virtual memory zone */
   status = lib$create_vm_zone (&VmCacheVmZoneId,
                                &Algorithm, &AlgorithmArg, &Flags,
                                &ExtendSize, &InitialSize, &BlockSize,
                                0, 0, 0, &ZoneNameDsc, 0, 0);
   if (VMSnok (status))
      ErrorExitVmsStatus (status, "lib$create_vm_zone()", FI_LI);
}

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

char* VmGetCache (unsigned long ChunkSize)

{
   int  status;
   unsigned long  BaseAddress;

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

   if (WATCH_MODULE(WATCH_MOD_VM))
      WatchThis (NULL, FI_LI, WATCH_MOD_VM,
                 "VmGetCache() !UL", ChunkSize);

   status = lib$get_vm (&ChunkSize, &BaseAddress, &VmCacheVmZoneId);
   if (VMSnok (status)) ErrorExitVmsStatus (status, "lib$get_vm()", FI_LI);
   return ((char*)BaseAddress);
}

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

VmFreeCache
(
char *ChunkPtr,
char *SourceModuleName,
int SourceLineNumber
)
{
   int  status,
        CurrentChunkSize;

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

   if (WATCH_MODULE(WATCH_MOD_VM))
   {
      if (ChunkPtr)
         CurrentChunkSize = *(unsigned long*)(ChunkPtr-VM_OFFSET);
      else
         CurrentChunkSize = 0;
      WatchThis (NULL, FI_LI, WATCH_MOD_VM,
                 "VmFreeCache() !UL", CurrentChunkSize);
   }

   status = lib$free_vm (0, &ChunkPtr, &VmCacheVmZoneId);
   if (VMSnok (status))
      ErrorExitVmsStatus (status, "lib$free_vm()",
                          SourceModuleName, SourceLineNumber);
}

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

VmPermCacheInit (int TotalKBytesMax)

{
   static $DESCRIPTOR (ZoneNameDsc, "HTTPd Perm-Cache");
   static unsigned long  Algorithm = LIB$K_VM_QUICK_FIT,
                         AlgorithmArg = 128,
                         Flags = LIB$M_VM_BOUNDARY_TAGS |
                                 LIB$M_VM_GET_FILL0 |
                                 LIB$M_VM_TAIL_LARGE,
                         ExtendSize,
                         InitialSize,
                         BlockSize = CACHE_PERM_CHUNK_SIZE;

   int  status;

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

   if (WATCH_MODULE(WATCH_MOD_VM))
      WatchThis (NULL, FI_LI, WATCH_MOD_VM,
                 "VmPermCacheInit() !UL", TotalKBytesMax);

   if ((InitialSize = TotalKBytesMax * 2) <= 0) InitialSize = 32;
   ExtendSize = InitialSize / 2;

   /* create the cache virtual memory zone */
   status = lib$create_vm_zone (&VmPermCacheVmZoneId,
                                &Algorithm, &AlgorithmArg, &Flags,
                                &ExtendSize, &InitialSize, &BlockSize,
                                0, 0, 0, &ZoneNameDsc, 0, 0);
   if (VMSnok (status))
      ErrorExitVmsStatus (status, "lib$create_vm_zone()", FI_LI);
}

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

char* VmGetPermCache (unsigned long ChunkSize)

{
   int  status;
   unsigned long  BaseAddress;

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

   if (WATCH_MODULE(WATCH_MOD_VM))
      WatchThis (NULL, FI_LI, WATCH_MOD_VM,
                 "VmGetPermCache() !UL", ChunkSize);

   if (!VmPermCacheVmZoneId) VmPermCacheInit (CacheTotalKBytesMax);

   status = lib$get_vm (&ChunkSize, &BaseAddress, &VmPermCacheVmZoneId);
   if (VMSnok (status)) ErrorExitVmsStatus (status, "lib$get_vm()", FI_LI);
   return ((char*)BaseAddress);
}

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

VmFreePermCache
(
char *ChunkPtr,
char *SourceModuleName,
int SourceLineNumber
)
{
   int  status,
        CurrentChunkSize;

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

   if (WATCH_MODULE(WATCH_MOD_VM))
   {
      if (ChunkPtr)
         CurrentChunkSize = *(unsigned long*)(ChunkPtr-VM_OFFSET);
      else
         CurrentChunkSize = 0;
      WatchThis (NULL, FI_LI, WATCH_MOD_VM,
                 "VmFreePermCache() !UL", CurrentChunkSize);
   }

   status = lib$free_vm (0, &ChunkPtr, &VmPermCacheVmZoneId);
   if (VMSnok (status))
      ErrorExitVmsStatus (status, "lib$free_vm()",
                          SourceModuleName, SourceLineNumber);
}

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

VmReport
(
REQUEST_STRUCT *rqptr,
REQUEST_AST NextTaskFunction
)
{
   static unsigned long  ShowVmCode123 = 0,
                         ShowVmCode567 = 4,
                         ShowVmZoneDetail = 3;

   static char  BeginPageFao [] =
"<P><TABLE CELLPADDING=3 CELLSPACING=0 BORDER=1>\n\
<TR><TD>\n\
<TABLE CELLPADDING=0 CELLSPACING=5 BORDER=0>\n\
<TR><TD><PRE>\n\n\
REQUEST HEAP STATISTICS for !UL requests (reset !UL time!%s)\n\n";

   static char  MoreStatsFao [] =
" !UL request!%s extended from initial allocation of !UL pages (!UL%)\n\
 !UL pages per request tuned for next restart (!UL default)\n\n\
 !UL bytes average (!UL pages)\n\
 !UL call!%s to GET heap memory\n\
 !UL per request ave, !UL max, !UL min, !UL bytes max, !UL bytes min\n\
 !UL call!%s to FREE heap memory\n\
 !UL per request ave, !UL max, !UL min, !UL bytes max, !UL bytes min\n\
 !UL call!%s to REALLOC heap memory\n\
 !UL per request ave, !UL max, !UL min, !UL bytes max, !UL bytes min\n\
\n!90*-\n\n";

   static char  EndPageFao [] =
"</PRE></TD></TR>\n\
</TABLE>\n\
</TD></TR>\n\
</TABLE>\n\
</BODY>\n\
</HTML>\n";

   int  cnt, idx, status,
        ByteCountAve,
        Count,
        Percent;
   unsigned short  Length;
   unsigned long  Context,
                  ExtendCount,
                  PageCountAve,
                  PageCountTuned,
                  ZoneId;
   unsigned long  *vecptr;
   unsigned long  FaoVector [32];
   char  Buffer [4096];
   $DESCRIPTOR (BufferDsc, Buffer);

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

   if (WATCH_MODULE(WATCH_MOD_VM))
      WatchThis (NULL, FI_LI, WATCH_MOD_VM, "VmReport()");

   VmRequestTune ();
   InstanceMutexLock (INSTANCE_MUTEX_HTTPD);
   PageCountTuned = HttpdGblSecPtr->VmRequestSizePages;
   InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);

   rqptr->rqResponse.PreExpired = PRE_EXPIRE_ADMIN;
   RESPONSE_HEADER_200_HTML (rqptr);
   AdminPageTitle (rqptr, "Virtual Memory Report");

   ByteCountAve = VmHeapStats.ByteCount / (VmHeapStatsCount ?
                                           VmHeapStatsCount : 1);
   PageCountAve = ByteCountAve >> 9;
   if (ByteCountAve & 0x1ff) PageCountAve++;

   status = NetWriteFao (rqptr, BeginPageFao,
                         VmHeapStatsCount,
                         VmHeapStatsResetCount);

   ExtendCount = 0;
   for (idx = 0; idx < VM_REQUEST_SIZE_PAGES_MAX / 4; idx++)
   {
      if (!VmHeapStatsPageCount[idx]) continue;
      if (VmHeapStatsCount)
         Percent = (VmHeapStatsPageCount[idx] * 100) / VmHeapStatsCount;
      else
         Percent = 0;
      if (idx*4 >= VmRequestSizePages)
         ExtendCount += VmHeapStatsPageCount[idx];
      if (idx*4 < VM_REQUEST_SIZE_PAGES_STAT_MAX)
         status = NetWriteFao (rqptr, " !UL-!UL pages !UL request!%s (!UL%)\n",
                               idx*4, ((idx+1)*4)-1, VmHeapStatsPageCount[idx],
                               Percent);
      else
         status = NetWriteFao (rqptr, " >=!UL pages !UL request!%s (!UL%)\n",
                               idx*4, VmHeapStatsPageCount[idx], Percent);
      if (VMSnok (status)) ErrorNoticed (status, "NetWriteFao()", FI_LI);
   }

   if (VmHeapStatsCount)
      Percent = (ExtendCount * 100) / VmHeapStatsCount;
   else
      Percent = 0;

   status = NetWriteFao (rqptr, MoreStatsFao,
                         ExtendCount, VmRequestSizePages, Percent,
                         PageCountTuned, VM_REQUEST_SIZE_PAGES,
                         ByteCountAve,
                         PageCountAve,
                         VmHeapStats.GetCount,
                         VmHeapStats.GetCount / (VmHeapStatsCount ?
                                                 VmHeapStatsCount : 1),
                         VmHeapStatsGetCountMax,
                         VmHeapStatsGetCountMin != VM_STATS_MAXMIN ?
                           VmHeapStatsGetCountMin : 0,
                         VmHeapStats.GetByteMax,
                         VmHeapStats.GetByteMin != VM_STATS_MAXMIN ?
                            VmHeapStats.GetByteMin : 0,
                         VmHeapStats.FreeCount,
                         VmHeapStats.FreeCount / (VmHeapStatsCount ?
                                                  VmHeapStatsCount : 1),
                         VmHeapStatsFreeCountMax,
                         VmHeapStatsFreeCountMin != VM_STATS_MAXMIN ?
                           VmHeapStatsFreeCountMin : 0,
                         VmHeapStats.FreeByteMax,
                         VmHeapStats.FreeByteMin != VM_STATS_MAXMIN ?
                            VmHeapStats.FreeByteMin : 0,
                         VmHeapStats.ReallocCount,
                         VmHeapStats.ReallocCount / (VmHeapStatsCount ?
                                                     VmHeapStatsCount : 1),
                         VmHeapStatsReallocCountMax,
                         VmHeapStatsFreeCountMin != VM_STATS_MAXMIN ?
                           VmHeapStatsReallocCountMin : 0,
                         VmHeapStats.ReallocByteMax,
                         VmHeapStats.ReallocByteMin != VM_STATS_MAXMIN ?
                            VmHeapStats.ReallocByteMin : 0);

   if (VMSnok (status)) ErrorNoticed (status, "NetWriteFao()", FI_LI);

   if (VMSnok (status = lib$show_vm (&ShowVmCode123, &VmWrite, rqptr)))
   {
      rqptr->rqResponse.ErrorTextPtr = "lib$show_vm()";
      ErrorVmsStatus (rqptr, status, FI_LI);
      SysDclAst (NextTaskFunction, rqptr);
      return;
   }

   if (VMSnok (status = lib$show_vm (&ShowVmCode567, &VmWrite, rqptr)))
   {
      rqptr->rqResponse.ErrorTextPtr = "lib$show_vm()";
      ErrorVmsStatus (rqptr, status, FI_LI);
      SysDclAst (NextTaskFunction, rqptr);
      return;
   }

   status = NetWriteFao (rqptr, "\n");
   if (VMSnok (status)) ErrorNoticed (status, "NetWriteFao()", FI_LI);

   Context = 0;
   while ((status = lib$find_vm_zone (&Context, &ZoneId)) == SS$_NORMAL)
   {
      status = NetWriteFao (rqptr, "!90*-\n\n");
      if (VMSnok (status)) ErrorNoticed (status, "NetWriteFao()", FI_LI);

      status = lib$show_vm_zone (&ZoneId, &ShowVmZoneDetail, &VmWrite, rqptr);
      if (VMSnok (status))
      {
         rqptr->rqResponse.ErrorTextPtr = "lib$show_vm_zone()";
         ErrorVmsStatus (rqptr, status, FI_LI);
         SysDclAst (NextTaskFunction, rqptr);
         return;
      }
   }
   if (status != LIB$_NOTFOU)
   {                            
      rqptr->rqResponse.ErrorTextPtr = "lib$find_vm_zone()";
      ErrorVmsStatus (rqptr, status, FI_LI);
      SysDclAst (NextTaskFunction, rqptr);
      return;
   }

   status = NetWriteFaol (rqptr, EndPageFao, NULL);
   if (VMSnok (status)) ErrorNoticed (status, "NetWriteFaol()", FI_LI);

   SysDclAst (NextTaskFunction, rqptr);
}

/*****************************************************************************/
/*
Action routine for lib$show_vm*() routines.  Simply write the contents of the
paremeter descriptor to the client plus a newline character.
*/ 

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

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

   if (WATCH_MODULE(WATCH_MOD_VM))
      WatchThis (NULL, FI_LI, WATCH_MOD_VM, "VmWrite()");

   status = NetWriteFao (rqptr, "!#&;AZ\n", DscPtr->dsc$w_length,
                                            DscPtr->dsc$a_pointer);
   if (VMSnok (status)) ErrorNoticed (status, "NetWriteFao()", FI_LI);

   return (SS$_NORMAL);
}

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

VmDebug (unsigned long ZoneId)

{
#ifdef DBUG

   static unsigned long  ShowVmZoneDetail = 3;

   int status;
   unsigned long Context;

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

   if (WATCH_MODULE(WATCH_MOD_VM))
      WatchThis (NULL, FI_LI, 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)) WatchDataFormated ("----------\n");

#endif
}


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