/*****************************************************************************/
/*
                                   hdisk.c

This application links to the Gift.c application's GIF and graphic code!
Functions begining Gift...() and storage beginning Graphic... is Gift.c code.

HyperDisk!  Provides disk details, including a usage bar graph, for one or
more disks specified as part of the path or as a CGI form variable "DISKS=".
Disk device names may be specified separated by commas, forward-slashes, or
(probably most simply) just with a full-colon (which is used by VMS as a
device name separator).  Disk details include blocks and Mbytes total, used
and free, as well as error count.  Component devices of volumes sets are
displayed individually.

Variations allowed in specifying disks:

   http://host/disk/device
   http://host/disk/device1:device2:device3
   http://host/disk/device1,device2,device3
   http://host/disk/device1/device2/device3
   http://host/disk?disk=device
   http://host/disk?disk=device1:device2:device3
   http://host/disk?disk=device1,device2,device3
   http://host/disk?disk=device1/device2/device3

If the string "graphiconly" occurs in the query string only a bar-graph GIF
for the specified device is generated and sent.  This allows documents to
include a tag such as <IMG SRC="/disk/sys$sysdevice?graphiconly"> to include
the usage bar-graph for any given device (of course only one may be specified
with each use).

Page colouration and "button" labels may be specified via the appropriate
command-line qualifiers (or corresponding logical name). Defaults for any not
specified.  Specifiy as /WHATEVER="" to NOT specify the corresponding colour
(i.e. leave it to the browser).  See "Qualifiers" section below, and also about
the logical name "HDISK$PARAM".

An example of changing the page colour to white and the banner to red!

  /PBGCOLOR="#ffffff" /PHBGCOLOR="#ff0000"

The script can format a page in either of two layouts.

  1. Tables are used to create a coloured header and button bar (DEFAULT).
     Default colours are white page with grey heading and button outlines.
  2. Textual header, horizontal rules and a textual button bar.
     No default colours.


ACKNOWLEDGEMENT
---------------
Although the graphic generation and GIF header code is mine the actual
GIF compression code employed is derived from the PBM suite, is copyright,
and used within the owner's guidelines:

 * GIF Image compression - LZW algorithm implemented with Trie type
 *                         structure.
 *                         Written by Bailey Brown, Jr.
 *                         last change May 24, 1990
 *                         file: compgif.c
 *
 *  You may use or modify this code as you wish, as long as you mention
 *  my name in your documentation.
 *
 *                  - Bailey Brown, Jr.


QUALIFIERS
----------
/DBUG           turns on all "if (Debug)" statements
/PBACKGROUND=   <body> background image path
/PBGCOLOR=      <body> background colour
/PBBGCOLOR=     button background color
/PBBORDER=      width of button border
/PHBGCOLOR=     heading background color
/PHBORDER=      width of heading and button-bar border
/PHLOCAL=       local information to be included in header
/PHTEXT=        heading text colour
/PLAYOUT=       1 is coloured header & buttons, 2 is text & horizontal rules
/PLINK=         <body> link colour
/PTEXT=         <body> text colour
/PVLINK=        <body> visited link colour


CGI VARIABLES
-------------
WWW_FORM_BRIEF          if the word brief occurs in the query string only
                        the bar graph is generated, no device details
WWW_FORM_DISK           one of more disk device names
WWW_FORM_DISKS          ditto
WWW_FORM_HEIGHT         height in pixels of the bar-graph
WWW_FORM_GRAPHICONLY    if the word graphiconly occurs in the query string a
                        gif representing the device is generated and returned
WWW_FORM_HELP           if the word help occurs in the query string it
                        results in a redirection to the help document
WWW_FORM_PERCENTAGE     results in a bar graph GIF being returned
WWW_FORM_WIDTH          width in pixels of the bar-graph
WWW_SCRIPT_NAME         script name
WWW_SERVER_NAME         server host name
WWW_SERVER_PORT         server IP port
WWW_QUERY_STRING        query string
WWW_PATH_INFO           document path (everything following the script name)


OSU ENVIRONMENT
---------------
Script responses are returned in OSU "raw" mode; the script taking care of the
full response header and correctly carriage-controlled data stream, text or
binary!!  The script standard output stream is reopened in binary mode (no
translation of '\n') with a maximum record size set to 4096 bytes (failure to
set this results in the OSU server reporting %SYSTEM-F-DATAOVERUN errors).  The
binary output is enclosed by suitably fflushed() "<DNETRAW>" and "</DNETRAW>"
control tags (the flush make them individual records as required by OSU).
If in debug the script output is placed into "text" mode.


BUILD DETAILS
-------------
See BUILD_HDISK.COM procedure.


VERSION HISTORY (update SOFTWAREVN as well!)
---------------
04-MAY-2004  MGD  v1.3.4, correct percentage for large devices,
                          minor cosmetic changes to device info table
23-DEC-2003  MGD  v1.3.3, minor conditional mods to support IA64
12-APR-2003  MGD  v1.3.2, link colour changed to 0000cc
30-OCT-1998  MGD  v1.3.1, refined help path redirection for OSU use
12-OCT-1998  MGD  v1.3.0, accomodations for OSU environment
24-JUL-1998  MGD  v1.2.1, suppress table background colours if empty
29-APR-1998  MGD  v1.2.0, cosmetic changes
18-APR-1997  MGD  v1.1.0, graphics pre-expired (generated each time accessed)
06-AUG-1996  MGD  v1.0.0, YAQH (wanted to try generating coloured GIFs :^)
*/
/*****************************************************************************/

#define SOFTWAREVN "1.3.4"
#define SOFTWARENM "HDISK"
#ifdef __ALPHA
   char SoftwareID [] = SOFTWARENM " AXP-" SOFTWAREVN;
#endif
#ifdef __ia64
   char SoftwareID [] = SOFTWARENM " IA64-" SOFTWAREVN;
#endif
#ifdef __VAX
   char SoftwareID [] = SOFTWARENM " VAX-" SOFTWAREVN;
#endif

/* standard C header files */
#include <stdio.h>
#include <ctype.h>
#include <errno.h>
#include <file.h>

/* VMS header files */
#include <dcdef.h>
#include <devdef.h>
#include <descrip.h>
#include <dvidef.h>
#include <libdef.h>
#include <libdtdef.h>
#include <ssdef.h>
#include <stsdef.h>

/* application header file */
#ifdef OSU_HDISK
#include "gift.h"
#else
#include "[-.gift]gift.h"
#endif

/*
Can't find any UCB definitions anywhere.  Cobbled these up out of the $GETDVI 
system service description of DVI$_STS item and the VMS Device Support 
Reference describing Unit Control Blocks. 
*/ 
#define UCB$V_TIM 0x1
#define UCB$V_INT 0x2
#define UCB$V_ERLOGIP 0x4
#define UCB$V_CANCEL 0x8
#define UCB$V_ONLINE 0x10
#define UCB$V_POWER 0x20
#define UCB$V_TIMOUT 0x40
#define UCB$V_INTTYPE 0x80
#define UCB$V_BSY 0x100
#define UCB$V_MOUNTING 0x200
#define UCB$V_DEADMO 0x400
#define UCB$V_VALID 0x800
#define UCB$V_UNLOAD 0x1000
#define UCB$V_TEMPLATE 0x2000
#define UCB$V_MNTVERIP 0x4000
#define UCB$V_WRONGVOL 0x8000
#define UCB$V_DELETEUCB 0x10000
#define UCB$V_LCL_VALID 0x20000
#define UCB$V_SUPMVMSG 0x40000
#define UCB$V_MNTVERPND 0x80000
#define UCB$V_DISMOUNT 0x100000
#define UCB$V_CLUTRAN 0x200000
#define UCB$V_WRTLOCKMV 0x400000
#define UCB$V_SVPN_END 0x800000

