/*****************************************************************************/
/*
                                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)
/ENV=<integer>          refer to this WASD environment (2..15)
/[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
/PLUCK                  place selected statistics into global DCL symbols
/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
/[NO]STATUS             display instance 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


COPYRIGHT
---------
Copyright (C) 1996-2020 Mark G.Daniel
This program, comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it under the
conditions of the GNU GENERAL PUBLIC LICENSE, version 3, or later version.
http://www.gnu.org/licenses/gpl.txt


VERSION HISTORY (update SOFTWAREVN as well!)
---------------
07-MAR-2020  MGD  v2.8.0, re-map global section when startup detected
09-NOV-2018  MGD  v2.7.0, refactor IP address handling
14-NOV-2017  MGD  v2.6.0, instance status data
                          if |WASD_ENV| defined use that in absence /INSTANCE
12-SEP-2015  MGD  v2.5.0, WASD v11 and HTTP/2
30-NOV-2014  MGD  v2.4.2, accomodate per-instance "current" data
18-JUN-2010  MGD  v2.4.1, add proctor, proxy tunnel and WebSocket
                          remove accept and reject to make way for WebSocket
31-OCT-2009  MGD  v2.4.0, just for WASD v10 
30-AUG-2009  MGD  v2.3.9, bytes per second
09-JUL-2009  MGD  v2.3.8, /ENV=
15-OCT-2008  MGD  v2.3.7, add WebDAV: module item
15-OCT-2008  MGD  v2.3.6, pluck server statistics
21-FEB-2008  MGD  v2.3.5, add remote user and realm 
                          add Request.BytesTxGzipPercent
                          PercentOf() calc percentages of unsigned longs
                          QuadPercentOf() calc percentages of unsigned quads
19-SEP-2006  MGD  v2.3.4, Accounting.ResponseStatusCodeCount changes name to
                            Accounting.ResponseStatusCodeGroup
                          PRC: now shows remaining prclm (as do other quotas)
15-JUL-2006  MGD  v2.3.3, abbreviate some stats for humungous sites (UMA :-)
                          add status for SUSPEND and instance PASSIVE modes
24-AUG-2005  MGD  v2.3.2, only display printable request URI characters
20-JUL-2005  MGD  v2.3.1, detect and report global section mismatch
28-AUG-2004  MGD  v2.3.0, modifications during HTTP/1.1 support
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.8.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
#ifdef __x86_64
#  define SOFTWAREID SOFTWARENM " " SOFTWAREVN " X86"
#endif

/* standard C header files */
#include <ctype.h>
#include <stdlib.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 <lnmdef.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 */
#ifdef THIS_WASD_H
/* when field testing and httpdmon.c is included in the server directory */
#include "./wasd.h"
#else
#include "../httpd/wasd.h"
#endif

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

#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'

typedef struct STRUCT_IOSBLK
{
   ushort  iosb$w_status;
   ushort  iosb$w_bcnt;
   ulong  iosb$l_reserved;
}
IOSBLK;

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

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

int  CliInstancePid,
     CurrentProcessCount,
     InstanceCount,
     InstanceEnvNumber,
     InstanceStringLength,
     IntervalSeconds = DEFAULT_INTERVAL_SECONDS,
     NextInstancePid,
     PageLength,
     PathAlertBellRepetition = PATH_ALERT_BELL_COUNT,
     PrevConnectCount,
     ServerPort = 80;

unsigned short  SyiClusterNodes,
                SyiNodeLength;

unsigned long  HttpdPid;

unsigned long  NowTime64 [2];

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

char  *ScrPtr;

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

int  HttpdGblSecLength;
HTTPD_GBLSEC  *HttpdGblSecPtr;

ACCOUNTING_STRUCT  *AccountingPtr;

/* 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* BytesPerString (unsigned int);
int CheckHttpdPid (void);
void AddInstanceStatus();
char* LookupHostName (ulong);
void MapGlobalSection (void);
int PercentOf (unsigned long, unsigned long);
int QuadPercentOf (unsigned long*, unsigned long*);
int SetSymbol (char*, char*, int);
int ThisLongAgo (unsigned long*, char*);
char* TimeString (); 
char* v10orPrev10 (char*, int);
 
/*****************************************************************************/
/*
*/

int main ()

{
   static $DESCRIPTOR (ttDsc, "TT:");

   int  status,
        MonitorStatus;
   char  *cptr;
   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 }
   },
   TtPageItemList [] = 
   {
      { sizeof(PageLength), DVI$_TT_PAGE, &PageLength, 0 },
      { 0, 0, 0, 0 }
   };
   IOSBLK  IOsb;

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

   GetParameters ();

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

   if (DemoMode) InstanceEnvNumber = 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 (DoPluck) exit (PluckStats ());

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

   status = sys$getsyi (0, 0, 0, &SyiItems, &IOsb, 0, 0);
   if (VMSok (status)) status = IOsb.iosb$w_status;
   if (VMSnok (status)) exit (status);

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

   status = sys$getdviw (0, 0, &ttDsc, &TtPageItemList, &IOsb, 0, 0, 0);
   if (VMSok (status)) status = IOsb.iosb$w_status;
   if (VMSnok (status)) exit (status);

   /* if WASD_ENV defined then use this value in the absence of a CLI value */
   if (!InstanceEnvNumber)
   {
      if (cptr = getenv("WASD_ENV"))
         InstanceEnvNumber = atoi (cptr);
      else
      if (DemoMode)
         InstanceEnvNumber = DEMO_INSTANCE_GROUP_NUMBER;
      else
         InstanceEnvNumber = INSTANCE_ENV_NUMBER_DEFAULT;
   }
   if (Debug) fprintf (stdout, "env=%d\n", InstanceEnvNumber);

   MapGlobalSection ();

   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, "WASD:%d", ServerPort);
   PortLength = sprintf (Port, "Port: %d", ServerPort);

   SoftwareIDLength = strlen(SOFTWAREID);

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

      sys$gettim (&NowTime64);

      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);

      if (CheckHttpdPid() == SS$_NONEXPR) MapGlobalSection ();

      if (HttpdGblSecPtr)
      {
         AccountingPtr = &HttpdGblSecPtr->Accounting;

         if (HttpdGblSecPtr->GblSecVersion != HTTPD_GBLSEC_VERSION_NUMBER)
            ScrPtr += sprintf (ScrPtr,
"%s           %s  GLOBAL SECTION MISMATCH!  \
(HTTPDMON:%08.08X WASD:%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);

         if (!DoNoStatusInfo) AddInstanceStatus ();

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

void MapGlobalSection (void)

{
   /* 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 unsigned long  RetAddr [2];
   static char  HttpdGblSecName [32];
   static $DESCRIPTOR (HttpdGblSecNameDsc, HttpdGblSecName);
   static $DESCRIPTOR (HttpdGblSecNameFaoDsc, GBLSEC_NAME_FAO);

   int  status,
        ByteSize,
        PageSize;
   unsigned short  ShortLength;

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

   if (Debug) fprintf (stdout, "MapGlobalSection() %08.08X\n", HttpdPid);

   if (HttpdGblSecPtr)
   {
      /* delete global section virtual addresses and remap */
      status = sys$deltva (&RetAddr, 0, 0);
      if (VMSnok (status)) exit (status);
      HttpdGblSecPtr = NULL;
   }

   if (!HttpdGblSecPtr)
   {
      sys$fao (&HttpdGblSecNameFaoDsc, &ShortLength, &HttpdGblSecNameDsc,
               HTTPD_NAME, HTTPD_GBLSEC_VERSION_NUMBER,
               InstanceEnvNumber, "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 monitor previously has been running then just wait */
         if (HttpdPid) return;

         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);
      }
   }

   if (HttpdGblSecPtr->GblSecVersion != HTTPD_GBLSEC_VERSION_NUMBER ||
       HttpdGblSecPtr->GblSecLength != sizeof(HTTPD_GBLSEC))
   {
      if (HttpdPid) fputs ("\n", stdout);
      fprintf (stdout,
"%%%s-E-GBLSEC, global section mismatch, rebuild HTTPDMON?\n",
               Utility);
      exit (STS$K_ERROR | STS$M_INHIB_MSG);
   }

   HttpdPid = HttpdGblSecPtr->HttpdProcessId;
}

