/*****************************************************************************/
/*
                                  WATCH.c

The WATCH facility is a powerful adjunct in server administration.  From the
administration menu it provides an online, real-time, in-browser-window view of
request processing in the running server.  The ability to observe live request
processing on an ad hoc basis, without changing server configuration or
shutting-down/restarting the server process, makes this facility a great
configuration and problem resolution tool.  It allows (amongst other uses)

  o  assessment of mapping rules
  o  assessment of authorization rules
  o  investigation of request processing problems
  o  general observation of server behaviour

A single client per server process can access the WATCH facility at any one
time. The report can be generated for a user-specified number of seconds or
aborted at any time using the browser's stop button.

An event is considered any significant point for which the server code has a
reporting call provided.  These have been selected to provide maximum
information with minimum clutter and impact on server performance.  Obvious
examples are connection acceptance and closure, request path resolution, error
report generation, network reads and writes, etc.  These events may be selected
on a "category" basis from the WATCH menu.

This module also provides functions to display a process using SHOW PROCESS
/ALL via the DCL.C module, and to delete a process, as well as a function to
"peek" at selected fields in the data structures of any request during
processing (intended more as a diagnosis and development tool).

The "module" WATCHing functionality is basically for low-level, on-line
debugging.  It is not intended as a general site administration tool.


FILTERS
-------
Apart from the item selectors there are filters that may be applied to session
selection.  These filters allow those matching to be selected (x)in (default)
or (x)out of the WATCH session.  All are wildcard strings.  Some allow
URL-encoding, providing the ability to specify otherwise non-printable codes
(e.g. %0a for a linefeed).  Those allowing URL-encoding require explicit
encoding for plus (%2b for '+') and percentage (%25 for '%') symbols.


TRIGGERS
--------
WASD v12.2.3 and later provides the ability to 'trigger' a WATCH session based
on network stream content.  Network stream received (Rx) and transmitted (Tx)
can be searched for the presence of a character sequence.  Unlike the filters
this a not a wildcard string and is case-sensitive.  Network triggers
(understandably) add significant overhead to WATCH processing.

URL-encoding allows for non-printing characters (described in 'filters' above).
A carriage-return plus line-feed sequence would be represented "%0d%0a".
It provides for more complex binary(-only) sequences to be constructed.

For TLS encypted network data only the cleartext is searched.

Triggers are applied on a per-request basis, though can be checked as [x]+
(plus) and this implies once triggered, the connection, HTTP/2 and HTTP/1.1
persistent, from then on WATCHes are applied to all subsequent requests on that
connection.  The plus flags are carried in the underlying I/O structures of
each request, and in the case of HTTP/2, in the underlying connection
structure.

Selection of a trigger can be exacting.  Some triggers can result in unwanted
collection.  For example, a receive trigger of "Firefox/" to capture all
related browser traffic may result in also capturing the WATCH Report header
and/or any traffic of the WATCHing browser if also Firefox.  The former issue
can be worked around by URL-encoding some part of the trigger sequence, as in
"%46irefox/".  The latter by using another variety of browser.

The bottom-line is that selecting an effective trigger requires some care.

Triggering can be a useful tool.  Long (or short) WATCH sessions, detached as
required, 'lying in wait' to activate the WATCH session when a particular
sequence of characters is present in the network stream.  Clear and encrypted
(TLS/SSL) network streams are obvious and easy to process.  HTTP/2 streams a
little less obvious due to framing and encoding of data.  WATCHing an HTTP/2
receive stream requires using a decoded header plus any body supplied with the
request.  WATCHing transmit, the HTTP/2 unencoded response header plus response
body output frames. 


WATCH LOG
---------
Prior to WASD v12.2.3 a "[x]Include ([x]only) in Server Process Log" dialogue
provided a method to preserve a record of a WATCH session (in the server
process log file).  It is now possible to preserve a session in an independent
log file.  The target selector for the session

  [BROWSER_]
  [plus_log]
  [detach_^]
  [new_log@]

provides a number of options with the default to the browser only.  Further
options allow display to the browser while simultaneously recording in a log
file, and a detached recording mode (see below), and an ephemeral option to
have the server generate another (time-stamped) log file name.  Log files are
automatically flushed once per minute, or with a HTTPD/DO=WATCH=FLUSH.

The log file name is automatically generated when the "Select WATCH Criteria"
interface is opened but can be manually changed to any name desired (the
location must be somewhere the server account can write).  And of course can be
regenerated at any time (a log selection is chosen) using [new_log@].

The default file name consists of "WATCH_yyyymmddhhmm.LOG" and is located in
the server log file directory.


WATCH DETACHED
--------------
WASD v12.2.3 and later provides a 'detached' WATCH where a session is set up
using the "Select WATCH Criteria" interface, with the target selector set to 
[detach_^] and the [WATCH] button clicked.  Prior versions were tied to the
browser for display and if the page was moved from, refreshed or cancelled the
WATCH session would end.  With 'detached' mode the session runs independently
of the browser for the specified duration.

A detached WATCH may be ended at any time by accessing the Server Admin menu
[WATCH] option and clicking the [END!] button, or using HTTPD/DO=WATCH=END.

The Server Admin menu in the date plus run-time statistics panel (bottom-right)
displays a message, "WATCH <time_remaining> (<bytes_written_to_log>)", to alert
the administrator any time a WATCH session is in progress (including but not
exclusively when detached).


EXCLUDING WATCH
---------------
NOT RECOMMENDED!  The WATCH menu and associated functionality can be
compiled-out of the server executable using appropriate macros.  The obvious
one for elimination is "module" WATCHing (as mentioned above, generally
intended for low-level debugging).  By default this is not a compile-time
inclusion and requires a WATCH_MOD=1 definition during building.  "Category"
WATCHing is intended as a general site administration tool and so for all but
the most demanding sites should be left compiled in (as it is by default).  To
exclude build using a WATCH_CAT=0 macro.


WASD_WATCH_ONE_SHOT
-------------------
When defined, This logical name specifies the items shown in a one-shot WATCH
(i.e. the [+] and [W] buttons in a request report).  Without the logical name
all items are displayed.  For example

  $ DEFINE /SYSTEM WASD_WATCH_ONE_SHOT 67109395

displays items connect, request, response, error, http/2 instead of all.  The
integer representing these items can be found in the WATCH report header.

|01-JAN-2024 01:31:22  WATCH REPORT  klaatu.lan:80
|-------------------------------------------------
|HTTPD_SSL 12.2.0 31-DEC-2023 21:36:24.05 DKA100:[WASD_ROOT.][AXP]HTTPD_SSL.EXE
8< snip 8<
|Instances: KLAATU::WASD:80
|Watching: connect, request, response, error, http/2 (67109395) via HTTP/2
|Filter: ONE-SHOT
                                            i.e. here ^^^^^^^^


COMMAND-LINE WATCH
------------------
The command-line version of WATCH accepts a number of variants.

   /WATCH[=NOSTARTUP,][ITEMS=([NO]item[,[NO]item)][,client[,service[,path]]]

The leading keyword NOSTARTUP suppresses WATCH output during server startup.
Items can be any category or module item name (e.g. MAPPING, _MAPURL,
AUTHORIZATION, _AUTH), or ALLCAT or ALLMOD for all categories or modules, and
NOwhatever to turn off a particular WATCH item.  The convenience keyword LIST
provides a list of all category and module keywords that may be used.

   /WATCH=ITEMS=(ALLCAT,NOMAPPING,ALLMOD,NO_FAO)
   /WATCH=NOSTARTUP,ITEMS=(MAPPING,AUTHORIZATION,_CONFIG,_AUTH..)
   /WATCH=ITEMS=(ALLCAT,ALLMOD,NO_FAO,NO_DETAIL),*,*,/HT_ROOT/SRC/*
   /WATCH=LIST


VERSION HISTORY
---------------
16-JUL-2024  MGD  WATCH now can generate standlone report file
                  WATCH can now collect data in a "detached" mode
                  WATCH can collect data after network "trigger"
                  "rabbit hole" restriction removed with revised strategy
29-DEC-2023  MGD  logical name WASD_WATCH_ONE_SHOT defines one-shot items
03-NOV-2022  MGD  use returned DclBegin() status to begin page
17-SEP-2022  MGD  WatchInUse() superceded an in-progress, same-client report
21-FEB-2022  MGD  WatchServerAlertQuotas() 30% every hour, 10% every minute
02-SEP-2021  MGD  WatchData() and WatchDataDump() constrain length
30-MAY-2021  MGD  WatchDataDump() snip and advise redundant data
21-MAY-2021  MGD  client filter substitutes client host for "me" and "moi" :-)
30-NOV-2019  MGD  bugfix; WatchDataDump() CHARS_PER_LINE calculation (sigh)
26-SEP-2019  MGD  more fiddling with WATCH report data delivery
15-JUL-2019  MGD  move WatchSystemPlus() functionality to sysPLUS.c module
25-AUG-2018  MGD  WatchSystemPlus() et.al.
06-JUL-2018  MGD  WatchPeek() add VM and Zone data
28-APR-2018  MGD  refactor Admin..() AST delivery
05-APR-2018  MGD  refactor WatchWrite() using NetWriteBuffered()
                  bugfix; WatchDataDump() CHARS_PER_LINE calculation
03-JAN-2018  MGD  make WATCH item width flexible using initial value 6 digits
                    with leading 3 digits HTTP/2 stream ID followed by 3 digits
                    connection ID number and on overflow increment by 2
18-NOV-2017  MGD  refactor WatchEnd() (yet again)
25-SEP-2017  MGD  WatchReport() move SSL item into Network group
                  WatchShowCluster() and WatchShowSystem() VMS V6.2 obsolete
                  bugfix; WatchEnd() misplaced use of |rqptr|
08-AUG-2017  MGD  relax HTTP/2 "rabbit hole"
                  bugfix; WatchEnd() remove conditional
                  bugfix; WatchBreakDetect() remove WatchEnd() parameter
18-AUG-2016  MGD  bugfix; WatchPeek() |HeaderFao| data
05-AUG-2015  MGD  HTTP/2 support
                  allow browser WATCH to piggyback on CLI WATCH
                  category TIMER moved to INTERNAL and purpose expanded
10-JUL-2007  MGD  add HTTP status filter
27-JUN-2007  MGD  WatchShowCluster()
31-DEC-2006  MGD  add "WebDAV" item
14-OCT-2006  MGD  significantly enhance original filtering
                  StringMatchRegex() instead of decc$match_wild() for filtering
                    (changes the syntax slightly and adds regex matching)
                  added REG_NEWLINE to REGEX_C_FLAGS so that anchors match
                    newlines in strings to support 'Request' filter in WATCH
20-JUL-2004  MGD  HTTP/1.1 compliance data
16-JUL-2004  MGD  remove potential %DCL-W-TKNOVF in WatchShowSystem()
19-JUL-2003  MGD  revise detached process candidate identification,
                  revise process report format
05-MAY-2003  MGD  remove 'quotas' WATCH item, add (string) 'match'
25-MAR-2003  MGD  WatchShowProcess() username
31-JAN-2003  MGD  DCL and DECnet record building items,
                  additional items in WatchShowSystem(),
                  no menu difference between category and module WATCHing
08-OCT-2002  MGD  add scripting account information
15-JUN-2002  MGD  bugfix; RequestUriPtr formatting in WatchPeek()
04-JUN-2002  MGD  reserve WATCH across all instances
31-MAR-2002  MGD  add CC command-line
03-FEB-2002  MGD  add server process log options
03-JAN-2002  MGD  refine command-line interface
29-OCT-2001  MGD  PERSONA_MACRO reporting
15-SEP-2001  MGD  WatchShowSystem(),
                  per-node and per-cluster instances
04-AUG-2001  MGD  support module WATCHing (WATCH_MOD),
                  conditional compilation of both WATCH_CAT and WATCH_MOD,
                  bugfix; check ParseQueryField() in WatchBegin() for NULL
18-APR-2001  MGD  move TCP/IP agent analysis to NetTcpIpAgentInfo()
14-MAR-2001  MGD  add WatchPeek() throttle, proxy authorization
06-JAN-2001  MGD  DETAIL category
01-OCT-2000  MGD  DCL task changes
26-AUG-2000  MGD  integrate WATCH peek and processing into WatchBegin()
17-JUN-2000  MGD  add "quotas" as a WATCH item,
                  bugfix; WatchCliSettings() storage
28-MAY-2000  MGD  add process quotas, using $getjpi ...lm values from startup
08-MAY-2000  MGD  make path filter a path/track filter
04-MAR-2000  MGD  use FaolToNet(), et.al.
02-JAN-2000  MGD  add ODS to WatchPackageInfo()
10-NOV-1999  MGD  use decc$match_wild() for the WatchFilter()
30-OCT-1999  MGD  dignified with a module of its own (unbundled from ADMIN.C)
07-NOV-1998  MGD  initial (as part of ADMIN.C)
*/
/*****************************************************************************/

#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 <ctype.h>
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

/* VMS related header files */
#include <descrip.h>
#include <dvidef.h>
#include <dvsdef.h>
#include <iodef.h>
#include <jpidef.h>
#include <libdef.h>
#include <libdtdef.h>
#include <lnmdef.h>
#include <prvdef.h>
#include <ssdef.h>
#include <stsdef.h>
#include <syidef.h>

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

/* some extra prototypes required for 'watchfunc.h' */
SesolaClientGetCert (void*);
SesolaNetAccept (void*);
SesolaNetEnd (void*);
SesolaNetClientConnect (void*);
SesolaNetClientShutdown (void*);

#include "watchfunc.h"

#define WASD_MODULE "WATCH"

#define DBUG 0

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

char  WatchItemHeader [] =
"|Time_______|Module__|Line|Item!#*_|Category__|Event...|\n";

char  ErrorWatchAuthNeeded [] =
         "Authorization must be enabled to access WATCH!",
      ErrorWatchPersonaNeeded [] =
          "/PERSONA must be enabled to attempt to show this process.",
      ErrorWatchBufferOvf [] = "*ERROR* sys$fao() BUFFEROVF",
      ErrorWatchNumber [] = "Not found in request or HTTP/2 lists.",
#if !WATCH_CAT
      ErrorWatchNoCategory [] = "Category WATCHing is not a compiled option!",
#endif /* !WATCH_CAT */
#if !WATCH_MOD
      ErrorWatchNoModule [] = "Module WATCHing is not a compiled option!",
#endif /* !WATCH_MOD */
      ErrorWatchCannotPeek [] = "Cannot PEEK!",
      ErrorWatchQueryString [] = "WATCH query string problem (no such item).",
      ErrorWatchSelf [] = "Cannot WATCH yourself!",
      ErrorWatchSysFao [] = "*ERROR* sys$fao()",
      ErrorWatchUrlEncoding [] = "URL encoding (%xx) problem!";

char  WatchQuotasAlert [32];

BOOL  WatchIsNoticed;

static ulong  WatchRmiMemErrs;

WATCH_STRUCT  Watch;

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

extern BOOL  DclPersonaServicesAvailable,
             DclScriptDetachProcess,
             LoggingEnabled,
#if ODS_DIRECT
             OdsDirect,
#endif /* ODS_DIRECT */
             OdsExtended,
             OperateWithSysPrv,
             PersonaMacro,
             ProtocolHttpsConfigured,
             ProxyServingEnabled;

extern int  DclMailboxBytLmRequired,
            EfnWait,
            GzipFindImageStatus,
            HttpdTickSecond,
            InstanceNodeCurrent,
            NetAcceptBytLmRequired,
            NetListenBytLmRequired,
            SesolaGblSecStructSize;

extern int64  HttpdTime64;

extern ulong  MailboxMask[],
              ProcessRightsIdent[],
              WorldMask[];

extern ushort  HttpdTime7[];

extern char  BuildDateTime[],
             CliScriptAs[],
             CommandLine[],
             ErrorSanityCheck[],
             HttpdScriptAsUserName[],
             ServerHostPort[],
             SoftwareID[],
             TcpIpAgentInfo[],
             TimeGmtString[];

extern char  *GzipZlibNamePtr,
             *GzipZlibVersionPtr;

extern CONFIG_STRUCT  Config;
extern MSG_STRUCT  Msgs;
extern LIST_HEAD  Http2List;
extern HTTPD_GBLSEC  *HttpdGblSecPtr;
extern HTTPD_PROCESS  HttpdProcess;
extern LIST_HEAD  RequestList;
extern REQUEST_STRUCT  RequestKludge;
extern SUPERVISOR_LIST  SupervisorListArray[];
extern SYS_INFO  SysInfo;

/*****************************************************************************/
/*
Check if the watch facility is already in use, report error if it is.  Provide
form for selection of WATCH parameters.
*/ 

void WatchReport (REQUEST_STRUCT *rqptr)

{
#if WATCH_CAT

   static char  ResponseFao [] =
"<style type=\"text/css\">\n\
.sbttl { display:inline-block; padding-top:0.3em; }\n\
input[type=radio] { margin:0 0 0 0.4em; vertical-align:text-top; }\n\
input[type=text] { font-family:monospace; margin:0; \
vertical-align:text-bottom; }\n\
</style>\n\
<script>\n\
\
var logFileIndex = 0;\n\
\
function logFile() {\n\
   var lfile = document.getElementsByName(\'lfile\')[0];\n\
   var lname = document.getElementsByName(\'lname\')[0];\n\
   if (lfile.value == \'*\') {\n\
      newLog();\n\
      lfile.selectedIndex = logFileIndex;\n\
   }\n\
   logFileIndex = lfile.selectedIndex;\n\
   if (lfile.value == \'0\')\n\
      lname.readOnly = true;\n\
   else\n\
      lname.readOnly = false;\n\
}\n\
\
function newLog() {\n\
   var url = \'/watch?log\';\n\
   xhr = new XMLHttpRequest();\n\
   xhr.open(\'GET\', url, true);\n\
   xhr.responseType = \'text\';\n\
\
   xhr.onreadystatechange = function() {\n\
      if (this.readyState == 4 && this.status == 200) {\n\
         var lname = document.getElementsByName(\'lname\')[0];\n\
         lname.value = this.responseText;\n\
      }\n\
   };\n\
\
   xhr.send();\n\
}\n\
\
</script>\n\
<form action=\"!AZ\">\n\
<p><table class=\"ctgry\">\n\
<tr><th class=\"ctttl\">Select WATCH Criteria</th></tr>\n\
<tr><td>\n\
\
<table class=\"lftlft\">\n\
<tr><td>\n\
<a href=\"" ADMIN_REPORT_WATCH_STRUCT "\" \
style=\"color:#000000;text-decoration:none;\">\
\
<span class=\"sbttl\">Request</span></a>\n\
<br><input type=\"checkbox\" name=\"rqp\" value=\"1\" checked> Processing\n\
<br><input type=\"checkbox\" name=\"rqh\" value=\"1\"> Header\n\
<br><input type=\"checkbox\" name=\"rqb\" value=\"1\"> Body\n\
<br><span class=\"sbttl\">Response</span></b><sup>&nbsp;</sup>\n\
<br><input type=\"checkbox\" name=\"rsp\" value=\"1\" checked> Processing\n\
<br><input type=\"checkbox\" name=\"rsh\" value=\"1\"> Header\n\
<br><input type=\"checkbox\" name=\"rsb\" value=\"1\"> Body\n\
</td><td>\n\
\
<span class=\"sbttl\">General</span>\n\
<br><input type=\"checkbox\" name=\"con\" value=\"1\" checked> Connection\n\
<br><input type=\"checkbox\" name=\"map\" value=\"1\"> Mapping\n\
<br><input type=\"checkbox\" name=\"aut\" value=\"1\"> Authorization\n\
<br><input type=\"checkbox\" name=\"err\" value=\"1\" checked> Error\n\
<br><input type=\"checkbox\" name=\"cgi\" value=\"1\"> CGI\n\
<br><input type=\"checkbox\" name=\"dcl\" value=\"1\"> DCL\n\
<br><input type=\"checkbox\" name=\"dnt\" value=\"1\"> DECnet\n\
<br><input type=\"checkbox\" name=\"dav\" value=\"1\"> WebDAV\n\
</nobr></td><td>\n\
\
<span class=\"sbttl\">Network</span>\n\
<br><input type=\"checkbox\" name=\"net\" value=\"1\"> Activity\n\
<br><input type=\"checkbox\" name=\"oct\" value=\"1\"> Data\n\
<br><input type=\"checkbox\" name=\"ht2\" value=\"1\"> HTTP/2\n\
<br><input type=\"checkbox\" name=\"ssl\" value=\"1\"> !AZSSL!AZ\n\
\
<br><span class=\"sbttl\">Other</span><sup>&nbsp;</sup>\n\
<br><input type=\"checkbox\" name=\"log\" value=\"1\"> !AZLogging!AZ\
<br><input type=\"checkbox\" name=\"mat\" value=\"1\"> Match\n\
<br><input type=\"checkbox\" name=\"scr\" value=\"1\"> Script\n\
&nbsp;&nbsp;&nbsp;&nbsp;\n\
<br><input type=\"checkbox\" name=\"int\" value=\"1\"> Internal\n\
</td><td>\n\
\
<span class=\"sbttl\">Proxy</span>\n\
!AZ\
<br><input type=\"checkbox\" name=\"pxy\" value=\"1\"> Processing\n\
<br><input type=\"checkbox\" name=\"prh\" value=\"1\"> Request Header\n\
<br><input type=\"checkbox\" name=\"prb\" value=\"1\"> Request Body\n\
<br><input type=\"checkbox\" name=\"psh\" value=\"1\"> Response Header\n\
<br><input type=\"checkbox\" name=\"psb\" value=\"1\"> Response Body\n\
<br><input type=\"checkbox\" name=\"prk\" value=\"1\"> Rework\n\
!AZ\
</td></tr>\n\
</table>\n\
\
!AZ\
\
<table class=\"lftlft\" style=\"margin-top:-0.7em;\">\n\
<tr><td>\n\
<table class=\"lftlft\">\n\
<tr><td colspan=\"2\"><span class=\"sbttl\">Filtering</span></td>\
<td colspan=\"2\" style=\"vertical-align:bottom;\">\
&nbsp;in&nbsp;out</td></tr>\n\
\
<tr><td colspan=\"2\">\n\
<input type=\"checkbox\" name=\"htp2\" value=\"2\">&nbsp;2&nbsp;\n\
<input type=\"checkbox\" name=\"htp11\" value=\"11\">&nbsp;1.1&nbsp;\n\
<input type=\"checkbox\" name=\"htp10\" value=\"10\">&nbsp;1.0&nbsp;\n\
<input type=\"checkbox\" name=\"htp09\" value=\"09\">&nbsp;0.9\n\
</td><td>\n\
<input type=\"radio\" name=\"htp\" value=\"i\" checked>\
<input type=\"radio\" name=\"htp\" value=\"o\">&nbsp;</td>\
<td>Protocol</td></tr>\n\
\
<tr><td colspan=\"2\">\
<input type=\"text\" name=\"clf\" size=\"40\" value=\"\" \
onfocus=\"this.select()\">\
</td><td>\
<input type=\"radio\" name=\"clo\" value=\"i\" checked>\
<input type=\"radio\" name=\"clo\" value=\"o\"></td><td>Client\
<span style=\"display:inline-block;position:relative;top:-0.2em;\
transform:scale(0.85);\">\
<input type=\"checkbox\" name=\"moi\" value=\"1\">moi</span>\
</td></tr>\n\
\
<tr><td colspan=\"2\">\
<input type=\"text\" name=\"sef\" size=\"40\" value=\"\" \
onfocus=\"this.select()\">\
</td><td>\
<input type=\"radio\" name=\"seo\" value=\"i\" checked>\
<input type=\"radio\" name=\"seo\" value=\"o\"></td>\
<td>Service</td></tr>\n\
\
<tr><td colspan=\"2\">\
<input type=\"text\" name=\"rhf\" size=\"40\" value=\"\" \
onfocus=\"this.select()\">\
</td><td>\
<input type=\"radio\" name=\"rho\" value=\"i\" checked>\
<input type=\"radio\" name=\"rho\" value=\"o\"></td>\
<td>Request&nbsp;**</td></tr>\n\
\
<tr><td colspan=\"2\">\
<input type=\"text\" name=\"uri\" size=\"40\" value=\"\" \
onfocus=\"this.select()\">\
</td><td>\
<input type=\"radio\" name=\"uro\" value=\"i\" checked>\
<input type=\"radio\" name=\"uro\" value=\"o\"></td>\
<td>URI&nbsp;**</td></tr>\n\
\
<tr><td style=\"margin-right:0;padding-right:0;\">\
<input type=\"text\" name=\"arf\" size=\"18\" value=\"\" \
onfocus=\"this.select()\">\
</td><td class=\"targht\" style=\"margin-left:0;padding-left:0;\">\
<input type=\"text\" name=\"auf\" size=\"18\" value=\"\" \
onfocus=\"this.select()\"></td>\
<td><input type=\"radio\" name=\"auo\" value=\"i\" checked>\
<input type=\"radio\" name=\"auo\" value=\"o\"></td>\
<td>Realm &amp; User&nbsp;</td></tr>\n\
\
<tr><td colspan=\"2\" class=\"targht\">\
<input type=\"text\" name=\"sts\" size=\"10\" maxlength=\"10\" value=\"\" \
onfocus=\"this.select()\">\
</td><td>\
<input type=\"radio\" name=\"sto\" value=\"i\" checked>\
<input type=\"radio\" disabled=\"disabled\"></td>\
<td>HTTP Status</td></tr>\n\
\
<tr><td colspan=\"2\">\
<input type=\"text\" name=\"trx\" size=\"40\" maxlength=\"40\" value=\"\" \
onfocus=\"this.select()\">\
</td><td>\n\
<div style=\"display:inline-block;position:relative;top:0.7em;left:0.2em;\">\
<input type=\"checkbox\" name=\"trplus\" value=\"1\">&#65291;</div></td>\
<td>Rx\
<div style=\"display:inline-block;position:relative;top:0.8em;left:0.4em;\">\
Trigger&nbsp;**</div>\
</td></tr>\n\
\
<tr><td colspan=\"2\">\
<input type=\"text\" name=\"ttx\" size=\"40\" maxlength=\"40\" value=\"\" \
onfocus=\"this.select()\">\
</td><td></td><td>Tx</td></tr>\n\
\
<tr><td colspan=\"2\"></td>\
<td colspan=\"2\" style=\"padding-left:1em;font-size:85%;font-style:italic;\">\
** can be URL-encoded</td></tr>\n\
\
</table>\n\
</td></tr>\n\
</table>\n\
\
<table class=\"lftlft\">\n\
<tr><td style=\"font-size:1em;vertical-align:center;\">\n\
<select name=\"dul\">\n\
<option> 30\n\
<option selected> 60\n\
<option> 120\n\
<option> 300\n\
<option> 600\n\
<option value=\"3600\"> 1hr\n\
<option value=\"28800\"> 8hrs\n\
<option value=\"86400\"> 24hrs\n\
<option value=\"31536000\"> &#8734;\n\
</select>\n\
&nbsp;or&nbsp;\n\
<input type=\"text\" name=\"dut\" size=\"5\" value=\"\"> Seconds Duration\n\
\
<p style=\"margin-top:0.6em;\">\
<select style=\"text-align:center;font-size:100%;\" \
name=\"lfile\" onchange=\"logFile()\">\n\
<option value=\"0\" selected>BROWSER\n\
<option value=\"1\">plus&nbsp;log\n\
<option value=\"2\">detach&thinsp;&#8682;\n\
<option value=\"*\">new&thinsp;log&thinsp;&#8635;\n\
</select>\n\
&nbsp;<input style=\"font-size:100%;\" \
type=\"text\" name=\"lname\" size=\"!UL\" maxlength=\"96\" value=\"!AZ\">\
<p><input style=\"font-size:100%;\" type=\"submit\" value=\" WATCH \"> &nbsp; \
<input type=reset value=\" reset \">\n\
</td></tr>\n\
</table>\n\
\
<script>logFile();</script>\n\
\
</td></tr>\n\
</table>\n\
\
</form>\n\
\
</div>\n\
</body>\n\
</html>\n";

#if WATCH_MOD

   static char  WatchModFao [] =
"<table class=\"lftlft\" style=\"margin-top:-0.7em;\">\n\
<tr><td colspan=\"5\"><span class=\"sbttl\">Module</span></td></tr>\n\
<tr><td>\n\
<input type=\"checkbox\" name=\"_aut\" value=\"1\"> AUTH..\n\
<br><input type=\"checkbox\" name=\"_bod\" value=\"1\"> BODY\n\
<br><input type=\"checkbox\" name=\"_cac\" value=\"1\"> CACHE\n\
<br><input type=\"checkbox\" name=\"_cgi\" value=\"1\"> CGI\n\
<br><input type=\"checkbox\" name=\"_con\" value=\"1\"> CONFIG&nbsp;\n\
<br><input type=\"checkbox\" name=\"_dcl\" value=\"1\"> DCL\n\
</td><td>\n\
<input type=\"checkbox\" name=\"_dec\" value=\"1\"> DECNET\n\
<br><input type=\"checkbox\" name=\"_dir\" value=\"1\"> DIR\n\
<br><input type=\"checkbox\" name=\"_fao\" value=\"1\"> FAO\n\
<br><input type=\"checkbox\" name=\"_fil\" value=\"1\"> FILE\n\
<br><input type=\"checkbox\" name=\"_hta\" value=\"1\"> HTADMIN\n\
<br><input type=\"checkbox\" name=\"_ht2\" value=\"1\"> HTTP2..\n\
</td><td>\n\
<input type=\"checkbox\" name=\"_ins\" value=\"1\"> INSTANCE\n\
<br><input type=\"checkbox\" name=\"_map\" value=\"1\"> MAPURL\n\
<br><input type=\"checkbox\" name=\"_met\" value=\"1\"> METACON\n\
<br><input type=\"checkbox\" name=\"_msg\" value=\"1\"> MSG\n\
<br><input type=\"checkbox\" name=\"_net\" value=\"1\"> NET\n\
<br><input type=\"checkbox\" name=\"_ods\" value=\"1\"> ODS\n\
</td><td>\n\
<input type=\"checkbox\" name=\"_pro\" value=\"1\"> PROXY..\n\
<br><input type=\"checkbox\" name=\"_put\" value=\"1\"> PUT\n\
<br><input type=\"checkbox\" name=\"_req\" value=\"1\"> REQUEST\n\
<br><input type=\"checkbox\" name=\"_res\" value=\"1\"> RESPONSE\n\
<br><input type=\"checkbox\" name=\"_ser\" value=\"1\"> SERVICE\n\
<br><input type=\"checkbox\" name=\"_ses\" value=\"1\"> SESOLA..\n\
</td><td>\n\
<input type=\"checkbox\" name=\"_ssi\" value=\"1\"> SSI\n\
<br><input type=\"checkbox\" name=\"_thr\" value=\"1\"> THROTTLE\n\
<br><input type=\"checkbox\" name=\"_upd\" value=\"1\"> UPD\n\
<br><input type=\"checkbox\" name=\"_vm\" value=\"1\"> VM\n\
<br><input type=\"checkbox\" name=\"_dav\" value=\"1\"> WebDAV\n\
<br><input type=\"checkbox\" name=\"_oth\" value=\"1\"> other\n\
<br><input type=\"checkbox\" name=\"_det\" value=\"1\"> detail\n\
</td></tr>\n\
</table>\n";

#else

   static char  WatchModFao [] = "";

#endif /* WATCH_MOD */

   int  status;
   ulong  FaoVector [32];
   ulong  *vecptr;
   char  *sptr;

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

   if (Watch.Disabled)
   {
      rqptr->rqResponse.HttpStatus = 403;
      ErrorGeneral (rqptr, MsgFor(rqptr,MSG_GENERAL_DISABLED), FI_LI);
      AdminEnd (rqptr);
      return;
   }

   if (!rqptr->RemoteUser[0])
   {
      rqptr->rqResponse.HttpStatus = 403;
      ErrorGeneral (rqptr, ErrorWatchAuthNeeded, FI_LI);
      AdminEnd (rqptr);
      return;
   }

   if (WatchInUse (rqptr)) return;

   AdminPageTitle (rqptr, "WATCH Report");

   vecptr = FaoVector;
   *vecptr++ = ADMIN_REPORT_WATCH;

   if (ProtocolHttpsConfigured)
   {
      *vecptr++ = "";
      *vecptr++ = "";
   }
   else
   {
      *vecptr++ = "<i>";
      *vecptr++ = "</i>";
   }

   if (LoggingEnabled)
   {
      *vecptr++ = "";
      *vecptr++ = "";
   }
   else
   {
      *vecptr++ = "<i>";
      *vecptr++ = "</i>";
   }

   if (ProxyServingEnabled)
   {
      *vecptr++ = "";
      *vecptr++ = "";
   }
   else
   {
      *vecptr++ = "<i>\n";
      *vecptr++ = "</i>\n";
   }

#if WATCH_MOD
   *vecptr++ = WatchModFao;
#else
   *vecptr++ = "";
#endif

   sptr = WatchNewLogName();
   *vecptr++ = strlen(sptr);
   *vecptr++ = sptr;

   FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI);
   status = FaolToNet (rqptr, ResponseFao, &FaoVector);
   if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);

   ResponseHeader200 (rqptr, "text/html", &rqptr->NetWriteBufferDsc);

   AdminEnd (rqptr);

