/*****************************************************************************/
/*
                                  HTTPd.c

WASD VMS Hypertext Transfer Protocol daemon.


COPYRIGHT NOTICE
----------------
WASD VMS Web Services, Copyright (C) 1996-2021 Mark G.Daniel.
(prior to 01-JUL-1997, "HFRD VMS Hypertext Services")

See COPYRIGHT.H for detail.


PRIVILEGES REQUIRED
-------------------
These should be INSTALLed against the image.

ALTPRI   Allows the server account to raise it's prioity above 4 if enabled by
         the /PRIORITY= qualifier.

CMKRNL   Required for single use of $GRANTID system service in DCL.C module.
         This rights identifier is used to mark detached WASD script processes.

DETACH   (IMPERSONATE) Allows the server to impersonate specific accounts
         for scripting and startup purposes.

OPER     used for $BRKTHRU messaging

PRMGBL   Create the permanent global section used by the HTTPDMON utility.

PRMMBX   Used by the DCL scripting module to create permanent mailboxes

PSWAPM   Allows the server process to prevent itself from being swapped out if 
         enabled by the /[NO]SWAP qualifier.

SHMEM    Allow shared section memory between multiple processors
         (VAX only ,for access to the global section used by HTTPDMON).

SYSGBL   Create the system global section used by the HTTPDMON utility.

SYSLCK   Allows SYSTEM locks to be enqueued for sending commands to all
         server processes on a node/cluster (via /DO=/ALL or Admin Menu).

SYSPRV   Used for various purposes, including creating sockets within the
         privileged port range (1-1023, which includes port 80 of course),
         accessing configuration files (which can be protected from world
         access), enable AUTHORIZED write access to the file system, checking
         authentication details, amongst others.

SYSNAM   Used for EXECUTUVE logical names.

WORLD    Allows some functions to obtain information about and affect 
         processes (e.g. scripts) that do not belong to the server process.


QUALIFIERS  (implemented in the CLI.c module)
----------
/ACCEPT=<string>        comma separated list of accepted hosts/domains
/ALL[=<integer>]        /do= this to all server processes on node/cluster
/AUTHORIZE=[SSL|ALL]    authorization may only be used with "https:",
                        all paths must be authorized
/CGI_PREFIX=<string>    prefix to CGI-script variable (symbol) names
/CLUSTER[=<integer>]    /do= this to all server processes on node/cluster
/DBUG                   turn on all "if (Debug)" statements (if compiled DBUG)
/DEMO                   demonstration mode 
/DETACH=>string>        DCL procedure to be run as /USER=
/DO=<string>            command to be passed to server
/ENV=<integer>          do to all servers in this WASD environment
/FILBUF=<integer>       number of bytes in record buffer
/FORMAT=<string>        log record format
/GBLSEC=DELETE          delete the permanent global section (with /INSTANCE=)
/IDENT=<string>         VMS rights identifier name used for detached processes
/INSTANCES=<integer>|CPU|PASSIVE|CONFIG  maximum number of per-node servers
/NOINSTANCES            suppresses the server's desire to $CREPRC new instances
/[NO]LOG[=]             logging enabled/disabled, optional log file name
/[NO]MONITOR            monitor enabled/disabled
/[NO]NETWORK            explicitly enables/disables global network mode
/NETBUF=<integer>       number of bytes in network read buffer
/NOTE=<string>          annotate server process log with this string
/[NO]ODS5               explicit extended file specification (testing only)
/OUTPUT=<file-name>     <stdout> to file
/OUTBUF=<integer>       number of bytes in output buffer
/PERIOD=<integer>       log file period
/PERSONA[=ident]        enable PERSONA services for DCL scripting,
                        optional identifier name controlling persona access
/PERSONA[=AUTHORIZED]   enable persona only if request subject to authorization
/PERSONA[=RELAXED]      optional keyword to allow privileged accounts to script
/PERSONA[=RELAXED=AUTHORIZED] optional keyword to allow privileged accounts
                              to script if request subject to authorization
/PORT=<integer>         server IP port number (overriden by [service] config)
/PRIORITY=<integer>     process priority (0-15)
/[NO]PROFILE            allow/disallow SYSUAF-authenticated access control
/PROFILE=NORULE         SYSUAF profile withput auth rule (pre-8.1 behaviour)
/[NO]PROMISCUOUS[=pwd]  authenticates any user name for path authorization
                        (optional password required for authorization) 
/REJECT=<string>        comma separated list of rejected hosts/domains
/SCRIPT=AS=<string>     detached scripting using this persona
                        (keyword SUBPROCESS forces subprocess scripting)
/SERVICES=<string>      list of [host-name:]port providing HTTP service
/SOFTWAREID=<string>    overrides the server's software ID string
/SYSPRV                 normally operate with SYSPRV enabled (CAUTION!)
/[NO]SSL=               enables Secure Sockets Layer, sets protocol parameters
/[NO]SYSUAF[=ID,PROXY,RELAXED,SSL]
                        allow/disallow SYSUAF authentication, or
                        SYSUAF authentication is allowed by identifier,
                        proxy SYSUAF authentication is allowed,
                        SYSUAF authentication allowed with any current account,
                        SYSUAF authentication is only allowed with "https:"
/[NO]SWAP               allow/disallow process to be swapped out
/USER=<string>          set the username (account) the server executes under
/VALBLK[=16|64]         overrides value block size determination
/VERSION                simply display the server version
/[NO]ZERO               accounting zeroed on startup (default is non-zeroed)


VERSION HISTORY
---------------
See VERSION.H
*/
/*****************************************************************************/

#ifdef WASD_VMS_V7
#undef _VMS__V6__SOURCE
#define _VMS__V6__SOURCE
#undef __VMS_VER
#define __VMS_VER 70000000
#undef __CRTL_VER
#define __CRTL_VER 70000000
#endif

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

/* VMS related header files */
#include <afrdef.h>
#include <descrip.h>
#include <devdef.h>
#include <iodef.h>
#include <jpidef.h>
#include <libdef.h>
#include <libdtdef.h>
#include <libclidef.h>
#include <libicb.h>
#include <lnmdef.h>
#include <opcdef.h>
#include <prvdef.h>
#include <prcdef.h>
#include <psldef.h>
#include <secdef.h>
#include <ssdef.h>
#include <stsdef.h>
#include <syidef.h>
#include <uaidef.h>

/* application-related header files */
#include "wasd.h"
#include "sha1.h"
#include "base64.h"
#include "copyright.h"

#ifdef __ia64
#  define ELVEN
#endif
#ifdef __x86_64
#  define ELVEN
#endif

#ifdef ELVEN
/*** hmmm, elfdef.h preceding wasd.h results in:

    } LKIDEF;
......^
%CC-E-NOLINKAGE, In this declaration, "LKIDEF" has no linkage and has a prior
declaration in this scope at line number *** in file
WASD_ROOT:[SRC.HTTPD]WASD.H;1.
at line number 81 in module LKIDEF of text library
SYS$COMMON:[SYSLIB]SYS$STARLET_C.TLB;1

(HP C V7.1-011 on OpenVMS IA64 V8.3-1H1)
(VSI C V7.4-001 on OpenVMS IA64 V8.4-2L1)
***/
#include <elfdef.h>
#endif

/* a control-y turned into a warning */
#define SS_W_CONTROLY (SS$_CONTROLY & 0xfffffffe)

#define WASD_MODULE "HTTPD"

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

/*
   These are the required privileges of the executing HTTP server.
   The server ACCOUNT should only have TMPMBX and NETMBX (just for
   extra security, policy ... keep privileged accounts to a minimum).
   Script processes are created only with the process's current privileges,
   which are always maintained at TMPMBX and NETMBX.  If additional
   privileges are required for any particular purpose (e.g. binding to
   a privileged IP port) then they are enabled, the action performed,
   and then they are disabled again immediately.
*/
ulong  AltPriMask [2] = { PRV$M_ALTPRI, 0 },
       AveJoePrvMask [2] = { PRV$M_NETMBX | PRV$M_TMPMBX, 0 },
       CmKrnlMask [2] = { PRV$M_CMKRNL, 0 },
       CrePrcMask [2] = { PRV$M_DETACH | PRV$M_SYSPRV, 0 },
       DetachMask [2] = { PRV$M_DETACH, 0 },
       GblSecPrvMask [2] = { PRV$M_SYSPRV | PRV$M_SYSGBL |
                                 PRV$M_PRMGBL, 0 },
//       GrantIdMask [2] = { PRV$M_CMKRNL | PRV$M_WORLD, 0 },
       GrantIdMask [2] = { PRV$M_CMKRNL | PRV$M_WORLD | PRV$M_DIAGNOSE, 0 },
//       MailboxMask [2] = { PRV$M_WORLD | PRV$M_SYSPRV, 0 },
       MailboxMask [2] = { PRV$M_WORLD | PRV$M_SYSPRV | PRV$M_OPER, 0 },
       PrivAcctMask [2] = { PRV$M_SETPRV | PRV$M_SYSPRV, 0 },
       PswapmMask [2] = { PRV$M_PSWAPM, 0 },
       ResetPrvMask [2] = { 0xffffffff, 0xffffffff },
       SecurityMask [2] = { 0, 0x40 /* PRV$M_SECURITY */ },
       SysLckMask [2] = { PRV$M_SYSLCK, 0 },
       SysNamMask [2] = { PRV$M_SYSNAM, 0 },
       SysNamPrvMask [2] = { PRV$M_SYSNAM | PRV$M_SYSPRV, 0 },
       SysPrvMask [2] = { PRV$M_SYSPRV, 0 },
       WorldMask [2] = { PRV$M_WORLD, 0 };

/*
   Decided to be able to eliminate the debug statements from production
   executables completely.  This will eliminate some largely unused code
   from the images reducing the overall file size, but more importantly
   will eliminate the test and branch execution overhead each time a debug
   statement is encountered.  Do this by conditionally turning the integer
   Debug storage into a constant false value ... compiler optimization of
   an impossible-to-execute section of code does the rest!  Very little
   other code needs to be explicitly conditionally compiled.
*/
#ifdef DBUG
BOOL  Debug;
#else
#define Debug 0
#endif

BOOL  HttpdAlignFaultReport,
      HttpdNetworkMode,
      HttpdServerExecuting,
      HttpdServerStartup,
      HttpdTicking,
      MonitorEnabled,
      NaturalLanguageEnglish,
      NoSwapOut = true,
      OperateWithSysPrv;

int  EfnWait,
     EfnNoWait,
     ExitStatus,
     GblSectionCount,
     GblSectionPermCount,
     GblPageCount,
     GblPagePermCount,
     HttpdAlignFaultCount,
     HttpdDayOfWeek,
     HttpdTickSecond,
     ProcessPriority = 4,
     ServerPort = 80;

/* developer sanity check */
const int  HttpdSizeOfHttp2 = sizeof(HTTP2_STRUCT),
           HttpdSizeOfStream2 = sizeof(HTTP2_STREAM_STRUCT);

int64  HttpdTime64,
       HttpdStartTime64;

ushort  HttpdTime7 [7];

char  HttpdScriptAsUserName [64],
      HttpdServerNote [256],
      NaturalLanguage [64],
      ProcessIdentName [32],
      ServerPortString [8];

int  HttpdServerNoteSize = sizeof(HttpdServerNote);

/* zero index is used to indicate request is not in a list */
#define SUPERVISOR_LIST_MAX 10
SUPERVISOR_LIST  SupervisorListArray [SUPERVISOR_LIST_MAX+1] =
/* these numbers are staggered so they are not all scanned at any one time */
   { { {0,0,0}, 0, },
     { {0,0,0}, 1, },
     { {0,0,0}, 15, },
     { {0,0,0}, 50, },
     { {0,0,0}, 110, },
     { {0,0,0}, 290, },
     { {0,0,0}, 590, },
     { {0,0,0}, 1100, },
     { {0,0,0}, 3500, },
     { {0,0,0}, 7100, },
     { {0,0,0}, 999999999, } };

/*
Seconds at which the network transfer statistics are updated.
Up until v10.4.0 this was set at 17.
WASDMON requires per-second updates of network data.
Reducing it to 1 should not have a significant impact forr most environments.
It it does, well just bump it back up and recompile!
*/
#define SUPERVISOR_NETWORK_UPDATE 1

struct AnExitHandler  ExitHandler;

ACCOUNTING_STRUCT  *AccountingPtr;
BOOL  AccountingZeroOnStartup;

SYS_INFO  SysInfo;
HTTPD_PROCESS  HttpdProcess;

/* default storage in case the real global section cannot be/is not in use */
HTTPD_GBLSEC  HttpdGblSecDefault;
HTTPD_GBLSEC  *HttpdGblSecPtr;
int  HttpdGblSecPages;

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

extern BOOL  AuthorizationEnabled,
             AuthPromiscuous,
             CliDetach,
             CliDemo,
             CliGblSecDelete,
             CliGblSecNoPerm,
             CliInstanceConfig,
             CliLoggingDisabled,
             CliLoggingEnabled,
             CliMonitorDisabled,
             CliMonitorEnabled,
             CliNetworkMode,
             CliNoNetworkMode,
             CliInstanceNoCrePrc,
             CliOdsExtendedEnabled,
             CliOdsExtendedDisabled,
             CliSysPlus,
             CliTests,
             CliVersion,
             ControlDoAllHttpd,
             DclScriptDetachProcess,
             LoggingEnabled,
             OdsExtended,
             ProtocolHttpsAvailable,
             ProxyServingEnabled;

extern const int64  Delta60Sec,
                    Delta01Sec;

extern int  ActivityTotalMinutes,
            CliServerPort,
            CliServicesSize,
            CliLockValueBlockSize,
            HttpdGblSecVersion,
            NetConcurrentMax,
            InstanceEnvNumber,
            InstanceNumber,
            OpcomMessages,
            OpcomTarget,
            RequestHistoryMax,
            ServerHostPortSize;

extern char  BuildDateTime[],
             BuildInfo[],
             CliLogFileName[],
             CliParameter[],
             CliProcessIdentName[],
             CliScriptAs[],
             CliServices[],
             CliUserName[],
             CommandLine[],
             ControlBuffer[],
             ErrorSanityCheck[],
             HttpdIpPackage[],
             HttpdSesola[],
             HttpdVersion[],
             LoggingFileName[],
             Services[],
             ServerHostPort[],
             SoftwareID[],
             TcpIpAgentInfo[],
             TimeGmtString[],
             WatchFuncCc[];

extern char  *CliOutputPtr;

extern CONFIG_STRUCT  Config;
extern LIST_HEAD  Http2List;
extern LIST_HEAD  RequestList;
extern INSTANCE_STATUS  *InstanceStatusTablePtr;
extern META_CONFIG  *MetaGlobalConfigPtr;
extern META_CONFIG  *MetaGlobalServicePtr;
extern META_CONFIG  *MetaGlobalMsgPtr;
extern PROXY_ACCOUNTING_STRUCT  *ProxyAccountingPtr;
extern WATCH_STRUCT  Watch;

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

void main ()