/*****************************************************************************/
/*
Add the HTTPd server process information to the screen buffer.
*/ 

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;
   static unsigned long  ConnectTime [QUAD2],
                         JpiLoginTime [QUAD2];
   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;
   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);

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

   if (AccountingPtr->LastExitStatus)
      sprintf (LastExitStatus, "  %sExit:%s %%X%08.08X",
               ANSIbold, ANSInormal, AccountingPtr->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", JpiPrcLm-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;

   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,
      AccountingPtr->StartupCount,
      LastExitStatus,
      ANSIceol,
      ANSIbold, ANSInormal,
      JpiPageFlts,
      ANSIbold, ANSInormal,
      100-PercentOf(JpiPagFilCnt,JpiPgFlQuota),
      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 int  ErrorsNoticedCount;
   static char  NCScounts [48] = "",
                WebSockOrNoticed [96];
   int  idx, status,
        CodeGroup,
        ConnectTotalCount,
        DigitCount,
        CurrentThrottleProcessing,
        CurrentThrottleQueued,
        CurrentWebSockets;
   char  *bpsptr;
   char  BytesRawRx [32],
         BytesRawTx [32],
         CurrentProcessingString [128],
         CurrentProcessCountString [16],
         Four03s [16];

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

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

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

   /* double-up on the real estate */
   if (AccountingPtr->ErrorsNoticedCount &&
       AccountingPtr->ErrorsNoticedCount != ErrorsNoticedCount)
   {
      sprintf (WebSockOrNoticed, "%sNoticed:%s %d",
               ANSIbold, ANSInormal,
               AccountingPtr->ErrorsNoticedCount);
      ErrorsNoticedCount = AccountingPtr->ErrorsNoticedCount;
   }
   else
      sprintf (WebSockOrNoticed, "%sWS:%s %d/%d%%  %sBusy:%s %d",
               ANSIbold, ANSInormal,
               AccountingPtr->DclWebSocketCount,
               PercentOf(AccountingPtr->DclWebSocketCount,
                         AccountingPtr->ProcessingTotalCount[HTTP12]),
               ANSIbold, ANSInormal,
               AccountingPtr->ConnectTooBusyCount);

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

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

   CommaNumber (64, &AccountingPtr->BytesRawRx, BytesRawRx); 
   CommaNumber (64, &AccountingPtr->BytesRawTx, BytesRawTx); 

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

   if (HttpdGblSecPtr->ConnectSuspend)
      sprintf (CurrentProcessingString, "          %s%s S U S P E N D %s%s",
               ANSIreverse, ANSIbold, ANSInormal, ANSInormal);
   else
   if (HttpdGblSecPtr->InstancePassive)
      sprintf (CurrentProcessingString, "          %s%s P A S S I V E %s%s",
               ANSIreverse, ANSIbold, ANSInormal, ANSInormal);
   else
      CurrentProcessingString[0] = '\0';

   for (CurrentWebSockets = idx = 0;
        idx <= HttpdGblSecPtr->InstanceNodeCurrent;
        idx++) CurrentWebSockets += AccountingPtr->CurrentWebSockets[idx];

   for (CurrentThrottleProcessing = idx = 0;
        idx <= HttpdGblSecPtr->InstanceNodeCurrent;
        idx++) CurrentThrottleProcessing +=
                  AccountingPtr->CurrentThrottleProcessing[idx];

   for (CurrentThrottleQueued = idx = 0;
        idx <= HttpdGblSecPtr->InstanceNodeCurrent;
        idx++) CurrentThrottleQueued +=
                  AccountingPtr->CurrentThrottleQueued[idx];

   bpsptr = BytesPerString (AccountingPtr->BytesPerSecondAve);

   ScrPtr += sprintf (ScrPtr,
"%s%s\r\n\
 %sRequest:%s %d  %sCurrent:%s %d/%d/%d/%d  \
%sThrottle:%s %d/%d/%d%%  %sPeak:%s %d/%d\
%s\r\n\
  %sHTTP/2:%s %d/%d%%  %s/1.n:%s %d/%d%%  %sSSL:%s %d/%d%%  %s\
%s\r\n\
 %sCONNECT:%s %d  %sGET:%s %d  %sHEAD:%s %d  %sPOST:%s %d  %sPUT:%s %d  (%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 CGI:%d CGIplus:%d/%d RTE:%d/%d Prc:%d%s Prct:%d\
%s\r\n\
    %sFile:%s %d/%d  %sProxy:%s %d\
  %sPut:%s %d  %sSSI:%s %d  %sWebDAV:%s %d/%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)\
%s\r\n",
      CurrentProcessingString, ANSIceol,
      ANSIbold, ANSInormal,
      AccountingPtr->ProcessingTotalCount[HTTP12],
      ANSIbold, ANSInormal,
      AccountingPtr->CurrentConnected[HTTP12],
      AccountingPtr->CurrentProcessing[HTTP12],
      CurrentWebSockets,
      HttpdGblSecPtr->ProxyAccounting.TunnelCurrent,
      ANSIbold, ANSInormal,
      CurrentThrottleProcessing,
      CurrentThrottleQueued,
      AccountingPtr->ThrottleBusyMetric,
      ANSIbold, ANSInormal,
      AccountingPtr->ConnectPeak[HTTP12],
      AccountingPtr->ProcessingPeak[HTTP12],
      ANSIceol,
      ANSIbold, ANSInormal,
      AccountingPtr->ProcessingTotalCount[HTTP2],
      PercentOf(AccountingPtr->ProcessingTotalCount[HTTP2],
                AccountingPtr->ProcessingTotalCount[HTTP12]),
      ANSIbold, ANSInormal,
      AccountingPtr->ProcessingTotalCount[HTTP1],
      PercentOf(AccountingPtr->ProcessingTotalCount[HTTP1],
                AccountingPtr->ProcessingTotalCount[HTTP12]),
      ANSIbold, ANSInormal,
      AccountingPtr->ConnectSSLCount,
      PercentOf(AccountingPtr->ConnectSSLCount,
                AccountingPtr->ConnectAcceptedCount),
      WebSockOrNoticed,
      ANSIceol,
      ANSIbold, ANSInormal,
      AccountingPtr->MethodConnectCount,
      ANSIbold, ANSInormal,
      AccountingPtr->MethodGetCount,
      ANSIbold, ANSInormal,
      AccountingPtr->MethodHeadCount,
      ANSIbold, ANSInormal,
      AccountingPtr->MethodPostCount,
      ANSIbold, ANSInormal,
      AccountingPtr->MethodPutCount,
      AccountingPtr->MethodDeleteCount +
         AccountingPtr->MethodExtensionCount +
         AccountingPtr->MethodOptionsCount +
         AccountingPtr->MethodTraceCount,
      ANSIceol,
      ANSIbold, ANSInormal,
      AccountingPtr->DoServerAdminCount,
      ANSIbold, ANSInormal,
      AccountingPtr->CacheLoadCount,
      AccountingPtr->CacheHitCount,
      AccountingPtr->CacheHitNotModifiedCount,
      ANSIbold, ANSInormal,
      AccountingPtr->DoDECnetCgiCount,
      AccountingPtr->DoDECnetOsuCount,
      ANSIbold, ANSInormal,
      AccountingPtr->DoDirectoryCount,
      NCScounts,
      ANSIceol,
      ANSIbold, ANSInormal,
      AccountingPtr->DoScriptCount,
      AccountingPtr->DoCgiPlusScriptCount,
      AccountingPtr->DclCgiPlusReusedCount,
      AccountingPtr->DoRteScriptCount,
      AccountingPtr->DclRteReusedCount,
      AccountingPtr->DclCrePrcCount,
      CurrentProcessCountString,
      AccountingPtr->DclProctorCount,
      ANSIceol,
      ANSIbold, ANSInormal,
      AccountingPtr->DoFileCount,
      AccountingPtr->DoFileNotModifiedCount,
      ANSIbold, ANSInormal,
      AccountingPtr->DoProxyCount,
      ANSIbold, ANSInormal,
      AccountingPtr->DoPutCount,
      ANSIbold, ANSInormal,
      AccountingPtr->DoSsiCount,
      ANSIbold, ANSInormal,
      AccountingPtr->DoWebDavCount,
      AccountingPtr->MethodWebDavCopyCount +
         AccountingPtr->MethodWebDav_DeleteCount +
         AccountingPtr->MethodWebDav_GetCount +
         AccountingPtr->MethodWebDavLockCount +
         AccountingPtr->MethodWebDavMkColCount +
         AccountingPtr->MethodWebDavMoveCount +
         AccountingPtr->MethodWebDav_OptionsCount +
         AccountingPtr->MethodWebDavPropFindCount +
         AccountingPtr->MethodWebDavPropPatchCount +
         AccountingPtr->MethodWebDav_PutCount +
         AccountingPtr->MethodWebDavUnLockCount,
      ANSIceol,
      ANSIceol,
      ANSIbold, ANSInormal,
      AccountingPtr->ResponseStatusCodeGroup[0],
      ANSIbold, ANSInormal,
      AccountingPtr->ResponseStatusCodeGroup[2],
      ANSIbold, ANSInormal,
      AccountingPtr->ResponseStatusCodeGroup[3],
      ANSIbold, ANSInormal,
      AccountingPtr->ResponseStatusCodeGroup[4],
      Four03s,
      ANSIbold, ANSInormal,
      AccountingPtr->ResponseStatusCodeGroup[5],
      ANSIceol,
      ANSIbold, ANSInormal,
      BytesRawRx,
      AccountingPtr->NetReadErrorCount,
      ANSIbold, ANSInormal,
      BytesRawTx,
      AccountingPtr->NetWriteErrorCount,
      bpsptr,
      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;

   int  idx, status,
        ConnectTotalCount,
        CountNetwork,
        CountTotal,
        FreeBlocks,
        FreeMBytes,
        PercentBytesCache,
        PercentBytesNetwork,
        PercentBytesCannotCache,
        PercentCountCacheRead,
        PercentCountCacheWrite,
        PercentCountRequestCannotCache,
        PercentCountResponseCannotCache,
        PercentCountNetwork,
        ErrorCount,
        FreePercent,
        TotalMBytes,
        TotalBlocks,
        UsedBlocks,
        UsedMBytes,
        UsedPercent;
   unsigned long  BytesCache [QUAD2],
                  BytesCannotCache [QUAD2],
                  BytesRaw [QUAD2],
                  BytesTotal [QUAD2];
   char  BytesCacheRx [32],
         BytesCacheTx [32],
         BytesCannotCacheRx [32],
         BytesCannotCacheTx [32],
         BytesRawRx [32],
         BytesRawTx [32],
         ConnectString [128];
   char  *cptr, *sptr,
         *DevNamePtr,
         *EnabledPtr,
         *SpaceAvailablePtr;

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

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

   ConnectTotalCount = AccountingPtr->ConnectIpv4Count +
                       AccountingPtr->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 = TotalBlocks >> 11;
   UsedMBytes = UsedBlocks >> 11;
   FreeMBytes = FreeBlocks >> 11;
   FreePercent = PercentOf(FreeBlocks,TotalBlocks);
   UsedPercent = 100 - FreePercent;

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

   status = lib$addx (&HttpdGblSecPtr->ProxyAccounting.BytesCacheRx,
                      &HttpdGblSecPtr->ProxyAccounting.BytesCacheTx,
                      &BytesCache, 0);
   if (Debug) fprintf (stdout, "lib$addx() %%X%08.08X\n", status);

   status = lib$addx (&HttpdGblSecPtr->ProxyAccounting.BytesCannotCacheRx,
                      &HttpdGblSecPtr->ProxyAccounting.BytesCannotCacheTx,
                      &BytesCannotCache, 0);
   if (Debug) fprintf (stdout, "lib$addx() %%X%08.08X\n", status);

   status = lib$addx (&HttpdGblSecPtr->ProxyAccounting.BytesRawRx,
                      &HttpdGblSecPtr->ProxyAccounting.BytesRawTx,
                      &BytesRaw, 0);
   if (Debug) fprintf (stdout, "lib$addx() %%X%08.08X\n", status);

   status = lib$addx (&BytesCache, &BytesRaw, &BytesTotal, 0);
   if (Debug) fprintf (stdout, "lib$addx() %%X%08.08X\n", status);

   CommaNumber (64, &HttpdGblSecPtr->ProxyAccounting.BytesCacheRx, 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.BytesCacheTx, 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.BytesRawRx, BytesRawRx); 
   CommaNumber (64, &HttpdGblSecPtr->ProxyAccounting.BytesRawTx, BytesRawTx); 

   PercentBytesCache = QuadPercentOf (&BytesTotal, &BytesCache);
   PercentBytesCannotCache = QuadPercentOf (&BytesTotal, &BytesCannotCache);
   PercentBytesNetwork = QuadPercentOf (&BytesTotal, &BytesRaw);

   /**********/
   /* counts */
   /**********/

   CountNetwork = HttpdGblSecPtr->ProxyAccounting.ConnectIpv4Count +
                  HttpdGblSecPtr->ProxyAccounting.ConnectIpv6Count;
   CountTotal = CountNetwork + HttpdGblSecPtr->ProxyAccounting.CacheReadCount;

   PercentCountCacheRead =
      PercentOf (HttpdGblSecPtr->ProxyAccounting.CacheReadCount,
                 CountTotal);

   PercentCountNetwork = PercentOf (CountNetwork, CountTotal);

   PercentCountCacheWrite =
      PercentOf (HttpdGblSecPtr->ProxyAccounting.CacheWriteCount,
                 CountNetwork);

   PercentCountResponseCannotCache =
      PercentOf (HttpdGblSecPtr->ProxyAccounting.ResponseCannotCacheCount,
                 CountNetwork);

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

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

   ScrPtr += sprintf (ScrPtr,
"%s\r\n\
   %sProxy:%s %s%s\
%s\r\n\
 %sCONNECT:%s %d  %sGET:%s %d  %sHEAD:%s %d  \
%sPOST:%s %d  %sPUT:%s %d  %sOther:%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 Literal:%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.MethodGetCount,
      ANSIbold, ANSInormal,
      HttpdGblSecPtr->ProxyAccounting.MethodHeadCount,
      ANSIbold, ANSInormal,
      HttpdGblSecPtr->ProxyAccounting.MethodPostCount,
      ANSIbold, ANSInormal,
      HttpdGblSecPtr->ProxyAccounting.MethodPutCount,
      ANSIbold, ANSInormal,
      HttpdGblSecPtr->ProxyAccounting.MethodDeleteCount +
         HttpdGblSecPtr->ProxyAccounting.MethodExtensionCount +
         HttpdGblSecPtr->ProxyAccounting.MethodOptionsCount +
         HttpdGblSecPtr->ProxyAccounting.MethodTraceCount,
      ANSIceol,
      ANSIbold, ANSInormal,
      PercentBytesCannotCache,
      HttpdGblSecPtr->ProxyAccounting.RequestCannotCacheCount,
      PercentCountRequestCannotCache,
      HttpdGblSecPtr->ProxyAccounting.ResponseCannotCacheCount,
      PercentCountResponseCannotCache,
      ANSIceol,
      ANSIbold, ANSInormal,
      BytesRawRx,
      BytesRawTx,
      PercentBytesNetwork,
      HttpdGblSecPtr->ProxyAccounting.ConnectIpv4Count +
         HttpdGblSecPtr->ProxyAccounting.ConnectIpv6Count,
      PercentCountNetwork,
      ANSIceol,
      ANSIbold, ANSInormal,
      AccountingPtr->LookupLiteralCount,
      AccountingPtr->LookupDnsNameCount,
      AccountingPtr->LookupCacheNameCount,
      AccountingPtr->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];
   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;
   IOSBLK  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$a_pointer = v10orPrev10(PROXY_CACHE_ROOT,-1);
      DevNameDsc.dsc$w_length = strlen(DevNameDsc.dsc$a_pointer);

      /* 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.iosb$w_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.iosb$w_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.iosb$w_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,
               PercentOf(TotalFreeBlocks,TotalBlocks), 
               PercentOf(TotalUsedBlocks,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;

   BOOL  IsHostName,
         IsIpAddr;
   int  status,
        BytesLength,
        StringLength;
#ifndef __VAX
   double  dValue;
#endif
   char  *bptr, *bpsptr, *cptr, *sptr, *tptr;
   char  BytesRawRx [32],
         BytesRawTx [32],
         BytesTxGzipPercent [8],
         RequestDuration [32];

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

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

   CommaNumber (64, &HttpdGblSecPtr->Request.BytesRawRx, BytesRawRx); 
   CommaNumber (64, &HttpdGblSecPtr->Request.BytesRawTx, BytesRawTx); 

   if (HttpdGblSecPtr->Request.BytesTxGzipPercent)
      sprintf (BytesTxGzipPercent, " (%d%%)",
               HttpdGblSecPtr->Request.BytesTxGzipPercent);
   else
      BytesTxGzipPercent[0] = '\0';

#ifndef __VAX
   dValue = atof(HttpdGblSecPtr->Request.Duration);
   if (dValue >= 0.0006)
      sprintf (RequestDuration, "%.3f", dValue);
   else
   if (dValue >= 0.00006)
      sprintf (RequestDuration, "%.4f", dValue);
   else
#endif
      strcpy (RequestDuration, HttpdGblSecPtr->Request.Duration);

   sptr = ScrPtr;

   sptr += sprintf (sptr,
"%s\r\n    %sTime:%s %s  %sStatus:%s %03d  \
%sRx:%s %s  %sTx:%s %s%s  %sDur:%s %s",
           ANSIceol, ANSIbold, ANSInormal,
           HttpdGblSecPtr->Request.Time,
           ANSIbold, ANSInormal,
           HttpdGblSecPtr->Request.HttpStatus,
           ANSIbold, ANSInormal,
           BytesRawRx,
           ANSIbold, ANSInormal,
           BytesRawTx, BytesTxGzipPercent,
           ANSIbold, ANSInormal,
           RequestDuration);

   bpsptr = BytesPerString (HttpdGblSecPtr->Request.BytesPerSecond);

   BytesLength = strlen(BytesRawRx) +
                 strlen(BytesRawTx) +
                 strlen(BytesTxGzipPercent);

   if (BytesLength <= 10) sptr += sprintf (sptr, " (%s)", bpsptr);
      
   sptr += sprintf (sptr,
"%s\r\n\
 %sService:%s %s%s%s%s",
           ANSIceol, ANSIbold, ANSInormal,
           HttpdGblSecPtr->Request.Service,
           HttpdGblSecPtr->Request.PrcNam[0] ? " (" : "",
           HttpdGblSecPtr->Request.PrcNam,
           HttpdGblSecPtr->Request.PrcNam[0] ? ")" : "");

   if (BytesLength >= 12)
   {
      StringLength = 50 + BytesLength;
      StringLength -= 11;
      StringLength -= strlen(HttpdGblSecPtr->Request.Service);
      if (HttpdGblSecPtr->Request.PrcNam[0])
         StringLength -= strlen(HttpdGblSecPtr->Request.PrcNam) + 3;
      if (StringLength < 2) StringLength = 2;
      while (StringLength--) *sptr++ = ' ';
      sptr += sprintf (sptr, "(%s)", bpsptr);
   }

   sptr += sprintf (sptr,
"%s\r\n\
    %sHost:%s ",
           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 (AccountingPtr->PathAlertCount > PrevPathAlertCount)
      {
         PrevPathAlertCount = AccountingPtr->PathAlertCount;
         PathAlertBellCount = PathAlertBellRepetition;
      }
   }
   if (PathAlertBellCount)
   {
      fputs ("\x07", stdout);
      PathAlertBellCount--;
   }

   /* 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++);
   }
   IsHostName = !(IsIpAddr = (*tptr == '\0')); 

   if (DoLookupHost)
      if (IsIpAddr)
         if (tptr = LookupHostName (cptr))
            if (IsHostName = (*tptr != '?' && *tptr != '!'))
               cptr = tptr;

   StringLength = strlen(cptr);
   /* excise some middle section if it would overflow the line */
   if (StringLength > 43)
   {
      while (*cptr && *cptr != '.') *sptr++ = *cptr++;
      *sptr++ = '.';
      *sptr++ = '.';
      while (*cptr && --StringLength > 43) cptr++;
      while (*cptr && *cptr != '.') cptr++;
   }
   while (*cptr) *sptr++ = *cptr++;

   if (IsHostName)
   {
      *sptr++ = ' ';
      *sptr++ = '(';
      for (cptr = HttpdGblSecPtr->Request.ClientIpAddressString;
           *cptr;
           *sptr++ = *cptr++);
      *sptr++ = ')';
   }

   switch (HttpdGblSecPtr->Request.HttpProtocol)
   {
      case HTTP_VERSION_2 : cptr = " HTTP/2"; break;
      case HTTP_VERSION_1_1 : cptr = " HTTP/1.1"; break;
      case HTTP_VERSION_1_0 : cptr = " HTTP/1.0"; break;
      case HTTP_VERSION_0_9 : cptr = " HTTP/0.9"; break;
      default : cptr = "";
   }
   while (*cptr) *sptr++ = *cptr++;

   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;
   if (HttpdGblSecPtr->Request.AuthUser[0])
      tptr -= strlen(HttpdGblSecPtr->Request.AuthUser) + 3;
   if (tptr < sptr) tptr = sptr;

   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; cptr++)
      if ((*cptr >= 0x20 && *cptr <= 0x7e) ||
          (*cptr >= 0xa0 && *cptr <= 0xfe))
         *sptr++ = *cptr; else *sptr++ = '.';
   if (sptr >= tptr)
      sptr += sprintf (sptr, "%s...%s", ANSIbold, ANSInormal);
   else
   if (HttpdGblSecPtr->Request.Alert) 
      sptr += sprintf (sptr, "%s", ANSInormal);

   if (HttpdGblSecPtr->Request.AuthUser[0])
      sptr += sprintf (sptr, " (%s)", HttpdGblSecPtr->Request.AuthUser);

   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);
}

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