#else /* WATCH_CAT */

   rqptr->rqResponse.HttpStatus = 403;
   ErrorGeneral (rqptr, ErrorWatchNoCategory, FI_LI);
   AdminEnd (rqptr);

#endif /* WATCH_CAT */
}

/*****************************************************************************/
/*
Called from REQUEST.C via an XHR request from JavaScript newLog() above.
*/
 
void WatchNewLog (REQUEST_STRUCT *rqptr)

{
   char  *sptr;

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

   rqptr->PersistentRequest = rqptr->PersistentResponse =
      rqptr->rqPathSet.Expired = rqptr->rqPathSet.NoLog = true;
   ResponseHeader200 (rqptr, "text/plain", NULL);
   sptr = WatchNewLogName ();
   NetWrite (rqptr, RequestEnd, sptr, strlen(sptr));
}

/*****************************************************************************/
/*
generate anew log file name returning the null-terminated name string.
*/
 
char* WatchNewLogName ()

{
   static char buf [48];

   char  *sptr;

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

   if (!(sptr = SysTrnLnm ("WASD_WATCH_LOGS")))
      if (!(sptr = SysTrnLnm ("WASD_SERVER_LOGS")))
         sptr = "WASD_SERVER_LOGS:";

   FaoToBuffer (buf, sizeof(buf), NULL,
                "!AZWATCH_!4ZL!2ZL!2ZL!2ZL!2ZL.LOG\0",
                sptr, HttpdTime7[0], HttpdTime7[1], HttpdTime7[2], 
                      HttpdTime7[3], HttpdTime7[4]);
   return (buf);
}

/*****************************************************************************/
/*
This function provides both a WATCH-processing report, and a WATCH-peek report.
This can be a WATCH-only report, peek-only, or peek-then-WATCH. If we're going
to use the WATCH-processing report then we have to do it exclusively, check if
the WATCH facility is already in use, report error if it is.  Parse the query
string WATCH parameters.  Report any errors.  Place the parameters into the
WATCH global storage.  This reserves the WATCH facility for this client via the
'Watch.RequestPtr' (and indicate this with a flag in the request structure,
which will be detected as the request concludes and the WATCH facility released
for reuse).  Generate a plain-text HTTP header and output a WATCH report
heading.  If a WATCH-peek report is requested call WatchPeek() to generate it. 
For a peek-only report we declares the next function AST there.  If
WATCH-processing was only/also requested generate a WATCH-processing header
then return BUT do not declare any AST to continue processing.  The client will
just "hang" there receiving output from WatchThis() via the structure pointed
to by 'Watch.RequestPtr'.  Can be used to WATCH all new requests (matching any
filter criteria of course) or in a "one-shot" mode where a single request is
selected to display all categories at any point during it's processing.
*/ 

void WatchBegin (REQUEST_STRUCT *rqptr)

{
   /* easiest just to wrap with WatchNone() */
   WatchNone (true);
   WatchBegin2 (rqptr);
   WatchNone (false);
}

void WatchBegin2 (REQUEST_STRUCT *rqptr)

{
#if WATCH_CAT

   static char  ResponseFao [] =
"!20%D  WATCH REPORT  !AZ\n\
!#*-\n\
!AZ (!AZ)\n\
!AZ\n\
!AZ\n\
$ CC (!#AZ/!UL) !AZ\n\
!AZ with !UL CPU!%s and !AZ running VMS !AZ \
(!AZ, !AZ, !AZ, ODS-DIRECT !AZ, !&@, REGEX !AZ, \
TO_LOWER_UPPER !UL, lksb$b_valblk[!UL])!AZ\n\
$ HTTPD !AZ\n\
!AZ\
!&@\
Process: !AZ !AZ !AZ !AZ\n\
!AZ\
!&@\
!&@";

   static char  DetachFao [] =
"</pre>\n\
<br>To cancel report at any time, <tt>HTTPD/DO=WATCH=END</tt> or \
<a class=\"abttn bttn425\" href=\"!AZ\">END!!</a>\n\
</body>\n\
</html>\n";

   int  declen, eolcnt, status;
   int64  valueCat64, valueMod64;
   char  *cptr, *qptr, *sptr, *zptr,
         *InstanceClusterPtr,
         *InstanceNodePtr;
   char  Buffer [8192],
         CategoryList [WATCH_CATEGORY_LIST_SIZE],
         FieldName [256],
         FieldValue [WATCH_FILTER_SIZE],
         FilterRequest [WATCH_FILTER_SIZE],
         FilterUri [WATCH_FILTER_SIZE],
         TriggerRx [WATCH_FILTER_SIZE],
         TriggerTx [WATCH_FILTER_SIZE];
   ushort  slen;
   ulong  ucnt;
   ulong  *vecptr;
   ulong  FaoVector [96];
   HTTP2_STRUCT  *h2eptr;
   REQUEST_STRUCT  *rqeptr;
   WATCH_STRUCT  WatchConfig;

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

   if (Watch.Disabled)
   {
      rqptr->rqResponse.HttpStatus = 403;
      ErrorGeneral (rqptr, MsgFor(rqptr,MSG_GENERAL_DISABLED), FI_LI);
      AdminEnd (rqptr);
      return;
   }

   if (!rqptr->RemoteUser[0])
   {
      rqptr->rqResponse.HttpStatus = 403;
      ErrorGeneral (rqptr, ErrorWatchAuthNeeded, FI_LI);
      AdminEnd (rqptr);
      return;
   }

   if (WatchInUse (rqptr)) return;

   memset (&WatchConfig, 0, sizeof(WatchConfig));
   WatchConfig.FilterStatus = -1;
   FieldName[0] = FieldValue[0] = FilterRequest[0] =
      FilterUri[0] = TriggerRx[0] = TriggerTx[0] = '\0';

   if (rqptr->rqHeader.QueryStringLength)
   {
      /***************************/
      /* build report parameters */
      /***************************/

      qptr = rqptr->rqHeader.QueryStringPtr;
      while (*qptr)
      {
         status = StringParseQuery (&qptr, FieldName, sizeof(FieldName),
                                           FieldValue, sizeof(FieldValue));
         if (VMSnok (status))
         {
            WatchReset ();
            if (status == SS$_IVCHAR) rqptr->rqResponse.HttpStatus = 400;
            rqptr->rqResponse.ErrorTextPtr = "parsing query string";
            ErrorVmsStatus (rqptr, status, FI_LI);
            AdminEnd (rqptr);
            return;
         }

         if (strsame (FieldName, "at", -1) && FieldValue[0])
         {
            WatchConfig.DoPeek = true;
            WatchConfig.ConnectNumber = strtol (FieldValue, NULL, 10);
         }
         else
         if (strsame (FieldName, "this", -1) && FieldValue[0])
         {
            WatchConfig.Category |= WATCH_ONE_SHOT_FLAG;
            WatchConfig.ConnectNumber = strtol (FieldValue, NULL, 10);
         }
         else
         if (strsame (FieldName, "aut", -1) && FieldValue[0])
            WatchConfig.Category |= WATCH_AUTH;
         else
         if (strsame (FieldName, "cgi", -1) && FieldValue[0])
            WatchConfig.Category |= WATCH_CGI;
         else
         if (strsame (FieldName, "con", -1) && FieldValue[0])
            WatchConfig.Category |= WATCH_CONNECT;
         else
         if (strsame (FieldName, "dcl", -1) && FieldValue[0])
         WatchConfig.Category |= WATCH_DCL;
         else
         if (strsame (FieldName, "dnt", -1) && FieldValue[0])
            WatchConfig.Category |= WATCH_DECNET;
         else
         if (strsame (FieldName, "err", -1) && FieldValue[0])
            WatchConfig.Category |= WATCH_ERROR;
         else
         if (strsame (FieldName, "ht2", -1) && FieldValue[0])
            WatchConfig.Category |= WATCH_HTTP2;
         else
         if (strsame (FieldName, "log", -1) && FieldValue[0])
            WatchConfig.Category |= WATCH_LOG;
         else
         if (strsame (FieldName, "map", -1) && FieldValue[0])
            WatchConfig.Category |= WATCH_MAPPING;
         else
         if (strsame (FieldName, "mat", -1) && FieldValue[0])
            WatchConfig.Category |= WATCH_MATCH;
         else
         if (strsame (FieldName, "net", -1) && FieldValue[0])
            WatchConfig.Category |= WATCH_NETWORK;
         else
         if (strsame (FieldName, "oct", -1) && FieldValue[0])
            WatchConfig.Category |= WATCH_NETWORK_OCTETS;
         else
         if (strsame (FieldName, "pxy", -1) && FieldValue[0])
            WatchConfig.Category |= WATCH_PROXY;
         else
         if (strsame (FieldName, "prh", -1) && FieldValue[0])
            WatchConfig.Category |= WATCH_PROXY_REQU_HDR;
         else
         if (strsame (FieldName, "prb", -1) && FieldValue[0])
            WatchConfig.Category |= WATCH_PROXY_REQU_BDY;
         else
         if (strsame (FieldName, "psh", -1) && FieldValue[0])
            WatchConfig.Category |= WATCH_PROXY_RESP_HDR;
         else
         if (strsame (FieldName, "psb", -1) && FieldValue[0])
            WatchConfig.Category |= WATCH_PROXY_RESP_BDY;
         else
         if (strsame (FieldName, "prk", -1) && FieldValue[0])
            WatchConfig.Category |= WATCH_PROXY_REWORK;
         else
         if (strsame (FieldName, "rqp", -1) && FieldValue[0])
            WatchConfig.Category |= WATCH_REQUEST;
         else
         if (strsame (FieldName, "rqb", -1) && FieldValue[0])
            WatchConfig.Category |= WATCH_REQUEST_BODY;
         else
         if (strsame (FieldName, "rqh", -1) && FieldValue[0])
            WatchConfig.Category |= WATCH_REQUEST_HEADER;
         else
         if (strsame (FieldName, "rsp", -1) && FieldValue[0])
            WatchConfig.Category |= WATCH_RESPONSE;
         else
         if (strsame (FieldName, "rsb", -1) && FieldValue[0])
            WatchConfig.Category |= WATCH_RESPONSE_BODY;
         else
         if (strsame (FieldName, "rsh", -1) && FieldValue[0])
            WatchConfig.Category |= WATCH_RESPONSE_HEADER;
         else
         if (strsame (FieldName, "scr", -1) && FieldValue[0])
            WatchConfig.Category |= WATCH_SCRIPT;
         else
         if (strsame (FieldName, "ssl", -1) && FieldValue[0])
            WatchConfig.Category |= WATCH_SESOLA;
         else
         if (strsame (FieldName, "int", -1) && FieldValue[0])
            WatchConfig.Category |= WATCH_INTERNAL;
         else
         if (strsame (FieldName, "dav", -1) && FieldValue[0])
            WatchConfig.Category |= WATCH_WEBDAV;
         else
         if (strsame (FieldName, "htp09", -1)) {
            if (FieldValue[0]) WatchConfig.FilterHttp09 = true;
         }
         else
         if (strsame (FieldName, "htp10", -1)) {
            if (FieldValue[0]) WatchConfig.FilterHttp10 = true;
         }
         else
         if (strsame (FieldName, "htp11", -1)) {
            if (FieldValue[0]) WatchConfig.FilterHttp11 = true;
         }
         else
         if (strsame (FieldName, "htp2", -1)) {
            if (FieldValue[0]) WatchConfig.FilterHttp2 = true;
         }
         else
         if (strsame (FieldName, "htp", -1)) {
            if (FieldValue[0] == 'o') WatchConfig.FilterOutHttp = true;
         }
         else
         if (strsame (FieldName, "clf", -1))
         {
            if (strsame (FieldValue, "me", -1) ||
                strsame (FieldValue, "moi", -1))
               strzcpy (WatchConfig.FilterClient,
TcpIpAddressToString (&rqptr->ClientPtr->Lookup.IpAddress, 0),
                        sizeof(WatchConfig.FilterClient));
            else
               strzcpy (WatchConfig.FilterClient, FieldValue,
                        sizeof(WatchConfig.FilterClient));
         }
         else
         if (strsame (FieldName, "moi", -1)) {
            strzcpy (WatchConfig.FilterClient,
TcpIpAddressToString (&rqptr->ClientPtr->Lookup.IpAddress, 0),
                     sizeof(WatchConfig.FilterClient));
         }
         else
         if (strsame (FieldName, "clo", -1)) {
            if (FieldValue[0] == 'o') WatchConfig.FilterOutClient = true;
         }
         else
         if (strsame (FieldName, "sef", -1))
            strzcpy (WatchConfig.FilterService, FieldValue,
                     sizeof(WatchConfig.FilterService));
         else
         if (strsame (FieldName, "seo", -1)) {
            if (FieldValue[0] == 'o') WatchConfig.FilterOutService = true;
         }
         else
         if (strsame (FieldName, "rhf", -1))
            strzcpy (FilterRequest, FieldValue, sizeof(FilterRequest));
         else
         if (strsame (FieldName, "rho", -1)) {
            if (FieldValue[0] == 'o') WatchConfig.FilterOutRequest = true;
         }
         else
         if (strsame (FieldName, "uri", -1) ||
             /* pre-v12.3 compatibility */ 
             strsame (FieldName, "paf", -1))
            strzcpy (FilterUri, FieldValue, sizeof(FilterUri));
         else
         if (strsame (FieldName, "uro", -1) ||
             /* pre-v11 compatibility */ 
             strsame (FieldName, "pao", -1)) {
            if (FieldValue[0] == 'o') WatchConfig.FilterOutURI = true;
         }
         else
         if (strsame (FieldName, "sts", -1)) {
            if (isdigit(FieldValue[0]))
               WatchConfig.FilterStatus = atoi(FieldValue);
         }
         else
         if (strsame (FieldName, "sto", -1)) {
            /* do absolutely nothing! */
         }
         else
         if (strsame (FieldName, "trx", -1))
            strzcpy (TriggerRx, FieldValue, sizeof(TriggerRx));
         else
         if (strsame (FieldName, "trplus", -1)) {
            if (FieldValue[0]) WatchConfig.TriggerPlus = true;
         }
         else
         if (strsame (FieldName, "ttx", -1))
            strzcpy (TriggerTx, FieldValue, sizeof(TriggerTx));
         else
         if (strsame (FieldName, "arf", -1))
            strzcpy (WatchConfig.FilterRealm, FieldValue,
                     sizeof(WatchConfig.FilterRealm));
         else
         if (strsame (FieldName, "aro", -1)) {
            if (FieldValue[0] == 'o') WatchConfig.FilterOutRealm = true;
         }
         else
         if (strsame (FieldName, "auf", -1))
            strzcpy (WatchConfig.FilterUser, FieldValue,
                     sizeof(WatchConfig.FilterUser));
         else
         if (strsame (FieldName, "auo", -1)) {
            if (FieldValue[0] == 'o') WatchConfig.FilterOutUser = true;
         }
         else
         if (strsame (FieldName, "lfile", -1))
            /* 0 browser only, 1 plus log file, 2 log file only, 3 detached */
            WatchConfig.LogFile = atoi (FieldValue);
         else
         if (strsame (FieldName, "lname", -1))
         {
            zptr = (sptr = WatchConfig.LogFileName) +
                   sizeof(WatchConfig.LogFileName)-1;
            for (cptr = FieldValue; *cptr; cptr++)
               if (!isspace(*cptr)) *sptr++ = *cptr;
            *sptr = '\0';
         }
         else
         if (strsame (FieldName, "dul", -1) ||
             strsame (FieldName, "dut", -1) ||
             strsame (FieldName, "sec", -1) ||
             strsame (FieldName, "seconds", -1))
         {
            for (cptr = FieldValue; *cptr && !isdigit(*cptr); cptr++);
            if (*cptr) WatchConfig.DurationSeconds = atoi (cptr);
         }
         else

#if WATCH_MOD

         if (strsame (FieldName, "_aut", -1) && FieldValue[0])
            WatchConfig.Module |= WATCH_MOD_AUTH;
         else
         if (strsame (FieldName, "_bod", -1) && FieldValue[0])
            WatchConfig.Module |= WATCH_MOD_BODY;
         else
         if (strsame (FieldName, "_cac", -1) && FieldValue[0])
            WatchConfig.Module |= WATCH_MOD_CACHE;
         else
         if (strsame (FieldName, "_cgi", -1) && FieldValue[0])
            WatchConfig.Module |= WATCH_MOD_CGI;
         else
         if (strsame (FieldName, "_con", -1) && FieldValue[0])
            WatchConfig.Module |= WATCH_MOD_CONFIG;
         else
         if (strsame (FieldName, "_dcl", -1) && FieldValue[0])
            WatchConfig.Module |= WATCH_MOD_DCL;
         else
         if (strsame (FieldName, "_det", -1) && FieldValue[0])
            WatchConfig.Module |= WATCH_MOD__DETAIL;
         else
         if (strsame (FieldName, "_dec", -1) && FieldValue[0])
            WatchConfig.Module |= WATCH_MOD_DECNET;
         else
         if (strsame (FieldName, "_dir", -1) && FieldValue[0])
            WatchConfig.Module |= WATCH_MOD_DIR;
         else
         if (strsame (FieldName, "_fao", -1) && FieldValue[0])
            WatchConfig.Module |= WATCH_MOD_FAO;
         else
         if (strsame (FieldName, "_fil", -1) && FieldValue[0])
            WatchConfig.Module |= WATCH_MOD_FILE;
         else
         if (strsame (FieldName, "_hta", -1) && FieldValue[0])
            WatchConfig.Module |= WATCH_MOD_HTADMIN;
         else
         if (strsame (FieldName, "_ht2", -1) && FieldValue[0])
            WatchConfig.Module |= WATCH_MOD_HTTP2;
         else
         if (strsame (FieldName, "_ins", -1) && FieldValue[0])
            WatchConfig.Module |= WATCH_MOD_INSTANCE;
         else
         if (strsame (FieldName, "_map", -1) && FieldValue[0])
            WatchConfig.Module |= WATCH_MOD_MAPURL;
         else
         if (strsame (FieldName, "_met", -1) && FieldValue[0])
            WatchConfig.Module |= WATCH_MOD_METACON;
         else
         if (strsame (FieldName, "_msg", -1) && FieldValue[0])
            WatchConfig.Module |= WATCH_MOD_MSG;
         else
         if (strsame (FieldName, "_net", -1) && FieldValue[0])
            WatchConfig.Module |= WATCH_MOD_NET;
         else
         if (strsame (FieldName, "_ods", -1) && FieldValue[0])
            WatchConfig.Module |= WATCH_MOD_ODS;
         else
         if (strsame (FieldName, "_oth", -1) && FieldValue[0])
            WatchConfig.Module |= WATCH_MOD__OTHER;
         else
         if (strsame (FieldName, "_pro", -1) && FieldValue[0])
            WatchConfig.Module |= WATCH_MOD_PROXY;
         else
         if (strsame (FieldName, "_put", -1) && FieldValue[0])
            WatchConfig.Module |= WATCH_MOD_PUT;
         else
         if (strsame (FieldName, "_req", -1) && FieldValue[0])
            WatchConfig.Module |= WATCH_MOD_REQUEST;
         else
         if (strsame (FieldName, "_res", -1) && FieldValue[0])
            WatchConfig.Module |= WATCH_MOD_RESPONSE;
         else
         if (strsame (FieldName, "_ser", -1) && FieldValue[0])
            WatchConfig.Module |= WATCH_MOD_SERVICE;
         else
         if (strsame (FieldName, "_ses", -1) && FieldValue[0])
            WatchConfig.Module |= WATCH_MOD_SESOLA;
         else
         if (strsame (FieldName, "_ssi", -1) && FieldValue[0])
            WatchConfig.Module |= WATCH_MOD_SSI;
         else
         if (strsame (FieldName, "_thr", -1) && FieldValue[0])
            WatchConfig.Module |= WATCH_MOD_THROTTLE;
         else
         if (strsame (FieldName, "_upd", -1) && FieldValue[0])
            WatchConfig.Module |= WATCH_MOD_UPD;
         else
         if (strsame (FieldName, "_vm", -1) && FieldValue[0])
            WatchConfig.Module |= WATCH_MOD_VM;
         else
         if (strsame (FieldName, "_dav", -1) && FieldValue[0])
            WatchConfig.Module |= WATCH_MOD_WEBDAV;
         else

#else

         if (FieldName[0] == '_')
         {
            WatchReset ();
            rqptr->rqResponse.HttpStatus = 403;
            ErrorGeneral (rqptr, ErrorWatchNoModule, FI_LI);
            AdminEnd (rqptr);
            return;
         }
         else

#endif /* WATCH_MOD */

         {
            WatchReset ();
            rqptr->rqResponse.HttpStatus = 403;
            ErrorGeneral (rqptr, ErrorWatchQueryString, FI_LI);
            AdminEnd (rqptr);
            return;
         }
      }
   }

   declen = 0;
   if (declen >= 0 && FilterUri[0])
   {
      strzcpy (WatchConfig.FilterUri, FilterUri,
               sizeof(WatchConfig.FilterUri));
      declen = StringUrlDecode(WatchConfig.FilterUri);
   }
   if (declen >= 0 && FilterRequest[0])
   {
      strzcpy (WatchConfig.FilterRequest, FilterRequest,
               sizeof(WatchConfig.FilterRequest));
      declen = StringUrlDecode(WatchConfig.FilterRequest);
   }
   if (declen >= 0 && TriggerRx[0])
   {
      strzcpy (WatchConfig.TriggerRx, TriggerRx,
               sizeof(WatchConfig.TriggerRx));
      declen = StringUrlDecode(WatchConfig.TriggerRx);
      if (declen > 0) WatchConfig.TriggerRxCount = declen;
   }
   if (declen >= 0 && TriggerTx[0])
   {
      strzcpy (WatchConfig.TriggerTx, TriggerTx,
               sizeof(WatchConfig.TriggerTx));
      declen = StringUrlDecode(WatchConfig.TriggerTx);
      if (declen > 0) WatchConfig.TriggerTxCount = declen;
   }
   if (declen < 0)
   {
      WatchReset ();
      rqptr->rqResponse.HttpStatus = 403;
      ErrorGeneral (rqptr, ErrorWatchUrlEncoding, FI_LI);
      AdminEnd (rqptr);
      return;
   }

   if (WatchConfig.Category || WatchConfig.Module) WatchConfig.DoWatch = true;

   if (!WatchConfig.DurationSeconds) WatchConfig.DurationSeconds = 60 * 60 * 24;
 
   /* 0 browser only, 1 plus log file, 2 log file only, 3 detached */
   if (WatchConfig.LogFile < 0)
      WatchConfig.LogFile = 0;
   else
   if (WatchConfig.LogFile > 3)
      WatchConfig.LogFile = 3;

   WatchConfig.SecondsToGo = WatchConfig.DurationSeconds;

   if (!WatchConfig.DoPeek && !WatchConfig.DoWatch)
   {
      WatchReset ();
      rqptr->rqResponse.HttpStatus = 403;
      ErrorGeneral (rqptr, ErrorWatchQueryString, FI_LI);
      AdminEnd (rqptr);
      return;
   }

   WatchSetWatch (rqptr, 0);

   if (WatchConfig.ConnectNumber)
   {
      /* find this connection number in the current request list */
      h2eptr = NULL;
      for (rqeptr = LIST_GET_HEAD(&RequestList);
           rqeptr != NULL;
           rqeptr = LIST_GET_NEXT(rqeptr))
         if (rqeptr->ConnectNumber == WatchConfig.ConnectNumber) break;
      if (rqeptr == NULL)
      {
         /* if not in the request list look in the HTTP/2 list */
         for (h2eptr = LIST_GET_HEAD(&Http2List);
              h2eptr != NULL;
              h2eptr = LIST_GET_NEXT(h2eptr))
            if (h2eptr->ConnectNumber == WatchConfig.ConnectNumber) break;
      }

      if (rqeptr == NULL && h2eptr == NULL)
      {
         WatchReset ();
         rqptr->rqResponse.HttpStatus = 400;
         ErrorGeneral (rqptr, ErrorWatchNumber, FI_LI);
         AdminEnd (rqptr);
         return;
      }

      if (rqeptr == rqptr)
      {
         WatchReset ();
         rqptr->rqResponse.HttpStatus = 400;
         ErrorGeneral (rqptr, ErrorWatchSelf, FI_LI);
         AdminEnd (rqptr);
         return;
      }

      if (h2eptr &&
          rqptr->Http2Ptr &&
          rqptr->Http2Ptr->ConnectNumber == h2eptr->ConnectNumber)
      {
         WatchReset ();
         rqptr->rqResponse.HttpStatus = 400;
         ErrorGeneral (rqptr, ErrorWatchSelf, FI_LI);
         AdminEnd (rqptr);
         return;
      }

      if (WatchConfig.Category & WATCH_ONE_SHOT_FLAG)
      {
         if (rqeptr)
         {
            rqeptr->WatchItem = WatchSetWatch (NULL, WATCH_NEW_ITEM);
            rqeptr->WatchItem |= WATCH_ITEM_ONE_SHOT_FLAG;
            WatchSetWatch (rqeptr, rqeptr->WatchItem);
            if (rqeptr->DclTaskPtr)
               rqeptr->DclTaskPtr->WatchItem = rqeptr->WatchItem;
            if (rqeptr->DECnetTaskPtr)
               rqeptr->DECnetTaskPtr->WatchItem = rqeptr->WatchItem;
            if (rqeptr->ProxyTaskPtr)
               rqeptr->ProxyTaskPtr->WatchItem = rqeptr->WatchItem;
            if (rqeptr->Http2Ptr)
               rqeptr->Http2Ptr->WatchItem = rqeptr->WatchItem;
         }
         else
         {
            h2eptr->WatchItem = WatchSetWatch (NULL, WATCH_NEW_ITEM);
            h2eptr->WatchItem |= WATCH_ITEM_HTTP2_FLAG;
         }
      }
   }
   else
      rqeptr = NULL;

   if (WatchConfig.DoPeek)
   {
      if (!rqeptr && !h2eptr)
      {
         WatchReset ();
         rqptr->rqResponse.HttpStatus = 400;
         ErrorGeneral (rqptr, ErrorWatchCannotPeek, FI_LI);
         AdminEnd (rqptr);
         return;
      }
   }

   WatchConfig.FilterSet =
      (WatchConfig.FilterHttp09 || WatchConfig.FilterHttp10 ||
       WatchConfig.FilterHttp11 || WatchConfig.FilterHttp2 ||
       WatchConfig.FilterRealm[0] || WatchConfig.FilterUser[0] ||
       WatchConfig.FilterClient[0] || WatchConfig.FilterUri[0] ||
       WatchConfig.FilterRequest[0] || WatchConfig.FilterService[0] ||
       WatchConfig.FilterStatus >= 0);

   /***********************/
   /* build report header */
   /***********************/

   CategoryList[0] = '\0';
   if (WatchConfig.Category & WATCH_ONE_SHOT_FLAG)
   {
      if (cptr = SysTrnLnm (WASD_WATCH_ONE_SHOT))
      {
         WatchConfig.Category = atoq(cptr);
         WatchConfig.Category &= WATCH_CAT_MASK;
         if (WatchConfig.Category)
         {
            WatchConfig.Category |= WATCH_ONE_SHOT_FLAG;
#if WATCH_MOD
            while (*cptr && (*cptr == '-' || isdigit(*cptr))) cptr++;
            while (*cptr && *cptr != '-' && !isdigit(*cptr)) cptr++;
            WatchConfig.Module = atoq(cptr);
            WatchConfig.Module &= WATCH_MOD_MASK;
#else
            WatchConfig.Module = 0;
#endif /* WATCH_MOD */
         }
         else
            WatchConfig.Module = 0;
      }
      else
      {
         WatchConfig.Category = WATCH_CAT_MASK | WATCH_ONE_SHOT_FLAG;
#if WATCH_MOD
         WatchConfig.Module = WATCH_MOD_MASK;
#endif /* WATCH_MOD */
      }
   }

   if (!CategoryList[0])
   {
      if (WatchConfig.CliEnabled) eolcnt = 132; else eolcnt = 255;
      zptr = (sptr = CategoryList) + eolcnt;
      /* first the categories */
      for (ucnt = 1; ucnt; ucnt = ucnt << 1)
      {
         cptr = WatchWhatCategory (WatchConfig.Category & ucnt);
         if (cptr)
         {
            if (sptr > CategoryList)
            {
               *sptr++ = ',';
               if (sptr > zptr)
               {
                  zptr = sptr + eolcnt;
                  *sptr++ = '\n';
               }
               else
                  *sptr++ = ' ';
            }
            while (*cptr) *sptr++ = to_lower(*cptr++);
         }
      }
      /* then any modules */
      for (ucnt = 1; ucnt; ucnt = ucnt << 1)
      {
#if WATCH_MOD
         cptr = WatchWhatModule (WatchConfig.Module & ucnt);
#endif
         if (cptr)
         {
            if (sptr > CategoryList)
            {
               *sptr++ = ',';
               if (sptr > zptr)
               {
                  zptr = sptr + eolcnt;
                  *sptr++ = '\n';
               }
               else
                  *sptr++ = ' ';
            }
            while (*cptr) *sptr++ = to_lower(*cptr++);
         }
      }
      *sptr = '\0';
   }

   vecptr = FaoVector;
   *vecptr++ = 0;
   *vecptr++ = ServerHostPort;
   *vecptr++ = 36 + strlen(ServerHostPort);
   *vecptr++ = HttpdProcess.ImageInfo;
   *vecptr++ = BuildDateTime;
   *vecptr++ = TcpIpAgentInfo;
   *vecptr++ = SesolaVersion();
   *vecptr++ = strchr(__VMS_VERSION, ' ') - __VMS_VERSION;
   *vecptr++ = __VMS_VERSION;
   *vecptr++ = __DECC_VER;
   *vecptr++ = WatchFuncCc;
   *vecptr++ = SysInfo.HwName;
   *vecptr++ = SysInfo.AvailCpuCnt;
   *vecptr++ = SysInfo.MemoryMBGB;
   *vecptr++ = SysInfo.Version;

#ifdef ODS_EXTENDED
   if (OdsExtended)
      *vecptr++ = "ODS-5 enabled";
   else
   if (SysInfo.VersionInteger >= 720)
      *vecptr++ = "ODS-5 disabled";
   else
      *vecptr++ = "ODS-5 unavailable";
#else /* ODS_EXTENDED */
   *vecptr++ = "ODS-5 unavailable";
#endif /* ODS_EXTENDED */

   *vecptr++ = ENAMEL_NAML_USED;
   *vecptr++ = ENAMEL_FIB_USED;
#if ODS_DIRECT
   *vecptr++ = OdsDirect ? "enabled" : "disabled";
#else
   *vecptr++ = "not compiled";
#endif /* ODS_DIRECT */

   if (Config.cfMisc.GzipResponseCompLevel)
   {
      if (VMSok(GzipFindImageStatus))
      {
         *vecptr++ = "!AZ V!AZ";
         *vecptr++ = GzipZlibNamePtr;
         *vecptr++ = GzipZlibVersionPtr;
      }
      else
      {
         *vecptr++ = "ZLIB !&S";
         *vecptr++ = GzipFindImageStatus;
      }
   }
   else
      *vecptr++ = "ZLIB disabled";

   *vecptr++ = Config.cfMisc.RegexSyntax ? "enabled" : "disabled";

   *vecptr++ = TO_LOWER_UPPER;
   *vecptr++ = SysInfo.LockValueBlockSize;
   if (OperateWithSysPrv)
      *vecptr++ = ", SYSPRV";
   else
      *vecptr++ = "";
   *vecptr++ = CommandLine;
   *vecptr++ = WatchServerQuotas (0);

   if (DclScriptDetachProcess)
   {
      *vecptr++ = "DCL Scripting: detached, !&@PERSONA!AZ !AZ\n";
      if (HttpdScriptAsUserName[0])
      {
         if (CliScriptAs[0])
            *vecptr++ = "/script=as=!AZ, ";
         else
            *vecptr++ = "as !AZ, ";
         *vecptr++ = HttpdScriptAsUserName;
      }
      else
         *vecptr++ = "";
      if (PersonaMacro)
         *vecptr++ = "_MACRO";
      else
         *vecptr++ = "";
      if (DclPersonaServicesAvailable)
         *vecptr++ = "enabled";
      else
         *vecptr++ = "disabled";
   }
   else
   {
      *vecptr++ =
"DCL Scripting: subprocess\n\
BYTLM-available:!UL BYTLM-per-subproc:!&@ (approx !&@ subprocesses) \
BYTLM-net-accept:!UL BYTLM-net-listen:!UL\n";
      *vecptr++ = HttpdProcess.BytLmAvailable;
      if (DclMailboxBytLmRequired)
      {
         *vecptr++ = "!UL";
         *vecptr++ = DclMailboxBytLmRequired;
         *vecptr++ = "!UL";
         *vecptr++ = (HttpdProcess.BytLmAvailable -
                       (NetAcceptBytLmRequired * Config.cfServer.ConnectMax)) /
                     DclMailboxBytLmRequired;
      }
      else
      {
         *vecptr++ = "?";
         *vecptr++ = "?";
      }
      *vecptr++ = NetAcceptBytLmRequired;
      *vecptr++ = NetListenBytLmRequired;
   }

   *vecptr++ = HttpdProcess.PrcNam;
   *vecptr++ = HttpdProcess.ModeName;
   *vecptr++ = HttpdProcess.SysInput;
   *vecptr++ = HttpdProcess.SysOutput;

   InstanceNodePtr = InstanceClusterPtr = NULL;
   if (InstanceNodeCurrent > 1)
   {
      InstanceLockList (INSTANCE_NODE, ", ", &InstanceNodePtr);
      if (InstanceNodePtr)
      {
         *vecptr++ = "Node: !AZ\n";
         *vecptr++ = InstanceNodePtr;
      }
      else
         *vecptr++ = "";
   }
   else
      *vecptr++ = "";

   InstanceLockList (INSTANCE_CLUSTER, ", ", &InstanceClusterPtr);
   if (InstanceClusterPtr)
   {
      *vecptr++ = "Instances: !AZ\n";
      *vecptr++ = InstanceClusterPtr;
   }
   else
      *vecptr++ = "";

   if (WatchConfig.DoWatch)
   {
      *vecptr++ = "!&@!&@!&@";
      *vecptr++ = "Watching: !AZ (!@SQ!&@) via !AZ for !&@\n!&@";
      *vecptr++ = CategoryList;
      valueCat64 = WatchConfig.Category & WATCH_CAT_MASK;
      *vecptr++ = &valueCat64;
      if (WatchConfig.Module)
      {
         *vecptr++ = ",!@SQ";
         valueMod64 = WatchConfig.Module & WATCH_MOD_MASK;
         *vecptr++ = &valueMod64;
      }
      else
         *vecptr++ = "";

      if (HTTP2_REQUEST(rqptr))
         *vecptr++ = "HTTP/2";
      else
      if (rqptr->rqHeader.HttpVersion == HTTP_VERSION_1_1)
         *vecptr++ = "HTTP/1.1";
      else
      if (rqptr->rqHeader.HttpVersion == HTTP_VERSION_1_0)
         *vecptr++ = "HTTP/1.0";
      else
         *vecptr++ = "HTTP/0.9";

      *vecptr++ = WatchToGo (WatchConfig.DurationSeconds);

      if (WatchConfig.TriggerRxCount || WatchConfig.TriggerTxCount)
      {
         /* the original (possibly formatted) strings */
         *vecptr++ = "Trigger: rx \"!AZ\", tx \"!AZ\" !AZ\n";
         *vecptr++ = TriggerRx;
         *vecptr++ = TriggerTx;
         if (WatchConfig.TriggerPlus)
            *vecptr++ = "+ (plus)";
         else
            *vecptr++ = "-";
      }
      else
         *vecptr++ = "Trigger: NONE\n";

      if (WatchConfig.FilterSet)
      {
         *vecptr++ =
"Filter Protocol:!AZ!AZ!AZ!AZ!AZ \
Client:!AZ:\"!AZ\" Service:!AZ:\"!AZ\" Request:!AZ:\"!AZ\" \
URI:!AZ:\"!AZ\" Realm:!AZ:\"!AZ\" User:!AZ:\"!AZ\" Status:IN:\"!&@\"\n";
         *vecptr++ = WatchConfig.FilterOutHttp ? "OUT" : "IN";
         *vecptr++ = WatchConfig.FilterHttp2 ? ":2" : "";
         *vecptr++ = WatchConfig.FilterHttp11 ? ":1.1" : "";
         *vecptr++ = WatchConfig.FilterHttp10 ? ":1.0" : "";
         *vecptr++ = WatchConfig.FilterHttp09 ? ":0.9" : "";
         *vecptr++ = WatchConfig.FilterOutClient ? "OUT" : "IN";
         *vecptr++ = WatchConfig.FilterClient;
         *vecptr++ = WatchConfig.FilterOutService ? "OUT" : "IN";
         *vecptr++ = WatchConfig.FilterService;
         *vecptr++ = WatchConfig.FilterOutRequest ? "OUT" : "IN";
         *vecptr++ = WatchConfig.FilterRequest;
         *vecptr++ = WatchConfig.FilterOutURI ? "OUT" : "IN";
         *vecptr++ = WatchConfig.FilterUri;
         *vecptr++ = WatchConfig.FilterOutRealm ? "OUT" : "IN";
         *vecptr++ = WatchConfig.FilterRealm;
         *vecptr++ = WatchConfig.FilterOutUser ? "OUT" : "IN";
         *vecptr++ = WatchConfig.FilterUser;
         if (WatchConfig.FilterStatus >= 0)
         {
            *vecptr++ = "!UL";
            *vecptr++ = WatchConfig.FilterStatus;
         }
         else
            *vecptr++ = "";
      }
      else
      if (WatchConfig.Category & WATCH_ONE_SHOT_FLAG)
         *vecptr++ = "Filter: ONE-SHOT\n";
      else
         *vecptr++ = "Filter: NONE\n";

      if (WatchConfig.LogFile)
      {
         *vecptr++ = "Log: !AZ\n";
         *vecptr++ = WatchConfig.LogFileName;
      }
      else
         *vecptr++ = "Log: NONE\n";
   }
   else
      *vecptr++ = "";

   FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI);
   status = FaolToBuffer (Buffer, sizeof(Buffer), &slen,
                          ResponseFao, &FaoVector);
   if (VMSnok (status) || status == SS$_BUFFEROVF)
      ErrorNoticed (rqptr, status, NULL, FI_LI);

   if (WatchConfig.LogFile)
   {
      WatchConfig.LogFileOut = fopen (WatchConfig.LogFileName, "r",
                                      "dna=;1", "shr=nil");
      if (WatchConfig.LogFileOut)
      {
         fclose (WatchConfig.LogFileOut);
         WatchConfig.LogFileOut = NULL;
         errno = EEXIST;
      }
      else
         WatchConfig.LogFileOut = fopen (WatchConfig.LogFileName, "w",
                                         "dna=;1", "rfm=var", "rat=cr",
                                         "shr=get", "mbc=127", "mbf=4");
      if (!WatchConfig.LogFileOut)
      {
         WatchReset ();
         rqptr->rqResponse.HttpStatus = 409;
         sprintf (Buffer, "%s ... %s", strerror(errno),
                  WatchConfig.LogFileName);
         Buffer[0] = toupper(Buffer[0]);
         ErrorGeneral (rqptr, Buffer, FI_LI);
         AdminEnd (rqptr);
         return;
      }

      FaoToStdout ("%HTTPD-I-WATCH, !20%D, open !AZ\n",
                   0, WatchConfig.LogFileName);
   }

   if (InstanceNodePtr) VmFree (InstanceNodePtr, FI_LI);
   if (InstanceClusterPtr) VmFree (InstanceClusterPtr, FI_LI);

   status = InstanceLockNoWait (INSTANCE_NODE_WATCH);
   if (status == SS$_NOTQUEUED)
   {
      WatchReset ();
      rqptr->rqResponse.HttpStatus = 409;
      ErrorVmsStatus (rqptr, status, FI_LI);
      AdminEnd (rqptr);
      return;
   }

   /*******************/
   /* enable WATCHing */
   /*******************/

   /* allow browser WATCH to piggyback on CLI WATCH */
   if (!Watch.CliEnabled)
   {
      /* so that WatchNone() "restores" the WATCH items  */
      WatchConfig.Category2 = WatchConfig.Category;
      WatchConfig.Module2 = WatchConfig.Module;
      /* copy the configuration WATCH structure onto the operating WATCH */
      memcpy (&Watch, &WatchConfig, sizeof(Watch));
   }

   /* 0 browser only, 1 plus log file, 2 log file only, 3 detached */
   if (Watch.LogFile >= 2)
   {
      /************/
      /* detached */
      /************/

      /* report header write */
      WatchWrite (Buffer, slen);

      rqptr->rqResponse.PreExpired = PRE_EXPIRE_ADMIN;
      ResponseHeader200 (rqptr, "text/html", NULL);
      AdminPageTitle (rqptr, "WATCH Detached", "<pre>");

      rqptr->NetWriteEscapeHtml = true;
      NetWriteBuffered (rqptr, NULL, Buffer, slen);
      rqptr->NetWriteEscapeHtml = false;

      FaoToBuffer (Buffer, sizeof(Buffer), &slen,
                   DetachFao, ADMIN_CONTROL_WATCH_END);
      NetWrite (rqptr, AdminEnd, Buffer, slen);
      return;
   }

   /*****************************/
   /* now we're WatchWrite()ing */
   /*****************************/

   /* detect a client stopping WATCHing */
   if (!HTTP2_REQUEST(rqptr)) WatchBreakDetect (rqptr);

   HttpdTimerSet (rqptr, TIMER_OUTPUT, -1);
   HttpdTimerSet (rqptr, TIMER_NOPROGRESS, -1);

   rqptr->WasWatching = rqptr->rqResponse.NoGzip = true;
   rqptr->rqResponse.PreExpired = PRE_EXPIRE_ADMIN;
   ResponseHeader200 (rqptr, "text/plain", NULL);
   Watch.RequestPtr = rqptr;
   Watch.Http2Ptr = rqptr->Http2Ptr;

   /* report header write */
   WatchWrite (Buffer, slen);

   if (Watch.DoPeek)
   {
      if (rqeptr)
         WatchPeek (rqptr, rqeptr);
      else
      if (h2eptr)
         WatchPeekHttp2 (rqptr, h2eptr, "|HTTP/2_connection...|");

      if (!Watch.DoWatch)
      {
         /* no more WatchReset() use WatchEnd() to finish  */
         WatchEnd ();
         return;
      }
   }

