/*****************************************************************************/
/*
                                HTTPdMon.c

This utility displays information about a WASD HTTPd server.  The information 
is derived from JPI data about the process, and read from a permanent system
global section in(to) which the server keeps the accounting and writes the
request data.

The /ALERT and /ALERT=ALL parameters ring the bell every time a new request is
reported (the connect count increases).  The /ALERT=HOST rings the bell every
time the host name in the request changes.  With /ALERT=PATH[=<integer>] when a
path matched by the mapping rule "SET /path* ALERT" is detected by the server
the bell is rung <integer> times (default is 10). The /LOOKUP qualifier
(default) attempts to resolve a dotted-decimal host address in the request (if
the server's [DNSlookup] configuration directive is disabled).  This operation
can introduce some latency as name resolution occurs.  A trailing "#" indicates
this is an address that has been resolved this way.  If it cannot be resolved a
"?" is appended to the numeric address.

The "(n servers)" field keeps track of multiple servers on a node or cluster. 
When there is a change in the number the previous value is displayed in
parentheses after the current and the terminal bell is rung (this can be
suppressed with "/ALERT=NOSERVERS").  Of course servers coming and going are
only detected if it happens at a frequency greater than the current display
interval.


QUALIFIERS
----------
/ALERT[=ALL|HOST|PATH[=<integer>]|[NO]SERVERS]
                        ring bell for all accesses or
                        just a change in host and/or
                        suppress bell if server count changes
/ALL=string             set the server-group name (up to 8 characters)
/DBUG                   turns on all "if (Debug)" statements
/DEMO                   demonstration mode (matches that of the HTTPd)
/[NO]GENERAL            display general HTTP serving information
/HELP                   display brief usage information
/IDENTIFICATION=        the specific instance (server) process PID
/INTERVAL=              number of seconds between display refresh
/[NO]LOOKUP             resolve dotted-decimal host addresses to names
/PORT=                  the IP port number of the server to monitor
/[NO]PROCESS            display HTTPd process information
/[NO]PROXY              display proxy serving information
/[NO]REQUEST            display request information
/REFRESH=               synonym for /INTERVAL


REQUIRED PRIVILEGES
-------------------
WORLD      for access to the server process' JPI data
SYSPRV     for access to the global section containing the server data


BUILD DETAILS
-------------
See BUILD_HTTPDMON.COM


VERSION HISTORY (update SOFTWAREVN as well!)
---------------
16-APR-2004  MGD  v2.2.0, modifications to support IPv6,
                          binary resource names for instance locks
27-JAN-2004  MGD  v2.1.5, connect processing and keep-alive accounting items
23-DEC-2003  MGD  v2.1.4, minor conditional mods to support IA64
22-SEP-2003  MGD  v2.1.3, line overflow - if necessary suppress '(403:count)'
11-JUN-2003  MGD  v2.1.2, ProxyMaintDeviceStats() inline with WASD v8.3
                          bugfix; ProxyMaintDeviceStats() volume count handling
26-NOV-2002  MGD  v2.1.1, abbreviate proxy cache Rx/Tx prevent line overflow
30-JUN-2002  MGD  v2.1.0, HTTPd v8.0, NCS, FTP, etc.,
                          /IDENTIFICATION= specific instance PID,
                          /ALERT=PATH notifies when "SET /path* ALERT" hit
17-MAR-2002  MGD  v2.0.2, CommaNumber() 64 bit kludge for VAX
04-AUG-2001  MGD  v2.0.1, provide 'ANSIceos' before a "STATUS:" field
07-JUL-2001  MGD  v2.0.0, HTTPd v7.2, uses permanent global section for data,
                          additional/changed accounting and status information
15-OCT-2000  MGD  v1.13.0, HTTPd v7.1, DLM server-group "aware", /ALL=,
                           no current process count if $creprc() detached
20-MAY-2000  MGD  v1.12.0, HTTPd v7.0, (no real changes)
30-OCT-1999  MGD  v1.11.0, HTTPd v6.1,
                           remove NETLIB support
20-JUN-1999  MGD  v1.10.0, add proxy percentage based on counts,
                           allow for disks >9GB in space calculations
25-JAN-1999  MGD  v1.9.0, HTTPd v6.0, proxy serving
27-OCT-1998  MGD  v1.8.0, HTTPd v5.3, (no real changes)
27-AUG-1998  MGD  v1.7.0, HTTPd v5.2, (no real changes)
21-JUN-1998  MGD  v1.6.0, HTTPd v5.1, alert, and lookup host address to name
14-FEB-1998  MGD  v1.5.0, HTTPd v5.0, DECnet, SSL and minor changes
15-OCT-1997  MGD  v1.4.0, HTTPd v4.5, cache
21-AUG-1997  MGD  v1.3.0, HTTPd v4.4, accounting structure > 255 bytes, duration
23-JUL-1997  MGD  v1.2.1, HTTPd v4.3, (no real changes)
14-JUN-1997  MGD  v1.2.0, HTTPd v4.2, CGIplus,
                          bugfix; arithmetic trap on AXP systems
01-FEB-1997  MGD  v1.1.0, HTTPd v4.0, (no real changes)
01-DEC-1995  MGD  v1.0.0, HTTPd v3.0, initial development
*/
/*****************************************************************************/

/* note that the layout is a different to other WASD software ID strings! */
#define SOFTWAREVN "v2.2.0"
#define SOFTWARENM "HTTPDMON"
#ifdef __ALPHA
#  define SOFTWAREID SOFTWARENM " " SOFTWAREVN " AXP"
#endif
#ifdef __ia64
#  define SOFTWAREID SOFTWARENM " " SOFTWAREVN " IA64"
#endif
#ifdef __VAX
#  define SOFTWAREID SOFTWARENM " " SOFTWAREVN " VAX"
#endif

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

/* VMS related header files */
#include <descrip.h>
#include <dvidef.h>
#include <iodef.h>
#include <jpidef.h>
#include <lckdef.h>
#include <libclidef.h>
#include <libdtdef.h>
#include <lkidef.h>
#include <psldef.h>
#include <prvdef.h>
#include <secdef.h>
#include <ssdef.h>
#include <stsdef.h>
#include <syidef.h>

/* this header file contains the accounting structure definitions */
#include "../httpd/wasd.h"

/* Internet-related header files */

#include <socket.h>
#include <in.h>
#include <netdb.h>
#include <inet.h>

#define BOOL int
#define true 1
#define false 0
 
#define FI_LI  __FILE__, __LINE__

/* drag these over from proxymaint.h */
#define PROXY_MAINT_DEVICE_MBYTES(Blocks) (Blocks >> 11)
#define PROXY_MAINT_DEVICE_MBYTES(Blocks) (Blocks >> 11)
#define PROXY_MAINT_DEVICE_PERCENT_FREE(Total,Free) \
        (Total ? ((int)((float)Free*100.0/(float)Total)) : 0)
#define PROXY_MAINT_DEVICE_PERCENT_USED(Total,Free) \
        (Total ? (100-(int)((float)Free*100.0/(float)Total)) : 0)

#define VMSok(x) ((x) & STS$M_SUCCESS)
#define VMSnok(x) !(((x) & STS$M_SUCCESS))

#define DEFAULT_INTERVAL_SECONDS 2

#define PATH_ALERT_BELL_COUNT 10

char  ErrorProxyMaintTooManyDevices [] = "Volume set has too many members.",
      Utility [] = "HTTPDMON";

#define ControlZ '\x1a'

long SysLckMask [2] = { PRV$M_SYSLCK, 0 };

BOOL  ControlY,
      Debug,
      DemoMode,
      DoAlertAll,
      DoAlertHost,
      DoAlertPath,
      DoAlertServers = true,
      DoGeneralInfo,
      DoNoGeneralInfo,
      DoLookupHost = true,
      DoProcessInfo,
      DoNoProcessInfo,
      DoProxyInfo,
      DoNoProxyInfo,
      DoRequestInfo,
      DoNoRequestInfo,
      DoShowHelp;

int  CliInstancePid,
     CurrentProcessCount,
     InstanceGroupNumber = 1,  /* default group number */
     InstanceStringLength,
     IntervalSeconds = DEFAULT_INTERVAL_SECONDS,
     NextInstancePid,
     PathAlertBellRepetition = PATH_ALERT_BELL_COUNT,
     PrevConnectCount,
     ServerPort = 80;

unsigned short  SyiClusterNodes,
                SyiNodeLength;

char  CommandLine [256],
      InstanceString [64],
      PrevHostName [256],
      Screen [8192],
      ServerProcessName [16],
      SyiNodeName [16];

char  *ScrPtr;

$DESCRIPTOR (TcpIpDeviceDsc, "UCX$DEVICE");

int  HttpdGblSecLength;
HTTPD_GBLSEC  *HttpdGblSecPtr;

/* ANSI terminal control sequences */
char ANSIblink [] = "\x1b[5m",
     ANSIbold [] = "\x1b[1m",
     ANSIceol [] = "\x1b[K",
     ANSIceos [] = "\x1b[J",
     ANSIcls [] = "\x1b[0;0H\x1b[J",
     ANSIhome [] = "\x1b[0;0H",
     ANSInormal [] = "\x1b[0m",
     ANSIreverse [] = "\x1b[7m",
     ANSIuline [] = "\x1b[4m";

/* required prototypes */
ControlY_AST ();
char* TcpIpHostCache (char*, int*, int*);
char* TcpIpLookupHostName (char*, int, int*);
int TcpIpStringAddress (char*, int*, int*);
char* TimeString (); 
 
/*****************************************************************************/
/*
*/

int main ()

{
   int  status,
        MonitorStatus;
   struct
   {
      unsigned short  buf_len;
      unsigned short  item;
      unsigned char  *buf_addr;
      void  *ret_len;
   }
   SyiItems [] =
   {
     { sizeof(SyiClusterNodes), SYI$_CLUSTER_NODES, &SyiClusterNodes, 0 },
     { sizeof(SyiNodeName)-1, SYI$_NODENAME, &SyiNodeName, &SyiNodeLength },
     { 0,0,0,0 }
   };

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

   GetParameters ();

   if (Debug) ANSIcls[0] = ANSIhome[0] = ANSIceol[0] = ANSIceos[0] = '\0';

   if (DemoMode) InstanceGroupNumber = DEMO_INSTANCE_GROUP_NUMBER;

   if (!DoGeneralInfo  && !DoProcessInfo && !DoRequestInfo && !DoProxyInfo)
   {
      if (!DoNoProcessInfo) DoProcessInfo = true;
      if (!DoNoGeneralInfo) DoGeneralInfo = true;
      if (!DoNoRequestInfo) DoRequestInfo = true;
   }

   if (DoProxyInfo)
   {
      if (!DoNoProcessInfo) DoProcessInfo = true;
      if (!DoGeneralInfo) DoGeneralInfo = false;
      if (!DoNoRequestInfo) DoRequestInfo = true;
   }

   if (DoShowHelp) exit (ShowHelp ());

   if (VMSnok (status = OnControlY (&ControlY_AST)))
      exit (status);

   if (VMSnok (status = sys$getsyi (0, 0, 0, &SyiItems, 0, 0, 0)))
      exit (status);
   SyiNodeName[SyiNodeLength] = '\0';
   if (Debug) fprintf (stdout, "|%s|\n", SyiNodeName);

   MonitorStatus = MonitorHttpd ();

   if (VMSnok (status = OnControlY (0)))
      exit (status);

   exit (MonitorStatus);
}

/*****************************************************************************/
/*
Assign a channel to the terminal device.  Create a buffered screenful of 
information about the HTTPd server and output it in one IO.
*/

int MonitorHttpd ()

{
   int  cnt, status,
        FillLeft,
        FillRight,
        FillRequired,
        PortLength,
        InstanceInformationLength,
        SoftwareIDLength,
        TimeStringLength;
   unsigned short  TTChannel;
   char  ReadBuffer;
   char  Line [128],
         Port [16];
   char  *TimeStringPtr;
   
   $DESCRIPTOR (TTDsc, "TT:");
   struct {
      unsigned short  Status;
      unsigned short  Offset;
      unsigned short  Terminator;
      unsigned short  TerminatorSize;
   } IOsb;

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

   if (Debug) fprintf (stdout, "MonitorHttpd()\n");

   if (VMSnok (status = sys$assign (&TTDsc, &TTChannel, 0, 0, 0)))
      return (status);

   if (VMSnok (status =
       sys$qiow (0, TTChannel, IO$_WRITELBLK, 0, 0, 0,
                 ANSIcls, sizeof(ANSIcls)-1, 0, 0, 0, 0)))
      return (status);

   sprintf (ServerProcessName, "HTTPd:%d", ServerPort);
   PortLength = sprintf (Port, "Port: %d", ServerPort);

   SoftwareIDLength = strlen(SOFTWAREID);

   for (;;)
   {
      if (ControlY) break;

      GetInstanceInformation();

      TimeStringPtr = TimeString ();
      TimeStringLength = strlen(TimeStringPtr);

      FillRequired = 65 - InstanceStringLength -
                          SoftwareIDLength - TimeStringLength;
      FillLeft = FillRight = FillRequired / 2;
      if (FillLeft + FillRight < FillRequired) FillRight++;
      for (cnt = 6; cnt > SyiNodeLength; cnt--) FillRight--;

      ScrPtr = Screen;
      ScrPtr += sprintf (ScrPtr,
         "%s %*s%s%s::%s %s  %*s%s%s%s%*s  %s%s%s%s\r\n",
         ANSIhome, 6 - SyiNodeLength, "",
         ANSIbold, SyiNodeName, ANSInormal, InstanceString,
         FillLeft, "", ANSIuline, SOFTWAREID, ANSInormal, FillRight, "",
         ANSIbold, TimeStringPtr, ANSInormal,
         ANSIceol);

      /* we need the startup count and last exit status from count date */
      if (VMSnok (status = GetAccounting ()))
         return (status);

      if (HttpdGblSecPtr->GblSecVersion != HTTPD_GBLSEC_VERSION_NUMBER)
         ScrPtr += sprintf (ScrPtr,
"%s        %s  GLOBAL SECTION MISMATCH!  \
(HTTPDMON:%08.08X HTTPd:%08.08X)  %s%s\x07",
            ANSIceol, ANSIreverse,
            HTTPD_GBLSEC_VERSION_NUMBER, HttpdGblSecPtr->GblSecVersion,
            ANSInormal, ANSIceol);

      if (DoProcessInfo)
         if (VMSnok (status = AddProcessInfo ()))
            return (status);

      if (DoGeneralInfo)
         if (VMSnok (status = AddGeneralInfo ()))
            return (status);

      if (DoProxyInfo)
         if (VMSnok (status = AddProxyInfo ()))
            return (status);

      if (HttpdGblSecPtr->StatusMessage[0])
         if (VMSnok (status = AddStatusMessage ()))
            return (status);
         else;
      else
      if (DoRequestInfo)
         if (VMSnok (status = AddRequest ()))
            return (status);

      strcpy (ScrPtr, ANSIceos);
      ScrPtr += sizeof(ANSIceos)-1;
      if (Debug) fprintf (stdout, "%d bytes\f", ScrPtr-Screen);

      if (VMSnok (status =
          sys$qiow (0, TTChannel, IO$_WRITELBLK, 0, 0, 0,
                    Screen, ScrPtr-Screen, 0, 0, 0, 0)))
         return (status);

      status = sys$qiow (0, TTChannel, IO$_READLBLK | IO$M_TIMED,
                         &IOsb, 0, 0,
                         &ReadBuffer, 1, IntervalSeconds, 0, 0, 0);

      if (status == SS$_TIMEOUT) continue;
      if (VMSnok (status)) return (status);
      if (IOsb.Terminator == ControlZ) return (SS$_NORMAL);
   }

   if (VMSnok (status =
       sys$qiow (0, TTChannel, IO$_WRITELBLK, 0, 0, 0,
                 Screen, ScrPtr-Screen, 0, 0, 0, 0)))
      return (status);

   return (status);
}

