[0001]
[0002]
[0003]
[0004]
[0005]
[0006]
[0007]
[0008]
[0009]
[0010]
[0011]
[0012]
[0013]
[0014]
[0015]
[0016]
[0017]
[0018]
[0019]
[0020]
[0021]
[0022]
[0023]
[0024]
[0025]
[0026]
[0027]
[0028]
[0029]
[0030]
[0031]
[0032]
[0033]
[0034]
[0035]
[0036]
[0037]
[0038]
[0039]
[0040]
[0041]
[0042]
[0043]
[0044]
[0045]
[0046]
[0047]
[0048]
[0049]
[0050]
[0051]
[0052]
[0053]
[0054]
[0055]
[0056]
[0057]
[0058]
[0059]
[0060]
[0061]
[0062]
[0063]
[0064]
[0065]
[0066]
[0067]
[0068]
[0069]
[0070]
[0071]
[0072]
[0073]
[0074]
[0075]
[0076]
[0077]
[0078]
[0079]
[0080]
[0081]
[0082]
[0083]
[0084]
[0085]
[0086]
[0087]
[0088]
[0089]
[0090]
[0091]
[0092]
[0093]
[0094]
[0095]
[0096]
[0097]
[0098]
[0099]
[0100]
[0101]
[0102]
[0103]
[0104]
[0105]
[0106]
[0107]
[0108]
[0109]
[0110]
[0111]
[0112]
[0113]
[0114]
[0115]
[0116]
[0117]
[0118]
[0119]
[0120]
[0121]
[0122]
[0123]
[0124]
[0125]
[0126]
[0127]
[0128]
[0129]
[0130]
[0131]
[0132]
[0133]
[0134]
[0135]
[0136]
[0137]
[0138]
[0139]
[0140]
[0141]
[0142]
[0143]
[0144]
[0145]
[0146]
[0147]
[0148]
[0149]
[0150]
[0151]
[0152]
[0153]
[0154]
[0155]
[0156]
[0157]
[0158]
[0159]
[0160]
[0161]
[0162]
[0163]
[0164]
[0165]
[0166]
[0167]
[0168]
[0169]
[0170]
[0171]
[0172]
[0173]
[0174]
[0175]
[0176]
[0177]
[0178]
[0179]
[0180]
[0181]
[0182]
[0183]
[0184]
[0185]
[0186]
[0187]
[0188]
[0189]
[0190]
[0191]
[0192]
[0193]
[0194]
[0195]
[0196]
[0197]
[0198]
[0199]
[0200]
[0201]
[0202]
[0203]
[0204]
[0205]
[0206]
[0207]
[0208]
[0209]
[0210]
[0211]
[0212]
[0213]
[0214]
[0215]
[0216]
[0217]
[0218]
[0219]
[0220]
[0221]
[0222]
[0223]
[0224]
[0225]
[0226]
[0227]
[0228]
[0229]
[0230]
[0231]
[0232]
[0233]
[0234]
[0235]
[0236]
[0237]
[0238]
[0239]
[0240]
[0241]
[0242]
[0243]
[0244]
[0245]
[0246]
[0247]
[0248]
[0249]
[0250]
[0251]
[0252]
[0253]
[0254]
[0255]
[0256]
[0257]
[0258]
[0259]
[0260]
[0261]
[0262]
[0263]
[0264]
[0265]
[0266]
[0267]
[0268]
[0269]
[0270]
[0271]
[0272]
[0273]
[0274]
[0275]
[0276]
[0277]
[0278]
[0279]
[0280]
[0281]
[0282]
[0283]
[0284]
[0285]
[0286]
[0287]
[0288]
[0289]
[0290]
[0291]
[0292]
[0293]
[0294]
[0295]
[0296]
[0297]
[0298]
[0299]
[0300]
[0301]
[0302]
[0303]
[0304]
[0305]
[0306]
[0307]
[0308]
[0309]
[0310]
[0311]
[0312]
[0313]
[0314]
[0315]
[0316]
[0317]
[0318]
[0319]
[0320]
[0321]
[0322]
[0323]
[0324]
[0325]
[0326]
[0327]
[0328]
[0329]
[0330]
[0331]
[0332]
[0333]
[0334]
[0335]
[0336]
[0337]
[0338]
[0339]
[0340]
[0341]
[0342]
[0343]
[0344]
[0345]
[0346]
[0347]
[0348]
[0349]
[0350]
[0351]
[0352]
[0353]
[0354]
[0355]
[0356]
[0357]
[0358]
[0359]
[0360]
[0361]
[0362]
[0363]
[0364]
[0365]
[0366]
[0367]
[0368]
[0369]
[0370]
[0371]
[0372]
[0373]
[0374]
[0375]
[0376]
[0377]
[0378]
[0379]
[0380]
[0381]
[0382]
[0383]
[0384]
[0385]
[0386]
[0387]
[0388]
[0389]
[0390]
[0391]
[0392]
[0393]
[0394]
[0395]
[0396]
[0397]
[0398]
[0399]
[0400]
[0401]
[0402]
[0403]
[0404]
[0405]
[0406]
[0407]
[0408]
[0409]
[0410]
[0411]
[0412]
[0413]
[0414]
[0415]
[0416]
[0417]
[0418]
[0419]
[0420]
[0421]
[0422]
[0423]
[0424]
[0425]
[0426]
[0427]
[0428]
[0429]
[0430]
[0431]
[0432]
[0433]
[0434]
[0435]
[0436]
[0437]
[0438]
[0439]
[0440]
[0441]
[0442]
[0443]
[0444]
[0445]
[0446]
[0447]
[0448]
[0449]
[0450]
[0451]
[0452]
[0453]
[0454]
[0455]
[0456]
[0457]
[0458]
[0459]
[0460]
[0461]
[0462]
[0463]
[0464]
[0465]
[0466]
[0467]
[0468]
[0469]
[0470]
[0471]
[0472]
[0473]
[0474]
[0475]
[0476]
[0477]
[0478]
[0479]
[0480]
[0481]
[0482]
[0483]
[0484]
[0485]
[0486]
[0487]
[0488]
[0489]
[0490]
[0491]
[0492]
[0493]
[0494]
[0495]
[0496]
[0497]
[0498]
[0499]
[0500]
[0501]
[0502]
[0503]
[0504]
[0505]
[0506]
[0507]
[0508]
[0509]
[0510]
[0511]
[0512]
[0513]
[0514]
[0515]
[0516]
[0517]
[0518]
[0519]
[0520]
[0521]
[0522]
[0523]
[0524]
[0525]
[0526]
[0527]
[0528]
[0529]
[0530]
[0531]
[0532]
[0533]
[0534]
[0535]
[0536]
[0537]
[0538]
[0539]
[0540]
[0541]
[0542]
[0543]
[0544]
[0545]
[0546]
[0547]
[0548]
[0549]
[0550]
[0551]
[0552]
[0553]
[0554]
[0555]
[0556]
[0557]
[0558]
[0559]
[0560]
[0561]
[0562]
[0563]
[0564]
[0565]
[0566]
[0567]
[0568]
[0569]
[0570]
[0571]
[0572]
[0573]
[0574]
[0575]
[0576]
[0577]
[0578]
[0579]
[0580]
[0581]
[0582]
[0583]
[0584]
[0585]
[0586]
[0587]
[0588]
[0589]
[0590]
[0591]
[0592]
[0593]
[0594]
[0595]
[0596]
[0597]
[0598]
[0599]
[0600]
[0601]
[0602]
[0603]
[0604]
[0605]
[0606]
[0607]
[0608]
[0609]
[0610]
[0611]
[0612]
[0613]
[0614]
[0615]
[0616]
[0617]
[0618]
[0619]
[0620]
[0621]
[0622]
[0623]
[0624]
[0625]
[0626]
[0627]
[0628]
[0629]
[0630]
[0631]
[0632]
[0633]
[0634]
[0635]
[0636]
[0637]
[0638]
[0639]
[0640]
[0641]
[0642]
[0643]
[0644]
[0645]
[0646]
[0647]
[0648]
[0649]
[0650]
[0651]
[0652]
[0653]
[0654]
[0655]
[0656]
[0657]
[0658]
[0659]
[0660]
[0661]
[0662]
[0663]
[0664]
[0665]
[0666]
[0667]
[0668]
[0669]
[0670]
[0671]
[0672]
[0673]
[0674]
[0675]
[0676]
[0677]
[0678]
[0679]
[0680]
[0681]
[0682]
[0683]
[0684]
[0685]
[0686]
[0687]
[0688]
[0689]
[0690]
[0691]
[0692]
[0693]
[0694]
[0695]
[0696]
[0697]
[0698]
[0699]
[0700]
[0701]
[0702]
[0703]
[0704]
[0705]
[0706]
[0707]
[0708]
[0709]
[0710]
[0711]
[0712]
[0713]
[0714]
[0715]
[0716]
[0717]
[0718]
[0719]
[0720]
[0721]
[0722]
[0723]
[0724]
[0725]
[0726]
[0727]
[0728]
[0729]
[0730]
[0731]
[0732]
[0733]
[0734]
[0735]
[0736]
[0737]
[0738]
[0739]
[0740]
[0741]
[0742]
[0743]
[0744]
[0745]
[0746]
[0747]
[0748]
[0749]
[0750]
[0751]
[0752]
[0753]
[0754]
[0755]
[0756]
[0757]
[0758]
[0759]
[0760]
[0761]
[0762]
[0763]
[0764]
[0765]
[0766]
[0767]
[0768]
[0769]
[0770]
[0771]
[0772]
[0773]
[0774]
[0775]
[0776]
[0777]
[0778]
[0779]
[0780]
[0781]
[0782]
[0783]
[0784]
[0785]
/*****************************************************************************/
/*
                                sMONitor.c

Pronounced: ess-monitor, ess-mon-ee-tar, ess-moan-ar-ter ... whatever

A CGI executable, browser viewable, VMS MONITOR utility executing in a PTD
subprocess with the VT100/ANSI screen sequences converted into HTML.

Up to six (pragmatic number only) such MONITOR teeminals can be displayed on a
single browser page, demonstrating the ability to do just that.

And of course, once set up, a particular configuration can be bookmarked.

Also see SCREPER.C code.

As with all applications providing insights into systems, potentially to
external parties, sMONitor requires server authorisation.  If access for the
great unwashed is desired, define the logical name SMONITOR_REMOTE_USER to
anything at all.


HOW IT WORKS
------------
This was really just an exercise in screen-scraping the output of a "regular"
command-line application onto a browser HTML page.  The script uses JavaScript
and the HTML DOM to build and populate that page.

The script has two essential modes of operation (ignoring the form page).

   1) build the browser page with display element
   2) populate the display element with output from the CLI application

The /build/ mode provides a browser page with the basic HTML/CSS/JavaScript
resources.  Inside that page an XMLHttpRequest() initiates the execution of
another instance of the script this time with a query-string.

The query-string /populate=1/ initiates the mode where the script uses the
pseudo-terminal driver (PTD) to create a terminal session and then spawn a
subprocess attached to that terminal (see SCREPER.C).  In that subprocess the
CLI application (in this case HTTPDmon) is invoked and its output to the
pseudo-terminal processed (scraped) to be HTML-escaped and otherwise munged for
representative display in the display element.  This munged output is
transmitted back to the browser XMLHttpRequest() in these chunks.  Each chunk
is parsed from the total output and displayed as that chunk.


COPYRIGHT
---------
Copyright (C) 2021 Mark G.Daniel

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

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

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


VERSION LOG
-----------
01-AUG-2021  MGD  initial
*/
/*****************************************************************************/