#else /* WATCH_CAT */

   rqptr->rqResponse.HttpStatus = 403;
   ErrorGeneral (rqptr, ErrorWatchNoCategory, FI_LI);
   AdminEnd (rqptr);

#endif /* WATCH_CAT */
}

/*****************************************************************************/
/*
Search the supplied buffer for the trigger character sequence.
*/ 

BOOL WatchTrigger
(
char *DataPtr,
int DataCount,
char *TriggerPtr
)
{
#if WATCH_CAT

   int  dcnt, tcnt;
   char  *aptr, *cptr, *czptr, *dptr, *dbptr, *dzptr, *tptr;

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

   if (DBUG && Watch.CliEnabled)
      fprintf (stdout, "WatchTrigger()\n");

   if ((tptr = TriggerPtr) == Watch.TriggerRx)
      tcnt = Watch.TriggerRxCount;
   else
      tcnt = Watch.TriggerTxCount;

   dzptr = (dptr = dbptr = DataPtr) + (dcnt = DataCount);
   dzptr -= tcnt;
   /* if the trigger is longer than the buffer data */
   if (dzptr <= dptr) return (false);
   czptr = tptr + tcnt;

   /* search for the trigger in the I/O buffer */
   while (dptr < dzptr)
   {
      aptr = dptr;
      cptr = tptr;
      while (cptr < czptr && *cptr == *aptr)
      {
         cptr++;
         aptr++;
      }
      if (cptr >= czptr) break;
      dptr++;
   }
   /* if trigger was not hit */
   if (cptr < czptr) return (false);

   WatchThis (WATCHALL, WATCH_NETWORK, "TRIGGER !AZ !UL of !UL bytes",
              TriggerPtr == Watch.TriggerRx ? "rx" : "tx",
              dptr - dbptr, dcnt);
   WatchDataDumpAt (DataPtr, DataCount, dptr - dbptr);

   return (true);

#endif /* WATCH_CAT */
}

/*****************************************************************************/
/*
For a triggered WATCH find the relevant request and set the WATCH item.
*/ 

int WatchSetTrigger (NETIO_STRUCT *ioptr)

{
#if WATCH_CAT

   int  number;
   HTTP2_STRUCT  *h2ptr;
   HTTP2_STREAM_STRUCT  *s2ptr;
   REQUEST_STRUCT  *rqptr;

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

   if (DBUG && Watch.CliEnabled)
      fprintf (stdout, "WatchSetTrigger()\n");

   if (s2ptr = (HTTP2_STREAM_STRUCT*)ioptr->Stream2Ptr)
   {
      /**********/
      /* HTTP/2 */
      /**********/

      if (rqptr = s2ptr->RequestPtr)
      {
         if (rqptr == Watch.RequestPtr) return (0);
         if (Watch.Http2Ptr && Watch.Http2Ptr == rqptr->Http2Ptr)
            return (WatchRabbitHole());
         if (number = WatchSetWatch (rqptr, WATCH_NEW_TRIGGER))
            return (number);
      }
      return (0);
   }

   if (ioptr->SesolaPtr)
   {
      /*******/
      /* TLS */
      /*******/

      if (rqptr = SesolaGetRequestPtr (ioptr->SesolaPtr))
      {
         if (rqptr == Watch.RequestPtr) return (0);
         if (Watch.Http2Ptr && Watch.Http2Ptr == rqptr->Http2Ptr)
            return (WatchRabbitHole());
         if (number = WatchSetWatch (rqptr, WATCH_NEW_TRIGGER))
            return (number);
      }
      return (0);
   }

   if (rqptr = ioptr->RequestPtr)
   {
      /*********/
      /* clear */
      /*********/

      if (rqptr == Watch.RequestPtr) return (0);
      if (Watch.Http2Ptr && Watch.Http2Ptr == rqptr->Http2Ptr)
         return (WatchRabbitHole());
      if (number = WatchSetWatch (rqptr, WATCH_NEW_TRIGGER))
         return (number);
      return (0);
   }

   return (0);

#endif /* WATCH_CAT */
}

/*****************************************************************************/
/*
Set WATCH item indicator to be the supplied value.

If |rqptr| is NULL then it's intended to return an item number for the original
(NetAccept()) network I/O structure, or for an HTTP/2 connection (see below).

When |rqptr| non-NULL then needs to be set for the request across the WATCH
items in various structures.

An HTTP/2 connection can be set for WATCHing via Http2Report().  This will set
the item in the associated NETIO structure and the (usually) associated TLS/SSL
I/O structure.  So when setting the request's WATCH item first check to see if
the associated I/O structures have an item set and DO NOT (RE)SET if not the
same as the current request number to be set.

Requests over HTTP/2 have the original HTTP/2 WATCH item (if any) in the least
significant digits and the stream ID in the most.
*/ 

int WatchSetWatch
(
REQUEST_STRUCT *rqptr,
int item
)
{
#if WATCH_CAT

   static char  divider [] = "****************************************";

   int  number;
   HTTP2_STRUCT  *h2ptr;

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

   if (DBUG && Watch.CliEnabled)
      fprintf (stdout, "WatchSetWatch() %s:%d %d %d\n", FI_LI, rqptr, item);
 
   if (!Watch.CliEnabled && !Watch.SecondsToGo) return (0);

   if (rqptr)
   {
      if (rqptr == Watch.RequestPtr) return (0);
      if (Watch.Http2Ptr && Watch.Http2Ptr == rqptr->Http2Ptr)
         return (WatchRabbitHole());
   }

   if (item == WATCH_NEW_ITEM ||
       item == WATCH_NEW_TRIGGER)
   {
      /* if triggers are configured then only a trigger can set */
      if (item == WATCH_NEW_ITEM && (Watch.TriggerRxCount ||
                                     Watch.TriggerTxCount)) return (0);

      if (rqptr)
      {
         WatchItemSize (rqptr);
         if (HTTP2_REQUEST(rqptr))
         {
            h2ptr = rqptr->Http2Ptr;
            if (h2ptr->WatchItem)
            {
               number = h2ptr->WatchItem & WATCH_ITEM_NUMBER_MASK;
               number = number % Watch.ItemPower2;
            }
            else
               number = ++Watch.ItemCount % Watch.ItemPower2;
            if (rqptr->Stream2Ptr)
               number += rqptr->Stream2Ptr->Ident * Watch.ItemPower2;
         }
         else
            number = ++Watch.ItemCount;
      }
      else
         number = ++Watch.ItemCount;
   }
   else
      number = item;

   if (rqptr)
   {
      if (Watch.CliEnabled)
         fprintf (stdout, "%s\nWatchSetWatch() %s:%d %u->%u\n%s\n",
                  divider, FI_LI, rqptr->WatchItem, number, divider);

      if ((h2ptr = rqptr->Http2Ptr) && !Watch.TriggerPlus)
      {
         /* if not WATCHing the HTTP/2 connection */
         if (!(h2ptr->WatchItem & WATCH_ITEM_HTTP2_FLAG))
            Http2SetWatch (h2ptr, number);
      }
      else
      if (rqptr->NetIoPtr)
         rqptr->NetIoPtr->WatchItem = number;

      /* after I/O WATCH item set/reset request item */
      rqptr->WatchItem = number; 
   }
   else
   if (Watch.CliEnabled)
      fprintf (stdout, "%s\nWatchSetWatch() %s:%d %u\n%s\n",
               divider, FI_LI, number, divider);

   return (number);

#endif /* WATCH_CAT */
}

/*****************************************************************************/
/*
Let the WATCHer know the request is part of the same HTTP/2 connection.
*/ 

int WatchRabbitHole ()

{
#if WATCH_CAT

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

   if (Watch.CliEnabled) fprintf (stdout, "WatchRabbitHole()\n");

   WatchThis (WATCHALL, WATCH_HTTP2, "RABBIT hole !AZ,!UL",
              Watch.Http2Ptr->NetIoPtr->ClientPtr->Lookup.HostName,
              Watch.Http2Ptr->NetIoPtr->ClientPtr->IpPort);
   return (0);

#endif /* WATCH_CAT */
}

/*****************************************************************************/
/*
WATCH item width starts at 6 digits (3 HTTP/2 ident plus 3 connection) and if
either overflows increments by 2 each time.
*/ 

void WatchItemSize (REQUEST_STRUCT *rqptr)

{
#if WATCH_CAT

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

   if (Watch.CliEnabled) fprintf (stdout, "WatchItemSize()\n");

   if (!Watch.ItemWidth)
      Watch.ItemWidth = 6;
   else
   if (Watch.ItemCount+1 >= Watch.ItemPower2 ||
       ((HTTP2_REQUEST(rqptr) &&
        rqptr->Stream2Ptr &&
        rqptr->Stream2Ptr->Ident >= Watch.ItemPower2)))
      Watch.ItemWidth += 2;

   if (Watch.ItemWidth != Watch.ItemDigits)
      Watch.ItemPower2 = ipow (10, Watch.ItemWidth / 2);

#endif /* WATCH_CAT */
}

/*****************************************************************************/
/*
Check and report if the WATCH facility is already being used (locally or via
another instance).
*/ 

#if WATCH_CAT

BOOL WatchInUse (REQUEST_STRUCT *rqptr)

{
   static char  InUseFao [] =
"<p>WATCH is currently in use !&@\n\
<p>To end current report click \
<a class=\"abttn bttn425\" href=\"!AZ\">END!!</a>\n";

   BOOL  BrowserInUse,
         DetachedInUse,
         InstanceInUse;
   int  status;
   ushort  slen;
   ulong  *vecptr;
   ulong  FaoVector [8];
   char  buf [256];

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

   if (DBUG && Watch.CliEnabled)
      fprintf (stdout, "WatchInUse()\n");

   DetachedInUse = BrowserInUse = InstanceInUse = false;

   /* attempt to obtain the WATCH lock */
   status = InstanceLockNoWait (INSTANCE_NODE_WATCH);
   if (status == SS$_NOTQUEUED)
   {
      /* the lock is already in use (WATCH is in use elsewhere) */
      if (Watch.RequestPtr)
         BrowserInUse = true;
      else
      if (Watch.Category || Watch.Module)
         DetachedInUse = true;
      else
         InstanceInUse = true;
   }

   if (!(BrowserInUse || DetachedInUse || InstanceInUse))
   {
      if (VMSnok (status = InstanceUnLock (INSTANCE_NODE_WATCH)))
         ErrorExitVmsStatus (status, "InstanceUnLock()", FI_LI);
      return (false);
   }

   AdminPageTitle (rqptr, "WATCH Report");

   vecptr = FaoVector;
   if (BrowserInUse)
   {
      *vecptr++ = "by !AZ";
      *vecptr++ = UserAtClient(Watch.RequestPtr);
   }
   else
   if (DetachedInUse)
      *vecptr++ = "detached.";
   else
   if (InstanceInUse)
      *vecptr++ = "via another instance.";
   else
      *vecptr++ = "via /WATCH";

   *vecptr++ = ADMIN_CONTROL_WATCH_END;

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

   ResponseHeader200 (rqptr, "text/html", &rqptr->NetWriteBufferDsc);

   AdminEnd (rqptr);
   return (true);
}                                   

#endif /* WATCH_CAT */

/*****************************************************************************/
/*
Request using the WATCH facility drops the connection.  Release WATCH.
*/ 

void WatchEnd ()

{
#if WATCH_CAT

   static char  EndFao [] = "|!%T end|\n";

   ushort  slen;
   char  buf [32];

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

   if (Watch.CliEnabled) fprintf (stdout, "WatchEnd()\n");

   WatchNone (true);
   FaoToBuffer (buf, sizeof(buf), &slen, EndFao, 0);
   WatchWrite (buf, slen);
   WatchWrite ("$$EnD$$\n", 8);
   WatchWrite (NULL, 0);
   Watch.EndWatch = true;
   WatchNone (false);

   if (Watch.LogFileOut)
   {
      FaoToStdout ("%HTTPD-I-WATCH, !20%D, close !AZ\n", 0, Watch.LogFileName);
      fclose (Watch.LogFileOut);
      Watch.LogFileOut = NULL;
      Watch.LogFileName[0] = '\0';
      Watch.LogFile = 0;
   }

   InstanceUnLock (INSTANCE_NODE_WATCH);

   if (Watch.RequestPtr) HttpdTimerSet (Watch.RequestPtr, TIMER_TERMINATE, 0);

   WatchReset ();

#endif /* WATCH_CAT */
}                                   

/*****************************************************************************/
/*
Reset the WATCH structure and any existing connections WATCH settings.
*/ 

void WatchReset ()

{
#if WATCH_CAT

   HTTP2_STRUCT  *h2eptr;
   REQUEST_STRUCT  *rqeptr;

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

   if (Watch.CliEnabled) fprintf (stdout, "WatchReset()\n");

   if (Watch.LogFileOut)
   {
      FaoToStdout ("%HTTPD-I-WATCH, log close !AZ\n", Watch.LogFileName);
      fclose (Watch.LogFileOut);
      Watch.LogFileOut = NULL;
      Watch.LogFileName[0] = '\0';
      Watch.LogFile = 0;
   }

  /* reset any requests' WATCHes in-progress */
  for (rqeptr = LIST_GET_HEAD(&RequestList);
        rqeptr != NULL;
        rqeptr = LIST_GET_NEXT(rqeptr))
   {
      if (!rqeptr->NetIoPtr) continue;
      rqeptr->NetIoPtr->WatchItem =
         rqeptr->NetIoPtr->WatchTriggerRxPlus =
         rqeptr->NetIoPtr->WatchTriggerTxPlus = 0;
   }
  /* reset any HTTP/w connections' WATCHes in-progress */
   for (h2eptr = LIST_GET_HEAD(&Http2List);
        h2eptr != NULL;
        h2eptr = LIST_GET_NEXT(h2eptr))
   {
      if (!h2eptr->NetIoPtr) continue;
      h2eptr->NetIoPtr->WatchItem =
         h2eptr->NetIoPtr->WatchTriggerRxPlus =
         h2eptr->NetIoPtr->WatchTriggerTxPlus = 0;
   }

   if (Watch.CliEnabled)
      Watch.RequestPtr = Watch.Http2Ptr = NULL;
   else
   {
      /* reset WATCH environment */
      WatchSetWatch (NULL, 0);
      memset (&Watch, 0, sizeof(WATCH_STRUCT));
   }

#endif /* WATCH_CAT */
}                                   

/*****************************************************************************/
/*
The WATCH client concluding the watching can only be detected via a break in
connection which in a quiescent system (no requests being processed) can in
turn only be detected by a broken network read I/O or a TLS/SSL shutdown
exchange initiated.
*/ 

void WatchBreakDetect (REQUEST_STRUCT *rqptr)

{
#if WATCH_CAT

   static uchar  buf [32];

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

   if (Watch.CliEnabled)
      fprintf (stdout, "WatchBreakDetect() 0x%08.08X 0x%08.08X 0x%08.08X\n",
               rqptr, rqptr->NetIoPtr->ReadPtr, buf);

   if (rqptr->NetIoPtr->ReadPtr == buf)
      WatchEnd ();
   else
      NetRead (rqptr, &WatchBreakDetect, buf, sizeof(buf));

#endif /* WATCH_CAT */
}

/*****************************************************************************/
/*
Return how many seconds before WATCH ends.
*/ 

char* WatchToGo (int secs)

{
#if WATCH_CAT

   static char  buf [32];

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

   if (secs > 60 * 60 * 2)
      sprintf (buf, "%u:%02u:%02u",
               secs / (60 * 60), secs % (60 * 60) / 60, secs % 60);
   else
   if (secs > 120)
      sprintf (buf, "%u:%02u",
               secs % (60 * 60) / 60, secs % 60);
   else
      sprintf (buf, "%u seconds", secs);
   return (buf);

#endif /* WATCH_CAT */
}

/*****************************************************************************/
/*
Called once per second every second.
*/ 

void WatchTick ()

{
#if WATCH_CAT

   static int PrevTickSecond;

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

   if (Watch.CliEnabled) fprintf (stdout, "WatchTick()");

   WatchWrite (NULL, 0);

   if (!Watch.SecondsToGo) return;

   if (PrevTickSecond) Watch.SecondsToGo -= HttpdTickSecond - PrevTickSecond;
   PrevTickSecond = HttpdTickSecond;
   if (Watch.SecondsToGo > 0) return;
   Watch.SecondsToGo = -1;
   WatchEnd ();
   Watch.SecondsToGo = PrevTickSecond = 0;

#endif /* WATCH_CAT */
}

/*****************************************************************************/
/*
Write WATCH report output to a file and/or network client as appropriate.
To reduce network latency of the often highly granular WATCH data use two
dynamic string buffers, filling one while the other is being written to the
client.
*/

void WatchWrite
(
void *DataPtr,
uint DataLength
)
{
#if WATCH_CAT

#define WATCH_STRDSC_SIZE 16384

   int  bcount, length;
   ushort  slen;
   char  buf [256];
   STR_DSC_AUTO (DataDsc);
   REQUEST_STRUCT  *rqptr;

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

   if (0 && Watch.CliEnabled)
      fprintf (stdout, "WatchWrite() %d %d\n", DataPtr, DataLength);

   if (DataPtr)
   {
      if (DataLength < 0) DataLength = strlen(DataPtr);
      if (!DataLength) return;

      bcount = Watch.ByteCount;
      Watch.ByteCount += DataLength;

      if (Watch.CliEnabled || Watch.LogFile)
         if (!MATCH8(DataPtr,"$$EnD$$\n"))
            fwrite (DataPtr, DataLength, 1, Watch.LogFileOut);

      /* 0 browser only, 1 plus log file, 2 detached */
      if (Watch.LogFile == 2) return;

      if (MATCH8(DataPtr,"$$EnD$$\n")) return;
   }

   if (!(rqptr = Watch.RequestPtr)) return;

   /******************/
   /* network client */
   /******************/

   WatchNone (true);

   if (DataPtr)
   {
      if (!STR_DSC_SANITY(&Watch.BufferDsc))
         StrDscBegin (rqptr, &Watch.BufferDsc, WATCH_STRDSC_SIZE);

      if (!STR_DSC_SANITY(&rqptr->NetWriteBufferDsc))
         StrDscBegin (rqptr, &rqptr->NetWriteBufferDsc, 0);

      StrDscThis (NULL, &DataDsc, DataPtr, DataLength);
      StrDscBuild (&Watch.BufferDsc, &DataDsc, NULL);
      /* 0 browser only, 1 plus log file, 2 detached */
      if (Watch.LogFile == 2) DataPtr = NULL;
   }

   if (!Watch.AstInUse && STR_DSC_SANITY(&Watch.BufferDsc))
   {
      length = StrDscLength (&Watch.BufferDsc);
      if ((!DataPtr && length) || (DataPtr && length > WATCH_STRDSC_SIZE))
      {
         StrDscSwap (&Watch.BufferDsc, &rqptr->NetWriteBufferDsc);
         StrDscNoContent (&Watch.BufferDsc);
         NetWriteStrDsc (rqptr, Watch.AstInUse = WatchWriteAst);
      }
   }

   WatchNone (false);

   if (!Watch.EndWatch) return;

   WatchReset ();
   AdminEnd (rqptr);

#endif /* WATCH_CAT */
}

/*****************************************************************************/
/*
If the the WATCH request pointer has been NULLed indicating the ending of the
WATCH report then reset the WATCH configuration ending the request.
*/

void WatchWriteAst (REQUEST_STRUCT *rqptr)

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

#if WATCH_CAT

   if (0 && Watch.CliEnabled)
      fprintf (stdout, "WatchWriteAst()\n");

   Watch.AstInUse = NULL;

   /* 0 browser only, 1 plus log file, 2 detached */
   if (Watch.LogFile == 2) return;

   if (VMSok (rqptr->NetIoPtr->WriteStatus))
   {
      if (!Watch.EndWatch) return;

      if (STR_DSC_SANITY(&Watch.BufferDsc) &&
          StrDscLength (&Watch.BufferDsc))
      {
         /* buffer has remaining content */
         WatchWrite (NULL, 0);
         return;
      }
   }

   WatchNone (true);
   WatchReset ();
   rqptr->AdminTaskPtr = NULL;
   WatchNone (false);

#endif /* WATCH_CAT */
}

/*****************************************************************************/
/*
Temporarily suppress category and module WATCHing while using routines that
provide WATCH points - or all hell breaks loose :-D   Note that WatchBegin()
actually puts the WATCH items into .Category2 and .Module2 while WatchNone() is
in effect and these are "restored" to the active flags.
*/ 

void WatchNone (BOOL none)

{
   static int  NoneDepth = 0;

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

#if WATCH_CAT

   if (none)
   {
      if (NoneDepth++ == 0)
      {
         Watch.Category2 = Watch.Category;
         Watch.Module2 = Watch.Module;
         Watch.Category = Watch.Module = 0;
      }
   }
   else
   {
      if (NoneDepth == 0)
         ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
      if (--NoneDepth == 0)
      {
         Watch.Category = Watch.Category2;
         Watch.Module = Watch.Module2;
         Watch.Category2 = Watch.Module2 = 0;
      }
   }

#endif /* WATCH_CAT */
}                                   

/*****************************************************************************/
/*
Filter on the HTTP protocol.
*/ 

void WatchFilterHttpProtocol (REQUEST_STRUCT *rqptr)

{
   BOOL  hit;
   char  *aptr;

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

#if WATCH_CAT

   /* can occur if ASTs are delivered after WATCH use is discontinued */
   if (!Watch.Category && !Watch.Module) return;

   if (Watch.Category & WATCH_ONE_SHOT_FLAG) return;

   if (!Watch.FilterSet) return;

   if (HTTP2_REQUEST(rqptr))
   {
      hit = Watch.FilterHttp2;
      aptr = "HTTP/2";
   }
   else
   if (rqptr->rqResponse.HttpVersion == HTTP_VERSION_1_1)
   {
      hit = Watch.FilterHttp11;
      aptr = "HTTP/1.1";
   }
   else
   if (rqptr->rqResponse.HttpVersion == HTTP_VERSION_1_0)
   {
      hit = Watch.FilterHttp10;
      aptr = "HTTP/1.0";
   }
   else
   if (rqptr->rqResponse.HttpVersion == HTTP_VERSION_0_9)
   {
      hit = Watch.FilterHttp09;
      aptr = "HTTP/0.9";
   }
   else
   {
      hit = false;
      aptr = "*BUGCHECK*";
   }

   if (Watch.FilterOutHttp)
   {
      /* if matches filter out */
      if (hit && rqptr->WatchItem)
         WatchFilterDrop (rqptr, aptr);
      else
      /* if doesn't match filter in */
      if (!hit && !rqptr->WatchItem)
         WatchFilterAdd (rqptr, aptr);
   }
   else
   /* if matches filter in */
   if (hit)
      WatchFilterAdd (rqptr, aptr);

#endif /* WATCH_CAT */
}                                   