#define VMSok(x) ((x) & STS$M_SUCCESS)
#define VMSnok(x) !(((x) & STS$M_SUCCESS))

#define boolean int
#define true 1
#define false 0

/* macro provides NULL pointer if CGI variable does not exist */
#define GetCgiVarIfExists(CharPointer,CgiVariableName) \
   CharPointer = getenv(CgiVariableName)

/* macro provides pointer to empty string even if CGI variable does not exist */
#define GetCgiVar(CharPointer,CgiVariableName) \
   if ((CharPointer = getenv(CgiVariableName)) == NULL) \
       CharPointer = ""; \
   if (Debug) fprintf (stdout, "%s |%s|\n", CgiVariableName, CharPointer);

#define ExcessiveErrorCount 5

#define DEFAULT_CHARSET "ISO-8859-1"

#define DEFAULT_HELP_PATH "/hdisk/-/hdisk.html"

#define DEFAULT_BUTTONS "Help"

/* this macro just plugs in some script-specific code into ButtonBar() */
#define SCRIPT_SPECIFIC_BUTTON_CODE \
   ButtonInternal[0] = HelpPathPtr;

#define DEFAULT_PS_BGCOLOR        "#ffffff"
#define DEFAULT_PS_TEXT           "#000000"
#define DEFAULT_PS_LINK           "#0000cc"
#define DEFAULT_PS_VLINK          "#0000cc"
#define DEFAULT_PS_HEADBGCOLOR    "#cccccc"
#define DEFAULT_PS_HEADBORDER     "0"
#define DEFAULT_PS_HEADTEXT       "#000000"
#define DEFAULT_PS_BUTTONBGCOLOR  "#ffffff"
#define DEFAULT_PS_BUTTONBORDER   "1"

#define PS_BACKGROUND     0
#define PS_BGCOLOR        1
#define PS_TEXT           2
#define PS_LINK           3
#define PS_VLINK          4
#define PS_HEADBGCOLOR    5
#define PS_HEADTEXT       6
#define PS_HEADBORDER     7
#define PS_BUTTONBGCOLOR  8
#define PS_BUTTONBORDER   9
#define PS_BODYTAG       10
#define PS_LAYOUT        11
#define PS_HEADLOCAL     12
#define PS_HEADPADDING   13

char  *PageScheme [16];

char  *MonthName [] =
{ "", "January", "February", "March", "April", "May", "June",
      "July", "August", "September", "October", "November", "December" };

char  *DayName [] =
{ "", "Monday", "Tuesday", "Wednesday", "Thursday",
      "Friday", "Saturday", "Sunday" };

char  Utility [] ="HDISK";

struct DiskInfoStruct
{
   struct DiskInfoStruct  *NextPtr;
   long  DevClass,
         DevChar,
         DevChar2,
         ErrCnt,
         FreeBlocks,
         MaxBlock,
         UsedBlocks,
         VolNumber,
         VolSetMem;
   char  FullDevName [65],
         NextDevName [65],
         MediaName [65];
   struct {
      unsigned long  lo32;
      unsigned long  hi32;
   } UcbStatus;
};

boolean  Debug,
         DoDiskInfoTable,
         DoGraphicOnly,
         HttpHasBeenOutput,
         OsuEnvironment;

unsigned long  DayOfWeek;
unsigned long  CurrentBinTime [2];
unsigned short  CurrentNumTime [7];
char  ContentTypeCharset [64],
      DayDateTime [64];

char  *ButtonPtr = DEFAULT_BUTTONS,
      *CgiFormDiskPtr,
      *CgiFormHeightPtr,
      *CgiFormPercentagePtr,
      *CgiFormWidthPtr,
      *CgiPathInfoPtr,
      *CgiQueryStringPtr,
      *CgiRequestSchemePtr,
      *CgiScriptNamePtr,
      *CgiServerNamePtr,
      *CgiServerSoftwarePtr,
      *CgiServerPortPtr,
      *CgiTypePtr,
      *CharsetPtr,
      *CliCharsetPtr,
      *HelpPathPtr = DEFAULT_HELP_PATH;

/* for historical reasons GIFT uses an output stream named "HttpOut" */
FILE  *HttpOut;

/* storage in the Gift.c module */
extern boolean  GraphicNoCacheGif;
extern int  GraphicHeight,
            GraphicWidth;
extern struct GraphicFontStruct Font [];

/* required function prototypes */
AtOsuExit ();

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

main ()