#define SOFTWAREVN "v1.0.0"
#define SOFTWARENM "SMONITOR"
#ifdef __ALPHA
#  define SOFTWAREID SOFTWARENM " " SOFTWAREVN " AXP"
#endif
#ifdef __ia64
#  define SOFTWAREID SOFTWARENM " " SOFTWAREVN " IA64"
#endif
#ifdef __x86_64
#  define SOFTWAREID SOFTWARENM " " SOFTWAREVN " X86"
#endif

#include <ctype.h>
#include <descrip.h>
#include <errno.h>
#include <fcntl.h>
#include <in.h>
#include <ints.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <stat.h>
#include <unistd.h>
#include <unixio.h>

#include <dvidef.h>
#include <iodef.h>
#include <iosbdef.h>
#include <jpidef.h>
#include <libdef.h>
#include <lib$routines.h>
#include <starlet.h>
#include <ssdef.h>
#include <syidef.h>

#include "screper.h"

#define FI_LI "SHTTPMON",__LINE__

#ifndef UINT64PTR
/* mainly to allow easy use of the __unaligned directive */
#define UINTPTR __unaligned unsigned int*
#define ULONGPTR __unaligned unsigned long*
#define USHORTPTR __unaligned unsigned short*
#define UINT64PTR __unaligned uint64*
#define INT64PTR __unaligned int64*
#endif