/*****************************************************************************/
/*
Filter first on the client host name or address, then second on the service
(virtual host) name.  If the filter begins with a scheme (i.e. "http:" or
"https:") then filter on that as well.
*/ 

void WatchFilterClientService (REQUEST_STRUCT *rqptr)

{
   BOOL  match;
   int  WatchItem;
   char  *aptr, *cptr, *sptr;

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

#if WATCH_CAT

   /* can occur if ASTs are delivered after WATCH use is discontinued */
   if (!Watch.Category && !Watch.Module) return;

   if (Watch.Category & WATCH_ONE_SHOT_FLAG) return;

   if (!Watch.FilterSet)
   {
      /* WATCH is enabled but no filters have been specified */
      if (!WATCHING (rqptr, WATCH_CONNECT)) return;
      if (rqptr != Watch.RequestPtr)
      {
         WatchItem = rqptr->WatchItem;
         WatchSetWatch (rqptr, WATCH_NEW_ITEM);
         if (HTTP2_REQUEST(rqptr))
         {
            WatchThis (WATCHITM(rqptr), WATCH_CONNECT,
                       "HTTP/2 begin !UL with !AZ,!UL on !AZ//!AZ,!UL (!&I)",
                       rqptr->Stream2Ptr->Ident,
                       rqptr->ClientPtr->Lookup.HostName,
                       rqptr->ClientPtr->IpPort, 
                       rqptr->ServicePtr->RequestSchemeNamePtr,
                       rqptr->ServicePtr->ServerHostName,
                       rqptr->ServicePtr->ServerPort,
                       &rqptr->ServicePtr->ServerIpAddress);
         }
         else
         if (rqptr->rqNet.PersistentCount)
            WatchThis (WATCHITM(rqptr), WATCH_CONNECT,
                       "PERSISTENT (!#ZL) with !AZ,!UL on !AZ//!AZ,!UL (!&I)",
                       Watch.ItemWidth, WatchItem,
                       rqptr->ClientPtr->Lookup.HostName,
                       rqptr->ClientPtr->IpPort, 
                       rqptr->ServicePtr->RequestSchemeNamePtr,
                       rqptr->ServicePtr->ServerHostName,
                       rqptr->ServicePtr->ServerPort,
                       &rqptr->ServicePtr->ServerIpAddress);
      }
      else
         WatchSetWatch (rqptr, 0);
      return;
   }

   if (!Watch.FilterClient[0] && !Watch.FilterService[0]) return;

   if (Watch.FilterClient[0])
   {
      aptr = "CLIENT";
      sptr = Watch.FilterClient;
      if (isdigit(*sptr))
         cptr = rqptr->ClientPtr->IpAddressString;
      else
         cptr = rqptr->ClientPtr->Lookup.HostName;

      WatchNone (true);
      match = StringMatchRegex (rqptr, cptr, sptr);
      WatchNone (false);
      if (match)
      {
         if (Watch.FilterOutClient)
         {
            WatchFilterDrop (rqptr, aptr);
            return;
         }
      }
      else
      if (!Watch.FilterOutClient)
         return;
   }

   if (Watch.FilterService[0])
   {
      if (Watch.FilterClient[0])
         aptr = "CLIENT+SERVICE";
      else
         aptr = "SERVICE";
      sptr = Watch.FilterService;
      /* filter on service */
      if (MATCH5 (sptr, "http:"))
      {
         if (rqptr->ServicePtr->RequestScheme == SCHEME_HTTP)
            if (Watch.FilterOutService)
            {
               WatchFilterDrop (rqptr, aptr);
               return;
            }
         sptr += 5;
      }
      else
      if (MATCH6 (sptr, "https:"))
      {
         if (rqptr->ServicePtr->RequestScheme == SCHEME_HTTPS)
            if (Watch.FilterOutService)
            {
               WatchFilterDrop (rqptr, aptr);
               return;
            }
         sptr += 6;
      }
      while (*sptr == '/') sptr++;
      cptr = rqptr->ServicePtr->ServerHostPort;
      WatchNone (true);
      match = StringMatchRegex (rqptr, cptr, sptr);
      WatchNone (false);
      if (match)
      {
         if (Watch.FilterOutService)
         {
            WatchFilterDrop (rqptr, aptr);
            return;
         }
      }
      else
      if (!Watch.FilterOutService)
         return;
   }

   if (rqptr->WatchItem) return;

   WatchFilterAdd (rqptr, aptr);

#endif /* WATCH_CAT */
}                                   

/*****************************************************************************/
/*
Filter on the request's path or URI if the path does not begin with a slash
(e.g. if a proxy request).  If the filter begins with a dollar and track IDs
are being generated consider a track has been specified.
*/ 

void WatchFilterPathTrack (REQUEST_STRUCT *rqptr)

{
   BOOL  match;
   char  *aptr, *cptr, *sptr;

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

#if WATCH_CAT

   /* can occur if ASTs are delivered after WATCH use is discontinued */
   if (!Watch.Category && !Watch.Module) return;

   if (!Watch.FilterSet) return;
   if (!Watch.FilterUri[0]) return;
   /* if filtering-in and it's already being WATChed */
   if (!Watch.FilterOutURI && rqptr->WatchItem) return;

   sptr = Watch.FilterUri;
   cptr = NULL;
   aptr = "URI";
   cptr = rqptr->rqHeader.RequestUriPtr;
   if (!cptr)
   {
      if (Watch.FilterOutURI)
      {
         WatchFilterDrop (rqptr, aptr);
         return;
      }
   }

   WatchNone (true);
   match = StringMatchRegex (rqptr, cptr, sptr);
   WatchNone (false);
   if (match)
   {
      if (Watch.FilterOutURI)
      {
         WatchFilterDrop (rqptr, aptr);
         return;
      }
   }
   else
   if (!Watch.FilterOutURI)
      return;

   if (rqptr->WatchItem) return;

   WatchFilterAdd (rqptr, aptr);

   /* if wanting request header report then satisfy at this late stage */
   if (WATCHING(rqptr,WATCH_REQUEST_HEADER))
   {
      WatchThis (WATCHITM(rqptr), WATCH_REQUEST_HEADER, "DATA");
      DictWatchEntry (NULL);
      DictWatch (rqptr->rqDictPtr, DICT_TYPE_INTERNAL, "request_line");
      DictWatch (rqptr->rqDictPtr, DICT_TYPE_REQUEST, "*");
   }

#endif /* WATCH_CAT */
}                                   

/*****************************************************************************/
/*
Filter first on the *entire* request header.
*/ 

void WatchFilterRequestHeader (REQUEST_STRUCT *rqptr)

{
   BOOL  match;
   char  *aptr, *cptr, *sptr;
   DICT_ENTRY_STRUCT  *denptr;

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

#if WATCH_CAT

   /* can occur if ASTs are delivered after WATCH use is discontinued */
   if (!Watch.Category && !Watch.Module) return;

   if (Watch.Category & WATCH_ONE_SHOT_FLAG) return;

   if (!Watch.FilterSet) return;
   if (!Watch.FilterRequest[0]) return;
   /* if filtering-in and it's already being WATChed */
   if (!Watch.FilterOutRequest && rqptr->WatchItem) return;

   if (!(cptr = rqptr->rqHeader.RequestHeaderPtr))
   {
      if ((denptr = RequestDictHeader (rqptr)) == NULL) return;
      cptr = DICT_GET_VALUE(denptr);
   }

   aptr = "REQUEST";
   sptr = Watch.FilterRequest;
   WatchNone (true);
   match = StringMatchRegex (rqptr, cptr, sptr);
   WatchNone (false);
   if (match)
   {
      if (Watch.FilterOutRequest)
      {
         WatchFilterDrop (rqptr, aptr);
         return;
      }
   }
   else
   if (!Watch.FilterOutRequest)
      return;

   if (rqptr->WatchItem) return;

   WatchFilterAdd (rqptr, aptr);

   /* if wanting request header report then satisfy at this late stage */
   if (WATCH_CATEGORY(WATCH_REQUEST_HEADER))
   {
      WatchThis (WATCHITM(rqptr), WATCH_REQUEST_HEADER, "DATA");
      DictWatchEntry (NULL);
      DictWatch (rqptr->rqDictPtr, DICT_TYPE_INTERNAL, "request_line");
      DictWatch (rqptr->rqDictPtr, DICT_TYPE_REQUEST, "*");
   }

#endif /* WATCH_CAT */
}                                   

/*****************************************************************************/
/*
Filter on the HTTP status value (late in the request I know).
*/ 

void WatchFilterHttpStatus (REQUEST_STRUCT *rqptr)

{
   char  *aptr; 

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

#if WATCH_CAT

   /* can occur if ASTs are delivered after WATCH use is discontinued */
   if (!Watch.Category && !Watch.Module) return;

   if (Watch.Category & WATCH_ONE_SHOT_FLAG) return;

   if (!Watch.FilterSet) return;
   if (Watch.FilterStatus < 0) return;
   /* if it's already being WATChed */
   if (rqptr->WatchItem) return;

   aptr = "STATUS";
   if ((Watch.FilterStatus && Watch.FilterStatus <= 5 &&
        rqptr->rqResponse.HttpStatus / 100 == Watch.FilterStatus) ||
       rqptr->rqResponse.HttpStatus == Watch.FilterStatus)
   {
      if (Watch.FilterOutRequest)
      {
         WatchFilterDrop (rqptr, aptr);
         return;
      }
   }
   else
   if (!Watch.FilterOutRequest)
      return;

   if (rqptr->WatchItem) return;

   WatchFilterAdd (rqptr, aptr);

   /* if wanting request header report then satisfy at this late stage */
   if (WATCH_CATEGORY(WATCH_REQUEST_HEADER))
   {
      WatchThis (WATCHITM(rqptr), WATCH_REQUEST_HEADER, "DATA");
      DictWatchEntry (NULL);
      DictWatch (rqptr->rqDictPtr, DICT_TYPE_INTERNAL, "request_line");
      DictWatch (rqptr->rqDictPtr, DICT_TYPE_REQUEST, "*");
   }

   /* same for authorisation data */
   if (WATCH_CATEGORY(WATCH_AUTH))
      if (rqptr->RemoteUser[0])
         WatchThis (WATCHITM(rqptr), WATCH_AUTH,
"user:\'!AZ\' details:\'!AZ\' can:!AZ remote:\'!AZ\' realm:\'!AZ\'",
                    rqptr->RemoteUser,
                    rqptr->rqAuth.UserDetailsPtr,
                    AuthCanString (rqptr->rqAuth.RequestCan,
                                   AUTH_CAN_FORMAT_LONG),
                    rqptr->rqAuth.RemoteUser,
                    rqptr->rqAuth.RealmPtr);

#endif /* WATCH_CAT */
}                                   

/*****************************************************************************/
/*
Filter first on authentication realm (if applicable) then on authenticated
(remote) user (if applicable). 
*/ 

void WatchFilterRealmUser (REQUEST_STRUCT *rqptr)

{
   BOOL  match;
   char  *aptr, *cptr, *sptr;

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

#if WATCH_CAT

   /* can occur if ASTs are delivered after WATCH use is discontinued */
   if (!Watch.Category && !Watch.Module) return;

   if (Watch.Category & WATCH_ONE_SHOT_FLAG) return;

   if (!Watch.FilterSet) return;
   if (!Watch.FilterRealm[0] && !Watch.FilterUser[0]) return;
   /* if filtering-in and it's already being WATChed */
   if (!Watch.FilterOutRealm &&
       !Watch.FilterOutUser &&
       rqptr->WatchItem) return;

   if (Watch.FilterRealm[0])
   {
      aptr = "REALM";
      sptr = Watch.FilterRealm;
      cptr = rqptr->rqAuth.RealmPtr;
      WatchNone (true);
      match = StringMatchRegex (rqptr, cptr, sptr);
      WatchNone (false);
      if (match)
      {
         if (Watch.FilterOutRealm)
         {
            WatchFilterDrop (rqptr, aptr);
            return;
         }
      }
      else
      if (!Watch.FilterOutRealm)
         return;
   }

   if (Watch.FilterUser[0])
   {
      if (Watch.FilterRealm[0])
         aptr = "REALM+USER";
      else
         aptr = "USER";
      sptr = Watch.FilterUser;
      cptr = rqptr->RemoteUser;
      WatchNone (true);
      match = StringMatchRegex (rqptr, cptr, sptr);
      WatchNone (false);
      if (match)
      {
         if (Watch.FilterOutUser)
         {
            WatchFilterDrop (rqptr, aptr);
            return;
         }
      }
      else
      if (!Watch.FilterOutUser)
         return;
   }

   if (rqptr->WatchItem) return;

   WatchFilterAdd (rqptr, aptr);

   /* if wanting request header report then satisfy at this late stage */
   if (WATCH_CATEGORY(WATCH_REQUEST_HEADER))
   {
      WatchThis (WATCHITM(rqptr), WATCH_REQUEST_HEADER, "DATA");
      DictWatchEntry (NULL);
      DictWatch (rqptr->rqDictPtr, DICT_TYPE_INTERNAL, "request_line");
      DictWatch (rqptr->rqDictPtr, DICT_TYPE_REQUEST, "*");
   }

#endif /* WATCH_CAT */
}                                   

/*****************************************************************************/
/*
If the request is not already being WATCHED then allocate a WATCH item number
and report it's addition.
*/ 

void WatchFilterAdd
(
REQUEST_STRUCT *rqptr,
char *AddingThis
)
{
   /*********/
   /* begin */
   /*********/

#if WATCH_CAT

   if (rqptr != Watch.RequestPtr)
   {
      WatchSetWatch (rqptr, WATCH_NEW_ITEM);
      WatchThis (WATCHITM(rqptr), WATCH_FILTER,
                 "!AZ adding !AZ,!UL on !AZ//!AZ,!UL (!&I)",
                 AddingThis,
                 rqptr->ClientPtr->Lookup.HostName,
                 rqptr->ClientPtr->IpPort, 
                 rqptr->ServicePtr->RequestSchemeNamePtr,
                 rqptr->ServicePtr->ServerHostName,
                 rqptr->ServicePtr->ServerPort,
                 &rqptr->ServicePtr->ServerIpAddress);
   }

#endif /* WATCH_CAT */
}                                   

/*****************************************************************************/
/*
If the request is currently being WATCHED then report it's removal and reset
the WATCH item number.
*/ 

void WatchFilterDrop
(
REQUEST_STRUCT *rqptr,
char *DroppingThis
)
{
   /*********/
   /* begin */
   /*********/

#if WATCH_CAT

   if (rqptr->WatchItem)
      WatchThis (WATCHITM(rqptr), WATCH_FILTER,
                 "!AZ dropping from WATCH", DroppingThis);
   rqptr->WatchItem = 0;

#endif /* WATCH_CAT */
}                                   

/*****************************************************************************/
/*
Provide a formatted WATCH entry.  |ReportFormat| parameter must be in a
sys$fao() acceptable format and sufficient variable number parameters be
supplied to satisfy any FAO directives in that format string.  The |WatchItem|,
|SourceModuleName| and |SourceLineNumber| are passed by the macros |WATCHITM|
and |WATCHALL|.  These macros provide compile time flexibility in the various
data structures (e.g. request, HTTP/2, network IO) that can be used to initiate
WATCH points.  All the |WATCHITM| structures need is a |->WatchItem| field. 
The |WATCHALL| just uses the item number zero.
*/ 

void WatchThis
(
int WatchItem,
char *SourceModuleName,
int SourceLineNumber,
int64 Category,
char *ReportFormat,
...
)
{
#if WATCH_CAT

   static char  BufferFao [] = "!&@!AZ|!%T !8AZ !4ZL !#ZL !10AZ !&@|\n";

   int  argcnt, status;
   ushort  slen;
   ulong  *vecptr;
   ulong  FaoVector [128];
   char  *cptr;
   char  Buffer [16384];
   va_list  argptr;
   WATCH_STRUCT  WatchBuffer;

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

   if (DBUG && Watch.CliEnabled)
      fprintf (stdout, "WatchThis() |%s|\n", ReportFormat);

   /* this can occur if ASTs are delivered after WATCH use is discontinued */
   if (!Watch.Category && !Watch.Module) return;

   WatchNone (true);

   /* fudge for SESOLANET and SESOLANETIO within 8 chars */
   if (MATCH8 (SourceModuleName+3, "OLANETIO")) SourceModuleName += 3;

   vecptr = FaoVector;

   if (!Watch.ItemWidth ||
       Watch.ItemWidth != Watch.ItemDigits)
   {
      WatchItemSize (NULL);
      Watch.ItemDigits = Watch.ItemWidth;
      *vecptr++ = WatchItemHeader;
      *vecptr++ = Watch.ItemWidth - 4;
   }
   else
      *vecptr++ = "";

   if (Category & WATCH_INTERNAL)
      *vecptr++ = WatchServerQuotas (0);
   else
      *vecptr++ = "";

   *vecptr++ = 0;
   *vecptr++ = SourceModuleName;
   *vecptr++ = SourceLineNumber;
   *vecptr++ = Watch.ItemWidth; 
   *vecptr++ = WatchItem & WATCH_ITEM_NUMBER_MASK;
#if WATCH_MOD
   if (Category & WATCH_MODULE_FLAG)
      cptr = WatchWhatModule (Category);
   else
#endif /* WATCH_MOD */
      cptr = WatchWhatCategory (Category);
   if (!cptr) cptr = "????????";
   *vecptr++ = cptr;

   /* append the report format string and it's parameters */
   *vecptr++ = ReportFormat;
   va_count (argcnt);
   va_start (argptr, ReportFormat);
   for (argcnt -= 5; argcnt; argcnt--)
      *vecptr++ = va_arg (argptr, unsigned long);
   va_end (argptr);

   if (DBUG && Watch.CliEnabled)
      fprintf (stdout, "WatchThis() %d |%s|\n", argcnt, ReportFormat);

   if (Category & WATCH_INTERNAL)
      *vecptr++ = WatchServerQuotas (0);
   else
      *vecptr++ = "";

   FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI);
   status = FaolToBuffer (Buffer, sizeof(Buffer), &slen,
                          BufferFao, &FaoVector);

   if (VMSnok (status) || status == SS$_BUFFEROVF)
   {
      HttpdStackTrace ("WatchThis()", FI_LI);
      fprintf (stdout, "%%HTTPD-W-NOTICED2, %s:%d FaolToBuffer() %%X%08.08X\n",
               FI_LI, status);
   }

   WatchWrite (Buffer, slen);

   WatchNone (false);

#endif /* WATCH_CAT */
}

/*****************************************************************************/
/*
Function to provide a formatted data WATCH entry, with trailing information
from the caller.  'DataFormat' parameter must be in a sys$fao() acceptable
format and sufficient variable number parameters be supplied to satisfy any FAO
directives in that format string.  Should include appropriate carriage-control.
*/ 

void WatchDataFormatted
(
char *DataFormat,
...
)
{
#if WATCH_CAT

   int  status,
        argcnt;
   ushort  slen;
   ulong  *vecptr;
   ulong  FaoVector [64];
   char  Buffer [65535];
   va_list  argptr;

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

   va_count (argcnt);

   if (DBUG && Watch.CliEnabled)
      fprintf (stdout, "WatchDataFormatted() %d\n", argcnt);

   /* this can occur if ASTs are delivered after WATCH use is discontinued */
   if (!Watch.Category && !Watch.Module) return;

   vecptr = FaoVector;
   va_start (argptr, DataFormat);
   for (argcnt -= 1; argcnt; argcnt--)
      *vecptr++ = va_arg (argptr, ulong);
   va_end (argptr);

   WatchNone (true);

   status = FaolToBuffer (Buffer, sizeof(Buffer), &slen,
                          DataFormat, &FaoVector);
   if (VMSnok (status) || status == SS$_BUFFEROVF)
      ErrorNoticed (NULL, status, NULL, FI_LI);
   Buffer[slen] = '\0';

   WatchNone (false);

   WatchWrite (Buffer, slen);

#endif /* WATCH_CAT */
}

/*****************************************************************************/
/*
Output the supplied, non-formatted data in the WATCH report.  Allows any
printable output to be included as a block in the WATCH output.
*/ 

void WatchData
(
char *DataPtr,
int DataLength
)
{
#if WATCH_CAT

   /* see comment against StrDscThis() */
   char  newline = '\n';

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

   if (DBUG && Watch.CliEnabled)
      fprintf (stdout, "WatchData() %d\n", DataLength);

   /* this can occur if ASTs are delivered after WATCH use is discontinued */
   if (!Watch.Category && !Watch.Module) return;

   if (!DataPtr) return;

   if (DataLength == -1) DataLength = strlen(DataPtr);

   if (DataLength < 0 || DataLength > 65535)
   {
      HttpdStackTrace ("WatchData()", FI_LI);
      ErrorNoticed (NULL, SS$_BUGCHECK, NULL, FI_LI);
      return;
   }

   WatchWrite (DataPtr, DataLength);

   if (DataLength && DataPtr[DataLength-1] != '\n')
      WatchWrite (&newline, 1);

#endif /* WATCH_CAT */
}

/*****************************************************************************/
/*
Output the supplied data using WATCH as a hex and printable character dump.
If the data length is negative dump to <stdout>.
*/ 

void WatchDataDump
(
char *DataPtr,
int DataLength
)
{
#if WATCH_CAT

   WatchDataDumpAt (DataPtr, DataLength, 0);

#endif /* WATCH_CAT */
}

/*****************************************************************************/
/*
Output the supplied data using WATCH as a hex and printable character dump.
If the data length is negative dump to <stdout>.
*/ 

void WatchDataDumpAt
(
char *DataPtr,
int DataLength,
int DataAt
)
{
#if WATCH_CAT

#define MAX_LINES 128
#define BYTES_PER_LINE 32
#define HEX_PER_LINE (BYTES_PER_LINE * 2)
#define BYTES_PER_GROUP 4
#define GROUPS_PER_LINE (BYTES_PER_LINE / BYTES_PER_GROUP)
#define CHARS_PER_LINE ((HEX_PER_LINE + GROUPS_PER_LINE + BYTES_PER_LINE) + 1)

   static char  HexDigits [] = "0123456789ABCDEF";

   BOOL  ToStdout;
   int  ByteCount,
        CurrentDataCount,
        DataCount,
        LinesSnipped;
   char  *cptr, *sptr, *zptr,
         *CurrentDataPtr,
         *PrevLinePtr,
         *ThisLinePtr;
   char  LineBuffer [CHARS_PER_LINE+1],
         DumpBuffer [(CHARS_PER_LINE * MAX_LINES) + 1];

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

   if (DBUG && Watch.CliEnabled)
      fprintf (stdout, "WatchDataDump() %d\n", DataLength);

   if (ToStdout = DataLength < 0) DataLength *= -1;

   if (DataLength > 65535)
   {
      HttpdStackTrace ("WatchDataDump()", FI_LI);
      ErrorNoticed (NULL, SS$_BUGCHECK, NULL, FI_LI);
      return;
   }

   /* this can occur if ASTs are delivered after WATCH use is discontinued */
   if (!Watch.Category && !Watch.Module && !ToStdout) return;

   if (!DataPtr) return;

   /* end of buffer (zptr) allows plenty for "overflow" */
   zptr = (sptr = DumpBuffer) + sizeof(DumpBuffer) - (CHARS_PER_LINE * 2);
   cptr = DataPtr;
   PrevLinePtr = "";
   LinesSnipped = 0;
   DataCount = DataLength;
   if (DataAt) 
   {
      DataAt = DataCount - DataAt;
      if (DataAt > 32) DataAt += 32;
   }

   while (DataCount)
   {
      CurrentDataPtr = cptr;
      CurrentDataCount = DataCount;

      ThisLinePtr = sptr;
      ByteCount = BYTES_PER_LINE;

      /* hex chars */
      while (ByteCount && DataCount)
      {
         *sptr++ = HexDigits[*(uchar*)cptr >> 4];
         *sptr++ = HexDigits[*(uchar*)cptr & 0xf];
         cptr++;
         DataCount--;
         ByteCount--;
         if (!(ByteCount % BYTES_PER_GROUP)) *sptr++ = ' ';
      }

      /* space to literal chars */
      while (ByteCount)
      {
         *sptr++ = ' ';
         *sptr++ = ' ';
         ByteCount--;
         if (!(ByteCount % BYTES_PER_GROUP)) *sptr++ = ' ';
      }

      cptr = CurrentDataPtr;
      DataCount = CurrentDataCount;

      /* literal chars */
      ByteCount = BYTES_PER_LINE;
      while (ByteCount && DataCount)
      {
         if (*cptr >= 32 && *cptr <= 126)
            *sptr++ = *cptr++;
         else
         {
            *sptr++ = '.';
            cptr++;
         }
         DataCount--;
         ByteCount--;
      }

      if (DataAt && DataCount < DataAt)
      {
         DataAt = 0;
         *sptr++ = ' ';
         *sptr++ = '<';
         *sptr++ = '=';
      }

      *sptr++ = '\n';

      if (!memcmp (PrevLinePtr, ThisLinePtr, sptr - ThisLinePtr))
      {
         /* this line is exactly the same as the previous */
         sptr = ThisLinePtr;
         LinesSnipped++;
      }
      else
      if (LinesSnipped)
      {
         *sptr = '\0';
         strzcpy (LineBuffer, sptr = ThisLinePtr, sizeof(LineBuffer));
         sptr += sprintf (sptr, "8< %d lines %d bytes 8<\n",
                          LinesSnipped, LinesSnipped * BYTES_PER_LINE); 
         PrevLinePtr = sptr;
         sptr += sprintf (sptr, "%s", LineBuffer);
         LinesSnipped = 0;
      }
      else
         PrevLinePtr = ThisLinePtr;

      if (sptr >= zptr)
      {
         /* flush the buffer */
         *sptr = '\0';
         if (LinesSnipped)
         {
            *sptr = '\0';
            strzcpy (LineBuffer, sptr = ThisLinePtr, sizeof(LineBuffer));
            sptr += sprintf (sptr, "8< %d line(s) %d byte(s) 8<\n",
                             LinesSnipped, LinesSnipped * BYTES_PER_LINE); 
            PrevLinePtr = "";
            sptr += sprintf (sptr, "%s", LineBuffer);
            LinesSnipped = 0;
         }
         if (ToStdout)
            fputs (DumpBuffer, stdout);
         else
            WatchData (DumpBuffer, sptr - DumpBuffer);
         /* end of buffer (zptr) allows plenty for "overflow" */
         zptr = (sptr = DumpBuffer) + sizeof(DumpBuffer) - (CHARS_PER_LINE * 2);
      }

      if (!DataCount)
      {
         /* WATCH data has been exhausted */
         if (LinesSnipped)
            sptr += sprintf (sptr, "8< %d line(s) %d byte(s) 8<\n",
                             LinesSnipped, LinesSnipped * BYTES_PER_LINE); 
         *sptr = '\0';
         if (ToStdout)
            fputs (DumpBuffer, stdout);
         else
            WatchData (DumpBuffer, sptr - DumpBuffer);
      }
   }

   if (ToStdout) fflush (stdout);

#endif /* WATCH_CAT */
}

/*****************************************************************************/
/*
If /percent/ is 50% or less then if any quota is less than that percenage of
original return a pointer to a string containing the quotas is returned,
otherwise a NULL.  If /percent/ is 0 then unconditionally return a pointer to a
string containing the quotas.
*/ 

char* WatchServerQuotas (int percent)

{
   static $DESCRIPTOR (QuotasFaoDsc,
"!AZAST:!UL/!UL BIO:!UL/!UL BYT:!UL/!UL DIO:!UL/!UL ENQ:!UL/!UL \
FIL:!UL/!UL PGFL:!UL/!UL PRC:!UL/!UL TQ:!UL/!UL\n\0");
   static char  Buffer [256];

   static int  JpiAstCnt,
               JpiBioCnt,
               JpiBytCnt,
               JpiDioCnt,
               JpiEnqCnt,
               JpiFilCnt,
               JpiPagFilCnt,
               JpiPrcCnt,
               JpiTqCnt;

   static struct {
      ushort  BufferLength;
      ushort  ItemCode;
      ulong  BufferAddress;
      ulong  ReturnLengthAddress;
   }
      JpiItem [] =
   { { sizeof(JpiAstCnt), JPI$_ASTCNT, &JpiAstCnt, 0 },
     { sizeof(JpiBioCnt), JPI$_BIOCNT, &JpiBioCnt, 0 },
     { sizeof(JpiBytCnt), JPI$_BYTCNT, &JpiBytCnt, 0 },
     { sizeof(JpiDioCnt), JPI$_DIOCNT, &JpiDioCnt, 0 },
     { sizeof(JpiEnqCnt), JPI$_ENQCNT, &JpiEnqCnt, 0 },
     { sizeof(JpiFilCnt), JPI$_FILCNT, &JpiFilCnt, 0 },
     { sizeof(JpiPagFilCnt), JPI$_PAGFILCNT, &JpiPagFilCnt, 0 },
     { sizeof(JpiPrcCnt), JPI$_PRCCNT, &JpiPrcCnt, 0 },
     { sizeof(JpiTqCnt), JPI$_TQCNT, &JpiTqCnt, 0 },
     { 0,0,0,0 }
   };

   int  status;
   ulong  *vecptr;
   ulong  FaoVector [32];
   char  alert [64],
         buf [64];
   struct dsc$descriptor_s  *faoptr;
   $DESCRIPTOR (BufferDsc, Buffer);
   IO_SB  IOsb;

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

   status = sys$getjpiw (EfnWait, 0, 0, &JpiItem, &IOsb, 0, 0);
   if (VMSok (status)) status = IOsb.Status;
   if (VMSnok (status))
   {
      fprintf (stdout, "%%HTTPD-W-NOTICED2, %s:%d sys$getjpiw() %%X%08.08X\n",
               FI_LI, status);
      return ("sys$getjpiw() failed!");
   }

   if (percent > 0)
   {
      buf[0] = '\0';
      if (JpiAstCnt <= HttpdProcess.AstLm * percent / 100)
         strzcat (buf, ",AST", sizeof(buf));
      if (JpiBioCnt <= HttpdProcess.BioLm * percent / 100)
         strzcat (buf, ",BIO", sizeof(buf));
      if (JpiBytCnt <= HttpdProcess.BytLm * percent / 100)
         strzcat (buf, ",BYT", sizeof(buf));
      if (JpiDioCnt <= HttpdProcess.DioLm * percent / 100)
         strzcat (buf, ",DIO", sizeof(buf));
      if (JpiEnqCnt <= HttpdProcess.EnqLm * percent / 100)
         strzcat (buf, ",ENQ", sizeof(buf));
      if (JpiFilCnt <= HttpdProcess.FilLm * percent / 100)
         strzcat (buf, ",FIL", sizeof(buf));
      if (JpiPagFilCnt <= HttpdProcess.PgFlQuo * percent / 100)
         strzcat (buf, ",PGFL", sizeof(buf));
//      if (JpiPrcCnt <= HttpdProcess.PrcLm * percent / 100)
//         strzcat (buf, ",PRC", sizeof(buf));
      if (JpiTqCnt <= HttpdProcess.TqLm * percent / 100)
         strzcat (buf, ",TQ", sizeof(buf));
      if (!buf[0]) return (NULL);
      sprintf (alert, "<=%d%%:%s; ", percent, buf+1);
   }

   vecptr = &FaoVector;

   if (percent && buf[0])
      *vecptr++ = alert;
   else
      *vecptr++ = "";

   *vecptr++ = JpiAstCnt;
   *vecptr++ = HttpdProcess.AstLm;
   *vecptr++ = JpiBioCnt;
   *vecptr++ = HttpdProcess.BioLm;
   *vecptr++ = JpiBytCnt;
   *vecptr++ = HttpdProcess.BytLm;
   *vecptr++ = JpiDioCnt;
   *vecptr++ = HttpdProcess.DioLm;
   *vecptr++ = JpiEnqCnt;
   *vecptr++ = HttpdProcess.EnqLm;
   *vecptr++ = JpiFilCnt;
   *vecptr++ = HttpdProcess.FilLm;
   *vecptr++ = JpiPagFilCnt;
   *vecptr++ = HttpdProcess.PgFlQuo;
   *vecptr++ = JpiPrcCnt;
   *vecptr++ = HttpdProcess.PrcLm;
   *vecptr++ = JpiTqCnt;
   *vecptr++ = HttpdProcess.TqLm;

   status = sys$faol (&QuotasFaoDsc, 0, &BufferDsc, &FaoVector);
   if (VMSnok (status) || status == SS$_BUFFEROVF)
      fprintf (stdout, "%%HTTPD-W-NOTICED2, %s:%d sys$faol() %%X%08.08X\n",
               FI_LI, status);

   return (Buffer);
}