{
   register char  *cptr, *sptr, *zptr;

   int  status,
        Percentage;
   char  UpCaseDiskName [256];

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

   if (getenv ("WWWEXEC_RUNDOWN_STRING") != NULL)
   {
      OsuEnvironment = true;
      atexit (&AtOsuExit);
   }

   if (getenv ("HDISK$DBUG") != NULL)
   {
      Debug = true;
      if (OsuEnvironment) fprintf (stdout, "<DNETTEXT>\n200 Success\n");
   }

   GetParameters ();

   SetPageScheme ();

   if (OsuEnvironment)
      CgiTypePtr = "OSU";
   else
      CgiTypePtr = "standard";

   if (Debug)
      system ("show sym *");
   else
   {
      if (OsuEnvironment)
      {
         /* reopen so that '\r' and '\n' are not filtered, maximum record size */
         if ((stdout =
             freopen ("SYS$OUTPUT", "w", stdout, "ctx=bin", "mrs=4096"))
             == NULL)
            exit (vaxc$errno);

         fprintf (stdout, "<DNETRAW>");
         fflush (stdout);
      }
      else
      {
         /* reopen so that '\r' and '\n' are not filtered */
         if ((stdout = freopen ("SYS$OUTPUT", "w", stdout, "ctx=bin")) == NULL)
            exit (vaxc$errno);
      }
   }

   if ((CharsetPtr = CliCharsetPtr) == NULL)
   {
      GetCgiVar (CharsetPtr, "WWW_REQUEST_CHARSET");
      if (!CharsetPtr[0]) GetCgiVar (CharsetPtr, "WWW_SERVER_CHARSET");
      if (!CharsetPtr[0]) CharsetPtr = DEFAULT_CHARSET;
   }
   if (CharsetPtr[0])
      sprintf (ContentTypeCharset, "; charset=%s", CharsetPtr);
   else
      ContentTypeCharset[0] = '\0';

   /* for historical reasons GIFT uses an output stream named "HttpOut" */
   HttpOut = stdout;

   /***********/
   /* process */
   /***********/

   GetCgiVar (CgiServerSoftwarePtr, "WWW_SERVER_SOFTWARE");
   GetCgiVar (CgiScriptNamePtr, "WWW_SCRIPT_NAME");
   GetCgiVar (CgiPathInfoPtr, "WWW_PATH_INFO");
   GetCgiVar (CgiQueryStringPtr, "WWW_QUERY_STRING");

   if (strstr (CgiQueryStringPtr, "hyperdisk") != NULL ||
       strstr (CgiQueryStringPtr, "HYPERDISK") != NULL)
   {
      GiftButton ("", "HyperDisk", 14);
      exit (SS$_NORMAL);
   }

   if (strstr (CgiQueryStringPtr, "furtherinfo") != NULL ||
       strstr (CgiQueryStringPtr, "FURTHERINFO") != NULL)
   {
      GiftButton ("", "  further\ninformation", 8);
      exit (SS$_NORMAL);
   }

   if (strstr (CgiQueryStringPtr, "brief") != NULL ||
       strstr (CgiQueryStringPtr, "BRIEF") != NULL)
      DoDiskInfoTable = false;
   else
      DoDiskInfoTable = true;

   if (strstr (CgiQueryStringPtr, "graphiconly") != NULL ||
       strstr (CgiQueryStringPtr, "GRAPHICONLY") != NULL)
      DoGraphicOnly = GraphicNoCacheGif = true;
   else
      DoGraphicOnly = GraphicNoCacheGif = false;

   GetCgiVar (CgiFormWidthPtr, "WWW_FORM_WIDTH");
   if (CgiFormWidthPtr[0])
   {
      GraphicWidth = atol(CgiFormWidthPtr);
      if (GraphicWidth <= 0 || GraphicWidth >= MaxGraphicWidth)
         exit (ErrorGeneral ("Graphic width error.",
                             __FILE__, __LINE__));
   }

   GetCgiVar (CgiFormHeightPtr, "WWW_FORM_HEIGHT");
   if (CgiFormHeightPtr[0])
   {
      GraphicHeight = atol(CgiFormHeightPtr);
      if (GraphicHeight <= 0 || GraphicHeight >= MaxGraphicHeight)
         exit (ErrorGeneral ("Graphic height error.",
                             __FILE__, __LINE__));
   }

   GetCgiVar (CgiFormPercentagePtr, "WWW_FORM_PERCENTAGE");
   if (CgiFormPercentagePtr[0])
   {
      /* provide a bar graph */
      Percentage = atol (CgiFormPercentagePtr);
      if (!isdigit(CgiFormPercentagePtr[0]) || Percentage > 100)
         exit (ErrorGeneral ("Percentage is not acceptable.",
                             __FILE__, __LINE__));
      GiftUsageBar (Percentage, 85, "");
      exit (SS$_NORMAL);
   }

   if (strlen(CgiPathInfoPtr) > 1)
      cptr = CgiPathInfoPtr+1;
   else
   {
      /* disks not in path, see if they are specified from a form */
      GetCgiVar (CgiFormDiskPtr, "WWW_FORM_DISK");
      /* nothing in singular "_disk", try plural "_disks" */
      if (!CgiFormDiskPtr[0]) GetCgiVar (CgiFormDiskPtr, "WWW_FORM_DISKS");
      /* no, no disks at all, provide some help! */
      if (!CgiFormDiskPtr[0])
      {
         char  ServerPort [32];

         GetCgiVar (CgiRequestSchemePtr, "WWW_REQUEST_SCHEME");
         GetCgiVar (CgiServerNamePtr, "WWW_SERVER_NAME");
         GetCgiVar (CgiServerPortPtr, "WWW_SERVER_PORT");
         if (!CgiRequestSchemePtr[0]) CgiRequestSchemePtr = "http:";
         if (!strcmp (CgiServerPortPtr, "80"))
            ServerPort[0] = '\0';
         else
            sprintf (ServerPort, ":%s", CgiServerPortPtr);

         fprintf (stdout,
"HTTP/1.0 302 Redirection\r\n\
Location: %s//%s%s%s\r\n\
Server: %s\r\n\
\r\n",
            CgiRequestSchemePtr,
            CgiServerNamePtr,
            ServerPort,
            HelpPathPtr,
            CgiServerSoftwarePtr);

         exit (SS$_NORMAL);
      }
      cptr = CgiFormDiskPtr;
   }

   zptr = (sptr = UpCaseDiskName) + sizeof(UpCaseDiskName);
   for (/* see above*/; *cptr && sptr < zptr; *sptr++ = toupper(*cptr++));
   if (sptr >= zptr)
      exit (ErrorGeneral ("String too small.", __FILE__, __LINE__));
   *sptr = '\0';

   if (DoGraphicOnly)
   {
      OneDiskInfo (UpCaseDiskName);
      exit (SS$_NORMAL);
   }

   sys$gettim (&CurrentBinTime);
   sys$numtim (&CurrentNumTime, &CurrentBinTime);

   if (VMSnok (status = lib$day_of_week (&CurrentBinTime, &DayOfWeek)))
      exit (ErrorVmsStatus (status, "day of week", 0, __FILE__, __LINE__));
   sprintf (DayDateTime, "%s, %d %s %d %02.02d:%02.02d:%02.02d",
            DayName[DayOfWeek], CurrentNumTime[2],
            MonthName[CurrentNumTime[1]], CurrentNumTime[0],
            CurrentNumTime[3], CurrentNumTime[4], CurrentNumTime[5]);

   ReportDiskInfo (UpCaseDiskName);

   exit (SS$_NORMAL);
}

/*****************************************************************************/
/*
Script executing in OSU environment exits.
*/

AtOsuExit ()

{
   if (Debug)
      fprintf (stdout, "</DNETTEXT>\n");
   else
   {
      fflush (stdout);
      fprintf (stdout, "</DNETRAW>");
      fflush (stdout);
   }
}

/*****************************************************************************/
/*
Get "command-line" parameters, whether from the command-line or from a
configuration logical containing the equivalent.
*/

GetParameters ()

{
   static char  CommandLine [256];
   static unsigned long  Flags = 0;

   register char  *aptr, *cptr, *clptr, *sptr;

   int  status;
   unsigned short  Length;
   char  ch;
   $DESCRIPTOR (CommandLineDsc, CommandLine);

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

   if ((clptr = getenv ("HDISK$PARAM")) == NULL)
   {
      /* get the entire command line following the verb */
      if (VMSnok (status =
          lib$get_foreign (&CommandLineDsc, 0, &Length, &Flags)))
         exit (status);
      (clptr = CommandLine)[Length] = '\0';
   }

   aptr = NULL;
   ch = *clptr;
   for (;;)
   {
      if (aptr != NULL) *aptr = '\0';
      if (!ch) break;

      *clptr = ch;
      if (Debug) fprintf (stdout, "clptr |%s|\n", clptr);
      while (*clptr && isspace(*clptr)) *clptr++ = '\0';
      aptr = clptr;
      if (*clptr == '/') clptr++;
      while (*clptr && !isspace (*clptr) && *clptr != '/')
      {
         if (*clptr != '\"')
         {
            clptr++;
            continue;
         }
         cptr = clptr;
         clptr++;
         while (*clptr)
         {
            if (*clptr == '\"')
               if (*(clptr+1) == '\"')
                  clptr++;
               else
                  break;
            *cptr++ = *clptr++;
         }
         *cptr = '\0';
         if (*clptr) clptr++;
      }
      ch = *clptr;
      if (*clptr) *clptr = '\0';
      if (Debug) fprintf (stdout, "aptr |%s|\n", aptr);
      if (!*aptr) continue;

      if (strsame (aptr, "/CHARSET=", 4))
      {
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (*cptr) cptr++;
         CliCharsetPtr = cptr;
         continue;
      }
      if (strsame (aptr, "/DBUG", -1))
      {
         Debug = true;
         continue;
      }
      if (strsame (aptr, "/HELP=", 4))
      {
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (*cptr) cptr++;
         HelpPathPtr = cptr;
         return (true);
      }
      if (GetPageParameter (aptr)) continue;

      if (*aptr != '/')
      {
         fprintf (stdout, "%%%s-E-MAXPARM, too many parameters\n \\%s\\\n",
                  Utility, aptr);
         exit (STS$K_ERROR | STS$M_INHIB_MSG);
      }
      else
      {
         fprintf (stdout, "%%%s-E-IVQUAL, unrecognized qualifier\n \\%s\\\n",
                  Utility, aptr+1);
         exit (STS$K_ERROR | STS$M_INHIB_MSG);
      }
   }
}

/*****************************************************************************/
/*
Get command-line parameters associated with page scheme.
*/

boolean GetPageParameter (char *aptr)