#define FONT_DEFAULT 11
#define INTERVAL_DEFAULT 3

#define MON_MAX 2*3

int  begun,
     inspect,
     fontSize;

char  *CgiQueryString,
      *CgiRemoteUser,
      *CgiRequestUri,
      *CgiScriptName,
      *CgiServerSoftware;

static char  ScriptName [64],
             SyiNodeName [16];

char  *MonClass [] = {
"CLUSTER",
"DECNET",
"DISK",
"DLOCK",
"IO",
"FCP",
"LOCK",
"FILE_SYSTEM_CACHE",
"MODES",
"MSCP_SERVER",
"PAGE",
"PROCESSES",
"RLOCK",
"RMS",
"SCS",
"STATES",
"SYSTEM",
"TIMER",
"TRANSACTION",
"VECTOR",
NULL
};

char  *MonQual [] = {
"/ALL",
"/AVERAGE",
"/CURRENT",
"/MAXIMUM",
"/MINIMUM",
"/TOPBIO",
"/TOPCPU",
"/TOPDIO",
"/TOPEXEC",
"/TOPFAULT",
"/TOPKERNEL",
"/TOPSUPER",
"/TOPUSER",
NULL
};

char  *MonStyle =
"<style type=\"text/css\">\n\
body { font-family:arial,verdana,helvetica,sans; font-size:11pt; \
background-color:white; color:black; margin:1em; }\n\
h1 { font-size:1.3em; }\n\
iframe { border:dotted gray 1px; }\n\
table.radio { display:inline; text-align:center; }\n\
table.radio tr { line-height:0.2em; }\n\
.node { padding:0.4em 0 0.3em 0; }\n\
.qual { padding-left:1.5em; }\n\
.select { font-size:9pt; }\n\
.select th { padding-left:2em; }\n\
.problem { color:red; }\n\
</style>\n";