/*****************************************************************************/
/*
Keep an eye on remaining process quotas.  If any quota drops to 30% provide an
alert every hour.  If it drops to 10% then every minute.  Though for ASTs
that's probably too late.  Provide an OPCOM meessage, and in-server-log stamp
of the event, and an alert shown on the Server Admin menu.
*/ 

void WatchServerAlertQuotas ()

{
   static int  PrevHour,
               PrevMin;

   int  quota;
   char  *cptr, *sptr;

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

   quota = 30;
#if WATCH_MOD
   if (cptr = SysTrnLnm (WASD_ALERT_QUOTAS)) quota = atoi(cptr);
#endif
   if (cptr = WatchServerQuotas (quota))
   {
      if (sptr = WatchServerQuotas (20))
      {
         quota = 20;
         cptr = sptr;
         if (sptr = WatchServerQuotas (10))
         {
            quota = 10;
            cptr = sptr;
         }
      }
   }
   if (!cptr) return;

   if (quota > 10 && HttpdTime7[3] != PrevHour)
      PrevHour = HttpdTime7[3];
   else
   if (HttpdTime7[4] != PrevMin)
      PrevMin = HttpdTime7[4];
   else
      return;

   for (sptr = cptr; *sptr && *sptr != '\n'; sptr++);
   *sptr = '\0';
   FaoToOpcom ("QUOTAS !UL% !AZ", quota, cptr);
   /* %SYSTEM-W-EXQUOTA, process quota exceeded */
   ErrorNoticed (NULL, 24, cptr, FI_LI);

   for (sptr = cptr; *sptr && *sptr != ';'; sptr++);
   *sptr = '\0';
   FaoToBuffer (WatchQuotasAlert, sizeof(WatchQuotasAlert), NULL,
                "!6%D !5%T !AZ", 0, 0, cptr);
   fflush (stdout);
}

/*****************************************************************************/
/*
Can be called to initialise a timestamp and then report incrementally.
*/

void WatchDuration (int64 *dura64ptr, char *module, int line)

{
   int64  delta64;

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

   if (module)
   {
      sys$gettim (&delta64);
      delta64 = *dura64ptr - delta64;
      FaoToStdout ("%WATCH-I-DURATION, !%T !AZ:!UL\n",
                   &delta64, module, line);
   }
   else
   {
      sys$gettim (dura64ptr);
      FaoToStdout ("%WATCH-I-DURATION, !%T\n", dura64ptr);
   }
}

/*****************************************************************************/
/*
Return a string corresponding to the function name of the address passed in
'FunctionPtr'.  The '#include watchfunc.h" below provides a static array
containing function address and name details that during module WATCHing can be
used by the FAO.C ('!&F') '!&A' directive to provide function names rather than
just address information.  Returns NULL if the address is unknown.  The
'watchfunc.h' file is generated by the BUILD_WATCHFUNC.COM procedure.
*/ 

char* WatchFunction (void *FunctionPtr)

{
   int  idx;

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

   if (!FunctionPtr) return (NULL);
   for (idx = 0; WatchFunc[idx].Address; idx++)
      if (WatchFunc[idx].Address == FunctionPtr) break;
   return (WatchFunc[idx].Name);
}

/*****************************************************************************/
/*
Return a string corresponding to the bit set in the parameter.
*/ 

#if WATCH_CAT

char* WatchWhatCategory (int64 number)

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

   switch (number)
   {
      case WATCH_AUTH             : return ("AUTHORIZE");
      case WATCH_CONNECT          : return ("CONNECT");
      case WATCH_CGI              : return ("CGI");
      case WATCH_DCL              : return ("DCL");
      case WATCH_DECNET           : return ("DECNET");
      case WATCH_ERROR            : return ("ERROR");
      case WATCH_FILTER           : return ("FILTER");
      case WATCH_HTTP2            : return ("HTTP/2");
      case WATCH_INTERNAL         : return ("INTERNAL");
      case WATCH_LOG              : return ("LOG");
      case WATCH_MAPPING          : return ("MAPPING");
      case WATCH_MATCH            : return ("MATCH");
      case WATCH_NETWORK          : return ("NETWORK");
      case WATCH_NETWORK_OCTETS   : return ("NET-OCTETS");
      case WATCH_PROXY            : return ("PROXY");
      case WATCH_PROXY_REQU_HDR   : return ("PRO-REQ-HEADER");
      case WATCH_PROXY_REQU_BDY   : return ("PRO-REQ-BODY");
      case WATCH_PROXY_RESP_HDR   : return ("PRO-RES-HEADER");
      case WATCH_PROXY_RESP_BDY   : return ("PRO-RES-BODY");
      case WATCH_PROXY_REWORK     : return ("PROXY-REWORK");
      case WATCH_REQUEST          : return ("REQUEST");
      case WATCH_REQUEST_BODY     : return ("REQ-BODY");
      case WATCH_REQUEST_HEADER   : return ("REQ-HEADER");
      case WATCH_RESPONSE         : return ("RESPONSE");
      case WATCH_RESPONSE_BODY    : return ("RES-BODY");
      case WATCH_RESPONSE_HEADER  : return ("RES-HEADER");
      case WATCH_SCRIPT           : return ("SCRIPT");
      case WATCH_SESOLA           : return ("SSL");
      case WATCH_WEBDAV           : return ("WEBDAV");
   }

   return (NULL);
}

#endif /* WATCH_CAT */

/*****************************************************************************/
/*
Return a string corresponding to the bit set in the parameter.
*/ 

#if WATCH_MOD

char* WatchWhatModule (int64 number)

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

   switch (number)
   {
      case WATCH_MOD_AUTH         : return ("_AUTH..");
      case WATCH_MOD_BODY         : return ("_BODY");
      case WATCH_MOD_CACHE        : return ("_CACHE");
      case WATCH_MOD_CGI          : return ("_CGI");
      case WATCH_MOD_CONFIG       : return ("_CONFIG");
      case WATCH_MOD_DCL          : return ("_DCL");
      case WATCH_MOD_DECNET       : return ("_DECNET");
      case WATCH_MOD_DIR          : return ("_DIR");
      case WATCH_MOD_FAO          : return ("_FAO");
      case WATCH_MOD_FILE         : return ("_FILE");
      case WATCH_MOD_HTADMIN      : return ("_HTADMIN");
      case WATCH_MOD_HTTP2        : return ("_HTTP2..");
      case WATCH_MOD_INSTANCE     : return ("_INSTANCE");
      case WATCH_MOD_MAPURL       : return ("_MAPURL");
      case WATCH_MOD_METACON      : return ("_METACON");
      case WATCH_MOD_MSG          : return ("_MSG");
      case WATCH_MOD_NET          : return ("_NET");
      case WATCH_MOD_REQUEST      : return ("_REQUEST");
      case WATCH_MOD_ODS          : return ("_ODS");
      case WATCH_MOD_PUT          : return ("_PUT");
      case WATCH_MOD_PROXY        : return ("_PROXY..");
      case WATCH_MOD_RESPONSE     : return ("_RESPONSE");
      case WATCH_MOD_SERVICE      : return ("_SERVICE");
      case WATCH_MOD_SESOLA       : return ("_SESOLA..");
      case WATCH_MOD_SSI          : return ("_SSI");
      case WATCH_MOD_THROTTLE     : return ("_THROTTLE");
      case WATCH_MOD_UPD          : return ("_UPD");
      case WATCH_MOD_VM           : return ("_VM");
      case WATCH_MOD_WEBDAV       : return ("_WEBDAV");
      /* special cases (no pun intended) */
      case WATCH_MOD__DETAIL      : return ("_detail");
      case WATCH_MOD__OTHER       : return ("_other");
   }

   return (NULL);
}

#endif /* WATCH_MOD */

/*****************************************************************************/
/*
Parse the /WATCH= qualifier string for command-line startup control. General
format is "/WATCH=[NOSTARTUP,]items[,module][,client][,service][,URI/track]". 
The first mandatory parameter, 'items', may be preceded by an optional
NOSTARTUP keyword.  This suppresses all WATCH output until the server is ready
to accept requests (reducing the some WATCH item output considerably).  The
'items' parameter can be one or two numbers representing the items to be
displayed (these may be found in the WATCH report output) or more conveniently
can be a parenthesized, comma-separated list of item names.  For example,
"/WATCH=ITEM=(MAPPING,REQUEST,RESPONSE)" and/or module names,
"/WATCH=ITEM=(REQUEST,RESPONSE,_AUTH..,_MAPURL,_METACON)".  The item names can
be any found in WatchWhat() immediately above and must be supplied exactly as
the strings appear in the switch() statement above (i.e. note some have
trailing "..").
*/ 

BOOL WatchCliParse (char *String)

{
#if WATCH_CAT

   BOOL  EndItemList,
         Include_Fao,
         Include_Instance,
         Include_Other,
         NoItem;
   ulong  ucnt;
   char  *cptr, *sptr, *zptr;

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

   if (strsame (String, "/NOWATCH", 6))
   {
      Watch.Disabled = -1;
      return (true);
   }
   if (Watch.Disabled < 0) return (true);
   Watch.CliEnabled = true;
   Watch.FilterStatus = -1;
   Include_Fao = Include_Instance = Include_Other = false;

   cptr = String;
   while (*cptr && *cptr != '=') cptr++;
   while (*cptr == '=' || *cptr == '\"') cptr++;
   if (!*cptr) return (true);
   if (*cptr == '(') cptr++;

   if (strsame (cptr, "LIST", -1))
   {
      char  CategoryList [WATCH_CATEGORY_LIST_SIZE];
      sptr = CategoryList;
      for (ucnt = 1; ucnt; ucnt = ucnt << 1)
      {
         cptr = WatchWhatCategory (ucnt);
         if (cptr)
         {
            if (sptr > CategoryList) { *sptr++ = ','; *sptr++ = ' '; }
            while (*cptr) *sptr++ = to_lower(*cptr++);
         }
      }
#if WATCH_MOD
      /* then any modules */
      for (ucnt = 1; ucnt; ucnt = ucnt << 1)
      {
         cptr = WatchWhatModule (ucnt);
         if (cptr)
         {
            if (sptr > CategoryList) { *sptr++ = ','; *sptr++ = ' '; }
            while (*cptr) *sptr++ = to_lower(*cptr++);
         }
      }
#endif /* WATCH_MOD */
      *sptr = '\0';
      FaoToStdout ("%HTTPD-I-WATCH, !AZ\n", CategoryList);
      exit (SS$_NORMAL);
   }

   if (strsame (cptr, "NOSTARTUP", 7))
   {
      Watch.CliNoStartup = true;
      while (isalpha(*cptr)) cptr++;
      if (*cptr == ',' || *cptr == '=') cptr++;
   }

   if (strsame (cptr, "ITEM=(", 6) ||
       strsame (cptr, "ITEMS=(", 7))
   {
      cptr += 6;
      if (*cptr == '(') cptr++;
      Watch.Category = Watch.Module = 0;
      EndItemList = false;
      while (*cptr && *cptr != ')' && !EndItemList)
      {
         sptr = cptr;
         while (*cptr && *cptr != ',' && *cptr != ')') cptr++;
         if (*cptr == ')') EndItemList = true;
         if (*cptr) *cptr++ = '\0';
         if (strsame (sptr, "NO", 2) && !strsame (sptr, "NOTICED", -1))
         {
            NoItem = true;
            sptr += 2;
         }
         else
            NoItem = false;
         for (ucnt = 1; ucnt; ucnt = ucnt << 1)
         {
            zptr = WatchWhatCategory (ucnt);
            if (zptr && strsame (sptr, zptr, -1))
            {
               if (NoItem)
                  Watch.Category &= ~ucnt;
               else
                  Watch.Category |= ucnt;
               break;
            }
#if WATCH_MOD
            zptr = WatchWhatModule (ucnt);
            if (zptr && strsame (sptr, zptr, -1))
            {
               if (NoItem)
                  Watch.Module &= ~ucnt;
               else
                  Watch.Module |= ucnt;
               break;
            }
#endif /* WATCH_MOD */
         }
         if (!ucnt)
         {
            if (strsame (sptr, "ALLCAT", -1))
               Watch.Category |= (ucnt = 0x7fffffff);
            else
            if (strsame (sptr, "ALLMOD", -1))
               Watch.Module |= (ucnt = 0x7fffffff);
#if WATCH_MOD
            /* unless specifically included these are always excluded */
            else
            if (strsame (sptr, "_FAO", -1))
               Include_Fao = true;
            else
            if (strsame (sptr, "_INSTANCE", -1))
               Include_Instance = true;
            else
            if (strsame (sptr, "_OTHER", -1))
               Include_Other = true;
#endif /* WATCH_MOD */
         }
         if (!ucnt)
         {
            FaoToStdout ("%HTTPD-E-WATCH, unknown item\n \\!AZ\\\n", sptr);
            return (false);
         }
         if (*cptr == ',') cptr++;
      }
   }
   else
   {
      if (!(Watch.Category = atoq(cptr)))
      {
         if (!isdigit(*cptr))
         {
            FaoToStdout ("%HTTPD-E-WATCH, invalid category number");
            return (false);
         }
      }
      while (*cptr && (*cptr == '-' || isdigit(*cptr))) cptr++;
      if (*cptr == ',') cptr++;
      if (*cptr == '-' || isdigit(*cptr))
      {
         if (!(Watch.Module = atoq(cptr)))
         {
            if (!isdigit(*cptr))
            {
               FaoToStdout ("%HTTPD-E-WATCH, invalid module number");
               return (false);
            }
         }
      }
   }

   Watch.Category &= WATCH_CAT_MASK;
   Watch.Module &= WATCH_MOD_MASK;

#if WATCH_MOD
   /* unless specifically included these are always excluded */
   if (!Include_Fao) Watch.Module = Watch.Module & ~WATCH_MOD_FAO;
//   if (!Include_Instance) Watch.Module = Watch.Module & ~WATCH_MOD_INSTANCE;
//   if (!Include_Other) Watch.Module = Watch.Module & ~WATCH_MOD__OTHER;
#endif /* WATCH_MOD */

   Watch.Module |= WATCH_MODULE_FLAG;

   if (*cptr != ',') return (true);
   zptr = (sptr = Watch.FilterClient) + sizeof(Watch.FilterClient);
   while (*cptr && *cptr != ',' && *cptr != '\"' && sptr < zptr)
      *sptr++ = *cptr++;
   if (sptr >= zptr)
   {
      FaoToStdout ("%HTTPD-E-WATCH, invalid client filter");
      return (false);
   }
   *sptr = '\0';
   if (*cptr == ',') cptr++;

   if (*cptr == ')') return (true);
   zptr = (sptr = Watch.FilterService) + sizeof(Watch.FilterService);
   while (*cptr && *cptr != ',' && *cptr != '\"' && sptr < zptr)
      *sptr++ = *cptr++;
   if (sptr >= zptr)
   {
      FaoToStdout ("%HTTPD-E-WATCH, invalid service filter");
      return (false);
   }
   *sptr = '\0';
   if (*cptr == ',') cptr++;

   if (*cptr == ')') return (true);
   zptr = (sptr = Watch.FilterUri) + sizeof(Watch.FilterUri);
   while (*cptr && *cptr != ',' && *cptr != '\"' && sptr < zptr)
      *sptr++ = *cptr++;
   if (sptr >= zptr)
   {
      FaoToStdout ("%HTTPD-E-WATCH, invalid URI/track filter");
      return (false);
   }
   *sptr = '\0';

   if (!Watch.FilterClient[0])
      strzcpy (Watch.FilterClient, "*", sizeof(Watch.FilterClient));
   if (!Watch.FilterUri[0])
      strzcpy (Watch.FilterUri, "*", sizeof(Watch.FilterUri));
   if (!Watch.FilterService[0])
      strzcpy (Watch.FilterService, "*", sizeof(Watch.FilterService));

   return (true);

#else /* WATCH_CAT */

   FaoToStdout ("%HTTPD-E-WATCH, is not a compiled option");
   return (false);

#endif /* WATCH_CAT */
}

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

void WatchCliSettings (BOOL StartUpComplete)

{
#if WATCH_CAT

   static int  WatchCategory,
               WatchModule;

   ulong  ucnt;
   char  *cptr, *sptr;
   char  CategoryList [WATCH_CATEGORY_LIST_SIZE];

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

   if (!StartUpComplete)
   {
      WatchCategory = Watch.Category;
      WatchModule = Watch.Module;
      if (Watch.CliNoStartup) Watch.Category = Watch.Module = 0;
      return;
   }
   Watch.Category = WatchCategory;
   Watch.Module = WatchModule;

   if (Watch.Disabled)
   {
      FaoToStdout ("%HTTPD-I-WATCH, disabled\n");
      return;
   }

   if (!Watch.Category && !Watch.Module) return;

   sptr = CategoryList;
   /* first any categories */
   for (ucnt = 1; ucnt; ucnt = ucnt << 1)
   {
      cptr = WatchWhatCategory (Watch.Category & ucnt);
      if (cptr)
      {
         if (sptr > CategoryList) { *sptr++ = ','; *sptr++ = ' '; }
         while (*cptr) *sptr++ = to_lower(*cptr++);
      }
   }
#if WATCH_MOD
   /* then any modules */
   for (ucnt = 1; ucnt; ucnt = ucnt << 1)
   {
      cptr = WatchWhatModule (Watch.Module & ucnt);
      if (cptr)
      {
         if (sptr > CategoryList) { *sptr++ = ','; *sptr++ = ' '; }
         while (*cptr) *sptr++ = to_lower(*cptr++);
      }
   }
#endif /* WATCH_MOD */
   *sptr = '\0';

   Watch.LogFileOut = stdout;

   FaoToStdout (
"%HTTPD-I-WATCH, !&?NOSTARTUP \r\r(!SL,!SL) !AZ\n\
-WATCH-I-CLIENT, client filter \"!AZ\"\n\
-WATCH-I-SERVICE, service filter \"!AZ\"\n\
-WATCH-I-PATH, URI/track filter \"!AZ\"\n",
            Watch.CliNoStartup,
            Watch.Category, Watch.Module & WATCH_MOD_MASK, CategoryList,
            Watch.FilterClient,
            Watch.FilterService,
            Watch.FilterUri);

#endif /* WATCH_CAT */
}

/*****************************************************************************/
/*
Generate a report page listing all of the processes belonging to the server
process.
*/

/* seems a lot but I recall some site having a HUGE number of IDs */
#define JPI_PROCESS_RIGHTS_MAX 1024

#define PSCAN$_GETJPI_BUFFER_SIZE 24

WatchProcessReport (REQUEST_STRUCT *rqptr)

{
   static char  BeginPage [] =
"<p><table class=\"lftlft\">\n\
<tr><th></th>\
<th>PID</th>\
<th>User</u></th>\
<th>Process Name</th>\
<th>Image</th>\
<th>Mode</th>\
<th>State</th>\
<th>Priority</th>\
</tr>\n";

   static char  ProcessFao [] =
"<tr!AZ>\
<td><a href=\"!AZ?pid=!8XL&puser=!AZ\">!3ZL</a></td>\
<td>!8XL</td>\
<td>!AZ</td>\
<td>!AZ</td>\
<td>!AZ</td>\
<td>!AZ</td>\
<td>!AZ</td>\
<td>!UL&nbsp;/&nbsp;!UL</td>\
</tr>\n";

   static char  EndPageFao [] =
"</table>\n\
!AZ\
</body>\n\
</html>\n";

   static char  *StateNameArray [] = {
                "1234","COLPG","MWAIT","CEF","PFW","LEF","LEFO",
                "HIB","HIBO","SUSP","SUSPO","FPG","COM","COMO","CUR" };

   static ulong  GetJpiControlFlags = JPI$M_IGNORE_TARGET_STATUS;

   static ulong  JpiMode,
                 JpiPid,
                 JpiPri,
                 JpiPrib,
                 JpiRightsSize,
                 JpiState;
   static char  JpiImagName [256],
                JpiNodeName [32],
                JpiPrcNam [16],
                JpiUserName [13];

   static struct
   {
      ushort  buf_len;
      ushort  item;
      uchar   *buf_addr;
      ushort  *short_ret_len;
   }
      JpiItems [] =
   {
      { sizeof(GetJpiControlFlags), JPI$_GETJPI_CONTROL_FLAGS,
        &GetJpiControlFlags, 0 },
      { sizeof(JpiPid), JPI$_PID, &JpiPid, 0 },
      { sizeof(JpiPri), JPI$_PRI, &JpiPri, 0 },
      { sizeof(JpiPrib), JPI$_PRIB, &JpiPrib, 0 },
      { sizeof(JpiMode), JPI$_MODE, &JpiMode, 0 },
      { sizeof(JpiImagName), JPI$_IMAGNAME, &JpiImagName, 0 },
      { sizeof(JpiPrcNam), JPI$_PRCNAM, &JpiPrcNam, 0 },
      { sizeof(JpiUserName), JPI$_USERNAME, &JpiUserName, 0 },
      { sizeof(JpiState), JPI$_STATE, &JpiState, 0 },
      { sizeof(JpiRightsSize), JPI$_RIGHTS_SIZE, &JpiRightsSize, 0 },
#define JPI_PROCESS_RIGHTS_ITEM 10
      { 0, JPI$_PROCESS_RIGHTS, 0, 0 },
      { 0,0,0,0 }
   },
      ScanItems [] =
   {
      { 0, PSCAN$_GETJPI_BUFFER_SIZE, 2048, 0},
      { 0,0,0,0 }
   };

   int  idx, status,
        IdentCount,
        ProcessCount,
        SetPrvStatus;
   ulong  *vecptr;
   ulong  ProcessContext;
   ulong  FaoVector [32];
   char  *cptr, *sptr;
   ulong  JpiProcessRights [JPI_PROCESS_RIGHTS_MAX*2];
   IO_SB  IOsb;

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

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

   JpiItems[JPI_PROCESS_RIGHTS_ITEM].buf_len = sizeof(JpiProcessRights);
   JpiItems[JPI_PROCESS_RIGHTS_ITEM].buf_addr = &JpiProcessRights;

   ProcessContext = 0;
   status = sys$process_scan (&ProcessContext, &ScanItems);
   if (VMSnok (status))
   {
      rqptr->rqResponse.ErrorTextPtr = "sys$process_scan()";
      ErrorVmsStatus (rqptr, status, FI_LI);
      AdminEnd (rqptr);
      return;
   }

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

   /* detached scripts (possibly executing as a non-server username) */
   if (DclScriptDetachProcess)
      if (VMSnok (SetPrvStatus = sys$setprv (1, &MailboxMask, 0, 0)))
         ErrorExitVmsStatus (SetPrvStatus, "sys$setprv()", FI_LI);

   ProcessCount = 0;

   for (;;)
   {
      status = sys$getjpiw (EfnWait, &ProcessContext, 0,
                            &JpiItems, &IOsb, 0, 0);
      if (VMSok (status)) status = IOsb.Status;
      if (VMSnok (status)) break;

      JpiPrcNam[15] = '\0';
      for (cptr = JpiPrcNam; *cptr && *cptr != ' '; cptr++);
      *cptr = '\0';

      JpiUserName[12] = '\0';
      for (cptr = JpiUserName; *cptr && *cptr != ' '; cptr++);
      *cptr = '\0';

      if (WATCH_MODULE(WATCH_MOD__OTHER))
         WatchThis (WATCHALL, WATCH_MOD__OTHER, "!8XL !&Z !&Z !UL",
                    JpiPid, JpiUserName, JpiPrcNam, JpiRightsSize);

      if (DclScriptDetachProcess &&
          JpiPid != HttpdProcess.Pid)
      {
         if (JpiRightsSize > sizeof(JpiProcessRights))
         {
            char  Buffer [32];
            sprintf (Buffer, "sys$getjpiw() %08.08X", JpiPid);
            ErrorNoticed (rqptr, SS$_BUFFEROVF, Buffer, FI_LI);
         }

         /* look through each of the identifiers in the list */
         idx = 0;
         for (IdentCount = JpiRightsSize / 8;
              IdentCount && JpiProcessRights[idx] != ProcessRightsIdent[0];
              IdentCount--) idx += 2;

         /* if we didn't find the identifier then continue */
         if (!IdentCount) continue;
      }

      ProcessCount++;

      for (cptr = JpiImagName; *cptr && *cptr != ';'; cptr++);
      if (*cptr == ';') *cptr-- = '\0';
      while (cptr > JpiImagName && *cptr != ']') cptr--;
      if (*cptr == ']') cptr++;

      if (JpiState > 0 && JpiState <= 14)
         sptr = StateNameArray[JpiState];
      else
         sprintf (sptr = StateNameArray[0], "%04.04X", JpiState);

      vecptr = FaoVector;
      if (ProcessCount % 2)
         *vecptr++ = "";
      else
         *vecptr++ = " class=\"hlght\"";
      *vecptr++ = ADMIN_REPORT_SHOW_PROCESS;
      *vecptr++ = JpiPid;
      *vecptr++ = JpiUserName;
      *vecptr++ = ProcessCount;
      *vecptr++ = JpiPid;
      *vecptr++ = JpiUserName;
      *vecptr++ = JpiPrcNam;
      if (*cptr)
         *vecptr++ = cptr;
      else
         *vecptr++ = "[<font size=\"-1\">DCL</font>]";
      switch (JpiMode)
      {
         case JPI$K_BATCH : *vecptr++ = "BAT"; break;
         case JPI$K_INTERACTIVE : *vecptr++ = "INT"; break;
         case JPI$K_NETWORK : *vecptr++ = "NET"; break;
         case JPI$K_OTHER : *vecptr++ = "OTH"; break;
         default : *vecptr++ = "?";
      }
      *vecptr++ = sptr;
      *vecptr++ = JpiPri;
      *vecptr++ = JpiPrib;

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

   if (DclScriptDetachProcess)
      if (VMSnok (SetPrvStatus = sys$setprv (0, &MailboxMask, 0, 0)))
         ErrorExitVmsStatus (SetPrvStatus, "sys$setprv()", FI_LI);

   if (status != SS$_NOMOREPROC)
   {
      rqptr->rqResponse.ErrorTextPtr = "sys$getjpiw()";
      ErrorVmsStatus (rqptr, status, FI_LI);
   }

   vecptr = FaoVector;
   *vecptr++ = AdminRefresh (rqptr);

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

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

   AdminEnd (rqptr);
}

/*****************************************************************************/
/*
Using a scripting script process do a SHOW PROCESS /ALL on the specified
process.  Used from the DclReport() but actually could be used on any process
the server has access to, including the server!
*/

void WatchShowProcess
(
REQUEST_STRUCT *rqptr,
char *ProcessIdString,
char *ProcessIdUserName
)
{
   static char  DclCommand [512];
   static ulong  JpiServerPid;
   static $DESCRIPTOR (DclCommandDsc, DclCommand);
   static $DESCRIPTOR (DclCommandFaoDsc,
"SHOW PROCESS /ALL /IDENT=!AZ\n\
SV=$SEVERITY\n\
IF SV THEN MO=F$GETJPI(\"!AZ\",\"MODE\")\n\
JT=\"\"\n\
IF SV THEN IF F$GETJPI(\"!AZ\",\"PID\").NES.F$GETJPI(\"!AZ\",\"MASTER_PID\") \
THEN JT=\" (subprocess)\"\n\
IF SV THEN IF JT.EQS.\"\".AND.F$GETJPI(\"!AZ\",\"JOBTYPE\").EQ.0 \
THEN JT=\" (detached)\"\n\
IF SV THEN IM=F$GETJPI(\"!AZ\",\"IMAGNAME\")\n\
IF SV THEN IF IM.EQS.\"\" THEN IM=\"[DCL]\"\n\
LF[0,8]=10\n\
IF SV THEN WRITE SYS$OUTPUT LF+\"Mode: \"+MO+JT+LF+LF+\"Image: \"+IM\n\0");

   static char  BeginPage [] =
"<p><table class=\"ctgry\">\n\
<tr><td>\n\
<table class=\"lftlft\">\n\
<tr><td><pre>";

   int  size, status;
   ulong  *vecptr;
   ulong  ProcessId;
   ulong  FaoVector [32];
   char  *cptr, *sptr;
   REQUEST_AST EndPageFunction;

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

   if (ProcessIdUserName[0])
   {
      if (strsame (ProcessIdUserName, HttpdScriptAsUserName, -1))
         rqptr->rqPathSet.ScriptAsPtr = HttpdScriptAsUserName;
      else
      if (strsame (ProcessIdUserName, HttpdProcess.UserName, -1))
         rqptr->rqPathSet.ScriptAsPtr = HttpdProcess.UserName;
      else
      {
         if (!DclPersonaServicesAvailable)
         {
            rqptr->rqResponse.HttpStatus = 403;
            ErrorGeneral (rqptr, ErrorWatchPersonaNeeded, FI_LI);
            AdminEnd (rqptr);
            return;
         }
         rqptr->rqPathSet.ScriptAsPtr = cptr =
            VmGetHeap (rqptr, size = strlen(ProcessIdUserName)+1);
         strzcpy (cptr, ProcessIdUserName, size);
      }
   }

   ProcessId = strtol (ProcessIdString, NULL, 16);

   /* suppress the [delete] button for the main server process!! */
   if (ProcessId == HttpdProcess.Pid)
      EndPageFunction = &WatchShowEnd;
   else
      EndPageFunction = &WatchShowProcessDeleteEnd;

   vecptr = FaoVector;
   *vecptr++ = ProcessIdString;
   *vecptr++ = ProcessIdString;
   *vecptr++ = ProcessIdString;
   *vecptr++ = ProcessIdString;
   *vecptr++ = ProcessIdString;
   *vecptr++ = ProcessIdString;

   status = sys$faol (&DclCommandFaoDsc, 0, &DclCommandDsc, &FaoVector);
   if (VMSnok (status) || status == SS$_BUFFEROVF)
   {
      rqptr->rqResponse.ErrorTextPtr = "sys$faol()";
      ErrorVmsStatus (rqptr, status, FI_LI);
      AdminEnd (rqptr);
      return;
   }

   status = DclBegin (rqptr, EndPageFunction, DclCommand,
                      NULL, NULL, NULL, NULL, NULL);
   if (VMSok (status))
   {
      rqptr->rqResponse.PreExpired = PRE_EXPIRE_ADMIN;
      ResponseHeader200 (rqptr, "text/html", NULL);
      AdminPageTitle (rqptr, "Show Process", BeginPage);
      rqptr->rqCgi.BufferRecords = rqptr->NetWriteEscapeHtml = true;
   }
}

/*****************************************************************************/
/*
Called when the scripting script process is complete.  Output the last portion
of the report page and AST to wherever was the buffered end-of-report function.
*/

void WatchShowProcessDeleteEnd (REQUEST_STRUCT *rqptr)

{
   static char  EndPageFao [] =
"</pre></td></tr>\n\
</table>\n\
</td></tr>\n\
</table>\n\
<p><form method=\"GET\" action=\"!AZ\">\n\
<input type=\"hidden\" name=\"pid\" value=\"!AZ\">\n\
<input type=\"submit\" value=\" Force Delete \">\n\
</form>\n\
</body>\n\
</html>\n";

   int  status;
   ulong  *vecptr;
   ulong  FaoVector [8];
   char  ProcessIdString [32];

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

   rqptr->NetWriteEscapeHtml = false;

   if (rqptr->rqResponse.HttpStatus == 200)
   {
      if (!rqptr->rqHeader.QueryStringLength ||
          !strsame (rqptr->rqHeader.QueryStringPtr, "pid=", 4))
      {
         WatchShowEnd (rqptr);
         return;
      }
      strzcpy (ProcessIdString,
               rqptr->rqHeader.QueryStringPtr+4,
               sizeof(ProcessIdString));

      vecptr = FaoVector;
      *vecptr++ = ADMIN_CONTROL_DELETE_PROCESS;
      *vecptr++ = ProcessIdString;

      FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI);
      status = FaolToNet (rqptr, EndPageFao, &FaoVector);
      if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
   }

   AdminEnd (rqptr);
}

/*****************************************************************************/
/*
Using a scripting script process to display relevant cluster details.
*/

void WatchShowCluster (REQUEST_STRUCT *rqptr)