{
   register char  *cptr;

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

   if (strsame (aptr, "/PBACKGROUND=", 4))
   {
      for (cptr = aptr; *cptr && *cptr != '='; cptr++);
      if (*cptr) cptr++;
      PageScheme[PS_BACKGROUND] = cptr;
      return (true);
   }
   if (strsame (aptr, "/PBGCOLOR=", 4))
   {
      for (cptr = aptr; *cptr && *cptr != '='; cptr++);
      if (*cptr) cptr++;
      PageScheme[PS_BGCOLOR] = cptr;
      return (true);
   }
   if (strsame (aptr, "/PBBGCOLOR=", 5))
   {
      for (cptr = aptr; *cptr && *cptr != '='; cptr++);
      if (*cptr) cptr++;
      PageScheme[PS_BUTTONBGCOLOR] = cptr;
      return (true);
   }
   if (strsame (aptr, "/PBBORDER=", 5))
   {
      for (cptr = aptr; *cptr && *cptr != '='; cptr++);
      if (*cptr) cptr++;
      PageScheme[PS_BUTTONBORDER] = cptr;
      return (true);
   }
   if (strsame (aptr, "/PHBGCOLOR=", 5))
   {
      for (cptr = aptr; *cptr && *cptr != '='; cptr++);
      if (*cptr) cptr++;
      PageScheme[PS_HEADBGCOLOR] = cptr;
      return (true);
   }
   if (strsame (aptr, "/PHBORDER=", 5))
   {
      for (cptr = aptr; *cptr && *cptr != '='; cptr++);
      if (*cptr) cptr++;
      PageScheme[PS_HEADBORDER] = cptr;
      return (true);
   }
   if (strsame (aptr, "/PHTEXT=", 4))
   {
      for (cptr = aptr; *cptr && *cptr != '='; cptr++);
      if (*cptr) cptr++;
      PageScheme[PS_HEADTEXT] = cptr;
      return (true);
   }
   if (strsame (aptr, "/PLAYOUT=", 4))
   {
      for (cptr = aptr; *cptr && *cptr != '='; cptr++);
      if (*cptr) cptr++;
      PageScheme[PS_LAYOUT] = cptr;
      return (true);
   }
   if (strsame (aptr, "/PLINK=", 4))
   {
      for (cptr = aptr; *cptr && *cptr != '='; cptr++);
      if (*cptr) cptr++;
      PageScheme[PS_LINK] = cptr;
      return (true);
   }
   if (strsame (aptr, "/PHLOCAL=", 4))
   {
      for (cptr = aptr; *cptr && *cptr != '='; cptr++);
      if (*cptr) cptr++;
      PageScheme[PS_HEADLOCAL] = cptr;
      return (true);
   }
   if (strsame (aptr, "/PTEXT=", 4))
   {
      for (cptr = aptr; *cptr && *cptr != '='; cptr++);
      if (*cptr) cptr++;
      PageScheme[PS_TEXT] = cptr;
      return (true);
   }
   if (strsame (aptr, "/PVLINK=", 4))
   {
      for (cptr = aptr; *cptr && *cptr != '='; cptr++);
      if (*cptr) cptr++;
      PageScheme[PS_VLINK] = cptr;
      return (true);
   }
   return (false);
}

/*****************************************************************************/
/*
Set the page layout and colouration.
*/

SetPageScheme ()

{
   int  size;
   char  *sptr;

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

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

   if (PageScheme[PS_LAYOUT] == NULL)
      PageScheme[PS_LAYOUT] = "1";

   if (PageScheme[PS_BACKGROUND] == NULL)
      PageScheme[PS_BACKGROUND] = "";

   if (PageScheme[PS_HEADLOCAL] == NULL)
      PageScheme[PS_HEADLOCAL] = "";

   if (PageScheme[PS_LAYOUT][0] == '2')
   {
      if (PageScheme[PS_BGCOLOR] == NULL) PageScheme[PS_BGCOLOR] = "";
      if (PageScheme[PS_TEXT] == NULL) PageScheme[PS_TEXT] = "";
      if (PageScheme[PS_LINK] == NULL) PageScheme[PS_LINK] = "";
      if (PageScheme[PS_VLINK] == NULL) PageScheme[PS_VLINK] = "";
      if (PageScheme[PS_HEADBGCOLOR] == NULL) PageScheme[PS_HEADBGCOLOR] = "";
      if (PageScheme[PS_HEADBORDER] == NULL) PageScheme[PS_HEADBORDER] = "";
      if (PageScheme[PS_HEADTEXT] == NULL) PageScheme[PS_HEADTEXT] = "";
      if (PageScheme[PS_BUTTONBGCOLOR] == NULL) PageScheme[PS_BUTTONBGCOLOR] = "";
      if (PageScheme[PS_BUTTONBORDER] == NULL) PageScheme[PS_BUTTONBORDER] = "";
   }
   else
   {
      if (PageScheme[PS_BGCOLOR] == NULL)
         PageScheme[PS_BGCOLOR] = DEFAULT_PS_BGCOLOR;
      if (PageScheme[PS_TEXT] == NULL)
         PageScheme[PS_TEXT] = DEFAULT_PS_TEXT;
      if (PageScheme[PS_LINK] == NULL)
         PageScheme[PS_LINK] = DEFAULT_PS_LINK;
      if (PageScheme[PS_VLINK] == NULL)
         PageScheme[PS_VLINK] = DEFAULT_PS_VLINK;
      if (PageScheme[PS_HEADBGCOLOR] == NULL)
         PageScheme[PS_HEADBGCOLOR] = DEFAULT_PS_HEADBGCOLOR;
      if (PageScheme[PS_HEADBORDER] == NULL)
         PageScheme[PS_HEADBORDER] = DEFAULT_PS_HEADBORDER;
      if (PageScheme[PS_HEADTEXT] == NULL)
         PageScheme[PS_HEADTEXT] = DEFAULT_PS_HEADTEXT;
      if (PageScheme[PS_BUTTONBGCOLOR] == NULL)
         PageScheme[PS_BUTTONBGCOLOR] = DEFAULT_PS_BUTTONBGCOLOR;
      if (PageScheme[PS_BUTTONBORDER] == NULL)
         PageScheme[PS_BUTTONBORDER] = DEFAULT_PS_BUTTONBORDER;
   }

   /* <BODY> tag attributes */
   size = strlen(PageScheme[PS_BACKGROUND]) +
          strlen(PageScheme[PS_BGCOLOR]) +
          strlen(PageScheme[PS_TEXT]) +
          strlen(PageScheme[PS_LINK]) +
          strlen(PageScheme[PS_VLINK]);
   if (size)
   {
      if ((sptr = calloc (1, size+64)) == NULL) exit (vaxc$errno);
      PageScheme[PS_BODYTAG] = sptr;
      if (PageScheme[PS_BACKGROUND][0])
         sptr += sprintf (sptr, " BACKGROUND=\"%s\"", PageScheme[PS_BACKGROUND]);
      if (PageScheme[PS_BGCOLOR][0])
         sptr += sprintf (sptr, " BGCOLOR=\"%s\"", PageScheme[PS_BGCOLOR]);
      if (PageScheme[PS_TEXT][0])
         sptr += sprintf (sptr, " TEXT=\"%s\"", PageScheme[PS_TEXT]);
      if (PageScheme[PS_LINK][0])
         sptr += sprintf (sptr, " LINK=\"%s\"", PageScheme[PS_LINK]);
      if (PageScheme[PS_VLINK][0])
         sptr += sprintf (sptr, " VLINK=\"%s\"", PageScheme[PS_VLINK]);
   }
   else
      PageScheme[PS_BODYTAG] = "";

   if (PageScheme[PS_HEADBGCOLOR][0])
   {
      if ((sptr = calloc (1, strlen(PageScheme[PS_HEADBGCOLOR])+16)) == NULL)
         exit (vaxc$errno);
      sprintf (sptr, " BGCOLOR=\"%s\"", PageScheme[PS_HEADBGCOLOR]);
      PageScheme[PS_HEADBGCOLOR] = sptr;
      PageScheme[PS_HEADPADDING] = "10";
   }
   else
      PageScheme[PS_HEADPADDING] = "0";

   if (PageScheme[PS_BUTTONBGCOLOR][0])
   {
      if ((sptr = calloc (1, strlen(PageScheme[PS_BUTTONBGCOLOR])+16)) == NULL)
         exit (vaxc$errno);
      sprintf (sptr, " BGCOLOR=\"%s\"", PageScheme[PS_BUTTONBGCOLOR]);
      PageScheme[PS_BUTTONBGCOLOR] = sptr;
   }
}