/*****************************************************************************/
/*
Get the HTTPd server data from its global section.
Data is written by UpdateGlobalSection() in [SRC.HTTPD]SUPPORT.C module.
*/

int GetAccounting ()

{
   /* system global section, map into first available virtual address */
   static int MapFlags = SEC$M_SYSGBL | SEC$M_EXPREG;
   /* it is recommended to map into any virtual address in the region (P0) */
   static unsigned long  InAddr [2] = { 0x200, 0x200 };

   static char  HttpdGblSecName [32];
   static $DESCRIPTOR (HttpdGblSecNameDsc, HttpdGblSecName);
   static $DESCRIPTOR (HttpdGblSecNameFaoDsc, GBLSEC_NAME_FAO);

   int  status,
        ByteSize,
        PageSize;
   unsigned short  ShortLength;
   unsigned long  RetAddr [2];

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

   if (Debug) fprintf (stdout, "GetAccounting()\n");

   if (!HttpdGblSecPtr)
   {
      /* only need to map it the one time */
      sys$fao (&HttpdGblSecNameFaoDsc, &ShortLength, &HttpdGblSecNameDsc,
               HTTPD_NAME, HTTPD_GBLSEC_VERSION,
               InstanceGroupNumber, "HTTPD");
      HttpdGblSecNameDsc.dsc$w_length = ShortLength;
      if (Debug) fprintf (stdout, "|%s|\n", HttpdGblSecName);

      /* map the specified global section */
      status = sys$mgblsc (&InAddr, &RetAddr, 0, MapFlags, &HttpdGblSecNameDsc,
                           0, 0);
      if (Debug)
         fprintf (stdout, "sys$mgblsc() %%X%08.08X begin:%d end:%d\n",
                  status, RetAddr[0], RetAddr[1]);

      if (VMSok (status))
      {
         ByteSize = (RetAddr[1]+1) - RetAddr[0];
         PageSize = (RetAddr[1]+1) - RetAddr[0] >> 9;
         HttpdGblSecPtr = (HTTPD_GBLSEC*)RetAddr[0];
         HttpdGblSecLength = ByteSize;
      }
      else
      {
         HttpdGblSecPtr = NULL;
         HttpdGblSecLength = ByteSize = PageSize = 0;
         if (status == SS$_NOSUCHSEC)
         {
            fprintf (stdout,
"%%%s-E-SERVER, no such server!\n\
-SYSTEM-E-NOSUCHSEC, no such (global) section\n",
                     Utility);
            exit (STS$K_ERROR | STS$M_INHIB_MSG);
         }
         else
            exit (status);
      }
   }

   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
Add the HTTPd server process information to the screen buffer using the 
process ID from the PID logical and a a sys$getjpi() call.  The PID logical 
contains binary data in the form of a longword process ID.
*/ 

int AddProcessInfo ()

{
   static char  JpiPrcNam [16],
                JpiUserName [13],
                UpTime [32];
   static unsigned long  JpiAstCnt,
                         JpiAstLm,
                         JpiBioCnt,
                         JpiBytLm,
                         JpiBytCnt,
                         JpiBioLm,
                         JpiCpuTime,
                         JpiDioCnt,
                         JpiDioLm,
                         JpiEnqCnt,
                         JpiEnqLm,
                         JpiFilCnt,
                         JpiFilLm,
                         JpiPageFlts,
                         JpiPagFilCnt,
                         JpiPgFlQuota,
                         JpiPid,
                         JpiPrcCnt,
                         JpiPrcLm,
                         JpiTqCnt,
                         JpiTqLm,
                         JpiVirtPeak,
                         JpiWsSize,
                         JpiWsPeak,
                         PageFileUsedPercent;
   static unsigned long  ConnectTime [2],
                         CurrentTime [2],
                         JpiLoginTime [2];
   static char  LastExitStatus [48] = "";
   static $DESCRIPTOR (UpTimeFaoDsc, "!%D");
   static $DESCRIPTOR (UpTimeDsc, UpTime);
   static struct
   {
      unsigned short  buf_len;
      unsigned short  item;
      unsigned char  *buf_addr;
      void  *ret_len;
   }
      JpiItems [] =
   {
      { sizeof(JpiAstCnt), JPI$_ASTCNT, &JpiAstCnt, 0 },
      { sizeof(JpiAstLm), JPI$_ASTLM, &JpiAstLm, 0 },
      { sizeof(JpiBioCnt), JPI$_BIOCNT, &JpiBioCnt, 0 },
      { sizeof(JpiBioLm), JPI$_BIOLM, &JpiBioLm, 0 },
      { sizeof(JpiBytCnt), JPI$_BYTCNT, &JpiBytCnt, 0 },
      { sizeof(JpiBytLm), JPI$_BYTLM, &JpiBytLm, 0 },
      { sizeof(JpiCpuTime), JPI$_CPUTIM, &JpiCpuTime, 0 },
      { sizeof(JpiDioCnt), JPI$_DIOCNT, &JpiDioCnt, 0 },
      { sizeof(JpiDioLm), JPI$_DIOLM, &JpiDioLm, 0 },
      { sizeof(JpiEnqCnt), JPI$_ENQCNT, &JpiEnqCnt, 0 },
      { sizeof(JpiEnqLm), JPI$_ENQLM, &JpiEnqLm, 0 },
      { sizeof(JpiFilCnt), JPI$_FILCNT, &JpiFilCnt, 0 },
      { sizeof(JpiFilLm), JPI$_FILLM, &JpiFilLm, 0 },
      { sizeof(JpiLoginTime), JPI$_LOGINTIM, &JpiLoginTime, 0 },
      { sizeof(JpiPageFlts), JPI$_PAGEFLTS, &JpiPageFlts, 0 },
      { sizeof(JpiPagFilCnt), JPI$_PAGFILCNT, &JpiPagFilCnt, 0 },
      { sizeof(JpiPgFlQuota), JPI$_PGFLQUOTA, &JpiPgFlQuota, 0 },
      { sizeof(JpiPid), JPI$_PID, &JpiPid, 0 },
      { sizeof(JpiPrcCnt), JPI$_PRCCNT, &JpiPrcCnt, 0 },
      { sizeof(JpiPrcLm), JPI$_PRCLM, &JpiPrcLm, 0 },
      { sizeof(JpiPrcNam), JPI$_PRCNAM, &JpiPrcNam, 0 },
      { sizeof(JpiTqCnt), JPI$_TQCNT, &JpiTqCnt, 0 },
      { sizeof(JpiTqLm), JPI$_TQLM, &JpiTqLm, 0 },
      { sizeof(JpiUserName), JPI$_USERNAME, &JpiUserName, 0 },
      { sizeof(JpiVirtPeak), JPI$_VIRTPEAK, &JpiVirtPeak, 0 },
      { sizeof(JpiWsSize), JPI$_WSSIZE, &JpiWsSize, 0 },
      { sizeof(JpiWsPeak), JPI$_WSPEAK, &JpiWsPeak, 0 },
      {0,0,0,0}
   };

   int  status,
        col1, col2, col3, col4, colen,
        len1, len2;
   unsigned short  Length;
   unsigned long  HttpdPid;
   char  *cptr, 
         *StatePtr,
         *UpTimePtr;
   char  str1 [32],
         str2 [32],
         AstString [32],
         BioString [32],
         BytString [32],
         DioString [32],
         EnqString [32],
         FilString [32],
         PrcString [32],
         TqString [32];

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

   if (Debug) fprintf (stdout, "AddProcessInfo()\n");

   if (!(HttpdPid = CliInstancePid))
      if (!(HttpdPid = NextInstancePid))
         if (!(HttpdPid = HttpdGblSecPtr->HttpdProcessId))
            exit (SS$_BUGCHECK);
   status = sys$getjpiw (0, &HttpdPid, 0, &JpiItems, 0, 0, 0);
   if (Debug) fprintf (stdout, "sys$getjpi() %%X%08.08X\n", status);
   if (VMSnok (status) || status == SS$_NONEXPR)
   {
      if (status == SS$_NONEXPR)
         StatePtr = "NONEXPR";
      else
      if (status == SS$_SUSPENDED)
         StatePtr = "MWAIT";
      else
         return (status);
   }
   else
      StatePtr = NULL;

   CurrentProcessCount = JpiPrcCnt;

   JpiUserName[12] = '\0';
   for (cptr = JpiUserName; *cptr && *cptr != ' '; cptr++);
   *cptr = '\0';
   if (Debug) fprintf (stdout, "JpiUserName |%s|\n", JpiUserName);

   JpiPrcNam[15] = '\0';
   for (cptr = JpiPrcNam; *cptr && *cptr != ' '; cptr++);
   *cptr = '\0';
   if (Debug) fprintf (stdout, "JpiPrcNam |%s|\n", JpiPrcNam);

   sys$gettim (&CurrentTime);
   lib$sub_times (&CurrentTime, &JpiLoginTime, &ConnectTime);
   sys$fao (&UpTimeFaoDsc, &Length, &UpTimeDsc, &ConnectTime);
   UpTime[Length] = '\0';
   for (UpTimePtr = UpTime; isspace(*UpTimePtr); UpTimePtr++);

   if (HttpdGblSecPtr->Accounting.LastExitStatus)
      sprintf (LastExitStatus, "  %sExit:%s %%X%08.08X",
               ANSIbold, ANSInormal, HttpdGblSecPtr->Accounting.LastExitStatus);

   len1 = sprintf (str1, "%d", JpiAstCnt);
   len2 = sprintf (str2, "%d", JpiEnqCnt);
   if (len2 > len1) len1 = len2; else len2 = len1;
   col1 = sprintf (AstString, "%*.*s/%d", len1, len1, str1, JpiAstLm);
   colen = sprintf (EnqString, "%*.*s/%d", len2, len2, str2, JpiEnqLm);
   if (colen > col1) col1 = colen;

   len1 = sprintf (str1, "%d", JpiBioCnt);
   len2 = sprintf (str2, "%d", JpiFilCnt);
   if (len2 > len1) len1 = len2; else len2 = len1;
   col2 = sprintf (BioString, "%*.*s/%d", len1, len1, str1, JpiBioLm);
   colen = sprintf (FilString, "%*.*s/%d", len2, len2, str2, JpiFilLm);
   if (colen > col2) col2 = colen;

   len1 = sprintf (str1, "%d", JpiBytCnt);
   len2 = sprintf (str2, "%d", JpiPrcCnt);
   if (len2 > len1) len1 = len2; else len2 = len1;
   col3 = sprintf (BytString, "%*.*s/%d", len1, len1, str1, JpiBytLm);
   colen = sprintf (PrcString, "%*.*s/%d", len2, len2, str2, JpiPrcLm);
   if (colen > col3) col3 = colen;

   len1 = sprintf (str1, "%d", JpiDioCnt);
   len2 = sprintf (str2, "%d", JpiTqCnt);
   if (len2 > len1) len1 = len2; else len2 = len1;
   col4 = sprintf (DioString, "%*.*s/%d", len1, len1, str1, JpiDioLm);
   colen = sprintf (TqString, "%*.*s/%d", len2, len2, str2, JpiTqLm);
   if (colen > col4) col4 = colen;

   PageFileUsedPercent = 0;
   if (VMSok (status) && JpiPagFilCnt)
      PageFileUsedPercent = 100 - ((JpiPagFilCnt * 100) / JpiPgFlQuota);

   ScrPtr += sprintf (ScrPtr, "%s\r\n\ %sProcess:%s ",
                      ANSIceol, ANSIbold, ANSInormal);

   if (StatePtr)
      ScrPtr += sprintf (ScrPtr, "%s %s %s", ANSIreverse, StatePtr, ANSInormal);
   else
      ScrPtr += sprintf (ScrPtr, "%s", JpiPrcNam);

   ScrPtr += sprintf (ScrPtr,
"  %sPID:%s %08.08X  %sUser:%s %s  %sVersion:%s %s\
%s\r\n\
      %sUp:%s %s  %sCPU:%s %d %02.02d:%02.02d:%02.02d.%02.02d\
  %sStartup:%s %d%s\
%s\r\n\
 %sPg.Flts:%s %d  %sPg.Used:%s %d%%  %sWsSize:%s %d  %sWsPeak:%s %d\
%s\r\n\
     %sAST:%s %-*s  %sBIO:%s %-*s  %sBYT:%s %-*s  %sDIO:%s %-*s\
%s\r\n\
     %sENQ:%s %-*s  %sFIL:%s %-*s  %sPRC:%s %-*s   %sTQ:%s %-*s\
%s\r\n",
      ANSIbold, ANSInormal,
      HttpdPid,
      ANSIbold, ANSInormal,
      JpiUserName,
      ANSIbold, ANSInormal,
      HttpdGblSecPtr->HttpdVersion,
      ANSIceol,
      ANSIbold, ANSInormal,
      UpTimePtr,
      ANSIbold, ANSInormal,
      JpiCpuTime / 8640000,                     /* CPU day */
      (JpiCpuTime % 8640000) / 360000,          /* CPU hour */
      (JpiCpuTime % 360000) / 6000,             /* CPU minute */
      (JpiCpuTime % 6000 ) / 100,               /* CPU second */
      JpiCpuTime % 100,                         /* CPU 10mS */
      ANSIbold, ANSInormal,
      HttpdGblSecPtr->Accounting.StartupCount,
      LastExitStatus,
      ANSIceol,
      ANSIbold, ANSInormal,
      JpiPageFlts,
      ANSIbold, ANSInormal,
      PageFileUsedPercent,
      ANSIbold, ANSInormal,
      JpiWsSize,
      ANSIbold, ANSInormal,
      JpiWsPeak,
      ANSIceol,
      ANSIbold, ANSInormal,
      col1, AstString,
      ANSIbold, ANSInormal,
      col2, BioString,
      ANSIbold, ANSInormal,
      col3, BytString,
      ANSIbold, ANSInormal,
      col4, DioString,
      ANSIceol,
      ANSIbold, ANSInormal,
      col1, EnqString,
      ANSIbold, ANSInormal,
      col2, FilString,
      ANSIbold, ANSInormal,
      col3, PrcString,
      ANSIbold, ANSInormal,
      col4, TqString,
      ANSIceol);

   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
Add the HTTPd server counter information to the screen buffer from the count 
logical.  The information in this logical is binary data in the form of the 
HTTPd count data structure.  This is used to extract each field member from 
the data.
*/

int AddGeneralInfo ()

{
   static char  ErrorsNoticed [48] = "",
                NCScounts [48] = "",
                PathAlert [48] = "";

   int  idx, status,
        ConnectTotalCount,
        DigitCount,
        CodeCount,
        SslPercent;
   char  BytesRawRx [32],
         BytesRawTx [32],
         CurrentProcessCountString [16],
         Four03s [16];

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

   if (Debug) fprintf (stdout, "AddGeneralInfo()\n");

   ConnectTotalCount = HttpdGblSecPtr->Accounting.ConnectIpv4Count +
                       HttpdGblSecPtr->Accounting.ConnectIpv6Count;
   if (DoAlertAll)
      if (PrevConnectCount && ConnectTotalCount > PrevConnectCount)
         fputs ("\x07", stdout);
   PrevConnectCount = ConnectTotalCount;

   if (HttpdGblSecPtr->Accounting.PathAlertCount)
      sprintf (PathAlert, "  %sAlert:%s %d",
               ANSIbold, ANSInormal,
               HttpdGblSecPtr->Accounting.PathAlertCount);

   if (HttpdGblSecPtr->Accounting.ErrorsNoticedCount)
      sprintf (ErrorsNoticed, "  %sNoticed:%s %d",
               ANSIbold, ANSInormal,
               HttpdGblSecPtr->Accounting.ErrorsNoticedCount);

   if (HttpdGblSecPtr->Accounting.NcsConvertCount)
      sprintf (NCScounts, "  %sNCS:%s %d/%d",
               ANSIbold, ANSInormal,
               HttpdGblSecPtr->Accounting.NcsCount,
               HttpdGblSecPtr->Accounting.NcsConvertCount);

   if (HttpdGblSecPtr->Accounting.ConnectSslCount)
      SslPercent =
         (HttpdGblSecPtr->Accounting.ConnectSslCount * 100) /
         HttpdGblSecPtr->Accounting.ConnectAcceptedCount;
   else
      SslPercent = 0;

   DigitCount = 0;
   for (idx = 0; idx <= 5; idx++)
   {
      if (idx == 1) continue;
      CodeCount = HttpdGblSecPtr->Accounting.ResponseStatusCodeCountArray[idx];
      DigitCount++;
      while (CodeCount /= 10) DigitCount++;
   }
   CodeCount = HttpdGblSecPtr->Accounting.RequestForbiddenCount;
   DigitCount++;
   while (CodeCount /= 10) DigitCount++;
   if (DigitCount > 35)
      Four03s[0] = '\0';
   else
      sprintf (Four03s, " (403:%d)",
               HttpdGblSecPtr->Accounting.RequestForbiddenCount);

   CommaNumber (64, &HttpdGblSecPtr->Accounting.QuadBytesRawRx[0], BytesRawRx); 
   CommaNumber (64, &HttpdGblSecPtr->Accounting.QuadBytesRawTx[0], BytesRawTx); 

   sprintf (CurrentProcessCountString, "/%d", CurrentProcessCount);

   ScrPtr += sprintf (ScrPtr,
"%s\r\n\
 %sRequest:%s %d  %sCurrent:%s %d/%d  %sThrottle:%s %d/%d/%d%%  %sPeak:%s %d/%d\
%s\r\n\
  %sAccept:%s %d  %sReject:%s %d  %sBusy:%s %d  %sSSL:%s %d/%d%%%s%s\
%s\r\n\
 %sCONNECT:%s %d  %sDELETE:%s %d  %sGET:%s %d  %sHEAD:%s %d  \
%sPOST:%s %d  %sPUT:%s %d\
%s\r\n\
   %sAdmin:%s %d  %sCache:%s %d/%d/%d  %sDECnet:%s %d/%d  %sDir:%s %d%s%s\r\n\
     %sDCL:%s CLI:%d CGI:%d CGIplus:%d/%d RTE:%d/%d Processes:%d%s\
%s\r\n\
    %sFile:%s %d/%d  %sIsMap:%s %d  %sProxy:%s %d\
  %sPut:%s %d  %sSSI:%s %d  %sUpd:%s %d\
%s\r\n\
%s\r\n\
     %s0xx:%s %d  %s2xx:%s %d  %s3xx:%s %d  %s4xx:%s %d%s  %s5xx:%s %d\
%s\r\n\
      %sRx:%s %s (%d err) %sTx:%s %s (%d err)\
%s\r\n",
      ANSIceol,
      ANSIbold, ANSInormal,
      HttpdGblSecPtr->Accounting.RequestParseCount,
      ANSIbold, ANSInormal,
      HttpdGblSecPtr->Accounting.ConnectCurrent,
      HttpdGblSecPtr->Accounting.ConnectProcessing,
      ANSIbold, ANSInormal,
      HttpdGblSecPtr->Accounting.ThrottleCurrentlyProcessing,
      HttpdGblSecPtr->Accounting.ThrottleCurrentlyQueued,
      HttpdGblSecPtr->Accounting.ThrottleBusyMetric,
      ANSIbold, ANSInormal,
      HttpdGblSecPtr->Accounting.ConnectPeak,
      HttpdGblSecPtr->Accounting.ConnectProcessingPeak,
      ANSIceol,
      ANSIbold, ANSInormal,
      HttpdGblSecPtr->Accounting.ConnectAcceptedCount,
      ANSIbold, ANSInormal,
      HttpdGblSecPtr->Accounting.ConnectRejectedCount,
      ANSIbold, ANSInormal,
      HttpdGblSecPtr->Accounting.ConnectTooBusyCount,
      ANSIbold, ANSInormal,
      HttpdGblSecPtr->Accounting.ConnectSslCount,
      SslPercent,
      PathAlert,
      ErrorsNoticed,
      ANSIceol,
      ANSIbold, ANSInormal,
      HttpdGblSecPtr->Accounting.MethodConnectCount,
      ANSIbold, ANSInormal,
      HttpdGblSecPtr->Accounting.MethodDeleteCount,
      ANSIbold, ANSInormal,
      HttpdGblSecPtr->Accounting.MethodGetCount,
      ANSIbold, ANSInormal,
      HttpdGblSecPtr->Accounting.MethodHeadCount,
      ANSIbold, ANSInormal,
      HttpdGblSecPtr->Accounting.MethodPostCount,
      ANSIbold, ANSInormal,
      HttpdGblSecPtr->Accounting.MethodPutCount,
      ANSIceol,
      ANSIbold, ANSInormal,
      HttpdGblSecPtr->Accounting.DoServerAdminCount,
      ANSIbold, ANSInormal,
      HttpdGblSecPtr->Accounting.CacheLoadCount,
      HttpdGblSecPtr->Accounting.CacheHitCount,
      HttpdGblSecPtr->Accounting.CacheHitNotModifiedCount,
      ANSIbold, ANSInormal,
      HttpdGblSecPtr->Accounting.DoDECnetCgiCount,
      HttpdGblSecPtr->Accounting.DoDECnetOsuCount,
      ANSIbold, ANSInormal,
      HttpdGblSecPtr->Accounting.DoDirectoryCount,
      NCScounts,
      ANSIceol,
      ANSIbold, ANSInormal,
      HttpdGblSecPtr->Accounting.DoDclCommandCount,
      HttpdGblSecPtr->Accounting.DoScriptCount,
      HttpdGblSecPtr->Accounting.DoCgiPlusScriptCount,
      HttpdGblSecPtr->Accounting.DclCgiPlusReusedCount,
      HttpdGblSecPtr->Accounting.DoRteScriptCount,
      HttpdGblSecPtr->Accounting.DclRteReusedCount,
      HttpdGblSecPtr->Accounting.DclCrePrcCount,
      CurrentProcessCountString,
      ANSIceol,
      ANSIbold, ANSInormal,
      HttpdGblSecPtr->Accounting.DoFileCount,
      HttpdGblSecPtr->Accounting.DoFileNotModifiedCount,
      ANSIbold, ANSInormal,
      HttpdGblSecPtr->Accounting.DoIsMapCount,
      ANSIbold, ANSInormal,
      HttpdGblSecPtr->ProxyAccounting.MethodConnectCount +
         HttpdGblSecPtr->ProxyAccounting.MethodDeleteCount +
         HttpdGblSecPtr->ProxyAccounting.MethodGetCount +
         HttpdGblSecPtr->ProxyAccounting.MethodHeadCount +
         HttpdGblSecPtr->ProxyAccounting.MethodPostCount +
         HttpdGblSecPtr->ProxyAccounting.MethodPutCount,
      ANSIbold, ANSInormal,
      HttpdGblSecPtr->Accounting.DoPutCount,
      ANSIbold, ANSInormal,
      HttpdGblSecPtr->Accounting.DoSsiCount,
      ANSIbold, ANSInormal,
      HttpdGblSecPtr->Accounting.DoUpdateCount,
      ANSIceol,
      ANSIceol,
      ANSIbold, ANSInormal,
      HttpdGblSecPtr->Accounting.ResponseStatusCodeCountArray[0],
      ANSIbold, ANSInormal,
      HttpdGblSecPtr->Accounting.ResponseStatusCodeCountArray[2],
      ANSIbold, ANSInormal,
      HttpdGblSecPtr->Accounting.ResponseStatusCodeCountArray[3],
      ANSIbold, ANSInormal,
      HttpdGblSecPtr->Accounting.ResponseStatusCodeCountArray[4],
      Four03s,
      ANSIbold, ANSInormal,
      HttpdGblSecPtr->Accounting.ResponseStatusCodeCountArray[5],
      ANSIceol,
      ANSIbold, ANSInormal,
      BytesRawRx,
      HttpdGblSecPtr->Accounting.NetReadErrorCount,
      ANSIbold, ANSInormal,
      BytesRawTx,
      HttpdGblSecPtr->Accounting.NetWriteErrorCount,
      ANSIceol);

   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
Add the HTTPd server counter information to the screen buffer from the count 
logical.  The information in this logical is binary data in the form of the 
HTTPd count data structure.  This is used to extract each field member from 
the data.
*/

int AddProxyInfo ()

{
   static int  FreeSpaceAlertCount;
   static long  Addx2 = 2;

   int  status,
        ConnectTotalCount,
        FreeBlocks,
        FreeMBytes,
        PercentBytesCache,
        PercentBytesNetwork,
        PercentBytesNotCacheable,
        PercentCountCacheRead,
        PercentCountCacheWrite,
        PercentCountRequestNotCacheable,
        PercentCountResponseNotCacheable,
        PercentCountNetwork,
        ErrorCount,
        FreePercent,
        TotalMBytes,
        TotalBlocks,
        UsedBlocks,
        UsedMBytes,
        UsedPercent;
   unsigned long  QuadBytesCache [2],
                  QuadBytesNotCacheable [2],
                  QuadBytesRaw [2],
                  QuadBytesTotal [2];
   float  FloatBytesCache,
          FloatBytesNotCacheable,
          FloatBytesNetwork,
          FloatBytesTotal,
          FloatCountCacheRead,
          FloatCountCacheWrite,
          FloatCountRequestNotCacheable,
          FloatCountResponseNotCacheable,
          FloatCountNetwork,
          FloatCountTotal,
          FloatNetworkCount,
          FloatPercent;
   char  BytesCacheRx [32],
         BytesCacheTx [32],
         BytesNotCacheableRx [32],
         BytesNotCacheableTx [32],
         BytesRawRx [32],
         BytesRawTx [32],
         BytesTotal [32],
         ConnectString [128];
   char  *cptr, *sptr,
         *DevNamePtr,
         *EnabledPtr,
         *SpaceAvailablePtr;

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

   if (Debug) fprintf (stdout, "AddProxyInfo()\n");

   ConnectTotalCount = HttpdGblSecPtr->Accounting.ConnectIpv4Count +
                       HttpdGblSecPtr->Accounting.ConnectIpv6Count;
   if (!DoGeneralInfo)
   {
      if (DoAlertAll)
         if (PrevConnectCount && ConnectTotalCount > PrevConnectCount)
            fputs ("\x07", stdout);
      PrevConnectCount = ConnectTotalCount;
   }

   if (HttpdGblSecPtr->ProxyAccounting.ServingEnabled)
      EnabledPtr = "enabled";
   else
      EnabledPtr = "DISABLED";

   if (HttpdGblSecPtr->ProxyAccounting.FreeSpaceAvailable)
   {
      SpaceAvailablePtr = "available";
      FreeSpaceAlertCount = 0;
   }
   else
   {
      SpaceAvailablePtr = "\x1b[1mUNAVAILABLE\x1b[0m";
      if (!FreeSpaceAlertCount--)
      {
         /* alert no free space every minute or so */
         if (IntervalSeconds >= 60)
            FreeSpaceAlertCount = 1;
         else 
            FreeSpaceAlertCount = 60 / IntervalSeconds;
      }
   }

   ProxyMaintDeviceStats (&DevNamePtr, &TotalBlocks, &UsedBlocks,
                          &FreeBlocks, &ErrorCount);
   TotalMBytes = PROXY_MAINT_DEVICE_MBYTES(TotalBlocks);
   UsedMBytes = PROXY_MAINT_DEVICE_MBYTES(UsedBlocks);
   FreeMBytes = PROXY_MAINT_DEVICE_MBYTES(FreeBlocks);
   UsedPercent = PROXY_MAINT_DEVICE_PERCENT_USED(TotalBlocks,FreeBlocks);
   FreePercent = PROXY_MAINT_DEVICE_PERCENT_FREE(TotalBlocks,FreeBlocks);

   /*********/
   /* bytes */
   /*********/

   status = lib$addx (&HttpdGblSecPtr->ProxyAccounting.QuadBytesCacheRx,
                      &HttpdGblSecPtr->ProxyAccounting.QuadBytesCacheTx,
                      &QuadBytesCache, &Addx2);
   if (Debug) fprintf (stdout, "lib$addx() %%X%08.08X\n", status);

   status = lib$addx (&HttpdGblSecPtr->ProxyAccounting.QuadBytesNotCacheableRx,
                      &HttpdGblSecPtr->ProxyAccounting.QuadBytesNotCacheableTx,
                      &QuadBytesNotCacheable, &Addx2);
   if (Debug) fprintf (stdout, "lib$addx() %%X%08.08X\n", status);

   status = lib$addx (&HttpdGblSecPtr->ProxyAccounting.QuadBytesRawRx,
                      &HttpdGblSecPtr->ProxyAccounting.QuadBytesRawTx,
                      &QuadBytesRaw, &Addx2);
   if (Debug) fprintf (stdout, "lib$addx() %%X%08.08X\n", status);

   status = lib$addx (&QuadBytesCache,
                      &QuadBytesRaw,
                      &QuadBytesTotal, &Addx2);
   if (Debug) fprintf (stdout, "lib$addx() %%X%08.08X\n", status);

   FloatBytesCache = (float)QuadBytesCache[0] +
                     (float)QuadBytesCache[1] * (float)0xffffffff;
   if (Debug) fprintf (stdout, "FloatBytesCache: %f\n", FloatBytesCache);

   CommaNumber (64, &HttpdGblSecPtr->ProxyAccounting.QuadBytesCacheRx[0], BytesCacheRx); 
   for (cptr = BytesCacheRx; *cptr && *cptr != ','; cptr++);
   if (*cptr) cptr++;
   while (*cptr && *cptr != ',') cptr++;
   if (*cptr)
   {
      sptr = cptr;
      cptr++;
      while (*cptr && *cptr != ',') cptr++;
      if (*cptr)
         *(unsigned short*)sptr = 'M\0';
      else
         *(unsigned short*)sptr = 'k\0';
   }
   CommaNumber (64, &HttpdGblSecPtr->ProxyAccounting.QuadBytesCacheTx[0], BytesCacheTx); 
   for (cptr = BytesCacheTx; *cptr && *cptr != ','; cptr++);
   if (*cptr) cptr++;
   while (*cptr && *cptr != ',') cptr++;
   if (*cptr)
   {
      sptr = cptr;
      cptr++;
      while (*cptr && *cptr != ',') cptr++;
      if (*cptr)
         *(unsigned short*)sptr = 'M\0';
      else
         *(unsigned short*)sptr = 'k\0';
   }
   CommaNumber (64, &HttpdGblSecPtr->ProxyAccounting.QuadBytesRawRx[0], BytesRawRx); 
   CommaNumber (64, &HttpdGblSecPtr->ProxyAccounting.QuadBytesRawTx[0], BytesRawTx); 
   CommaNumber (64, &QuadBytesRaw[0], BytesTotal); 

   /********************/
   /* bytes percentage */
   /********************/

   FloatBytesNotCacheable = (float)QuadBytesNotCacheable[0] +
                            (float)QuadBytesNotCacheable[1] * (float)0xffffffff;
   if (Debug)
      fprintf (stdout, "FloatBytesNotCacheable: %f\n", FloatBytesNotCacheable);

   FloatBytesNetwork = (float)QuadBytesRaw[0] +
                   (float)QuadBytesRaw[1] * (float)0xffffffff;
   if (Debug) fprintf (stdout, "FloatBytesNetwork: %f\n", FloatBytesNetwork);

   FloatBytesTotal = (float)QuadBytesTotal[0] +
                     (float)QuadBytesTotal[1] * (float)0xffffffff;
   if (Debug) fprintf (stdout, "FloatBytesTotal: %f\n", FloatBytesTotal);

   if (FloatBytesTotal > 0.0)
   {
      PercentBytesCache = (int)(FloatPercent =
                          FloatBytesCache * 100.0 / FloatBytesTotal);
      if (FloatPercent - (float)PercentBytesCache >= 0.5) PercentBytesCache++;

      PercentBytesNotCacheable =
         (int)(FloatPercent = FloatBytesNotCacheable * 100.0 / FloatBytesTotal);
      if (FloatPercent - (float)PercentBytesNotCacheable >= 0.5)
         PercentBytesNotCacheable++;

      PercentBytesNetwork =
         (int)(FloatPercent = FloatBytesNetwork * 100.0 / FloatBytesTotal);
      if (FloatPercent - (float)PercentBytesNetwork >= 0.5)
         PercentBytesNetwork++;
   }
   else
      PercentBytesCache = PercentBytesNotCacheable = PercentBytesNetwork = 0;

   /*********************/
   /* counts percentage */
   /*********************/

   FloatCountRequestNotCacheable = HttpdGblSecPtr->ProxyAccounting.RequestNotCacheableCount;
   FloatCountResponseNotCacheable = HttpdGblSecPtr->ProxyAccounting.ResponseNotCacheableCount;
   FloatCountNetwork = HttpdGblSecPtr->ProxyAccounting.ConnectIpv4Count +
                       HttpdGblSecPtr->ProxyAccounting.ConnectIpv6Count;
   FloatCountCacheRead = HttpdGblSecPtr->ProxyAccounting.CacheReadCount;
   FloatCountCacheWrite = HttpdGblSecPtr->ProxyAccounting.CacheWriteCount;
   FloatCountTotal = FloatCountNetwork + FloatCountCacheRead;

   if (FloatCountTotal > 0.0)
   {
      PercentCountCacheRead = (int)(FloatPercent =
                          FloatCountCacheRead * 100.0 / FloatCountTotal);
      if (FloatPercent - (float)PercentCountCacheRead >= 0.5)
         PercentCountCacheRead++;

      PercentCountRequestNotCacheable =
         (int)(FloatPercent =
               FloatCountRequestNotCacheable * 100.0 / FloatCountTotal);
      if (FloatPercent - (float)PercentCountRequestNotCacheable >= 0.5)
         PercentCountRequestNotCacheable++;

      PercentCountNetwork = 
         (int)(FloatPercent = FloatCountNetwork * 100.0 / FloatCountTotal);
      if (FloatPercent - (float)PercentCountNetwork >= 0.5)
         PercentCountNetwork++;
   }
   else
      PercentCountCacheRead =
         PercentCountRequestNotCacheable =
         PercentCountNetwork = 0;

   if (FloatCountNetwork > 0.0)
   {
      PercentCountCacheWrite = 
         (int)(FloatPercent = FloatCountCacheWrite * 100.0 / FloatCountNetwork);
      if (FloatPercent - (float)PercentCountCacheWrite >= 0.5)
         PercentCountCacheWrite++;

      PercentCountResponseNotCacheable =
         (int)(FloatPercent =
               FloatCountResponseNotCacheable * 100.0 / FloatCountNetwork);
      if (FloatPercent - (float)PercentCountResponseNotCacheable >= 0.5)
         PercentCountResponseNotCacheable++;
   }
   else
      PercentCountCacheWrite = PercentCountResponseNotCacheable = 0;

   /***********/
   /* display */
   /***********/

   if (DoGeneralInfo)
      ConnectString[0] = '\0';
   else
      sprintf (ConnectString, "  %sConnect:%s Current:%d Peak:%d",
               ANSIbold, ANSInormal,
               HttpdGblSecPtr->Accounting.ConnectCurrent,
               HttpdGblSecPtr->Accounting.ConnectPeak);

   ScrPtr += sprintf (ScrPtr,
"%s\r\n\
   %sProxy:%s %s%s\
%s\r\n\
 %sCONNECT:%s %d  %sDELETE:%s %d  %sGET:%s %d  %sHEAD:%s %d  \
%sPOST:%s %d  %sPUT:%s %d\
%s\r\n\
     %sNot:%s cacheable  Rx/Tx: %d%%  Request:%d (%d%%) Response:%d (%d%%)\
%s\r\n\
 %sNetwork:%s Rx:%s Tx:%s (%d%%)  Requested:%d (%d%%)\
%s\r\n\
  %sLookup:%s Numeric:%d DNS:%d Cache:%d Error:%d  %sFTP:%s %d\
%s\r\n\
   %sCache:%s Rx:%s Tx:%s (%d%%)  Rd:%d/%d (%d%%) Wr:%d (%d%%)\
%s\r\n\
  %sDevice:%s %s %d blocks (%dMB)  %sErrors:%s %d  %sSpace:%s %s\
%s\r\n\
          %d used (%dMB %d%%), %d free (%dMB %d%%)\
%s\r\n\
    %sScan:%s %.69s\
%s\r\n",
      ANSIceol,
      ANSIbold, ANSInormal,
      EnabledPtr,
      ConnectString,
      ANSIceol,
      ANSIbold, ANSInormal,
      HttpdGblSecPtr->ProxyAccounting.MethodConnectCount,
      ANSIbold, ANSInormal,
      HttpdGblSecPtr->ProxyAccounting.MethodDeleteCount,
      ANSIbold, ANSInormal,
      HttpdGblSecPtr->ProxyAccounting.MethodGetCount,
      ANSIbold, ANSInormal,
      HttpdGblSecPtr->ProxyAccounting.MethodHeadCount,
      ANSIbold, ANSInormal,
      HttpdGblSecPtr->ProxyAccounting.MethodPostCount,
      ANSIbold, ANSInormal,
      HttpdGblSecPtr->ProxyAccounting.MethodPutCount,
      ANSIceol,
      ANSIbold, ANSInormal,
      PercentBytesNotCacheable,
      HttpdGblSecPtr->ProxyAccounting.RequestNotCacheableCount,
      PercentCountRequestNotCacheable,
      HttpdGblSecPtr->ProxyAccounting.ResponseNotCacheableCount,
      PercentCountResponseNotCacheable,
      ANSIceol,
      ANSIbold, ANSInormal,
      BytesRawRx,
      BytesRawTx,
      PercentBytesNetwork,
      HttpdGblSecPtr->ProxyAccounting.ConnectIpv4Count +
         HttpdGblSecPtr->ProxyAccounting.ConnectIpv6Count,
      PercentCountNetwork,
      ANSIceol,
      ANSIbold, ANSInormal,
      HttpdGblSecPtr->Accounting.LookupLiteralCount,
      HttpdGblSecPtr->Accounting.LookupDnsNameCount,
      HttpdGblSecPtr->Accounting.LookupCacheNameCount,
      HttpdGblSecPtr->Accounting.LookupDnsNameErrorCount,
      ANSIbold, ANSInormal,
      HttpdGblSecPtr->ProxyAccounting.FtpCount,
      ANSIceol,
      ANSIbold, ANSInormal,
      BytesCacheRx,
      BytesCacheTx,
      PercentBytesCache,
      HttpdGblSecPtr->ProxyAccounting.CacheReadCount,
      HttpdGblSecPtr->ProxyAccounting.CacheRead304Count,
      PercentCountCacheRead,
      HttpdGblSecPtr->ProxyAccounting.CacheWriteCount,
      PercentCountCacheWrite,
      ANSIceol,
      ANSIbold, ANSInormal,
      DevNamePtr, 
      TotalBlocks, TotalMBytes,
      ANSIbold, ANSInormal,
      ErrorCount,
      ANSIbold, ANSInormal,
      SpaceAvailablePtr,
      ANSIceol,
      UsedBlocks, UsedMBytes, UsedPercent,
      FreeBlocks, FreeMBytes, FreePercent,
      ANSIceol,
      ANSIbold, ANSInormal,
      HttpdGblSecPtr->ProxyAccounting.StatusString,
      ANSIceol);

   return (SS$_NORMAL);
}

/****************************************************************************/
/*
Return information about file system space on the proxy cache device.  Will
function correctly with volume sets of up to eight members.  Returns a VMS
status code that should be checked for success.
*/

int ProxyMaintDeviceStats
(
char **DevNamePtrPtr,
int *TotalBlocksPtr,
int *UsedBlocksPtr,
int *FreeBlocksPtr,
int *ErrorCountPtr
)
{
#define PROXY_MAINT_CACHE_DEVICE_MAX 8

   static int  DeviceCount,
               ErrCnt,
               FreeBlocks,
               MaxBlock,
               VolCount;
   static unsigned short  Length;
   static short  DevChannel [PROXY_MAINT_CACHE_DEVICE_MAX];
   static char  CacheDevName [65],
                DevName [65] = PROXY_CACHE_DEVICE;
   static $DESCRIPTOR (DevNameDsc, "");
   static struct {
      short BufferLength;
      short ItemCode;
      void  *BufferPtr;
      void  *LengthPtr;
   }
   DevNamItemList [] = 
   {
      { sizeof(CacheDevName), DVI$_DEVNAM, &CacheDevName, &Length },
      { sizeof(VolCount), DVI$_VOLCOUNT, &VolCount, 0 },
      { 0, 0, 0, 0 }
   },
   NextDevNamItemList [] = 
   {
      { sizeof(DevName), DVI$_NEXTDEVNAM, &DevName, &Length },
      { 0, 0, 0, 0 }
   },
   BlocksItemList [] = 
   {
      { sizeof(MaxBlock), DVI$_MAXBLOCK, &MaxBlock, 0 },
      { sizeof(FreeBlocks), DVI$_FREEBLOCKS, &FreeBlocks, 0 },
      { sizeof(ErrCnt), DVI$_ERRCNT, &ErrCnt, 0 },
      { 0, 0, 0, 0 }
   };

   int  idx,
        status,
        ErrorCount,
        TotalBlocks,
        TotalFreeBlocks,
        TotalUsedBlocks;
   IO_SB  IOsb;

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

   if (Debug) fprintf (stdout, "ProxyMaintDeviceStats()\n");

   if (DevNamePtrPtr) *DevNamePtrPtr = DevName;
   if (TotalBlocksPtr) *TotalBlocksPtr = 0;
   if (UsedBlocksPtr) *UsedBlocksPtr = 0;
   if (FreeBlocksPtr) *FreeBlocksPtr = 0;
   if (ErrorCountPtr) *ErrorCountPtr = 0;

   if (!DeviceCount)
   {
      /**************/
      /* initialize */
      /**************/

      DevNameDsc.dsc$w_length = strlen(PROXY_CACHE_ROOT);
      DevNameDsc.dsc$a_pointer = PROXY_CACHE_ROOT;

      /* assign a channel to the cache device (or primary if a volume set) */
      if (VMSnok (status =
          sys$assign (&DevNameDsc, &DevChannel[DeviceCount], 0, 0)))
         return (status);

      DeviceCount++;

      status = sys$getdviw (0, DevChannel[0], 0,
                            &DevNamItemList, &IOsb, 0, 0, 0);
      if (VMSok (status)) status = IOsb.Status;
      if (VMSnok (status)) return (status);

      CacheDevName[Length] = '\0';
      if (CacheDevName[0] == '_')
         memmove (CacheDevName, CacheDevName+1, Length);
      if (Debug) fprintf (stdout, "|%s| %d\n", CacheDevName, VolCount);

      /* loop assigning a channel to all devices in volume set (if it is!) */
      while (--VolCount)
      {
         if (DeviceCount >= PROXY_MAINT_CACHE_DEVICE_MAX)
            exit (SS$_BUGCHECK);

         status = sys$getdviw (0, DevChannel[0], 0,
                               &NextDevNamItemList, &IOsb, 0, 0, 0);
         if (VMSok (status)) status = IOsb.Status;
         if (VMSnok (status)) return (status);

         DevName[Length] = '\0';
         if (Debug) fprintf (stdout, "|%s|\n", DevName);

         if (!Length) break;

         DevNameDsc.dsc$w_length = Length;
         DevNameDsc.dsc$a_pointer = DevName;

         if (VMSnok (status =
             sys$assign (&DevNameDsc, &DevChannel[DeviceCount++], 0, 0)))
            return (status);
      }

      if (Debug) fprintf (stdout, "DeviceCount: %d\n", DeviceCount);
   }

   /***********/
   /* process */
   /***********/

   ErrorCount = TotalBlocks = TotalFreeBlocks = 0;

   for (idx = 0; idx < DeviceCount; idx++)
   {
      status = sys$getdviw (0, DevChannel[idx], 0,
                            &BlocksItemList, &IOsb, 0, 0, 0);
      if (VMSok (status)) status = IOsb.Status;
      if (VMSnok (status)) return (status);

      if (Debug) fprintf (stdout, "%d %d\n", MaxBlock, FreeBlocks);

      TotalBlocks += MaxBlock;
      TotalFreeBlocks += FreeBlocks;
      ErrorCount += ErrCnt;
   }

   TotalUsedBlocks = TotalBlocks - TotalFreeBlocks;

   if (Debug)
      fprintf (stdout, "%d %d %d %dMB %dMB %dMB %d%% %d%%\n",
               TotalBlocks, TotalFreeBlocks, TotalUsedBlocks,
               TotalBlocks >> 11, TotalFreeBlocks >> 11, TotalUsedBlocks >> 11,
               TotalFreeBlocks*100/TotalBlocks, 
               TotalUsedBlocks*100/TotalBlocks);

   if (DevNamePtrPtr) *DevNamePtrPtr = CacheDevName;
   if (TotalBlocksPtr) *TotalBlocksPtr = TotalBlocks;
   if (UsedBlocksPtr) *UsedBlocksPtr = TotalUsedBlocks;
   if (FreeBlocksPtr) *FreeBlocksPtr = TotalFreeBlocks;
   if (ErrorCountPtr) *ErrorCountPtr = ErrorCount;

   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
Add the information about the latest request to the screen buffer from the 
request logical.  Each plain-text string in this logical is terminated by a 
null character.
*/ 

int AddRequest ()

{
   static int  PathAlertBellCount = 0,
               PrevPathAlertCount = 0;
   static char  *LookupHostNamePtr;

   int  status;
   char  *bptr, *cptr, *sptr, *tptr;
   char  HostName [256];

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

   if (Debug) fprintf (stdout, "AddRequest()\n");

   sptr = ScrPtr;

   sptr += sprintf (sptr,
"%s\r\n    %sTime:%s %s  %sStatus:%s %03d  \
%sRx:%s %d  %sTx:%s %d  %sDuration:%s %s%s\r\n\
 %sService:%s %s%s%s%s%s\r\n\
    %sHost:%s ",
           ANSIceol, ANSIbold, ANSInormal,
           HttpdGblSecPtr->Request.Time,
           ANSIbold, ANSInormal,
           HttpdGblSecPtr->Request.HttpStatus,
           ANSIbold, ANSInormal,
           HttpdGblSecPtr->Request.BytesRawRx,
           ANSIbold, ANSInormal,
           HttpdGblSecPtr->Request.BytesRawTx,
           ANSIbold, ANSInormal,
           HttpdGblSecPtr->Request.Duration,
           ANSIceol, ANSIbold, ANSInormal,
           HttpdGblSecPtr->Request.Service,
           HttpdGblSecPtr->Request.PrcNam[0] ? " (" : "",
           HttpdGblSecPtr->Request.PrcNam,
           HttpdGblSecPtr->Request.PrcNam[0] ? ")" : "",
           ANSIceol, ANSIbold, ANSInormal);

   cptr = HttpdGblSecPtr->Request.ClientHostName;

   if (PrevHostName[0])
   {
      if (strcmp (cptr, PrevHostName))
      {
         strcpy (PrevHostName, cptr);
         LookupHostNamePtr = NULL;
         if (DoAlertHost) fputs ("\x07", stdout);
      }
   }
   else
      strcpy (PrevHostName, cptr);

   if (DoAlertPath && HttpdGblSecPtr->Request.Alert)
   {
      if (HttpdGblSecPtr->Accounting.PathAlertCount > PrevPathAlertCount)
      {
         PrevPathAlertCount = HttpdGblSecPtr->Accounting.PathAlertCount;
         PathAlertBellCount = PathAlertBellRepetition;
      }
   }
   if (PathAlertBellCount)
   {
      fputs ("\x07", stdout);
      PathAlertBellCount--;
   }

   if (DoLookupHost)
   {
      if (!LookupHostNamePtr)
      {
         /* check for IPv4 address */
         for (tptr = cptr; isdigit(*tptr) || *tptr == '.'; tptr++);
         if (*tptr)
         {
            /* nope, check for IPv6 address */
            for (tptr = cptr;
                 isxdigit(*tptr) || *tptr == ':' ||
                    *tptr == '-' || *tptr == '.';
                 tptr++);
         }
         if (*tptr)
            LookupHostNamePtr = NULL; 
         else
            LookupHostNamePtr = TcpIpLookupHostName (cptr, 0, NULL);
      }

      if (LookupHostNamePtr)
      {
         /* looks like a resolveable IPv4 or IPv6 address */
         tptr = LookupHostNamePtr;
         while (*tptr) *sptr++ = *tptr++;
         *sptr++ = ' ';
         *sptr++ = '(';
         while (*cptr) *sptr++ = *cptr++;
         *sptr++ = ')';
      }
      else
         while (*cptr) *sptr++ = *cptr++;
   }
   else
      while (*cptr) *sptr++ = *cptr++;

   /* ensure it doesn't cause the line to wrap */
   HostName[69] = '\0';

   if (HttpdGblSecPtr->Request.ReadError[0])
   {
      sptr += sprintf (sptr, "%s\r\n  %sRx-ERR:%s ",
                       ANSIceol, ANSIbold, ANSInormal);
      tptr = sptr + 68;
      for (cptr = HttpdGblSecPtr->Request.ReadError;
           *cptr && sptr < tptr;
           *sptr++ = *cptr++);
   }

   if (HttpdGblSecPtr->Request.WriteError[0])
   {
      sptr += sprintf (sptr, "%s\r\n  %sTx-ERR:%s ",
                       ANSIceol, ANSIbold, ANSInormal);
      tptr = sptr + 68;
      for (cptr = HttpdGblSecPtr->Request.WriteError;
           *cptr && sptr < tptr;
           *sptr++ = *cptr++);
   }

   sptr += sprintf (sptr, "%s\r\n %sRequest:%s %s",
                    ANSIceol, ANSIbold, ANSInormal,
                    HttpdGblSecPtr->Request.Alert ? ANSIbold : "");

   /* limit the request string to 3 x 80 character lines minus field name */
   tptr = sptr + 226;
   /* allow for foregoing error reports */
   if (HttpdGblSecPtr->Request.ReadError[0]) tptr -= 80;
   if (HttpdGblSecPtr->Request.WriteError[0]) tptr -= 80;

   for (cptr = HttpdGblSecPtr->Request.MethodName;
        *cptr && sptr < tptr;
        *sptr++ = *cptr++);
   if (HttpdGblSecPtr->Request.Uri[0] && sptr < tptr) *sptr++ = ' ';
   for (cptr = HttpdGblSecPtr->Request.Uri;
        *cptr && sptr < tptr;
        *sptr++ = *cptr++);
   if (sptr >= tptr)
      sptr += sprintf (sptr, "%s...%s", ANSIbold, ANSInormal);
   else
   if (HttpdGblSecPtr->Request.Alert) 
      sptr += sprintf (sptr, "%s", ANSInormal);

   sptr += sprintf (sptr, "%s", ANSIceol);

   ScrPtr = sptr;

   return (SS$_NORMAL);
}

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

int AddStatusMessage ()

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

   if (Debug) fprintf (stdout, "AddStatusMessage()\n");

   ScrPtr += sprintf (ScrPtr, "%s\r\n  %sSTATUS:%s %s%s",
                      ANSIceos, ANSIbold, ANSInormal,
                      HttpdGblSecPtr->StatusMessage, ANSIceol);

   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
Get the IP name using synchronous address-to-name Ipv4/Ipv6 lookup.
Supply either an IP address string, or an IPv4 or IPv6 address.
The IP address string must be terminated by white-space or a null.
The static host name buffer should not be modified by the calling routine.

(This code is *derived* from a similar functionality in [SRC.HTTPD]TCPIP.C)
*/

char* TcpIpLookupHostName
(
char *IpAddressString,
int Ip4Address,
int *Ip6AddressPtr
)
{
   /* this is one second delta */
   static unsigned long  RetryDelta [2] = { -10000000, -1 };

   static unsigned char ControlSubFunction [4] =
      { INETACP_FUNC$C_GETHOSTBYADDR, INETACP$C_TRANS, 0, 0 };
   static struct dsc$descriptor ControlSubFunctionDsc =
      { 4, DSC$K_DTYPE_T, DSC$K_CLASS_S, (char*)&ControlSubFunction };

   static unsigned short  LookupChannel;
   static char  HostName [127+1];
   static $DESCRIPTOR (HostNameDsc, HostName);

   int  status,
        RetryAttempts;
   unsigned short  HostNameLength;
   int  LocalIp6Address [4];
   char  *cptr, *sptr, *zptr;
   struct dsc$descriptor *dscptr;
   struct dsc$descriptor HostAddressDsc;
   IO_SB  LookupIOsb;

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

   if (Debug) fprintf (stdout, "TcpIpLookupHostName()\n");

   /* assign a channel to the internet template device */
   if (!LookupChannel)
   {
      status = sys$assign (&TcpIpDeviceDsc, &LookupChannel, 0, 0);
      if (VMSnok (status)) exit (status);
   }

   if (!Ip6AddressPtr) Ip6AddressPtr = LocalIp6Address;

   if (IpAddressString)
   {
      Ip4Address = 0;
      memset (Ip6AddressPtr, 0, 16);
      status = TcpIpStringAddress (IpAddressString,
                                   &Ip4Address, Ip6AddressPtr);
      if (VMSnok (status)) return (NULL);
   }

   if (Ip4Address)
      cptr = TcpIpHostCache (NULL, &Ip4Address, NULL);
   else
      cptr = TcpIpHostCache (NULL, NULL, Ip6AddressPtr);
   if (cptr)
      if (*cptr == '?')
         return (NULL);
      else
         return (cptr);

   dscptr = (struct dsc$descriptor*)&HostNameDsc;
   dscptr->dsc$b_class = DSC$K_CLASS_S;
   dscptr->dsc$b_dtype = DSC$K_DTYPE_T;
   dscptr->dsc$w_length = sizeof(HostName)-1;
   dscptr->dsc$a_pointer = HostName;

   dscptr = &HostAddressDsc;
   dscptr->dsc$b_class = DSC$K_CLASS_S;
   dscptr->dsc$b_dtype = DSC$K_DTYPE_T;
   if (Ip4Address)
   {
      dscptr->dsc$w_length = 4;
      dscptr->dsc$a_pointer = (char*)&Ip4Address;
   }
   else
   if (Ip6AddressPtr)
   {
      dscptr->dsc$w_length = 16;
      dscptr->dsc$a_pointer = (char*)Ip6AddressPtr;
   }
   else
      exit (SS$_BUGCHECK);

#ifdef TCPIP_LOOKUP_HOST_NAME_RETRY
   RetryAttempts = TCPIP_LOOKUP_HOST_NAME_RETRY;
#else
   RetryAttempts = 10;
#endif

   while (RetryAttempts--)
   {
      status = sys$qiow (0, LookupChannel, IO$_ACPCONTROL,
                         &LookupIOsb, 0, 0,
                         &ControlSubFunctionDsc, &HostAddressDsc,
                         &HostNameLength, &HostNameDsc, 0, 0);
      if (Debug)
         fprintf (stdout, "sys$qiow() %%X%08.08X %%X%08.08X\n",
                  status, LookupIOsb.Status);

      if (VMSnok (status)) LookupIOsb.Status = status;
      if (VMSok (LookupIOsb.Status)) break;

      sys$schdwk (0, 0, &RetryDelta, 0);
      sys$hiber();
   }

   if (VMSok (LookupIOsb.Status)) 
   {
      HostName[HostNameLength] = '\0';
      if (Debug) fprintf (stdout, "|%s|\n", HostName);
      /* set the cache entry */
      if (Ip4Address)
         TcpIpHostCache (HostName, &Ip4Address, NULL);
      else
         TcpIpHostCache (HostName, NULL, Ip6AddressPtr);
      return (HostName);
   }
   else
   {
      /* a cache entry indicating it couldn't be resolved */
      if (Ip4Address)
         TcpIpHostCache ("?", &Ip4Address, NULL);
      else
         TcpIpHostCache ("?", NULL, Ip6AddressPtr);
      return (NULL);
   }
}

/*****************************************************************************/
/*
Get the IP address using synchronous name-to-address Ipv4/Ipv6 lookup.
The host name string must be terminated by white-space or a null.

(This code is *derived* from a similar functionality in [SRC.HTTPD]TCPIP.C)
*/

char* TcpIpLookupAddress
(
char *HostName,
int *Ip4AddressPtr,
int *Ip6AddressPtr
)
{
   /* this is one second delta */
   static unsigned long  RetryDelta [2] = { -10000000, -1 };

   static unsigned char ControlSubFunction [4] =
      { INETACP_FUNC$C_GETHOSTBYNAME, INETACP$C_TRANS, 0, 0 };
   static struct dsc$descriptor AddressDsc =
      { 0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0};
   static struct dsc$descriptor ControlSubFunctionDsc =
      { 4, DSC$K_DTYPE_T, DSC$K_CLASS_S, (char*)&ControlSubFunction };

   static unsigned short  LookupChannel;

   int  status,
        RetryAttempts;
   unsigned short  AddressLength,
                   HostNameLength;
   char  *cptr, *sptr, *zptr;
   int  Bytes16 [4];
   struct dsc$descriptor *dscptr;
   struct dsc$descriptor HostAddressDsc;
   struct dsc$descriptor HostNameDsc;
   IO_SB  LookupIOsb;

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

   if (Debug) fprintf (stdout, "TcpIpLookupAddress()\n");

   /* assign a channel to the internet template device */
   if (!LookupChannel)
   {
      status = sys$assign (&TcpIpDeviceDsc, &LookupChannel, 0, 0);
      if (VMSnok (status)) exit (status);
   }

   for (cptr = HostName; *cptr; cptr++);
   HostNameLength = cptr - HostName;

   /* first, IPv4 literal address */
   for (cptr = HostName; isdigit(*cptr) || *cptr == '.'; cptr++);
   if (*cptr)
   {
      /* if not then, IPv6 literal address */
      for (cptr = HostName;
           isxdigit(*cptr) || *cptr == ':' || *cptr == '-';
           cptr++);
   }
   if (!*cptr)
   {
      /*******************/
      /* literal address */
      /*******************/

      if (Ip4AddressPtr) *Ip4AddressPtr = 0;
      if (Ip6AddressPtr) memset (Ip6AddressPtr, 0, 16);
      status = TcpIpStringAddress (HostName, Ip4AddressPtr, Ip6AddressPtr);
      if (VMSok (status))
         return (HostName);
      else
         return (NULL);
   }

   cptr = TcpIpHostCache (HostName, Ip4AddressPtr, Ip6AddressPtr);
   if (cptr)
      if (*cptr == '?')
         return (NULL);
      else
         return (cptr);

   dscptr = (struct dsc$descriptor*)&HostNameDsc;
   dscptr->dsc$b_class = DSC$K_CLASS_S;
   dscptr->dsc$b_dtype = DSC$K_DTYPE_T;
   dscptr->dsc$w_length = HostNameLength;
   dscptr->dsc$a_pointer = HostName;

   dscptr = &HostAddressDsc;
   dscptr->dsc$b_class = DSC$K_CLASS_S;
   dscptr->dsc$b_dtype = DSC$K_DTYPE_T;
   /* give the full buffer and then check the returned length */
   dscptr->dsc$w_length = sizeof(Bytes16);
   dscptr->dsc$a_pointer = (char*)Bytes16;
   memset (&Bytes16, 0, sizeof(Bytes16));

#ifdef TCPIP_LOOKUP_HOST_NAME_RETRY
   RetryAttempts = TCPIP_LOOKUP_HOST_NAME_RETRY;
#else
   RetryAttempts = 10;
#endif

   while (RetryAttempts--)
   {
      status = sys$qiow (0, LookupChannel, IO$_ACPCONTROL,
                         &LookupIOsb, 0, 0,
                         &ControlSubFunctionDsc,
                         &HostNameDsc,
                         &AddressLength,
                         &HostAddressDsc, 0, 0);
      if (Debug)
         fprintf (stdout, "sys$qiow() %%X%08.08X %%X%08.08X\n",
                  status, LookupIOsb.Status);

      if (VMSnok (status)) LookupIOsb.Status = status;
      if (VMSok (LookupIOsb.Status)) break;

      sys$schdwk (0, 0, &RetryDelta, 0);
      sys$hiber();
   }

   if (VMSok(LookupIOsb.Status))
   {
      if (AddressLength != 4 && AddressLength != 16) exit (SS$_BUGCHECK);
      /* set an entry in the host cache, copy the address to the pointer */
      if (AddressLength == 4)
      {
         TcpIpHostCache (HostName, Bytes16, NULL);
         memcpy (Ip4AddressPtr, Bytes16, 4);
      }
      else
      {
         TcpIpHostCache (HostName, NULL, Bytes16);
         memcpy (Ip6AddressPtr, Bytes16, 16);
      }
   }
   else
   {
      /* a cache entry indicating it couldn't be resolved */
      if (AddressLength == 4)
         TcpIpHostCache ("?", Bytes16, NULL);
      else
         TcpIpHostCache ("?", NULL, Bytes16);
      return (NULL);
   }
}

/*****************************************************************************/
/*
Maintains and allows lookup of a host name/address Ipv4/Ipv6 cache.

To lookup name->address supply a non-NULL 'HostName' and pointer(s)
to 'Ip4AddressPtr' (an int) and 'Ip6AddressPtr' (an array of int).

To lookup address->name supply one of 'Ip4AddressPtr' or 'Ip6AddressPtr' and
a NULL 'HostName'.  A pointer to host name is returned, or NULL.

To set an entry, supply 'HostName' and one of 'Ip4AddressPtr' (an int) and
'Ip6AddressPtr' (an array of int).  Supplying a zero address cancels the entry.

The host name must be delimitted by white-space or a null.

(This code is *derived* from a similar functionality in [SRC.HTTPD]TCPIP.C)
*/

char* TcpIpHostCache
(
char *HostName,
int *Ip4AddressPtr,
int *Ip6AddressPtr
)
{
#define HOST_CACHE_CHUNK 64
#define HOST_CACHE_EXPIRE_SECONDS 600

   typedef struct _HOST_CACHE_ENTRY {
      char  HostName [127+1];
      int  Ip4Address;
      int  Ip6Address [4];
      int  ExpireSecond,
           HostNameLength;
   } HOST_CACHE_ENTRY;

   static int  Ip6AddressZero [4];

   static int  HostCacheCount,
               HostCacheMax;
   static HOST_CACHE_ENTRY  *HostCachePtr;

   int  cnt,
        HostNameLength;
   unsigned long  CurrentSecond;
   char  *cptr, *sptr, *zptr;
   HOST_CACHE_ENTRY  *hcptr;

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

   if (Debug)
      fprintf (stdout, "TcpIpHostCache() %d %d %d %d %d\n",
               HostCacheMax, HostCacheCount,
               HostName, Ip4AddressPtr, Ip6AddressPtr);

   if (!HostCachePtr)
   {
      HostCacheMax = HOST_CACHE_CHUNK;
      HostCachePtr = calloc (HostCacheMax, sizeof(HOST_CACHE_ENTRY));
   }

   time (&CurrentSecond);

   if (HostName && Ip4AddressPtr && Ip6AddressPtr)
   {
      /*******************/
      /* name to address */
      /*******************/

      for (cptr = HostName; *cptr; cptr++);
      HostNameLength = cptr - HostName;
      hcptr = HostCachePtr;
      for (cnt = 0; cnt < HostCacheCount; hcptr++, cnt++)
      {
         if (hcptr->HostNameLength != HostNameLength) continue;
         if (memcmp (hcptr->HostName, HostName, HostNameLength)) continue;
         if (hcptr->ExpireSecond > CurrentSecond)
         {
            /* not-expired hit! */
            if (Ip4AddressPtr) *Ip4AddressPtr = hcptr->Ip4Address;
            if (Ip6AddressPtr) memcpy (Ip6AddressPtr, hcptr->Ip6Address, 16);
            if (Debug) fprintf (stdout, "name->addr HIT\n");
            return (HostName);
         }
         /* expired */
         hcptr->HostNameLength = 0;
         if (Debug) fprintf (stdout, "name->addr EXPIRED\n");
         return (NULL);
      }
      if (Debug) fprintf (stdout, "name->addr MISS\n");
      return (NULL);
   }

   if (!HostName && (Ip4AddressPtr || Ip6AddressPtr))
   {
      /*******************/
      /* address to name */
      /*******************/

      hcptr = HostCachePtr;
      for (cnt = 0; cnt < HostCacheCount; hcptr++, cnt++)
      {
         if (!hcptr->HostNameLength) continue;
         if (Ip4AddressPtr && *Ip4AddressPtr != hcptr->Ip4Address) continue;
         if (Ip6AddressPtr && memcmp (Ip6AddressPtr, hcptr->Ip6Address, 16))
            continue;
         if (hcptr->ExpireSecond > CurrentSecond)
         {
            /* not-expired hit! */
            if (Debug) fprintf (stdout, "addr->name HIT\n");
            return (hcptr->HostName);
         }
         /* expired */
         hcptr->HostNameLength = 0;
         if (Debug) fprintf (stdout, "addr->name EXPIRED\n");
         return (NULL);
      }
      if (Debug) fprintf (stdout, "addr->name MISS\n");
      return (NULL);
   }

   if ((HostName && Ip4AddressPtr && !Ip6AddressPtr) ||
       (HostName && !Ip4AddressPtr && Ip6AddressPtr))
   {
      /*******************/
      /* set cache entry */
      /*******************/

      for (cptr = HostName; *cptr; cptr++);
      HostNameLength = cptr - HostName;
      /* check if there an equivalent entry already in the cache */
      hcptr = HostCachePtr;
      for (cnt = 0; cnt < HostCacheCount; hcptr++, cnt++)
      {
         if (hcptr->HostNameLength != HostNameLength) continue;
         if (memcmp (hcptr->HostName, HostName, HostNameLength)) continue;
         /* yes, already in the cache */
         if (Ip4AddressPtr && !*Ip4AddressPtr ||
             Ip6AddressPtr && memcmp (Ip6AddressPtr, Ip6AddressZero, 16))
         {
            /* cancel the entry */
            hcptr->HostNameLength = 0;
            if (Debug) fprintf (stdout, "RESET\n");
            return (NULL);
         }
         if (Debug) fprintf (stdout, "set ALREADY\n");
         return (NULL);
      }
      /* check for an entry available for reuse */
      hcptr = HostCachePtr;
      for (cnt = 0; cnt < HostCacheCount; hcptr++, cnt++)
      {
         if (!hcptr->HostNameLength) break;
         if (hcptr->ExpireSecond < CurrentSecond)
         {
            /* this one needs refreshing */
            hcptr->HostNameLength = 0;
            break;
         }
      }
      if (cnt >= HostCacheCount)
      {
         /* nothing available for reuse */
         if (HostCacheCount < HostCacheMax)
         {
            /* use the next entry */
            HostCacheCount++;
            if (Debug) fprintf (stdout, "set NEXT\n");
         }
         else
         {
            /* out of next entries, expand the cache */
            HostCacheMax += HOST_CACHE_CHUNK;
            HostCachePtr = calloc (HostCacheMax, sizeof(HOST_CACHE_ENTRY));
            hcptr = &HostCachePtr[HostCacheCount++];
            if (Debug) fprintf (stdout, "set EXPAND\n");
         }
      }
      /* populate the entry */
      zptr = (sptr = hcptr->HostName) + sizeof(hcptr->HostName)-1;
      for (cptr = HostName; *cptr && sptr < zptr; *sptr++ = *cptr++);
      *sptr = '\0';
      hcptr->HostNameLength = sptr - hcptr->HostName;
      if (Ip4AddressPtr)
         hcptr->Ip4Address = *Ip4AddressPtr;
      else
         hcptr->Ip4Address = 0;
      if (Ip6AddressPtr)
         memcpy (hcptr->Ip6Address, Ip6AddressPtr, 16);
      else
         memset (hcptr->Ip6Address, 0, 16);
      hcptr->ExpireSecond = CurrentSecond + HOST_CACHE_EXPIRE_SECONDS;
      if (Debug) fprintf (stdout, "SET\n");
      return (HostName);
   }

   exit (SS$_BUGCHECK);
}

/*****************************************************************************/
/*
Convert an IPv4 dotted-decimal or IPv6 hexadecimal format (normal or
compressed) string into an appropriate address.  The address must be delimited
by white-space or a null.  The white-space criterion is important in any
modification from TCPIP.C!

(This code is *derived* from a similar functionality in [SRC.HTTPD]TCPIP.C)
*/

int TcpIpStringAddress
(
char *String,
int *Ip4AddressPtr,
int *Ip6AddressPtr
)
{
   int  cnt, idx,
        Ip4Address;
   int  Ip4Octets [4];
   unsigned short  Ip6Address [8];
   unsigned short  *ip6ptr;
   unsigned int  uint;
   unsigned int  Ip6Octets [10];  /* well sort-of */
   char  *cptr, *sptr, *zptr;

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

   if (Debug) fprintf (stdout, "TcpIpStringAddress() |%s|\n", String);

   if (Ip4AddressPtr) *Ip4AddressPtr = 0;
   if (Ip6AddressPtr) memset (Ip6AddressPtr, 0, 16);

   /* will reach end-of-string if it's an IPv4 address */
   for (cptr = String; isdigit(*cptr) || *cptr == '.'; cptr++);

   if (!*cptr)
   {
      /********/
      /* IPv4 */
      /********/

      memset (Ip4Octets, 0, sizeof(Ip4Octets));
      cnt = sscanf (String, "%d.%d.%d.%d",
                    &Ip4Octets[0], &Ip4Octets[1],
                    &Ip4Octets[2], &Ip4Octets[3]);
      if (cnt != 4) return (SS$_ENDOFFILE);
      Ip4Address = 0;
      for (idx = 0; idx <= 3; idx++)
      {
         if (Ip4Octets[idx] < 0 || Ip4Octets[idx] > 255)
            return (SS$_ENDOFFILE);
         Ip4Address |= Ip4Octets[idx] << idx * 8;
      }
      if (Debug) fprintf (stdout, "IPv4 %08.08X\n", Ip4Address);
      *Ip4AddressPtr = Ip4Address;
      return (SS$_NORMAL);
   }

   /********/
   /* IPv6 */
   /********/

   memset (Ip4Octets, 0, sizeof(Ip4Octets));
   memset (Ip6Octets, 0, sizeof(Ip6Octets));

   /*
      _normal_                       _compressed_
      1070:0:0:0:0:800:200C:417B     1070::800:200C:417B
      0:0:0:0:0:0:13.1.68.3          ::13.1.68.3
      0:0:0:0:0:FFFF:129.144.52.38   ::FFFF:129.144.52.38
      _hyphen-variant_
      1070-0-0-0-0-800-200C-417B     1070--800-200C-417B
      0-0-0-0-0-0-13.1.68.3          --13.1.68.3
      0-0-0-0-0-FFFF-129.144.52.38   --FFFF-129.144.52.38
   */

   idx = 0;
   zptr = "";
   cptr = String;
   while (*cptr)
   {
      if (idx > 7) return (SS$_ENDOFFILE);
      /* look ahead at the next delimiter */
      for (sptr = cptr; isxdigit(*sptr); sptr++);
      if (*sptr == ':' || (!*sptr && *zptr == ':') ||
          *sptr == '-' || (!*sptr && *zptr == '-'))
      {
         /* IPv6 (or variant) syntax */
         uint = (unsigned long)strtol (cptr, NULL, 16);
         if (uint > 0xffff) return (SS$_ENDOFFILE);
         /* network byte-order */
         Ip6Octets[idx] = (uint >> 8) | (uint << 8);
         idx++;
         if (*(unsigned short*)sptr == '::' ||
             *(unsigned short*)sptr == '--')
         {
            /* indicate the ellipsis zeroes */
            Ip6Octets[idx] = 0xffffffff;
            idx++;
            sptr++;
         }
      }
      else
      if (*sptr == '.' || (!*sptr && *zptr == '.'))
      {
         /* dropped into dotted-decimal, IPv4 compatible syntax */
         cnt = sscanf (cptr, "%d.%d.%d.%d",
                       &Ip4Octets[3], &Ip4Octets[2],
                       &Ip4Octets[1], &Ip4Octets[0]);
         if (cnt != 4) return (SS$_ENDOFFILE);
         while (isdigit(*cptr) || *cptr == '.') cptr++;
         if (*cptr) return (SS$_ENDOFFILE);
         if (Ip4Octets[0] < 0 || Ip4Octets[0] > 255) return (SS$_ENDOFFILE);
         if (Ip4Octets[1] < 0 || Ip4Octets[1] > 255) return (SS$_ENDOFFILE);
         if (Ip4Octets[2] < 0 || Ip4Octets[2] > 255) return (SS$_ENDOFFILE);
         if (Ip4Octets[3] < 0 || Ip4Octets[3] > 255) return (SS$_ENDOFFILE);
         Ip6Octets[idx++] = (Ip4Octets[3] << 8) | Ip4Octets[2];
         Ip6Octets[idx++] = (Ip4Octets[1] << 8) | Ip4Octets[0];
         break;
      }
      else
         return (SS$_ENDOFFILE);
      cptr = zptr = sptr;
      if (*cptr) cptr++;
   }

   memset (ip6ptr = Ip6Address, 0, sizeof(Ip6Address));
   cnt = 9 - idx;
   for (idx = 0; idx < 8; idx++)
   {
      if (Ip6Octets[idx] == 0xffffffff)
      {
         if (cnt < 0) return (SS$_ENDOFFILE);
         while (cnt--) ip6ptr++;
      }
      else
         *ip6ptr++ = Ip6Octets[idx];
   }

   if (Debug)
      fprintf (stdout,
"IPv6 %04.04X%04.04X%04.04X%04.04X%04.04X%04.04X%04.04X%04.04X\n",
               Ip6Address[7], Ip6Address[6], Ip6Address[5], Ip6Address[4], 
               Ip6Address[3], Ip6Address[2], Ip6Address[1], Ip6Address[0]);
   memcpy (Ip6AddressPtr, Ip6Address, 16);
   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
See [SRC.HTTPD]CONTROL.C for other information.  Uses the VMS Distributed Lock
Manager to keep track of how many servers are currently executing on the
node/cluster.  NL locks indicate interest (used by this utility), CR locks
indicate a server waiting for a group directive.  This function enqueues a
single NL lock, then periodically get all the locks associated with that
resource and counts up the number of CR locks - giving the number of servers!
If the number of servers varies then (by default) sound the bell!!
*/

GetInstanceInformation ()

{
   static int  InstanceCount,
               PrevClusterCount = -1,
               PrevNodeCount = -1;
   static char  ClusterLockName [32],
                NodeLockName [32],
                PrevInstanceString [32];
   static $DESCRIPTOR (ClusterLockNameDsc, ClusterLockName);
   static $DESCRIPTOR (NodeLockNameDsc, NodeLockName);
   static LKIDEF  LkiLocks [32];
   static struct lksb  ClusterLockLksb,
                       NodeLockLksb;

   static struct
   {
      unsigned short  TotalLength,  /* bits 0..15 */
                      LockLength;  /* bits 16..30 */
   } ReturnLength;

   static struct
   {
      unsigned short  buf_len;
      unsigned short  item;
      unsigned char  *buf_addr;
      void  *ret_len;
   }
   LkiItems [] =
   {
      { sizeof(LkiLocks), LKI$_LOCKS, &LkiLocks, &ReturnLength },
      {0,0,0,0}
   };

   int  cnt, status,
        ClusterCount,
        LockCount,
        LockNameMagic,
        NodeCount,
        NonNlLockCount;
   char  *cptr, *sptr, *zptr;
   IO_SB  IOsb;
   LKIDEF  *lkiptr;

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

   if (Debug) fprintf (stdout, "GetInstanceInformation()\n");

   status = sys$setprv (1, &SysLckMask, 0, 0);
   if (status == SS$_NOTALLPRIV) exit (SS$_NOPRIV);
   
   if (!ClusterLockName[0])
   {
      if (InstanceGroupNumber > INSTANCE_GROUP_NUMBER_MAX)
      {
         fprintf (stdout, "%%%s-E-INSTANCE, group number range 1 to %d\n",
                  Utility, INSTANCE_GROUP_NUMBER_MAX);
         exit (STS$K_ERROR | STS$M_INHIB_MSG);
      }

      /* a byte comprising two 4 bit fields, version and server group number */
      LockNameMagic = ((HTTPD_LOCK_VERSION & 0xf) << 4) |
                      (InstanceGroupNumber & 0xf);

      /* build the (binary) resource name for the cluster lock */
      zptr = (sptr = ClusterLockName) + sizeof(ClusterLockName)-1;
      for (cptr = HTTPD_NAME; *cptr && sptr < zptr; *sptr++ = *cptr++);
      if (sptr < zptr) *sptr++ = (char)LockNameMagic;
      if (sptr < zptr) *sptr++ = (char)0x01;
      ClusterLockNameDsc.dsc$w_length = sptr - ClusterLockName;

      /* enqueue a just-interested NL lock */
      status = sys$enqw (0, LCK$K_NLMODE, &ClusterLockLksb,
                         LCK$M_EXPEDITE | LCK$M_SYSTEM,
                         &ClusterLockNameDsc, 0, 0, 0, 0, 0, 2, 0);
      if (VMSok (status)) status = ClusterLockLksb.lksb$w_status;
      if (VMSnok (status)) exit (status);

      /* build the (binary) resource name for the node lock */
      zptr = (sptr = NodeLockName) + sizeof(NodeLockName)-1;
      for (cptr = HTTPD_NAME; *cptr && sptr < zptr; *sptr++ = *cptr++);
      if (sptr < zptr) *sptr++ = (char)LockNameMagic;
      for (cptr = SyiNodeName; *cptr && sptr < zptr; *sptr++ = *cptr++);
      if (sptr < zptr) *sptr++ = (char)0x05;
      NodeLockNameDsc.dsc$w_length = sptr - NodeLockName;

      /* enqueue a just-interested NL lock */
      status = sys$enqw (0, LCK$K_NLMODE, &NodeLockLksb,
                         LCK$M_EXPEDITE | LCK$M_SYSTEM,
                         &NodeLockNameDsc, 0, 0, 0, 0, 0, 2, 0);
      if (VMSok (status)) status = NodeLockLksb.lksb$w_status;
      if (VMSnok (status)) exit (status);
   }

   /* get and count the cluster instance locks */

   status = sys$getlkiw (0, &ClusterLockLksb.lksb$l_lkid, &LkiItems, &IOsb,
                         0, 0, 0);
   if (VMSok (status)) status = ClusterLockLksb.lksb$w_status;
   if (VMSnok (status)) exit (status);

   if (ReturnLength.LockLength)
   {
      /* if insufficient buffer space */
      if (ReturnLength.LockLength & 0x8000) exit (SS$_BADPARAM);
      LockCount = ReturnLength.TotalLength / ReturnLength.LockLength;
   }
   else
      LockCount = 0;
   if (Debug) fprintf (stdout, "%d\n", LockCount);

   ClusterCount = 0;
   lkiptr = &LkiLocks;
   for (cnt = LockCount; cnt; cnt--)
   {
      if (Debug) fprintf (stdout, "lki$b_grmode: %d\n", lkiptr->lki$b_grmode);
      if (lkiptr->lki$b_grmode != LCK$K_NLMODE) ClusterCount++;
      lkiptr++;
   }

   /* get and count the node instance locks */

   status = sys$getlkiw (0, &NodeLockLksb.lksb$l_lkid, &LkiItems, &IOsb,
                         0, 0, 0);
   if (VMSok (status)) status = NodeLockLksb.lksb$w_status;
   if (VMSnok (status)) exit (status);

   if (ReturnLength.LockLength)
   {
      /* if insufficient buffer space */
      if (ReturnLength.LockLength & 0x8000) exit (SS$_BADPARAM);
      LockCount = ReturnLength.TotalLength / ReturnLength.LockLength;
   }
   else
      LockCount = 0;
   if (Debug) fprintf (stdout, "%d\n", LockCount);

   NodeCount = 0;
   lkiptr = &LkiLocks;
   for (cnt = LockCount; cnt; cnt--)
   {
      if (Debug) fprintf (stdout, "lki$b_grmode: %d\n", lkiptr->lki$b_grmode);
      if (lkiptr->lki$b_grmode != LCK$K_NLMODE) NodeCount++;
      lkiptr++;
   }

   if (++InstanceCount > NodeCount) InstanceCount = 1;
   NextInstancePid = NonNlLockCount = 0;
   lkiptr = &LkiLocks;
   for (cnt = LockCount; cnt; cnt--)
   {
      if (Debug) fprintf (stdout, "lki$b_grmode: %d\n", lkiptr->lki$b_grmode);
      if (lkiptr->lki$b_grmode != LCK$K_NLMODE)
      {
         NonNlLockCount++;
         if (Debug) fprintf (stdout, "%d %d\n", InstanceCount, NonNlLockCount);
         if (InstanceCount == NonNlLockCount)
         {
            if (Debug) fprintf (stdout, "%08x\n", lkiptr->lki$l_pid);
            NextInstancePid = lkiptr->lki$l_pid;
            break;
         }
      }
      lkiptr++;
   }

   sys$setprv (0, &SysLckMask, 0, 0);

   /* build the string */

   if (PrevClusterCount < 0)
   {
      /* initialize */
      PrevClusterCount = ClusterCount;
      PrevNodeCount = NodeCount;
   }

   if (NodeCount != PrevNodeCount || ClusterCount != PrevClusterCount)
   {
      sptr = PrevInstanceString;
      *sptr++ = '(';
      for (cptr = InstanceString + sizeof(ANSIbold)-1;
           isdigit(*cptr) || *cptr == '/';
           *sptr++ = *cptr++);
      *sptr++ = ')';
      *sptr = '\0';
   }

   sptr = InstanceString;
   sprintf (sptr, "%s%d", ANSIbold, NodeCount);
   while (*sptr) sptr++;
   if (SyiClusterNodes > 1)
   {
      sprintf (sptr, "/%d", ClusterCount);
      while (*sptr) sptr++;
   }
   strcpy (sptr, ANSInormal);
   while (*sptr) sptr++;
   if (PrevInstanceString[0])
   {
      for (cptr = PrevInstanceString; *cptr; *sptr++ = *cptr++);
      *sptr = '\0';
   }

   /* determine the length before adding the non-displaying bells!! */
   InstanceStringLength = sptr - InstanceString;
   InstanceStringLength -= sizeof(ANSIbold)-1 + sizeof(ANSInormal)-1;

   if (NodeCount != PrevNodeCount || ClusterCount != PrevClusterCount)
   {
      PrevClusterCount = ClusterCount;
      PrevNodeCount = NodeCount;
      for (cptr = "\x07\x07\x07"; *cptr; *sptr++ = *cptr++);
      *sptr = '\0';
   }

   if (Debug) fprintf (stdout, "|%s|\n", InstanceString);
}

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

int OnControlY (void *FunctionAddress)

{
   static BOOL  Disabled = false;
   static unsigned long  Mask = LIB$M_CLI_CTRLY,
                         OldMask;
   static unsigned short  TTChannel = 0;

   int  status;
   $DESCRIPTOR (TTDsc, "TT:");

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

   if (FunctionAddress)
   {
      if (!TTChannel)
         if (VMSnok (status = sys$assign (&TTDsc, &TTChannel, 0, 0, 0)))
            return (status);

      if (VMSnok (status =
          sys$qiow (0, TTChannel, IO$_SETMODE | IO$M_CTRLYAST, 0, 0, 0,
                    FunctionAddress, 0, PSL$C_USER, 0, 0, 0)))
         return (status);

      if (VMSnok (status =
          sys$qiow (0, TTChannel, IO$_SETMODE | IO$M_CTRLCAST, 0, 0, 0,
                    FunctionAddress, 0, PSL$C_USER, 0, 0, 0)))
         return (status);

      if (!Disabled)
      {
         Disabled = true;
         return (lib$disable_ctrl (&Mask, &OldMask));
      }
      else
         return (status);
   }
   else
   {
      sys$cancel (TTChannel);
      return (lib$enable_ctrl (&OldMask, 0));
   }
}

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

ControlY_AST ()

{
   ControlY = true;
   sys$wake (0, 0);
}

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

char* TimeString ()

{
   static int  LibDayOfWeek = LIB$K_DAY_OF_WEEK;
   static char  *WeekDays [] =
   {"","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"};
   static char  TimeString [35];
   static $DESCRIPTOR (DayDateTimeDsc, "!AZ, !%D");
   static $DESCRIPTOR (TimeStringDsc, TimeString);

   unsigned long  BinTime [2];
   int  status,
        DayOfWeek;
   unsigned short  Length;

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

   sys$gettim (&BinTime);
   lib$cvt_from_internal_time (&LibDayOfWeek, &DayOfWeek, &BinTime);
   sys$fao (&DayDateTimeDsc, &Length, &TimeStringDsc,
            WeekDays[DayOfWeek], &BinTime);
   TimeString[Length-3] = '\0';
   return (TimeString);
}

/*****************************************************************************/
/*
Convert the 32/64 bit integer (depending on architecture) pointed to into an
ASCII number string containing commas.  The destination string should contain
capacity of a minimum 16 characters for 32 bits, or 32 characters for 64 bits.
*/

CommaNumber
(
int Bits,
unsigned long Value,
char *String
)
{
   static $DESCRIPTOR (Value32FaoDsc, "!UL");
   static $DESCRIPTOR (Value64FaoDsc, "!@UJ");

   int  cnt, status;
   unsigned short  Length;
   double  dValue;
   char  *cptr, *sptr;
   char  Scratch [32];
   $DESCRIPTOR (ScratchDsc, Scratch);

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

   if (Debug) fprintf (stdout, "CommaNumber()\n");

   if (Bits > 32)
#ifdef __VAX 
   {
      /* a horrible kludge but apparently ok all the way up to 53 bits */
      dValue = (double)((unsigned long*)Value)[0];
      dValue += (double)((unsigned long*)Value)[1] * 4294967296.0;
      Length = sprintf (Scratch, "%.0f", dValue);
      status = SS$_NORMAL;
   }
#else /* Alpha or IA64 */
      status = sys$fao (&Value64FaoDsc, &Length, &ScratchDsc, Value);
#endif /* #ifdef __VAX  */
   else
      status = sys$fao (&Value32FaoDsc, &Length, &ScratchDsc, Value);
   if (VMSnok (status))
   {
      strcpy (String, "*ERROR*");
      return;
   }
   Scratch[Length] = '\0';
   if (((Length-1) / 3) < 1)
   {
      strcpy (String, Scratch);
      return;
   }
   else
   if (!(cnt = Length % 3))
      cnt = 3;
   
   cptr = Scratch;
   sptr = String;
   while (*cptr)
   {
      if (!cnt--)
      {
         *sptr++ = ',';
         cnt = 2;
      }
      *sptr++ = *cptr++;
   }
   *sptr = '\0';
}

/*****************************************************************************/
/*
Get "command-line" parameters, whether from the command-line or from a
configuration logical containing the equivalent.
*/

GetParameters ()

{
   static char  CommandLine [256];
   static unsigned long  Flags = 0;

   int  status;
   unsigned short  Length;
   char  ch;
   char  *aptr, *cptr, *clptr, *sptr, *zptr;
   $DESCRIPTOR (CommandLineDsc, CommandLine);

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

   if (Debug) fprintf (stdout, "GetParameters()\n");

   if (!(clptr = getenv ("HTTPDMON$PARAM")))
   {
      /* get the entire command line following the verb */
      if (VMSnok (status =
          lib$get_foreign (&CommandLineDsc, 0, &Length, &Flags)))
         exit (status);
      (clptr = CommandLine)[Length] = '\0';
   }

   aptr = NULL;
   ch = *clptr;
   for (;;)
   {
      if (aptr && *aptr == '/') *aptr = '\0';
      if (!ch) break;

      *clptr = ch;
      if (Debug) fprintf (stdout, "clptr |%s|\n", clptr);
      while (*clptr && isspace(*clptr)) *clptr++ = '\0';
      aptr = clptr;
      if (*clptr == '/') clptr++;
      while (*clptr && !isspace (*clptr) && *clptr != '/')
      {
         if (*clptr != '\"')
         {
            clptr++;
            continue;
         }
         cptr = clptr;
         clptr++;
         while (*clptr)
         {
            if (*clptr == '\"')
               if (*(clptr+1) == '\"')
                  clptr++;
               else
                  break;
            *cptr++ = *clptr++;
         }
         *cptr = '\0';
         if (*clptr) clptr++;
      }
      ch = *clptr;
      if (*clptr) *clptr = '\0';
      if (Debug) fprintf (stdout, "aptr |%s|\n", aptr);
      if (!*aptr) continue;

      if (strsame (aptr, "/ALERT=", 4))
      {
         sptr = aptr;
         while (*aptr && *aptr != '=') aptr++;
         if (*aptr) aptr++;
         while (*aptr)
         {
            if (!*aptr || strsame (aptr, "ALL", 3))
            {
               DoAlertAll = true;
               DoAlertHost = false;
            }
            else
            if (strsame (aptr, "HOST", 4))
            {
               DoAlertAll = false;
               DoAlertHost = true;
            }
            else
            if (strsame (aptr, "PATH", 4))
            {
               DoAlertAll = false;
               DoAlertPath = true;
               while (*aptr && *aptr != '=') aptr++;
               if (*aptr)
               {
                  aptr++;
                  PathAlertBellRepetition = atoi(aptr);
               }
            }
            else
            if (strsame (aptr, "NOSERVERS", 9))
               DoAlertServers = false;
            else
            if (strsame (aptr, "SERVERS", 7))
               DoAlertServers = true;
            else
            {
               fprintf (stdout, "%%%s-E-INVPARM, invalid parameter\n \\%s\\\n",
                        Utility, sptr);
               exit (STS$K_ERROR | STS$M_INHIB_MSG);
            }
            while (*aptr && *aptr != ',') aptr++;
            if (*aptr) aptr++;
         }
         continue;
      }
      if (strsame (aptr, "/ALL=", 4))
      {
         /* force server-group name to begin with a set string */
         sptr = aptr;
         while (*aptr && *aptr != '=') aptr++;
         if (*aptr) aptr++;
         if (!isdigit(*aptr))
         {
            fprintf (stdout, "%%%s-E-INVPARM, invalid parameter\n \\%s\\\n",
                     Utility, sptr);
            exit (STS$K_ERROR | STS$M_INHIB_MSG);
         }
         InstanceGroupNumber = atoi(aptr);
         continue;
      }
      if (strsame (aptr, "/DBUG", -1))
      {
         Debug = true;
         continue;
      }
      if (strsame (aptr, "/DEMO", -1))
      {
         DemoMode = true;
         continue;
      }
      if (strsame (aptr, "/GENERAL", 4))
      {
         DoGeneralInfo = true;
         DoNoGeneralInfo = false;
         continue;
      }
      if (strsame (aptr, "/NOGENERAL", 6))
      {
         DoGeneralInfo = false;
         DoNoGeneralInfo = true;
         continue;
      }
      if (strsame (aptr, "/HELP", 4))
      {
         DoShowHelp = true;
         continue;
      }
      if (strsame (aptr, "/IDENTIFICATION=", 3))
      {
         sptr = aptr;
         while (*aptr && *aptr != '=') aptr++;
         if (*aptr) aptr++;
         if (!isxdigit(*aptr))
         {
            fprintf (stdout, "%%%s-E-INVPARM, invalid parameter\n \\%s\\\n",
                     Utility, sptr);
            exit (STS$K_ERROR | STS$M_INHIB_MSG);
         }
         sscanf (aptr, "%x", &CliInstancePid);
         continue;
      }
      if (strsame (aptr, "/INTERVAL=", 4) ||
          strsame (aptr, "/REFRESH=", 4))
      {
         sptr = aptr;
         while (*aptr && *aptr != '=') aptr++;
         if (*aptr) aptr++;
         if (!isdigit(*aptr))
         {
            fprintf (stdout, "%%%s-E-INVPARM, invalid parameter\n \\%s\\\n",
                     Utility, sptr);
            exit (STS$K_ERROR | STS$M_INHIB_MSG);
         }
         IntervalSeconds = atoi(aptr);
         continue;
      }
      if (strsame (aptr, "/LOOKUP", 4))
      {
         DoLookupHost = true;
         continue;
      }
      if (strsame (aptr, "/NOLOOKUP", 6))
      {
         DoLookupHost = false;
         continue;
      }
      if (strsame (aptr, "/PORT=", 4))
      {
         sptr = aptr;
         while (*aptr && *aptr != '=') aptr++;
         if (*aptr) aptr++;
         if (!isdigit(*aptr))
         {
            fprintf (stdout, "%%%s-E-INVPARM, invalid parameter\n \\%s\\\n",
                     Utility, sptr);
            exit (STS$K_ERROR | STS$M_INHIB_MSG);
         }
         ServerPort = atoi(aptr);
         continue;
      }
      if (strsame (aptr, "/PROCESS", 5))
      {
         DoProcessInfo = true;
         DoNoProcessInfo = false;
         continue;
      }
      if (strsame (aptr, "/NOPROCESS", 7))
      {
         DoProcessInfo = false;
         DoNoProcessInfo = true;
         continue;
      }
      if (strsame (aptr, "/PROXY", 5))
      {
         DoProxyInfo = true;
         DoNoProxyInfo = false;
         continue;
      }
      if (strsame (aptr, "/NOPROXY", 7))
      {
         DoProxyInfo = false;
         DoNoProxyInfo = true;
         continue;
      }
      if (strsame (aptr, "/REQUEST", 4))
      {
         DoRequestInfo = true;
         DoNoRequestInfo = false;
         continue;
      }
      if (strsame (aptr, "/NOREQUEST", 6))
      {
         DoRequestInfo = false;
         DoNoRequestInfo = true;
         continue;
      }

      if (*aptr != '/')
      {
         fprintf (stdout, "%%%s-E-MAXPARM, too many parameters\n \\%s\\\n",
                  Utility, aptr);
         exit (STS$K_ERROR | STS$M_INHIB_MSG);
      }
      else
      {
         fprintf (stdout, "%%%s-E-IVQUAL, unrecognized qualifier\n \\%s\\\n",
                  Utility, aptr+1);
         exit (STS$K_ERROR | STS$M_INHIB_MSG);
      }
   }
}

/****************************************************************************/
/*
*/
 
int ShowHelp ()
 
{
   fprintf (stdout,
"%%%s-I-HELP, usage for the WASD HTTPd Monitor (%s)\n\
\n\
Continuously displays the status of an HTTPd process (must be executed on the\n\
system that has the process running on it, defaulting to port 80).  Provides\n\
process information, server counters, latest request information, with proxy\n\
processing statistics optionally available.  By default attempts to resolve\n\
request dotted-decimal host address to host name.  The alert qualifier\n\
activates the terminal bell for an increase in server connect count (=ALL)\n\
or change in requesting host (=HOST), or mapped alert path hit (=PATH).\n\
\n\
$ HTTPDMON [qualifiers...]\n\
\n\
/ALERT[=ALL|HOST|PATH[=<integer>]] /DEMO /[NO]GENERAL /HELP\n\
/INTERVAL=<integer> /[NO]LOOKUP /PRCNAM=prcnam /PORT=<integer> /[NO]PROCESS\n\
/[NO]PROXY /REFRESH=integer /[NO]REQUEST\n\
\n\
Usage examples:\n\
\n\
$ HTTPDMON\n\
$ HTTPDMON /INTERVAL=15 /PROXY\n\
$ HTTPDMON /PORT=8080 /ALERT\n\
$ HTTPDMON /NOLOOKUP\n\
\n",
   Utility, SOFTWAREID);
 
   return (SS$_NORMAL);
}
 
/****************************************************************************/
/*
Does a case-insensitive, character-by-character string compare and returns 
true if two strings are the same, or false if not.  If a maximum number of 
characters are specified only those will be compared, if the entire strings 
should be compared then specify the number of characters as 0.
*/ 
 
BOOL strsame
(
char *sptr1,
char *sptr2,

int  count
)
{
   while (*sptr1 && *sptr2)
   {
      if (toupper (*sptr1++) != toupper (*sptr2++)) return (false);
      if (count)
         if (!--count) return (true);
   }
   if (*sptr1 || *sptr2)
      return (false);
   else
      return (true);
}
 
/*****************************************************************************/