{
   static $DESCRIPTOR (DclFaoDsc,
"HAR=F$SEARCH(\"CGI-BIN:[000000]HTTPD_ADMIN_REPORT_CLUSTER.COM\")\n\
IF HAR.NES.\"\" THEN @\'HAR\'\n\
SAY=\"WRITE SYS$OUTPUT\"\n\
NL=\"\'\'SAY\' \"\"\"\"\"\n\
H80=\"\'\'SAY\' F$FAO(\"\"!!/!80*-!!/\"\")\"\n\
SYS=\"SHOW SYSTEM/FULL/CLUSTER\"\n\
SYS\n\
H80\n\
SAY F$FAO(\"!!/Server Process(es):!!/!!/\")\n\
SYS/OWNER=!AZ\n\
SAY F$FAO(\"!!/(Default) Scripting Process(es):!!/!!/\")\n\
SYS/OWNER=!AZ\n\
H80\n\
NL\n\
SHOW USER/CLUSTER/INT/NET/BAT/SUB\n\
NL\n\
SHOW USER/CLUSTER/INT/NET/BAT/SUB/FULL/NOHEAD\n\
NL\n\0");

   static char  BeginPage [] =
"<p><table class=\"ctgry\">\n\
<tr><td>\n\
<table class=\"lftlft\">\n\
<tr><td><pre>";

   int  status;
   char  DclBuffer [1024];
   $DESCRIPTOR (DclBufferDsc, DclBuffer);

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

   sys$fao (&DclFaoDsc, 0, &DclBufferDsc,
            HttpdProcess.UserName,
            HttpdScriptAsUserName);

   status = DclBegin (rqptr, &WatchShowEnd, DclBuffer,
                      NULL, NULL, NULL, NULL, NULL);
   if (VMSok (status))
   {
      rqptr->rqResponse.PreExpired = PRE_EXPIRE_ADMIN;
      ResponseHeader200 (rqptr, "text/html", NULL);
      AdminPageTitle (rqptr, "Cluster Report", BeginPage);

      /* filter-null required, V7.3 (at least) SHOW CPU/FULL contains them!! */
      rqptr->rqCgi.BufferRecords = 
         rqptr->rqCgi.FilterStream =
         rqptr->NetWriteEscapeHtml = true;
   }
}

/*****************************************************************************/
/*
Using a scripting script process to display relevant system details.
*/

void WatchShowSystem (REQUEST_STRUCT *rqptr)

{
   static $DESCRIPTOR (DclFaoDsc,
"HAR=F$SEARCH(\"CGI-BIN:[000000]HTTPD_ADMIN_REPORT_SYSTEM.COM\")\n\
IF HAR.NES.\"\" THEN @\'HAR\'\n\
SAY=\"WRITE SYS$OUTPUT\"\n\
NL=\"\'\'SAY\' \"\"\"\"\"\n\
H80=\"\'\'SAY\' F$FAO(\"\"!!/!!80*-!!/\"\")\"\n\
SYI=F$FAO(\"!!AS, a !!AS with !!UL CPU and !!ULMB running VMS !!AS\",\
F$GETSYI(\"NODENAME\"),F$EDIT(F$GETSYI(\"HW_NAME\"),\"COMPRESS,TRIM\"),\
F$GETSYI(\"AVAILCPU_CNT\"),\
(F$GETSYI(\"MEMSIZE\")*(F$GETSYI(\"PAGE_SIZE\")/512)/2048),\
F$EDIT(F$GETSYI(\"VERSION\"),\"COLLAPSE\"))\n\
HDR=F$FAO(\"  Pid    Process Name    State  Pri      I/O\
       CPU       Page flts  Pages!!/\")\n\
SAY SYI\n\
SAY F$FAO(\"!!#*-!!/!!/!!AS\",F$LENGTH(SYI),HDR)\n\
SYS=\"SHOW SYSTEM/FULL/NOHEAD\"\n\
SYS\n\
H80\n\
SAY F$FAO(\"!!/Server Process(es):!!/!!/\")\n\
SYS/OWNER=!AZ\n\
SAY F$FAO(\"!!/(Default) Scripting Process(es):!!/!!/\")\n\
SYS/OWNER=!AZ\n\
H80\n\
NL\n\
SHOW USER/NODE/INT/NET/BAT/SUB\n\
NL\n\
SHOW USER/NODE/INT/NET/BAT/SUB/FULL/NOHEAD\n\
H80\n\
NL\n\
SHOW MEMORY/FULL\n\
H80\n\
SHOW CPU/FULL\n\
H80\n\
SHOW DEVICE/BRIEF\n\
DEFINE/USER SYS$ERROR NL:\n\
DEFINE/USER SYS$OUTPUT NL:\n\
SHOW ERROR\n\
OK=F$INTEGER(F$EXTRACT(3,7,$STATUS)).EQ.1\n\
IF OK THEN NL\n\
IF OK THEN SHOW ERROR\n\
H80\n\
SHOW NET\n\
SHOW NET/FULL\n\0");

   static char  BeginPage [] =
"<p><table class=\"ctgry\">\n\
<tr><td>\n\
<table class=\"lftlft\">\n\
<tr><td><pre>";

   int  status;
   char  DclBuffer [2048],
         SystemBuffer [256];
   $DESCRIPTOR (DclBufferDsc, DclBuffer);
   $DESCRIPTOR (SystemBufferDsc, SystemBuffer);

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

   status = sys$fao (&DclFaoDsc, 0, &DclBufferDsc,
                     HttpdProcess.UserName,
                     HttpdScriptAsUserName);
   if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);

   status = DclBegin (rqptr, &WatchShowEnd, DclBuffer,
                      NULL, NULL, NULL, NULL, NULL);
   if (VMSok (status))
   {
      rqptr->rqResponse.PreExpired = PRE_EXPIRE_ADMIN;
      ResponseHeader200 (rqptr, "text/html", NULL);
      AdminPageTitle (rqptr, "System Report", BeginPage);

      /* filter-null required, V7.3 (at least) SHOW CPU/FULL contains them!! */
      rqptr->rqCgi.BufferRecords = 
         rqptr->rqCgi.FilterStream =
         rqptr->NetWriteEscapeHtml = true;
   }
}

/*****************************************************************************/
/*
Called when the scripting script process is complete.  Output the last portion
of the report page and AST to wherever was the buffered end-of-report function.
*/

void WatchShowEnd (REQUEST_STRUCT *rqptr)

{
   static char  EndPageFao [] =
"</pre></td></tr>\n\
</table>\n\
</td></tr>\n\
</table>\n\
</div>\n\
</body>\n\
</html>\n";

   int  status;

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

   rqptr->NetWriteEscapeHtml = false;
   if (rqptr->rqResponse.HttpStatus == 200)
   {
      status = FaolToNet (rqptr, EndPageFao, NULL);
      if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
   }
   AdminEnd (rqptr);
}

/*****************************************************************************/
/*
Just delete the process specified by 'ProcessIdString'.
*/

void WatchDeleteProcess (REQUEST_STRUCT *rqptr)

{
   int  status,
        SetPrvStatus;
   ulong  ProcessId;

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

   if (rqptr->rqHeader.QueryStringLength &&
       strsame (rqptr->rqHeader.QueryStringPtr, "pid=", 4))
      ProcessId = strtol (rqptr->rqHeader.QueryStringPtr+4, NULL, 16);
   else
      ProcessId  = 0;

   if (ProcessId)
   {
      if (DclScriptDetachProcess)
      {
         /* detached scripts, possibly executing as a non-server username */
         if (VMSnok (SetPrvStatus = sys$setprv (1, &WorldMask, 0, 0)))
            ErrorExitVmsStatus (SetPrvStatus, "sys$setprv()", FI_LI);
         status = sys$delprc (&ProcessId, 0);
         if (VMSnok (SetPrvStatus = sys$setprv (0, &WorldMask, 0, 0)))
            ErrorExitVmsStatus (SetPrvStatus, "sys$setprv()", FI_LI);
      }
      else
         status = sys$delprc (&ProcessId, 0);
   }
   else
      status = SS$_BUGCHECK;

   if (VMSnok (status))
   {
      rqptr->rqResponse.HttpStatus = 409;
      rqptr->rqResponse.ErrorTextPtr = "when deleting";
      ErrorVmsStatus (rqptr, status, FI_LI);
      AdminEnd (rqptr);
      return;
   }

   rqptr->rqResponse.PreExpired = PRE_EXPIRE_ADMIN;
   ReportSuccess (rqptr, "Server !AZ deleted process !8XL.",
                  ServerHostPort, ProcessId);

   AdminEnd (rqptr);
}

/*****************************************************************************/
/*
Called from WatchBegin().  Provide a plain-text dump displaying some of the
essential fields from various data structures in an executing request. 
Intended as a diagnosis and development tool.  If 'rqptr' is NULL then the
information is written to SYS$OUTPUT (and in this case leaks a little memory).
*/