{
   void  *close_enough_to_the_top = 0xDEFEC8ED;

   int  idx, status,
        GblSecDeleteCount;
   char  *cptr, *sptr, *zptr;
   $DESCRIPTOR (NaturalLanguageDsc, NaturalLanguage);
   ODS_STRUCT  WasdRootOds;

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

   /* initialize virtual memory management */
   VmInit ();

   /* generate version information */
   VersionInfo ();

   /* no global section (yet) gotta have the storage somewhere! */
   HttpdGblSecPtr = &HttpdGblSecDefault;
   HttpdGblSecPages = sizeof(HTTPD_GBLSEC) / 512;
   if (HttpdGblSecPages & 0x1ff) HttpdGblSecPages++;
   AccountingPtr = &HttpdGblSecPtr->Accounting;
   ProxyAccountingPtr = &HttpdGblSecPtr->ProxyAccounting;

   if (VMSnok (status = ParseCommandLine ()))
      exit (status);

#ifdef DBUG
   if (!Debug)
      if ((char*)getenv ("WASD_HTTPD_DBUG"))
         Debug = true;
#endif

   if (CliOutputPtr)
      if (!(stdout = freopen (CliOutputPtr, "w", stdout)))
         exit (vaxc$errno);

   if (Watch.CliEnabled) WatchCliSettings (false);

   if (!CliDemo && !InstanceEnvNumber)
   {
      /* if WASD_ENV defined then use this value in the absence of CLI value */
      if (cptr = SysTrnLnm (WASD_ENV))
         InstanceEnvNumber = atoi (cptr);
      else
         InstanceEnvNumber = INSTANCE_ENV_NUMBER_DEFAULT;
   }

   /* get required server system information */
   HttpdSystemInfo ();

   /* get required server process information */
   HttpdProcessInfo ();

   if (!HttpdProcess.PrivilegedAccount)
   {
      /*********************************************************/
      /* things we're not allowed to do without account SYSPRV */
      /*********************************************************/

      if (AuthPromiscuous)      exit (SS$_NOSYSPRV);
      if (CliDemo)              exit (SS$_NOSYSPRV);
      if (CliDetach)            exit (SS$_NOSYSPRV);
      if (CliGblSecDelete)      exit (SS$_NOSYSPRV);
      if (CliInstanceConfig)    exit (SS$_NOSYSPRV);
      if (CliParameter[0])      exit (SS$_NOSYSPRV);
      if (CliServices[0])       exit (SS$_NOSYSPRV);
      if (CliUserName[0])       exit (SS$_NOSYSPRV);
      if (ControlBuffer[0])     exit (SS$_NOSYSPRV);
      if (HttpdServerNote[0])   exit (SS$_NOSYSPRV);
   }

   if (CliTests)
   {
      /*********/
      /* /TEST */
      /*********/

#if WATCH_MOD
{
      if (strsame (CliParameter, "BASE64", 6))
      {
         base64_self_test (1);
         exit (SS$_NORMAL);
      }
      if (strsame (CliParameter, "DICTIONARY", 4))
      {
         DictTest (CliParameter+4);
         exit (SS$_NORMAL);
      }
      if (strsame (CliParameter, "INCONFIG", 8))
      {
         char  buf [16];
         for (cptr = CliParameter + 8; ISLWS(*cptr); cptr++);
         sys$gettim (&HttpdTime64);
         status = InConfigInit (cptr);
         if (VMSnok (status)) exit (status);
         fputs ("*** HIT RETURN TO EXIT ***\n", stdout);
         fgets (buf, sizeof(buf), stdin);
         exit (status);
      }
      if (strsame (CliParameter, "IP=CIDR=", 8))
      {
         /* n.n.n.n/n n.n.n.n */
         for (cptr = sptr = CliParameter+8; *sptr && *sptr != ' '; sptr++);
         if (*sptr) *sptr++ = '\0';
         fprintf (stdout, "%s %s %d\n",
                  cptr, sptr, NetRejectCIDR (sptr, cptr));
         exit (SS$_NORMAL);
      }
      if (strsame (CliParameter, "IP=RANGE=", 9))
      {
         /* n.n.n.n-n.n.n.n n.n.n.n */
         for (cptr = sptr = CliParameter+9; *sptr && *sptr != ' '; sptr++);
         if (*sptr) *sptr++ = '\0';
         fprintf (stdout, "%s %s %d\n",
                  cptr, sptr, NetRejectRange (sptr, cptr));
         exit (SS$_NORMAL);
      }
      if (strsame (CliParameter, "STRSAME", 7))
      {
         for (cptr = CliParameter + 7; ISLWS(*cptr); cptr++);
         for (sptr = cptr; *sptr && *sptr != ','; sptr++);
         if (*sptr) *sptr++ = '\0';
         for (zptr = sptr; *zptr && *zptr != ','; zptr++);
         if (*zptr) *zptr++ = '\0';
         strsame_test (cptr, sptr, atol(zptr));
         exit (SS$_NORMAL);
      }
      if (strsame (CliParameter, "STRZCPY", 7))
      {
         for (cptr = CliParameter + 7; ISLWS(*cptr); cptr++);
         strzcpy_test (atoi(cptr));
         exit (SS$_NORMAL);
      }
      if (strsame (CliParameter, "SYSLOG", 6))
      {
         sys$gettim (&HttpdTime64);
         sys$numtim (&HttpdTime7, &HttpdTime64);
         TimeSetHttpdUTC ();
         NetGetServerHostName ();
         for (cptr = CliParameter + 6; *cptr == ' '; cptr++);
         SysLogInit ();
         SysLogOpcom (cptr);
         exit (SS$_NORMAL);
      }
#if ODS_DIRECT
      if (strsame (CliParameter, "ODS=DIRECT=", 11) ||
          strsame (CliParameter, "ODS=SEARCH=", 11))
      {
         OdsDirectPerf (CliParameter+4);
         exit (SS$_NORMAL);
      }
#endif /* ODS_DIRECT */
      if (strsame (CliParameter, "MATCH", 5))
      {
         MatchPerf ();
         exit (SS$_NORMAL);
      }
      if (strsame (CliParameter, "MKCERT", 6))
      {
         cptr = SesolaMkCert ();
         FaoToStdout ("!AZ", cptr);
         exit (SS$_NORMAL);
      }
      if (strsame (CliParameter, "QUOTAS", 6))
      {
         uint64  cnt, b64, d64, e64;
         sys$gettim (&b64);
         for (cnt = 100000; cnt; cnt--) WatchServerQuotas(10);
         sys$gettim (&e64);
         d64 = e64 - b64;
         printf ("%llumS %lld/S\n", d64 / 10000,
                                    100000 / (d64 / 10000) * 1000);
         exit (SS$_NORMAL);
      }
      if (strsame (CliParameter, "SHA1", 4))
      {
         SHA1SelfTest ();
         exit (SS$_NORMAL);
      }
      if (strsame (CliParameter, "TCPIP=", 6))
      {
         IPADDRESS  IpAddress;
         status = TcpIpStringToAddress (CliParameter+6, &IpAddress);
         exit (status);
      }
      if (strsame (CliParameter, "32To64", 6))
      {
         uint  t32;
         uint64  t64;
         TimeSetHttpdUTC ();
         time (&t32);
         TimeUnix32ToVms64 (t32, &t64);
         FaoToStdout ("!%T | !%D | !AZ\n", &t64, &t64, ctime(&t32));
         exit (SS$_NORMAL);
      }
      if (strsame (CliParameter, "UTC", 3))
      {
         TimeTest ();
         exit (SS$_NORMAL);
      }
      if (strsame (CliParameter, "WEBDAV=DLM=", 11))
      {
         status = DavWebDlmTest (CliParameter+11);
         exit (status);
      }
      exit (SS$_ABORT); 
}
#else
      FaoToStdout ("%HTTPD-W-TESTS, NOT a compiled option\n");
#endif /* WATCH_MOD */
      exit (SS$_NORMAL); 
   }

   if (CliSysPlus)
   {
      /**********************/
      /* CLI system+ report */
      /**********************/

      sysPlusReport (NULL);
      exit (SS$_NORMAL);
   }

   if (CliDetach)
   {
      /****************************/
      /* create a detached server */
      /****************************/

      exit (HttpdDetachServerProcess ());
   }

   if (CliInstanceConfig)
   {
      /*****************************/ 
      /* ensure config values used */
      /*****************************/ 

      HttpdGblSecInit ();
      exit (InstanceUseConfig()); 
   }

   if (CliGblSecDelete)
   {
      /**************************/
      /* delete global sections */
      /**************************/

      /* permanent ones, that is */
      GblSecDeleteCount = 0;
      status = HttpdGblSecInit ();
      if (VMSok (status)) GblSecDeleteCount++;
      status = GraphActivityGblSecInit ();
      if (VMSok (status)) GblSecDeleteCount++;
      FaoToStdout ("%HTTPD-!AZ-GBLSEC, deleted !UL global sections\n",
                   GblSecDeleteCount ? "I" : "W", GblSecDeleteCount);
      exit (SS$_NORMAL); 
   }

   /* initialize the resources and locks for multi-instance processing */
   InstanceLockInit ();

   if (HttpdServerNote[0])
   {
      /***************************/
      /* annotate the server log */
      /***************************/

      status = ControlCommand (HttpdServerNote, !ControlBuffer[0]);
      /* can be used standalone or in conjunction with /DO=.. */
      if (!ControlBuffer[0] || VMSnok (status)) exit (status);
   }

   if (ControlBuffer[0])
   {
      /*****************************/
      /* control the HTTPd process */
      /*****************************/

      exit (ControlCommand (ControlBuffer, true));
   }

   /***************************/
   /* software and image info */
   /***************************/

   /* output the GNU GENERAL PUBLIC LICENSE message */
   FaoToStdout ("%HTTPD-I-SOFTWAREID, !AZ\n!AZ",
                SoftwareID, CopyRightMessageBrief);

   HttpdImageInfo ();
   FaoToStdout ("%HTTPD-I-IMAGE, !AZ\n", HttpdProcess.ImageInfo);

   if (CliVersion)
   {
      FaoToStdout ("-HTTPD-I-BUILD, !AZ, CC (!#AZ/!UL) !AZ\n",
                   BuildDateTime,
                   strchr(__VMS_VERSION, ' ') - __VMS_VERSION,
                   __VMS_VERSION, __DECC_VER,
                   WatchFuncCc);
      TcpIpSetAgentInfo ();
      FaoToStdout ("-HTTPD-I-SYSTEM, !AZ !UL CPU!%s !ULMB VMS !AZ\n",
                   SysInfo.HwName, SysInfo.AvailCpuCnt,
                   SysInfo.MemoryMB, SysInfo.Version);
      FaoToStdout ("-HTTPD-I-TCPIP, !AZ\n", TcpIpAgentInfo);
      FaoToStdout ("-HTTPD-I-SSL, !AZ\n",
                   *(cptr = SesolaVersion()) ? cptr : "none");

      status = SetGlobalSymbol ("HTTPD_VERSION", HTTPD_VERSION);
      if (VMSnok(status)) exit (status);
      exit (SS$_NORMAL); 
   }

   if (CliDemo)
   {
      /*************/
      /* demo mode */
      /*************/

      /* with 'instances' demonstration mode was becoming a little complex */
      CliLoggingEnabled = false;
      AuthPromiscuous = CliInstanceNoCrePrc =
         CliLoggingDisabled = CliGblSecNoPerm = true;
      InstanceEnvNumber = DEMO_INSTANCE_GROUP_NUMBER;
      if (!CliServices[0])
      {
         strzcpy (CliServices, "http://*:7080", CliServicesSize);
         if (ProtocolHttpsAvailable)
            strzcat (CliServices, ",https://*:7443", CliServicesSize);
      }
   }

   /****************/
   /* HTTPd server */
   /****************/

   /* failsafe for runaway startups (yup, happened with multi-instances) */
   if (SysTrnLnm (WASD_STARTUP_ABORT)) sys$delprc (0, 0);

   HttpdServerExecuting = HttpdServerStartup = true;

   /* initialize ticker */
   HttpdTick (0);

   FaoToStdout ("%HTTPD-I-STARTUP, !20%D\n", &HttpdStartTime64);

#if ALIGN_FAULT
   /* start collection alignment faults as early as possible */
   HttpdAlignFault ("START");
#endif /* ALIGN_FAULT */

   cptr = CONFIG_WASD_ROOT;
   OdsStructInit (&WasdRootOds, true);
   OdsParse (&WasdRootOds, cptr, strlen(cptr), NULL, 0,
             NAM$M_NOCONCEAL | NAM$M_SYNCHK, 0, 0);
   sptr = strstr (WasdRootOds.ExpFileName, ".][000000]");
   if (sptr) SET2(sptr,']\0');

   FaoToStdout ("%HTTPD-I-!#AZ, !AZ\n",
                strchr(cptr,':') - cptr, cptr, WasdRootOds.ExpFileName);

   FaoToStdout ("%HTTPD-I-ENVIRONMENT, !UL\n", InstanceEnvNumber);

   FaoToStdout ("%HTTPD-I-SYSTEM, !AZ VMS !AZ\n",
                SysInfo.HwName, SysInfo.Version);

   if (HttpdProcess.Grp <= SysInfo.MaxSysGroup)
      FaoToStdout (
"%HTTPD-W-SYSPRV, operating with implicit SYSPRV (UIC group !UL)\n",
                   HttpdProcess.Grp);

   /* get the TCP/IP agent information */
   TcpIpSetAgentInfo ();
   FaoToStdout ("%HTTPD-I-TCPIP, !AZ\n", TcpIpAgentInfo);

   switch (HttpdProcess.Mode)
   {
      case JPI$K_INTERACTIVE : HttpdProcess.ModeName = "INTERACTIVE"; break;
      case JPI$K_NETWORK     : HttpdProcess.ModeName = "NETWORK"; break;
      case JPI$K_OTHER       : HttpdProcess.ModeName = "OTHER"; break;
      case JPI$K_BATCH       : HttpdProcess.ModeName = "BATCH"; break;
      default : HttpdProcess.ModeName = "?";
   }
   FaoToStdout ("%HTTPD-I-MODE, !AZ\n", HttpdProcess.ModeName);
   if (HttpdProcess.Mode == JPI$K_NETWORK) HttpdNetworkMode = true;
   /* the server process' detached scripts can still have mode overridden */
   if (CliNetworkMode) HttpdNetworkMode = true;
   if (CliNoNetworkMode) HttpdNetworkMode = false;

   /* set the flag and report indicating whether ODS-5 is supported */
   OdsSetExtended ();
   /* (testing only) */
   if (CliOdsExtendedEnabled) OdsExtended = true;
   if (CliOdsExtendedDisabled) OdsExtended = false;

   /* ensure the GMT time/logical is available */
   TimeSetHttpdUTC ();
   FaoToStdout ("%HTTPD-I-GMT, !AZ\n", TimeGmtString);

   /* set up and declare the exit handler */
   ExitHandler.HandlerAddress = &HttpdExit;
   ExitHandler.ArgCount = 1;
   ExitHandler.ExitStatusPtr = &ExitStatus;
   if (VMSnok (status = sys$dclexh (&ExitHandler)))
      ErrorExitVmsStatus (status, "sys$dclexh()", FI_LI);
   HttpdOnControlY (false);

   /* join per-node and per-cluster instances (the earlier the better) */
   InstanceServerInit ();

   /* make sure the process' privileges are those of a mere mortal */
   if (VMSnok (status = sys$setprv (0, &ResetPrvMask, 0, 0)))
      ErrorExitVmsStatus (status, "sys$setprv()", FI_LI);
   if (VMSnok (status = sys$setprv (1, &AveJoePrvMask, 0, 0)))
      ErrorExitVmsStatus (status, "sys$setprv()", FI_LI);

   if (OperateWithSysPrv && OPERATE_WITH_SYSPRV)
   {
      /* looks like we're been asked to use Superman's Xray vision! */
      if (VMSnok (status = sys$setprv (1, &SysPrvMask, 0, 0)))
         ErrorExitVmsStatus (status, "sys$setprv()", FI_LI);

      /* OK, now reset the SYSPRV bit so it isn't manipulated */
      CrePrcMask[0] &= ~PRV$M_SYSPRV;
      GblSecPrvMask[0] &= ~PRV$M_SYSPRV;
      MailboxMask[0] &= ~PRV$M_SYSPRV;
      SysPrvMask[0] &= ~PRV$M_SYSPRV;

      FaoToStdout ("%HTTPD-I-SYSPRV, operating with SYSPRV\n");
   }
   else
   if (OperateWithSysPrv)
   {
      OperateWithSysPrv = false;
      FaoToStdout ("%HTTPD-W-SYSPRV, is NOT a compiled option\n");
   }

   if (ProcessPriority > 15) ProcessPriority = 15;
   if (VMSnok (status = sys$setprv (1, &AltPriMask, 0, 0)))
      ErrorExitVmsStatus (status, "sys$setprv()", FI_LI);
   if (VMSnok (status = sys$setpri (0, 0, ProcessPriority, 0, 0, 0)))
      ErrorExitVmsStatus (status, "sys$setpri()", FI_LI);
   if (VMSnok (status = sys$setprv (0, &AltPriMask, 0, 0)))
      ErrorExitVmsStatus (status, "sys$setprv()", FI_LI);

   /* anywhere near before starting logging! */
   NetGetServerHostName ();

   /* inline configuration */
   status = InConfigInit (NULL);
   if (VMSnok (status) && status != SS$_NOLOGNAM) exit (status);

   /* read the server configuration file */
   ConfigLoad (&MetaGlobalConfigPtr);

   /* a blast from the past */
   if (CliServerPort)
      ServerPort = CliServerPort;
   else
   if (Config.cfServer.DefaultPort)
      ServerPort = Config.cfServer.DefaultPort;
   if (ServerPort < 1 || ServerPort > 65535)
      ErrorExitVmsStatus (0, "IP port", FI_LI);
   sprintf (ServerPortString, "%d", ServerPort);

   /* initialize the GZIP module and ZLIB sharable image */
   GzipInit ();

   /* set/reset global section used by CLI control and the HTTPMON utility */
   if (!CliDemo) HttpdGblSecInit ();

   /* make any adjustments required after configuration load */
   InstanceFinalInit ();

   /* initialize Secure Sockets Layer, if available and if required */
   SesolaInit ();

   /* initialise (or not) HTTP/2 processing */
   Http2Init ();

   /* get the configured services */
   ServiceConfigLoad (&MetaGlobalServicePtr);

   /* set this instance's process name */
   InstanceProcessName ();

   {
      /* don't use WASD functions to write into the locked areas */
      $DESCRIPTOR (FaoDsc, "%HTTPD-I-STARTUP, !20%D, !AZ\0");
      $DESCRIPTOR (StringDsc, ""); 

      StringDsc.dsc$w_length = sizeof(HttpdGblSecPtr->StatusMessage)-1;
      StringDsc.dsc$a_pointer = HttpdGblSecPtr->StatusMessage;
      InstanceMutexLock (INSTANCE_MUTEX_HTTPD);
      sys$fao (&FaoDsc, 0, &StringDsc, 0, HttpdProcess.PrcNam);
      InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);
   }

   /* note the process name has to be set before OPCOM can be used */
   if (OpcomMessages)
   {
      FaoToOpcom ("%HTTPD-I-STARTUP, !AZ", SoftwareID);
      SysLogInit ();
   }

   /* update (reset) the request information */
   if (MonitorEnabled) RequestGblSecUpdate (NULL);

   /* now that we have the accounting data (in the global section) */
   if (AccountingZeroOnStartup) ControlZeroAccounting ();

   /* set up a rights identifier name (currently for detached scripting) */
   zptr = (sptr = ProcessIdentName) + sizeof(ProcessIdentName)-1;
   if (CliProcessIdentName[0])
      for (cptr = CliProcessIdentName; *cptr && sptr < zptr; *sptr++ = *cptr++);
   else
   {
      /* server-set rights identifiers begin with the following */
      for (cptr = PROCESS_RIGHTS_ID_PREFIX; *cptr; *sptr++ = *cptr++);
      for (cptr = HttpdProcess.PrcNam; *cptr && sptr < zptr; cptr++)
         if (isalnum(*cptr)) *sptr++ = to_upper(*cptr); else *sptr++ = '_';
   }
   *sptr = '\0';
   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER, "!&Z", ProcessIdentName);

   if (CliMonitorDisabled)
      MonitorEnabled = false;
   else
   if (Config.cfMisc.MonitorEnabled || CliMonitorEnabled)
      MonitorEnabled = true;
   else
      MonitorEnabled = false;

   /* initialize message database, check contents of some of those messages */
   MsgConfigLoad (&MetaGlobalMsgPtr);
   ErrorCheckReportFormats ();

   /* WebDAV initialisation */
   DavWebInit ();

   /* authentication/authorization configuration */
   AuthConfigInit ();

   /*
      Now that authentication (potentially using X509) has been configured
      the multi-instance SSL session cache (if required) can be initialized
      and appropriately adjust the record size upwards to accomodate sessions
      potentially including client certificate details.
   */
   SesolaCacheInit ();

   /* load the rule mapping database */
   MapUrl_Load ();

   /* initialize proxy processing */
   ProxyInit ();

   /* if required initialize the TCP/IP host name/address cache */
   if (Config.cfMisc.DnsLookupClient || ProxyServingEnabled)
      TcpIpHostCacheInit ();

   /* check for and if necessary enable the default scripting account */
   HttpdScriptAs ();

   /* initialize request virtual memory management */
   VmRequestInit (); 

   /* initialize DCL processing */
   DclInit ();

   /* initialize file cache  */
   CacheInit (true);

   /* initialize activity statistics, record server event */
   GraphActivityGblSecInit ();
   GraphActivityEvent (ACTIVITY_STARTUP);

   /* initialize the language environment */
   if (VMSok (status = lib$get_users_language (&NaturalLanguageDsc)))
   {
      for (cptr = NaturalLanguage; *cptr && !ISLWS(*cptr); cptr++);
      *cptr = '\0';
      if (strsame (NaturalLanguage, "ENGLISH", -1))
         NaturalLanguageEnglish = true;
      else
      {
         NaturalLanguageEnglish = false;
         FaoToStdout ("%HTTPD-I-LANGUAGE, natural language is !AZ\n",
                      NaturalLanguage);
      }
   }
   else
   {
      if (status == LIB$_ENGLUSED)
      {
         NaturalLanguageEnglish = true;
         strzcpy (NaturalLanguage, "ENGLISH", sizeof(NaturalLanguage));
         FaoToStdout (
            "%HTTPD-I-LANGUAGE, natural language has defaulted to !AZ\n",
            NaturalLanguage);
      }
      else
         ErrorExitVmsStatus (status, "lib$get_users_language()", FI_LI);
   }

   /* initialize request history mechanism (ensure it's reasonable!) */
   RequestHistoryMax = Config.cfMisc.RequestHistory;
   if (RequestHistoryMax > 999) RequestHistoryMax = 0;

   /* disable process swapping */
   if (NoSwapOut)
   {
      if (VMSnok (status = sys$setprv (1, &PswapmMask, 0, 0)))
         ErrorExitVmsStatus (status, "sys$setprv()", FI_LI);
      if (VMSnok (status = sys$setswm (1)))
         ErrorExitVmsStatus (status, "sys$setswm()", FI_LI);
      if (VMSnok (status = sys$setprv (0, &PswapmMask, 0, 0)))
         ErrorExitVmsStatus (status, "sys$setprv()", FI_LI);
   }

   /* need SYSPRV to bind to a well-known port */
   sys$setprv (1, &SysPrvMask, 0, 0);

   /* create network services */
   NetCreateService ();

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

   /* get the available BYTLM quota after all server sockets created */
   HttpdProcess.BytLmAvailable = GetJpiBytLm();

   /* initialize logging after service creation (in case of per-service logs) */
   if (VMSnok (status = Logging (NULL, LOGGING_BEGIN)))
      ErrorNoticed (NULL, status, NULL, FI_LI);

   /* ex-officio server host and port */
   if (cptr = SysTrnLnm (WASD_SERVER_HOST_PORT))
      strzcpy (ServerHostPort, cptr, ServerHostPortSize);

   InstanceMutexLock (INSTANCE_MUTEX_HTTPD);

   /* cancel any startup messages provided for the monitor */
   HttpdGblSecPtr->StatusMessage[0] = '\0';

   /*
      Perhaps a little bit too clever ...
      ALWAYS initialises the zeroeth (instances not enabled) element.
      If instances are NOT enabled then zeroes all the rest too.
      If instances ARE enabled then zeroes only the element belonging to it.
   */
   for (idx = 0;;)
   {
      AccountingPtr->CurrentInstanceConnected[HTTP12][idx] =
         AccountingPtr->CurrentInstanceConnected[HTTP1][idx] =
         AccountingPtr->CurrentInstanceConnected[HTTP2][idx] =
         AccountingPtr->CurrentInstanceProcessing[HTTP12][idx] =
         AccountingPtr->CurrentInstanceProcessing[HTTP1][idx] =
         AccountingPtr->CurrentInstanceProcessing[HTTP2][idx] = 0;

      AccountingPtr->CurrentDclScriptCgiPlus[idx] =
         AccountingPtr->CurrentDclScriptProcess[idx] =
         AccountingPtr->CurrentDclScriptRTE[idx] =
         AccountingPtr->CurrentDECnetTasks[idx] =
         AccountingPtr->CurrentDECnetCGI[idx] =
         AccountingPtr->CurrentDECnetOSU[idx] =
         AccountingPtr->CurrentPersistentHttp1[idx] =
         AccountingPtr->CurrentThrottleProcessing[idx] =
         AccountingPtr->CurrentThrottleQueued[idx] =
         AccountingPtr->CurrentWebSockets[idx] = 0;

      if (InstanceNumber)
         if (idx == InstanceNumber)
            break;
         else
            idx = InstanceNumber;
      else
      if (idx++ >= INSTANCE_MAX)
         break;
   }

   NetUpdateConnected (NULL, 0);
   NetUpdateProcessing (NULL, 0);

   AccountingPtr->InstanceNodeData[InstanceNumber].StartupCount++;
   AccountingPtr->InstanceNodeData[InstanceNumber].StartTime64 = HttpdStartTime64;

   AccountingPtr->StartupCount++;

   InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);

   if (CliDemo)
   {
      /*************/
      /* demo mode */
      /*************/

      FaoToStdout (
"%HTTPD-I-DEMO, demonstration mode\n\
1.i subprocess scripting\n\
2.i promiscuous authentication\n\
3.i directory access control files ignored\n\
4.i [DirAccess] enabled\n\
5.i [DirMetaInfo] enabled\n\
6.i [DirWildcard] enabled\n\
7.i [Logging] disabled\n\
8.i [ReportBasicOnly] disabled\n\
9.i [ReportMetaInfo] enabled\n");

      /*
         Subprocess scripting is forced in DCL.C
         Logging is disabled and promiscuous authentication enabled
         in the previous if(CliDemo) section.
      */
      Config.cfReport.BasicOnly = false;
      Config.cfReport.MetaInfoEnabled = true;
      Config.cfDir.Access = true;
      Config.cfDir.AccessSelective = false;
      Config.cfDir.MetaInfoEnabled = true;
      Config.cfDir.WildcardEnabled = true;
   }

   HttpdSysProtProcDmp ();

   InConfigStartup (false);

   sys$gettim (&HttpdTime64);
   HttpdTime64 -= HttpdStartTime64;
   FaoToStdout ("%HTTPD-I-DURATION, !%T\n", &HttpdTime64);
   sys$gettim (&HttpdTime64);
   FaoToStdout ("%HTTPD-I-BEGIN, !20%D, !AZ accepting requests\n",
                &HttpdTime64, HttpdProcess.PrcNam);

   HttpdSysOutDaily ();

   HttpdServerStartup = false;

   /*****************************/
   /* begin to process requests */
   /*****************************/

   /* ready to accept requests */
   InstanceReady ();

   /* if WATCHing of the startup was suppressed activate from here-on */
   if (Watch.CliEnabled) WatchCliSettings (true);

   /* queue accepts on all services (as an AST so no interruptions) */
   SysDclAst (NetAcceptBegin, 0);

   /*
      Just set and wait!
      Well that's the way it should be anyway.  BUT ...
      Whenever $GRANTID() is used in DclSysCommandAst() this $HIBER()
      (very) occasionally returns!!  Apparently $GRANTID works by queuing
      a kernel mode AST to the target process with setimr, wake, and cantimr
      involved.  This leaves windows for possible spurious wakes.
      Just keep track of these for interest' sake!
   */
   for (;;)
   {
      sys$hiber ();
      /* do not use a mutex here! an AST delivery could break the logic!! */
      AccountingPtr->SpuriousWakeCount++;
   }
   exit (SS$_BUGCHECK);
}