/* prototypes */
void sMonitorBuild ();
int sMonitorCount ();
void sMonitorExit (int, int);
int sMonitorNumber (int);
void sMonitorPage ();
void sMonitorPopulate ();
void sMonitorTerminal (int);
void sMonitorSelect ();
int sMonitorUrlDecode (char*);

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

int main (int argc, char *argv[])

{
   static unsigned long  JpiPidItem = JPI$_PID;
   static unsigned long  SyiNodeNameItem = SYI$_NODENAME;
   static $DESCRIPTOR (SyiNodeNameDsc, SyiNodeName);

   int  interval, number, page;
   ushort  slen;
   char  *cptr, *sptr;

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

   if (argc > 1)
   {
      if (!strncasecmp (argv[1], "/VERSION", 4))
         fprintf (stdout, "%s\n%s\n", SOFTWAREID, ScreperDo(NULL, "-version"));
      exit (SS$_NORMAL);
   }

   lib$getsyi (&SyiNodeNameItem, 0, &SyiNodeNameDsc, &slen, 0, 0);
   SyiNodeName[slen] = '\0';

   if (!(CgiServerSoftware = getenv ("WWW_SERVER_SOFTWARE")))
      CgiServerSoftware = getenv ("SERVER_SOFTWARE");

   if (!CgiServerSoftware) sMonitorExit (SS$_NORMAL, __LINE__);

   if (cptr = getenv ("SMONITOR_INSPECT"))
      inspect = atol (cptr);

   if (!(CgiRequestUri = getenv ("WWW_REQUEST_URI")))
      if (!(CgiRequestUri = getenv ("REQUEST_URI")))
         CgiRequestUri = "";

   if (!(CgiScriptName = getenv ("WWW_SCRIPT_NAME")))
      if (!(CgiScriptName = getenv ("SCRIPT_NAME")))
         CgiScriptName = "";

   if (!(CgiQueryString = getenv ("WWW_QUERY_STRING")))
      if (!(CgiQueryString = getenv ("QUERY_STRING")))
         CgiQueryString = "";
   CgiQueryString = strdup (CgiQueryString);
   sMonitorUrlDecode (CgiQueryString);

   strcpy (ScriptName, "sMONitor"); 

   if (!(CgiRemoteUser = getenv ("SMONITOR_REMOTE_USER")))
      if (!(CgiRemoteUser = getenv ("WWW_REMOTE_USER")))
         if (!(CgiRemoteUser = getenv ("REMOTE_USER")))
            CgiRemoteUser = "";

   if (!*CgiRemoteUser)
   {
      fprintf (stdout,
"Status: 403 Forbidden\r\n\
Content-Type: text/html\r\n\
\r\n\
<!DOCTYPE html>\n\
<html>\n\
<head>\n\
<meta name=\"generator\" content=\"%s\">\n\
<title>%s:: %s</title>\n\
%s\
</head>\n\
<body>\n\
<pre><span class=\"problem\">authorization failure</span></pre>\n\
</body>\n\
</html>\n",
               SOFTWAREID, SyiNodeName, ScriptName, MonStyle);
      sMonitorExit (SS$_NORMAL, __LINE__);
   }

   if (*CgiQueryString)
   {
   if (!(stdout = freopen ("SYS$OUTPUT", "w", stdout, "ctx=bin")))
      sMonitorExit (vaxc$errno, __LINE__);

      fontSize = FONT_DEFAULT;
      if (cptr = strstr (CgiQueryString, "font="))
         fontSize = atoi(cptr+5);
      if (fontSize < 8 || fontSize >= 18) fontSize = FONT_DEFAULT;

      if (strstr (CgiQueryString, "populate="))
         sMonitorPopulate ();
      else
         sMonitorPage ();
   }
   else
      sMonitorSelect ();

   sMonitorExit (SS$_NORMAL, __LINE__);
}