void WatchPeek
(
REQUEST_STRUCT *rqptr,
REQUEST_STRUCT *rqeptr
)
{
#define WATCH_NULL_STRING "(null)"
#define WATCH_NULL(string) (!string ? WATCH_NULL_STRING : string)

   static char  VmZoneFao [] =
"|\n\
!33<HttpdTickSecond!> !UL\n\
!33<ConnectNumber!> !UL (!UL)\n\
!33<VmHeapZoneId!> !8XL\n\
\n";

   static char  ServiceFao [] =
"|\n\
!33<ServicePtr!> !&X\n\
!33<->ServerChannel!> !UL (!AZ)\n\
!33<->ServerHostPort!> !&Z\n\
!33<->ServerIpAddressString!> !&Z\n\
!33<->RequestSchemeNamePtr!> !&Z\n\
!33<->ProxyTunnel!> !UL\n\
!33<->RawSocket!> !UL\n\
!33<->SSLserverPtr!> !&X\n\
!33<->SSLclientPtr!> !&X\n\
|\n\
!33<WatchItem!> !8XL\n\
!33<RequestState!> !UL !AZ\n\
!33<NotePadPtr!> !&Z\n\
!33<ProxyReverseLocationPtr!> !&Z\n\
|\n\
!33<Http2Ptr!> !&X\n\
!33<Stream2Ptr!> !&X\n\
|\n";

   static char  Http2Fao [] =
"!33<Stream2Ptr->Depend!> !UL\n\
!33<Stream2Ptr->RequestPtr!> !&X\n\
!33<Stream2Ptr->ContinPtr!> !&X\n\
!33<Stream2Ptr->ContinSize!> !UL\n\
!33<Stream2Ptr->DropTickSecond!> !UL\n\
!33<Stream2Ptr->FlowStallTickSecond!> !UL\n\
!33<Stream2Ptr->Ident!> !UL\n\
!33<Stream2Ptr->Priority!> !UL\n\
!33<Stream2Ptr->DataReadPtr!> !&X\n\
!33<Stream2Ptr->DataReadSize!> !UL\n\
!33<Stream2Ptr->DataReadLength!> !UL\n\
!33<Stream2Ptr->ReadWindowSize!> !UL\n\
!33<Stream2Ptr->RequestEnd!> !&B\n\
!33<Stream2Ptr->StreamCancel!> !&B\n\
!33<Stream2Ptr->StreamEnd!> !&B\n\
!33<Stream2Ptr->StreamError!> !&B\n\
!33<Stream2Ptr->StreamOpen!> !&B\n\
!33<Stream2Ptr->Weight!> !UL\n\
!33<Stream2Ptr->WriteWindowSize!> !UL\n\
!33<Stream2Ptr->QueuedWriteCount!> !UL\n\
|\n";

   static char  NetFao [] =
"!33<rqNet.ReadErrorCount!> !UL\n\
!33<rqNet.ReadErrorStatus!> !&S\n\
!33<rqNet.WriteErrorCount!> !UL\n\
!33<rqNet.WriteErrorStatus!> !&S\n\
!33<rqNet.GzipDataLength!> !UL\n\
!33<rqNet.GzipAstFunction!> !&A\n\
!33<rqNet.PipelineBufferPtr!> !&X\n\
!33<rqNet.PipelineBufferCount!> !UL\n\
!33<rqNet.PipelineBufferSize!> !UL\n\
!33<rqNet.PipelineRequestCount!> !UL\n\
!33<rqNet.PersistentCount!> !UL\n";

   static char  TimerFao [] =
"|\n\
!33<BytesRx64!> !@UQ\n\
!33<BytesTx64!> !@UQ\n\
!33<BytesRawRx64!> !@UQ\n\
!33<BytesRawTx64!> !@UQ\n\
!33<PersistentRequest!> !&B\n\
!33<PersistentResponse!> !&B\n\
!33<rqTmr.InputSecond!> !UL\n\
!33<rqTmr.PersistentSecond!> !UL\n\
!33<rqTmr.ListIndex!> !UL!&@\n\
!33<rqTmr.NoProgressBytesTx!> !UL\n\
!33<rqTmr.NoProgressSecond!> !UL\n\
!33<rqTmr.NoProgressPeriod!> !UL\n\
!33<rqTmr.OutputSecond!> !UL\n\
!33<rqTmr.TimeoutCount!> !UL !AZ\n\
!33<rqTmr.ThrottleSecond!> !UL\n\
!33<rqPathSet.ThrottleSet!> !&B\n\
!33<rqPathSet.ThrottleFrom!> !UL/!UL!AZ\n\
!33<rqPathSet.ThrottleTo!> !UL\n\
!33<rqPathSet.ThrottleResume!> !UL\n\
!33<rqPathSet.ThrottleBusy!> !UL\n\
!33<rqPathSet.ThrottleIndex!> !UL!&@\n\
!33<throttlePerUser!> !&B\n\
|\n\
!33<rqTime.BeginTime64!> !%D (!AZ ago)\n\
!33<rqTime.GmDateTime!> !&Z\n";

   static char  HeaderFao [] =
"!33<rqHeader.RequestHeaderPtr!> {!UL}|!-!#AZ|\n\
!33<rqHeader.RequesHeaderLength!> !UL\n\
!33<rqHeader.RequestBodyPtr!> {!UL}|!-!#AZ|\n\
!33<rqHeader.MethodName!> !&Z\n\
!33<rqHeader.RequestUriPtr!> {!UL}!&P\n\
!33<rqHeader.HttpVersion!> !UL.!UL\n\
!33<rqHeader.AcceptPtr!> !&Z\n\
!33<rqHeader.AcceptCharsetPtr!> !&Z\n\
!33<rqHeader.AcceptEncodingPtr!> !&Z\n\
!33<rqHeader.AcceptLangPtr!> !&Z\n\
!33<rqHeader.AuthorizationPtr!> !&Z\n\
!33<rqHeader.CacheControl!> no-cache:!&B no-store:!&B max-age=0:!&B\n\
!33<rqHeader.ContentLength64!> !@UQ\n\
!33<rqHeader.ContentTypePtr!> !&Z\n\
!33<rqHeader.CookiePtr!> !&Z\n\
!33<rqHeader.ForwardedPtr!> !&Z\n\
!33<rqHeader.HostPtr!> !&Z\n\
!33<rqHeader.IfMatchPtr!> !&Z\n\
!33<rqHeader.IfNoneMatchPtr!> !&Z\n\
!33<rqHeader.OriginPtr!> !&Z\n\
!33<rqHeader.ProxyAuthorizationPtr!> !&Z\n\
!33<rqHeader.RefererPtr!> !&Z\n\
!33<rqHeader.UserAgentPtr!> !&Z\n\
!33<rqHeader.XForwardedForPtr!> !&Z\n";

   static char  BodyFao [] =
"!33<rqBody.UnEncodeStream!> !&B\n\
!33<rqBody.ChunkState!> !UL\n\
!33<rqBody.ChunkCount!> !UL\n\
!33<rqBody.ChunkSize!> !UL\n\
!33<rqBody.ChunkSizeString!> !&Z\n\
!33<rqBody.ChunkedTrailerBufferCount!> !UL\n\
!33<rqBody.ChunkedTrailerBufferSize!> !UL\n\
!33<rqBody.ChunkedTrailerBufferPtr!> !&X\n\
!33<rqBody.ChunkedTrailerNewLineCount!> !UL\n\
!33<rqBody.ContentLength64!> !@UQ\n\
!33<rqBody.ContentCount64!> !@UQ\n\
!33<rqBody.DataVBN!> !UL\n\
!33<rqBody.DataPtr!> 0x!XL\n\
!33<rqBody.DataCount!> !UL\n\
!33<rqBody.DataSize!> !UL\n\
!33<rqBody.DataStatus!> !&S\n\
!33<rqBody.DiscardReadCount!> !UL\n\
!33<rqBody.AstFunction!> !&A\n\
!33<rqBody.ProcessFunction!> !&A\n\
!33<rqBody.ProcessPtr!> !&X\n\
|\n";

   static char  PathInfoFao [] =
"!33<rqHeader.PathInfoPtr!> !&Z\n\
!33<rqHeader.QueryStringPtr!> !&Z\n\
!33<Md5HashPath!> |!16&H|\n\
!33<MappedPathPtr!> !&Z\n\
!33<RequestMappedFile!> !&Z\n\
!33<ParseOds.ExpFileName!> !&Z\n\
!33<ScriptName!> !&Z\n\
!33<RequestMappedScript!> !&Z\n\
!33<RequestMappedRunTime!> !&Z\n\
!33<IsCgiPlusScript!> !&B\n\
!33<rqPathSet.PathOds!> !UL !AZ\n\
!33<PathOds!> !UL !AZ\n\
!33<PathOdsExtended!> !&B\n\
|\n\
!33<rqResponse.HttpStatus!> !UL\n\
!33<rqResponse.HeaderGenerated!> !&B\n\
!33<rqResponse.HeaderSent!> !&B\n\
!33<rqResponse.ErrorReportPtr!> !&Z\n\
!33<rqResponse.LocationPtr!> !&Z\n\
!33<rqResponse.ContentEncodeAsGzip!> !&B\n\
!33<rqResponse.ContentIsEncodedGzip!> !&B\n\
!33<rqResponse.ContentLength64!> !@SQ\n\
!33<rqResponse.ContentTypePtr!> !&Z\n\
!33<rqResponse.ChunkedBufferPtr!> !&X\n\
!33<rqResponse.ChunkedBufferSize!> !UL\n\
!33<rqResponse.TransferEncodingChunke!> !&B\n\
|\n\
!33<rqWebSocket.ScriptProcessPid!> !8XL\n\
!33<rqWebSocket.InputSize!> !UL\n\
!33<rqWebSocket.InputChannel!> !UL (!AZ)\n\
!33<rqWebSocket.InputIOsb!> !&S !UL\n\
!33<rqWebSocket.QueuedInput!> !UL\n\
!33<rqWebSocket.QueuedNetRead!> !UL\n\
!33<rqWebSocket.OutputSize!> !UL\n\
!33<rqWebSocket.OutputChannel!> !UL (!AZ)\n\
!33<rqWebSocket.OutputIOsb!> !&S !UL\n\
!33<rqWebSocket.QueuedOutput!> !UL\n\
!33<rqWebSocket.QueuedNetWrite!> !UL\n\
|\n";

   static char  AuthFao [] =
"!33<RemoteUser!> !&Z\n\
!33<RemoteUserPassword!> |!#**|\n\
!33<ResolvedRemoteUser!> !&B\n\
!33<rqAuth.FinalStatus!> !&S\n\
!33<rqAuth.RequestCan!> 0x!4XL (!AZ)\n\
!33<rqAuth.UserCan!> 0x!4XL (!AZ)\n\
!33<rqAuth.GroupCan!> 0x!4XL (!AZ)\n\
!33<rqAuth.WorldCan!> 0x!4XL (!AZ)\n\
!33<rqAuth.Type!> !&Z\n\
!33<rqAuth.UserDetailsPtr!> !&Z\n\
!33<rqAuth.HttpsOnly!> !&B\n\
!33<rqAuth.SkelKeyAuthenticated!> !&B\n\
!33<rqAuth.SysUafAuthenticated!> !&B\n\
!33<rqAuth.VmsIdentifiersCount!> !UL\n\
!33<rqAuth.VmsIdentifiersPtr!> 0x!XL\n\
!33<rqAuth.VmsUserProfile!> !&B\n\
!33<rqAuth.VmsUserProfileLength!> !UL\n\
!33<rqAuth.VmsUserProfilePtr!> 0x!XL\n\
!33<rqAuth.VmsUserScriptAs!> !&B\n\
!33<rqAuth.DirectoryPtr!> !&Z\n\
!33<rqAuth.RealmPtr!> !&Z (!AZ)\n\
!33<rqAuth.RealmDescrPtr!> !&Z\n\
!33<rqAuth.PathParameterPtr!> !&Z\n\
!33<rqAuth.GroupWritePtr!> !&Z (!AZ)\n\
!33<rqAuth.GroupReadPtr!> !&Z (!AZ)\n\
!33<rqAuth.GroupRestrictListPtr!> !&Z\n\
!33<rqAuth.WorldRestrictListPtr!> !&Z\n\
!33<rqAuth.ProxyStringPtr!> !&Z\n\
!33<rqAuth.RemoteUser!> !&Z\n\
|\n";

   static char  CgiFao [] =
"!33<rqCgi.BufferLength!> !UL\n\
!33<rqCgi.BufferRemaining!> !UL\n\
!33<rqCgi.BufferPtr!> 0x!XL\n\
!33<rqCgi.BufferCurrentPtr!> 0x!XL\n\
!33<rqCgi.CalloutInProgress!> !&B\n\
!33<rqCgi.CalloutOutputCount!> !UL\n\
!33<rqCgi.CalloutOutputPtr!> 0x!XL !&Z\n\
!33<rqCgi.ContentEncodingGzip!> !UL\n\
!33<rqCgi.ContentTypeText!> !&B\n\
!33<rqCgi.EofStr!> !&Z\n\
!33<rqCgi.EotStr!> !&Z\n\
!33<rqCgi.EscStr!> !&Z\n\
!33<rqCgi.Header100Continue!> !&B\n\
!33<rqCgi.Header100ContinueDone!> !&B\n\
!33<rqCgi.HeaderLineCount!> !UL\n\
!33<rqCgi.IsCliDcl!> !&B\n\
!33<rqCgi.OutputMode!> !UL (!AZ)\n\
!33<rqCgi.ProcessingBody!> !&B\n\
!33<rqCgi.RecordCount!> !UL\n\
!33<rqCgi.ScriptRetryCount!> !UL\n\
!33<rqCgi.TransferEncoding!> !&B\n\
!33<rqCgi.TransferEncodingChunked!> !UL\n\
!33<rqCgi.XVMSRecordMode!> !&B\n\
";

   static char  CacheFao [] =
"|\n\
!33<rqCache.EntryPtr!> 0x!XL\n\
|\n\
!33<AgentRequestPtr!> !&Z\n\
!33<AgentResponsePtr!> !&Z\n\
!33<AgentAstParam!> !&X\n\
|\n\
!33<DclTaskPtr!> 0x!XL\n";

   static char  DECnetFao [] = "!33<DECnetTaskPtr!> !&X\n";

   static char  DECnetTaskFao [] =
"!33<ConnectPtr!> !&X\n\
!33<DECnetChannel!> !UL\n\
!33<DECnetConnectIOsb!> !&S !UL\n\
!33<DECnetReadIOsb!> !&S !UL\n\
!33<DECnetWriteIOsb!> !&S !UL\n\
!33<DECnetAstFunction!> !&A\n\
!33<ScriptResponded!> !&B\n\
!33<BuildRecords!> !&B\n\
!33<BuildCount!> !UL\n\
!33<CgiDialogState!> !UL\n\
!33<OsuDialogState!> !UL\n\
!33<OsuDnetCgi!> !&B\n\
!33<QueuedDECnetIO!> !UL\n\
!33<QueuedBodyRead!> !UL\n\
!33<QueuedNetWrite!> !UL\n\
!33<NextTaskFunction!> !&A\n\
!33<RequestPtr!> !&X\n\
|\n";

   static char  DescrFao [] =
"!33<DescrTaskPtr!> !&X\n\
!33<DirTaskPtr!> !&X\n\
!33<FileTaskPtr!> !&X\n\
!33<HTAdminTaskPtr!> !&X\n\
|\n\
!33<ProxyTaskPtr!> !&X\n";

   static char  ProxyTaskFao [] =
"!33<NetIoPtr->Channel!> !UL (!AZ)\n\
!33<NetIoPtr->TcpMaxQio!> !UL\n\
!33<NetIoPtr->TcpMaxSeg!> !UL\n\
!33<NetIoPtr->TcpRcvBuf!> !UL\n\
!33<NetIoPtr->TcpSndBuf!> !UL\n\
!33<NetIoPtr->ReadIOsb!> !&S !UL\n\
!33<NetIoPtr->ReadStatus!> !&S\n\
!33<NetIoPtr->ReadCount!> !UL\n\
!33<NetIoPtr->ReadAstFunction!> !&A\n\
!33<NetIoPtr->ReadPtr!> !&X\n\
!33<NetIoPtr->ReadSize!> !UL\n\
!33<NetIoPtr->WriteIOsb!> !&S !UL\n\
!33<NetIoPtr->WriteStatus!> !&S\n\
!33<NetIoPtr->WriteCount!> !UL\n\
!33<NetIoPtr->WriteAstFunction!> !&A\n\
!33<NetIoPtr->WritePtr!> !&X\n\
!33<NetIoPtr->WriteLength!> !UL\n\
!33<NetIoPtr->BytesRawRx64!> !@UQ\n\
!33<NetIoPtr->BytesRawTx64!> !@UQ\n\
!33<ConnectIpAddress!> !&I\n\
!33<RequestHostIpAddress!> !&I\n\
!33<RequestHostPort!> !&Z\n\
!33<RequestSchemeName!> !&Z\n\
!33<ProxyTunnel!> !UL\n\
!33<ProxyLookupRetryCount!> !UL\n\
!33<ProxyConnectIOsb!> !&S !UL\n\
!33<VerifyRecordPtr!> !&Z\n\
!33<RebuiltRequestLength!> !UL\n\
!33<RebuiltRequestPtr!> !&Z\n\
!33<ResponseBodyLength!> !UL\n\
!33<ResponseBufferCount!> !UL\n\
!33<ResponseBufferPtr!> !&Z\n\
!33<ResponseBufferCurrentPtr!> !&X\n\
!33<ResponseBufferRemaining!> !UL\n\
!33<ResponseBufferNetCount!> !UL\n\
!33<ResponseBufferNetPtr!> !&X\n\
!33<ResponseCacheControlMaxAge!> !UL\n\
!33<ResponseCacheControlMaxAgeZero!> !&B\n\
!33<ResponseCacheControlMustReval!> !&B\n\
!33<ResponseCacheControlNoCache!> !&B\n\
!33<ResponseCacheControlNoStore!> !&B\n\
!33<ResponseCacheControlNoTransform!> !&B\n\
!33<ResponseCacheControlPrivate!> !&B\n\
!33<ResponseCacheControlProxyReval!> !&B\n\
!33<ResponseCacheControlPublic!> !&B\n\
!33<ResponseCacheControlSMaxAge!> !UL\n\
!33<ResponseChunkedCount!> !UL\n\
!33<ResponseChunkedEnd!> !&B\n\
!33<ResponseChunkedEol!> !&B\n\
!33<ResponseChunkedEot!> !&B\n\
!33<ResponseChunkedInit!> !&B\n\
!33<ResponseChunkedSize!> !UL\n\
!33<ResponseChunkedString!> !&Z\n\
!33<ResponseChunkedNewlineCount!> !UL\n\
!33<ResponseConsecutiveNewLineCount!> !UL\n\
!33<ResponseContentEncodingGzip!> !&B\n\
!33<ResponseContentEncodingUnknown!> !&B\n\
!33<ResponseContentLength!> !SL\n\
!33<ResponseExpires!> !&Z (!%D)\n\
!33<ResponseHeaderLength!> !UL\n\
!33<ResponseHeaderPtr!> |!#AZ|\n\
!33<ResponseLastModified!> !&Z (!%D)\n\
!33<ResponseTransferEncodingChunked!> !&B\n\
!33<ResponseUpgradeHttp2onTLS!> !&B\n\
!33<ResponseUpgradeHttp2toTLS!> !&B\n\
!33<ResponseUpgradeWebSocket!> !&B\n";

   static char  PutFao [] =
"!33<PutTaskPtr!> !&X\n\
!33<SsiTaskPtr!> !&X\n\
!33<UpdTaskPtr!> !&X\n\
|\n";

   int  status;
   int64  Time64,
          Duration64,
          ResultTime64;
   ushort  Length;
   ulong  Remainder,
          Seconds,
          SubSeconds;
   ulong  FaoVector [128];
   ulong  *vecptr;
   char  *cptr;
   char  Buffer [8192],
         ProxyDevName [64],
         ServerDevName [64];
   MAP_RULE_META  *mrptr;

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

   WatchNone (true);

   NetGetBgDevice (rqeptr->ServicePtr->ServerChannel,
                   ServerDevName, sizeof(ServerDevName));

   sys$gettim (&Time64);
   Duration64 = rqeptr->rqTime.BeginTime64 - Time64;

   vecptr = FaoVector;

   *vecptr++ = HttpdTickSecond;
   *vecptr++ = rqeptr->ConnectNumber;
   if (rqeptr->Http2Ptr)
      *vecptr++ = rqeptr->Http2Ptr->ConnectNumber;
   else
      *vecptr++ = 0;
   *vecptr++ = rqeptr->VmHeapZoneId;

   status = FaolToBuffer (Buffer, sizeof(Buffer), &Length,
                          VmZoneFao, &FaoVector);
   if (VMSnok (status) || status == SS$_BUFFEROVF)
      ErrorNoticed (rqptr, status, NULL, FI_LI);

   WatchWrite (Buffer, Length);

   WatchVmZone (rqptr, rqeptr->VmHeapZoneId);

   vecptr = FaoVector;

   *vecptr++ = rqeptr->ServicePtr;
   *vecptr++ = rqeptr->ServicePtr->ServerChannel;
   *vecptr++ = ServerDevName+1;
   *vecptr++ = rqeptr->ServicePtr->ServerHostPort;
   *vecptr++ = rqeptr->ServicePtr->ServerIpAddressString;
   *vecptr++ = rqeptr->ServicePtr->RequestSchemeNamePtr;
   *vecptr++ = rqeptr->ServicePtr->ProxyTunnel;
   *vecptr++ = rqeptr->ServicePtr->RawSocket;
   *vecptr++ = rqeptr->ServicePtr->SSLserverPtr;
   *vecptr++ = rqeptr->ServicePtr->SSLclientPtr;

   *vecptr++ = rqeptr->WatchItem;
   *vecptr++ = rqeptr->RequestState;
   *vecptr++ = RequestState (rqeptr->RequestState);
   *vecptr++ = rqeptr->NotePadPtr;
   *vecptr++ = rqeptr->ProxyReverseLocationPtr;
   *vecptr++ = rqeptr->Http2Ptr;
   *vecptr++ = rqeptr->Stream2Ptr;

   FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI);
   status = FaolToBuffer (Buffer, sizeof(Buffer), &Length,
                          ServiceFao, &FaoVector);
   if (VMSnok (status) || status == SS$_BUFFEROVF)
      ErrorNoticed (rqptr, status, NULL, FI_LI);

   WatchWrite (Buffer, Length);

   if (rqeptr->Stream2Ptr)
   {
      vecptr = FaoVector;

      *vecptr++ = rqeptr->Stream2Ptr->Depend;
      *vecptr++ = rqeptr->Stream2Ptr->RequestPtr;
      *vecptr++ = rqeptr->Stream2Ptr->ContinPtr;
      *vecptr++ = rqeptr->Stream2Ptr->ContinSize;
      *vecptr++ = rqeptr->Stream2Ptr->FlowStallTickSecond;
      *vecptr++ = rqeptr->Stream2Ptr->DropTickSecond;
      *vecptr++ = rqeptr->Stream2Ptr->Ident;
      *vecptr++ = rqeptr->Stream2Ptr->Priority;
      *vecptr++ = rqeptr->Stream2Ptr->DataReadPtr;
      *vecptr++ = rqeptr->Stream2Ptr->DataReadSize;
      *vecptr++ = rqeptr->Stream2Ptr->DataReadLength;
      *vecptr++ = rqeptr->Stream2Ptr->ReadWindowSize;
      *vecptr++ = rqeptr->Stream2Ptr->RequestEnd;
      *vecptr++ = rqeptr->Stream2Ptr->StreamCancel;
      *vecptr++ = rqeptr->Stream2Ptr->StreamEnd;
      *vecptr++ = rqeptr->Stream2Ptr->StreamError;
      *vecptr++ = rqeptr->Stream2Ptr->StreamOpen;
      *vecptr++ = rqeptr->Stream2Ptr->Weight;
      *vecptr++ = rqeptr->Stream2Ptr->WriteWindowSize;
      *vecptr++ = rqeptr->Stream2Ptr->QueuedWriteCount;

      FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI);
      status = FaolToBuffer (Buffer, sizeof(Buffer), &Length,
                             Http2Fao, &FaoVector);
      if (VMSnok (status) || status == SS$_BUFFEROVF)
         ErrorNoticed (rqptr, status, NULL, FI_LI);

      WatchWrite (Buffer, Length);
   }

   vecptr = FaoVector;

   *vecptr++ = rqeptr->rqNet.ReadErrorCount;
   *vecptr++ = rqeptr->rqNet.ReadErrorStatus;
   *vecptr++ = rqeptr->rqNet.WriteErrorCount;
   *vecptr++ = rqeptr->rqNet.WriteErrorStatus;
   *vecptr++ = rqeptr->rqNet.GzipDataLength;
   *vecptr++ = rqeptr->rqNet.GzipAstFunction;
   *vecptr++ = rqeptr->rqNet.PipelineBufferPtr;
   *vecptr++ = rqeptr->rqNet.PipelineBufferCount;
   *vecptr++ = rqeptr->rqNet.PipelineBufferSize;
   *vecptr++ = rqeptr->rqNet.PipelineRequestCount;
   *vecptr++ = rqeptr->rqNet.PersistentCount;

   FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI);
   status = FaolToBuffer (Buffer, sizeof(Buffer), &Length,
                          NetFao, &FaoVector);
   if (VMSnok (status) || status == SS$_BUFFEROVF)
      ErrorNoticed (rqptr, status, NULL, FI_LI);

   WatchWrite (Buffer, Length);

   WatchPeekNetIo (rqptr, rqeptr->NetIoPtr,  "rqNet.NetIoPtr->");

   if (rqeptr->Http2Ptr)
      WatchPeekHttp2 (rqptr, rqeptr->Http2Ptr,
                      "|\nHttp2Ptr->"); 

   vecptr = FaoVector;

   *vecptr++ = &rqeptr->BytesRx64;
   *vecptr++ = &rqeptr->BytesTx64;
   *vecptr++ = &rqeptr->NetIoPtr->BytesRawRx64;
   *vecptr++ = &rqeptr->NetIoPtr->BytesRawTx64;
   *vecptr++ = rqeptr->PersistentRequest;
   *vecptr++ = rqeptr->PersistentResponse;
   *vecptr++ = rqeptr->rqTmr.InputSecond;
   *vecptr++ = rqeptr->rqTmr.PersistentSecond;
   *vecptr++ = rqeptr->rqTmr.ListIndex;
   if (rqeptr->rqTmr.ListIndex)
   {
      *vecptr++ = " (!UL seconds)";
      *vecptr++ = SupervisorListArray[rqeptr->rqTmr.ListIndex].ChunkSeconds;
   }
   else
      *vecptr++ = "";
   *vecptr++ = rqeptr->rqTmr.NoProgressBytesTx;
   *vecptr++ = rqeptr->rqTmr.NoProgressSecond;
   *vecptr++ = rqeptr->rqTmr.NoProgressPeriod;
   *vecptr++ = rqeptr->rqTmr.OutputSecond;
   *vecptr++ = rqeptr->rqTmr.TimeoutCount;
   *vecptr++ = HttpdTimeoutType(rqeptr->rqTmr.TimeoutType);
   *vecptr++ = rqeptr->rqTmr.ThrottleSecond;

   *vecptr++ = rqeptr->rqPathSet.ThrottleSet;
   *vecptr++ = rqeptr->rqPathSet.ThrottleFrom;
   *vecptr++ = rqeptr->rqPathSet.ThrottlePerUser;
   if (rqeptr->rqPathSet.ThrottleFrom)
   {
      if (!rqeptr->ThrottleListEntry.DataPtr)
         *vecptr++ = " (QUEUED)";
      else
         *vecptr++ = " (PROCESSING)";
   }
   else
      *vecptr++ = "";
   *vecptr++ = rqeptr->rqPathSet.ThrottleTo;
   *vecptr++ = rqeptr->rqPathSet.ThrottleResume;
   *vecptr++ = rqeptr->rqPathSet.ThrottleBusy;
   *vecptr++ = rqeptr->rqPathSet.ThrottleIndex;
   /* if throttled get the path rule using the index number */
   if (rqeptr->rqPathSet.ThrottleIndex)
      mrptr = MapUrl_ThrottleRule (rqeptr->rqPathSet.ThrottleIndex);
   else
      mrptr = NULL;
   if (mrptr)
   {
      *vecptr++ = " (!AZ throttle=!UL,!UL,!UL,!UL,!AZ,!AZ)";
      *vecptr++ = mrptr->TemplatePtr;
      *vecptr++ = mrptr->mpPathSet.ThrottleFrom;
      *vecptr++ = mrptr->mpPathSet.ThrottleTo;
      *vecptr++ = mrptr->mpPathSet.ThrottleResume;
      *vecptr++ = mrptr->mpPathSet.ThrottleBusy;
      *vecptr++ = MetaConShowSeconds (rqptr, mrptr->mpPathSet.ThrottleTimeoutQueue);
      *vecptr++ = MetaConShowSeconds (rqptr, mrptr->mpPathSet.ThrottleTimeoutBusy);
   }
   else
      *vecptr++ = "";
   *vecptr++ = rqeptr->ThrottlePerUser;

   *vecptr++ = &rqeptr->rqTime.BeginTime64;
   *vecptr++ = DurationString (rqptr, &Duration64);
   *vecptr++ = rqeptr->rqTime.GmDateTime;

   FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI);
   status = FaolToBuffer (Buffer, sizeof(Buffer), &Length,
                          TimerFao, &FaoVector);
   if (VMSnok (status) || status == SS$_BUFFEROVF)
      ErrorNoticed (rqptr, status, NULL, FI_LI);

   WatchWrite (Buffer, Length);

   vecptr = FaoVector;

   if (rqeptr->rqHeader.RequestHeaderPtrInvalid)
   {
      *vecptr++ = sizeof("(invalid)")-1;
      *vecptr++ = "(invalid)";
   }
   else
   {
      *vecptr++ = rqeptr->rqHeader.RequestHeaderLength;
      *vecptr++ = WATCH_NULL(rqeptr->rqHeader.RequestHeaderPtr);
   }
   *vecptr++ = rqeptr->rqHeader.RequestHeaderLength;
   *vecptr++ = rqeptr->rqHeader.RequestBodyCount;
   *vecptr++ = WATCH_NULL(rqeptr->rqHeader.RequestBodyPtr);
   *vecptr++ = rqeptr->rqHeader.MethodName;
   if (rqeptr->rqHeader.RequestUriPtr)
      *vecptr++ = strlen(rqeptr->rqHeader.RequestUriPtr);
   else
      *vecptr++ = 0;
   *vecptr++ = rqeptr->rqHeader.RequestUriPtr;
   *vecptr++ = rqeptr->rqHeader.HttpVersion / 10;
   *vecptr++ = rqeptr->rqHeader.HttpVersion % 10;
   *vecptr++ = rqeptr->rqHeader.AcceptPtr;
   *vecptr++ = rqeptr->rqHeader.AcceptCharsetPtr;
   *vecptr++ = rqeptr->rqHeader.AcceptEncodingPtr;
   *vecptr++ = rqeptr->rqHeader.AcceptLangPtr;
   *vecptr++ = rqeptr->rqHeader.AuthorizationPtr;
   *vecptr++ = rqeptr->rqHeader.CacheControlNoCache;
   *vecptr++ = rqeptr->rqHeader.CacheControlNoStore;
   *vecptr++ = rqeptr->rqHeader.CacheControlMaxAgeZero;
   *vecptr++ = &rqeptr->rqHeader.ContentLength64;
   *vecptr++ = rqeptr->rqHeader.ContentTypePtr;
   *vecptr++ = rqeptr->rqHeader.CookiePtr;
   *vecptr++ = rqeptr->rqHeader.ForwardedPtr;
   *vecptr++ = rqeptr->rqHeader.HostPtr;
   *vecptr++ = rqeptr->rqHeader.IfMatchPtr;
   *vecptr++ = rqeptr->rqHeader.IfNoneMatchPtr;
   *vecptr++ = rqeptr->rqHeader.OriginPtr;
   *vecptr++ = rqeptr->rqHeader.ProxyAuthorizationPtr;
   *vecptr++ = rqeptr->rqHeader.RefererPtr;
   *vecptr++ = rqeptr->rqHeader.UserAgentPtr;
   *vecptr++ = rqeptr->rqHeader.XForwardedForPtr;

   FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI);
   status = FaolToBuffer (Buffer, sizeof(Buffer), &Length,
                          HeaderFao, &FaoVector);
   if (VMSnok (status) || status == SS$_BUFFEROVF)
      ErrorNoticed (rqptr, status, NULL, FI_LI);

   WatchWrite (Buffer, Length);

   vecptr = FaoVector;

   *vecptr++ = rqeptr->rqBody.UnEncodeStream;
   *vecptr++ = rqeptr->rqBody.ChunkState;
   *vecptr++ = rqeptr->rqBody.ChunkCount;
   *vecptr++ = rqeptr->rqBody.ChunkSize;
   *vecptr++ = rqeptr->rqBody.ChunkSizeString;
   *vecptr++ = rqeptr->rqBody.ChunkedTrailerBufferCount;
   *vecptr++ = rqeptr->rqBody.ChunkedTrailerBufferSize;
   *vecptr++ = rqeptr->rqBody.ChunkedTrailerBufferPtr;
   *vecptr++ = rqeptr->rqBody.ChunkedTrailerNewLineCount;
   *vecptr++ = &rqeptr->rqBody.ContentLength64;
   *vecptr++ = &rqeptr->rqBody.ContentCount64;
   *vecptr++ = rqeptr->rqBody.DataVBN;
   *vecptr++ = rqeptr->rqBody.DataPtr;
   *vecptr++ = rqeptr->rqBody.DataCount;
   *vecptr++ = rqeptr->rqBody.DataSize;
   *vecptr++ = rqeptr->rqBody.DataStatus;
   *vecptr++ = rqeptr->rqBody.DiscardReadCount;
   *vecptr++ = rqeptr->rqBody.AstFunction;
   *vecptr++ = rqeptr->rqBody.ProcessFunction;
   *vecptr++ = rqeptr->rqBody.ProcessPtr;

   FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI);
   status = FaolToBuffer (Buffer, sizeof(Buffer), &Length,
                          BodyFao, &FaoVector);
   if (VMSnok (status) || status == SS$_BUFFEROVF)
      ErrorNoticed (rqptr, status, NULL, FI_LI);

   WatchWrite (Buffer, Length);

   vecptr = FaoVector;

   *vecptr++ = rqeptr->rqHeader.PathInfoPtr;
   *vecptr++ = rqeptr->rqHeader.QueryStringPtr;
   *vecptr++ = &rqeptr->Md5HashPath;
   *vecptr++ = rqeptr->MappedPathPtr;
   *vecptr++ = rqeptr->RequestMappedFile;
   *vecptr++ = rqeptr->ParseOds.ExpFileName;
   *vecptr++ = rqeptr->ScriptName;
   *vecptr++ = rqeptr->RequestMappedScript;
   *vecptr++ = rqeptr->RequestMappedRunTime;
   *vecptr++ = rqeptr->IsCgiPlusScript;
   *vecptr++ = rqeptr->rqPathSet.PathOds;
   switch (rqeptr->rqPathSet.PathOds)
   {
      case MAPURL_PATH_ODS_2 : *vecptr++ = "ODS-2"; break;
      case MAPURL_PATH_ODS_5 : *vecptr++ = "ODS-5"; break;
      case MAPURL_PATH_ODS_ADS : *vecptr++ = "ADS"; break;
      case MAPURL_PATH_ODS_PWK : *vecptr++ = "PWK"; break;
      case MAPURL_PATH_ODS_SMB : *vecptr++ = "SMB"; break;
      case MAPURL_PATH_ODS_SRI : *vecptr++ = "SRI"; break;
      default : *vecptr++ = "ods-2";
   }
   *vecptr++ = rqeptr->PathOds;
   switch (rqeptr->PathOds)
   {
      case MAPURL_PATH_ODS_2 : *vecptr++ = "ODS-2"; break;
      case MAPURL_PATH_ODS_5 : *vecptr++ = "ODS-5"; break;
      case MAPURL_PATH_ODS_ADS : *vecptr++ = "ADS"; break;
      case MAPURL_PATH_ODS_PWK : *vecptr++ = "PWK"; break;
      case MAPURL_PATH_ODS_SMB : *vecptr++ = "SMB"; break;
      case MAPURL_PATH_ODS_SRI : *vecptr++ = "SRI"; break;
      default : *vecptr++ = "ods-2";
   }
   *vecptr++ = rqeptr->PathOdsExtended;

   *vecptr++ = rqeptr->rqResponse.HttpStatus;
   *vecptr++ = rqeptr->rqResponse.HeaderGenerated;
   *vecptr++ = rqeptr->rqResponse.HeaderSent;
   *vecptr++ = rqeptr->rqResponse.ErrorReportPtr;
   *vecptr++ = rqeptr->rqResponse.LocationPtr;
   *vecptr++ = rqeptr->rqResponse.ContentEncodeAsGzip;
   *vecptr++ = rqeptr->rqResponse.ContentIsEncodedGzip;
   *vecptr++ = &rqeptr->rqResponse.ContentLength64;
   *vecptr++ = rqeptr->rqResponse.ContentTypePtr;
   *vecptr++ = rqeptr->rqResponse.ChunkedBufferPtr;
   *vecptr++ = rqeptr->rqResponse.ChunkedBufferSize;
   *vecptr++ = rqeptr->rqResponse.TransferEncodingChunked;

   *vecptr++ = rqeptr->rqWebSocket.ScriptProcessPid;
   *vecptr++ = rqeptr->rqWebSocket.InputSize;
   *vecptr++ = rqeptr->rqWebSocket.InputChannel;
   *vecptr++ = rqeptr->rqWebSocket.InputDevName;
   *vecptr++ = rqeptr->rqWebSocket.InputIOsb.Status;
   *vecptr++ = rqeptr->rqWebSocket.InputIOsb.Count;
   *vecptr++ = rqeptr->rqWebSocket.QueuedInput;
   *vecptr++ = rqeptr->rqWebSocket.QueuedNetRead;
   *vecptr++ = rqeptr->rqWebSocket.OutputSize;
   *vecptr++ = rqeptr->rqWebSocket.OutputChannel;
   *vecptr++ = rqeptr->rqWebSocket.OutputDevName;
   *vecptr++ = rqeptr->rqWebSocket.OutputIOsb.Status;
   *vecptr++ = rqeptr->rqWebSocket.OutputIOsb.Count;
   *vecptr++ = rqeptr->rqWebSocket.QueuedOutput;
   *vecptr++ = rqeptr->rqWebSocket.QueuedNetWrite;

   FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI);
   status = FaolToBuffer (Buffer, sizeof(Buffer), &Length,
                          PathInfoFao, &FaoVector);
   if (VMSnok (status) || status == SS$_BUFFEROVF)
      ErrorNoticed (rqptr, status, NULL, FI_LI);

   WatchWrite (Buffer, Length);

   vecptr = FaoVector;

   *vecptr++ = rqeptr->RemoteUser;
   *vecptr++ = strlen(rqeptr->RemoteUserPassword);
   *vecptr++ = rqeptr->rqAuth.ResolvedRemoteUser;
   *vecptr++ = rqeptr->rqAuth.FinalStatus;
   *vecptr++ = rqeptr->rqAuth.RequestCan;
   *vecptr++ = AuthCanString (rqeptr->rqAuth.RequestCan, AUTH_CAN_FORMAT_LONG);
   *vecptr++ = rqeptr->rqAuth.UserCan;
   *vecptr++ = AuthCanString (rqeptr->rqAuth.UserCan, AUTH_CAN_FORMAT_LONG);
   *vecptr++ = rqeptr->rqAuth.GroupCan;
   *vecptr++ = AuthCanString (rqeptr->rqAuth.GroupCan, AUTH_CAN_FORMAT_LONG);
   *vecptr++ = rqeptr->rqAuth.WorldCan;
   *vecptr++ = AuthCanString (rqeptr->rqAuth.WorldCan, AUTH_CAN_FORMAT_LONG);
   *vecptr++ = rqeptr->rqAuth.Type;
   *vecptr++ = rqeptr->rqAuth.UserDetailsPtr;
   *vecptr++ = rqeptr->rqAuth.HttpsOnly;
   *vecptr++ = rqeptr->rqAuth.SkelKeyAuthenticated;
   *vecptr++ = rqeptr->rqAuth.SysUafAuthenticated;
   *vecptr++ = rqeptr->rqAuth.VmsIdentifiersCount;
   *vecptr++ = rqeptr->rqAuth.VmsIdentifiersPtr;
   *vecptr++ = rqeptr->rqAuth.VmsUserProfile;
   *vecptr++ = rqeptr->rqAuth.VmsUserProfileLength;
   *vecptr++ = rqeptr->rqAuth.VmsUserProfilePtr;
   *vecptr++ = rqeptr->rqAuth.VmsUserScriptAs;
   *vecptr++ = rqeptr->rqAuth.DirectoryPtr;
   *vecptr++ = rqeptr->rqAuth.RealmPtr;
   *vecptr++ = AuthSourceString (rqeptr->rqAuth.RealmPtr,
                                 rqeptr->rqAuth.SourceRealm);
   *vecptr++ = rqeptr->rqAuth.RealmDescrPtr;
   *vecptr++ = rqeptr->rqAuth.PathParameterPtr;
   *vecptr++ = rqeptr->rqAuth.GroupWritePtr;
   *vecptr++ = AuthSourceString (rqeptr->rqAuth.GroupWritePtr,
                                 rqeptr->rqAuth.SourceGroupWrite);
   *vecptr++ = rqeptr->rqAuth.GroupReadPtr;
   *vecptr++ = AuthSourceString (rqeptr->rqAuth.GroupReadPtr,
                                 rqeptr->rqAuth.SourceGroupRead);
   *vecptr++ = rqeptr->rqAuth.GroupRestrictListPtr;
   *vecptr++ = rqeptr->rqAuth.WorldRestrictListPtr;
   *vecptr++ = rqeptr->rqAuth.ProxyStringPtr;
   *vecptr++ = rqeptr->rqAuth.RemoteUser;

   FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI);
   status = FaolToBuffer (Buffer, sizeof(Buffer), &Length,
                          AuthFao, &FaoVector);
   if (VMSnok (status) || status == SS$_BUFFEROVF)
      ErrorNoticed (rqptr, status, NULL, FI_LI);

   WatchWrite (Buffer, Length);

   vecptr = FaoVector;

   *vecptr++ = rqeptr->rqCgi.BufferLength;
   *vecptr++ = rqeptr->rqCgi.BufferRemaining;
   *vecptr++ = rqeptr->rqCgi.BufferPtr;
   *vecptr++ = rqeptr->rqCgi.BufferCurrentPtr;
   *vecptr++ = rqeptr->rqCgi.CalloutInProgress;
   *vecptr++ = rqeptr->rqCgi.CalloutOutputCount;
   *vecptr++ = rqeptr->rqCgi.CalloutOutputPtr;
   *vecptr++ = rqeptr->rqCgi.CalloutOutputPtr;
   *vecptr++ = rqeptr->rqCgi.ContentEncodingGzip;
   *vecptr++ = rqeptr->rqCgi.ContentTypeText;
   *vecptr++ = rqeptr->rqCgi.EofStr;
   *vecptr++ = rqeptr->rqCgi.EotStr;
   *vecptr++ = rqeptr->rqCgi.EscStr;
   *vecptr++ = rqeptr->rqCgi.Header100Continue;
   *vecptr++ = rqeptr->rqCgi.Header100ContinueDone;
   *vecptr++ = rqeptr->rqCgi.HeaderLineCount;
   *vecptr++ = rqeptr->rqCgi.IsCliDcl;
   *vecptr++ = rqeptr->rqCgi.OutputMode;
   switch (rqeptr->rqCgi.OutputMode)
   {
      case CGI_OUTPUT_MODE_STREAM : *vecptr++ = "STREAM"; break;
      case CGI_OUTPUT_MODE_RECORD : *vecptr++ = "RECORD"; break;
      case CGI_OUTPUT_MODE_CRLF : *vecptr++ = "CRLF"; break;
      default : *vecptr++ = "?";
   }
   *vecptr++ = rqeptr->rqCgi.ProcessingBody;
   *vecptr++ = rqeptr->rqCgi.RecordCount;
   *vecptr++ = rqeptr->rqCgi.ScriptRetryCount;
   *vecptr++ = rqeptr->rqCgi.TransferEncoding;
   *vecptr++ = rqeptr->rqCgi.TransferEncodingChunked;
   *vecptr++ = rqeptr->rqCgi.XVMSRecordMode;

   FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI);
   status = FaolToBuffer (Buffer, sizeof(Buffer), &Length,
                          CgiFao, &FaoVector);
   if (VMSnok (status) || status == SS$_BUFFEROVF)
      ErrorNoticed (rqptr, status, NULL, FI_LI);

   WatchWrite (Buffer, Length);

   if ((cptr = rqeptr->rqCgi.BufferPtr))
   {
      /*****************/
      /* CGI variables */
      /*****************/

      for (;;)
      {
         if (!(Length = *(USHORTPTR)cptr)) break;
         status = FaoToBuffer (Buffer, sizeof(Buffer), &Length, "!&Z\n",
                               cptr + sizeof(short));
         if (VMSnok (status) || status == SS$_BUFFEROVF)
            ErrorNoticed (rqptr, status, NULL, FI_LI);

         WatchWrite (Buffer, Length);

         cptr += *(USHORTPTR)cptr + sizeof(short);
      }
   }

   vecptr = FaoVector;
   *vecptr++ = rqeptr->rqCache.EntryPtr;
   *vecptr++ = rqeptr->AgentRequestPtr;
   *vecptr++ = rqeptr->AgentResponsePtr;
   *vecptr++ = rqeptr->AgentAstParam;
   *vecptr++ = rqeptr->DclTaskPtr;

   FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI);
   status = FaolToBuffer (Buffer, sizeof(Buffer), &Length,
                          CacheFao, &FaoVector);
   if (VMSnok (status) || status == SS$_BUFFEROVF)
      ErrorNoticed (rqptr, status, NULL, FI_LI);

   WatchWrite (Buffer, Length);

   if (rqeptr->DclTaskPtr) WatchPeekDcl (rqptr, rqeptr->DclTaskPtr);

   vecptr = FaoVector;
   *vecptr++ = rqeptr->DECnetTaskPtr;

   FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI);
   status = FaolToBuffer (Buffer, sizeof(Buffer), &Length,
                          DECnetFao, &FaoVector);
   if (VMSnok (status) || status == SS$_BUFFEROVF)
      ErrorNoticed (rqptr, status, NULL, FI_LI);

   WatchWrite (Buffer, Length);

   if (rqeptr->DECnetTaskPtr)
   {
      /***************/
      /* DECnet task */
      /***************/

      vecptr = FaoVector;
      *vecptr++ = rqeptr->DECnetTaskPtr->ConnectPtr;
      *vecptr++ = rqeptr->DECnetTaskPtr->DECnetChannel;
      *vecptr++ = rqeptr->DECnetTaskPtr->DECnetConnectIOsb.Status;
      *vecptr++ = rqeptr->DECnetTaskPtr->DECnetConnectIOsb.Count;
      *vecptr++ = rqeptr->DECnetTaskPtr->DECnetReadIOsb.Status;
      *vecptr++ = rqeptr->DECnetTaskPtr->DECnetReadIOsb.Count;
      *vecptr++ = rqeptr->DECnetTaskPtr->DECnetWriteIOsb.Status;
      *vecptr++ = rqeptr->DECnetTaskPtr->DECnetWriteIOsb.Count;
      *vecptr++ = rqeptr->DECnetTaskPtr->DECnetAstFunction;
      *vecptr++ = rqeptr->DECnetTaskPtr->ScriptResponded;
      *vecptr++ = rqeptr->DECnetTaskPtr->BuildRecords;
      *vecptr++ = rqeptr->DECnetTaskPtr->BuildCount;
      *vecptr++ = rqeptr->DECnetTaskPtr->CgiDialogState;
      *vecptr++ = rqeptr->DECnetTaskPtr->OsuDialogState;
      *vecptr++ = rqeptr->DECnetTaskPtr->OsuDnetCgi;
      *vecptr++ = rqeptr->DECnetTaskPtr->QueuedDECnetIO;
      *vecptr++ = rqeptr->DECnetTaskPtr->QueuedBodyRead;
      *vecptr++ = rqeptr->DECnetTaskPtr->QueuedNetWrite;
      *vecptr++ = rqeptr->DECnetTaskPtr->NextTaskFunction;
      *vecptr++ = rqeptr->DECnetTaskPtr->RequestPtr;

      FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI);
      status = FaolToBuffer (Buffer, sizeof(Buffer), &Length,
                             DECnetTaskFao, &FaoVector);
      if (VMSnok (status) || status == SS$_BUFFEROVF)
         ErrorNoticed (rqptr, status, NULL, FI_LI);

      WatchWrite (Buffer, Length);
   }

   vecptr = FaoVector;
   *vecptr++ = rqeptr->DescrTaskPtr;
   *vecptr++ = rqeptr->DirTaskPtr;
   *vecptr++ = rqeptr->FileTaskPtr;
   *vecptr++ = rqeptr->HTAdminTaskPtr;
   *vecptr++ = rqeptr->ProxyTaskPtr;

   FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI);
   status = FaolToBuffer (Buffer, sizeof(Buffer), &Length,
                          DescrFao, &FaoVector);
   if (VMSnok (status) || status == SS$_BUFFEROVF)
      ErrorNoticed (rqptr, status, NULL, FI_LI);

   WatchWrite (Buffer, Length);

   if (rqeptr->ProxyTaskPtr)
   {
      /**************/
      /* proxy task */
      /**************/

      NetGetBgDevice (rqeptr->ProxyTaskPtr->NetIoPtr->Channel,
                      ProxyDevName, sizeof(ProxyDevName));

      vecptr = FaoVector;
      *vecptr++ = rqeptr->ProxyTaskPtr->NetIoPtr->Channel;
      *vecptr++ = ProxyDevName+1;
      *vecptr++ = rqeptr->ProxyTaskPtr->NetIoPtr->TcpMaxQio;
      *vecptr++ = rqeptr->ProxyTaskPtr->NetIoPtr->TcpMaxSeg;
      *vecptr++ = rqeptr->ProxyTaskPtr->NetIoPtr->TcpRcvBuf;
      *vecptr++ = rqeptr->ProxyTaskPtr->NetIoPtr->TcpSndBuf;
      *vecptr++ = rqeptr->ProxyTaskPtr->NetIoPtr->ReadIOsb.Status;
      *vecptr++ = rqeptr->ProxyTaskPtr->NetIoPtr->ReadIOsb.Count;
      *vecptr++ = rqeptr->ProxyTaskPtr->NetIoPtr->ReadStatus;
      *vecptr++ = rqeptr->ProxyTaskPtr->NetIoPtr->ReadCount;
      *vecptr++ = rqeptr->ProxyTaskPtr->NetIoPtr->ReadAstFunction;
      *vecptr++ = rqeptr->ProxyTaskPtr->NetIoPtr->ReadPtr;
      *vecptr++ = rqeptr->ProxyTaskPtr->NetIoPtr->ReadSize;
      *vecptr++ = rqeptr->ProxyTaskPtr->NetIoPtr->WriteIOsb.Status;
      *vecptr++ = rqeptr->ProxyTaskPtr->NetIoPtr->WriteIOsb.Count;
      *vecptr++ = rqeptr->ProxyTaskPtr->NetIoPtr->WriteStatus;
      *vecptr++ = rqeptr->ProxyTaskPtr->NetIoPtr->WriteCount;
      *vecptr++ = rqeptr->ProxyTaskPtr->NetIoPtr->WriteAstFunction;
      *vecptr++ = rqeptr->ProxyTaskPtr->NetIoPtr->WritePtr;
      *vecptr++ = rqeptr->ProxyTaskPtr->NetIoPtr->WriteLength;
      *vecptr++ = &rqeptr->ProxyTaskPtr->NetIoPtr->BytesRawRx64;
      *vecptr++ = &rqeptr->ProxyTaskPtr->NetIoPtr->BytesRawTx64;
      *vecptr++ = &rqeptr->ProxyTaskPtr->ConnectIpAddress;
      *vecptr++ = &rqeptr->ProxyTaskPtr->RequestHostIpAddress;
      *vecptr++ = rqeptr->ProxyTaskPtr->RequestHostPort;
      *vecptr++ = rqeptr->ProxyTaskPtr->RequestSchemeName;
      *vecptr++ = rqeptr->ProxyTaskPtr->ProxyTunnel;
      *vecptr++ = rqeptr->ProxyTaskPtr->ProxyLookupRetryCount;
      *vecptr++ = rqeptr->ProxyTaskPtr->ProxyConnectIOsb.Status;
      *vecptr++ = rqeptr->ProxyTaskPtr->ProxyConnectIOsb.Count;
      *vecptr++ = rqeptr->ProxyTaskPtr->VerifyRecordPtr;
      *vecptr++ = rqeptr->ProxyTaskPtr->RebuiltRequestLength;
      *vecptr++ = rqeptr->ProxyTaskPtr->RebuiltRequestPtr;
      *vecptr++ = rqeptr->ProxyTaskPtr->ResponseBodyLength;
      *vecptr++ = rqeptr->ProxyTaskPtr->ResponseBufferNetCount;
      *vecptr++ = rqeptr->ProxyTaskPtr->ResponseBufferNetPtr;
      *vecptr++ = rqeptr->ProxyTaskPtr->ResponseBufferCurrentPtr;
      *vecptr++ = rqeptr->ProxyTaskPtr->ResponseBufferRemaining;
      *vecptr++ = rqeptr->ProxyTaskPtr->ResponseBufferCount;
      *vecptr++ = rqeptr->ProxyTaskPtr->ResponseBufferPtr;
      *vecptr++ = rqeptr->ProxyTaskPtr->ResponseCacheControlMaxAge;
      *vecptr++ = rqeptr->ProxyTaskPtr->ResponseCacheControlMaxAgeZero;
      *vecptr++ = rqeptr->ProxyTaskPtr->ResponseCacheControlMustReval;
      *vecptr++ = rqeptr->ProxyTaskPtr->ResponseCacheControlNoCache;
      *vecptr++ = rqeptr->ProxyTaskPtr->ResponseCacheControlNoStore;
      *vecptr++ = rqeptr->ProxyTaskPtr->ResponseCacheControlNoTransform;
      *vecptr++ = rqeptr->ProxyTaskPtr->ResponseCacheControlPrivate;
      *vecptr++ = rqeptr->ProxyTaskPtr->ResponseCacheControlProxyReval;
      *vecptr++ = rqeptr->ProxyTaskPtr->ResponseCacheControlPublic;
      *vecptr++ = rqeptr->ProxyTaskPtr->ResponseCacheControlSMaxAge;
      *vecptr++ = rqeptr->ProxyTaskPtr->ResponseChunkedCount;
      *vecptr++ = rqeptr->ProxyTaskPtr->ResponseChunkedEnd;
      *vecptr++ = rqeptr->ProxyTaskPtr->ResponseChunkedEol;
      *vecptr++ = rqeptr->ProxyTaskPtr->ResponseChunkedEot;
      *vecptr++ = rqeptr->ProxyTaskPtr->ResponseChunkedInit;
      *vecptr++ = rqeptr->ProxyTaskPtr->ResponseChunkedSize;
      *vecptr++ = rqeptr->ProxyTaskPtr->ResponseChunkedString;
      *vecptr++ = rqeptr->ProxyTaskPtr->ResponseChunkedNewlineCount;
      *vecptr++ = rqeptr->ProxyTaskPtr->ResponseConsecutiveNewLineCount;
      *vecptr++ = rqeptr->ProxyTaskPtr->ResponseContentEncodingGzip;
      *vecptr++ = rqeptr->ProxyTaskPtr->ResponseContentEncodingUnknown;
      *vecptr++ = rqeptr->ProxyTaskPtr->ResponseContentLength;
      *vecptr++ = rqeptr->ProxyTaskPtr->ResponseExpires;
      *vecptr++ = &rqeptr->ProxyTaskPtr->ResponseExpiresTime64;
      *vecptr++ = rqeptr->ProxyTaskPtr->ResponseHeaderLength;
      if (!rqeptr->ProxyTaskPtr->ResponseHeaderPtr)
      {
         *vecptr++ = sizeof(WATCH_NULL_STRING)-1;
         *vecptr++ = WATCH_NULL_STRING;
      }
      else
      if (rqeptr->ProxyTaskPtr->ResponseConsecutiveNewLineCount < 2)
      {
         *vecptr++ = rqeptr->ProxyTaskPtr->ResponseHeaderLength;
         *vecptr++ = WATCH_NULL(rqeptr->ProxyTaskPtr->ResponseHeaderPtr);
      }
      else
      {
         *vecptr++ = sizeof(WATCH_NULL_STRING)-1;
         *vecptr++ = WATCH_NULL_STRING;
      }
      *vecptr++ = rqeptr->ProxyTaskPtr->RebuiltHeaderLength;
      *vecptr++ = rqeptr->ProxyTaskPtr->RebuiltHeaderPtr;
      *vecptr++ = rqeptr->ProxyTaskPtr->ResponseLastModified;
      *vecptr++ = &rqeptr->ProxyTaskPtr->ResponseLastModifiedTime64;
      *vecptr++ = rqeptr->ProxyTaskPtr->ResponseTransferEncodingChunked;
      *vecptr++ = rqeptr->ProxyTaskPtr->ResponseUpgradeWebSocket;

      FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI);
      status = FaolToBuffer (Buffer, sizeof(Buffer), &Length,
                             ProxyTaskFao, &FaoVector);
      if (VMSnok (status) || status == SS$_BUFFEROVF)
         ErrorNoticed (rqptr, status, NULL, FI_LI);

      WatchWrite (Buffer, Length);
   }

   vecptr = FaoVector;
   *vecptr++ = rqeptr->PutTaskPtr;
   *vecptr++ = rqeptr->SsiTaskPtr;
   *vecptr++ = rqeptr->UpdTaskPtr;

   FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI);
   status = FaolToBuffer (Buffer, sizeof(Buffer), &Length, PutFao, &FaoVector);
   if (VMSnok (status) || status == SS$_BUFFEROVF)
      ErrorNoticed (rqptr, status, NULL, FI_LI);

   WatchWrite (Buffer, Length);

   WatchNone (false);
}