/*****************************************************************************/
/*
Provides a divider for top and bottom of the content of the page. This can be
a coloured bar (using <TABLE>) or a horizontal rule depending on the page
layout. "Buttons" providing script-internal and/or additional user-specified
links ('ButtonPtr' string) can be placed with(in) this bar. All button labels
are derived from 'ButtonPtr', with script-internal buttons using link-paths
set up via 'ButtonInternal[]' array, or any user-specified path depending on
requirement. An empty path (i.e. PathPtr[0] == '\0') obviously does not have a
link created, it just displays the button label. For a button-bar at the top
of the document use 1, bottom of the document use 2, and for just a bar with
no buttons at all use 0.
*/

ButtonBar (int Top1Bottom2)

{
#define MAX_BUTTON_COUNT 8

   static int  ButtonCount = -1;
   static char  *ButtonInternal [MAX_BUTTON_COUNT],
                *ButtonLabel [MAX_BUTTON_COUNT],
                *ButtonPath [MAX_BUTTON_COUNT];

   int  idx;
   char  *PathPtr;

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

   if (Debug) fprintf (stdout, "ButtonBar() %d\n", Top1Bottom2);

   if (ButtonCount == -1)
   {
      register char  *cptr, *sptr;

      if (Debug) fprintf (stdout, "|%s|\n", ButtonPtr);
      cptr = ButtonPtr;
      for (ButtonCount = 0;
           ButtonCount < MAX_BUTTON_COUNT && *cptr;
           ButtonCount++)
      {
         for (sptr = cptr; *sptr && *sptr != '=' && *sptr != ';'; sptr++)
            if (*sptr == '\\') memcpy (sptr, sptr+1, strlen(sptr));
         if (*sptr == '=') *sptr++ = '\0';
         ButtonLabel[ButtonCount] = cptr;
         cptr = sptr;
         for (sptr = cptr; *sptr && *sptr != ';'; sptr++)
            if (*sptr == '\\') memcpy (sptr, sptr+1, strlen(sptr));
         if (*sptr) *sptr++ = '\0';
         ButtonPath[ButtonCount] = cptr;
         cptr = sptr;
      }
   }

   if (Top1Bottom2)
   {
      /***********************************/
      /* set up script-specified buttons */
      /***********************************/

      SCRIPT_SPECIFIC_BUTTON_CODE
   }

   if (PageScheme[PS_LAYOUT][0] == '2')
   {
      /************/
      /* format 2 */
      /************/

      if (Top1Bottom2 == 2 || !Top1Bottom2)
      {
         fprintf (stdout, "<HR ALIGN=left SIZE=2 WIDTH=95%%>\n");
         if (!Top1Bottom2) return;
      }

      fprintf (stdout, "<FONT SIZE=-1><NOBR>\n");
      for (idx = 0; idx < ButtonCount; idx++)
      {
         if (ButtonInternal[idx] == NULL)
            PathPtr = ButtonPath[idx];
         else
            PathPtr = ButtonInternal[idx];
         if (idx) fprintf (stdout, "&nbsp;");
         if (PathPtr[0])
            fprintf (stdout, "[<A HREF=\"%s\">%s</A>]\n",
                     PathPtr, ButtonLabel[idx]);
         else
            fprintf (stdout, "[%s]\n", ButtonLabel[idx]);
      }
      fprintf (stdout, "</NOBR></FONT>\n");

      if (Top1Bottom2 == 1)
         fprintf (stdout, "<HR ALIGN=left SIZE=2 WIDTH=95%%>\n");
   }
   else
   {
      /************/
      /* format 1 */
      /************/

      fprintf (stdout,
"<TABLE BORDER=0 CELLPADDING=0 CELLSPACING=0>\
<TR><TD HEIGHT=2></TD></TR>\
</TABLE>\n\
<TABLE BORDER=%s CELLPADDING=%s CELLSPACING=0 WIDTH=100%%>\n\
<TR><TD%s>\n",
         PageScheme[PS_HEADBORDER],
         PageScheme[PS_HEADBORDER],
         PageScheme[PS_HEADBGCOLOR]);

      if (ButtonCount == 0 || !Top1Bottom2)
         fprintf (stdout, "&nbsp;\n");
      else
      {
         fprintf (stdout,
"<TABLE BORDER=%s CELLPADDING=1 CELLSPACING=0>\n",
            PageScheme[PS_BUTTONBORDER]);

         for (idx = 0; idx < ButtonCount; idx++)
         {
            if (ButtonInternal[idx] == NULL)
               PathPtr = ButtonPath[idx];
            else
               PathPtr = ButtonInternal[idx];
            if (PathPtr[0])
               fprintf (stdout,
"<TD ALIGN=center%s><FONT SIZE=-1>\
<NOBR>&nbsp;&nbsp;<A HREF=\"%s\">%s</A>&nbsp;&nbsp;</NOBR></FONT></TD>\n",
                  PageScheme[PS_BUTTONBGCOLOR], PathPtr, ButtonLabel[idx]);
            else
               fprintf (stdout,
"<TD ALIGN=center%s><FONT SIZE=-1>\
<NOBR>&nbsp;&nbsp;%s&nbsp;&nbsp;</NOBR></FONT></TD>\n",
                  PageScheme[PS_BUTTONBGCOLOR], ButtonLabel[idx]);
         }

         fprintf (stdout, "</TR></TABLE>\n");
      }

      fprintf (stdout,
"</TD></TR>\n\
</TABLE>\n\
<TABLE BORDER=0 CELLPADDING=0 CELLSPACING=0>\
<TR><TD HEIGHT=2></TD></TR>\
</TABLE>\n");
   }
}

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

ReportDiskInfo (char *DiskName)

{
   register char  *cptr, *sptr;
   register int  cnt;

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

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

   fprintf (stdout,
"HTTP/1.0 200 Success\r\n\
Server: %s\r\n\
Content-Type: text/html%s\r\n\
Expires: Fri, 13 Jan 1978 14:00:00 GMT\r\n\
\r\n\
<HTML>\n\
<HEAD>\n\
<META NAME=\"generator\" CONTENT=\"%s\">\n\
<META NAME=\"environment\" CONTENT=\"%s\">\n\
<TITLE>hyperDISK ... %s</TITLE>\n\
</HEAD>\n\
<BODY%s>\n\
<TABLE BORDER=%s CELLPADDING=%s CELLSPACING=0 WIDTH=100%%>\n\
<TR><TD%s>\n\
<FONT COLOR=\"%s\" SIZE=+1><B>\n\
%s\n\
</B></FONT><BR>\n\
<FONT COLOR=\"%s\" SIZE=-1>\n\
&nbsp;<SUP>*</SUP><U>hyperDISK</U>\n\
</FONT>\n\
</TD>%s</TR>\n\
</TABLE>\n\
<BLOCKQUOTE>\n",
      CgiServerSoftwarePtr, ContentTypeCharset,
      SoftwareID, CgiTypePtr,
      DiskName, 
      PageScheme[PS_BODYTAG],
      PageScheme[PS_HEADBORDER],
      PageScheme[PS_HEADPADDING],
      PageScheme[PS_HEADBGCOLOR],
      PageScheme[PS_HEADTEXT],
      DayDateTime,
      PageScheme[PS_HEADTEXT],
      PageScheme[PS_HEADLOCAL]);

   HttpHasBeenOutput = true;

   cptr = DiskName;
   while (*cptr)
   {
      sptr = cptr;
      while (*cptr && isspace(*cptr)) cptr++;
      while (*cptr && *cptr != ':' && *cptr != ',' && *cptr != '/') cptr++;
      if (*cptr) *cptr++ = '\0';
      if (!*sptr) continue;
      OneDiskInfo (sptr);
   }

   fprintf (stdout,
"</BLOCKQUOTE>\n\
<P>\n");

   ButtonBar (2);

   fprintf (stdout,
"</BODY>\n\
</HTML>\n");
}

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