/*****************************************************************************/
/*
Provide a form controlling the various MONITOR parameters.
*/

void sMonitorSelect ()

{
   static unsigned long  SyiClusterMember;
   static unsigned long  SyiClusterMemberItem = SYI$_CLUSTER_MEMBER;
   static char  *selected = " checked";

   int  idx, count, font = 11, interval = 6;
   char  *cptr, *dptr, *sptr;

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

   lib$getsyi (&SyiClusterMemberItem, &SyiClusterMember, 0, 0, 0, 0);

   begun = 1;

   fprintf (stdout,
"Status: 200 OK\r\n\
Content-Type: text/html\r\n\
Script-Control: X-stream-mode=1\r\n\
\r\n\
<!DOCTYPE html>\n\
<html>\n\
<head>\n\
<meta name=\"generator\" content=\"%s\">\n\
<title>%s:: %s</title>\n\
%s\
</head>\n\
<body>\n\
<h1>sMONitor</h1>\n\
<form method=\"GET\" action=\"%s\" \
content-type=\"application/x-www-form-urlencoded\">\n\
<table class=\"select\">\n",
            SOFTWAREID, SyiNodeName, ScriptName, MonStyle,
            CgiScriptName);

   /* prefix allows to differentiate between LOCK, DLOCK, RLOCK */
   fprintf (stdout, "<input type=\"hidden\" name=\"classes\", value=\"\">\n");

   fprintf (stdout, "<tr style=\"text-align:left;\">");
   for (count = 1; count <= MON_MAX; count++)
      fprintf (stdout, "<th>%d</th>", count);
   fprintf (stdout, "</tr>\n");

   fprintf (stdout, "<tr><td colspan=\"%d\" style=\"border-bottom:"
                    "solid gray 1px;\"></td></tr>\n", MON_MAX);

   for (idx = 0; cptr = MonClass[idx]; idx++)
   {
      fprintf (stdout, "<tr>\n");
      for (count = 1; count <= MON_MAX; count++)
      {
         if (!strcmp(cptr,"RMS"))
            sptr = " disabled";
         else
         if (count == 1 && !strcmp(cptr,"SYSTEM"))
            sptr = " checked";
         else
            sptr = "";
         fprintf (stdout, "<td><input type=\"checkbox\" name=\"%s\" "
                          "value=\"%d\"%s>&nbsp;%s</td>\n",
                          cptr, count, sptr, cptr);
      }
      fprintf (stdout, "</tr>\n");
   }

   fprintf (stdout, "<tr><td colspan=\"%d\" style=\"border-bottom:"
                    "dotted gray 1px;\"></td></tr>\n", MON_MAX);

   for (idx = 0; cptr = MonQual[idx]; idx++)
   {
      fprintf (stdout, "<tr>\n");
      for (count = 1; count <= MON_MAX; count++)
         fprintf (stdout, "<td class=\"qual\">"
                          "<input type=\"checkbox\" name=\"%s\" "
                          "value=\"%d\">&nbsp;%s</td>\n",
                          cptr, count, cptr);
      fprintf (stdout, "</tr>\n");
   }

   fprintf (stdout, "<tr><td colspan=\"%d\" style=\"border-bottom:"
                    "dotted gray 1px;\"></td></tr>\n", MON_MAX);

   fprintf (stdout, "<tr>\n");
   for (count = 1; count <= MON_MAX; count++)
      fprintf (stdout, "<td class=\"node%s\">%s"
                       "<input type=\"text\" size=\"8\" name=\"NODE%d\" "
                       "value=\"\"%s></td>\n",
                       count == 1 ? " " : " qual",
                       count == 1 ? "/NODE=&nbsp;" : "",
                       count,
                       SyiClusterMember ? "" : " disabled");
   fprintf (stdout, "</tr>\n");

   fprintf (stdout, "<tr><td colspan=\"%d\" style=\"border-bottom:"
                    "solid gray 1px;\"></td></tr>\n", MON_MAX);

   fprintf (stdout,
"<tr><td>&nbsp;</td></tr>\n\
<tr><td colspan=\"2\">\n\
<table class=\"radio\">\n\
<tr><td style=\"text-align:right;\">Interval:</td>\n\
<td>2</td><td>4</td><td>6</td><td>10</td><td>20</td></tr>\n\
<tr><td></td>\
<td><input type=\"radio\" name=\"interval\" value=\"2\"%s></td>\
<td><input type=\"radio\" name=\"interval\" value=\"4\"%s></td>\
<td><input type=\"radio\" name=\"interval\" value=\"6\"%s></td>\
<td><input type=\"radio\" name=\"interval\" value=\"10\"%s></td>\
<td><input type=\"radio\" name=\"interval\" value=\"20\"%s></td>\
</tr>\
</table>\n\
</td></tr>\n\
\
<tr><td></td></tr>\n\
<tr><td colspan=\"2\">\n\
<table class=\"radio\">\n\
<tr><td style=\"text-align:right;padding-left:1.4em;\">Font:</td>\n\
<td>8</td><td>10</td><td>11</td><td>12</td><td>14</td></tr>\n\
<tr><td></td>\
<td><input type=\"radio\" name=\"font\" value=\"8\"%s></td>\
<td><input type=\"radio\" name=\"font\" value=\"10\"%s></td>\
<td><input type=\"radio\" name=\"font\" value=\"11\"%s></td>\
<td><input type=\"radio\" name=\"font\" value=\"12\"%s></td>\
<td><input type=\"radio\" name=\"font\" value=\"14\"%s></td>\
</tr>\
</table>\n\
</td></tr>\n",
            interval == 2 ? selected : "",
            interval == 4 ? selected : "",
            interval == 6 ? selected : "",
            interval == 10 ? selected : "",
            interval == 20 ? selected : "",
            font == 8 ? selected : "",
            font == 10 ? selected : "",
            font == 11 ? selected : "",
            font == 12 ? selected : "",
            font == 14 ? selected : "");

   fprintf (stdout,
"</table>\n\
<p><input type=\"submit\" name=\"monitor\" value=\"MONITOR\">&nbsp;&nbsp;\
<input type=\"reset\" name=\"default\" value=\"default\">\n\
</form>\n");

   fprintf (stdout,
"</body>\n\
</html>\n");

   sMonitorExit (SS$_NORMAL, __LINE__);
}