/*****************************************************************************/
/*
Determine whether a function has been called as an AST or directly in a code
path.  Three contexts are involved; 1) this function, 2) call to this function,
3) calling function.  Hence the two previous contexts.  If the third context is
from system space then it's (in the context of this WASD usage) an AST.

For 64 bit platforms use the LIB$ API.

For VAX use a solution cobbled together from fragments out there on the 'net.

For test see "/$/HttpdIsAstCall/" in RequestParseExecute().
*/

BOOL HttpdIsAstCall ()

{
#ifdef __ALPHA

   int  retval;
   struct invo_context_blk  icb;

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

   lib$get_curr_invo_context (&icb);
   retval = lib$get_prev_invo_context (&icb);
   if (retval) retval = lib$get_prev_invo_context (&icb);

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER,
                 "HttpdIsAstCall() Alpha !UL !UL !6XL !&B !8XL!8XL",
                 retval,
                 icb.libicb$l_context_length,
                 icb.libicb$v_fflags_bits,
                 icb.libicb$v_ast_frame,
                 (ulong*)icb.libicb$q_program_counter[1],
                 (ulong*)icb.libicb$q_program_counter[0]);

   if (retval)
      return ((ulong*)icb.libicb$q_program_counter[1] != 0);
   else
      return (false);

#endif /* __ALPHA */

#ifdef __ia64

   int  retval;
   struct invo_context_blk  icb;

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

   lib$i64_init_invo_context (&icb, LIBICB$K_INVO_CONTEXT_VERSION);
   lib$i64_get_curr_invo_context (&icb);
   retval = lib$i64_get_prev_invo_context (&icb);
   if (retval) retval = lib$i64_get_prev_invo_context (&icb);

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER,
                 "HttpdIsAstCall() ia64 !UL !UL !6XL !8XL!8XL",
                 retval,
                 icb.libicb$l_context_length,
                 icb.libicb$v_fflags_bits,
                 ((ulong*)&icb.libicb$ih_pc)[1],
                 ((ulong*)&icb.libicb$ih_pc)[0]);

   if (retval)
      return (((ulong*)&icb.libicb$ih_pc)[1] != 0);
   else
      return (false);

#endif /* __ia64 */

#ifdef __x86_64

   int  retval;
   struct invo_context_blk  icb;

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

   lib$x86_init_invo_context (&icb, LIBICB$K_INVO_CONTEXT_VERSION);
   lib$x86_get_curr_invo_context (&icb);
   retval = lib$x86_get_prev_invo_context (&icb);
   if (retval) retval = lib$x86_get_prev_invo_context (&icb);

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER,
                 "HttpdIsAstCall() ia64 !UL !UL !6XL !8XL!8XL",
                 retval,
                 icb.libicb$l_context_length,
                 icb.libicb$v_fflags_bits,
                 ((ulong*)&icb.libicb$ih_ip)[1],
                 ((ulong*)&icb.libicb$ih_ip)[0]);

   if (retval)
      return (((ulong*)&icb.libicb$ih_ip)[1] != 0);
   else
      return (false);

#endif /* __x86_64 */
}

/*****************************************************************************/
/*
Write the supplied text to the server process <stdout>.
*/

void HttpdNoteThis (char* note)

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

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER, "HttpdNoteThis() !AZ", note);

   if (!note || !note[0]) return;

   FaoToStdout ("%HTTPD-I-NOTE, !20%D, !AZ\n", 0, note);
}

/*****************************************************************************/
/*
Write the supplied text to the .
*/

void HttpdToOpcom (char* msg)

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

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER, "HttpdToOpcom() !AZ", msg);

   if (!msg || !msg[0]) return;

   FaoToOpcom ("!AZ", msg);
}

/*****************************************************************************/
/*
Equivalent of $ DEFINE /EXEC SYS$PROTECTED_PROCDMP SYS$LOGIN:
*/

void HttpdSysProtProcDmp ()

{
   static $DESCRIPTOR (LogNameDsc, "SYS$PROTECTED_PROCDMP");
   static $DESCRIPTOR (LogTableDsc, "LNM$PROCESS");
   static char SysErrLog [] =  "SYS$LOGIN:";
   static uchar  ExecMode = 1;  /* PSL$C_EXEC */
   static struct {
      short int  buf_len;
      short int  item;
      unsigned char   *buf_addr;
      unsigned short  *ret_len;
   } CreLnmItem [2];

   int  status;

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

   CreLnmItem[0].item = LNM$_STRING;
   CreLnmItem[0].buf_addr = SysErrLog;
   CreLnmItem[0].buf_len = sizeof(SysErrLog)-1;

   if (VMSnok (status = sys$setprv (1, &SysNamMask, 0, 0)))
      ErrorExitVmsStatus (status, "sys$setprv()", FI_LI);

   status = sys$crelnm (0, &LogTableDsc, &LogNameDsc, &ExecMode, &CreLnmItem);
   if (VMSnok(status)) exit (status);

   if (VMSnok (status = sys$setprv (0, &SysNamMask, 0, 0)))
      ErrorExitVmsStatus (status, "sys$setprv()", FI_LI);
}

/*****************************************************************************/
/*
https://forum.vmssoftware.com/viewtopic.php?f=12&t=9080&sid=da64db2007555318a99a5c65155a51c3#p21346
*/

#ifdef __x86_64

#define exe$gpq_swrpb EXE$GPQ_SWRPB
#pragma message save
#pragma message disable (mayhideloss)
#include <x86_cpuid_info.h>
#pragma message restore

/* +1 due buf[49] = '\0'; */
static char HttpdX86brand [49+1];

static int HttpdKrnlX86CpuInfo (long *arglist)
{
   int  status;
   status = x86_cpuid_get_brand_string (sizeof(HttpdX86brand), HttpdX86brand);
   return (status);
}

static char* HttpdX86CpuInfo ()

{
   int  status;
   status = sys$cmkrnl (&HttpdKrnlX86CpuInfo, 0);
   if (VMSnok (status)) sprintf (HttpdX86brand, "%%X%08.08X", status);
   return (HttpdX86brand);
}

#endif

/*****************************************************************************/
/*
Get required system information.
*/

HttpdSystemInfo ()

{
   static $DESCRIPTOR (DECnetDeviceDsc, "_NET:");
   static $DESCRIPTOR (NetStartupStatusDsc, "NET$STARTUP_STATUS");
   static $DESCRIPTOR (LnmSystemTableDsc, "LNM$SYSTEM_TABLE");

   static ushort  Length;
   static char  CsidNodeName [16],
                CsidVersion [9],
                DECnetScratch [32];

   static VMS_ITEM_LIST3
   DECnetLnmItem [] =
   {
      { sizeof(DECnetScratch), LNM$_STRING, NULL, 0 },
      { 0,0,0,0 }
   };

   static VMS_ITEM_LIST3
   SyiItem [] =
   {
     { sizeof(SysInfo.AvailCpuCnt), SYI$_AVAILCPU_CNT,
       &SysInfo.AvailCpuCnt, 0 },
     { sizeof(SysInfo.HwName)-1, SYI$_HW_NAME,
       &SysInfo.HwName, &SysInfo.HwNameLength },
     { sizeof(SysInfo.MaxSysGroup), SYI$_MAXSYSGROUP,
       &SysInfo.MaxSysGroup, 0 },
     { sizeof(SysInfo.MemSize)-1, SYI$_MEMSIZE, &SysInfo.MemSize, 0 },
     { sizeof(SysInfo.PageSize)-1, SYI$_PAGE_SIZE, &SysInfo.PageSize, 0 },
     { sizeof(SysInfo.NodeName)-1, SYI$_NODENAME,
       &SysInfo.NodeName, &SysInfo.NodeNameLength },
     { sizeof(SysInfo.Version)-1, SYI$_VERSION, &SysInfo.Version, 0 },
     { sizeof(SysInfo.BootTime64), SYI$_BOOTTIME, &SysInfo.BootTime64, 0 },
     { sizeof(SysInfo.ClusterMember), SYI$_CLUSTER_MEMBER,
       &SysInfo.ClusterMember, 0 },
     { 0,0,0,0 }
   };

#ifdef SYI$_CPUID
   static VMS_ITEM_LIST3
   SyiCpuIdItem [] =
   {
     { sizeof(SysInfo.CpuId)-1, SYI$_CPUID, &SysInfo.CpuIdLength, 0 },
     { 0,0,0,0 }
   };
#endif

   static VMS_ITEM_LIST3
   SyiCsidItem [] =
   {
     { sizeof(CsidNodeName)-1, SYI$_NODENAME, &CsidNodeName, 0 },
     { sizeof(CsidVersion)-1, SYI$_VERSION, &CsidVersion, 0 },
     { 0,0,0,0 }
   };

   int  status,
        CsidVersionInteger;
   ushort  DECnetChannel;
   ulong  CsidAdr;
   char  *cptr, *sptr;
   IO_SB  IOsb;

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

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER, "HttpdSystemInfo()");

   /* get system information */
   status = sys$getsyiw (EfnWait, 0, 0, &SyiItem, &IOsb, 0, 0);
   if (VMSok (status)) status = IOsb.Status;
   if (VMSnok (status)) ErrorExitVmsStatus (status, NULL, FI_LI);

   if (cptr = getenv("WASD_VMS_VERSION"))
      strncpy (SysInfo.Version, cptr, sizeof(SysInfo.Version));

   SysInfo.HwName[SysInfo.HwNameLength] = '\0';
   for (cptr = sptr = SysInfo.HwName; *cptr; *sptr++ = *cptr++)
      while (SAME2(cptr,'  ')) cptr++;
   *sptr = '\0';
   while (sptr > SysInfo.HwName && *(sptr-1) == ' ') sptr--;
   *sptr = '\0';

/* https://forum.vmssoftware.com/viewtopic.php?f=12&t=9080&start=10#p21392 */
#ifdef SYI$_CPUID
   /* get system information */
   status = sys$getsyiw (EfnWait, 0, 0, &SyiCpuIdItem, &IOsb, 0, 0);
   if (VMSok (status)) status = IOsb.Status;
   if (VMSok (status)) SysInfo.CpuId[SysInfo.CpuIdLength] = '\0';
#else
   status = SS$_BADPARAM;
#endif

#ifdef __x86_64
   *sptr++ = ' ';
   *sptr++ = '\"';
   if (VMSok (status))
      for (cptr = SysInfo.CpuId; *cptr; *sptr++ = *cptr++);
   else
      for (cptr = HttpdX86CpuInfo(); *cptr; *sptr++ = *cptr++);
   *sptr = '\0';
   while (sptr > SysInfo.HwName && *(sptr-1) == ' ') sptr--;
   *sptr++ = '\"';
   *sptr = '\0';
#else
   if (VMSok (status))
   {
      *sptr++ = ' ';
      *sptr++ = '\"';
      for (cptr = SysInfo.CpuId; *cptr; *sptr++ = *cptr++);
      *sptr = '\0';
      while (sptr > SysInfo.HwName && *(sptr-1) == ' ') sptr--;
      *sptr++ = '\"';
      *sptr = '\0';
   }
#endif

   SysInfo.NodeName[SysInfo.NodeNameLength] = '\0';
   SysInfo.Version[sizeof(SysInfo.Version)-1] = '\0';
   for (cptr = SysInfo.Version; *cptr && *cptr != ' '; cptr++);
   *cptr = '\0';

   if (isalpha(SysInfo.Version[0]) &&
       isdigit(SysInfo.Version[1]) &&
       SysInfo.Version[2] == '.' &&
       isdigit(SysInfo.Version[3]))
   {
      /* e.g. "V7.3" */
      SysInfo.VersionInteger = ((SysInfo.Version[1]-48) * 100) +
                               ((SysInfo.Version[3]-48) * 10);
      /* if something like "V7.3-2" */
      if (SysInfo.Version[4] == '-')
         if (isdigit(SysInfo.Version[5]))
            SysInfo.VersionInteger += SysInfo.Version[5]-48;
   }
   else
   {
      FaoToStdout (
"%HTTPD-E-VMS, cannot understand VMS version string \"!AZ\"\n\
-VMS-I-KLUDGE, continue by $ DEFINE /SYSTEM WASD_VMS_VERSION \"Vn.n\"\n",
                   SysInfo.Version);
      exit (SS$_BUGCHECK);
   }

   /* the number of pagelets supported in an architecture page */
   SysInfo.PageFactor = SysInfo.PageSize / 512;
   (uint)SysInfo.MemoryMB = (uint64)SysInfo.MemSize *
                            (uint64)SysInfo.PageFactor / 2048;
   if (SysInfo.MemoryMB > 4095)
      sprintf (SysInfo.MemoryMBGB, "%dGB", SysInfo.MemoryMB / 1024);
   else
      sprintf (SysInfo.MemoryMBGB, "%dMB", SysInfo.MemoryMB);

   if (SysInfo.PageFactor >= 16)
      /* q&d - try and cater for systems with up to 64GB memory */
      SysInfo.MemoryMB = SysInfo.MemSize * (SysInfo.PageFactor / 16) / 128;
   else
      SysInfo.MemoryMB = SysInfo.MemSize * SysInfo.PageFactor / 2048;

   /* versions earlier than 7.0 do not support EFN$C_ENF */
   if (SysInfo.VersionInteger >= 700)
      EfnWait = EfnNoWait = EFN$C_ENF;
   else
   {
      /* and require a unique event flag number */
      if (VMSnok (status = lib$get_ef (&EfnWait)))
         ErrorExitVmsStatus (status, "lib$get_ef()", FI_LI);
      if (VMSnok (status = lib$get_ef (&EfnNoWait)))
         ErrorExitVmsStatus (status, "lib$get_ef()", FI_LI);
   }

   /* establish whether DECnet is running and guess it's version */
   status = sys$assign (&DECnetDeviceDsc, &DECnetChannel, 0, 0);
   if (VMSok (status))
   {
      sys$dassgn (DECnetChannel);
      status = sys$trnlnm (0, &LnmSystemTableDsc, &NetStartupStatusDsc,
                           0, &DECnetLnmItem);
      if (VMSok (status))
         SysInfo.DECnetVersion = 5;
      else
         SysInfo.DECnetVersion = 4;
   }
   else
      SysInfo.DECnetVersion = 0;

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER,
"!&Z !&Z !&Z ver:!UL MB:!UL cpu:!UL DECnet:!UL EfnWait:!UL EfnNoWait:!UL",
                 SysInfo.NodeName, SysInfo.HwName, SysInfo.Version,
                 SysInfo.VersionInteger, SysInfo.MemoryMB,
                 SysInfo.AvailCpuCnt, SysInfo.DECnetVersion,
                 EfnWait, EfnNoWait);

   /* 16 bytes for VAX and pre-V8.2 Alpha and Itanium, otherwise 64 bytes */ 
   SysInfo.LockValueBlockSize = LOCK_VALUE_BLOCK_16;

   if (SysInfo.VersionInteger >= 820)
      SysInfo.LockValueBlockSize = LOCK_VALUE_BLOCK_64;

   /* search the cluster for anything not 64 bit value block compliant */
   CsidAdr = -1;
   for (;;)
   {
      memset (&CsidNodeName, 0, sizeof(CsidNodeName));
      memset (&CsidVersion, 0, sizeof(CsidVersion));

      /* get cluster system information */
      status = sys$getsyiw (EfnWait, &CsidAdr, 0, &SyiCsidItem, &IOsb, 0, 0);
      if (VMSok (status)) status = IOsb.Status;
      if (status == SS$_NOMORENODE) break;
      if (status == SS$_UNREACHABLE)
      {
         ErrorNoticed (NULL, status, "unexpected cluster response", FI_LI);
         SysInfo.LockValueBlockSize = LOCK_VALUE_BLOCK_16;
         continue;
      }
      if (VMSnok (status)) ErrorExitVmsStatus (status, NULL, FI_LI);

      for (cptr = CsidVersion; *cptr && *cptr != ' '; cptr++);
      *cptr = '\0';
      if (isalpha(CsidVersion[0]) &&
          isdigit(CsidVersion[1]) &&
          CsidVersion[2] == '.' &&
          isdigit(CsidVersion[3]))
      {
         /* e.g. "V7.3" */
         CsidVersionInteger = ((CsidVersion[1]-48) * 100) +
                               ((CsidVersion[3]-48) * 10);
         /* if something like "V7.3-2" */
         if (CsidVersion[4] == '-')
            if (isdigit(CsidVersion[5]))
               CsidVersionInteger += CsidVersion[5]-48;
      }
      else
         ErrorNoticed (NULL, SS$_BUGCHECK, "VMS version string", FI_LI);

      if (CsidVersionInteger < 820)
         SysInfo.LockValueBlockSize = LOCK_VALUE_BLOCK_16;

      if (WATCH_MODULE(WATCH_MOD__OTHER))
         WatchThis (WATCHALL, WATCH_MOD__OTHER, "!&Z !&Z !UL",
                    CsidNodeName, CsidVersion, CsidVersionInteger);
   }

   /* veto! (well sort-of, still SS$_XVALNOTVALID falls-back if necessary) */
   if (CliLockValueBlockSize == -1)
   {
      /* set to the default value for this platform */
      CliLockValueBlockSize == 0;
      if (SysInfo.VersionInteger >= 820)
         SysInfo.LockValueBlockSize = LOCK_VALUE_BLOCK_64;
      else
         SysInfo.LockValueBlockSize = LOCK_VALUE_BLOCK_16;
   }
   else
   if (CliLockValueBlockSize)
   {
      /* set to the command-line specified value */
      SysInfo.LockValueBlockSize = CliLockValueBlockSize;
   }

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER,
                 "lksb$b_valblk[!UL]", SysInfo.LockValueBlockSize);
}

/*****************************************************************************/
/*
Gets required current process' information/characteristics.  Translates the
server's SYS$OUTPUT process logical name, $DISPLAYs it to get the file name (if
any), checks it's a file, sets the name in storage.
*/

HttpdProcessInfo ()