OneDiskInfo (char *DiskName)

{
   int  status,
        ErrorCount,
        VolumeCount;
   unsigned long  PercentageUsed,
                  TotalMaxBlock,
                  TotalFreeBlocks,
                  TotalUsedBlocks;
   struct DiskInfoStruct  *DiskInfoPtr,
                          *FirstDiskInfoPtr,
                          *NextDiskInfoPtr;

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

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

   if ((FirstDiskInfoPtr = calloc (sizeof(struct DiskInfoStruct), 1)) == NULL)
      exit (ErrorVmsStatus (vaxc$errno, "allocating memory", "",
                            __FILE__, __LINE__));

   if (VMSnok (status = GetDiskInfo (DiskName, FirstDiskInfoPtr)))
   {
      free (FirstDiskInfoPtr);
      if (status == SS$_NOSUCHDEV)
      {
         fprintf (stdout,
            "<H2>%s ... <FONT COLOR=\"#ff0000\">NO SUCH DEVICE!</FONT></H2>\n",
             DiskName);
         return;
      }
      exit (ErrorVmsStatus (status, DiskName, "", __FILE__, __LINE__));
   }

   if (!(FirstDiskInfoPtr->DevClass & DC$_DISK))
   {
      free (FirstDiskInfoPtr);
      fprintf (stdout,
         "<H2>%s ... <FONT COLOR=\"#ff0000\">NOT A DISK DEVICE!</FONT></H2>\n",
         DiskName);
      return;
   }

   if (!(FirstDiskInfoPtr->DevChar & DEV$M_MNT))
   {
      free (FirstDiskInfoPtr);
      fprintf (stdout,
         "<H2>%s ... <FONT COLOR=\"#ff0000\">NOT MOUNTED!</FONT></H2>\n",
         DiskName);
      return;
   }

   /* on-line bit should normally be 1 */
   if (!(FirstDiskInfoPtr->UcbStatus.lo32 & UCB$V_ONLINE))
   {
      free (FirstDiskInfoPtr);
      fprintf (stdout,
         "<H2>%s ... <FONT COLOR=\"#ff0000\">NOT ONLINE!</FONT></H2>\n",
         DiskName);
      return;
   }

   /* unit timeout bit should normally be 0 */
   if (FirstDiskInfoPtr->UcbStatus.lo32 & UCB$V_TIMOUT)
   {
      free (FirstDiskInfoPtr);
      fprintf (stdout,
"<H2>%s ... <FONT COLOR=\"#ff0000\">MOUNT VERIFY HAS TIMED-OUT!</FONT></H2>\n\
<P>This device was in a mount verify state, <B>but</B> that has now \
timed-out.  It could have been off-line, had the wrong volume on-line, \
had excessive errors or a corrupted file system.  <B>In short ... \
there is a major problem that should investigated ASAP.</B>\n",
               DiskName);
      return;
   }

   /* mount verify bit should normally be 0 */
   if (FirstDiskInfoPtr->UcbStatus.lo32 & UCB$V_MNTVERIP)
   {
      free (FirstDiskInfoPtr);
      fprintf (stdout,
"<H2>%s ... <FONT COLOR=\"#ff0000\">IN MOUNT VERIFY!</FONT></H2>\n\
<P>This device is in a mount verify state.  It could be off-line, \
have the wrong volume on-line, have excessive errors or a corrupted \
file system.  <B>In short ... there is a major problem that should \
investigated ASAP.</B>\n",
               DiskName);
      return;
   }

   if (FirstDiskInfoPtr->VolSetMem && FirstDiskInfoPtr->VolNumber > 1)
   {
      free (FirstDiskInfoPtr);
      fprintf (stdout,
"<H2>%s ... <FONT COLOR=\"#ff0000\">NOT PRIMARY MEMBER!</FONT></H2>\n\
<P>This device is part of a volume set, but is not the primary member \
of that set.  Specify only the primary member!\n",
               DiskName);
      return;
   }

   /***************************************/
   /* additional devices for a volume set */
   /***************************************/

   DiskInfoPtr = FirstDiskInfoPtr;
   while (DiskInfoPtr->NextDevName[0])
   {
      if ((DiskInfoPtr->NextPtr =
           calloc (sizeof(struct DiskInfoStruct), 1)) == NULL)
         exit (ErrorVmsStatus (vaxc$errno, "allocating memory", "",
                               __FILE__, __LINE__));

      GetDiskInfo (DiskInfoPtr->NextDevName, DiskInfoPtr->NextPtr);
      DiskInfoPtr = DiskInfoPtr->NextPtr;
   }

   /********************/
   /* calculate totals */
   /********************/

   ErrorCount = TotalMaxBlock = TotalFreeBlocks = TotalUsedBlocks =
      VolumeCount = 0;
   DiskInfoPtr = FirstDiskInfoPtr;
   while (DiskInfoPtr != NULL)
   {
      VolumeCount++;
      ErrorCount += DiskInfoPtr->ErrCnt;
      TotalMaxBlock += DiskInfoPtr->MaxBlock;
      TotalFreeBlocks += DiskInfoPtr->FreeBlocks;
      TotalUsedBlocks += DiskInfoPtr->UsedBlocks;
      DiskInfoPtr = DiskInfoPtr->NextPtr;
   }

   /* round up or down? */
   if (((TotalUsedBlocks * 10) / (TotalMaxBlock / 100)) % 10 >= 5)
      PercentageUsed = ((TotalUsedBlocks * 10) / (TotalMaxBlock / 10)) + 1;
   else
      PercentageUsed = (TotalUsedBlocks * 10) / (TotalMaxBlock / 10);

   /****************/
   /* graphic only */
   /****************/

   if (DoGraphicOnly)
   {
      /* local storage */
      char  String [256];

      if (PercentageUsed == 100)
         sprintf (String, "%s  %d%% used", DiskName, PercentageUsed);
      else
      if (PercentageUsed)
         sprintf (String, "%s  %d%% used %d%% free",
                  DiskName, PercentageUsed, 100 - PercentageUsed);
      else
         sprintf (String, "%s %d%% free", DiskName, 100 - PercentageUsed);
      GiftUsageBar (PercentageUsed, 85, String);
      /* no need to deallocate memory, will be exit()ing shortly */
      return;
   }

   /*********************/
   /* graphic image tag */
   /*********************/

   fprintf (stdout,
"<H2>%s ... used: %d%% free: %d%%",
            DiskName, PercentageUsed, 100 - PercentageUsed);

   if (ErrorCount >= ExcessiveErrorCount)
      fprintf (stdout, " <FONT COLOR=\"#ff0000\">ERRORS!</FONT>");

   fprintf (stdout,
"</H2>\n\
<IMG SRC=\"%s?percentage=%d\" ALT=\"[disk usage indicator]\">\n",
            CgiScriptNamePtr, PercentageUsed);

   if (DoDiskInfoTable)
   {
      /***************************************/
      /* present table of device information */
      /***************************************/

      if (VolumeCount == 1)
         fprintf (stdout, "<P>");
      else
         fprintf (stdout, "<P>Volume set comprising %d devices.\n<BR>",
                  VolumeCount);

      fprintf (stdout,
"<TABLE CELLPADDING=3 CELLSPACING=0 BORDER=1>\n\
<TR><TD>\n\
<TABLE CELLPADDING=0 CELLSPACING=5 BORDER=0>\n\
<TR>\
<TD></TD>\
<TH COLSPAN=2><U>Total</U></TH>\
<TH COLSPAN=3><U>Used</U></TH>\
<TH COLSPAN=3><U>Free</U></TH>\
<TH><U>Errors</U>&nbsp;&nbsp;</TH>\
<TH><U>Media</U></TH>\
</TR>\n\
<TR>\
<TD></TD>\
<TH ALIGN=right VALIGN=bottom><FONT SIZE=-1><U>blocks</U></FONT></TH>\
<TH ALIGN=right VALIGN=bottom><FONT SIZE=-1><U>MBytes</U></FONT>&nbsp;&nbsp;</TH>\
<TH ALIGN=right VALIGN=bottom><FONT SIZE=-1><U>blocks</U></FONT></TH>\
<TH ALIGN=right VALIGN=bottom><FONT SIZE=-1><U>MBytes</U></FONT></TH>\
<TH ALIGN=right VALIGN=bottom><FONT SIZE=-1><U>percent</U></FONT>&nbsp;&nbsp;</TH>\
<TH ALIGN=right VALIGN=bottom><FONT SIZE=-1><U>blocks</U></FONT></TH>\
<TH ALIGN=right VALIGN=bottom><FONT SIZE=-1><U>MBytes</U></FONT></TH>\
<TH ALIGN=right VALIGN=bottom><FONT SIZE=-1><U>percent</U></FONT>&nbsp;&nbsp;</TH>\
</TR>\n");

      /* all disks (just one, or multiple if a volume set) */

      DiskInfoPtr = FirstDiskInfoPtr;
      while (DiskInfoPtr != NULL)
      {
         /* round up or down? */
         if (((TotalUsedBlocks * 10) / (TotalMaxBlock / 100)) % 10 >= 5)
            PercentageUsed = ((DiskInfoPtr->UsedBlocks * 10) /
                              (DiskInfoPtr->MaxBlock / 10)) + 1;
         else
            PercentageUsed = (DiskInfoPtr->UsedBlocks * 10) /
                             (DiskInfoPtr->MaxBlock / 10);

         fprintf (stdout,
"<TR ALIGN=right>\
<TH ALIGN=left><U>%s</U>&nbsp;&nbsp;</TH>\
<TD ALIGN=right>%d</TD>\
<TD ALIGN=right>%d&nbsp;&nbsp;</TD>\
<TD ALIGN=right>%d</TD>\
<TD ALIGN=right>%d</TD>\
<TD ALIGN=right>%d%%&nbsp;&nbsp;</TD>\
<TD ALIGN=right>%d</TD>\
<TD ALIGN=right>%d</TD>\
<TD ALIGN=right>%d%%&nbsp;&nbsp;</TD>\
<TD ALIGN=right>%d&nbsp;&nbsp;</TD>\
<TD ALIGN=right>%s</TD>\
</TR>\n",
         DiskInfoPtr->FullDevName,
         DiskInfoPtr->MaxBlock,
         DiskInfoPtr->MaxBlock >> 11,
         DiskInfoPtr->UsedBlocks,
         DiskInfoPtr->UsedBlocks >> 11,
         PercentageUsed,
         DiskInfoPtr->FreeBlocks,
         DiskInfoPtr->FreeBlocks >> 11,
         100 - PercentageUsed,
         DiskInfoPtr->ErrCnt,
         DiskInfoPtr->MediaName);

         DiskInfoPtr = DiskInfoPtr->NextPtr;
      }

      /* block and Mbyte totals */

      fprintf (stdout,
"<TR ALIGN=right>\
<TD></TD>\
<TD ALIGN=right>%d</TD>\
<TD></TD>\
<TD ALIGN=right>%d</TD>\
<TD></TD>\
<TD></TD>\
<TD ALIGN=right>%d</TD>\
<TD></TD>\
<TD></TD>\
<TD></TD>\
<TD></TD>\
</TR>\n\
<TR ALIGN=right>\
<TD></TD>\
<TD></TD>\
<TD ALIGN=right>%d&nbsp;&nbsp;</TD>\
<TD></TD>\
<TD ALIGN=right>%d</TD>\
<TD></TD>\
<TD></TD>\
<TD ALIGN=right>%d</TD>\
</TR>\n",
      TotalMaxBlock,
      TotalUsedBlocks,
      TotalFreeBlocks,
      TotalMaxBlock >> 11,
      TotalUsedBlocks >> 11,
      TotalFreeBlocks >> 11);

      fprintf (stdout, "</TABLE>\n</TD></TR>\n</TABLE>\n");
   }

   /*************************/
   /* free allocated memory */
   /*************************/

   NextDiskInfoPtr = FirstDiskInfoPtr;
   while (NextDiskInfoPtr != NULL)
   {
      DiskInfoPtr = NextDiskInfoPtr;
      NextDiskInfoPtr = NextDiskInfoPtr->NextPtr;
      free (DiskInfoPtr);
   }
}

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