int CheckHttpdPid (void)

{
   static unsigned long  JpiPid;
   static struct
   {
      unsigned short  buf_len;
      unsigned short  item;
      unsigned char  *buf_addr;
      void  *ret_len;
   }
      JpiItems [] =
   {
      { sizeof(JpiPid), JPI$_PID, &JpiPid, 0 },
      {0,0,0,0}
   };

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

   if (Debug) fprintf (stdout, "CheckHttpdPid() %08.08X\n", HttpdPid);

   return (sys$getjpiw (0, &HttpdPid, 0, &JpiItems, 0, 0, 0));
}

/*****************************************************************************/
/*
Calculate percentages of unsigned longs using floats to avoid integer overflow
and allowing more accurate rounding.
*/

int PercentOf
(
unsigned long arg1,
unsigned long arg2
)
{
   int  iperc;
   float  farg1, farg2, fperc;

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

   if (arg2)
   {
      farg1 = (float)arg1;
      farg2 = (float)arg2;
      fperc = farg1 * 100.0 / farg2;
      iperc = (int)fperc;
      if (fperc - (float)iperc >= 0.5) iperc++;
      return (iperc);
   }
   return (0);
}

/*****************************************************************************/
/*
Calculate percentages of quadwards using floats to avoid overflow and allowing
more accurate rounding.
*/