{
   static ushort  Length;
   static char  LogValue [64];
   static $DESCRIPTOR (LnmJobDsc, "LNM$JOB");
   static $DESCRIPTOR (LnmProcessDsc, "LNM$PROCESS");
   static $DESCRIPTOR (SysOutputDsc, "SYS$OUTPUT");
   static $DESCRIPTOR (SysInputDsc, "SYS$INPUT");
   static $DESCRIPTOR (WASteeSysInputDsc, "WASTEE_SYS$INPUT");
   static $DESCRIPTOR (WASteeSysOutputDsc, "WASTEE_SYS$OUTPUT");

   static VMS_ITEM_LIST3
   JpiItems [] =
   {
     { sizeof(HttpdProcess.Pid), JPI$_PID, &HttpdProcess.Pid, 0 },
     { sizeof(HttpdProcess.Uic), JPI$_UIC, &HttpdProcess.Uic, 0 },
     { sizeof(HttpdProcess.Mode), JPI$_MODE, &HttpdProcess.Mode, 0 },
     { sizeof(HttpdProcess.AuthPriv), JPI$_AUTHPRIV,
       &HttpdProcess.AuthPriv, 0 },
     { sizeof(HttpdProcess.UserName)-1, JPI$_USERNAME,
       &HttpdProcess.UserName, 0 },
     { sizeof(HttpdProcess.PrcNam)-1, JPI$_PRCNAM,
       &HttpdProcess.PrcNam, &HttpdProcess.PrcNamLength },
     { sizeof(HttpdProcess.AstLm), JPI$_ASTLM, &HttpdProcess.AstLm, 0 },
     { sizeof(HttpdProcess.BioLm), JPI$_BIOLM, &HttpdProcess.BioLm, 0 },
     { sizeof(HttpdProcess.BytLm), JPI$_BYTLM, &HttpdProcess.BytLm, 0 },
     { sizeof(HttpdProcess.DioLm), JPI$_DIOLM, &HttpdProcess.DioLm, 0 },
     { sizeof(HttpdProcess.EnqLm), JPI$_ENQLM, &HttpdProcess.EnqLm, 0 },
     { sizeof(HttpdProcess.FilLm), JPI$_FILLM, &HttpdProcess.FilLm, 0 },
     { sizeof(HttpdProcess.Grp), JPI$_GRP, &HttpdProcess.Grp, 0 },
     { sizeof(HttpdProcess.PgFlQuo), JPI$_PGFLQUOTA, &HttpdProcess.PgFlQuo, 0 },
     { sizeof(HttpdProcess.PrcLm), JPI$_PRCLM, &HttpdProcess.PrcLm, 0 },
     { sizeof(HttpdProcess.TqLm), JPI$_TQLM, &HttpdProcess.TqLm, 0 },
     { 0,0,0,0 }
   },
   LnmItems [] =
   {
      { sizeof(LogValue)-1, LNM$_STRING, LogValue, &Length },
      { 0,0,0,0 }
   };

   int  status;
   char  *cptr;
   IO_SB  IOsb;
   struct FAB  ScratchFab;
   struct NAM  ScratchNam;

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

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER, "HttpdProcessInfo()");

   /* get some details of the account */
   status = sys$getjpiw (EfnWait, 0, 0, &JpiItems, &IOsb, 0, 0);
   if (VMSok (status)) status = IOsb.Status;
   if (VMSnok (status)) ErrorExitVmsStatus (status, NULL, FI_LI);

   HttpdProcess.UserName[sizeof(HttpdProcess.UserName)-1] = '\0';
   for (cptr = HttpdProcess.UserName; *cptr && *cptr != ' '; cptr++);
   *cptr = '\0';
   HttpdProcess.PrcNam[HttpdProcess.PrcNamLength] = '\0';

   if (HttpdProcess.AuthPriv[0] & PrivAcctMask[0])
      HttpdProcess.PrivilegedAccount = true;
   else
      HttpdProcess.PrivilegedAccount = false;

   status = sys$trnlnm (0, &LnmProcessDsc, &SysInputDsc, 0, &LnmItems);
   if (VMSnok (status)) ErrorExitVmsStatus (status, NULL, FI_LI);
   if (!SAME2(LogValue,0x001b))
   {
      HttpdProcess.SysInputFile = false;
      strzcpy (HttpdProcess.SysInput, "PPF?", sizeof(HttpdProcess.SysInput));
   }
   else
   {
      ScratchFab = cc$rms_fab;
      ScratchFab.fab$w_ifi = *(USHORTPTR)(LogValue+2) | FAB$M_PPF_IND;
      ScratchFab.fab$l_nam = &ScratchNam;
      ScratchNam = cc$rms_nam;
      ScratchNam.nam$l_rsa = HttpdProcess.SysInput;
      ScratchNam.nam$b_rss = sizeof(HttpdProcess.SysInput)-1;

      status = sys$display (&ScratchFab, 0, 0);
      if (VMSnok (status)) ErrorExitVmsStatus (status, "sys$display()", FI_LI);

      if (ScratchFab.fab$l_dev & DEV$M_TRM)
      {
         HttpdProcess.SysInputFile = false;
         strzcpy (HttpdProcess.SysInput, "TERMINAL",
                  sizeof(HttpdProcess.SysInput));
      }
      else
      {
         HttpdProcess.SysInputFile = true;
         HttpdProcess.SysInput[ScratchNam.nam$b_rsl] = '\0';
      }
   }

   status = sys$trnlnm (0, &LnmProcessDsc, &SysOutputDsc, 0, &LnmItems);
   if (VMSnok (status)) ErrorExitVmsStatus (status, NULL, FI_LI);
   if (!SAME2(LogValue,0x001b))
   {
      HttpdProcess.SysOutputFile = false;
      strzcpy (HttpdProcess.SysOutput, "PPF?", sizeof(HttpdProcess.SysOutput));
   }
   else
   {
      ScratchFab = cc$rms_fab;
      ScratchFab.fab$w_ifi = *(USHORTPTR)(LogValue+2) | FAB$M_PPF_IND;
      ScratchFab.fab$l_nam = &ScratchNam;
      ScratchNam = cc$rms_nam;
      ScratchNam.nam$l_rsa = HttpdProcess.SysOutput;
      ScratchNam.nam$b_rss = sizeof(HttpdProcess.SysOutput)-1;

      status = sys$display (&ScratchFab, 0, 0);
      if (VMSnok (status)) ErrorExitVmsStatus (status, "sys$display()", FI_LI);

      if (ScratchFab.fab$l_dev & DEV$M_TRM)
      {
         HttpdProcess.SysOutputFile = false;
         strzcpy (HttpdProcess.SysOutput, "TERMINAL",
                  sizeof(HttpdProcess.SysOutput));
      }
      else
      if (MATCH4(HttpdProcess.SysOutput,"_MPA") &&
          HttpdProcess.SysInput[0] == '_')
      {
         /* i.e. WASD_ROOT:[SRC.UTILS]WASTEE.C */
         HttpdProcess.SysOutputFile = false;
         strzcpy (HttpdProcess.SysOutput, "PIPE",
                  sizeof(HttpdProcess.SysOutput));
         HttpdProcess.SysInputFile = false;
         strzcpy (HttpdProcess.SysInput, "PIPE",
                  sizeof(HttpdProcess.SysInput));
      }
      else
      {
         HttpdProcess.SysOutputFile = true;
         HttpdProcess.SysOutput[ScratchNam.nam$b_rsl] = '\0';
      }
   }

   /* see WASD_ROOT:[SRC.UTILS]WASTEE.C and /PPF */
   status = sys$trnlnm (0, &LnmJobDsc, &WASteeSysInputDsc, 0, &LnmItems);
   if (VMSok (status))
   {
      HttpdProcess.SysInputFile = true;
      strzcpy (HttpdProcess.SysInput, LogValue, sizeof(HttpdProcess.SysInput));
      status = sys$trnlnm (0, &LnmJobDsc, &WASteeSysOutputDsc, 0, &LnmItems);
      if (VMSok (status))
      {
         HttpdProcess.SysOutputFile = true;
         strzcpy (HttpdProcess.SysOutput, LogValue,
                  sizeof(HttpdProcess.SysOutput));
      }
   }

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER,
"Mode:!UL Uic:!8XL UserName:!&Z PrcNam:!&Z ProcessId:!8XL Grp:!UL \
priv:<63-32>!8XL<31-00>!8XL !&B in:!&Z !&B out:!&Z !&B",
          HttpdProcess.Mode, HttpdProcess.Uic,
          HttpdProcess.UserName, HttpdProcess.PrcNam,
          HttpdProcess.Pid, HttpdProcess.Grp, HttpdProcess.AuthPriv[1],
          HttpdProcess.AuthPriv[0], HttpdProcess.PrivilegedAccount,
          HttpdProcess.SysInput, HttpdProcess.SysInputFile,
          HttpdProcess.SysOutput, HttpdProcess.SysOutputFile);
}

/****************************************************************************/
/*
Check if the /SCRIPT=AS=<username> has been specified, or if not the default
HTTP$NOBODY account, exists and is allowed to be used (not privileged or a
member of the SYSTEM group).  If it is then set the global storage
'HttpdScriptAsUserName' to that username, if not the set it to an invalid user
name to prevent default scripting.  The /SCRIPT=AS=SUBPROCESS keyword forces
subprocess scripting from the command line.
*/ 

int HttpdScriptAs ()

{
   static ulong  Context = -1;
   static ulong  UaiFlags,
                 UaiUic;
   static ulong  UaiPriv [2];

   static struct
   {
      ushort  buf_len;
      ushort  item;
      uchar   *buf_addr;
      ushort  *short_ret_len;
   }
   UaiItems [] =
   {
      { sizeof(UaiFlags), UAI$_FLAGS, &UaiFlags, 0 },
      { sizeof(UaiPriv), UAI$_PRIV, &UaiPriv, 0 },
      { sizeof(UaiUic), UAI$_UIC, &UaiUic, 0 },
      {0,0,0,0}
   };

   int  status;
   char  *uptr;
   static $DESCRIPTOR (UserNameDsc, "");

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

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER, "HttpdScriptAs()\n");

   /* try any supplied /SCRIPT=AS=, or default scripting account */
   if (CliScriptAs[0])
   {
      if (strsame (CliScriptAs, "SUBPROCESS", -1))
      {
         FaoToStdout ("%HTTPD-W-SCRIPTING, as server account !AZ\n",
                      HttpdProcess.UserName);
         return (SS$_NORMAL);
      }
      UserNameDsc.dsc$a_pointer = uptr = CliScriptAs;
   }
   else
      UserNameDsc.dsc$a_pointer = uptr = "HTTP$NOBODY";
   UserNameDsc.dsc$w_length = strlen(uptr);

   /*  preemptively disable default scripting with an illegal username */
   HttpdScriptAsUserName[0] = '!';
   strzcpy (HttpdScriptAsUserName+1, uptr, sizeof(HttpdScriptAsUserName)-1);

   /* turn on SYSPRV to allow access to SYSUAF records */
   sys$setprv (1, &SysPrvMask, 0, 0);
   status = sys$getuai (0, &Context, &UserNameDsc, &UaiItems, 0, 0, 0);
   sys$setprv (0, &SysPrvMask, 0, 0);

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER,
"sys$getuai() !&S uic:!8XL flags:!8XL priv:<63-32>!8XL<31-00>!8XL",
                 status, UaiUic, UaiFlags, UaiPriv[1], UaiPriv[0]);

   if (status == RMS$_RNF)
   {
      if (uptr == CliScriptAs)
      {
         /* command-line specified account MUST exist! */
         status = SS$_INVUSER;
         FaoToStdout ("%HTTPD-E-SCRIPTING, as !AZ disabled\n-!&M\n",
                      uptr, status);
         return (status);
      }

      /* the HTTP$NOBODY account doesn't exist, undo preemptive disable */
      HttpdScriptAsUserName[0] = '\0';
      FaoToStdout ("%HTTPD-W-SCRIPTING, as server account !AZ\n",
                   HttpdProcess.UserName);
      return (SS$_NORMAL);
   }

   if (VMSnok (status))
   {
      FaoToStdout ("%HTTPD-E-SCRIPTING, as !AZ disabled\n-!&M\n",
                   uptr, status);
      return (status);
   }

   if (UaiFlags & UAI$M_DISACNT)
   {
      FaoToStdout ("%HTTPD-E-SCRIPTING, as !AZ disabled - DISUSERED\n", uptr);
      return (SS$_INVUSER);
   }

   if ((UaiUic & 0xffff0000) >> 16 <= SysInfo.MaxSysGroup)
   {
      FaoToStdout ("%HTTPD-E-SCRIPTING, as !AZ disabled - SYSTEM GROUP\n",
                   uptr);
      return (SS$_INVUSER);
   }

   if (UaiPriv[0] & ~AveJoePrvMask[0] ||
       UaiPriv[1] & ~AveJoePrvMask[1])
   {
      /* something other than NETMBX and TMPMBX authorized */
      FaoToStdout ("%HTTPD-E-SCRIPTING, as !AZ disabled - PRIVILEGED\n", uptr);
      return (SS$_INVUSER); 
   }

   /* overwrite the preemptive disable */
   strzcpy (HttpdScriptAsUserName, uptr, sizeof(HttpdScriptAsUserName));
   if (strsame (HttpdScriptAsUserName, HttpdProcess.UserName, -1))
      FaoToStdout ("%HTTPD-W-SCRIPTING, as server account !AZ\n",
                   HttpdProcess.UserName);
   else
      FaoToStdout ("%HTTPD-I-SCRIPTING, as !AZ\n", HttpdScriptAsUserName);
   return (SS$_NORMAL); 
}

/*****************************************************************************/
/*
Create a detached server process.  If the /USER= qualifier was used the process
will  have changed it's persona and the process created here will be created
under that user name.  If 'CliParameter' is not supplied it looks for the
startup procedure from 'StartupProcedures' in succession.  If it can't find one
it exits with an error status.  To support startup with a WASD-specific logical
name table (v10.0) it has become necessary to parse the command and output
files to obtain the unconcealed devices.
*/

HttpdDetachServerProcess ()

{
   static char  *StartupProcedures[] =
   {
      "WASD_STARTUP:STARTUP_SERVER.COM",
      "WASD_ROOT:[STARTUP]STARTUP_SERVER.COM",
      "WASD_ROOT:[LOCAL]STARTUP_SERVER.COM",
      "HT_STARTUP:STARTUP_SERVER.COM",
      "HT_ROOT:[STARTUP]STARTUP_SERVER.COM",
      "HT_ROOT:[LOCAL]STARTUP_SERVER.COM",
      NULL
   };

   static struct
   {
      short  flag;
      char  data [1+UAF$S_USERNAME + 1+UAF$S_USERNAME +
                  1+UAF$S_PASSWORD + 1+UAF$S_ACCOUNT];
   } LgiData;
   static $DESCRIPTOR (LgiDataDsc,"");

   static ulong  CrePrcFlags = PRC$M_DETACH;
   static $DESCRIPTOR (LoginOutDsc, "SYS$SYSTEM:LOGINOUT.EXE");
   static char  PrcNam [16];
   static $DESCRIPTOR (PrcNamDsc, PrcNam);
   static $DESCRIPTOR (SysCommandDsc, "");
   static $DESCRIPTOR (SysOutputDsc, "");
   static ushort  Length;

   int  idx, status;
   uint  ProcessPid;
   ushort  slen;
   char  *cptr, *sptr, *zptr;
   char  LogFileName [256],
         SysCommand [256];
   ODS_STRUCT  SysCommandOds,
               SysOutputOds;

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

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER, "HttpdDetachServerProcess()");

   sys$gettim (&HttpdTime64);
   sys$numtim (&HttpdTime7, &HttpdTime64);

   if (CliParameter[0])
      cptr = CliParameter;
   else
   {
      /* look for one of several possible startup procedures */
      for (idx = 0; cptr = StartupProcedures[idx]; idx++)
      {
         if (VMSok (status = OdsFileExists (NULL, cptr))) break;
         if (status != RMS$_DEV && status != RMS$_DNF && status != RMS$_FNF)
            break;
      }
      if (VMSnok (status))
      {
         if (!cptr) cptr = "STARTUP_SERVER.COM";
         ErrorExitVmsStatus (status, cptr, FI_LI);
      }
   }

   OdsStructInit (&SysCommandOds, true);
   OdsParse (&SysCommandOds, cptr, strlen(cptr), NULL, 0,
             NAM$M_NOCONCEAL | NAM$M_SYNCHK, 0, 0);

   SysCommandDsc.dsc$a_pointer = SysCommandOds.ExpFileName;
   SysCommandDsc.dsc$w_length = SysCommandOds.ExpFileNameLength;

   FaoToBuffer (LogFileName, sizeof(LogFileName), &Length,
                "!AZ!AZ_!4ZL!2ZL!2ZL!2ZL!2ZL!2ZL.LOG",
                CONFIG_SERVER_LOGS,
                SysInfo.NodeName,
                HttpdTime7[0], HttpdTime7[1], HttpdTime7[2],
                HttpdTime7[3], HttpdTime7[4], HttpdTime7[5],
                HttpdTime7[6]);

   OdsStructInit (&SysOutputOds, true);
   OdsParse (&SysOutputOds, LogFileName, Length, NULL, 0,
             NAM$M_NOCONCEAL | NAM$M_SYNCHK, 0, 0);

   SysOutputDsc.dsc$a_pointer = SysOutputOds.ExpFileName;
   SysOutputDsc.dsc$w_length = SysOutputOds.ExpFileNameLength;

   if (InstanceEnvNumber > 1)
   {
      /* the starting HTTPd process will look for and parse this number */
      FaoToBuffer (PrcNam, sizeof(PrcNam), &Length,
                   "!ULWASD:starting", InstanceEnvNumber);
      PrcNam[PrcNamDsc.dsc$w_length = Length] = '\0';
   }
   else
   {
      PrcNamDsc.dsc$a_pointer = "WASD:starting";
      PrcNamDsc.dsc$w_length = 13;
   }

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchDataFormatted ("$CREPRC !UL !&Z !&Z !&Z !AZ !AZ !&B\n",
                          ProcessPriority,
                          LoginOutDsc.dsc$a_pointer,
                          SysCommandDsc.dsc$a_pointer,
                          SysOutputDsc.dsc$a_pointer,
                          CliUserName,
                          PrcNamDsc.dsc$a_pointer,
                          HttpdNetworkMode);

   if (CliNetworkMode ||
       HttpdNetworkMode)
   {
      /*********************************/
      /* create "network" mode process */
      /*********************************/
      
      if (CliUserName[0])
         cptr = CliUserName;
      else
         cptr = HttpdProcess.UserName;
      memset (&LgiData, 0, sizeof(LgiData));
      LgiData.flag = LGI$M_NET_PROXY;
      idx = 0;
      LgiData.data[idx] = strlen(cptr);
      memcpy (&LgiData.data[idx+1], cptr, LgiData.data[idx]);
      idx += LgiData.data[idx] + 1;
      LgiData.data[idx++] = 0;
      LgiData.data[idx++] = 0;
      LgiData.data[idx++] = 0;
      LgiDataDsc.dsc$a_pointer = &LgiData;
      LgiDataDsc.dsc$w_length = sizeof(LgiData.flag) + idx;
      CrePrcFlags = PRC$M_DETACH | PRC$M_NETWRK | PRC$M_NOPASSWORD;
      
      status = sys$creprc (&ProcessPid,
                           &LoginOutDsc,
                           /* composite SYS$INPUT and log file name */
                           &SysCommandDsc,
                           /* proxy login data structure */
                           &LgiDataDsc,
                           /* SYS$NET, which must be redirected to */
                           &SysOutputDsc, 0, 0,
                           &PrcNamDsc,
                           ProcessPriority, 0, 0,
                           CrePrcFlags, 0, 0);
   }
   else
   {
      /***********************/
      /* create "other" mode */
      /***********************/

      if (CliUserName[0])
      {
         /* needs to be done explicitly in case PERSONA_MACRO is in use */
         PersonaInit ();
         if (VMSnok (status = PersonaAssume (CliUserName)))
            exit (status);
      }

      status = sys$creprc (&ProcessPid,
                           &LoginOutDsc,
                           &SysCommandDsc,
                           &SysOutputDsc, 0, 0, 0,
                           &PrcNamDsc,
                           ProcessPriority, 0, 0,
                           CrePrcFlags, 0, 0);

      /* needs to be done explicitly in case PERSONA_MACRO is in use */
      if (CliUserName[0]) PersonaAssume (NULL);
   }

   if (VMSnok (status))
      ErrorExitVmsStatus (status, "sys$creprc()", FI_LI);

   FaoToStdout (
"%HTTPD-S-PROC_ID, identification of created process is !8XL\n",
      ProcessPid);

   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
Exit the HTTP server, via this declared exit handler.
Use vanilla output in case other routines are implicated.
*/

HttpdExit (ulong *ExitStatusPtr)