/*****************************************************************************/
/*
A page with multiple terminal screen outputs, each in an iframe.
If a single screen then sMonitorTerminal() provides that directly.
*/

void sMonitorPage ()

{
   int  number;
   char  *cptr;

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

   if ((number = sMonitorCount()) <= 1)
   {
      sMonitorTerminal (1);
      return;
   }

   number = 0;
   if (cptr = strstr (CgiQueryString, "&number="))
      number = atoi (cptr+8);
   if (number >= 1 && number <= MON_MAX)
   {
      sMonitorTerminal (number);
      return;
   }

   begun = 1;

   fprintf (stdout,
"Status: 200 OK\r\n\
Content-Type: text/html\r\n\
Script-Control: X-stream-mode=1\r\n\
\r\n\
<!DOCTYPE html>\n\
<html>\n\
<head>\n\
<meta name=\"generator\" content=\"%s\">\n\
<title>%s:: %s</title>\n\
%s\
%s\
</head>\n\
<body>\n",
            SOFTWAREID, SyiNodeName, ScriptName,
            ScreperDo(NULL,"-resize"), MonStyle);

   fprintf (stdout,
"<div style=\"display:inline-flex; flex-wrap:wrap;\">\n");

   for (number = 1; number <= MON_MAX; number++)
      if (sMonitorNumber (number))
         fprintf (stdout,
"<iframe id=\"monitor%d\" src=\"%s&number=%d\" scrolling=\"no\">\n\
</iframe>\n",
                  number, CgiRequestUri, number);

   fprintf (stdout,
"</div>\n\
%s\
</body>\n\
</html>\n", ScreperDo(NULL,"-pause"));

   sMonitorExit (SS$_NORMAL, __LINE__);
}