int QuadPercentOf
(
unsigned long *qarg1,
unsigned long *qarg2
)
{
   int  iqperc;
   float  fqarg1, fqarg2, fqperc;

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

   if (qarg2[0] || qarg2[1])
   {
      fqarg1 = (float)qarg1[0] + ((float)qarg1[1] * 4294967296.0);
      fqarg2 = (float)qarg2[0] + ((float)qarg2[1] * 4294967296.0);
      if (fqarg2 == 0.0) return (0);
      fqperc = fqarg1 * 100.0 / fqarg2;
      iqperc = (int)fqperc;
      if (fqperc - (float)iqperc >= 0.5) iqperc++;
      return (iqperc);
   }
   return (0);
}

/*****************************************************************************/
/*
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  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;
   IOSBLK  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 (InstanceEnvNumber > INSTANCE_ENV_NUMBER_MAX)
      {
         fprintf (stdout, "%%%s-E-INSTANCE, group number range 1 to %d\n",
                  Utility, INSTANCE_ENV_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) |
                      (InstanceEnvNumber & 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)INSTANCE_CLUSTER;
      ClusterLockNameDsc.dsc$w_length = sptr - ClusterLockName;
      if (Debug) fprintf (stdout, "cluster: |%*.*s|\n",
                          ClusterLockNameDsc.dsc$w_length,
                          ClusterLockNameDsc.dsc$w_length,
                          ClusterLockNameDsc.dsc$a_pointer);

      /* 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)INSTANCE_NODE;
      NodeLockNameDsc.dsc$w_length = sptr - NodeLockName;

      if (Debug) fprintf (stdout, "node: |%*.*s|\n",
                          NodeLockNameDsc.dsc$w_length,
                          NodeLockNameDsc.dsc$w_length,
                          NodeLockNameDsc.dsc$a_pointer);

      /* 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, "cluster LockCount: %d\n", LockCount);

   ClusterCount = 0;
   lkiptr = &LkiLocks;
   for (cnt = LockCount; cnt; cnt--)
   {
      if (Debug) fprintf (stdout, "1 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, "node LockCount: %d\n", LockCount);

   NodeCount = 0;
   lkiptr = &LkiLocks;
   for (cnt = LockCount; cnt; cnt--)
   {
      if (Debug) fprintf (stdout, "2 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, "3 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, "lki$l_pid: %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, "InstanceString: |%s|\n", InstanceString);
}

/*****************************************************************************/
/*
See [SRC.HTTPD]INSTANCE.C InstanceCliReport().
*/