{
   static $DESCRIPTOR (ErrorFaoDsc, "%HTTPD-F-EXIT, !AZ, !AZ %X!8XL\r\n-!AZ\0");
   static $DESCRIPTOR (DateTimeFaoDsc, "!20%D\0");

   static char  DateTime [32];
   static $DESCRIPTOR (DateTimeDsc, DateTime);

   int  status;
   ushort  slen;
   ulong  ExitStatus;
   char  MsgString [256] = "?";
   $DESCRIPTOR (MsgStringDsc, MsgString); 
   $DESCRIPTOR (StringDsc, ""); 
   struct invo_context_blk icb;

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

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER,
                 "HttpdExit() !&S", *ExitStatusPtr);

   /* exit handler executes in non-AST context so disable */
   sys$setast (0);

   ExitStatus = *ExitStatusPtr;

   /* run down any script processes associated with script processing */
   DclExit ();

   /* any ephemeral configuration files disappear */
   InConfigClose ();

   /* any proxy verify records still in use are cleared */
   ProxyVerifyInit ();

   /* any buffered rejection notes flushed */
   NetRejectConnect (NULL, NULL);

   /* Q&D flush SYS$OUTPUT */
   fsync (STDOUT_FILENO);

   if (LoggingEnabled)
   {
      /* provide a server exit entry */
      Logging (NULL, LOGGING_END);
   }

   /* don't worry about locking the section with this last gasp */
   AccountingPtr->LastExitStatus = ExitStatus;
   AccountingPtr->LastExitPid = HttpdProcess.Pid;
   sys$gettim (&AccountingPtr->LastExitTime64);
   sys$fao (&DateTimeFaoDsc, 0, &DateTimeDsc, 0);

   status = sys$getmsg (ExitStatus, &slen, &MsgStringDsc, 15, 0);
   if (VMSok (status)) MsgString[slen] = '\0';

   if (VMSnok (ExitStatus))
   {
      /**************/
      /* error exit */
      /**************/

      StringDsc.dsc$w_length = sizeof(HttpdGblSecPtr->StatusMessage)-1;
      StringDsc.dsc$a_pointer = HttpdGblSecPtr->StatusMessage;

      sys$fao (&ErrorFaoDsc, 0, &StringDsc,
               DateTime, HttpdProcess.PrcNam, ExitStatus, MsgString+1);

      /* record server event */
      GraphActivityEvent (ACTIVITY_EXIT_ERROR);

      /* report to process log */
      fprintf (stdout, "%%HTTPD-F-EXIT, %s, %s %%X%08.08X\n",
               DateTime, HttpdProcess.PrcNam, ExitStatus);

      if (ExitStatus != SS_W_CONTROLY)
      {
         /* add traceback information */
         int  SanityCheck = 100;
#ifdef __ALPHA
         lib$get_curr_invo_context (&icb);
         while (lib$get_prev_invo_context (&icb),
                !icb.libicb$v_bottom_of_stack && SanityCheck--)
            fprintf (stdout, "-HTTPD-F-TRACE, %08.08X%08.08X\n",
                     icb.libicb$q_program_counter[1],
                     icb.libicb$q_program_counter[0]);
#endif
#ifdef __ia64
         lib$i64_init_invo_context (&icb, LIBICB$K_INVO_CONTEXT_VERSION);
   
         lib$i64_get_curr_invo_context (&icb);
            while (lib$i64_get_prev_invo_context (&icb),
                   !icb.libicb$v_bottom_of_stack && SanityCheck--)
            fprintf (stdout, "-HTTPD-F-TRACE, %08.08X%08.08X\n",
                     ((ULONGPTR)&icb.libicb$ih_pc)[1],
                     ((ULONGPTR)&icb.libicb$ih_pc)[0]);
#endif
#ifdef __x86_64
         lib$x86_init_invo_context (&icb, LIBICB$K_INVO_CONTEXT_VERSION);
   
         lib$x86_get_curr_invo_context (&icb);
            while (lib$x86_get_prev_invo_context (&icb),
                   !icb.libicb$v_bottom_of_stack && SanityCheck--)
            fprintf (stdout, "-HTTPD-F-TRACE, %08.08X%08.08X\n",
                     ((ULONGPTR)&icb.libicb$ih_ip)[1],
                     ((ULONGPTR)&icb.libicb$ih_ip)[0]);
#endif
         /* list current and history list requests */
         RequestDump ();
      }
   }

   if (VMSok (ExitStatus))
   {
      /***************/
      /* normal exit */
      /***************/

      /* record server exit event */
      GraphActivityEvent (ACTIVITY_EXIT);

      fprintf (stdout, "%%HTTPD-I-EXIT, %s, %s %%X%08.08X\n-%s\n",
               DateTime, HttpdProcess.PrcNam, ExitStatus, MsgString+1);
#if WATCH_MOD
      fputs (WatchServerQuotas(0), stdout);
      HttpdStackTrace ("exit()", FI_LI);
      RequestDump ();
#endif
   }

   if (OpcomMessages)
   {
      /*********/
      /* opcom */
      /*********/

      struct
      {
         ulong  TargetType;
         ulong  RequestId;
         char  MsgText [986+1];
      } OpcomMsg;
      $DESCRIPTOR (OpcomDsc, "");
      $DESCRIPTOR (OpcomFaoDsc, "Process !AZ reports\r\n"
                                "%HTTPD-!AZ-EXIT, !AZ, %X!8XL\r\n-!AZ");

      OpcomDsc.dsc$a_pointer = &OpcomMsg.MsgText;
      OpcomDsc.dsc$w_length = sizeof(OpcomMsg.MsgText)-1;
      status = sys$fao (&OpcomFaoDsc, &slen, &OpcomDsc,
                        HttpdProcess.PrcNam,
                        VMSnok(ExitStatus) ? "F" : "I",
                        DateTime, ExitStatus, MsgString+1);
      OpcomMsg.MsgText[slen] = '\0';
      /* errors noticed uses vanilla output (for the same reasons) */
      if (VMSnok (status)) FaoErrorNoticed (status, NULL, FI_LI);

      OpcomMsg.TargetType = OPC$_RQ_RQST + ((OpcomTarget & 0xffffff) << 8);
      OpcomMsg.RequestId = 0;

      OpcomDsc.dsc$a_pointer = &OpcomMsg;
      OpcomDsc.dsc$w_length = slen + 8;

      /* an error exit is ALWAYS reported via OPCOM */
      if (VMSnok (ExitStatus))
      {
         status = sys$sndopr (&OpcomDsc, 0);
         SysLogOpcom (OpcomMsg.MsgText);
      }
      else
      /* others will be reported is enabled */
      if (OpcomMessages & OPCOM_HTTPD)
      {
         status = sys$sndopr (&OpcomDsc, 0);
         SysLogOpcom (OpcomMsg.MsgText);
      }
      else
         status = SS$_NORMAL;
      /* errors noticed uses vanilla output (for the same reasons) */
      if (VMSnok (status)) FaoErrorNoticed (status, NULL, FI_LI);
   }

   InstanceStatusExit (ExitStatus);

   if (VMSnok (ExitStatus)) fputs ("-HTTPD-F-ADIEU, ...\n", stdout);

   fsync (STDOUT_FILENO);

   /* anything that needs to be explicitly done during instance shutdown */
   InstanceExit ();

   HttpdSysOutDaily ();

   /* reenable user mode ASTs */
   sys$setast (1);
}

/*****************************************************************************/
/*
Trace of call stack for debug purposes.  Include call in code to use :-)
*/

void HttpdStackTrace
(
char *title,
char *module,
int line
)
{
   static char  buf [32];
   static $DESCRIPTOR (TimeBufDsc, buf);
   static $DESCRIPTOR (TimeFaoDsc, "!8%T\0");

   int  status;
   struct invo_context_blk icb;

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

   sys$fao (&TimeFaoDsc, 0, &TimeBufDsc, 0);

   fprintf (stdout, "TRACE+++++++++++ %s %s:%d %s\n", title, module, line, buf);

   fflush (stdout);

#ifdef __ALPHA
   lib$get_curr_invo_context (&icb);
   while (lib$get_prev_invo_context (&icb) && !icb.libicb$v_bottom_of_stack)
      fprintf (stdout, "%08.08X%08.08X\n",
               icb.libicb$q_program_counter[1],
               icb.libicb$q_program_counter[0]);
#endif
#ifdef __ia64
   lib$i64_init_invo_context (&icb, LIBICB$K_INVO_CONTEXT_VERSION);
   lib$i64_get_curr_invo_context (&icb);
   while (lib$i64_get_prev_invo_context (&icb) && !icb.libicb$v_bottom_of_stack)
      fprintf (stdout, "%08.08X%08.08X\n",
               ((ULONGPTR)&icb.libicb$ih_pc)[1],
               ((ULONGPTR)&icb.libicb$ih_pc)[0]);
#endif
#ifdef __x86_64
   lib$x86_init_invo_context (&icb, LIBICB$K_INVO_CONTEXT_VERSION);
   lib$x86_get_curr_invo_context (&icb);
   while (lib$x86_get_prev_invo_context (&icb) && !icb.libicb$v_bottom_of_stack)
      fprintf (stdout, "%08.08X%08.08X\n",
               ((ULONGPTR)&icb.libicb$ih_ip)[1],
               ((ULONGPTR)&icb.libicb$ih_ip)[0]);
#endif

   fprintf (stdout, "+++++++++++TRACE\n");

   fflush (stdout);
}

/*****************************************************************************/
/*
Get the next stack context and if it appears to be a system memory address then
consider the stack "empty".  Return true for empty, false for not.
*/

BOOL HttpdStackEmpty ()

{
   int  status;
   struct invo_context_blk icb;

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

#ifdef __ALPHA
   lib$get_curr_invo_context (&icb);
   if (lib$get_prev_invo_context (&icb) && !icb.libicb$v_bottom_of_stack)
      if (lib$get_prev_invo_context (&icb) && !icb.libicb$v_bottom_of_stack)
         return (icb.libicb$q_program_counter[1] == 0xffffffff);
#endif
#ifdef __ia64
   lib$i64_init_invo_context (&icb, LIBICB$K_INVO_CONTEXT_VERSION);
   lib$i64_get_curr_invo_context (&icb);
   if (lib$i64_get_prev_invo_context (&icb) && !icb.libicb$v_bottom_of_stack)
      if (lib$i64_get_prev_invo_context (&icb) && !icb.libicb$v_bottom_of_stack)
         return (((ULONGPTR)&icb.libicb$ih_pc)[1] == 0xffffffff);
#endif
#ifdef __x86_64
   lib$x86_init_invo_context (&icb, LIBICB$K_INVO_CONTEXT_VERSION);
   lib$x86_get_curr_invo_context (&icb);
   if (lib$x86_get_prev_invo_context (&icb) && !icb.libicb$v_bottom_of_stack)
      if (lib$x86_get_prev_invo_context (&icb) && !icb.libicb$v_bottom_of_stack)
         return ((((ULONGPTR)&icb.libicb$ih_ip)[1] & 0xffff0000) == 0xffff0000);
#endif
   return (false);
}

/*****************************************************************************/
/*
Collect alignment fault statistics.

Intended to be used as a tool for the WASD developer to monitor alignment
faults (expensive on Alpha and VERY expensive on Itanium) and track down (and
perhaps remedy) the code sections generating those faults.

Data is collected into an array of PC addresses masked to aggregate those
addresses into groups (conserving the number of actual entries).  The PCs thus
generated allow the linker map and then associated compiler lists to be used to
"zero-in" on sections of code generating alignments faults.

Collection and reporting is enabled by default but can be modified using the
command-line /DO=ALIGN=START, /DO=ALIGN=STOP and /DO=ALIGN=ZERO.  The control
string also allows three data collection parameters to be set (which also
starts collection) /DO=ALIGN=<data-kB>[,<item-max>[,<PC-mask-hex>]].

When WASD is correctly coded there should be no alignment faults (or close to
none perhaps due to those lurking in uncommonly traversed code paths) so it
might be reassuring that an alignment fault report indicating none is really
working!  The /DO=ALIGN=FAULT=<integer> will generate alignment faults whenever
the data collection function is called (usually at least once per second). 
These will be reported in the HTTPD module (of course). Use /DO=ALIGN=FAULT=0
to disabled fault generation. 
*/

#if ALIGN_FAULT

void* HttpdAlignFault (char *ControlString)

{
#define ALIGN_BUFFER_SIZE 64  /* kByte */
#define ALIGN_PCS_MAX 128

   static int  GenerateFault,
               GetBufferSize,
               ItemArraySize,
               ItemCount,
               ItemMax,
               ItemOverflow,
               PCmask,
               ReportBufferSize,
               ReportOverflow,
               StartTickSecond;
   static uchar  *GetBufferPtr,
                         *ReportBufferPtr;
   static struct FaultDataStruct  ItemData;
   static struct FaultAccumStruct  *ItemArrayPtr;
                         
   int  idx, status;
   ulong  pc;
   ulong  GetBufferLength = 0;
   uchar  *afrptr, *zfrptr;
   char  *cptr;

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

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER,
                 "HttpdAlignFault() !&Z", ControlString);

   if (!ControlString)
   {
      /**********************/
      /* get and accumulate */
      /**********************/

      if (!HttpdAlignFaultReport) return (NULL);

      /* generate reassurement faults for quiet reports */
      for (idx = 0; idx < GenerateFault; idx++)
         ((ushort*)(GetBufferPtr+1))[idx]++;

      status = sys$get_align_fault_data (GetBufferPtr,
                                         GetBufferSize,
                                         &GetBufferLength);
      if (VMSnok (status))
         ErrorNoticed (NULL, status, "sys$get_align_fault_data()", FI_LI);

      if (WATCH_MODULE(WATCH_MOD__OTHER))
         WatchThis (WATCHALL, WATCH_MOD__OTHER, "!UL !UL !UL",
                    ReportBufferSize, GetBufferSize, GetBufferLength);

      /* (educated) guess as to whether the storage was exhausted */
      if (GetBufferLength >= GetBufferSize - AFR$K_USER_LENGTH)
         ReportOverflow++;

      zfrptr = (afrptr = GetBufferPtr) + GetBufferLength;
      while (afrptr < zfrptr)
      {
         HttpdAlignFaultCount++;

         pc = ((struct afrdef*)afrptr)->afr$l_fault_pc_l;
         pc = pc & PCmask;

         for (idx = 0; idx < ItemCount; idx++)
            if (ItemArrayPtr[idx].pc == pc) break;

         if (idx < ItemCount)
            ItemArrayPtr[idx].cnt++;
         else
         if (ItemCount < ItemMax)
         {
            ItemArrayPtr[ItemCount].pc = pc;
            ItemArrayPtr[ItemCount].cnt = 1;
            ItemCount++;
         }
         else
            ItemOverflow++;

         afrptr += AFR$C_USER_LENGTH;
      }

      /* populate the data structure to be returned */
      ItemData.ItemArray = ItemArrayPtr;
      ItemData.ItemCount = ItemCount;
      ItemData.ItemMax = ItemMax;
      ItemData.ItemOverflow = ItemOverflow;
      ItemData.GetBufferSize = GetBufferSize;
      ItemData.PCmask = PCmask;
      ItemData.ReportBufferSize = ReportBufferSize;
      ItemData.ReportOverflow = ReportOverflow;
      ItemData.StartTickSecond = StartTickSecond;

      return (&ItemData);
   }


   cptr = ControlString;

   if (strsame (cptr, "START", -1) ||
       isdigit(*cptr))
   {
      /********************/
      /* start collecting */
      /********************/

      if (HttpdAlignFaultReport) return (NULL);

      GenerateFault = HttpdAlignFaultCount = ItemCount =
         ItemMax = ItemOverflow = ReportOverflow = 0;
      StartTickSecond = HttpdTickSecond;

      if (isdigit(*cptr)) ReportBufferSize = atoi(cptr);
      if (ReportBufferSize < ALIGN_BUFFER_SIZE)
         ReportBufferSize = ALIGN_BUFFER_SIZE;
      ReportBufferSize *= 1024;

      while (*cptr && isdigit(*cptr)) cptr++;
      while (*cptr && !isdigit(*cptr)) cptr++;
      ItemMax = atoi(cptr);
      if (ItemMax < ALIGN_PCS_MAX) ItemMax = ALIGN_PCS_MAX;

      /* two hex digits (256 addresses, 8 bits) of mask can be specified */
      while (*cptr && isdigit(*cptr)) cptr++;
      while (*cptr && !isxdigit(*cptr)) cptr++;
      if (*cptr)
      {
         PCmask = strtol(cptr,NULL,16);
         PCmask = (PCmask & 0xff) | 0xffffff00;
      }
      else
//         PCmask = 0xfffffff0;
         PCmask = 0xffffffff;

      ReportBufferPtr = VmGet (ReportBufferSize);

      ItemArraySize = sizeof(struct FaultAccumStruct) * ItemMax;
      ItemArrayPtr = (struct FaultAccumStruct*) VmGet (ItemArraySize);

      GetBufferSize = ReportBufferSize;
      GetBufferPtr = VmGet (GetBufferSize);

      status = sys$start_align_fault_report (AFR$C_BUFFERED,
                                             ReportBufferPtr,
                                             ReportBufferSize);
      if (VMSok (status))
      {
         HttpdAlignFaultReport = true;
         FaoToStdout ("%HTTPD-I-ALIGN, start collecting \
alignment faults (!ULkB,!UL,0x!8XL)\n",
                      ReportBufferSize/1024, ItemMax, PCmask);
      }
      else
      {
         VmFree (GetBufferPtr, FI_LI);
         VmFree (ItemArrayPtr, FI_LI);
         VmFree (ReportBufferPtr, FI_LI);
         GetBufferSize = HttpdAlignFaultCount =
            ItemArraySize = ReportBufferSize = 0;
         GetBufferPtr = ItemArrayPtr = ReportBufferPtr = NULL;
         ErrorNoticed (NULL, status,
                       "sys$start_align_fault_report()", FI_LI);
      }
   }
   else
   if (strsame (cptr, "STOP", -1))
   {
      /*******************/
      /* stop collecting */
      /*******************/

      if (!HttpdAlignFaultReport) return (NULL);

      status = sys$stop_align_fault_report ();
      if (VMSok (status))
      {
         HttpdAlignFaultReport = false;
         VmFree (GetBufferPtr, FI_LI);
         VmFree (ItemArrayPtr, FI_LI);
         VmFree (ReportBufferPtr, FI_LI);
         GetBufferSize = HttpdAlignFaultCount = 
            ItemArraySize = ReportBufferSize = 0;
         GetBufferPtr = ItemArrayPtr = ReportBufferPtr = NULL;
         FaoToStdout ("%HTTPD-I-ALIGN, stop collecting alignment faults\n");
      }
      else
         ErrorNoticed (NULL, status, "sys$stop_align_fault_report()", FI_LI);
   }
   else
   if (strsame (cptr, "ZERO", -1))
   {
      /*******************/
      /* zero statistics */
      /*******************/

      if (!HttpdAlignFaultReport) return (NULL);

      HttpdAlignFaultCount = ItemCount = ItemOverflow = ReportOverflow = 0;
      StartTickSecond = HttpdTickSecond;
      memset (ItemArrayPtr, 0, ItemArraySize);
   }
   else
   if (strsame (cptr, "FAULT=", 6))
   {
      /*******************/
      /* generate faults */
      /*******************/

      GenerateFault = atoi(cptr+6);
   }

   return (NULL);
}

#endif /* ALIGN_FAULT */

/*****************************************************************************/
/*
Elementary bubble sort with PC ascending.
Just makes the alignment fault PC listing a little more intuitive.
*/

#if ALIGN_FAULT

HttpdAlignSort
(
struct FaultAccumStruct *ItemArrayPtr,
int ItemCount
)
{
   int  cnt, idx1, idx2;
   ulong  pc;

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

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER, "HttpdAlignSort()");

   for (idx1 = 0; idx1 < ItemCount; idx1++)
   {
      for (idx2 = idx1+1; idx2 < ItemCount; idx2++)
      {
         if (ItemArrayPtr[idx1].pc < ItemArrayPtr[idx2].pc) continue;
         pc = ItemArrayPtr[idx1].pc;
         cnt = ItemArrayPtr[idx1].cnt;
         ItemArrayPtr[idx1].pc = ItemArrayPtr[idx2].pc;
         ItemArrayPtr[idx1].cnt = ItemArrayPtr[idx2].cnt;
         ItemArrayPtr[idx2].pc = pc;
         ItemArrayPtr[idx2].cnt = cnt;
      }
   }
}

#endif /* ALIGN_FAULT */

/*****************************************************************************/
/*
Report on the statistics gathered by HttpAlignFault().
If logical name WASD_ALIGN_MAP points to a linker map the also provides the
code module containing the PC and its offset.
*/ 

#if ALIGN_FAULT

HttpdAlignReport (REQUEST_STRUCT *rqptr)

{
   static char  BeginTableFao [] =
"<p><table class=\"ctgry\">\n\
<tr><td>\n\
<table class=\"rghtrght\">\n";

   static char  HeadFao [] =
"!AZ<th></th><th><u>PC</u></th>\
<th><u>Count</u></th>";

   static char  ItemFao [] =
"!AZ<th>!UL.</th><td><tt>!AZ</tt></td><td>!UL</td>";

   static char  ItemTotalFao [] =
"<tr><th colspan=\"2\">Total:</th><td>!UL</td></tr>\n\
<tr><th colspan=\"2\">Per-Second:</th><td>!UL</td></tr>\n";

   static char  EndTableFao [] =
"</table>\n\
</td></tr>\n\
</table>\n";

   static char  EndPageFao [] =
"<p>PC mask:&nbsp; 0x!8XL\n\
<br>PCs:&nbsp; !UL max&nbsp; (overflow:!UL)\n\
<br>Buffer:&nbsp; !ULkB&nbsp; (overflow:!UL)\n\
</body>\n\
</html>\n";

   static struct FaultDataStruct  ItemData;

   int  cnt, idx1, idx2,
        ColStep,
        ItemCount,
        NumbCols;
   uint  SecondsElapsed;
   struct FaultDataStruct  *ItemDataPtr;
   struct FaultAccumStruct  *ItemArrayPtr = NULL;

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

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER, "HttpdAlignReport()");

   ItemDataPtr = HttpdAlignFault (NULL);

   AdminPageTitle (rqptr, "Alignment Fault Report");

   if (ItemDataPtr)
   {
      ItemArrayPtr = ItemDataPtr->ItemArray;
      ItemCount = ItemDataPtr->ItemCount;
      HttpdAlignSort (ItemArrayPtr, ItemCount);

      if (ItemCount < 15)
         NumbCols = 1;
      else
      if (ItemCount < 80)
         NumbCols = 3;
      else
      if (ItemCount < 160)
         NumbCols = 4;
      else
         NumbCols = 5;
      ColStep = ItemCount / NumbCols;
      if (ItemCount % NumbCols) ColStep++;

      FaoToNet (rqptr, BeginTableFao);
      if (HttpdAlignFaultCount)
      {
         FaoToNet (rqptr, "<tr>");
         for (cnt = 0; cnt < NumbCols; cnt++)
            FaoToNet (rqptr, HeadFao, cnt ? "<th></th>" : "");
         FaoToNet (rqptr, "</tr>\n");
      }
      for (idx1 = 0; idx1 * NumbCols < ItemCount; idx1++)
      {
         FaoToNet (rqptr, "<tr>");
         for (cnt = 0; cnt < NumbCols; cnt++)
         {
            idx2 = idx1 + (ColStep * cnt);
            if (idx2 >= ItemCount) break;
            FaoToNet (rqptr, ItemFao,
                      cnt ? "<td></td>" : "",
                      idx2+1,
                      HttpdAlignModule(ItemArrayPtr[idx2].pc),
                      ItemArrayPtr[idx2].cnt);
         }
         FaoToNet (rqptr, "</tr>\n");
      }
      SecondsElapsed = HttpdTickSecond - ItemDataPtr->StartTickSecond;
      if (SecondsElapsed)
         FaoToNet (rqptr, ItemTotalFao,
                   HttpdAlignFaultCount,
                   HttpdAlignFaultCount / SecondsElapsed);
      FaoToNet (rqptr, EndTableFao);

      /* reset the module function */
      HttpdAlignModule (0);
   }
   else
      ItemDataPtr = &ItemData;

   FaoToNet (rqptr, EndPageFao,
             ItemDataPtr->PCmask,
             ItemDataPtr->ItemMax, ItemDataPtr->ItemOverflow,
             ItemDataPtr->ReportBufferSize/1024, ItemDataPtr->ReportOverflow);

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

   AdminEnd (rqptr);
}