/*****************************************************************************/
/*
Peek at the supplied DCL structure.
*/

void WatchPeekDcl
(
REQUEST_STRUCT *rqptr,
DCL_TASK *tkptr
)
{
   static char  DclTaskFao [] =
"!33<RequestPtr!> !&X\n\
!33<TaskType!> !UL (!AZ)\n\
!33<LifeTimeSecond!> !&@\n\
!33<CrePrcTermMbxChannel!> !UL (!AZ)\n\
!33<CrePrcDetachProcess!> !UL\n\
!33<CrePrcDetachStarting!> !UL\n\
!33<CrePrcUserName!> !&Z\n\
!33<CgiPlusVarStruct!> !UL\n\
!33<CgiPlusInChannel!> !UL (!AZ)\n\
!33<CgiPlusInIOsb!> !&S !UL\n\
!33<QueuedCgiPlusIn!> !UL\n\
!33<HttpInputChannel!> !UL (!AZ)\n\
!33<HttpInputIOsb!> !&S !UL\n\
!33<QueuedHttpInput!> !UL\n\
!33<ClientReadBufferSize!> !UL\n\
!33<ClientReadStripCrLf!> !UL\n\
!33<QueuedClientRead!> !UL\n\
!33<SysCommandChannel!> !UL (!AZ)\n\
!33<SysCommandIOsb!> !&S !UL\n\
!33<QueuedSysCommand!> !UL\n\
!33<QueuedSysCommandAllowed!> !UL\n\
!33<SysOutputSize!> !UL\n\
!33<SysOutputChannel!> !UL (!AZ)\n\
!33<SysOutputIOsb!> !&S !UL\n\
!33<QueuedSysOutput!> !UL\n\
!33<QueuedClientOutput!> !UL\n\
!33<BuildRecords!> !&B\n\
!33<SysOutputBuildCount!> !UL\n\
!33<ClientWriteErrorCount!> !UL\n\
!33<ScriptProcessPid!> !8XL\n\
!33<CrePrcTermRecord.acc$l_finalsts!> !&S\n\
!33<ScriptProcessActivated!> !&B\n\
!33<ScriptProcessResponded!> !&B\n\
!33<TaskRunDown!> !UL\n\
!33<DeleteProcess!> !&B\n\
!33<ForceImageExit!> !&B\n\
!33<ForceImageExitGetJpi!> !&B\n\
!33<ForceImageExitIssued!> !&B\n\
!33<ForceImageExitSecond!> !UL\n\
!33<JpiImagNameIOsb.Status!> !&S\n\
!33<JpiImagName!> |!#AZ|\n\
!33<ScriptCpuMax!> !UL\n\
!33<ScriptCpuTimGetJpi!> !UL\n\
!33<JpiCpuTimIOsb.Status!> !&S\n\
!33<JpiCpuTim!> !UL\n\
!33<ScriptCpuTimMax!> !UL\n\
!33<CalloutFunction!> !&A\n\
!33<DclCommandPtr!> !&Z\n\
!33<DclCommandSize!> !UL\n\
!33<DclCommandLength!> !UL\n\
!33<ScriptName!> !&Z\n\
!33<ScriptFileName!> !&Z\n\
!33<MemBufCount64!> !@UQ\n\
!33<MemBufInProgress!> !&B\n\
!33<MemBufGblSecName!> !&Z\n\
!33<MemBufGblSecPtr!> !8XL\n\
!33<MemBufSize!> !UL\n\
|\n";

   int  status;
   ushort  slen;
   ulong  FaoVector [96];
   ulong  *vecptr;
   char  Buffer [4096];

   if (!tkptr) return;

   vecptr = FaoVector;
   *vecptr++ = tkptr->RequestPtr;
   *vecptr++ = tkptr->TaskType;
   switch (tkptr->TaskType)
   {
      case DCL_TASK_TYPE_NONE : *vecptr++ = "none"; break;
      case DCL_TASK_TYPE_CLI : *vecptr++ = "CLI"; break;
      case DCL_TASK_TYPE_CGI_SCRIPT : *vecptr++ = "CGI"; break;
      case DCL_TASK_TYPE_CGIPLUS_SCRIPT : *vecptr++ = "CGIplus"; break;
      case DCL_TASK_TYPE_RTE_SCRIPT : *vecptr++ = "RTE"; break;
      default : *vecptr++ = "?";
   }
   if (tkptr->LifeTimeSecond == DCL_DO_NOT_DISTURB)
      *vecptr++ = "DO-NOT-DISTURB";
   else
   {
      *vecptr++ = "!UL";
      *vecptr++ = tkptr->LifeTimeSecond;
   }
   *vecptr++ = tkptr->CrePrcTermMbxChannel;
   *vecptr++ = tkptr->CrePrcTermMbxDevName+1;
   *vecptr++ = tkptr->CrePrcDetachProcess;
   *vecptr++ = tkptr->CrePrcDetachStarting;
   *vecptr++ = tkptr->CrePrcUserName;
   *vecptr++ = tkptr->CgiPlusVarStruct;
   *vecptr++ = tkptr->CgiPlusInChannel;
   *vecptr++ = tkptr->CgiPlusInDevName+1;
   *vecptr++ = tkptr->CgiPlusInIOsb.Status;
   *vecptr++ = tkptr->CgiPlusInIOsb.Count;
   *vecptr++ = tkptr->QueuedCgiPlusIn;
   *vecptr++ = tkptr->HttpInputChannel;
   *vecptr++ = tkptr->HttpInputDevName+1;
   *vecptr++ = tkptr->HttpInputIOsb.Status;
   *vecptr++ = tkptr->HttpInputIOsb.Count;
   *vecptr++ = tkptr->QueuedHttpInput;
   *vecptr++ = tkptr->ClientReadBufferSize;
   *vecptr++ = tkptr->ClientReadStripCrLf;
   *vecptr++ = tkptr->QueuedClientRead;
   *vecptr++ = tkptr->SysCommandChannel;
   *vecptr++ = tkptr->SysCommandDevName+1;
   *vecptr++ = tkptr->SysCommandIOsb.Status;
   *vecptr++ = tkptr->SysCommandIOsb.Count;
   *vecptr++ = tkptr->QueuedSysCommand;
   *vecptr++ = tkptr->QueuedSysCommandAllowed;
   *vecptr++ = tkptr->SysOutputSize;
   *vecptr++ = tkptr->SysOutputChannel;
   *vecptr++ = tkptr->SysOutputDevName+1;
   *vecptr++ = tkptr->SysOutputIOsb.Status;
   *vecptr++ = tkptr->SysOutputIOsb.Count;
   *vecptr++ = tkptr->QueuedSysOutput;
   *vecptr++ = tkptr->QueuedClientOutput;
   *vecptr++ = tkptr->BuildRecords;
   *vecptr++ = tkptr->SysOutputBuildCount;
   *vecptr++ = tkptr->ClientWriteErrorCount;
   *vecptr++ = tkptr->ScriptProcessPid;
   *vecptr++ = tkptr->CrePrcTermRecord.acc$l_finalsts;
   *vecptr++ = tkptr->ScriptProcessActivated;
   *vecptr++ = tkptr->ScriptProcessResponded;
   *vecptr++ = tkptr->TaskRunDown;
   *vecptr++ = tkptr->DeleteProcess;
   *vecptr++ = tkptr->ForceImageExit;
   *vecptr++ = tkptr->ForceImageExitGetJpi;
   *vecptr++ = tkptr->ForceImageExitIssued;
   *vecptr++ = tkptr->ForceImageExitSecond;
   *vecptr++ = tkptr->JpiImagNameIOsb.Status;
   *vecptr++ = tkptr->JpiImagNameLength;
   *vecptr++ = tkptr->JpiImagName;
   *vecptr++ = tkptr->ScriptCpuMax;
   *vecptr++ = tkptr->ScriptCpuTimGetJpi;
   *vecptr++ = tkptr->JpiCpuTimIOsb.Status;
   *vecptr++ = tkptr->JpiCpuTim;
   *vecptr++ = tkptr->ScriptCpuTimMax;
   *vecptr++ = tkptr->CalloutFunction;
   *vecptr++ = tkptr->DclCommandPtr;
   *vecptr++ = tkptr->DclCommandSize;
   *vecptr++ = tkptr->DclCommandLength;
   *vecptr++ = tkptr->ScriptName;
   *vecptr++ = tkptr->ScriptFileName;
   *vecptr++ = &tkptr->MemBufCount64;
   *vecptr++ = tkptr->MemBufInProgress;
   *vecptr++ = tkptr->MemBufGblSecName;
   *vecptr++ = tkptr->MemBufGblSecPtr;
   *vecptr++ = tkptr->MemBufSize;

   FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI);
   status = FaolToBuffer (Buffer, sizeof(Buffer), &slen,
                          DclTaskFao, &FaoVector);
   if (VMSnok (status) || status == SS$_BUFFEROVF)
      ErrorNoticed (rqptr, status, NULL, FI_LI);

   WatchWrite (Buffer, slen);
}

/*****************************************************************************/
/*
Peek at the supplied HTTP/2 structure.
*/

void WatchPeekHttp2
(
REQUEST_STRUCT *rqptr,
HTTP2_STRUCT *h2ptr,
char *Title
)
{
   static char  NetIoFao [] =
"!AZ\n\
!33<BytesRawRx64!> !@UQ\n\
!33<BytesRawTx64!> !@UQ\n\
!33<ClientInitialWindowSize!> !UL\n\
!33<ClientMaxConcStreams!> !UL\n\
!33<ClientMaxFrameSize!> !UL\n\
!33<ClientMaxHeaderListSize!> !UL\n\
!33<ClientMaxHeaderTableSize!> !UL\n\
!33<ClientPushPromise!> !UL\n\
!33<ConnectNumber!> !UL\n\
!33<ConnectTime64!> !%D (!AZ ago)\n\
!33<FlowFrameCount!> !UL\n\
!33<FlowFrameTally!> !UL\n\
!33<FlowStallCount!> !UL\n\
!33<FlowStallTally!> !UL\n\
!33<FlowStallTickSecond!> !UL\n\
!33<FrameCountRx!> !UL\n\
!33<FrameCountTx!> !UL\n\
!33<FrameRequestCountRx!> !UL\n\
!33<FrameRequestCountTx!> !UL\n\
!33<GoAwayIdent!> !UL\n\
!33<GoAwayLastStreamIdent!> !UL\n\
!33<GoAwayTickSecond!> !UL (!SL seconds)\n\
!33<IdleTickSecond!> !UL (!SL seconds)\n\
!33<LastStreamIdent!> !UL\n\
!33<PingTime64!> !%D!&@\n\
!33<PingMicroSeconds!> !UL\n\
!33<PingBackTickSecond!> !UL (!SL seconds)\n\
!33<PingSendTickSecond!> !UL (!SL seconds)\n\
!33<QueuedWriteList[0].Count!> !UL\n\
!33<QueuedWriteList[1].Count!> !UL\n\
!33<QueuedWriteList[2].Count!> !UL\n\
!33<QueuedWriteList[3].Count!> !UL\n\
!33<QueuedWritePeak!> !UL\n\
!33<ReadBufferCount!> !UL\n\
!33<ReadBufferPtr!> !&X\n\
!33<ReadBufferSize!> !UL\n\
!33<ReadInUseCount!> !UL\n\
!33<ReadWindowSize!> !UL\n\
!33<RequestCount!> !UL\n\
!33<RequestCurrent!> !UL\n\
!33<RequestPeak!> !UL\n\
!33<ServerHeaderTableSize!> !UL\n\
!33<ServerInitialWindowSize!> !UL\n\
!33<ServerMaxConcStreams!> !UL\n\
!33<ServerMaxFrameSize!> !UL\n\
!33<ServerMaxHeaderTableSize!> !UL\n\
!33<ServerMaxHeaderListSize!> !UL\n\
!33<ServerPushPromise!> !UL\n\
!33<ServicePtr!> !&X\n\
!33<->ServerChannel!> !UL (!AZ)\n\
!33<->ServerHostPort!> !&Z\n\
!33<->ServerIpAddressString!> !&Z\n\
!33<StreamList.Count!> !UL\n\
!33<WatchItem!> !8XL\n\
!33<WriteInUseCount!> !SL\n\
!33<WriteWindowSize!> !SL\n\
!33<NETIO_IN_PROGRESS!> !&B\n";

   int  status;
   int64  Time64,
          ConnectDuration64,
          PingDuration64;
   ushort  Length;
   ulong  FaoVector [96];
   ulong  *vecptr;
   char  Buffer [4096],
         ServerDevName [64];

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

   WatchNone (true);

   NetGetBgDevice (h2ptr->ServicePtr->ServerChannel,
                   ServerDevName, sizeof(ServerDevName));

   sys$gettim (&Time64);
   ConnectDuration64 = h2ptr->ConnectTime64 - Time64;
   PingDuration64 = h2ptr->PingTime64 - Time64;

   vecptr = FaoVector;

   *vecptr++ = Title ? Title : "";

   *vecptr++ = &h2ptr->BytesRawRx64;
   *vecptr++ = &h2ptr->BytesRawTx64;
   *vecptr++ = h2ptr->ClientInitialWindowSize;

   *vecptr++ = h2ptr->ClientMaxConcStreams;
   *vecptr++ = h2ptr->ClientMaxFrameSize;
   *vecptr++ = h2ptr->ClientMaxHeaderListSize;
   *vecptr++ = h2ptr->ClientMaxHeaderTableSize;
   *vecptr++ = h2ptr->ClientPushPromise;

   *vecptr++ = h2ptr->ConnectNumber;
   *vecptr++ = &h2ptr->ConnectTime64;
   *vecptr++ = DurationString (rqptr, &ConnectDuration64);

   *vecptr++ = h2ptr->FlowFrameCount;
   *vecptr++ = h2ptr->FlowFrameTally;
   *vecptr++ = h2ptr->FlowStallCount;
   *vecptr++ = h2ptr->FlowStallTally;
   *vecptr++ = h2ptr->FlowStallTickSecond;

   *vecptr++ = h2ptr->FrameCountRx;
   *vecptr++ = h2ptr->FrameCountTx;
   *vecptr++ = h2ptr->FrameRequestCountRx;
   *vecptr++ = h2ptr->FrameRequestCountTx;

   *vecptr++ = h2ptr->GoAwayIdent;
   *vecptr++ = h2ptr->GoAwayLastStreamIdent;
   *vecptr++ = h2ptr->GoAwayTickSecond;
   *vecptr++ = h2ptr->GoAwayTickSecond - HttpdTickSecond;
   *vecptr++ = h2ptr->IdleTickSecond;
   *vecptr++ = h2ptr->IdleTickSecond - HttpdTickSecond;
   *vecptr++ = h2ptr->LastStreamIdent;
   *vecptr++ = &h2ptr->PingTime64;
   if (!h2ptr->PingTime64)
      *vecptr++ = "";
   else
   {
      *vecptr++ = " (!AZ ago)";
      *vecptr++ = DurationString (rqptr, &PingDuration64);
   }
   *vecptr++ = h2ptr->PingMicroSeconds;
   *vecptr++ = h2ptr->PingBackTickSecond;
   *vecptr++ = h2ptr->PingBackTickSecond ?
               h2ptr->PingBackTickSecond - HttpdTickSecond : 0;
   *vecptr++ = h2ptr->PingSendTickSecond;
   *vecptr++ = h2ptr->PingSendTickSecond ?
               h2ptr->PingSendTickSecond - HttpdTickSecond : 0;

   *vecptr++ = LIST_GET_COUNT(&h2ptr->QueuedWriteList[0]);
   *vecptr++ = LIST_GET_COUNT(&h2ptr->QueuedWriteList[1]);
   *vecptr++ = LIST_GET_COUNT(&h2ptr->QueuedWriteList[2]);
   *vecptr++ = LIST_GET_COUNT(&h2ptr->QueuedWriteList[3]);
   *vecptr++ = h2ptr->QueuedWritePeak;

   *vecptr++ = h2ptr->ReadBufferCount;
   *vecptr++ = h2ptr->ReadBufferPtr;
   *vecptr++ = h2ptr->ReadBufferSize;
   *vecptr++ = h2ptr->ReadInUseCount;
   *vecptr++ = h2ptr->ReadWindowSize;

   *vecptr++ = h2ptr->RequestCount;
   *vecptr++ = h2ptr->RequestCurrent;
   *vecptr++ = h2ptr->RequestPeak;

   *vecptr++ = h2ptr->ServerHeaderTableSize;
   *vecptr++ = h2ptr->ServerInitialWindowSize;
   *vecptr++ = h2ptr->ServerMaxConcStreams;
   *vecptr++ = h2ptr->ServerMaxFrameSize;
   *vecptr++ = h2ptr->ServerMaxHeaderTableSize;
   *vecptr++ = h2ptr->ServerMaxHeaderListSize;
   *vecptr++ = h2ptr->ServerPushPromise;

   *vecptr++ = h2ptr->ServicePtr;
   *vecptr++ = h2ptr->ServicePtr->ServerChannel;
   *vecptr++ = ServerDevName+1;
   *vecptr++ = h2ptr->ServicePtr->ServerHostPort;
   *vecptr++ = h2ptr->ServicePtr->ServerIpAddressString;

   *vecptr++ = LIST_GET_COUNT(&h2ptr->StreamList);

   *vecptr++ = h2ptr->WatchItem;
   *vecptr++ = h2ptr->WriteInUseCount;
   *vecptr++ = h2ptr->WriteWindowSize;
   *vecptr++ = NETIO_IN_PROGRESS (h2ptr->NetIoPtr);

   FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI);
   status = FaolToBuffer (Buffer, sizeof(Buffer), &Length,
                          NetIoFao, &FaoVector);
   if (VMSnok (status) || status == SS$_BUFFEROVF)
      ErrorNoticed (rqptr, status, NULL, FI_LI);

   WatchWrite (Buffer, Length);

   WatchPeekNetIo (rqptr, h2ptr->NetIoPtr, "Http2Ptr->NetIoPtr->");

   WatchNone (false);
}

/*****************************************************************************/
/*
Peek at the supplied NETIO structure.
*/

void WatchPeekNetIo
(
REQUEST_STRUCT *rqptr,
NETIO_STRUCT *ioptr,
char *Title
)
{
   static char  NetIoFao [] =
"|\n\
!33<!AZ!> !&X\n\
!33<Channel!> !UL (!AZ)\n\
!33<TcpMaxQio!> !UL\n\
!33<TcpMaxSeg!> !UL\n\
!33<TcpRcvBuf!> !UL\n\
!33<TcpSndBuf!> !UL\n\
!33<BlocksRawRx64!> !@UQ\n\
!33<BytesRawRx64!> !@UQ\n\
!33<BlocksRawTx64!> !@UQ\n\
!33<BytesRawTx64!> !@UQ\n\
!33<ReadAstFunction!> !&A\n\
!33<ReadAstParam!> !&X\n\
!33<ReadCount!> !UL\n\
!33<ReadErrorCount!> !UL\n\
!33<ReadErrorStatus!> !&S\n\
!33<ReadPtr!> !&X\n\
!33<ReadSize!> !UL\n\
!33<ReadIOsb!> !&S !UL\n\
!33<VmsStatus!> !&S\n\
!33<SSHversionDigit!> 0x!2XL\n\
!33<WriteAstFunction!> !&A\n\
!33<WriteAstParam!> !&X\n\
!33<WriteCount!> !UL\n\
!33<WriteErrorCount!> !UL\n\
!33<WriteErrorStatus!> !&S\n\
!33<WriteLength!> !UL\n\
!33<WritePtr!> !&X\n\
!33<WriteLength!> !UL\n\
!33<WriteIOsb!> !&S !UL\n\
!33<ClientPtr->Lookup.HostName!> !&Z\n\
!33<ClientPtr->IpAddress!> !&I\n\
!33<ClientPtr->IpPort!> !UL\n\
!33<ClientPtr->MultiHomeIpAddress!> !&I\n";

   static char  SesolaPtrNull [] =
"|\nSesolaPtr->                       0x00000000\n";

   int  status;
   ushort  Length;
   ulong  FaoVector [48];
   ulong  *vecptr;
   char  Buffer [2048],
         DevName [64];

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

   WatchNone (true);

   NetGetBgDevice (ioptr->Channel, DevName, sizeof(DevName));

   vecptr = FaoVector;

   *vecptr++ = Title ? Title : "ioptr->";
   *vecptr++ = ioptr;
   *vecptr++ = ioptr->Channel;
   *vecptr++ = DevName+1;
   *vecptr++ = ioptr->TcpMaxQio;
   *vecptr++ = ioptr->TcpMaxSeg;
   *vecptr++ = ioptr->TcpRcvBuf;
   *vecptr++ = ioptr->TcpSndBuf;
   *vecptr++ = &ioptr->BlocksRawRx64;
   *vecptr++ = &ioptr->BytesRawRx64;
   *vecptr++ = &ioptr->BlocksRawTx64;
   *vecptr++ = &ioptr->BytesRawTx64;

   *vecptr++ = ioptr->ReadAstFunction;
   *vecptr++ = ioptr->ReadAstParam;
   *vecptr++ = ioptr->ReadCount;
   *vecptr++ = ioptr->ReadErrorCount;
   *vecptr++ = ioptr->ReadErrorStatus;
   *vecptr++ = ioptr->ReadPtr;
   *vecptr++ = ioptr->ReadSize;
   *vecptr++ = ioptr->ReadIOsb.Status;
   *vecptr++ = ioptr->ReadIOsb.Count;
   *vecptr++ = ioptr->VmsStatus;
   *vecptr++ = ioptr->SSHversionDigit;

   *vecptr++ = ioptr->WriteAstFunction;
   *vecptr++ = ioptr->WriteAstParam;
   *vecptr++ = ioptr->WriteCount;
   *vecptr++ = ioptr->WriteErrorCount;
   *vecptr++ = ioptr->WriteErrorStatus;
   *vecptr++ = ioptr->WriteLength;
   *vecptr++ = ioptr->WritePtr;
   *vecptr++ = ioptr->WriteLength;
   *vecptr++ = ioptr->WriteIOsb.Status;
   *vecptr++ = ioptr->WriteIOsb.Count;

   *vecptr++ = ioptr->ClientPtr->Lookup.HostName;
   *vecptr++ = &ioptr->ClientPtr->IpAddress;
   *vecptr++ = ioptr->ClientPtr->IpPort;
   *vecptr++ = &ioptr->ClientPtr->MultiHomeIpAddress;

   FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI);
   status = FaolToBuffer (Buffer, sizeof(Buffer), &Length,
                          NetIoFao, &FaoVector);
   if (VMSnok (status) || status == SS$_BUFFEROVF)
      ErrorNoticed (rqptr, status, NULL, FI_LI);

   WatchWrite (Buffer, Length);

   if (ioptr->SesolaPtr == NULL)
      WatchWrite (SesolaPtrNull, sizeof(SesolaPtrNull)-1);
   else
      SesolaWatchPeek (rqptr, ioptr->SesolaPtr);

   WatchNone (false);
}

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

void WatchVmZone
(
REQUEST_STRUCT *rqptr,
ulong ZoneId
)
{
   static ulong  ShowVmZoneDetail = 3;

   int  status;
   ushort  slen;
   char  buf [256];

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

   status = lib$show_vm_zone (&ZoneId, &ShowVmZoneDetail, &WatchVmWrite, rqptr);
   if (VMSnok (status))
   {
      FaoToBuffer (buf, sizeof(buf), &slen, "LIB$SHOW_VM_ZONE !&S\n", status);
      WatchWrite (buf, slen);
   }
}

/*****************************************************************************/
/*
Action routine for lib$show_vm*() routines.
Munge the peek link from the zone name.
*/ 

int WatchVmWrite
(
struct dsc$descriptor *DscPtr,
REQUEST_STRUCT *rqptr
)
{
   int  status;
   char  *cptr, *czptr, *sptr, *zptr;
   char  buf [256];

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

   zptr = (sptr = buf) + sizeof(buf)-2;
   czptr = (cptr = DscPtr->dsc$a_pointer) + DscPtr->dsc$w_length;
   while (cptr < czptr && sptr < zptr) *sptr++ = *cptr++;
   *sptr = '\0';
   if (sptr = strstr (buf, "WASD Request Heap "))
   {
      sptr += 18;
      if (cptr = strstr (sptr, "at="))
      {
         for (cptr += 3; isdigit(*cptr); *sptr++ = *cptr++);
         *sptr++ = '\"';
         *sptr = '\0';  
      }
   }
   for (sptr = buf; *sptr; sptr++);
   *sptr++ = '\n';
   *sptr = '\0';  
   WatchWrite (buf, sptr - buf);

   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
Display the size of selected data structures.
*/ 

void WatchReportStruct (REQUEST_STRUCT *rqptr)

{
#if WATCH_MOD

   static char  ResponseFao [] =
"!25<AccountingStruct!> !6UL  (bytes)\n\
!25<ActivityGblSecStruct!> !6UL\n\
!25<AuthCacheRecordStruct!> !6UL\n\
!25<AuthConfigStruct!> !6UL\n\
!25<AuthGblSecStruct!> !6UL\n\
!25<AuthHtaRecordStruct!> !6UL\n\
!25<AuthPathRecordStruct!> !6UL\n\
!25<AuthRealmRecordStruct!> !6UL\n\
!25<BodyProcessStruct!> !6UL\n\
!25<CacheStruct!> !6UL\n\
!25<DclTaskStruct!> !6UL\n\
!25<DclScriptNameCacheStruct!> !6UL\n\
!25<DirTaskStruct!> !6UL\n\
!25<DECnetConnectStruct!> !6UL\n\
!25<DECnetTaskStruct!> !6UL\n\
!25<Http2Struct!> !6UL\n\
!25<Http2StreamStruct!> !6UL\n\
!25<Http2WriteStruct!> !6UL\n\
!25<HttpdGblSecStruct!> !6UL\n\
!25<MonRequestStruct!> !6UL\n\
!25<NetIoStruct!> !6UL\n\
!25<OdsStruct!> !6UL\n\
!25<ProxyTaskStruct!> !6UL\n\
!25<ProxyAccountingStruct!> !6UL\n\
!25<ProxyVerifyGblSecStruct!> !6UL\n\
!25<PutTaskStruct!> !6UL\n\
!25<RequestStruct!> !6UL\n\
!25<ServiceStruct!> !6UL\n\
!25<SesolaGblSecStruct!> !6UL\n\
!25<SsiStruct!> !6UL\n\
!25<StrDscStruct!> !6UL\n\
!25<UpdTaskStruct!> !6UL\n\
!25<VmStruct!> !6UL\n\
!25<WebDavTaskStruct!> !6UL\n";

   int  status;
   ulong  FaoVector [64];
   ulong  *vecptr;

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

   if (!rqptr->RemoteUser[0])
   {
      rqptr->rqResponse.HttpStatus = 403;
      ErrorGeneral (rqptr, ErrorWatchAuthNeeded, FI_LI);
      AdminEnd (rqptr);
      return;
   }

   vecptr = FaoVector;
   *vecptr++ = sizeof(struct AccountingStruct);
   *vecptr++ = sizeof(struct ActivityGblSecStruct);
   *vecptr++ = sizeof(struct AuthCacheRecordStruct);
   *vecptr++ = sizeof(struct AuthConfigStruct);
   *vecptr++ = sizeof(struct AuthGblSecStruct);
   *vecptr++ = sizeof(struct AuthHtaRecordStruct);
   *vecptr++ = sizeof(struct AuthPathRecordStruct);
   *vecptr++ = sizeof(struct AuthRealmRecordStruct);
   *vecptr++ = sizeof(struct BodyProcessStruct);
   *vecptr++ = sizeof(struct CacheStruct);
   *vecptr++ = sizeof(struct DclTaskStruct);
   *vecptr++ = sizeof(struct DclScriptNameCacheStruct);
   *vecptr++ = sizeof(struct DirTaskStruct);
   *vecptr++ = sizeof(struct DECnetConnectStruct);
   *vecptr++ = sizeof(struct DECnetTaskStruct);
   *vecptr++ = sizeof(struct Http2Struct);
   *vecptr++ = sizeof(struct Http2StreamStruct);
   *vecptr++ = sizeof(struct Http2WriteStruct);
   *vecptr++ = sizeof(HTTPD_GBLSEC);
   *vecptr++ = sizeof(struct MonRequestStruct);
   *vecptr++ = sizeof(struct NetIoStruct);
   *vecptr++ = sizeof(struct OdsStruct);
   *vecptr++ = sizeof(struct ProxyTaskStruct);
   *vecptr++ = sizeof(PROXY_ACCOUNTING_STRUCT);
   *vecptr++ = sizeof(struct ProxyVerifyGblSecStruct);
   *vecptr++ = sizeof(struct PutTaskStruct);
   *vecptr++ = sizeof(struct RequestStruct);
   *vecptr++ = sizeof(struct ServiceStruct);
   *vecptr++ = SesolaGblSecStructSize;
   *vecptr++ = sizeof(struct SsiTaskStruct);
   *vecptr++ = sizeof(struct StrDscStruct);
   *vecptr++ = sizeof(struct UpdTaskStruct);
   *vecptr++ = sizeof(struct VmStruct);
   *vecptr++ = sizeof(struct WebDavTaskStruct);

   FaoCheck (sizeof(FaoVector), &FaoVector, vecptr, FI_LI);
   status = FaolToNet (rqptr, ResponseFao, &FaoVector);
   if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);

   ResponseHeader200 (rqptr, "text/plain", &rqptr->NetWriteBufferDsc);

   AdminEnd (rqptr);

#else /* WATCH_MOD */

   rqptr->rqResponse.HttpStatus = 403;
   ErrorGeneral (rqptr, ErrorWatchNoModule, FI_LI);
   AdminEnd (rqptr);

#endif /* WATCH_MOD */
}

/*****************************************************************************/
/*
Time stamp 0..9 provides duration in milli-seconds since previous.
Development purposes only.
*/ 

void WatchTimeStamp (int which, char *module, int line)

{
   static int  LastStamp = 0;
   static int64  BinStamp64 [10];

   int64  time64;

   if (which < 0 || which > 9)
      WatchThis (WATCHALL, WATCH_INTERNAL, "+++++!UL 0..9 only!! !AZ:!UL",
                 which, module, line);
   else
   {
      sys$gettim (&BinStamp64[which]);
      if (which > 0)
      {
         time64 = (BinStamp64[which] - BinStamp64[which-1]) / 10000;
         WatchThis (WATCHALL, WATCH_INTERNAL, "+++++!UL !@SQ mSec !AZ:!UL",
                    which, &time64, module, line);
         LastStamp = which;
      }
      else
      if (LastStamp > 0)
      {
         time64 = (BinStamp64[0] - BinStamp64[LastStamp]) / 10000;
         WatchThis (WATCHALL, WATCH_INTERNAL, "+++++0 !@SQ mSec !AZ:!UL",
                    &time64, module, line);
         LastStamp = 0;
      }
   }
}

/*****************************************************************************/
/*
Cheap and nasty debug aide.
Just insert WATCHMOLI; at places you want a time/module/line stamp.
*/ 

void WatchModuleLine (char *module, int line)

{
   static char  TimeNow [16];
   static $DESCRIPTOR (TimeNowDsc, TimeNow);
   static $DESCRIPTOR (TimeFaoDsc, "!%T\0");

   TimeNowDsc.dsc$w_length = sizeof(TimeNow);
   sys$fao (&TimeFaoDsc, NULL, &TimeNowDsc, 0);
   fprintf (stdout, "+++++ %s %s:%d +++++\n", TimeNow, module, line);
   fsync (STDOUT_FILENO);
}

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