void AddInstanceStatus ()

{
   static ulong  LibDeltaMins = LIB$K_DELTA_MINUTES;

   BOOL  stale;
   int  idx, len, maxtab, maxlen, status, total;
   ulong  MinsAgo;
   ulong  AgoTime64 [2];
   char  *cptr;
   char  number [64],
         ExitAgoBuf [16],
         ExitStatusBuf [16],
         LineBuffer [256],
         StartAgoBuf [16],
         TmpAgoBuf [16],
         UpdateAgoBuf [16];
   INSTANCE_STATUS  *istptr;

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

   if (Debug) fprintf (stdout, "AddInstanceStatus() %d\n", PageLength);
 
   maxtab = AccountingPtr->InstanceStatusTableCount;

   if (maxtab == 1 && !DoStatusInfo) return;

   if (!DoStatusInfo)
   {
      if (PageLength <= 24) return;
      if (PageLength < 24 + maxtab + 2) return;
   }

   for (idx = maxlen = 0; idx < maxtab; idx++)
   {
      istptr = &AccountingPtr->InstanceStatusTable[idx];
      if ((len = strlen(istptr->InstanceName)) > maxlen) maxlen = len;
   }
   if (maxlen < 8) maxlen = 8;

   ScrPtr += sprintf (ScrPtr,
"\r\n%s\r\n\
    %s%-*s%s %s%4.4s%s %s%4.4s%s %s%5.5s%s %s%4.4s%s \
%s%-10s%s %s%7.7s%s %s%4.4s%s %s%6.6s%s\r\n",
             ANSIceos,
             ANSIbold, maxlen, "Instance", ANSInormal,
             ANSIbold, " Ago", ANSInormal,
             ANSIbold, "  Up", ANSInormal,
             ANSIbold, "Count", ANSInormal,
             ANSIbold, "Exit", ANSInormal,
             ANSIbold, "Status", ANSInormal,
             ANSIbold, "Version", ANSInormal,
             ANSIbold, "/Min", ANSInormal,
             ANSIbold, " /Hour", ANSInormal);

   for (idx = total = 0; idx < maxtab; idx++)
   {
      istptr = &AccountingPtr->InstanceStatusTable[idx];

      /* if a purged entry */
      if (QUAD_ZERO(istptr->UpdateTime64)) continue;

      /* right justify each of these */
      ThisLongAgo (&istptr->ExitTime64, TmpAgoBuf);
      sprintf (ExitAgoBuf, "%4s", TmpAgoBuf);
      ThisLongAgo (&istptr->StartTime64, TmpAgoBuf);
      sprintf (StartAgoBuf, "%4s", TmpAgoBuf);
      ThisLongAgo (&istptr->UpdateTime64, TmpAgoBuf);
      sprintf (UpdateAgoBuf, "%4s", TmpAgoBuf);

      if (istptr->ExitStatus)
         sprintf (ExitStatusBuf, "%%X%08.08X", istptr->ExitStatus);
      else
         ExitStatusBuf[0] = '\0';

      status = lib$sub_times (&NowTime64, &istptr->UpdateTime64, &AgoTime64);
      if (VMSok (status))
         status = lib$cvt_from_internal_time (&LibDeltaMins, &MinsAgo, &AgoTime64);
      if (VMSok(status) && MinsAgo > INSTANCE_STATUS_STALE_MINS)
      {
         stale = true;
         strcpy (number, "    ");
      }
      else
      {
         stale = false;
         sprintf (number, " %s%2d%s ", ANSIbold, ++total, ANSInormal);
      }
      for (cptr = number; *cptr; cptr++);

      sprintf (LineBuffer,
"%s%-*s %4.4s %4.4s %5u %4.4s %10.10s %7.7s %4u %6u\r\n",
                   number,
                   maxlen, istptr->InstanceName, UpdateAgoBuf,
                   StartAgoBuf, istptr->StartupCount,
                   ExitAgoBuf, ExitStatusBuf,
                   istptr->HttpdVersion,
                   istptr->MinuteCount,
                   istptr->HourCount);
      if (stale)
         for (cptr = LineBuffer + (cptr-number); *cptr; cptr++)
            if (*cptr == ' ') *cptr = '-';

      ScrPtr += sprintf (ScrPtr, "%s", LineBuffer);
   }

   if (!total)
      ScrPtr += sprintf (ScrPtr, " %s 0%s reset?\r\n", ANSIbold, ANSInormal);

   ScrPtr -= 2;
   *ScrPtr++ = ' ';
   *ScrPtr++ = ' ';
   *ScrPtr++ = '\0';
}