#endif /* ALIGN_FAULT */

/*****************************************************************************/
/*
Return a pointer to a static buffer with either the PC parameter or if the file
WASD_ALIGN_MAP (logical name) exists the PC with the module associated with
that PC and the offset into that module.  Must be called with PC zero to free
allocated resources.
*/

#if ALIGN_FAULT

char* HttpdAlignModule (ulong pc)

{
   static BOOL  LoadMap = true;
   static char  *CodePtr;
   static char  PcData [64];
   static ODS_STRUCT  MapFileOds;

   int  status;
   ulong  end, start;
   char  *cptr, *sptr, *zptr;
   char  ModuleName [32];

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

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER,
                 "HttpdAlignModule() !8XL", pc);

   if (!pc)
   {
      if (MapFileOds.DataLinePtr) VmFree (MapFileOds.DataLinePtr, FI_LI);
      memset (&MapFileOds, 0, sizeof(ODS_STRUCT));
      CodePtr = NULL;
      LoadMap = true;
      return (NULL);
   }

   FaoToBuffer (PcData, sizeof(PcData), 0, "!8XL", pc);

   if (LoadMap)
   {
      LoadMap = false;
      status = OdsLoadTextFile (&MapFileOds,
                                ProtocolHttpsAvailable ?
                                "WASD_ROOT:[SRC.HTTPD]HTTPD_SSL.MAP" :
                                "WASD_ROOT:[SRC.HTTPD]HTTPD.MAP");
      if (VMSnok(status))
         status = OdsLoadTextFile (&MapFileOds, "WASD_ALIGN_MAP");
      if (VMSnok (status)) return (PcData);
      if (CodePtr = strstr (MapFileOds.DataPtr, "$CODE$"))
         if (cptr = strstr (CodePtr, "$BSS$"))
            *cptr = '\0';
   }

   if (!CodePtr) return (PcData);

   start = end = 0;
   ModuleName[0] = '\0';
   cptr = CodePtr;
   while (*cptr)
   {
      start = end = 0;
      while (*cptr && *cptr != '\n') cptr++;
      if (!*cptr) break;
      cptr++;
      if (!isspace(*cptr) && !strncmp (cptr, "$CODE$", 6)) continue;
      while (*cptr && isspace(*cptr) && *cptr != '\n') cptr++;
      if (*cptr == '\n') continue;
      ModuleName[0] = '\0';
      zptr = (sptr = ModuleName) + sizeof(ModuleName)-1;      
      while (*cptr && !isspace(*cptr) && *cptr != '\n' && sptr < zptr)
         *sptr++ = *cptr++;
      *sptr = '\0';
      while (*cptr && !isspace(*cptr) && *cptr != '\n') cptr++;
      while (*cptr && isspace(*cptr) && *cptr != '\n') cptr++;
      if (!isxdigit(*cptr)) continue;
      start = strtol(cptr,NULL,16);
      while (*cptr && !isspace(*cptr) && *cptr != '\n') cptr++;
      while (*cptr && isspace(*cptr) && *cptr != '\n') cptr++;
      if (!isxdigit(*cptr)) continue;
      end = strtol(cptr,NULL,16);
      if (pc >= start && pc <= end) break;
   }

   if (start && end)
      FaoToBuffer (PcData, sizeof(PcData), 0,
                   "!8XL<br>!AZ+!8XL", pc, ModuleName, pc-start);

   return (PcData);
}

#endif /* ALIGN_FAULT */

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

int HttpdOnControlY (BOOL ControlY)

{
   static BOOL  Disabled = false;
   static ulong  Mask = LIB$M_CLI_CTRLY,
                 OldMask;
   static ushort  TTChannel = 0;
   static IO_SB  IOsb;

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

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

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER,
                 "HttpdOnControlY() !&B", ControlY);

   if (ControlY) exit (SS_W_CONTROLY);

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

   status = sys$qiow (EfnWait, TTChannel, IO$_SETMODE | IO$M_CTRLYAST, &IOsb,
                      0, 0, &HttpdOnControlY, true, PSL$C_USER, 0, 0, 0);
   if (VMSok (status)) status = IOsb.Status;
   if (VMSnok (status)) return (status);

   status = sys$qiow (EfnWait, TTChannel, IO$_SETMODE | IO$M_CTRLCAST, &IOsb,
                      0, 0, &HttpdOnControlY, true, PSL$C_USER, 0, 0, 0);
   if (VMSok (status)) status = IOsb.Status;
   if (VMSnok (status)) return (status);

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

/*****************************************************************************/
/*
Server exit if more than expected privileges.
*/

void HttpdCheckPriv
(
char *SourceModuleName,
int SourceLineNumber
)
{
   ulong  *pmptr;
   char  Buffer [128];

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

   if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL))
      WatchThis (WATCHALL, WATCH_MOD__OTHER, "HttpdCheckPriv()");

   if (!(pmptr = HttpdExpectedPriv())) return;

   HttpdStackTrace ("HttpdCheckPriv()", FI_LI);
   FaoToBuffer (Buffer, sizeof(Buffer), 0,
                "privilege sanity check: <63-32>!8XL<31-00>!8XL",
                pmptr[0], pmptr[0]);
   ErrorExitVmsStatus (SS$_BUGCHECK, Buffer,
                       SourceModuleName, SourceLineNumber);
}

/*****************************************************************************/
/*
As the code gets more complex it's becoming increasingly possible a coding or
design error will leave privileges turned on somewhere.  To help detect any
such problem ensure everthing's off that's supposed to be off.
*/

ulong* HttpdExpectedPriv ()

{
   static ulong  CurrPriv [2];

   int  status;

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

   if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL))
      WatchThis (WATCHALL, WATCH_MOD__OTHER, "HttpdExpectedPriv()");

   if (VMSnok (status = sys$setprv (1, &AveJoePrvMask, 0, &CurrPriv)))
      ErrorExitVmsStatus (status, "sys$setprv()", FI_LI);

   if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL))
      WatchDataFormatted ("!&X !&X\n", CurrPriv[1], CurrPriv[0]);

   /* if only NETMBX and TMPMBX enabled then ok */
   if (!(CurrPriv[0] & ~AveJoePrvMask[0] ||
         CurrPriv[1] & ~AveJoePrvMask[1])) return (NULL);

#if OPERATE_WITH_SYSPRV
   /* if operating with SYSPRV and it's enabled then ok */
   if (OperateWithSysPrv &&
       (CurrPriv[0] & ~AveJoePrvMask[0] == SysPrvMask[0])) return (NULL);
#endif

   return (CurrPriv);
}

/*****************************************************************************/
/*
This function calls itself (via a timer AST) every second or every sixty
seconds.  It updates the 'HttpdTickSecond' global storage by using system time
as a baseline (which works around any latency or cluster transition issues,
etc.)  This counts upwards across year boundaries.  It then calls the various
"activity supervisors" within the server.  If any of these has requests or
tasks still to supervise, or other periodic activities to perform,  they do it
and return a true which indicates the per-second ticker should continue to
tick.  Part of the duty of this function is to update the global storage
'HttpdTickSecond' which indicates the timeline progress of the server as well
as is used by various sections to set expiry points (marked by these global
"seconds").  Per-second ticking is initiated by any one of a small number of
code points calling this function with a zero value parameter upon the first
request processed in an otherwise quiescent server ('HttpdTicking' is false).
*/

HttpdTick (long reqidt)

{
   static ushort  PrevDay = -1,
                  PrevHour = -1,
                  PrevMinute = -1;

   int  status;
   char  *cptr;

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

   if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL))
      WatchThis (WATCHALL, WATCH_MOD__OTHER, "HttpdTick() !&X", reqidt);

   sys$gettim (&HttpdTime64);
   sys$numtim (&HttpdTime7, &HttpdTime64);
   HttpdTickSecond = HttpdGetTickSecond();

   if (!HttpdStartTime64)
   {
      /* initialise */
      sys$gettim (&HttpdStartTime64);
      TimeSetHttpdUTC ();
   }

   if (PrevDay != HttpdTime7[2])
   {
      PrevDay = HttpdTime7[2];
      lib$day_of_week (&HttpdTime64, &HttpdDayOfWeek);
   }

   if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL))
      WatchThis (WATCHALL, WATCH_MOD__OTHER,
                 "TICK !UL !%D", HttpdTickSecond, &HttpdTime64);

   if (Watch.SecondsToGo) WatchTick ();

   if (HttpdServerStartup)
      HttpdTicking = true;
   else
   {
      InstanceStatusUpdate (NULL);

#if ALIGN_FAULT
      /* (if active) accumulate alignment fault data */
      if (HttpdAlignFaultReport) HttpdAlignFault (NULL);
#endif /* ALIGN_FAULT */

      HttpdTicking = false;
      if (HttpdSupervisor ()) HttpdTicking = true;
      if (NetAcceptSupervisor ()) HttpdTicking = true;
      if (Http2Supervisor ()) HttpdTicking = true;
      if (InstanceSupervisor ()) HttpdTicking = true;
      if (DclSupervisor (-1)) HttpdTicking = true;
      if (DECnetSupervisor ()) HttpdTicking = true;

      if (PrevMinute != HttpdTime7[4])
      {
         /****************/
         /* every minute */
         /****************/

         if (Watch.LogFileOut) fsync (fileno(Watch.LogFileOut));

         SesolaNetIoPerMinute ();

         if (PrevMinute >= 0)
         {
            /* after the first minute */
            TimeSetHttpdUTC ();
            VmCheckPgFlLimit ();
            sysPlusRmi ((REQUEST_STRUCT*)-1);
            sysPlusNet ((REQUEST_STRUCT*)-1);
#if !WATCH_MOD
            if (SysInfo.ClusterMember)
#endif
            {
               sysPlusScs ((REQUEST_STRUCT*)-1);
               sysPlusMscp ((REQUEST_STRUCT*)-1);
            }
#if WATCH_MOD
            NetTestSupervisor ();
#endif
            ProxyTunnelLogicalName (NULL);
            ThrottleMonitorReset ();
            TcpIpHostCacheSupervisor (HttpdTickSecond);
            Logging (NULL, LOGGING_FLUSH);
            NetRejectPurgeStatus (-1);
            SysLogInit ();
            HttpdSysOutDaily ();
         }
         PrevMinute = HttpdTime7[4];

         if (PrevHour != HttpdTime7[3])
         {
            /**************/
            /* every hour */
            /**************/

            if (PrevHour >= 0)
            {
               /* after the first hour */
               Logging (NULL, LOGGING_TIMESTAMP);
               Logging (NULL, LOGGING_SHUT);
            }
            PrevHour = HttpdTime7[3];
         }
      }
   }

   if (HttpdTicking)
   {
      if (reqidt != &Delta01Sec) sys$cantim (&Delta01Sec, 0);
      status = sys$setimr (0, &Delta01Sec, &HttpdTick, &Delta01Sec, 0);
   }
   else
   {
      if (reqidt != &Delta60Sec) sys$cantim (&Delta60Sec, 0);
      status = sys$setimr (0, &Delta60Sec, &HttpdTick, &Delta60Sec, 0);
   }
   if (VMSnok (status)) ErrorExitVmsStatus (status, "sys$setimr", FI_LI);
}

/*****************************************************************************/
/*
HttpdTickSecond is just a one second period, not a timestamp per se.
*/

ulong HttpdGetTickSecond ()

{
   static uint64  FirstTick64;

   uint64  time64;

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

   if (!FirstTick64) sys$gettim (&FirstTick64);
   sys$gettim (&time64);
   return ((ulong)((time64 - FirstTick64) / 10000000));
}

/*****************************************************************************/
/*
Set the timer counters for the request according to the function code. 
HttpdSupervisor() will monitor these counters.  The period is set by adding the
period in seconds to the current second count to derive a value in the furture. 
When this is reached or exceeded the timer has expired.   The 'DurationSeconds'
is an optional parameter.  If set to zero the standard timer values are used. 
If -1 then the timer expires at the next supervision (i.e. immediately).  If
otherwise non-zero the value is used.  This allows CGI callouts and scripting
control to set these with specific values.
*/

HttpdTimerSet
(
REQUEST_STRUCT *rqptr,
int Function,
int DurationSeconds
)
{
   static BOOL  Initialize = true;
   static ulong  InputSeconds,
                 PersistentSeconds,
                 NoProgressPeriod,
                 OutputSeconds;

   int  idx, status,
        TimerSeconds;

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

   if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL))
      WatchThis (WATCHALL, WATCH_MOD__OTHER,
                 "HttpdTimerSet() !UL !SL", Function, DurationSeconds);

   if (Initialize)
   {
      Initialize = false;
      if (Config.cfTimeout.Input)
         InputSeconds = Config.cfTimeout.Input;
      else
         InputSeconds = DEFAULT_TIMEOUT_INPUT_MINUTES * 60;
      if (Config.cfTimeout.Output)
         OutputSeconds = Config.cfTimeout.Output;
      else
         OutputSeconds = DEFAULT_TIMEOUT_OUTPUT_MINUTES * 60;
      if (Config.cfTimeout.NoProgress)
         NoProgressPeriod = Config.cfTimeout.NoProgress;
      else
         NoProgressPeriod = DEFAULT_TIMEOUT_NOPROGRESS_MINUTES * 60;
      if (Config.cfTimeout.Persistent)
         PersistentSeconds = Config.cfTimeout.Persistent;
      else
         PersistentSeconds = DEFAULT_TIMEOUT_PERSISTENT_SECONDS;
      /* ensure small periods are at least that duration */
      if (PersistentSeconds && PersistentSeconds < 10) PersistentSeconds++;
   }

   /* once the request has been terminated (and aborted) that's it! */
   if (rqptr->rqTmr.TimeoutCount) return;

   switch (Function)
   {
      case TIMER_INPUT :

          rqptr->rqTmr.PersistentSecond =
            rqptr->rqTmr.NoProgressBytesRx =
            rqptr->rqTmr.NoProgressBytesTx =
            rqptr->rqTmr.NoProgressSecond =
            rqptr->rqTmr.NoProgressPeriod =
            rqptr->rqTmr.OutputSecond =
            rqptr->rqTmr.TerminateSecond =
            rqptr->rqTmr.TimeoutType =
            rqptr->rqTmr.ThrottleSecond = 0;

         if (DurationSeconds)
            TimerSeconds = DurationSeconds;
         else
            TimerSeconds = InputSeconds;

         if (WATCHING (rqptr, WATCH_INTERNAL))
            WatchThis (WATCHITM(rqptr), WATCH_INTERNAL,
                       "TIMER input !SL seconds", TimerSeconds);

         rqptr->rqTmr.InputSecond = HttpdTickSecond + TimerSeconds + 1;

         break;

      case TIMER_OUTPUT :

         rqptr->rqTmr.InputSecond =
            rqptr->rqTmr.PersistentSecond =
            rqptr->rqTmr.TerminateSecond =
            rqptr->rqTmr.TimeoutType =
            rqptr->rqTmr.ThrottleSecond = 0;

         /* establish the baseline no-progress indicators (lsb) */
         rqptr->rqTmr.NoProgressBytesRx = rqptr->NetIoPtr->BytesRawRx64;
         rqptr->rqTmr.NoProgressBytesTx = rqptr->NetIoPtr->BytesRawTx64;

         if (DurationSeconds)
            TimerSeconds = DurationSeconds;
         else
         if (rqptr->rqPathSet.TimeoutOutput)
            TimerSeconds = rqptr->rqPathSet.TimeoutOutput;
         else
            TimerSeconds = OutputSeconds;

         if (rqptr->rqPathSet.TimeoutNoProgress)
            rqptr->rqTmr.NoProgressPeriod = rqptr->rqPathSet.TimeoutNoProgress;
         else
            rqptr->rqTmr.NoProgressPeriod = NoProgressPeriod;

         if (WATCHING (rqptr, WATCH_INTERNAL))
            WatchThis (WATCHITM(rqptr), WATCH_INTERNAL,
                       "TIMER output !SL/!UL seconds",
                       TimerSeconds, rqptr->rqTmr.NoProgressPeriod);

         rqptr->rqTmr.NoProgressSecond =
            HttpdTickSecond + rqptr->rqTmr.NoProgressPeriod + 1;

         rqptr->rqTmr.OutputSecond = HttpdTickSecond + TimerSeconds + 1;

         /* use the smaller of the two values to establish the list */
         if (rqptr->rqTmr.NoProgressPeriod < TimerSeconds)
            TimerSeconds = rqptr->rqTmr.NoProgressPeriod;

         break;

      case TIMER_NOPROGRESS :

         rqptr->rqTmr.InputSecond =
            rqptr->rqTmr.PersistentSecond =
            rqptr->rqTmr.TerminateSecond =
            rqptr->rqTmr.TimeoutType =
            rqptr->rqTmr.ThrottleSecond = 0;

         /* establish the baseline no-progress indicators (lsb) */
         rqptr->rqTmr.NoProgressBytesRx = rqptr->NetIoPtr->BytesRawRx64;
         rqptr->rqTmr.NoProgressBytesTx = rqptr->NetIoPtr->BytesRawTx64;

         if (DurationSeconds)
            rqptr->rqTmr.NoProgressPeriod = DurationSeconds;
         else
         if (rqptr->rqPathSet.TimeoutNoProgress)
            rqptr->rqTmr.NoProgressPeriod = rqptr->rqPathSet.TimeoutNoProgress;
         else
            rqptr->rqTmr.NoProgressPeriod = NoProgressPeriod;

         if (WATCHING (rqptr, WATCH_INTERNAL))
            WatchThis (WATCHITM(rqptr), WATCH_INTERNAL,
                       "TIMER no-progress !UL seconds",
                       rqptr->rqTmr.NoProgressPeriod);

         rqptr->rqTmr.NoProgressSecond =
            HttpdTickSecond + rqptr->rqTmr.NoProgressPeriod + 1;

         /* no need to adjust the list! */
         return;

      case TIMER_PERSISTENT :

         rqptr->rqTmr.InputSecond =
            rqptr->rqTmr.OutputSecond =
            rqptr->rqTmr.NoProgressBytesRx =
            rqptr->rqTmr.NoProgressBytesTx =
            rqptr->rqTmr.NoProgressSecond =
            rqptr->rqTmr.NoProgressPeriod =
            rqptr->rqTmr.TerminateSecond =
            rqptr->rqTmr.TimeoutType =
            rqptr->rqTmr.ThrottleSecond = 0;

         if (DurationSeconds)
            TimerSeconds = DurationSeconds;
         else
            TimerSeconds = PersistentSeconds;

         if (WATCHING (rqptr, WATCH_INTERNAL))
            WatchThis (WATCHITM(rqptr), WATCH_INTERNAL,
                       "TIMER persistent !SL seconds", TimerSeconds);

         rqptr->rqTmr.PersistentSecond = HttpdTickSecond + TimerSeconds + 1;

         break;

      case TIMER_THROTTLE :

         rqptr->rqTmr.InputSecond =
            rqptr->rqTmr.PersistentSecond =
            rqptr->rqTmr.NoProgressBytesRx =
            rqptr->rqTmr.NoProgressBytesTx =
            rqptr->rqTmr.NoProgressSecond =
            rqptr->rqTmr.NoProgressPeriod =
            rqptr->rqTmr.OutputSecond =
            rqptr->rqTmr.TerminateSecond =
            rqptr->rqTmr.TimeoutType = 0;

         if (DurationSeconds)
            TimerSeconds = DurationSeconds;
         else
         /* timeout-busy only becomes affective after any timeout-queue */
         if (rqptr->rqPathSet.ThrottleTimeoutQueue)
            TimerSeconds = rqptr->rqPathSet.ThrottleTimeoutQueue;
         else
         if (rqptr->rqPathSet.ThrottleTimeoutBusy)
            TimerSeconds = rqptr->rqPathSet.ThrottleTimeoutBusy;
         else
            TimerSeconds = OutputSeconds;

         if (WATCHING (rqptr, WATCH_INTERNAL))
            WatchThis (WATCHITM(rqptr), WATCH_INTERNAL,
                       "TIMER throttle !SL seconds", TimerSeconds);

         rqptr->rqTmr.ThrottleSecond = HttpdTickSecond + TimerSeconds + 1;

         break;

      case TIMER_TERMINATE :

         rqptr->rqTmr.InputSecond =
            rqptr->rqTmr.PersistentSecond =
            rqptr->rqTmr.NoProgressBytesRx =
            rqptr->rqTmr.NoProgressBytesTx =
            rqptr->rqTmr.NoProgressSecond =
            rqptr->rqTmr.NoProgressPeriod =
            rqptr->rqTmr.OutputSecond =
            rqptr->rqTmr.ThrottleSecond =
            rqptr->rqTmr.TimeoutType = 0;

         if (WATCHING (rqptr, WATCH_INTERNAL))
            WatchThis (WATCHITM(rqptr), WATCH_INTERNAL, "TIMER terminate");

         TimerSeconds = DurationSeconds;
         rqptr->rqTmr.TerminateSecond = HttpdTickSecond + TimerSeconds + 1;

         break;

      default :
         ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
   }

   /* add/move to new list */
   HttpdSupervisorList (rqptr, TimerSeconds);

   rqptr->rqTmr.TimeoutCount = 0;
}