/*****************************************************************************/
/*
A page with the embedded terminal screen output.
If multiple concurrent monitors then this will be in an iframe.
*/

void sMonitorTerminal (int number)

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

   begun = 1;

   fprintf (stdout,
"Status: 200 OK\r\n\
Content-Type: text/html\r\n\
Script-Control: X-stream-mode=1\r\n\
\r\n\
<!DOCTYPE html>\n\
<html>\n\
<head>\n\
<meta name=\"generator\" content=\"%s\">\n\
<title>%s:: %s</title>\n\
%s\
%s\
</head>\n\
<body onload=\"doScreper()\">\n\
%s\
</body>\n\
</html>\n",
            SOFTWAREID, SyiNodeName, ScriptName,
            ScreperDo (NULL, "-css"),
            ScreperDo (NULL, "-javascript"),
            ScreperDo (NULL, "-screen"));

   sMonitorExit (SS$_NORMAL, __LINE__);
}

/*****************************************************************************/
/*
Spawn the command subprocess and provide the HTML-ified, scraped terminal
screen output back to the embedded virtual terminal.
*/

void sMonitorPopulate ()

{
   int  cnt, idx, interval, number, status;
   char  *aptr, *cptr, *rptr, *sptr;
   char  scratch [64],
         scrdo [512];
   void  *scrptr;

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

   begun = 1;

   interval = INTERVAL_DEFAULT;
   if (cptr = strstr (CgiQueryString, "interval="))
   {
      interval = atoi(cptr+9);
      if (interval < 2 || interval > 60) interval = INTERVAL_DEFAULT;
   }

   number = 0;
   if (cptr = strstr (CgiQueryString, "number="))
      number = atoi (cptr+7);
   if (number < 1 || number > MON_MAX) number = 1;

   sptr += sprintf (sptr = scrdo,
"-inspect=%d -utility=\"%s\" -page=24 -font=%d -snapshot=%d -dcl=",
                    inspect, "sMONitor", fontSize, (interval * 10) - 5);

   sptr += sprintf (sptr, "MONITOR ", interval);
   for (idx = cnt = 0; cptr = MonClass[idx]; idx++)
   {
      /* need to differentiate between LOCK, DLOCK, RLOCK */
      sprintf (scratch, "&%s=%d", cptr, number);
      if (strstr (CgiQueryString, scratch))
         sptr += sprintf (sptr, "%s%s", cnt++ ? "," : "", MonClass[idx]);
   }
   if (!cnt) strcat (sptr, "SYSTEM");
   while (*sptr) sptr++;

   for (idx = 0; cptr = MonQual[idx]; idx++)
   {
      sprintf (scratch, "&%s=%d", cptr, number);
      if (strstr (CgiQueryString, scratch))
         sptr += sprintf (sptr, " %s", MonQual[idx]);
   }

   sprintf (scratch, "&NODE%d=", number);
   if (cptr = strstr (CgiQueryString, scratch))
   {
      while (*cptr && *cptr != '=') cptr++;
      if (*cptr) cptr++;
      for (aptr = cptr; *cptr && *cptr != '&'; cptr++);
      if (cptr - aptr > 0)
         sptr += sprintf (sptr, " /NODE=%*.*s", cptr-aptr, cptr-aptr, aptr);
   }

   sptr += sprintf (sptr, " /INTERVAL=%d ", interval);

   /* facilitate development - no referer then is populate=1 directly */
   if (!(rptr = getenv ("WWW_HTTP_REFERER")))
      rptr = getenv ("HTTP_REFERER");

   if (inspect && rptr)
      fputs ("Status: 200 OK\r\n\
Content-Type: text/plain\r\n\
Script-Control: X-record-mode=1\r\n\
\r\n", stdout);
   else
      fputs ("Status: 200 OK\r\n\
Content-Type: text/html\r\n\
Script-Control: X-stream-mode=1\r\n\
Script-Control: X-timeout-output=none\r\n\
Script-Control: X-content-encoding-gzip=0\r\n\
\r\n", stdout);

   if (inspect) printf ("|%s|\n|%s|\n", CgiQueryString, scrdo);

   fflush (stdout);

   if (!rptr)
      /* no referer then is populate=1 directly */
      fprintf (stdout, "<!DOCTYPE html>\n<html>\n%s<body>\n<pre>\n",
               ScreperDo (NULL, "-css"));
 
   scrptr = ScreperInit ();
   sptr = ScreperDo (scrptr, scrdo);
   if (*(USHORTPTR)sptr == '%X')
      status = strtol (sptr+2, NULL, 16);
   else
      status = SS$_BUGCHECK;
   if (status & 1) sys$hiber();
   sMonitorExit (status, __LINE__);
}