/*****************************************************************************/
/*
Format a string containing how many seconds, minutes, hours, days ago from the
current time.  For example; "15s", "10m", "17h", "152d".  Return VMS status.
|AgoBuf| should be at least 16 chars.
*/

int ThisLongAgo
(
ulong *Time64Ptr,
char *AgoBuf
)
{
   static ulong  LibDeltaSecs = LIB$K_DELTA_SECONDS;
   static $DESCRIPTOR (AgoFaoDsc, "!UL!AZ\0");
   static $DESCRIPTOR (AgoBufDsc, "");

   int  status;
   ulong  SecsAgo;
   ulong  AgoTime64 [2];

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

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

   AgoBuf[0] = '\0';

   if (QUAD_ZERO(Time64Ptr)) return (SS$_NORMAL);

   status = lib$sub_times (&NowTime64, Time64Ptr, &AgoTime64);
   if (VMSok (status))
      status = lib$cvt_from_internal_time (&LibDeltaSecs, &SecsAgo, &AgoTime64);
   if (VMSnok (status)) return (status);

   AgoBufDsc.dsc$a_pointer = AgoBuf;
   AgoBufDsc.dsc$w_length = 16;
 
   status = SS$_NORMAL;

   if (SecsAgo >= (60*60*24))
      status = sys$fao (&AgoFaoDsc, 0, &AgoBufDsc, SecsAgo / (60*60*24), "d");
   else
   if (SecsAgo >= (60*60))
      status = sys$fao (&AgoFaoDsc, 0, &AgoBufDsc,  SecsAgo / (60*60), "h");
   else
   if (SecsAgo >= 60)
      status = sys$fao (&AgoFaoDsc, 0, &AgoBufDsc, SecsAgo / 60, "m");
   else
   if (SecsAgo > 1)
      status = sys$fao (&AgoFaoDsc, 0, &AgoBufDsc, SecsAgo, "s");
   else
      strcpy (AgoBuf, "now");

   return (status);
}

/*****************************************************************************/
/*
Get the IP name using asynchronous address-to-name lookup.  Despite the implied
IPv6 functionality TCP/IP Services lookup only supports IPv4 (sigh, one day
perhaps).  There will always be a delay in the lookup because this function is 
called during AST delivery, so buffer the IP addresses and host names.  Returns
a pointer to the host name, to '?' during lookup, or '!' if not resolvable.
*/