/*****************************************************************************/
/*
Whenever at least one request is being processed this function is called every
second by HttpdTick() to supervise the requests' progress.  An array of lists
is maintained so that the duration of the scan is somewhat limited each second,
peaks occuring only when non-one-second lists need scanning.  Timer duration
counts are set by adding the timer period in seconds to the current second
count to get a second count in the future.  When this is reached  or exceeded
the timer has expired.  Time queue entries are moved from list to list as the
remaining period in seconds changes, ensuring that the entry is checked at
appropriate intervals.  Return true to indicate the HTTPd should continue to
tick.
*/

BOOL HttpdSupervisor ()

{
   int  idx, retval,
        ChunkSeconds,
        DeltaSecondCount,
        TimerSecond,
        TimeoutType;
   uint  cat;
   int64  BytesRawRx64,
          BytesRawTx64; 
   HTTP2_STRUCT  *h2ptr;
   LIST_ENTRY  *leptr;
   NETIO_STRUCT  *ioptr;
   PROXY_TASK  *ptkptr;
   REQUEST_STRUCT  *rqeptr;

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

/**
   if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL))
   {
      WatchThis (WATCHALL, WATCH_MOD__OTHER,
                 "SUPERVISOR !UL", HttpdTickSecond);
      WatchDataFormatted ("SECONDS COUNT SCAN\n");
      for (idx = 1; idx <= SUPERVISOR_LIST_MAX; idx++)
      {
         ChunkSeconds = SupervisorListArray[idx].ChunkSeconds;
         WatchDataFormatted ("!7UL !5UL !AZ\n",
            SupervisorListArray[idx].ChunkSeconds,
            LIST_GET_COUNT (&SupervisorListArray[idx].RequestList),
            (idx == 1 || !(HttpdTickSecond % ChunkSeconds)) ? " YES" : "  no");
      }
   }
**/

   if (RequestList.HeadPtr == NULL) return (false);

   /************************************/
   /* update selected network counters */
   /************************************/

   if (!(HttpdTickSecond % SUPERVISOR_NETWORK_UPDATE))
   {
      InstanceMutexLock (INSTANCE_MUTEX_HTTPD);
      if (ActivityTotalMinutes) InstanceMutexLock (INSTANCE_MUTEX_ACTIVITY);

      for (leptr = RequestList.HeadPtr; leptr; leptr = leptr->NextPtr)
      {
         rqeptr = (REQUEST_STRUCT*)leptr;
         ioptr = rqeptr->NetIoPtr;
         if (!ioptr->Channel)
            if (rqeptr->Stream2Ptr)
               if (rqeptr->Http2Ptr)
                  ioptr = rqeptr->Http2Ptr->NetIoPtr;

         if (!(ioptr->BytesTallyRx64 || ioptr->BytesTallyTx64)) continue;

         /* update activity first, it uses the bytes accounted-for value */
         if (ActivityTotalMinutes) GraphActivityUpdate (rqeptr, false);

         /* update using the running tally accumulators */
         AccountingPtr->BytesRawRx64[HTTP12] += ioptr->BytesTallyRx64;
         AccountingPtr->BytesRawTx64[HTTP12] += ioptr->BytesTallyTx64;
         AccountingPtr->BlocksRawTx64[HTTP12] += ioptr->BlocksTallyTx64;
         AccountingPtr->BlocksRawRx64[HTTP12] += ioptr->BlocksTallyRx64;

         /* reset the running tally accumulators */
         ioptr->BytesTallyRx64 = ioptr->BytesTallyTx64 =
            ioptr->BlocksTallyRx64 = ioptr->BlocksTallyTx64 = 0;

         if (ptkptr = (PROXY_TASK*)rqeptr->ProxyTaskPtr)
         {
            if (!(ioptr = ptkptr->NetIoPtr)) continue;

            if (!(ioptr->BytesTallyRx64 || ioptr->BytesTallyTx64)) continue;

            /* update proxy using the running tally accumulators */
            ProxyAccountingPtr->BytesRawRx64 += ioptr->BytesTallyRx64;
            ProxyAccountingPtr->BytesRawTx64 += ioptr->BytesTallyTx64;
            ProxyAccountingPtr->BlocksRawTx64 += ioptr->BlocksTallyTx64;
            ProxyAccountingPtr->BlocksRawRx64 += ioptr->BlocksTallyRx64;

            /* reset the running tally accumulators */
            ioptr->BytesTallyRx64 = ioptr->BytesTallyTx64 =
               ioptr->BlocksTallyRx64 = ioptr->BlocksTallyTx64 = 0;
         }
      }

      if (ActivityTotalMinutes) InstanceMutexUnLock (INSTANCE_MUTEX_ACTIVITY);
      InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);
   }

   /***************************/
   /* scan supervisor list(s) */
   /***************************/

   for (idx = 1; idx <= SUPERVISOR_LIST_MAX; idx++)
   {
      ChunkSeconds = SupervisorListArray[idx].ChunkSeconds;
      if (!(idx == 1 || !(HttpdTickSecond % ChunkSeconds))) continue;
      DeltaSecondCount = HttpdTickSecond + ChunkSeconds;

      /* process the current request list entries */
      leptr = SupervisorListArray[idx].RequestList.HeadPtr;
      while (leptr)
      {
         rqeptr = (REQUEST_STRUCT*)leptr->DataPtr;
         leptr = leptr->NextPtr;
/**
         if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL))
            WatchThis (WATCHALL, WATCH_MOD__OTHER,
"!UL <- !UL -> !UL (!UL) in: !UL o:!UL np:!UL ka:!UL term:!UL",
               leptr->PrevPtr, leptr, leptr->NextPtr, rqeptr,
               rqeptr->rqTmr.InputSecond, rqeptr->rqTmr.OutputSecond,
               rqeptr->rqTmr.NoProgressSecond, rqeptr->rqTmr.PersistentSecond,
               rqeptr->rqTmr.TimeoutCount);
**/
         if (rqeptr->rqTmr.TimeoutCount)
         {
            rqeptr->rqTmr.TimeoutCount++;
            if (rqeptr->rqTmr.TimeoutCount == TIMEOUT_ABORT_SECONDS)
               NetAbortSocket (rqeptr);
            continue;
         }

         /* ordered in the most common occurance of timings */
         if (rqeptr->rqTmr.OutputSecond)
         {
            /*********************/
            /* output/noprogress */
            /*********************/

            if (HTTP2_REQUEST(rqeptr))
            {
               /* HTTP/2 accumulated by the stream */
               BytesRawTx64 = rqeptr->Http2Ptr->BytesRawTx64;
               BytesRawRx64 = rqeptr->Http2Ptr->BytesRawRx64;
            }
            else
            {
               /* HTTP/1.n accumulated in the network structure */
               BytesRawTx64 = rqeptr->NetIoPtr->BytesRawTx64;
               BytesRawRx64 = rqeptr->NetIoPtr->BytesRawRx64;
            }

            if (rqeptr->rqTmr.NoProgressBytesTx != BytesRawTx64 ||
                rqeptr->rqTmr.NoProgressBytesRx != BytesRawRx64)
            {
               rqeptr->rqTmr.NoProgressSecond =
                  HttpdTickSecond + rqeptr->rqTmr.NoProgressPeriod + 1;
               rqeptr->rqTmr.NoProgressBytesTx = BytesRawTx64;
               rqeptr->rqTmr.NoProgressBytesRx = BytesRawRx64;
            }

            /* use the lesser of output and no-progress counts */
            if (rqeptr->rqTmr.OutputSecond <= rqeptr->rqTmr.NoProgressSecond)
            {
               if (rqeptr->rqTmr.OutputSecond > DeltaSecondCount) continue;
               TimerSecond = rqeptr->rqTmr.OutputSecond;
               TimeoutType = TIMEOUT_OUTPUT;
            }
            else
            {
               if (rqeptr->rqTmr.NoProgressSecond > DeltaSecondCount) continue;
               TimerSecond = rqeptr->rqTmr.NoProgressSecond;
               TimeoutType = TIMEOUT_NOPROGRESS;
            }

            if (TimerSecond > HttpdTickSecond)
            {
               /* move to new list */
               HttpdSupervisorList (rqeptr, TimerSecond-HttpdTickSecond);
               continue;
            }

            rqeptr->rqTmr.TimeoutType = TimeoutType;
            /* fall through to shut down */
         }
         else
         if (TimerSecond = rqeptr->rqTmr.InputSecond)
         {
            /*********/
            /* input */
            /*********/

            if (TimerSecond > DeltaSecondCount) continue;
            if (TimerSecond > HttpdTickSecond)
            {
               /* move to new list */
               HttpdSupervisorList (rqeptr, TimerSecond-HttpdTickSecond);
               continue;
            }
            rqeptr->rqTmr.TimeoutType = TIMEOUT_INPUT;
            /* fall through to shut down */
         }
         else
         if (TimerSecond = rqeptr->rqTmr.PersistentSecond)
         {
            /*************************/
            /* persistent connection */
            /*************************/

            if (TimerSecond > DeltaSecondCount) continue;
            if (TimerSecond > HttpdTickSecond)
            {
               /* move to new list */
               HttpdSupervisorList (rqeptr, TimerSecond-HttpdTickSecond);
               continue;
            }
            rqeptr->rqTmr.TimeoutType = TIMEOUT_PERSISTENT;
            /* fall through to shut down */
         }
         else
         if (TimerSecond = rqeptr->rqTmr.ThrottleSecond)
         {
            /************/
            /* throttle */
            /************/

            if (TimerSecond > DeltaSecondCount) continue;
            if (TimerSecond > HttpdTickSecond)
            {
               /* move to new list */
               HttpdSupervisorList (rqeptr, TimerSecond-HttpdTickSecond);
               continue;
            }

            if (WATCHING (rqeptr, WATCH_INTERNAL))
               WatchThis (WATCHITM(rqeptr), WATCH_INTERNAL,
                          "TIMER throttle EXPIRED");

            /* does not fall through to shut down! */
            continue;
         }
         else
         if (TimerSecond = rqeptr->rqTmr.TerminateSecond)
         {
            /*************/
            /* terminate */
            /*************/

            if (TimerSecond > DeltaSecondCount) continue;
            if (TimerSecond > HttpdTickSecond)
            {
               /* move to new list */
               HttpdSupervisorList (rqeptr, TimerSecond-HttpdTickSecond);
               continue;
            }

            rqeptr->rqTmr.TimeoutType = TIMEOUT_TERMINATE;
            /* fall through to shut down */
         }

         /*************************/
         /* shut down the request */
         /*************************/

         if ((WATCH_ITM(rqeptr) &&
             WATCH_CATEGORY((cat = WATCH_CONNECT)) ||
             WATCH_CATEGORY((cat = WATCH_INTERNAL))))
             WatchThis (WATCHITM(rqeptr), cat, "TIMER !AZ",
                        HttpdTimeoutType (rqeptr->rqTmr.TimeoutType));

         if (Watch.RequestPtr == rqeptr)
         {
            WatchEnd ();
            continue;
         }

         rqeptr->rqTmr.TimeoutCount++;

         if (rqeptr->rqTmr.TimeoutType == TIMEOUT_INPUT)
            rqeptr->rqResponse.HttpStatus = 408;
         else
         if (Watch.RequestPtr != rqeptr)
            rqeptr->rqResponse.HttpStatus = 500;

         RequestAbort (rqeptr);
      }
   }

   return (true);
}

/*****************************************************************************/
/*
Return a pointer to a string.
*/

char* HttpdTimeoutType (int type)

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

   switch (type)
   {
      case TIMEOUT_NONE : return ("");
      case TIMEOUT_INPUT : return ("(t/o:input) ");
      case TIMEOUT_THROTTLE : return ("(t/o:throttle) ");
      case TIMEOUT_OUTPUT : return ("(t/o:output) ");
      case TIMEOUT_NOPROGRESS : return ("(t/o:noprogress) ");
      case TIMEOUT_PERSISTENT : return ("(t/o:persistent) ");
      case TIMEOUT_TERMINATE : return ("(t/o:terminate) ");
      default : return ("(t/o:unknown)");
   }
}

/*****************************************************************************/
/*
If necessary remove a request entry from it's appropriate supervisor array
list.  The search for an element within the range of timer currently still
outstanding with the request and add it to the HEAD of that list.  The head is
important because HttpdSupervisor() scans through these lists from head to tail
and if the tail is played with at the same time all hell breaks loose.
*/

HttpdSupervisorList
(
REQUEST_STRUCT *rqptr,
int TimerSeconds
)
{
   int  idx;

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

   if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL))
      WatchThis (WATCHALL, WATCH_MOD__OTHER,
                 "HttpdSupervisorList() !SL", TimerSeconds);

   if (rqptr->rqTmr.ListIndex)
   {
      /* remove from current list */
      ListRemove (&SupervisorListArray[rqptr->rqTmr.ListIndex].RequestList,
                  &rqptr->rqTmr.ListEntry);
      rqptr->rqTmr.ListIndex = 0;
      rqptr->rqTmr.ListEntry.DataPtr = NULL;
   }

   /* just removing from timer list */
   if (TimerSeconds == -1) return;

   /* add to new list */
   for (idx = 1; idx < SUPERVISOR_LIST_MAX; idx++)
   {
      if (TimerSeconds > SupervisorListArray[idx+1].ChunkSeconds)
         continue;
      /* add this to the head of the list (less carpet pulling that way) */
      ListAddHead (&SupervisorListArray[idx].RequestList,
                   &rqptr->rqTmr.ListEntry, LIST_ENTRY_TYPE_TIMER);
      rqptr->rqTmr.ListEntry.DataPtr = (void*)rqptr;
      rqptr->rqTmr.ListIndex = idx;
      return;
   }

   ErrorNoticed (rqptr, 0, "supervisor list", FI_LI);

   /* ensure it goes somewhere */
   ListAddHead (&SupervisorListArray[1].RequestList,
                &rqptr->rqTmr.ListEntry, LIST_ENTRY_TYPE_TIMER);
   rqptr->rqTmr.ListEntry.DataPtr = (void*)rqptr;
   rqptr->rqTmr.ListIndex = 1;
}

/*****************************************************************************/
/*
Provide a report on the server supervisor (do tell!)
*/ 

HttpdSupervisorReport (REQUEST_STRUCT *rqptr)

{
   static char  BeginPage [] =
"<p><table class=\"ctgry\">\n\
<tr><td>\n\
<table class=\"lftlft\">\n\
<tr>\
<th class=\"sbttl\">List</th>\
<th class=\"sbttl talft\">Seconds</th>\
<th class=\"sbttl\">Count</th>\
<th class=\"sbttl\">Scan</th>\
</tr>\n\
<tr HEIGHT=5></tr>\n";

   static char  ListFao [] =
"<tr>\
<th>!UL</th>\
<td>!&@</td>\
<td class=\"targht\">!UL</td>\
<td>!AZ</td>\
</tr>\n";

   static char  EndOfPageFao [] =
"<tr>\
<th class=\"sbttl targht\" colspan=\"2\">Total:</th>\
<td>!UL</td>\
</tr>\n\
</table>\n\
<p><table class=\"rghtlft\">\n\
<tr><th>Supervisor:</th>\
<td>!UL</td></tr>\
<tr><th>Year:</th>\
<td>!UL</td></tr>\
<tr><th>Month:</th>\
<td>!UL</td></tr>\
<tr><th>Day:</th>\
<td>!UL</td></tr>\
<tr><th>Hour:</th>\
<td>!UL</td></tr>\
<tr><th>Minute:</th>\
<td>!UL</td></tr>\
<tr><th>Second:</th>\
<td>!UL</td></tr>\
</table>\n\
</td></tr>\n\
</table>\n\
</div>\n\
</body>\n\
</html>\n";

   int  idx, status,
        ChunkRange,
        ChunkSeconds,
        Count,
        CountTotal;
   ulong  *vecptr;
   ulong  FaoVector [32];

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

   if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL))
      WatchThis (WATCHALL, WATCH_MOD__OTHER, "HttpdSupervisorReport()");

   AdminPageTitle (rqptr, "Supervisor Report", BeginPage);

   ChunkRange = CountTotal = 0;
   for (idx = 1; idx < SUPERVISOR_LIST_MAX; idx++)
   {
      ChunkSeconds = SupervisorListArray[idx].ChunkSeconds;
      ChunkRange = SupervisorListArray[idx+1].ChunkSeconds;
      Count = LIST_GET_COUNT (&SupervisorListArray[idx].RequestList);
      CountTotal += Count;

      vecptr = FaoVector;
      *vecptr++ = idx;
      if (idx == 9)
      {
         *vecptr++ = "!UL - <i>infinite</i>";
         *vecptr++ = ChunkSeconds;
      }
      else
      {
         *vecptr++ = "!UL - !UL";
         *vecptr++ = ChunkSeconds;
         *vecptr++ = ChunkRange-1;
      }
      *vecptr++ = Count;
      if (idx == 1 || (HttpdTickSecond && !(HttpdTickSecond % ChunkSeconds)))
         *vecptr++ = "YES";
      else
         *vecptr++ = "no";

      status = FaolToNet (rqptr, ListFao, &FaoVector);
      if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
   }

   vecptr = FaoVector;
   *vecptr++ = CountTotal;
   *vecptr++ = HttpdTickSecond;
   *vecptr++ = HttpdTime7[0];
   *vecptr++ = HttpdTime7[1];
   *vecptr++ = HttpdTime7[2];
   *vecptr++ = HttpdTime7[3];
   *vecptr++ = HttpdTime7[4];
   *vecptr++ = HttpdTime7[5];

   status = FaolToNet (rqptr, EndOfPageFao, &FaoVector);
   if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);

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

   AdminEnd (rqptr);
}

/*****************************************************************************/
/*
Create and/or map (an existing) a permanent global section.  This global
section is used to contain the accounting structure and other data that might
be of interest to a separate utility such a HTTPDMON.  The utility merely maps
the section and continues to read values from the "common" memory.  This used
to be stored in logical names but this is seen as a more elegant and efficient
method.  The section name is based on the string "WASD_HTTPD_" plus the
"official" port number of the server process.  As this is a permanent global
section it can "permanently" store accounting data between server invocations
(in much the same way the old method of using logicals could).  Because it is
permanent though, and global section is created on a per-server basis, anyone
who "plays around" with servers on a whole range of "official" port numbers run
the risk of consuming all system sections and/or global pages.  To allow for
removing no-longer-required global sections this function contains a hack
allowing the server qualifier /GBLSEC=DELETE (plus /INSTANCE= if necessary) to
delete the corresponding global section.
*/ 

int HttpdGblSecInit ()

