/*****************************************************************************/
/*
                                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
/[NO]GEOLOCATE[=<string>]  control optional geolocation
/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

With optional geolocation, BUILD_HTTPDMON_GEOLOCATE.COM


COPYRIGHT
---------
Copyright (C) 1996-2024 Mark G.Daniel

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.


VERSION HISTORY (update SOFTWAREVN as well!)
---------------
20-AUG-2024  MGD  v3.2.0, proxy FTP obsolete
19-JUN-2024  MGD  v3.1.2, (<other-methods>) all extra ->Method<what>Count 
13-JAN-2024  MGD  v3.1.1, compiler-fix; when function and (stack?) storage
                          share identical names 
                  VSI C x86-64 V7.5-009 (GEM 50XBR) on OpenVMS x86_64 V9.2-1
                  G2L-E-ASSERT, GEM ENTRY TimeString ... defined as GEM VARIABLE
02-FEB-2022  MGD  v3.1.0, optional geolocation
                          bugfix; TcpIpLookup() ares[aCacheIdx]
06-MAY-2021  MGD  v3.0.0, WASD v12.n.n
                          so long, farewell, Auf Wiedersehen, goodnight (-VAX)
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 "v3.2.0"
#define SOFTWARENM "HTTPDMON"
#ifdef __ALPHA
#  define SOFTWAREID SOFTWARENM " " SOFTWAREVN " AXP"
#endif
#ifdef __ia64
#  define SOFTWAREID SOFTWARENM " " SOFTWAREVN " IA64"
#endif
#ifdef __x86_64
#  define SOFTWAREID SOFTWARENM " " SOFTWAREVN " X86"
#endif

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

/* VMS related header files */
#include <descrip.h>
#include <dvidef.h>
#include <errno.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 */

/* ensure BSD 4.4 structures  */
#define _SOCKADDR_LEN
/* BUT MultiNet BG driver does not support BSD 4.4 AF_INET addresses */
#define NO_SOCKADDR_LEN_4

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

#ifdef HTTPDMON_GEOLOCATE
#include "geolocate.h"
#endif

#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 [2] = { 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;

int64  NowTime64;