GetDiskInfo
(
char *DiskName,
struct DiskInfoStruct *diptr
)
{
   static long  DviDevClass = DVI$_DEVCLASS,
                DviDevChar = DVI$_DEVCHAR,
                DviDevChar2 = DVI$_DEVCHAR2,
                DviFullDevNam = DVI$_FULLDEVNAM,
                DviFreeBlocks = DVI$_FREEBLOCKS,
                DviErrCnt = DVI$_ERRCNT,
                DviMaxBlock = DVI$_MAXBLOCK,
                DviMediaName = DVI$_MEDIA_NAME,
                DviNextDevNam = DVI$_NEXTDEVNAM,
                DviSts = DVI$_STS,
                DviVolNumber = DVI$_VOLNUMBER,
                DviVolSetMem = DVI$_VOLSETMEM;

   register char  *cptr, *sptr;

   int  status;
   unsigned short  StringItemLength;
   $DESCRIPTOR (DeviceNameDsc, "");
   $DESCRIPTOR (StringItemDsc, "");

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

   if (Debug) fprintf (stdout, "GetDiskInfo() |%s|\n", DiskName);

   diptr->MaxBlock = diptr->FreeBlocks = diptr->UsedBlocks = 0;
   diptr->FullDevName[0] = diptr->NextDevName[0] = diptr->MediaName[0] = '\0';
   diptr->UcbStatus.lo32 = diptr->UcbStatus.hi32 = 0;

   DeviceNameDsc.dsc$w_length = strlen (DiskName);
   DeviceNameDsc.dsc$a_pointer = DiskName;

   if (VMSnok (status =
       lib$getdvi (&DviDevClass, 0, &DeviceNameDsc, &diptr->DevClass, 0, 0)))
      return (status);

   if (!(diptr->DevClass & DC$_DISK)) return (SS$_NORMAL);

   if (VMSnok (status =
       lib$getdvi (&DviDevChar, 0, &DeviceNameDsc, &diptr->DevChar, 0, 0)))
      return (status);

   if (!(diptr->DevChar & DEV$M_MNT)) return (SS$_NORMAL);

   if (VMSnok (status =
       lib$getdvi (&DviDevChar2, 0, &DeviceNameDsc, &diptr->DevChar2, 0, 0)))
      return (status);

   if (VMSnok (status =
       lib$getdvi (&DviSts, 0, &DeviceNameDsc, &diptr->UcbStatus, 0, 0)))
      return (status);

   StringItemDsc.dsc$w_length = sizeof(diptr->FullDevName);
   StringItemDsc.dsc$a_pointer = diptr->FullDevName;

   if (VMSnok (status =
       lib$getdvi (&DviFullDevNam, 0, &DeviceNameDsc,
                   0, &StringItemDsc, &StringItemLength)))
      return (status);

   diptr->FullDevName[StringItemLength] = '\0';
   if (diptr->FullDevName[0] == '_')
   {
      /* copy the string over itself to get rid of the leading underscore */
      cptr = diptr->FullDevName;
      sptr = diptr->FullDevName+1;
      while (*sptr != '\0' && *sptr != ' ') *cptr++ = *sptr++;
      *cptr = '\0';
   }

   StringItemDsc.dsc$w_length = sizeof(diptr->MediaName);
   StringItemDsc.dsc$a_pointer = diptr->MediaName;

   if (VMSnok (status =
       lib$getdvi (&DviMediaName, 0, &DeviceNameDsc,
                   0, &StringItemDsc, &StringItemLength)))
      return (status);

   /* terminate the device name at the first white-space */
   diptr->MediaName[StringItemLength] = '\0';
   cptr = diptr->MediaName;
   while (*cptr != '\0' && *cptr != ' ') cptr++;
   *cptr = '\0';

   if (VMSnok (status =
       lib$getdvi (&DviVolSetMem, 0, &DeviceNameDsc,
                   &diptr->VolSetMem, 0, 0)))
      return (status);

   if (diptr->VolSetMem)
   {
      if (VMSok (status))
         status = lib$getdvi (&DviVolNumber, 0, &DeviceNameDsc,
                              &diptr->VolNumber, 0, 0);

      StringItemDsc.dsc$w_length = sizeof(diptr->NextDevName);
      StringItemDsc.dsc$a_pointer = diptr->NextDevName;

      status = lib$getdvi (&DviNextDevNam, 0, &DeviceNameDsc,
                           0, &StringItemDsc, &StringItemLength);

      diptr->NextDevName[StringItemLength] = '\0';
      if (diptr->NextDevName[0] == '_')
      {
         /* copy the string over itself to get rid of the leading underscore */
         cptr = diptr->NextDevName;
         sptr = diptr->NextDevName+1;
         while (*sptr != '\0' && *sptr != ' ') *cptr++ = *sptr++;
         *cptr = '\0';
      }
   }

   if (VMSnok (status =
       lib$getdvi (&DviMaxBlock, 0, &DeviceNameDsc,
                   &diptr->MaxBlock, 0, 0)))
      return (status);

   if (VMSnok (status =
       lib$getdvi (&DviFreeBlocks, 0, &DeviceNameDsc,
                   &diptr->FreeBlocks, 0, 0)))
      return (status);

   diptr->UsedBlocks = diptr->MaxBlock - diptr->FreeBlocks;

   if (Debug)
      fprintf (stdout,
"**********\n\
UcbStatus.lo32: %08.08X\n\
UcbStatus.hi32: %08.08X\n\
DevClass: %d\n\
DevChar: %d\n\
DevChar2: %d\n\
MaxBlock: %d\n\
FreeBlocks: %d\n\
UsedBlocks: %d\n\
VolNumber: %d\n\
VolSetMem: %d\n\
ErrCnt: %d\n\
FullDevName: %s\n\
NextDevName: %s\n\
MediaName: %s\n\
**********\n",
     diptr->UcbStatus.lo32,
     diptr->UcbStatus.hi32,
     diptr->DevClass,
     diptr->DevChar,
     diptr->DevChar2,
     diptr->MaxBlock,
     diptr->FreeBlocks,
     diptr->UsedBlocks,
     diptr->VolNumber,
     diptr->VolSetMem,
     diptr->ErrCnt,
     diptr->FullDevName,
     diptr->NextDevName,
     diptr->MediaName);

   return (SS$_NORMAL);
}

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