{
   static char  ReportGblSecPages [] =
"%HTTPD-I-GBLSEC, !AZ global section of !UL page(let)s\n";

   /* global, allocate space, system, in page file, permanent, writable */
   static int CreFlags = SEC$M_GBL | SEC$M_EXPREG | SEC$M_SYSGBL |
                         SEC$M_PAGFIL | SEC$M_PERM | SEC$M_WRT;
   static int DelFlags = SEC$M_SYSGBL;
   /* system & owner full access, group and world no access */
   static ulong  ProtectionMask = 0xff00;
   /* it is recommended to map into any virtual address in the region (P0) */
   static ulong  InAddr [2] = { 0x200, 0x200 };

   int  attempt, status,
        GblSecPages,
        PageCount;
   short  ShortLength;
   ulong  RetAddr [2];
   char  GblSecName [32];
   $DESCRIPTOR (GblSecNameDsc, GblSecName);
   HTTPD_GBLSEC  *gsptr;

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

   if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL))
      WatchThis (WATCHALL, WATCH_MOD__OTHER, "HttpdGblSecInit()");

   FaoToBuffer (GblSecName, sizeof(GblSecName), &ShortLength,
                GBLSEC_NAME_FAO, HTTPD_NAME, HTTPD_GBLSEC_VERSION_NUMBER,
                InstanceEnvNumber, "HTTPD");
   GblSecNameDsc.dsc$w_length = ShortLength;

   if (CliGblSecDelete)
   {
      /* delete the specified global section */
      sys$setprv (1, &GblSecPrvMask, 0, 0);
      status = sys$dgblsc (DelFlags, &GblSecNameDsc, 0);
      sys$setprv (0, &GblSecPrvMask, 0, 0);
      return (status);
   }

   GblSecPages = sizeof(HTTPD_GBLSEC) / 512;
   if (GblSecPages & 0x1ff) GblSecPages++;

   /* do not create a permanent global section */
   if (CliGblSecNoPerm) CreFlags &= ~SEC$M_PERM;

   for (attempt = 1; attempt <= 2; attempt++)
   {
      /* create and/or map the specified global section */
      sys$setprv (1, &GblSecPrvMask, 0, 0);
      status = sys$crmpsc (&InAddr, &RetAddr, 0, CreFlags,
                           &GblSecNameDsc, 0, 0, 0, GblSecPages, 0,
                           ProtectionMask, GblSecPages);
      sys$setprv (0, &GblSecPrvMask, 0, 0);

      if (WATCH_MODULE(WATCH_MOD__OTHER))
         WatchThis (WATCHALL, WATCH_MOD__OTHER,
                    "sys$crmpsc() !&S begin:!UL end:!UL bytes:!UL",
                    status, RetAddr[0], RetAddr[1], RetAddr[1] - RetAddr[0]);

      PageCount = (RetAddr[1]+1) - RetAddr[0] >> 9;
      HttpdGblSecPtr = gsptr = (HTTPD_GBLSEC*)RetAddr[0];
      if (VMSnok (status) || status == SS$_CREATED) break;

      /* section already exists, break if 'same size' and version! */
      if (PageCount >= GblSecPages &&
          HttpdGblSecPtr->GblSecVersion == HttpdGblSecVersion)
         break;

      /* delete the current global section, have one more attempt */
      sys$setprv (1, &GblSecPrvMask, 0, 0);
      status = sys$dgblsc (DelFlags, &GblSecNameDsc, 0);
      sys$setprv (0, &GblSecPrvMask, 0, 0);
      status = SS$_IDMISMATCH;
   }

   if (VMSnok (status))
   {
      /* must have this global section! */
      char  String [256];
      FaoToBuffer (String, sizeof(String), NULL,
                   "1 global section, !UL global pages", GblSecPages);
      ErrorExitVmsStatus (status, String, FI_LI);
   }

   if (status == SS$_CREATED)
      FaoToStdout (ReportGblSecPages, "created", PageCount);
   else
      FaoToStdout (ReportGblSecPages, "existing", PageCount);

   /* if it has a different structure reset the storage */
   if (gsptr->GblSecLength != sizeof(HTTPD_GBLSEC))
   {
      memset (gsptr, 0, sizeof(HTTPD_GBLSEC));
      ControlZeroAccounting ();
   }

   /* if it was just created or reset due to different structure */
   if (!gsptr->GblSecVersion)
   {
      gsptr->GblSecVersion = HttpdGblSecVersion;
      gsptr->GblSecLength = sizeof(HTTPD_GBLSEC);
   }

   HttpdGblSecPages = PageCount;
   AccountingPtr = &gsptr->Accounting;
   ProxyAccountingPtr = &gsptr->ProxyAccounting;
   gsptr->HttpdProcessId = HttpdProcess.Pid;
   strzcpy (gsptr->HttpdVersion, HttpdVersion, sizeof(gsptr->HttpdVersion));

   if (CliGblSecNoPerm)
   {
      GblSectionCount++;
      GblPageCount += PageCount;
   }
   else
   {
      GblSectionPermCount++;
      GblPagePermCount += PageCount;
   }

   return (status);
}

/*****************************************************************************/
/*
The above funtion *creates* and maps.  This just maps.
*/

int HttpdGblSecMap ()

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

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

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

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

   if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL))
      WatchThis (WATCHALL, WATCH_MOD__OTHER, "HttpdGblSecMap()");

   AccountingPtr = HttpdGblSecPtr = NULL;

   sys$fao (&HttpdGblSecNameFaoDsc, &slen, &HttpdGblSecNameDsc,
            HTTPD_NAME, HTTPD_GBLSEC_VERSION_NUMBER,
            InstanceEnvNumber, "HTTPD");
   HttpdGblSecNameDsc.dsc$w_length = slen;

   status = sys$mgblsc (&InAddr, &RetAddr, 0, MapFlags, &HttpdGblSecNameDsc,
                        0, 0);

   if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL))
      WatchThis (WATCHALL, WATCH_MOD__OTHER,
                 "sys$mgblsc() !AZ %X!8XL begin:!UL end:!UL bytes:!UL",
                 HttpdGblSecName, status, RetAddr[0], RetAddr[1],
                 RetAddr[1] - RetAddr[0]);

   if (VMSnok (status)) return (status);

   ByteSize = (RetAddr[1]+1) - RetAddr[0];
   PageSize = (RetAddr[1]+1) - RetAddr[0] >> 9;
   HttpdGblSecPtr = (HTTPD_GBLSEC*)RetAddr[0];

   if (HttpdGblSecPtr->GblSecVersion != HTTPD_GBLSEC_VERSION_NUMBER ||
       HttpdGblSecPtr->GblSecLength != sizeof(HTTPD_GBLSEC))
      return (SS$_GBLSEC_MISMATCH);

   AccountingPtr = &HttpdGblSecPtr->Accounting;

   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
Populate |HttpdProcess.ImageInfo| with a string that identifies the actual
image in use.  This can then be used in log entries, process output, etc.

IA64 ELF image analysis plagiarised from:
//  File: elf_imginfo.c
//  Author: Hartmut Becker
//  Creation Date:  24-Sep-2004
*/ 

void HttpdImageInfo ()

{
   static int  ImagNameLength;
   static char ImagName [256];
   static VMS_ITEM_LIST3
   JpiItems [] =
   {
     { sizeof(ImagName), JPI$_IMAGNAME, &ImagName, &ImagNameLength },
     { 0,0,0,0 }
   };

#ifdef ELVEN
   static const char MagicPlus [] =
   {
       EHDR$K_ELFMAG0, EHDR$K_ELFMAG1, EHDR$K_ELFMAG2, EHDR$K_ELFMAG3,
       EHDR$K_ELFCLASS64, EHDR$K_ELFDATA2LSB
   };
#endif

   int  size, status;
   ushort  Length;
   char  *cptr,
         *ImageDatePtr,
         *ImageIdentPtr,
         *ImageNamePtr;
   char  ErrorInfo [256],
         ImageRecord [512];
   ulong  Genesis [2];
   FILE  *ImageFile;
   IO_SB  IOsb;

#ifdef ELVEN
   int  cnt;
   ulong  ImageDate [2];
   char  *secptr = NULL;
   ELF64_EHDR  ElfHeader;
   ELF64_SHDR  *shptr = NULL;
   ELF64_NHDR  *nhptr;
#endif

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

   if (WATCH_MODULE(WATCH_MOD_NET))
      WatchThis (WATCHALL, WATCH_MOD_NET, "HttpdImageInfo()");

   memset (ErrorInfo, 0, sizeof(ErrorInfo));

   /* get the image name */
   status = sys$getjpiw (EfnWait, 0, 0, &JpiItems, &IOsb, 0, 0);
   if (VMSok (status)) status = IOsb.Status;
   if (VMSnok (status)) ErrorExitVmsStatus (status, NULL, FI_LI);

   HttpdProcess.ImageInfo[ImagNameLength] = '\0';

   /* HttpdProcessInfo() uses this to obtain image name then space is reused */
   ImageFile = fopen (ImagName, "r", "shr=get", "dna=WASD_EXE:.EXE");
   if (!ImageFile)
   {
      FaoToBuffer (ErrorInfo+1, sizeof(ErrorInfo)-1, &Length,
                   "%X!8XL(!UL)", vaxc$errno, __LINE__);
      goto ImageIdentFailed;
   }

#ifndef ELVEN
   if (fread (&ImageRecord, sizeof(ImageRecord), 1, ImageFile) != 1)
   {
      FaoToBuffer (ErrorInfo+1, sizeof(ErrorInfo)-1, &Length,
                   "%X!8XL(!UL)", vaxc$errno, __LINE__);
      goto ImageIdentFailed;
   }
#endif

#ifdef __ALPHA
   ImageNamePtr = ImageRecord + 200;
   ImageIdentPtr = ImageRecord + 240;
   ImageDatePtr = ImageRecord + 192;
#endif

#ifdef ELVEN

   ImageNamePtr = ImageIdentPtr = "?";
   ImageDatePtr = 0;

   if (fread (&ElfHeader, sizeof(ElfHeader), 1, ImageFile) != 1)
   {
      FaoToBuffer (ErrorInfo+1, sizeof(ErrorInfo)-1, &Length,
                   "%X!8XL(!UL)", vaxc$errno, __LINE__);
      goto ImageIdentFailed;
   }

   if (strncmp ((char*)&ElfHeader.ehdr$t_e_ident,
                MagicPlus,
                sizeof(MagicPlus)-1)) 
   {
      FaoToBuffer (ErrorInfo+1, sizeof(ErrorInfo)-1, &Length,
                   "ELF file format? (!UL)", __LINE__);
      goto ImageIdentFailed;
   }

   shptr = (ELF64_SHDR*) malloc (sizeof *shptr *ElfHeader.ehdr$w_e_shnum);
   if (fseek (ImageFile, ElfHeader.ehdr$q_e_shoff, SEEK_SET))
   {
      FaoToBuffer (ErrorInfo+1, sizeof(ErrorInfo)-1, &Length,
                   "%X!8XL(!UL)", vaxc$errno, __LINE__);
      goto ImageIdentFailed;
   }
   if (fread (shptr, sizeof *shptr*ElfHeader.ehdr$w_e_shnum, 1, ImageFile) != 1)
   {
      FaoToBuffer (ErrorInfo+1, sizeof(ErrorInfo)-1, &Length,
                   "%X!8XL(!UL)", vaxc$errno, __LINE__);
      goto ImageIdentFailed;
   }

   for (cnt = 1; cnt < ElfHeader.ehdr$w_e_shnum; cnt++)
   {
      if (shptr[cnt].shdr$l_sh_type == SHDR$K_SHT_NOTE)
      {
         secptr = malloc (shptr[cnt].shdr$q_sh_size);
         if (fseek (ImageFile, shptr[cnt].shdr$q_sh_offset, SEEK_SET))
         {
            FaoToBuffer (ErrorInfo+1, sizeof(ErrorInfo)-1, &Length,
                         "%X!8XL(!UL)", vaxc$errno, __LINE__);
            goto ImageIdentFailed;
         }
         if (fread (secptr, shptr[cnt].shdr$q_sh_size, 1, ImageFile) != 1)
         {
            FaoToBuffer (ErrorInfo+1, sizeof(ErrorInfo)-1, &Length,
                         "%X!8XL(!UL)", vaxc$errno, __LINE__);
            goto ImageIdentFailed;
         }

         for (nhptr = (ELF64_NHDR*)secptr;
              (char*)nhptr-secptr < shptr[cnt].shdr$q_sh_size;
              nhptr = (ELF64_NHDR*)((char*)nhptr+sizeof(ELF64_NHDR)+
                                ((nhptr->nhdr$q_nh_namesz+7)&~7)+
                                ((nhptr->nhdr$q_nh_descsz+7)&~7)))
         {
            if (nhptr->nhdr$q_nh_descsz > 0)
            {
               switch (nhptr->nhdr$q_nh_type)
               {
                  case NHDR$K_NT_VMS_LINKTIME:
                       ImageDatePtr = (ULONGPTR)
                                      ((char*)nhptr+sizeof(ELF64_NHDR) +
                                       ((nhptr->nhdr$q_nh_namesz+7)&~7));
                       memcpy (ImageDate, ImageDatePtr, 8);
                       ImageDatePtr = ImageDate;
                       break ;
                  case NHDR$K_NT_VMS_IMGNAM:
                       cptr = (char*)nhptr+sizeof(ELF64_NHDR) +
                              ((nhptr->nhdr$q_nh_namesz+7)&~7);
                       ImageNamePtr = malloc (size = strlen(cptr)+1);
                       strzcpy (ImageNamePtr, cptr, size);
                       break ;
                  case NHDR$K_NT_VMS_IMGID:
                       cptr = (char*)nhptr+sizeof(ELF64_NHDR) +
                              ((nhptr->nhdr$q_nh_namesz+7)&~7);
                       ImageIdentPtr = malloc (size = strlen(cptr)+1);
                       strzcpy (ImageIdentPtr, cptr, size);
                       break ;
                  case NHDR$K_NT_VMS_GSTNAM:
                       break ;
                  case NHDR$K_NT_VMS_IMGBID:
                       break ;
                  case NHDR$K_NT_VMS_LINKID:
                       break ;
                  default:
                       break ;
               }
            }
         }
         free (secptr);
      }
   }

   free (shptr);

#endif

   /* goto target */
   ImageIdentFailed:

   fclose (ImageFile);

   if (ErrorInfo[0])
   {
#ifdef ELVEN
      /* uses null-terminated strings */
      ImageNamePtr = ImageIdentPtr = ErrorInfo + 1;
#else
      /* uses counted strings */
      ImageNamePtr = ImageIdentPtr = ErrorInfo;
#endif
      memset (Genesis, 0, sizeof(Genesis));
      ImageDatePtr = Genesis;
   }

#ifdef ELVEN
   /* ever'thin's null-terminated in this brave new world? */
   FaoToBuffer (HttpdProcess.ImageInfo, sizeof(HttpdProcess.ImageInfo), NULL,
                "!AZ !AZ !%D !AZ",
                ImageNamePtr, ImageIdentPtr, ImageDatePtr, ImagName);
#else
   FaoToBuffer (HttpdProcess.ImageInfo, sizeof(HttpdProcess.ImageInfo), NULL,
                "!AC !AC !%D !AZ",
                ImageNamePtr, ImageIdentPtr, ImageDatePtr, ImagName);
#endif
}                                   

/*****************************************************************************/
/*
Create a daily archive from the server process PPF log.

The daily log file is placed in the same location as the server log files.
The non-multiple instance log name is <node-name>_yyyymmdd_DAILY.LOG
The multiple instance log name is <node-name>_<integer>_yyyymmdd_DAILY.LOG

It works by periodically (every minute) opening the server log file for
read-only access.  The total number of records previously counted is read from
the global common.  Each record is then $FIND skipped while counting from 1 to 
EOF while counting the number of records,  When EOF is encountered the total
number of records in that file is stored back in the global common (and
therefore survives image (re)startups.

Only records, determined by the count exceeding the previous total number, that
have not previously been written to the daily log are $GET read from the server
log and written to the daily log.  Opening the daily log file for write is
deferred until it is known that there are additional record(s) to be written.

The daily log file has a timestamp added at startup and then each hour to
provide an indication that it continues to run in the absence of any additional
server log records.

Enable by logical name WASD_SERVER_DAILY being defined at server startup.
*/

void HttpdSysOutDaily ()

{
   static ushort  DailyNameLen;
   static int  LastDaily,
                LastHourly,
                RecordCount;
   static char  DailyName [128];

   ushort  slen;
   int  count, status;
   char  *cptr, *sptr, *zptr,
         *SysOutPtr;
   char  record [2048];
   ODS_STRUCT  DailyOds,
               SysOutOds;

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

   if (WATCH_MODULE(WATCH_MOD__OTHER) && WATCH_MODULE(WATCH_MOD__DETAIL))
      WatchThis (WATCHALL, WATCH_MOD__OTHER, "HttpdSysOutDaily() !&B !UL !UL",
                 HttpdServerStartup, LastDaily, HttpdTime7[2]);

   /* at startup check if daily summary is enabled */
   if (HttpdServerStartup)
      if (HttpdProcess.SysOutputFile)
         if (SysTrnLnm (WASD_SERVER_DAILY))
            LastDaily = LastHourly = 999;

   /* if not enabled just return */
   if (!LastDaily) return;

   fsync (STDOUT_FILENO);

   if (LastDaily != HttpdTime7[2])
   {
      /********************************/
      /* day changed (or initialised) */
      /********************************/

      LastDaily = HttpdTime7[2];

      if (InstanceNumber)
         FaoToBuffer (DailyName, sizeof(DailyName), &DailyNameLen,
                      "!AZ!AZ_!UL_!4ZL!2ZL!2ZL_DAILY.LOG",
                      CONFIG_SERVER_LOGS,
                      SysInfo.NodeName, InstanceNumber,
                      HttpdTime7[0], HttpdTime7[1], HttpdTime7[2]);
      else
         FaoToBuffer (DailyName, sizeof(DailyName), &DailyNameLen,
                      "!AZ!AZ_!4ZL!2ZL!2ZL_DAILY.LOG",
                      CONFIG_SERVER_LOGS,
                      SysInfo.NodeName,
                      HttpdTime7[0], HttpdTime7[1], HttpdTime7[2]);

      InstanceMutexLock (INSTANCE_MUTEX_HTTPD);
      if (HttpdProcess.Pid != HttpdGblSecPtr->SysOutDailyPid[InstanceNumber])
      {
         /* not the same server process (full @[STARTUP]STARTUP) */
         HttpdGblSecPtr->SysOutDailyPid[InstanceNumber] = HttpdProcess.Pid;
         /* set total record count to zero in the global common */
         HttpdGblSecPtr->SysOutDailyCount[InstanceNumber] = 0;
      }
      InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);
   }

   /************************/
   /* open server log file */
   /************************/

   SysOutPtr = HttpdProcess.SysOutput;

   OdsStructInit (&SysOutOds, true);
   status = OdsOpenShrUpdReadOnly (&SysOutOds, SysOutPtr, false, true);
   if (VMSnok (status))
   {
      ErrorNoticed (NULL, status, SysOutPtr, FI_LI);
      return;
   }

   /* get total record count from the global common */
   InstanceMutexLock (INSTANCE_MUTEX_HTTPD);
   RecordCount = HttpdGblSecPtr->SysOutDailyCount[InstanceNumber];
   InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);

   /***************************/
   /* absorb previous records */
   /***************************/

   count = 0;
   for (;;)
   {
      status = sys$find (&SysOutOds.Rab, 0, 0);
      if (status == RMS$_EOF) break;
      if (VMSnok (status))
      {
         ErrorNoticed (NULL, status, SysOutPtr, FI_LI);
         break;
      }
      if (++count > RecordCount) break;
   }

   /* if additonal record(s) in log */
   if ((count > RecordCount) ||
       (LastHourly != HttpdTime7[3]) ||
       (HttpdTime7[3] == 23 && HttpdTime7[4] == 59))
   {
      /*******************/
      /* open daily file */
      /*******************/

      sys$setprv (1, &SysPrvMask, 0, 0);

      status = OdsCreate (&DailyOds, DailyName, DailyNameLen, NULL, 0,
                          FAB$M_PUT + FAB$M_GET, FAB$M_CIF + FAB$M_TEF,
                          FAB$M_NIL, FAB$C_STMLF, FAB$M_CR, NULL, NULL, NULL);

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

      DailyOds.Rab = cc$rms_rab;
      DailyOds.Rab.rab$l_fab = &DailyOds.Fab;
      DailyOds.Rab.rab$l_rop = RAB$M_EOF;
      DailyOds.Rab.rab$l_rbf = record;

      if (VMSok (status))
         status = sys$connect (&DailyOds.Rab, 0, 0);

      if (VMSnok (status))
      {
         OdsClose (&DailyOds, 0, 0);
         OdsClose (&SysOutOds, 0, 0);
         ErrorNoticed (NULL, status, DailyName, FI_LI);
         return;
      }

      if (LastHourly != HttpdTime7[3])
      {
         /*************/
         /* timestamp */
         /*************/

         LastHourly = HttpdTime7[3];
         FaoToBuffer (record, sizeof(record), &slen,
                      "%HTTPD-I-DAILY, !8%T", 0);
         DailyOds.Rab.rab$w_rsz = slen;
         status = sys$put (&DailyOds.Rab, 0, 0);
         if (VMSnok (status))
            ErrorNoticed (NULL, status, DailyName, FI_LI);
      }

      if (count > RecordCount)
      {
         /*********************************/
         /* read/write additional records */
         /*********************************/

         SysOutOds.Rab.rab$l_ubf = record;
         SysOutOds.Rab.rab$w_usz = sizeof(record)-2;

         count--;
         for (;;)
         {
            status = sys$get (&SysOutOds.Rab, 0, 0);
            if (status == RMS$_EOF) break;
            if (VMSnok (status))
            {
               ErrorNoticed (NULL, status, SysOutPtr, FI_LI);
               break;
            }
            DailyOds.Rab.rab$w_rsz = SysOutOds.Rab.rab$w_rsz;
            status = sys$put (&DailyOds.Rab, 0, 0);
            if (VMSnok (status))
            {
               ErrorNoticed (NULL, status, DailyName, FI_LI);
               break;
            }
            count++;
         }

         InstanceMutexLock (INSTANCE_MUTEX_HTTPD);
         HttpdGblSecPtr->SysOutDailyCount[InstanceNumber] = count;
         InstanceMutexUnLock (INSTANCE_MUTEX_HTTPD);
      }

      if (HttpdTime7[3] == 23 && HttpdTime7[4] == 59)
      {
         /*************/
         /* timestamp */
         /*************/

         FaoToBuffer (record, sizeof(record), &slen,
                      "%HTTPD-I-DAILY, !8%T", 0);
         DailyOds.Rab.rab$w_rsz = slen;
         status = sys$put (&DailyOds.Rab, 0, 0);
         if (VMSnok (status))
            ErrorNoticed (NULL, status, DailyName, FI_LI);
      }

      OdsClose (&DailyOds, 0, 0);
   }

   OdsClose (&SysOutOds, 0, 0);
}

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