char  CliGeoLocate [256],
      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[0K",
     ANSIceos [] = "\x1b[0J",
     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 CommaNumber (int, void*, char*);
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);
void ThisLongAgo (int64*, char*);
char* TimeString (); 
void TryIt (char*);
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 (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 ();

   if (DoPluck) exit (PluckStats ());

   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 (Debug) fprintf (stdout, "%d %d\n",
                          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 int64  ConnectTime64,
                 JpiLoginTime64;
   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(JpiLoginTime64), JPI$_LOGINTIM, &JpiLoginTime64, 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);

   ConnectTime64 = JpiLoginTime64 - NowTime64;
   sys$fao (&UpTimeFaoDsc, &Length, &UpTimeDsc, &ConnectTime64);
   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->BytesRawRx64, BytesRawRx); 
   CommaNumber (64, &AccountingPtr->BytesRawTx64, 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->RequestSSLCount,
      PercentOf(AccountingPtr->RequestSSLCount,
                AccountingPtr->ProcessingTotalCount[HTTP12]),
      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,
         AccountingPtr->MethodWebDavCopyCount +
         AccountingPtr->MethodWebDavLockCount +
         AccountingPtr->MethodWebDavMkColCount +
         AccountingPtr->MethodWebDavMoveCount +
         AccountingPtr->MethodWebDavPropFindCount +
         AccountingPtr->MethodWebDavPropPatchCount +
         AccountingPtr->MethodWebDavUnLockCount +
      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;
   int64  BytesRaw64,
          BytesTotal64;
   char  BytesRawRx [32],
         BytesRawTx [32],
         ConnectString [128];
   char  *cptr, *sptr,
         *EnabledPtr;

   /*********/
   /* 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";

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

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

   CommaNumber (64, &HttpdGblSecPtr->ProxyAccounting.BytesRawRx64, BytesRawRx); 
   CommaNumber (64, &HttpdGblSecPtr->ProxyAccounting.BytesRawTx64, BytesRawTx); 

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

   /***********/
   /* 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\
  %sSOCKS5:%s Count:%d Success:%d Fail:%d\
%s\r\n\
  %sRework:%s Count:%d No.Type:%d Search:%d Replace:%d Too.Big:%d\
%s\r\n\
 %sNetwork:%s Rx:%s Tx:%s\
%s\r\n\
  %sLookup:%s Literal:%d DNS:%d Cache:%d Error:%d\
%s\r\n",
      ANSIceol,
      ANSIbold, ANSInormal,
      EnabledPtr,
      ConnectString,
      ANSIceol,
      ANSIbold, ANSInormal,
      HttpdGblSecPtr->ProxyAccounting.Socks5Count,
      HttpdGblSecPtr->ProxyAccounting.Socks5SuccessCount,
      HttpdGblSecPtr->ProxyAccounting.Socks5FailCount,
      ANSIceol,
      ANSIbold, ANSInormal,
      HttpdGblSecPtr->ProxyAccounting.ReworkCount,
      HttpdGblSecPtr->ProxyAccounting.ReworkNoType,
      HttpdGblSecPtr->ProxyAccounting.ReworkReplaceSearch,
      HttpdGblSecPtr->ProxyAccounting.ReworkReplaceCount,
      HttpdGblSecPtr->ProxyAccounting.ReworkTooBig,
      ANSIceol,
      ANSIbold, ANSInormal,
      BytesRawRx,
      BytesRawTx,
      ANSIceol,
      ANSIbold, ANSInormal,
      AccountingPtr->LookupLiteralCount,
      AccountingPtr->LookupDnsNameCount,
      AccountingPtr->LookupCacheNameCount,
      AccountingPtr->LookupDnsNameErrorCount,
      ANSIceol);

   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,
        GeoLength,
        LineLength,
        LineLimit,
        StringLength;
   double  dValue;
   char  *bptr, *bpsptr, *cptr, *sptr, *tptr;
   char  BytesRawRx [32],
         BytesRawTx [32],
         BytesTxGzipPercent [8];

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

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

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

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

   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,
           HttpdGblSecPtr->Request.Duration);

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

   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] ? ")" : "");

   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 = "";
   }

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

   StringLength = 41 + 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 (%s)", cptr, bpsptr);

   sptr += sprintf (sptr,
"%s\r\n\
    %sHost:%s ",
           ANSIceol, ANSIbold, ANSInormal);
   LineLength = 10;

   cptr = HttpdGblSecPtr->Request.ClientHostName;

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

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

   /* check for IPv4 address */
   tptr = cptr;
   /* if an IPv6 compressed IPv4 address */
   if (!strncmp (tptr, "::FFFF:", 7)) tptr += 7;
   while (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 (*(ulong*)(tptr = cptr) == '::FF') tptr += 7;

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

   StringLength = strlen(cptr);
   StringLength += strlen (HttpdGblSecPtr->Request.ClientIpAddressString);
   /* excise some middle section if it would overflow the line */
   if (StringLength > 80-16)
   {
      while (*cptr && --StringLength > 80-16) cptr++;
      *sptr++ = '8';
      *sptr++ = '<';
   }
   while (*cptr) *sptr++ = *cptr++;
   LineLength += StringLength;

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

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

   LineLimit = ((PageLength - 21) * 80) - 14;

#ifdef HTTPDMON_GEOLOCATE

   if (!CliGeoLocate[0])
   {
      char  *cptr, *sptr, *zptr;
      if (cptr = GeoLocateLogicalName ("HTTPDMON_GEOLOCATE"))
      {
         zptr = (sptr = CliGeoLocate) + sizeof(CliGeoLocate)-1;
         while (*cptr && sptr < zptr) *sptr++ = *cptr++;
         *sptr = '\0';
      }
      else
         CliGeoLocate[0] = '*';
   }

   GeoLength = 0;
   if (CliGeoLocate[0] && CliGeoLocate[0] != '!')
   {
      cptr = HttpdGblSecPtr->Request.ClientIpAddressString;

      if (isalnum(CliGeoLocate[0]))
         cptr = GeoLocate (CliGeoLocate, cptr);
      else
      if (isalnum(CliGeoLocate[1]))
         cptr = GeoLocate (CliGeoLocate+1, cptr);
      else
         cptr = GeoLocate (NULL, cptr);

      if (*cptr && *cptr != '(' && *cptr != '[')
         cptr = GeoLocateData (cptr, 0);

      GeoLength = strlen(cptr);
   }

   if (GeoLength && GeoLength > 76 - LineLength)
   {
      sptr += sprintf (sptr, "%s\r\n          {%s}",
                       ANSIceol, cptr);
      LineLimit -= 80;
   }
   else
   if (GeoLength)
      sptr += sprintf (sptr, " {%s}", cptr);

#endif /* HTTPDMON_GEOLOCATE */

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

   /* limit request string to number of 80 char lines left minus field name */
   tptr = sptr + LineLimit;
   /* 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;
   char  *cptr;
   char  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 (!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';

      MinsAgo = (NowTime64 - istptr->UpdateTime64) / TIME64_ONE_MIN;
      if (MinsAgo > INSTANCE_STATUS_STALE_MINS)
         stale = true;
      else
      {
         stale = false;
         total++;
      }

      sprintf (LineBuffer,
"%s%-*s %4.4s %4.4s %5u %4.4s %10.10s %7.7s %4u %6u%s\r\n",
                   stale ? " -" : "  ",
                   maxlen, istptr->InstanceName, UpdateAgoBuf,
                   StartAgoBuf, istptr->StartupCount,
                   ExitAgoBuf, ExitStatusBuf,
                   istptr->HttpdVersion,
                   istptr->MinuteCount,
                   istptr->HourCount,
                   stale ? "-" : "");
      if (stale)
         for (cptr = LineBuffer + 2; *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.
*/

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

   int  status;
   ulong  SecsAgo;

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

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

   AgoBuf[0] = '\0';

   if (!*Time64Ptr) return (SS$_NORMAL);

   SecsAgo = (NowTime64 - *Time64Ptr) / TIME64_ONE_SEC;

   AgoBufDsc.dsc$a_pointer = AgoBuf;
   AgoBufDsc.dsc$w_length = 16;
 
   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");
}

/*****************************************************************************/
/*
Development only.
*/
#define TCPIP_LOOKUP_HOST_NAME_RETRY 3

int  AcpControl;

char* LookupHostName (ulong arg1)
{
   char  *sptr;
   if (AcpControl)
      sptr = LookupHostName2 (arg1);
   else
   {
      sptr = TcpIpLookup (NULL, (char*)arg1, NULL, NULL);
      if (*sptr == '[') return ("!");
   }
   return (sptr);
}

/*****************************************************************************/
/*
If |Name| is non-NULL lookup the IP address using the host name.
If |Address| is non-NULL lookup the host name using the address.
If the Ip4AddtrPtr and/or Ip6AddrPtr are non-NULL populate them.
Return either a pointer to the resolved host name or IP address string or an
error message between square brackets.
*/

char* TcpIpLookup
(
char *Name,
char *Address,
uchar *Ip4Addr,
uchar *Ip6Addr
)
{
#define CACHE_MAX 16

   static  aCacheIdx, nCacheIdx;
   static char  abuf [CACHE_MAX][256],
                ares [CACHE_MAX][256],
                nbuf [CACHE_MAX][256],
                nres [CACHE_MAX][256];

   int  idx, retry, retval;
   void  *addptr;
   struct sockaddr_in  addr4;
   struct sockaddr_in6  addr6;
   struct addrinfo  hints;
   struct addrinfo  *aiptr;

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

   if (Debug) fprintf (stdout, "TcpIpLookup() |%s|%s| %d %d\n",
                       Name, Address, Ip4Addr, Ip6Addr);

   if (Ip4Addr) memset (Ip4Addr, 0, 4);
   if (Ip6Addr) memset (Ip6Addr, 0, 16);

   if (Name)
   {
      for (idx = 0; idx < CACHE_MAX; idx++)
         if (!strcmp (nbuf[idx], Name))
            return (nres[idx]);
      idx = nCacheIdx++ % CACHE_MAX;
      snprintf (nbuf[idx], sizeof(nbuf[0]), "%s", Name);

      aiptr = NULL;
      memset (&hints, 0, sizeof(hints));
      hints.ai_flags |= AI_CANONNAME;
      retval = 0;
      for (retry = TCPIP_LOOKUP_HOST_NAME_RETRY; retry; retry--)
      {
         retval = getaddrinfo (Name, NULL, &hints, &aiptr);
         if (retval != EINTR && retval != EAI_AGAIN) break;
         sleep (1);
      }
      if (retval)
      {
         if (retval == EAI_NONAME)
            sprintf (nres[idx], "[unknown]");
         else
         if (retval == EAI_FAIL || retval == EAI_AGAIN)
            sprintf (nres[idx], "[failed]");
         else
            snprintf (nres[idx], sizeof(nres[0]),
                      "[%s]", gai_strerror(retval));
         return (nres[idx]);
      }
      else
      {
         if (aiptr->ai_family == AF_INET)
         {
            /* IPv4 */
            addptr = &((struct sockaddr_in *)aiptr->ai_addr)->sin_addr;
            if (Ip4Addr) memcpy (Ip4Addr, addptr, 4);
         }
         else
         {
            /* must be IPv6 */
            addptr = &((struct sockaddr_in6 *)aiptr->ai_addr)->sin6_addr;
            if (Ip6Addr) memcpy (Ip6Addr, addptr, 16);
         }

         if (!inet_ntop (aiptr->ai_family, addptr, nres[idx], sizeof(nres[0])))
            snprintf (nres[idx], sizeof(nres[0]), "[%s]", strerror(errno));
      }

      /* free the addrinfo */
      freeaddrinfo(aiptr);

      return (nres[idx]);
   }

   if (Address)
   {
      for (idx = 0; idx < CACHE_MAX; idx++)
         if (!strcmp (abuf[idx], Address))
            return (ares[idx]);
      idx = aCacheIdx++ % CACHE_MAX;
      strcpy (abuf[idx], Address);

      retval = 0;
      memset (&addr4, 0, sizeof(addr4));
      if (inet_pton (AF_INET, Address, &addr4.sin_addr) > 0)
      {
         /* MultiNet does not support BSD 4.4 AF_INET addresses */
#ifdef NO_SOCKADDR_LEN_4
         /* this kludge seems to work for both! */
         *(USHORTPTR)&addr4 = AF_INET;
#else
         addr4.sin_len = sizeof(struct sockaddr_in);
         addr4.sin_family = AF_INET;
#endif
         if (Ip4Addr)
            memcpy (Ip4Addr, &addr4.sin_addr, sizeof(addr4.sin_addr));
         for (retry = TCPIP_LOOKUP_HOST_NAME_RETRY; retry; retry--)
         {
            retval = getnameinfo ((struct sockaddr*)&addr4, sizeof(addr4),
                                  ares[idx], sizeof(ares[0]),
                                  NULL, 0, NI_NAMEREQD);
            if (retval != EINTR && retval != EAI_AGAIN) break;
            sleep (1);
         }
         if (retval)
         {
            if (retval == EAI_NONAME)
               sprintf (ares[idx], "[unknown]");
            else
            if (retval == EAI_FAIL || retval == EAI_AGAIN)
               sprintf (ares[idx], "[failed]");
            else
               snprintf (ares[idx], sizeof(ares[0]),
                         "[%s]", gai_strerror(retval));
         }
         return (ares[idx]);
      }
      else
      {
         memset (&addr6, 0, sizeof(addr6));
         if (inet_pton (AF_INET6, Address, &addr6.sin6_addr) > 0)
         {
            addr6.sin6_len = sizeof(struct sockaddr_in6);
            addr6.sin6_family = AF_INET6;
            if (Ip6Addr)
               memcpy (Ip6Addr, &addr6.sin6_addr, sizeof(addr6.sin6_addr));
            for (retry = TCPIP_LOOKUP_HOST_NAME_RETRY; retry; retry--)
            {
               retval = getnameinfo ((struct sockaddr*)&addr6, addr6.sin6_len,
                                     ares[idx], sizeof(ares[0]),
                                     NULL, 0, NI_NAMEREQD);
               if (retval != EINTR && retval != EAI_AGAIN) break;
               sleep (1);
            }
            if (retval)
            {
               if (retval == EAI_NONAME)
                  sprintf (ares[idx], "[unknown]");
               else
               if (retval == EAI_FAIL || retval == EAI_AGAIN)
                  sprintf (ares[idx], "[failed]");
               else
                  snprintf (ares[idx], sizeof(ares[0]),
                            "[%s]", gai_strerror(retval));
            }
         }
         else
            snprintf (ares[idx], sizeof(ares[0]), "[%s]", strerror(errno));
         return (ares[idx]);
      }
   }

   return ("[bugcheck]");
}

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

Returns /%X00000014 %SYSTEM-F-BADPARAM, bad parameter value/ when fed an IPv6
address string.

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* LookupHostName2 (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$C_HOSTENT_OFFSET 5
#define INETACP_FUNC$C_GETHOSTBYNAME 1
#define INETACP_FUNC$C_GETHOSTBYADDR 2

#define HOSTENT_SIZE 1024

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

   static $DESCRIPTOR (TcpIpDeviceDsc, "UCX$DEVICE");
   static unsigned char ControlSubFunction [4] =
      { INETACP_FUNC$C_GETHOSTBYADDR, INETACP$C_HOSTENT_OFFSET, 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;
   struct LookupCacheStruct  *lcptr;
   struct hostent  *heptr;

   /*********/
   /* 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++)
      {
         lcptr = &LookupCache[idx];
         lcptr->HostAddrDsc.dsc$b_class = DSC$K_CLASS_S;
         lcptr->HostAddrDsc.dsc$b_dtype = DSC$K_DTYPE_T;
         lcptr->HostEntDsc.dsc$b_class = DSC$K_CLASS_S;
         lcptr->HostEntDsc.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++)
      {
         lcptr = &LookupCache[idx];
         if (!MATCH8 (cptr, lcptr->IpAddrStr)) continue;
         if (strcmp (cptr, lcptr->IpAddrStr)) continue;
         if (Debug) fprintf (stdout, "|%s|->\n", lcptr->HostName);
         return (lcptr->HostName);
      }
      /* look for an unused or expired entry */
      for (idx = 0; idx < CACHE_MAX; idx++)
      {
         lcptr = &LookupCache[idx];
         if (!lcptr->IpAddrStr[0]) break;
         if (ThisTimeStamp >= lcptr->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);

      lcptr = &LookupCache[idx];

      lcptr->IpAddrStr[0] = lcptr->HostName[1] = '\0';

      if (strchr (cptr, ':'))
      {
         if (inet_pton (AF_INET6, cptr, &lcptr->Ip6Address) != 1)
         {
            *(ulong*)lcptr->HostName = '!\0\0\0';
            lcptr->ExpiresStamp = ThisTimeStamp + RETRY_SECONDS;
            return (lcptr->HostName);
         }
         if (Debug) fprintf (stdout, "IPv6\n");
      }
      else
      {
         if (inet_pton (AF_INET, cptr, &lcptr->Ip4Address) != 1)
         {
            *(ulong*)lcptr->HostName = '!\0\0\0';
            lcptr->ExpiresStamp = ThisTimeStamp + RETRY_SECONDS;
            return (lcptr->HostName);
         }
         if (Debug) fprintf (stdout, "IPv4\n");
      }

      lcptr->HostName[0] = '?';
      lcptr->ExpiresStamp = ThisTimeStamp + EXPIRE_SECONDS;

      zptr = (sptr = lcptr->IpAddrStr) + sizeof(lcptr->IpAddrStr)-1;
      while (*cptr && sptr < zptr) *sptr++ = *cptr++;
      *sptr = '\0';

      lcptr->HostAddrDsc.dsc$w_length = sptr - lcptr->IpAddrStr;
      lcptr->HostAddrDsc.dsc$a_pointer = lcptr->IpAddrStr;

      lcptr->HostEntPtr = calloc (1, HOSTENT_SIZE);
      if (!lcptr->HostEntPtr)
      {
         *(ulong*)lcptr->HostName = '!\0\0\0';
         return (lcptr->HostName);
      }
      lcptr->HostEntDsc.dsc$w_length = HOSTENT_SIZE;
      lcptr->HostEntDsc.dsc$a_pointer = lcptr->HostEntPtr;

      lcptr->RetryCount = RETRY_ATTEMPTS;
   }
   else
   {
      /**************/
      /* lookup AST */
      /**************/

      lcptr = &LookupCache[idx = arg1];

      status = lcptr->LookupIOsb.iosb$w_status;

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

      if (VMSok (status)) 
      {
         /************/
         /* resolved */
         /************/

         heptr = (struct hostent *)lcptr->HostEntPtr;
         heptr->h_name += (unsigned int)heptr;

         zptr = (sptr = lcptr->HostName) + sizeof(lcptr->HostName)-1;
         for (cptr = heptr->h_name; *cptr && sptr < zptr; *sptr++ = *cptr++);
         *sptr = '\0';

         if (Debug) fprintf (stdout, "|%s|\n", lcptr->HostName);
         if (!lcptr->HostName[0]) *(ulong*)lcptr->HostName = '!\0\0\0';

         free (lcptr->HostEntPtr);
         lcptr->HostEntPtr = NULL;

         return (lcptr->HostName);
      }

      if (status == SS$_ENDOFFILE || !lcptr->RetryCount)
      {
         sys$gettim (&ThisBinTime);
         ThisTimeStamp = decc$fix_time (&ThisBinTime);
         lcptr->ExpiresStamp = ThisTimeStamp + RETRY_SECONDS;
         *(ulong*)lcptr->HostName = '!\0\0\0';
         free (lcptr->HostEntPtr);
         lcptr->HostEntPtr = NULL;
         return (lcptr->HostName);
      }
      lcptr->RetryCount--;
   }

   status = sys$qio (0, LookupChannel,
                     IO$_ACPCONTROL | IO$M_EXTEND /* IPv6 */,
                     &lcptr->LookupIOsb,
                     LookupHostName, idx,
                     &ControlSubFunctionDsc,
                     &lcptr->HostAddrDsc,
                     &lcptr->HostEntDsc.dsc$w_length,
                     &lcptr->HostEntDsc, 0, 0);
   if (Debug) fprintf (stdout, "[%d] sys$qio() %%X%08.08X\n", idx, status);

   if (VMSnok(status)) lcptr->IpAddrStr[0] = '\0';

   return (lcptr->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 maximum 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  buf [35];
   static $DESCRIPTOR (DayDateTimeDsc, "!AZ, !%D");
   static $DESCRIPTOR (TimeStringDsc, buf);

   int  status,
        DayOfWeek;
   unsigned short  Length;

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

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

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

char* BytesPerString (unsigned int BytesPerSecond)

{
   static char  buf [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 >= 10000000000)
      sprintf (buf, "%.1fGB/s", (float)BytesPerSecond/1000000000.0);
   else
   if (BytesPerSecond >= 100000000)
      sprintf (buf, "%dMB/s", BytesPerSecond/1000000);
   else
   if (BytesPerSecond >= 1000000)
      sprintf (buf, "%.1fMB/s", (float)BytesPerSecond/1000000.0);
   else
   if (BytesPerSecond >= 100000)
      sprintf (buf, "%dkB/s", BytesPerSecond/1000);
   else
   if (BytesPerSecond >= 1000)
      sprintf (buf, "%.1fkB/s", (float)BytesPerSecond/1000.0);
   else
      sprintf (buf, "%dB/s", BytesPerSecond);

   return (buf);
}

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

void CommaNumber
(
int Bits,
void *vptr,
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)
      status = sys$fao (&Value64FaoDsc, &Length, &ScratchDsc, vptr);
   else
      status = sys$fao (&Value32FaoDsc, &Length, &ScratchDsc, *(ulong*)vptr);
   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);
}

/*****************************************************************************/
/*
Development purposes only.
*/

void TryIt (char* string)

{
   int  count;
   char  *cptr;

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

   if (strsame (string, "lookup=", 7))
   {
      for (count = 5; count; count--)
         if (cptr = LookupHostName (string + 7))
         {
            if (*cptr != '?') break;
            sleep (1);
         }
      fprintf (stdout, "%d |%s|\n", count, cptr);
   }
}

/*****************************************************************************/
/*
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, "/ACPCONTROL", 4))
      {
         /* development only */
         AcpControl = true;
         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, "/GEOLOCATE=", 4))
      {
#ifdef HTTPDMON_GEOLOCATE
         while (*aptr && *aptr != '=') aptr++;
         if (*aptr) aptr++;
         if (!*aptr)
            CliGeoLocate[0] = '*';
         else
         if (*aptr == '0')
            CliGeoLocate[0] = '!';
         else
         {
            zptr = (sptr = CliGeoLocate) + sizeof(CliGeoLocate)-1;
            while (*aptr && sptr < zptr) *sptr++ = *aptr++;
            *sptr = '\0';
         }
#else /* HTTPDMON_GEOLOCATE */
         fprintf (stdout, "%%%s-E-IVQUAL, not a compiled option\n \\%s\\\n",
                  Utility, aptr+1);
         exit (STS$K_ERROR | STS$M_INHIB_MSG);
#endif /* HTTPDMON_GEOLOCATE */
         continue;
      }
      if (strsame (aptr, "/NOGEOLOCATE", 6))
      {
         CliGeoLocate[0] = '!';
         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 (strsame (aptr, "/TRY=", 4))
      {
         /* development purposes only */
         sptr = aptr;
         while (*aptr && *aptr != '=') aptr++;
         if (*aptr) aptr++;
         TryIt (aptr);
         exit (SS$_NORMAL);
      }

      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\n\
/[NO]GEOLOCATION[=<string>] /HELP /INTERVAL=<integer> /[NO]LOOKUP /PLUCK\n\
/PRCNAM=prcnam /PORT=<integer> /[NO]PROCESS /[NO]PROXY /REFRESH=integer\n\
/[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);
}
 
/*****************************************************************************/