ErrorGeneral
(
char *Text,
char *SourceFileName,
int SourceLineNumber
)
{
   register char  *cptr;

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

   /* 
      The source file format provided by the "__FILE__" macro will be
      "device:[directory]name.type;ver".  Reduce that to "name.type".
   */
   for (cptr = SourceFileName; *cptr && *cptr != ';'; cptr++);
   if (*cptr)
   {
      while (*cptr != '.') cptr--;
      *cptr-- = '\0';
   }
   while (*cptr != ']') cptr--;
   cptr++;

   if (HttpHasBeenOutput)
   {
      fprintf (stdout,
"<P><HR SIZE=3>\n\
<H1>ERROR!</H1>\n\
<!-- %s, %s, %d -->\n\
<P>Reported by server.\n\
<P>%s\n\
</BODY>\n\
</HTML>\n",
      SoftwareID, cptr, SourceLineNumber, Text);
   }
   else
   {
      fprintf (stdout,
"HTTP/1.0 404 Error\r\n\
Server: %s\r\n\
Content-Type: text/html%s\r\n\
\r\n\
<HTML>\n\
<HEAD>\n\
<META NAME=\"generator\" CONTENT=\"%s\">\n\
<META NAME=\"module\" CONTENT=\"%s\">\n\
<META NAME=\"line\" CONTENT=\"%d\">\n\
<TITLE>Error 404</TITLE>\n\
</HEAD>\n\
<BODY>\n\
<H1>ERROR!</H1>\n\
<P>Reported by server.\n\
<P>%s\n\
</BODY>\n\
</HTML>\n",
      CgiServerSoftwarePtr, ContentTypeCharset,
      SoftwareID, cptr, SourceLineNumber, Text);
   }

   return (SS$_NORMAL);
}

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

ErrorVmsStatus
(
int StatusValue,
char *Text,
char *HiddenText,
char *SourceFileName,
int SourceLineNumber
)
{
   static char  Message [256];
   static $DESCRIPTOR (MessageDsc, Message);

   register char  *cptr;
   int  status;
   short int  Length;

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

   if (VMSok (status = sys$getmsg (StatusValue, &Length, &MessageDsc, 1, 0))) 
   {
      Message[Length] = '\0';
      Message[0] = toupper(Message[0]);
   }
   else
      strcpy (Message, "&quot;sys$getmsg() failed&quot;");

   /* 
      The source file format provided by the "__FILE__" macro will be
      "device:[directory]name.type;ver".  Reduce that to "name.type".
   */
   for (cptr = SourceFileName; *cptr && *cptr != ';'; cptr++);
   if (*cptr)
   {
      while (*cptr != '.') cptr--;
      *cptr-- = '\0';
   }
   while (*cptr != ']') cptr--;
   cptr++;

   if (HttpHasBeenOutput)
   {
      fprintf (stdout,
"<P><HR SIZE=3>\n\
<H1>ERROR!</H1>\n\
<!-- %s, %s, %d -->\n\
<P>Reported by server.\n\
<P>%s ... <TT>%s</TT>\n\
<!-- %%X%08.08X \"%s\" -->\n\
</BODY>\n\
</HTML>\n",
      SoftwareID, cptr, SourceLineNumber,
      Message, Text, StatusValue, HiddenText);
   }
   else
   {
      fprintf (stdout,
"HTTP/1.0 404 Error\r\n\
Server: %s\r\n\
Content-Type: text/html%s\r\n\
\r\n\
<HTML>\n\
<HEAD>\n\
<META NAME=\"generator\" CONTENT=\"%s\">\n\
<META NAME=\"module\" CONTENT=\"%s\">\n\
<META NAME=\"line\" CONTENT=\"%d\">\n\
<TITLE>Error 404</TITLE>\n\
</HEAD>\n\
<BODY>\n\
<H1>ERROR!</H1>\n\
<P>Reported by server.\n\
<P>%s ... <TT>%s</TT>\n\
<!-- %%X%08.08X \"%s\" -->\n\
</BODY>\n\
</HTML>\n",
      CgiServerSoftwarePtr, ContentTypeCharset,
      SoftwareID, cptr, SourceLineNumber,
      Message, Text, StatusValue, HiddenText);
   }

   return (SS$_NORMAL);
}

/****************************************************************************/
/*
Does a case-insensitive, character-by-character string compare and returns 
true if two strings are the same, or false if not.  If a maximum number of 
characters are specified only those will be compared, if the entire strings 
should be compared then specify the number of characters as 0.
*/ 

boolean strsame
(
register char *sptr1,
register char *sptr2,
register int  count
)
{
   while (*sptr1 && *sptr2)
   {
      if (toupper (*sptr1++) != toupper (*sptr2++)) return (false);
      if (count)
         if (!--count) return (true);
   }
   if (*sptr1 || *sptr2)
      return (false);
   else
      return (true);
}

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