char* LookupHostName (ulong arg1)
{
#define CACHE_MAX 128  /* absolute max is 1024 */
#define EXPIRE_SECONDS 300
#define RETRY_ATTEMPTS   5
#define RETRY_SECONDS   60

#define INETACP$C_TRANS 2
#define INETACP_FUNC$C_GETHOSTBYNAME 1
#define INETACP_FUNC$C_GETHOSTBYADDR 2

   struct LookupCacheStruct
   {
       int  RetryCount;
       ushort  HostNameLength;
       ulong  ExpiresStamp;
       ulong  Ip4Address;
       ulong  Ip6Address [4];
       char  HostName [127+1],
             IpAddrStr [31+1];
       struct dsc$descriptor HostAddressDsc;
       struct dsc$descriptor HostNameDsc;
       IOSBLK  LookupIOsb;
   };
   static struct LookupCacheStruct  LookupCache [CACHE_MAX];

   static $DESCRIPTOR (TcpIpDeviceDsc, "UCX$DEVICE");
   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 int  CacheUsed;
   static unsigned short  LookupChannel;

   int  idx, status;
   ulong  ThisTimeStamp;
   ulong  ThisBinTime [2];
   char  *cptr, *sptr, *zptr;

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

   if (Debug) fprintf (stdout, "LookupHostName() %d\n", arg1);

   if (!LookupChannel)
   {
      /* assign a channel to the internet template device */
      status = sys$assign (&TcpIpDeviceDsc, &LookupChannel, 0, 0);
      if (VMSnok (status)) exit (status);
      /* initialise the descriptors */
      for (idx = 0; idx < CACHE_MAX; idx++)
      {
         LookupCache[idx].HostNameDsc.dsc$b_class = DSC$K_CLASS_S;
         LookupCache[idx].HostNameDsc.dsc$b_dtype = DSC$K_DTYPE_T;
         LookupCache[idx].HostNameDsc.dsc$w_length =
            sizeof(LookupCache[idx].HostName)-1;
         LookupCache[idx].HostNameDsc.dsc$a_pointer =
            LookupCache[idx].HostName;
         LookupCache[idx].HostAddressDsc.dsc$b_class = DSC$K_CLASS_S;
         LookupCache[idx].HostAddressDsc.dsc$b_dtype = DSC$K_DTYPE_T;
      }
   }

   if (arg1 > 1023)
   {
      /**************************/
      /* lookup this IP address */
      /**************************/

      sys$gettim (&ThisBinTime);
      ThisTimeStamp = decc$fix_time (&ThisBinTime);

      cptr = (char*)arg1;
      if (Debug) fprintf (stdout, "->|%s|\n", cptr);

      /* search the cache */
      for (idx = 0; idx < CacheUsed; idx++)
      {
         if (!MATCH8 (cptr, LookupCache[idx].IpAddrStr)) continue;
         if (strcmp (cptr, LookupCache[idx].IpAddrStr)) continue;
         if (Debug) fprintf (stdout, "|%s|->\n", LookupCache[idx].HostName);
         return (LookupCache[idx].HostName);
      }
      /* look for an unused or expired entry */
      for (idx = 0; idx < CACHE_MAX; idx++)
      {
         if (!LookupCache[idx].IpAddrStr[0]) break;
         if (ThisTimeStamp >= LookupCache[idx].ExpiresStamp) break;
      }
      /* otherwise look for an unresolveable entry */
      if (idx >= CACHE_MAX)
         for (idx = 0; idx < CACHE_MAX; idx++)
            if (LookupCache[idx].IpAddrStr[0] == '!') break;
      /* otherwise just cycle through the cache array entries */
      if (idx >= CACHE_MAX) idx = 0;
      if (idx > CacheUsed) CacheUsed = idx;
      if (Debug) fprintf (stdout, "idx %d %d\n", idx, CacheUsed);

      LookupCache[idx].IpAddrStr[0] = LookupCache[idx].HostName[1] = '\0';

      if (strchr (cptr, ':'))
      {
         if (inet_pton (AF_INET6, cptr, &LookupCache[idx].Ip6Address) != 1)
         {
            LookupCache[idx].HostName[0] = '!';
            LookupCache[idx].ExpiresStamp = ThisTimeStamp + RETRY_SECONDS;
            return (LookupCache[idx].HostName);
         }
         LookupCache[idx].Ip4Address = 0;
      }
      else
      if (inet_pton (AF_INET, cptr, &LookupCache[idx].Ip4Address) != 1)
      {
         LookupCache[idx].HostName[0] = '!';
         LookupCache[idx].ExpiresStamp = ThisTimeStamp + RETRY_SECONDS;
         return (LookupCache[idx].HostName);
      }

      LookupCache[idx].HostName[0] = '?';
      LookupCache[idx].ExpiresStamp = ThisTimeStamp + EXPIRE_SECONDS;

      zptr = (sptr = LookupCache[idx].IpAddrStr) +
              sizeof(LookupCache[idx].IpAddrStr)-1;
      while (*cptr && sptr < zptr) *sptr++ = *cptr++;
      *sptr = '\0';

      if (LookupCache[idx].Ip4Address)
      {
         LookupCache[idx].HostAddressDsc.dsc$w_length =
            sizeof(LookupCache[idx].Ip4Address);
         LookupCache[idx].HostAddressDsc.dsc$a_pointer =
            (char*)&LookupCache[idx].Ip4Address;
      }
      else
      {
         LookupCache[idx].HostAddressDsc.dsc$w_length =
            sizeof(LookupCache[idx].Ip6Address);
         LookupCache[idx].HostAddressDsc.dsc$a_pointer =
            (char*)&LookupCache[idx].Ip6Address;
      }
      LookupCache[idx].RetryCount = RETRY_ATTEMPTS;
   }
   else
   {
      /**************/
      /* lookup AST */
      /**************/

      idx = arg1;
      status = LookupCache[idx].LookupIOsb.iosb$w_status;

      if (Debug) fprintf (stdout, "sys$qio() %%X%08.08X\n", status);

      if (VMSok (status)) 
      {
         LookupCache[idx].HostName[LookupCache[idx].HostNameLength] = '\0';
         if (Debug) fprintf (stdout, "|%s|\n", LookupCache[idx].HostName);
         if (!LookupCache[idx].HostName[0])
         {
            LookupCache[idx].HostName[0] = '!';
            LookupCache[idx].HostName[1] = '\0';
         }
         return (LookupCache[idx].HostName);
      }

      if (status == SS$_ENDOFFILE ||
         !LookupCache[idx].RetryCount--)
      {
         sys$gettim (&ThisBinTime);
         ThisTimeStamp = decc$fix_time (&ThisBinTime);
         LookupCache[idx].ExpiresStamp = ThisTimeStamp + RETRY_SECONDS;
         LookupCache[idx].HostName[0] = '!';
         LookupCache[idx].HostName[1] = '\0';
         return (LookupCache[idx].HostName);
      }
      LookupCache[idx].RetryCount--;
   }

   status = sys$qio (0, LookupChannel, IO$_ACPCONTROL | IO$M_EXTEND,
                     &LookupCache[idx].LookupIOsb,
                     LookupHostName, idx,
                     &ControlSubFunctionDsc,
                     &LookupCache[idx].HostAddressDsc,
                     &LookupCache[idx].HostNameLength,
                     &LookupCache[idx].HostNameDsc, 0, 0);
   if (Debug) fprintf (stdout, "[%d] sys$qio() %%X%08.08X\n", idx, status);

   if (VMSnok(status)) LookupCache[idx].IpAddrStr[0] = '\0';

   return (LookupCache[idx].HostName);
}

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

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);
}

/*****************************************************************************/
/*
Place the values of selected server statistics into global CLI symbols.  This
allows a process to periodically pooll these values and do whatever it likes
with the provided values.  Exits immediately.  (A Swoose special :-)
*/

int PluckStats ()