/*****************************************************************************/
/*
By parsing the query string, return true or false if the specified terminal
number is present.
*/

int sMonitorNumber (int number)

{
   int  idx;
   char  *cptr;
   char  class [64];

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

   for (idx = 0; cptr = MonClass[idx]; idx++)
   {
      sprintf (class, "&%s=%d", cptr, number);
      if (strstr (CgiQueryString, class)) return (1);
   }
   return (0);
}

/*****************************************************************************/
/*
By parsing the query string, return the number of individual monitor terminals.
*/

int sMonitorCount ()

{
   int  count, number;

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

   count = 0;
   for (number = 1; number <= MON_MAX; number++)
      if (sMonitorNumber (number)) count++;
   return (count);
}

/*****************************************************************************/
/*
Perform in-place URL decode.  Perform strdup() before calling to retain
original then free() when finished using, as required.
*/

int sMonitorUrlDecode (char *string)

{
   uint  ch;
   char  *cptr, *sptr;

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

   for (cptr = sptr = string; *cptr; cptr++)
   {
      if (*cptr == '+')
         *sptr++ = ' ';
      else
      if (*cptr == '%')
      {
         *sptr = '\0';
         cptr++;
         if (*(cptr+1))
         {
            if (sscanf (cptr, "%02x", &ch) < 1) return (0);
            *sptr++ = ch;
         }
         else
            return (0);
         cptr++;
      }
      else
         *sptr++ = *cptr;
   }
   *sptr = '\0';
   return (sptr - string);
}

/*****************************************************************************/
/*
Provide some WATCHable exit information.
*/

void sMonitorExit (int status, int line)

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

   if (!begun)
      fputs ("Status: 200 OK\r\n\
Content-Type: text/plain\r\n\
Script-Control: X-record-mode=1\r\n\
\r\n", stdout);

    fprintf (stdout, "\n<!-- sMONitor:%d %%X%08.08X -->\n", line, status);

    exit (status);
}

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