{
   int  idx, status;
   char  LastExitStatus [16];

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

   MapGlobalSection ();

   AccountingPtr = &HttpdGblSecPtr->Accounting;

   sprintf (LastExitStatus, "%%X%08.08X", AccountingPtr->LastExitStatus);

   SetSymbol ("HTTPDMON_VERSION", HttpdGblSecPtr->HttpdVersion, 0);
   SetSymbol ("HTTPDMON_LASTEXIT", LastExitStatus, 0);
   SetSymbol ("HTTPDMON_CONNECT_CURRENT", NULL,
              AccountingPtr->CurrentConnected[HTTP12]);
   SetSymbol ("HTTPDMON_CONNECT_PEAK", NULL,
              AccountingPtr->ConnectPeak[HTTP12]);
   SetSymbol ("HTTPDMON_REQUEST_CURRENT", NULL,
              AccountingPtr->CurrentProcessing[HTTP12]);
   SetSymbol ("HTTPDMON_REQUEST_PEAK", NULL,
              AccountingPtr->ProcessingPeak[HTTP12]);

   return (SS$_NORMAL);
}

/****************************************************************************/
/*
Assign a global symbol.  If the string pointer is null the numeric value is
used.  Symbol lengths are fixed to a maxim,um of 255.
*/ 

SetSymbol
(
char *Name,
char *String,
int Value
)
{
   static int  CliSymbolType = LIB$K_CLI_GLOBAL_SYM;
   static char  ValueString [32];
   static $DESCRIPTOR (NameDsc, "");
   static $DESCRIPTOR (ValueDsc, "");
   static $DESCRIPTOR (ValueFaoDsc, "!UL");
   static $DESCRIPTOR (ValueStringDsc, ValueString);

   int  status;

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

   if (Debug)
      fprintf (stdout, "SetSymbol() |%s|%s| %d\n", Name, String, Value);

   NameDsc.dsc$a_pointer = Name;
   NameDsc.dsc$w_length = strlen(Name);
   if (!String)
   {
      ValueDsc.dsc$a_pointer = ValueString;
      sys$fao (&ValueFaoDsc, &ValueDsc.dsc$w_length, &ValueStringDsc, Value);
      ValueString[ValueDsc.dsc$w_length] = '\0';
   }
   else
   {
      ValueDsc.dsc$a_pointer = String;
      if ((ValueDsc.dsc$w_length = strlen(String)) > 255)
         ValueDsc.dsc$w_length = 255;
   }

   if (Debug) fprintf (stdout, "|%s| %d\n", Name, ValueDsc.dsc$w_length);

   if (VMSnok (status = lib$set_symbol (&NameDsc, &ValueDsc, &CliSymbolType)))
      exit (status);
}

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

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);

   int  status,
        DayOfWeek;
   unsigned short  Length;

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

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

/*****************************************************************************/
/*
Return a pointer to a string containing a representative bytes-per-second.
*/

char* BytesPerString (unsigned int BytesPerSecond)

{
   static char  BytesPerString [32];

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

/*
Prevent "%CC-E-INTCONST, Ill-formed integer constant" under
Compaq C V6.4-005 on OpenVMS VAX V7.2 (at least).
*/
   if (BytesPerSecond >= (10000000 * 1000))
      sprintf (BytesPerString, "%.1fGB/s", (float)BytesPerSecond/1000000000.0);
   else
   if (BytesPerSecond >= 100000000)
      sprintf (BytesPerString, "%dfMB/s", BytesPerSecond/1000000);
   else
   if (BytesPerSecond >= 1000000)
      sprintf (BytesPerString, "%.1fMB/s", (float)BytesPerSecond/1000000.0);
   else
   if (BytesPerSecond >= 100000)
      sprintf (BytesPerString, "%dkB/s", BytesPerSecond/1000);
   else
   if (BytesPerSecond >= 1000)
      sprintf (BytesPerString, "%.1fkB/s", (float)BytesPerSecond/1000.0);
   else
      sprintf (BytesPerString, "%dB/s", BytesPerSecond);

   return (BytesPerString);
}

/*****************************************************************************/
/*
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, "!@SQ");

   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 bit of a 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';
}

/*****************************************************************************/
/*
From [SRC.HTTPD]SUPPORT.C
Support version 10 and pre-version-10 logical and file naming conventions. 
Look for one of multiple (usually just two but can be more) possible logical
names.  When one is successfully translated return a pointer to it's null
terminated name.  Otherwise test for a subsequent and return a pointer to it if
found.  If none is found then return NoneFound if zero or positive, return the
LogicalName if minus one.  The constants should be #defined in the manner of
"?WASD_CONFIG_GLOBAL\0HTTPD$CONFIG\0".
*/

char* v10orPrev10
(
char *LogicalName,
int NoneFound
)
{
   static unsigned short  Length;
   static char  ValueString [128];
   static $DESCRIPTOR (LogicalNameDsc, "");
   static $DESCRIPTOR (LnmFileDevDsc, "LNM$FILE_DEV");
   static struct
   {
      unsigned short  buf_len;
      unsigned short  item;
      void  *buf_addr;
      void  *ret_len;
   } LnmItems [] =
   {
      { sizeof(ValueString)-1, LNM$_STRING, ValueString, &Length },
      { 0,0,0,0 }
   };

   int  status;
   char  *cptr, *sptr;

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

   if (!LogicalName || LogicalName[0] != '?') return (LogicalName);

   /* stop at any logical name delimiting colon (perhaps of a file name) */
   for (sptr = cptr = LogicalName+1; *sptr && *sptr != ':'; sptr++);
   while (*cptr)
   {
      LogicalNameDsc.dsc$a_pointer = cptr;
      LogicalNameDsc.dsc$w_length = sptr - cptr;
      status = sys$trnlnm (0, &LnmFileDevDsc, &LogicalNameDsc, 0, &LnmItems);
      if (VMSok (status)) return (cptr);
      /* scan past any logical-delimiting colon */
      while (*sptr) sptr++;
      /* stop at any logical name delimiting colon (perhaps of a file name) */
      for (cptr = (sptr += 1); *sptr && *sptr != ':'; sptr++);
   }

   if (NoneFound != -1) return ((char*)NoneFound);
   return (LogicalName+1);
}

/*****************************************************************************/
/*
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))
      {
         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);
         }
         InstanceEnvNumber = atoi(aptr);
         continue;
      }
      if (strsame (aptr, "/DBUG", -1))
      {
         Debug = true;
         continue;
      }
      if (strsame (aptr, "/DEMO", -1))
      {
         DemoMode = true;
         continue;
      }
      if (strsame (aptr, "/ENV=", 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);
         }
         InstanceEnvNumber = atoi(aptr);
         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, "/PLUCK", 5))
      {
         DoPluck = true;
         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 (strsame (aptr, "/STATUS", 4))
      {
         DoStatusInfo = true;
         DoNoStatusInfo = false;
         continue;
      }
      if (strsame (aptr, "/NOSTATUS", 6))
      {
         DoStatusInfo = false;
         DoNoStatusInfo = 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 /ENV=<integer> /[NO]GENERAL /HELP\n\
/INTERVAL=<integer> /[NO]LOOKUP /PLUCK /PRCNAM=prcnam /PORT=<integer>\n\
/[NO]PROCESS /[NO]PROXY /REFRESH=integer /[NO]REQUEST /[NO]STATUS\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);
}
 
/*****************************************************************************/

