/*****************************************************************************
/*
                                  FAO.c

Formatted write using $FAO()-like directives, just a functional subset, it also
differs in some significant ways, including extended functionality.  The
functions can write into either specified storage or into the request's output
buffer (probably more efficient that $FAO()ing into storage then copying this
to the network buffer).  All parameters are passed via a $FAOL()-like
pointer-to-longword-vector.  Strings are always assumed to be null-terminated.

A resultant string is always null-terminated (even after an error), hence the
'BufferSize' can always only store 'BufferSize'-1 characters.  'LengthPtr'
provides storage for the number of characters minus the null-termination.

It is no exageration to state that this functionality has made the sorts of
things undertaken with it ten times easier and more efficient!!


$FAO DIRECTIVES SUPPORTED
-------------------------
!AC    counted ASCII string (first byte must be count, i.e. 0 to 255 chars)
!AZ    null-terminated string
!SL    decimal, signed longword
!SQ    signed quadword (indirect character MUST be used (i.e. "!@SQ"))
!UL    decimal, unsigned longword
!XL    hexadecimal, longword
!ZL    zero-filled decimal, unsigned longword
!%D    date/time
!%I    numeric to ASCII identifier
!%S    if the last converted numeric value was not 1 add "S"
!%s    if the last converted numeric value was not 1 add "s"
!%T    time
!*     multiple '*'+1 characters
!+     step-over the next parameter from the vector (anything BUT a "!&@")
!-     back-up to the previous parameter in the vector (anything BUT a "!&@")
!<     begin specified width output field (cannot nest, must have width!)
!>     end specified width output field
!!     exclamation mark


WASD EXTENSIONS
---------------
!&.    replace non-printable characters with periods (e.g. "!&.AZ")
!&\    replace non-printable characters with slash-escapes (e.g. "!&\\AZ")
!&,    comma-ize the following number (e.g. "!&,SL")
!&;    HTML-escape the following string (e.g. "!&;AZ")
!&%    URL-encode the following string (e.g. "!&%AZ")
!&[    format string as a file specification (requires preceding ODS flag)
!&^    force the following string to upper case
!&!    force the following string to lower case
!&_    underline the following string if it contains a space (<u>..</u>)
!&"    quote/escape the following string if it contains a space ("..\"..")
!&+    if !AZ string is not null or empty then add a leading space
!&/    if !AZ string is not null or empty the add a trailing newline
!&>    right justfy (or left fill, if prefered)
!&8    UTF-8 encode string (8 bit characters only)
!&A    address as a parameter, as "&00000000" (for module WATCHing) 
!&B    boolean (for WATCHing)
!&C    a single character
!&D    date/time string truncated after the minute
!&E    until end-of-line (i.e. up to the next <CR> or <LF>)
!&F    local function address, as "00000000()" (for module WATCHing) 
!&H    bytes in hexadecimal (field width is number)
!&I    dotted-decimal from 32 bit network-order IP address
!&L    same as !UL but implicitly comma-ized (as by '!&,')
!&M    full status message string (returned from sys$getmsg())
!&m    text-only status message string (returned from sys$getmsg())
!&O    add " SELECTED" if true, "" if false (for <select> option items)
!&P    request URI (mask password, for module WATCHing)
!&R    add " CHECKED" if true, "" if false (for <input> radio buttons)
!&S    VMS status value, as "%X00000000" (plus message string if non-success)
!&U    URL encode
!&W    weekday/date/time (field-width applies to date/time component only)
!&X    hexadecimal value, as "0x00000000"
!&Z    null-terminated string, as "{length}string" (for module WATCHing)
!&?    simple conditional, "!&?string if *vector TRUE\rstring if FALSE\r"
!&@    nested format string address from vector (FILO stacks the current one)
!%%    same as !&@ (for backward compatibility of strings in WASD_CONFIG_MSG)


The WASD extensions perform two roles.  Some modify the way a following
directive is formatted, for example HTML-escaping it.  Others provide
additonal format directives to standard $FAO.  Field width should always be
placed BEFORE the WASD extensions.  Indirection should be indicated immediately
before the respective formatting directive.

Each directive can have a positive, decimal integer between the '!' and the
directive providing a field width, e.g. "!10AZ", or have the field width taken
from the vector using "!#AZ".  These behave in the same manner as the sys$fao()
field width parameter. 

Any directive may use the indirect character (unlike sys$fao(), where only
numeric directives may).  This indicates the vector value is the 32 bit address
of the value, rather than the value itself, e.g. "!@AZ", "!10@UL", etc.


VERSION HISTORY
---------------
16-JUL-2019  MGD  '&>' right justify (or left fill)
24-JUL-2018  MGD  FaoLibPutOutput()
29-OCT-2017  MGD  bugfix; '%D' do not cancel field-width then "(none)"
02-JAN-2016  MGD  bugfix; |if (fw <= 0) fw = 32;| to |if (fw < 0) fw = 32;|
23-APR-2015  MGD  bugfix; FaoSAK() sdptr = StrDscBuffer(StrDscPtr);
11-JUL-2013  MGD  FaoUrlEncodeTable tilde from "%7e" to "~" (cadaver issue)
11-SEP-2010  MGD  '&\' slash-escapes common non-printable codes
27-JUN-2010  MGD  '&S' now provides message string if non-success
30-MAY-2010  MGD  '&8' UTF-8 encode string (only 8 bit characters)
09-JUN-2007  MGD  use STR_DSC
                  make '!&E' the same functionality as previously '!&L'
                    and make '!&L' an implicitly comma-ized '!UL'
16-JUL-2005  MGD  '&.' replace non-printable characters with periods,
                  replace ErrorNoticed() with FaoErrorNoticed() in
                    FaoToStdout() and FaoToOpcom() for the obvious reason
01-MAR-2003  MGD  '&+' if not null or empty !AZ adds a leading space,
                  '&/' if not null or empty !AZ adds a trailing newline,
                  change behaviour of NULL !AZ to showing an empty string
18-OCT-2002  MGD  '&"' quotes if there is a space in the string
19-AUG-2002  MGD  '&[' for ODS file naming conversions,
                  '&^' now force-to-upper-case, '&!' force-to-lower,
                  '&_' underlines if there is a space in the string
08-AUG-2002  MGD  '&U' request URI (mask password) now '&P',
                  '&U' now URL-encodes a URL (allowing for query string, etc.)
16-JUN-2002  MGD  make field-width work for '&,'
31-MAR-2002  MGD  '&U' request URI (mask password)
17-MAR-2002  MGD  'SQ' kludge for VAX quadword values 
10-JAN-2002  MGD  add !&R and !&O
04-AUG-2001  MGD  differentiate WASD extensions with '!&',
                  support module WATCHing
07-JUL-2001  MGD  add !IP to FaolSAK()
17-MAY-2001  MGD  dignified with a module of it's own
02-FEB-2001  MGD  add !%I to FaolSAK()
27-AUG-2000  MGD  add !SL to FaolSAK()
04-MAR-2000  MGD  add FaolSAK() and it's siblings
                  FaoToBuffer(), FaolToBuffer(), FaoToNet()
*/
/*****************************************************************************/

#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 <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* VMS related header files */
#include <descrip.h>
#include <jpidef.h>
#include <libdef.h>
#include <libdtdef.h>
#include <lnmdef.h>
#include <opcdef.h>
#include <prvdef.h>
#include <psldef.h>
#include <rms.h>
#include <secdef.h>
#include <ssdef.h>
#include <stsdef.h>

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

#define WASD_MODULE "FAO"

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

#if WATCH_MOD
   BOOL  DebugFaoSak;
#  define DEBUG_FAOSAK DebugFaoSak
#else
#  define DEBUG_FAOSAK 0
#endif

int  OpcomMessages,
     OpcomTarget;

char  *FaoUrlEncodeTable [] =
{
   "%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07",
   "%08", "%09", "%0a", "%0b", "%0c", "%0d", "%0e", "%0f", 
   "%10", "%11", "%12", "%13", "%14", "%15", "%16", "%17", 
   "%18", "%19", "%1a", "%1b", "%1c", "%1d", "%1e", "%1f", 
   "%20", "%21", "%22", "%23", "$",   "%25", "%26", "%27",
   "%28", "%29", "*",   "%2b", "%2c", "-",   ".",   "/",
   "0",   "1",   "2",   "3",   "4",   "5",   "6",   "7",
   "8",   "9",   "%3a", ";",   "%3c", "%3d", "%3e", "%3f",
   "%40", "A",   "B",   "C",   "D",   "E",   "F",   "G",    /* 0x47 */
   "H",   "I",   "J",   "K",   "L",   "M",   "N",   "O",    /* 0x4f */
   "P",   "Q",   "R",   "S",   "T",   "U",   "V",   "W",    /* 0x57 */
   "X",   "Y",   "Z",   "%5b", "%5c", "%5d", "%5e", "_",    /* 0x5f */
   "%60", "a",   "b",   "c",   "d",   "e",   "f",   "g",    /* 0x67 */
   "h",   "i",   "j",   "k",   "l",   "m",   "n",   "o",    /* 0x6f */
   "p",   "q",   "r",   "s",   "t",   "u",   "v",   "w",    /* 0x77 */
   "x",   "y",   "z",   "%7b", "%7c", "%7d", "~",   "%7f",
   "%80", "%81", "%82", "%83", "%84", "%85", "%86", "%87",
   "%88", "%89", "%8a", "%8b", "%8c", "%8d", "%8e", "%8f",
   "%90", "%91", "%92", "%93", "%94", "%95", "%96", "%97",
   "%98", "%99", "%9a", "%9b", "%9c", "%9d", "%9e", "%9f",
   "%a0", "%a1", "%a2", "%a3", "%a4", "%a5", "%a6", "%a7",
   "%a8", "%a9", "%aa", "%ab", "%ac", "%ad", "%ae", "%af",
   "%b0", "%b1", "%b2", "%b3", "%b4", "%b5", "%b6", "%b7",
   "%b8", "%b9", "%ba", "%bb", "%bc", "%bd", "%be", "%bf",
   "%c0", "%c1", "%c2", "%c3", "%c4", "%c5", "%c6", "%c7",
   "%c8", "%c9", "%ca", "%cb", "%cc", "%cd", "%ce", "%cf",
   "%d0", "%d1", "%d2", "%d3", "%d4", "%d5", "%d6", "%d7",
   "%d8", "%d9", "%da", "%db", "%dc", "%dd", "%de", "%df",
   "%e0", "%e1", "%e2", "%e3", "%e4", "%e5", "%e6", "%e7",
   "%e8", "%e9", "%ea", "%eb", "%ec", "%ed", "%ee", "%ef",
   "%f0", "%f1", "%f2", "%f3", "%f4", "%f5", "%f6", "%f7",
   "%ff", "%f9", "%fa", "%fb", "%fc", "%fd", "%fe", "%ff"
};

/* keep this for a couple of versions until the above proves itself */

char  *FaoUrlEncodeTable2 [] =
{
   "%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07",
   "%08", "%09", "%0a", "%0b", "%0c", "%0d", "%0e", "%0f", 
   "%10", "%11", "%12", "%13", "%14", "%15", "%16", "%17", 
   "%18", "%19", "%1a", "%1b", "%1c", "%1d", "%1e", "%1f", 
   "%20", "%21", "%22", "%23", "$",   "%25", "%26", "%27",
   "%28", "%29", "*",   "%2b", "%2c", "-",   ".",   "/",
   "0",   "1",   "2",   "3",   "4",   "5",   "6",   "7",
   "8",   "9",   "%3a", ";",   "%3c", "%3d", "%3e", "%3f",
   "%40", "A",   "B",   "C",   "D",   "E",   "F",   "G",    /* 0x47 */
   "H",   "I",   "J",   "K",   "L",   "M",   "N",   "O",    /* 0x4f */
   "P",   "Q",   "R",   "S",   "T",   "U",   "V",   "W",    /* 0x57 */
   "X",   "Y",   "Z",   "%5b", "%5c", "%5d", "%5e", "_",    /* 0x5f */
   "%60", "a",   "b",   "c",   "d",   "e",   "f",   "g",    /* 0x67 */
   "h",   "i",   "j",   "k",   "l",   "m",   "n",   "o",    /* 0x6f */
   "p",   "q",   "r",   "s",   "t",   "u",   "v",   "w",    /* 0x77 */
   "x",   "y",   "z",   "%7b", "%7c", "%7d", "%7e", "%7f",
   "%80", "%81", "%82", "%83", "%84", "%85", "%86", "%87",
   "%88", "%89", "%8a", "%8b", "%8c", "%8d", "%8e", "%8f",
   "%90", "%91", "%92", "%93", "%94", "%95", "%96", "%97",
   "%98", "%99", "%9a", "%9b", "%9c", "%9d", "%9e", "%9f",
   "%a0", "%a1", "%a2", "%a3", "%a4", "%a5", "%a6", "%a7",
   "%a8", "%a9", "%aa", "%ab", "%ac", "%ad", "%ae", "%af",
   "%b0", "%b1", "%b2", "%b3", "%b4", "%b5", "%b6", "%b7",
   "%b8", "%b9", "%ba", "%bb", "%bc", "%bd", "%be", "%bf",
   "%c0", "%c1", "%c2", "%c3", "%c4", "%c5", "%c6", "%c7",
   "%c8", "%c9", "%ca", "%cb", "%cc", "%cd", "%ce", "%cf",
   "%d0", "%d1", "%d2", "%d3", "%d4", "%d5", "%d6", "%d7",
   "%d8", "%d9", "%da", "%db", "%dc", "%dd", "%de", "%df",
   "%e0", "%e1", "%e2", "%e3", "%e4", "%e5", "%e6", "%e7",
   "%e8", "%e9", "%ea", "%eb", "%ec", "%ed", "%ee", "%ef",
   "%f0", "%f1", "%f2", "%f3", "%f4", "%f5", "%f6", "%f7",
   "%ff", "%f9", "%fa", "%fb", "%fc", "%fd", "%fe", "%ff"
};

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

#ifdef DBUG
extern BOOL Debug;
#else
#define Debug 0 
#endif

extern BOOL  OdsExtended;

extern int  OutputBufferSize;

extern int  ToLowerCase[],
            ToUpperCase[];

extern char  ErrorSanityCheck[],
             SoftwareID[];

extern char  *DayName[];

extern HTTPD_PROCESS  HttpdProcess;
extern WATCH_STRUCT  Watch;

/*****************************************************************************/
/*
FAOL Swiss Army Knife.  This is the primary FAO function described in the
module prologue.  It will be more than obvious it has been written to optimize
efficiency (in the author's limited appreciation anyway) rather than
readability.
*/ 

int FaolSAK
(
REQUEST_STRUCT *rqptr,
void *BufferPtr,
int BufferSize,
ushort *LengthPtr,
char *FormatString,
ulong *VectorPtr
)
{
#define FORMAT_STACK_MAX 8

   static char  HexDigits [] = "0123456789abcdef";
   static char  HexDigitsUpper [] = "0123456789ABCDEF";
   static char  EncBuffer [] = "%xx";
   static char  OneChar [] = "\0";
   static $DESCRIPTOR (DayDateTimeFaoDsc, "!AZ, !#%D\0");
   static $DESCRIPTOR (HexValueFaoDsc, "0x!8XL\0");
   static $DESCRIPTOR (LengthStringFaoDsc, "{!UL}!-!#AZ\0");
   static $DESCRIPTOR (Md5FaoDsc, "!8XL!8XL!8XL!8XL\0");
   static $DESCRIPTOR (Md5WidthFaoDsc, "!#<!8XL!8XL!8XL!8XL!>\0");
   static $DESCRIPTOR (PDFaoDsc, "!%D\0");
   static $DESCRIPTOR (PIFaoDsc, "!%I\0");
   static $DESCRIPTOR (PTFaoDsc, "!%T\0");
   static $DESCRIPTOR (SLFaoDsc, "!SL\0");
   static $DESCRIPTOR (SLWidthFaoDsc, "!#SL\0");
   static $DESCRIPTOR (SQFaoDsc, "!@SQ\0");
   static $DESCRIPTOR (SQWidthFaoDsc, "!#@SQ\0");
   static $DESCRIPTOR (ULFaoDsc, "!UL\0");
   static $DESCRIPTOR (ULWidthFaoDsc, "!#UL\0");
   static $DESCRIPTOR (VmsStatusFaoDsc, "%X!8XL\0");
   static $DESCRIPTOR (VmsStatusMsgFaoDsc, "%X!8XL (!AZ)\0");
   static $DESCRIPTOR (WatchingAddressFaoDsc, "&!8XL/!AZ()\0");
   static $DESCRIPTOR (WatchingFunctionFaoDsc, "!8XL()\0");
   static $DESCRIPTOR (XLFaoDsc, "!XL\0");
   static $DESCRIPTOR (XLWidthFaoDsc, "!#XL\0");
   static $DESCRIPTOR (ZLFaoDsc, "!ZL\0");
   static $DESCRIPTOR (ZLWidthFaoDsc, "!#ZL\0");

   BOOL  CommaNumber,
         FileSpec,
         ForceLowerCase,
         ForceUpperCase,
         HtmlEscape,
         IndirectValue,
         PeriodNonPrintable,
         QuoteIfSpace,
         RightJustify,
         SlashNonPrintable,
         SpaceThenString,
         TrailingNewlineString,
         UnderlineIfSpace,
         UrlEncode,
         Utf8Encode;
   int  ch, fw, ofw, idx,
        status,
        CommaCount,
        DescrLength,
        FileOds,
        LastNumericValue, 
        SysGetMsgFlags;
   ushort  Length;
   ulong  DayOfWeek,
          VectorValue;
   ulong  Time64 [2];
   ulong  *ulptr,
          *Time64Ptr;
   char  *bptr, *bzptr, *cptr, *fptr, *sptr, *zptr,
         *HexDigitsPtr,
         *LastFptr;
   char  *FormatStack [FORMAT_STACK_MAX];
   char  FileString [ODS_MAX_FILE_NAME_LENGTH],
         String [2048];
   $DESCRIPTOR (StringDsc, String);
   struct dsc$descriptor  *VmsDscPtr;
   STR_DSC  *sdptr,
            *StrDscPtr;
   STR_DSC_AUTO (BufferDsc);
#ifdef __VAX
   double  dValue;
#endif

#if WATCH_MOD
   {
      static BOOL  DebugFaoSakCheck;
      if (!DebugFaoSakCheck)
      {
         DebugFaoSakCheck = true;
         DebugFaoSak = getenv ("WASD_DEBUG_FAOSAK");
      }
   }
#endif

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

   if (WATCHMOD (rqptr, WATCH_MOD_FAO))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_FAO,
                 "FaolSAK() !&X !UL !&X !AZ",
                 BufferPtr, BufferSize, LengthPtr, FormatString);

   status = SS$_NORMAL;
   LastNumericValue = 1; 
   /* start with output-field-width set to infinite */
   ofw = -1;

   if (LengthPtr) *LengthPtr = 0;

   if (BufferPtr)
   {
      if (BufferSize == -1)
      {
         /* writing to specified STR_DSC storage */
         sdptr = StrDscPtr = (STR_DSC*)BufferPtr;
         sdptr = StrDscBuffer (sdptr);
         bptr = STR_DSC_PTR(sdptr) + STR_DSC_LEN(sdptr);
         bzptr = STR_DSC_PTR(sdptr) + STR_DSC_SIZE(sdptr);
      }
      else
      {
         /* writing to specified char[] storage */
         sdptr = StrDscPtr = StrDscExternal (NULL, &BufferDsc,
                                             BufferPtr, BufferSize);
         bptr = STR_DSC_PTR(sdptr) = BufferPtr;
         bzptr = STR_DSC_PTR(sdptr) + BufferSize-1;
      }
   }
   else
   {
      /* writing to network buffer */
      sdptr = StrDscIfNotBegin (rqptr, &rqptr->NetWriteBufferDsc,
                                       OutputBufferSize);
      sdptr = StrDscPtr = StrDscBuffer (sdptr);
      bptr = STR_DSC_PTR(sdptr) + STR_DSC_LEN(sdptr);
      bzptr = STR_DSC_PTR(sdptr) + STR_DSC_SIZE(sdptr);
   }

   /* put initial format string into index zero of the format string stack */
   idx = 0;
   FormatStack[idx] = FormatString;

   /*************************/
   /* nested format strings */
   /*************************/

   for (;;)
   {
      /*****************/
      /* format string */
      /*****************/

      fptr = FormatStack[idx];
      while (*fptr)
      {
         while (*fptr && *fptr != '!' && ofw)
         {
            /* a literal character */
            if (bptr < bzptr)
            {
               *bptr++ = *fptr++;
               ofw--;
               continue;
            }
            if (STR_DSC_IS_EXTERNAL(sdptr)) goto FaolSAKovf;
            STR_DSC_LEN(sdptr) = bptr - STR_DSC_PTR(sdptr);
            sdptr = StrDscBuffer (StrDscPtr);
            bptr = STR_DSC_PTR(sdptr) + STR_DSC_LEN(sdptr);
            bzptr = STR_DSC_PTR(sdptr) + STR_DSC_SIZE(sdptr);
         }
         /* if end of literal characters (i.e. not a '!') then break */
         if (!*fptr) break;

         /* if no character follows the '!' then just forget it */
         LastFptr = fptr;
         if (!*++fptr) break;

         /*********************/
         /* format directives */
         /*********************/

         if (DEBUG_FAOSAK)  
            fprintf (stdout, "fptr |%10.10s|\n", fptr);

         fw = -1;
         IndirectValue = false;

         /*********************/
         /* field width, etc. */
         /*********************/

         if (isdigit(*fptr))
         {
            /* field-width */
            fw = atoi(fptr);
            while (isdigit(*fptr)) fptr++;
            /* for writes into network buffers, let's keep this < 1024 */
            if (!BufferPtr) fw &= 0xfff;
         }
         else
         if (*fptr == '#')
         {
            /* field-width from vector */
            fptr++;
            fw = (int)*VectorPtr++;
            /* for writes into network buffers, let's keep this < 1024 */
            if (!BufferPtr) fw &= 0xfff;
         }
         /** if (DEBUG_FAOSAK)  fprintf (stdout, "fw: %d\n", fw); **/

         if (*fptr == '@')
         {
            fptr++;
            IndirectValue = true;
         }

         FileOds = 0;
         DescrLength = -1;
         CommaNumber = FileSpec = ForceLowerCase = ForceUpperCase =
            HtmlEscape = PeriodNonPrintable = QuoteIfSpace = RightJustify =
            SpaceThenString = SlashNonPrintable = TrailingNewlineString =
            UnderlineIfSpace = UrlEncode = Utf8Encode = false;

         while (*fptr == '&')
         {
            /************************/
            /* formatting modifiers */
            /************************/

            if (SAME2(fptr,'&.'))
            {
               fptr += 2;
               PeriodNonPrintable = true;
               continue;
            }
            if (SAME2(fptr,'&\\'))
            {
               fptr += 2;
               SlashNonPrintable = true;
               continue;
            }
            if (SAME2(fptr,'&,'))
            {
               fptr += 2;
               CommaNumber = true;
               continue;
            }
            if (SAME2(fptr,'&;'))
            {
               fptr += 2;
               HtmlEscape = true;
               continue;
            }
            if (SAME2(fptr,'&%'))
            {
               fptr += 2;
               UrlEncode = true;
               continue;
            }
            if (SAME2(fptr,'&['))
            {
               fptr += 2;
               FileSpec = true;
               /* get the numeric value of the file naming schema */
               FileOds = *VectorPtr++;
               continue;
            }
            if (SAME2(fptr,'&!'))
            {
               fptr += 2;
               ForceLowerCase = true;
               continue;
            }
            if (SAME2(fptr,'&^'))
            {
               fptr += 2;
               ForceUpperCase = true;
               continue;
            }
            if (SAME2(fptr,'&_'))
            {
               fptr += 2;
               UnderlineIfSpace = true;
               continue;
            }
            if (SAME2(fptr,'&"'))
            {
               fptr += 2;
               QuoteIfSpace = true;
               continue;
            }
            if (SAME2(fptr,'&+'))
            {
               fptr += 2;
               SpaceThenString = true;
               continue;
            }
            if (SAME2(fptr,'&/'))
            {
               fptr += 2;
               TrailingNewlineString = true;
               continue;
            }
            if (SAME2(fptr,'&8'))
            {
               fptr += 2;
               Utf8Encode = true;
               continue;
            }
            if (SAME2(fptr,'&>'))
            {
               fptr += 2;
               RightJustify = true;
               continue;
            }
            break;
         }

         if (*fptr == '@')
         {
            fptr++;
            IndirectValue = true;
         }

         /* default is no string to copy if it drops through */
         sptr = NULL;

         /* directives in guessed order of most common usage */
         if (SAME2(fptr,'AZ'))
         {
            /**************************/
            /* null-terminated string */
            /**************************/

            fptr += 2;
            if (IndirectValue)
            {
               if (!*VectorPtr)
               {
                  status = SS$_ACCVIO;
                  break;
               }
               sptr = *(char*)*VectorPtr++;
            }
            else
               sptr = (char*)*VectorPtr++;
            if (WATCHMOD (rqptr, WATCH_MOD_FAO))
               if (!sptr)
                  WatchThis (WATCHITM(rqptr), WATCH_MOD_FAO, "(null)");
            if (!sptr) sptr = "";
            /* null or empty string suppresses these */
            if (!sptr[0]) SpaceThenString = TrailingNewlineString = false;
            if (DEBUG_FAOSAK) fprintf (stdout, "!AZ |%s|\n", sptr);
            /* drop through to buffer the string pointed to by 'sptr' */
         }
         else
         if (SAME2(fptr,'UL') ||
             SAME2(fptr,'&L'))
         {
            /*********************/
            /* unsigned longword */
            /*********************/
   
            if (SAME2(fptr,'&L')) CommaNumber = true;

            fptr += 2;
            if (IndirectValue)
            {
               if (!*VectorPtr)
               {
                  status = SS$_ACCVIO;
                  break;
               }
               LastNumericValue = *(ULONGPTR)VectorPtr++;
            }
            else
               LastNumericValue = *VectorPtr++;
            if (DEBUG_FAOSAK) 
               fprintf (stdout, "!UL %d\n", LastNumericValue);
            if (fw > 0 && !CommaNumber)
               status = sys$fao (&ULWidthFaoDsc, 0, &StringDsc,
                                 fw, LastNumericValue);
            else
               status = sys$fao (&ULFaoDsc, 0, &StringDsc,
                                 LastNumericValue);
            if (VMSnok (status)) break;
            if (!CommaNumber) fw = -1;
            sptr = String;
            /* drop through to buffer the string pointed to by 'sptr' */
         }
         else
         if (SAME2(fptr,'XL'))
         {
            /************************/
            /* hexadecimal longword */
            /************************/

            fptr += 2;
            if (IndirectValue)
            {
               if (!*VectorPtr)
               {
                  status = SS$_ACCVIO;
                  break;
               }
               LastNumericValue = *(ULONGPTR)VectorPtr++;
            }
            else
               LastNumericValue = *VectorPtr++;
            if (DEBUG_FAOSAK) 
               fprintf (stdout, "!UL %d\n", LastNumericValue);
            if (fw > 0 && !CommaNumber)
               status = sys$fao (&XLWidthFaoDsc, 0, &StringDsc,
                                 fw, LastNumericValue);
            else
               status = sys$fao (&XLFaoDsc, 0, &StringDsc,
                                 LastNumericValue);
            if (VMSnok (status)) break;
            if (!CommaNumber) fw = -1;
            sptr = String;
            /* drop through to buffer the string pointed to by 'sptr' */
         }
         else
         if (SAME2(fptr,'ZL'))
         {
            /************************/
            /* zero-filled longword */
            /************************/

            fptr += 2;
            if (IndirectValue)
            {
               if (!*VectorPtr)
               {
                  status = SS$_ACCVIO;
                  break;
               }
               LastNumericValue = *(ULONGPTR)VectorPtr++;
            }
            else
               LastNumericValue = *VectorPtr++;
            if (DEBUG_FAOSAK) 
               fprintf (stdout, "!UL %d\n", LastNumericValue);
            if (fw > 0 && !CommaNumber)
               status = sys$fao (&ZLWidthFaoDsc, 0, &StringDsc,
                                 fw, LastNumericValue);
            else
               status = sys$fao (&ZLFaoDsc, 0, &StringDsc,
                                 LastNumericValue);
            if (VMSnok (status)) break;
            if (!CommaNumber) fw = -1;
            sptr = String;
            /* drop through to buffer the string pointed to by 'sptr' */
         }
         else
         if (SAME2(fptr,'&@') ||
             SAME2(fptr,'%%'))  /* for backward compatibility */
         {
            /************************/
            /* nested format string */
            /************************/

            fptr += 2;
            if (idx >= FORMAT_STACK_MAX)
            {
               status = SS$_OVRMAXARG;
               break;
            }
            if (IndirectValue)
            {
               if (!*VectorPtr)
               {
                  status = SS$_ACCVIO;
                  break;
               }
               cptr = *(char*)*VectorPtr++;
            }
            else
               cptr = (char*)*VectorPtr++;
            if (!cptr)
            {
               status = SS$_ACCVIO;
               break;
            }
            /* if empty then just continue with the current format string */
            if (!cptr[0]) continue;
            /* save the current format string pointer */
            FormatStack[idx++] = fptr;
            /* set the new format string pointer */
            fptr = FormatStack[idx] = cptr;
            if (DEBUG_FAOSAK) 
               fprintf (stdout, "!&@ idx: %d fptr |%s|\n", idx, fptr);
            continue;
         }
         else
         if (SAME2(fptr,'&h') ||
             SAME2(fptr,'&H'))
         {
            /***************/
            /* hexadecimal */
            /***************/
   
            fptr += 2;
            if (IndirectValue)
            {
               if (!*VectorPtr)
               {
                  status = SS$_ACCVIO;
                  break;
               }
               cptr = *(char*)*VectorPtr++;
            }
            else
               cptr = (char*)*VectorPtr++;
            if (!cptr)
               sptr = "(null)";
            else
            {
               if (SAME2(fptr,'&h'))
                  HexDigitsPtr = HexDigits;
               else
                  HexDigitsPtr = HexDigitsUpper;
               zptr = (sptr = String) + sizeof(String)-1;
               if (fw < 0) fw = 32;
               while (fw-- > 0)
               {
                  if (sptr >= zptr) break;
                  *sptr++ = HexDigitsPtr[(*(uchar*)cptr >> 4) & 0x0f];
                  if (sptr >= zptr) break;
                  *sptr++ = HexDigitsPtr[*(uchar*)cptr & 0x0f];
                  cptr++;
               }
               *sptr = '\0';
               sptr = String;
            }
            fw = -1;
            /* drop through to buffer the string pointed to by 'sptr' */
         }
         else
         if (SAME2(fptr,'&I'))
         {
            /**************************************/
            /* dotted-decimal/IPv6-hex IP address */
            /**************************************/
   
            fptr += 2;
            if (IndirectValue)
            {
               if (!*VectorPtr)
               {
                  status = SS$_ACCVIO;
                  break;
               }
               LastNumericValue = *(ULONGPTR)VectorPtr++;
            }
            else
               LastNumericValue = *VectorPtr++;
            /* 'LastNumericValue' can be a pointer or an integer */
            sptr = TcpIpAddressToString (LastNumericValue, fw);
            fw = -1;
            /* drop through to buffer the string pointed to by 'sptr' */
         }
         else
         if (SAME2(fptr,'&E'))
         {
            /*****************/
            /* the next line */
            /*****************/

            fptr += 2;
            if (IndirectValue)
            {
               if (!*VectorPtr)
               {
                  status = SS$_ACCVIO;
                  break;
               }
               sptr = *(char*)*VectorPtr++;
            }
            else
               sptr = (char*)*VectorPtr++;
            if (sptr)
            {
               for (zptr = sptr;
                    *zptr && *zptr != '\r' && *zptr != '\n';
                    zptr++);
               /* use field-width to control how much of the string is output */
               fw = zptr - sptr;
               if (DEBUG_FAOSAK)
                  fprintf (stdout, "!&L |%*.*s|\n", fw, fw, sptr);
               status = sys$fao (&LengthStringFaoDsc, 0, &StringDsc, fw, sptr);
               sptr = String;
            }
            else
               sptr = "{0}(null)";
            fw = -1;
            /* drop through to buffer the string pointed to by 'sptr' */
         }
         else
         if (SAME2(fptr,'&M') ||
             SAME2(fptr,'&m'))
         {
            /******************/
            /* status message */
            /******************/

            if (SAME2(fptr,'&M'))
               SysGetMsgFlags = 15;
            else
               SysGetMsgFlags = 1;
            fptr += 2;
            if (IndirectValue)
            {
               if (!*VectorPtr)
               {
                  status = SS$_ACCVIO;
                  break;
               }
               VectorValue = *(ULONGPTR)VectorPtr++;
            }
            else
               VectorValue = *VectorPtr++;
            if (DEBUG_FAOSAK) 
               fprintf (stdout, "!%%M %d\n", VectorValue);

            status = sys$getmsg (VectorValue, &Length, &StringDsc,
                                 SysGetMsgFlags, 0);
            if (VMSnok (status))
               sptr = "*ERROR* sys$getmsg()";
            else
            {
               (sptr = String)[Length] = '\0';
               if (SysGetMsgFlags == 15) sptr++;
            }
            if (DEBUG_FAOSAK) 
               fprintf (stdout, "sys$getmsg() |%s|\n", String);
            /* drop through to buffer the string pointed to by 'sptr' */
         }
         else
         if (SAME2(fptr,'SQ'))
         {
            /*******************/
            /* signed quadword */
            /*******************/
   
            /*
               Note that the "!SQ" directive is being used, for VMS 6.2
               the "!UQ" && "!UJ" loop infinitely for signed values.
               Also note the indirect character MUST be used (i.e. "!@SQ").
               Also that VAX doesn't support it - so a kludge is employed.
            */

            fptr += 2;
            if (IndirectValue)
            {
               if (!*VectorPtr)
               {
                  status = SS$_ACCVIO;
                  break;
               }
               LastNumericValue = 0;
            }
            else
            {
               /* must be indirect */
#if WATCH_MOD
               fprintf (stdout, "%%HTTPD-W-FAO, SS$_BADPARAM %s:%d\n \\%s\\\n",
                        FI_LI, fptr-2); 
#endif /* WATCH_MOD */
               status = SS$_BADPARAM;
               break;
            }
            if (DEBUG_FAOSAK) 
               fprintf (stdout, "!SQ %d\n", LastNumericValue);
#ifdef __VAX
            /* kludge, apparently ok all the way up to 53 bits */
            dValue = (double)((ULONGPTR)(*VectorPtr))[0];
            dValue += (double)((ULONGPTR)(*VectorPtr))[1] * 4294967296.0;
            VectorPtr++;
            status = SS$_NORMAL;
            if (fw > 0 && !CommaNumber)
               sprintf (String, "%*.0f", fw, dValue);
            else
               sprintf (String, "%.0f", dValue);
#else /* Alpha or ia64 */
            if (fw > 0 && !CommaNumber)
               status = sys$fao (&SQWidthFaoDsc, 0, &StringDsc,
                                 fw, *VectorPtr++);
            else
               status = sys$fao (&SQFaoDsc, 0, &StringDsc,
                                 *VectorPtr++);
#endif /* ifdef __VAX */
            if (VMSnok (status)) break;
            if (!CommaNumber) fw = -1;
            sptr = String;
            /* drop through to buffer the string pointed to by 'sptr' */
         }
         else
         if (SAME2(fptr,'SL'))
         {
            /*******************/
            /* signed longword */
            /*******************/
   
            fptr += 2;
            if (IndirectValue)
            {
               if (!*VectorPtr)
               {
                  status = SS$_ACCVIO;
                  break;
               }
               LastNumericValue = *(ULONGPTR)VectorPtr++;
            }
            else
               LastNumericValue = *VectorPtr++;
            if (DEBUG_FAOSAK) 
               fprintf (stdout, "!SL %d\n", LastNumericValue);
            if (fw > 0 && !CommaNumber)
               status = sys$fao (&SLWidthFaoDsc, 0, &StringDsc,
                                 fw, LastNumericValue);
            else
               status = sys$fao (&SLFaoDsc, 0, &StringDsc,
                                 LastNumericValue);
            if (VMSnok (status)) break;
            if (!CommaNumber) fw = -1;
            sptr = String;
            /* drop through to buffer the string pointed to by 'sptr' */
         }
         else
         if (SAME2(fptr,'%D') ||
             SAME2(fptr,'&D'))
         {
            /*************/
            /* date/time */
            /*************/

            cptr = fptr;
            fptr += 2;
            if (IndirectValue)
            {
               if (!*VectorPtr)
               {
                  status = SS$_ACCVIO;
                  break;
               }
               Time64Ptr = *(ULONGPTR)VectorPtr++;
            }
            else
               Time64Ptr = *VectorPtr++;
            if (DEBUG_FAOSAK) 
               fprintf (stdout, "!%%D %d\n", Time64Ptr);

            if (!Time64Ptr) sys$gettim (Time64Ptr = &Time64);
            if (QUAD_ZERO(Time64Ptr))
               sptr = "(none)";
            else
            {
               status = sys$fao (&PDFaoDsc, 0, &StringDsc, Time64Ptr);
               if (VMSnok (status)) break;

               for (sptr = String; *sptr && *sptr != '-'; sptr++);
               /* absolute times have leading space changed to leading zero */
               if (*sptr && String[0] == ' ')
                  (sptr = String)[0] = '0';
               else
               if (*sptr)
                  sptr = String;
               else
                  /* delta times have leading spaces absorbed */
                  for (sptr = String; *sptr && *sptr == ' '; sptr++);

               if (SAME2(cptr,'&D'))
               {
                  /* massage the time a little further */
                  for (cptr = sptr; *cptr && *cptr != ' '; cptr++);
                  if (*cptr)
                     if (sptr[2] == '-')
                        *cptr++ = ':';
                     else
                        *cptr++ = '-';
                  while (*cptr && *cptr != ':') cptr++;
                  if (*cptr) cptr++;
                  while (*cptr && *cptr != ':') cptr++;
                  *cptr = '\0';
               }
            }
            /* drop through to buffer the string pointed to by 'sptr' */
         }
         else
         if (SAME2(fptr,'%I'))
         {
            /***********************/
            /* numeric to ASCII ID */
            /***********************/

            fptr += 2;
            if (IndirectValue)
            {
               if (!*VectorPtr)
               {
                  status = SS$_ACCVIO;
                  break;
               }
               VectorValue = *(ULONGPTR)VectorPtr++;
            }
            else
               VectorValue = *VectorPtr++;
            if (DEBUG_FAOSAK) 
               fprintf (stdout, "!%%D %d\n", VectorValue);

            status = sys$fao (&PIFaoDsc, 0, &StringDsc, VectorValue);
            if (VMSnok (status)) break;
            sptr = String;
            /* drop through to buffer the string pointed to by 'sptr' */
         }
         else
         if (SAME2(fptr,'%T'))
         {
            /********/
            /* time */
            /********/

            fptr += 2;
            if (IndirectValue)
            {
               if (!*VectorPtr)
               {
                  status = SS$_ACCVIO;
                  break;
               }
               Time64Ptr = *(ULONGPTR)VectorPtr++;
            }
            else
               Time64Ptr = *VectorPtr++;
            if (DEBUG_FAOSAK) 
               fprintf (stdout, "!%%T %d\n", Time64Ptr);

            if (!Time64Ptr) sys$gettim (Time64Ptr = &Time64);
            if (QUAD_ZERO(Time64Ptr))
            {
               sptr = "(none)";
               if (fw > 0) fw = -1;
            }
            else
            {
               status = sys$fao (&PTFaoDsc, 0, &StringDsc, Time64Ptr);
               if (VMSnok (status)) break;
               sptr = String;
            }
            /* drop through to buffer the string pointed to by 'sptr' */
         }
         else
         if (SAME2(fptr,'%s') ||
             SAME2(fptr,'%S'))
         {
            /******************/
            /* add 'S' or 's' */
            /******************/

            if (DEBUG_FAOSAK) 
               fprintf (stdout, "!%%%c\n", *(fptr+1));
            if (LastNumericValue == 1)
            {
               fptr += 2;
               continue;
            }
            fptr++;
            fw = -1;
            if (*fptr++ == 's')
               sptr = "s";
            else
               sptr = "S";
            /* drop through to buffer the string pointed to by 'sptr' */
         }
         else
         if (SAME2(fptr,'AC'))
         {
            /************************/
            /* counted ASCII string */
            /************************/

            fptr += 2;
            if (IndirectValue)
            {
               if (!*VectorPtr)
               {
                  status = SS$_ACCVIO;
                  break;
               }
               cptr = *(char*)*VectorPtr++;
            }
            else
               cptr = (char*)*VectorPtr++;
            if (!cptr)
            {
               status = SS$_ACCVIO;
               break;
            }
            if (DEBUG_FAOSAK) 
               fprintf (stdout, "!AC %d |%s|\n", (uint)*cptr, cptr+1);

            /* get the count byte */
            ch = (uint)*cptr++;
            while (ch)
            {
               if (bptr < bzptr)
               {
                  *bptr++ = *cptr++;
                  ch--;
                  continue;
               }
               if (STR_DSC_IS_EXTERNAL(sdptr)) goto FaolSAKovf;
               STR_DSC_LEN(sdptr) = bptr - STR_DSC_PTR(sdptr);
               sdptr = StrDscBuffer (StrDscPtr);
               bptr = STR_DSC_PTR(sdptr) + STR_DSC_LEN(sdptr);
               bzptr = STR_DSC_PTR(sdptr) + STR_DSC_SIZE(sdptr);
            }
            /* only if required drop through to space-fill */
            if (fw <= 0) continue;
            /* no string to copy  */
            sptr = NULL;
         }
         else
         if (SAME2(fptr,'&C'))
         {
            /* a single character */
            fptr += 2;
            String[0] = *VectorPtr++;
            String[1] = '\0';
            sptr = String;
            fw = -1;
            /* drop through to buffer the string pointed to by 'sptr' */
         }
         else
         if (SAME2(fptr,'&P'))
         {
            /*****************/
            /* mask password */
            /*****************/

            fptr += 2;
            cptr = (char*)*VectorPtr++;
            if (!cptr) cptr = "(null)";
            zptr = (sptr = String) + sizeof(String)-1;
            while (*cptr && sptr < zptr)
            {
               if (cptr[0] == ':' && cptr[1] == '/' && cptr[2] == '/') break;
               *sptr++ = *cptr++; 
            }
            if (*cptr)
            {
               if (sptr < zptr) *sptr++ = *cptr++;
               if (sptr < zptr) *sptr++ = *cptr++;
               if (sptr < zptr) *sptr++ = *cptr++;
               while (*cptr && *cptr != '/' && *cptr != '@' && sptr < zptr)
                  *sptr++ = *cptr++;
               if (*cptr == '@')
               {
                  if (sptr < zptr) *sptr++ = *cptr++;
                  while (sptr > String && *sptr != ':') sptr--;
                  if (*sptr == ':')
                  {
                     sptr++;
                     while (*sptr && *sptr != '@') *sptr++ = '*';
                     if (*sptr) sptr++;
                  }
               }
               while (*cptr && sptr < zptr) *sptr++ = *cptr++;
            }
            *sptr = '\0';
            sptr = String;
            /* drop through to buffer the string pointed to by 'sptr' */
         }
         else
         if (SAME2(fptr,'&U'))
         {
            /******************/
            /* encode URL/URI */
            /******************/

            fptr += 2;
            cptr = (char*)*VectorPtr++;
            if (cptr)
               StringUrlEncodeURL (cptr, sptr = String, sizeof(String));
            else
               sptr = "(null)";
            /* drop through to buffer the string pointed to by 'sptr' */
         }
         else
         if (SAME2(fptr,'&W'))
         {
            /*********************/
            /* weekday/date/time */
            /*********************/

            fptr += 2;
            if (IndirectValue)
            {
               if (!*VectorPtr)
               {
                  status = SS$_ACCVIO;
                  break;
               }
               Time64Ptr = *(ULONGPTR)VectorPtr++;
            }
            else
               Time64Ptr = *VectorPtr++;
            if (DEBUG_FAOSAK) 
               fprintf (stdout, "!%%W %d\n", Time64Ptr);

            if (!Time64Ptr) sys$gettim (Time64Ptr = &Time64);
            if (QUAD_ZERO(Time64Ptr))
            {
               sptr = "(none)";
               if (fw > 0) fw = -1;
            }
            else
            if (VMSnok (lib$day_of_week (Time64Ptr, &DayOfWeek)))
               sptr = "*ERROR*1";
            else
            if (VMSnok (sys$fao (&DayDateTimeFaoDsc, 0, &StringDsc,
                DayName[DayOfWeek], fw > 0 ? fw : 23, Time64Ptr)))
               sptr = "*ERROR*2";
            else
               sptr = String;
            /* reset the field-width (only applies to date/time) */
            if (fw > 0) fw = -1;
            /* drop through to buffer the string pointed to by 'sptr' */
         }
         else
         if (SAME2(fptr,'&R'))
         {
            /************************/
            /* radio button boolean */
            /************************/

            fptr += 2;
            if (IndirectValue)
            {
               if (!*VectorPtr)
               {
                  status = SS$_ACCVIO;
                  break;
               }
               VectorValue = *(ULONGPTR)VectorPtr++;
            }
            else
               VectorValue = *VectorPtr++;
            if (DEBUG_FAOSAK) 
               fprintf (stdout, "!&? %d\n", VectorValue);

            if (VectorValue) sptr = " CHECKED"; else sptr = "";
            fw = -1;
            /* drop through to buffer the string pointed to by 'sptr' */
         }
         else
         if (SAME2(fptr,'&O'))
         {
            /*************************/
            /* select option boolean */
            /*************************/

            fptr += 2;
            if (IndirectValue)
            {
               if (!*VectorPtr)
               {
                  status = SS$_ACCVIO;
                  break;
               }
               VectorValue = *(ULONGPTR)VectorPtr++;
            }
            else
               VectorValue = *VectorPtr++;
            if (DEBUG_FAOSAK) 
               fprintf (stdout, "!&? %d\n", VectorValue);

            if (VectorValue) sptr = " SELECTED"; else sptr = "";
            fw = -1;
            /* drop through to buffer the string pointed to by 'sptr' */
         }
         else
         if (SAME2(fptr,'&?'))
         {
            /**********************/
            /* simple conditional */
            /**********************/

            /* e.g. "!&?string if *vector TRUE\rstring if FALSE\r" */
            fptr += 2;
            if (IndirectValue)
            {
               if (!*VectorPtr)
               {
                  status = SS$_ACCVIO;
                  break;
               }
               VectorValue = *(ULONGPTR)VectorPtr++;
            }
            else
               VectorValue = *VectorPtr++;
            if (DEBUG_FAOSAK) 
               fprintf (stdout, "!&? %d\n", VectorValue);

            if (!VectorValue)
            {
               /* if false skip over the true string */
               while (*fptr && *fptr != '\r') fptr++;
               if (*fptr == '\r') fptr++;
               if (!*fptr) break;
            }
            while (*fptr && *fptr != '\r' && fw && ofw)
            {
               if (bptr < bzptr)
               {
                  *bptr++ = *fptr++;
                  fw--;
                  ofw--;
                  continue;
               }
               if (STR_DSC_IS_EXTERNAL(sdptr)) goto FaolSAKovf;
               STR_DSC_LEN(sdptr) = bptr - STR_DSC_PTR(sdptr);
               sdptr = StrDscBuffer (StrDscPtr);
               bptr = STR_DSC_PTR(sdptr) + STR_DSC_LEN(sdptr);
               bzptr = STR_DSC_PTR(sdptr) + STR_DSC_SIZE(sdptr);
            }
            if (*fptr != '\r')
            {
               /* field-width has expired before the return */
               while (*fptr && *fptr != '\r') fptr++;
            }
            if (*fptr == '\r') fptr++;
            if (VectorValue)
            {
               /* was true, skip over the false string */
               while (*fptr && *fptr != '\r') fptr++;
               if (*fptr == '\r') fptr++;
            }
            /* only if required drop through to space-fill */
            if (fw <= 0) continue;
         }
         else
         if (*fptr == '+')
         {
            /**************************/
            /* discard next parameter */
            /**************************/

            fptr++;
            VectorValue = *VectorPtr++;
            if (DEBUG_FAOSAK) 
               fprintf (stdout, "!+ %d\n", VectorValue);
         }
         else
         if (*fptr == '-')
         {
            /****************************/
            /* reuse previous parameter */
            /****************************/

            fptr++;
            VectorValue = *--VectorPtr;
            if (DEBUG_FAOSAK) 
               fprintf (stdout, "!- %d\n", VectorValue);
         }
         else
         if (*fptr == '!')
         {
            /****************************/
            /* an escaped '!' character */
            /****************************/

            if (DEBUG_FAOSAK)  fprintf (stdout, "!!\n");
            fptr++;
            sptr = "!";
            /* drop through to buffer the string pointed to by 'sptr' */
         }
         else
         if (*fptr == '*')
         {
            /*****************************/
            /* multiple '*'+1 characters */
            /*****************************/

            if (DEBUG_FAOSAK)  fprintf (stdout, "!*\n");
            if (!*++fptr) fw = 0;
            while (fw > 0 && ofw)
            {
               if (bptr < bzptr)
               {
                  *bptr++ = *fptr;
                  fw--;
                  ofw--;
                  continue;
               }
               if (STR_DSC_IS_EXTERNAL(sdptr)) goto FaolSAKovf;
               STR_DSC_LEN(sdptr) = bptr - STR_DSC_PTR(sdptr);
               sdptr = StrDscBuffer (StrDscPtr);
               bptr = STR_DSC_PTR(sdptr) + STR_DSC_LEN(sdptr);
               bzptr = STR_DSC_PTR(sdptr) + STR_DSC_SIZE(sdptr);
            }
            if (*fptr) fptr++;
            continue;
         }
         else
         if (*fptr == '<')
         {
            /****************************/
            /* begin output-field-width */
            /****************************/

            if (DEBUG_FAOSAK)  fprintf (stdout, "!<\n");
            fptr++;
            if (ofw >= 0)
            {
               /* can't nest these little blighters! */
#if WATCH_MOD
               fprintf (stdout, "%%HTTPD-W-FAO, SS$_BADPARAM %s:%d\n \\%s\\\n",
                        FI_LI, fptr-1); 
#endif /* WATCH_MOD */
               status = SS$_BADPARAM;
               break;
            }
            ofw = fw;
            continue;
         }
         else
         if (*fptr == '>')
         {
            /**************************/
            /* end output-field-width */
            /**************************/

            if (DEBUG_FAOSAK)  fprintf (stdout, "!>\n");
            fptr++;
            if (ofw < 0)
            {
               /* didn't notice a leading "!<" */
#if WATCH_MOD
               fprintf (stdout, "%%HTTPD-W-FAO, SS$_BADPARAM %s:%d\n \\%s\\\n",
                        FI_LI, fptr-1); 
#endif /* WATCH_MOD */
               status = SS$_BADPARAM;
               break;
            }
            /* get (any) current outstanding that needs space-filling */
            fw = ofw;
            /* reset output-field-width to infinite */
            ofw = -1;
            /* only if required drop through to space-fill */
            if (fw <= 0) continue;
         }

         /***********************/
         /* mainly for WATCHing */
         /***********************/
   
         else
         if (SAME2(fptr,'&A'))
         {
            /* an "address" as &x00000000 */
            fptr += 2;
            LastNumericValue = *VectorPtr++;
            if (!LastNumericValue)
               cptr = "null";
            else
            if (!(cptr = WatchFunction (LastNumericValue)))
               cptr = "?";
            status = sys$fao (&WatchingAddressFaoDsc, 0, &StringDsc,
                              LastNumericValue, cptr);
            if (VMSnok (status)) break;
            fw = -1;
            sptr = String;
            /* drop through to buffer the string pointed to by 'sptr' */
         }
         else
         if (SAME2(fptr,'&B'))
         {
            /* a boolean */
            fptr += 2;
            LastNumericValue = *VectorPtr++;
            if (LastNumericValue) sptr = "TRUE"; else sptr = "FALSE";
            fw = -1;
            /* drop through to buffer the string pointed to by 'sptr' */
         }
         else
         if (SAME2(fptr,'&F'))
         {
            /* a "function" address as &00000000 */
            fptr += 2;
            LastNumericValue = *VectorPtr++;
            status = sys$fao (&WatchingFunctionFaoDsc, 0, &StringDsc,
                              LastNumericValue);
            if (VMSnok (status)) break;
            fw = -1;
            sptr = String;
            /* drop through to buffer the string pointed to by 'sptr' */
         }
         else
         if (SAME2(fptr,'&S'))
         {
            /* VMS status value as %X00000000 */
            fptr += 2;
            LastNumericValue = *VectorPtr++;
            if (LastNumericValue && VMSnok (LastNumericValue))
            {
               /* for non-success status provide corresponding message */
               char  Msg [256];
               $DESCRIPTOR (MsgDsc, Msg);
               SysGetMsgFlags = 15;
               status = sys$getmsg (LastNumericValue, &Length, &MsgDsc,
                                    SysGetMsgFlags, 0);
               if (VMSnok (status))
                  strcpy (Msg, "*ERROR* sys$getmsg()");
               else
                  Msg[Length] = '\0';
               status = sys$fao (&VmsStatusMsgFaoDsc, 0, &StringDsc,
                                 LastNumericValue, Msg);
            }
            else
               status = sys$fao (&VmsStatusFaoDsc, 0, &StringDsc,
                                 LastNumericValue);
            if (VMSnok (status)) break;
            fw = -1;
            sptr = String;
            /* drop through to buffer the string pointed to by 'sptr' */
         }
         else
         if (SAME2(fptr,'&X'))
         {
            /* hexadecimal as 0x00000000 */
            fptr += 2;
            LastNumericValue = *VectorPtr++;
            status = sys$fao (&HexValueFaoDsc, 0, &StringDsc,
                              LastNumericValue);
            if (VMSnok (status)) break;
            fw = -1;
            sptr = String;
            /* drop through to buffer the string pointed to by 'sptr' */
         }
         else
         if (SAME2(fptr,'&Z'))
         {
            /* a null-terminated string as {length}string */
            fptr += 2;
            sptr = (char*)*VectorPtr++;
            if (sptr)
            {
               status = sys$fao (&LengthStringFaoDsc, 0, &StringDsc,
                                 strlen(sptr), sptr);
               sptr = String;
            }
            else
               sptr = "{0}(null)";
            if (DEBUG_FAOSAK)  fprintf (stdout, "!AZ |%s|\n", sptr);
            /* drop through to buffer the string pointed to by 'sptr' */
         }
         else
         {
#if WATCH_MOD
            fprintf (stdout, "%%HTTPD-W-FAO, SS$_BADPARAM %s:%d\n \\%s\\\n",
                     FI_LI, fptr); 
#endif /* WATCH_MOD */
            status = SS$_BADPARAM;
            break;
         }

         /*********************************/
         /* copy a string into the buffer */
         /*********************************/

         if (DEBUG_FAOSAK) 
            fprintf (stdout, "sptr |%s|\n", sptr ? sptr : "(null)");

         if (sptr)
         {
            if (RightJustify)
            {
               /*****************/
               /* right justify */
               /*****************/

               Length = fw - strlen(sptr);
               while (fw && ofw && Length > 0)
               {
                  if (bptr < bzptr)
                  {
                     *bptr++ = ' ';
                     Length--;
                     fw--;
                     ofw--;
                     continue;
                  }
                  if (STR_DSC_IS_EXTERNAL(sdptr)) goto FaolSAKovf;
                  STR_DSC_LEN(sdptr) = bptr - STR_DSC_PTR(sdptr);
                  sdptr = StrDscBuffer (StrDscPtr);
                  bptr = STR_DSC_PTR(sdptr) + STR_DSC_LEN(sdptr);
                  bzptr = STR_DSC_PTR(sdptr) + STR_DSC_SIZE(sdptr);
               }
            }

            if (CommaNumber)
            {
               /****************/
               /* comma number */
               /****************/

               /* not foolproof but probably close enough */
               if (*sptr == '-' || *sptr == '+' && bptr < bzptr)
                  *bptr++ = *sptr++;
               Length = strlen(sptr);

               while (fw && ofw && Length + ((Length-1) / 3) < fw)
               {
                  if (bptr < bzptr)
                  {
                     *bptr++ = ' ';
                     fw--;
                     ofw--;
                     continue;
                  }
                  if (STR_DSC_IS_EXTERNAL(sdptr)) goto FaolSAKovf;
                  STR_DSC_LEN(sdptr) = bptr - STR_DSC_PTR(sdptr);
                  sdptr = StrDscBuffer (StrDscPtr);
                  bptr = STR_DSC_PTR(sdptr) + STR_DSC_LEN(sdptr);
                  bzptr = STR_DSC_PTR(sdptr) + STR_DSC_SIZE(sdptr);
               }
               if (((Length-1) / 3) >= 1)
               {
                  if (!(CommaCount = Length % 3))
                     CommaCount = 3;

                  while (*sptr && fw && ofw)
                  {
                     if (bptr < bzptr)
                     {
                        if (!CommaCount--)
                        {
                           *bptr++ = ',';
                           fw--;
                           ofw--;
                           CommaCount = 3;
                        }
                        else
                        {
                           *bptr++ = *sptr++;
                           fw--;
                           ofw--;
                        }
                        continue;
                     }
                     if (STR_DSC_IS_EXTERNAL(sdptr)) goto FaolSAKovf;
                     STR_DSC_LEN(sdptr) = bptr - STR_DSC_PTR(sdptr);
                     sdptr = StrDscBuffer (StrDscPtr);
                     bptr = STR_DSC_PTR(sdptr) + STR_DSC_LEN(sdptr);
                     bzptr = STR_DSC_PTR(sdptr) + STR_DSC_SIZE(sdptr);
                  }
                  /* only if required drop through to space-fill */
                  if (fw <= 0) continue;
                  sptr = NULL;
               }
               /* drop thru to copy 3 or less digits into the buffer */
               FileSpec = HtmlEscape = QuoteIfSpace = SpaceThenString =
                  TrailingNewlineString = UnderlineIfSpace = UrlEncode =
                  Utf8Encode = false;
            }
         }

         if (sptr)
         {
            if (FileSpec)
            {
               /**********************/
               /* file specification */
               /**********************/

               switch (FileOds)
               {
                  case 0 :
                  case MAPURL_PATH_ODS_2 :
                     if (!ForceUpperCase) ForceLowerCase = true;
                     /* just leave 'sptr' the way it is! */
                     break;

#ifdef ODS_EXTENDED
                  case MAPURL_PATH_ODS_5 :
                     MapOds5VmsToUrl (FileString, sptr, 
                                      sizeof(FileString), true);
                     sptr = FileString;
                     break;
#endif /* ODS_EXTENDED */

                  case MAPURL_PATH_ODS_ADS :
                  case MAPURL_PATH_ODS_SMB :
                     MapOdsAdsVmsToUrl (FileString, sptr,
                                        sizeof(FileString), true);
                     sptr = FileString;
                     break;

                  case MAPURL_PATH_ODS_PWK :
                     MapOdsPwkVmsToUrl (FileString, sptr,
                                        sizeof(FileString), true);
                     sptr = FileString;
                     break;

                  case MAPURL_PATH_ODS_SRI :
                     MapOdsSriVmsToUrl (FileString, sptr,
                                         sizeof(FileString), true);
                     sptr = FileString;
                     break;

                  default :
                     return (SS$_BADPARAM);
               }
            }

            if (QuoteIfSpace || SpaceThenString || UnderlineIfSpace)
            {
               for (cptr = sptr; *cptr && *cptr != ' '; cptr++);
               if (*cptr || SpaceThenString)
               {
                  if (QuoteIfSpace)
                     cptr = "\"";
                  else
                  if (SpaceThenString)
                     cptr = " ";
                  else
                     cptr = "<u>";
                  while (*cptr)
                  {
                     if (bptr < bzptr)
                     {
                        *bptr++ = *cptr++;
                        continue;
                     }
                     if (STR_DSC_IS_EXTERNAL(sdptr)) goto FaolSAKovf;
                     STR_DSC_LEN(sdptr) = bptr - STR_DSC_PTR(sdptr);
                     sdptr = StrDscBuffer (StrDscPtr);
                     bptr = STR_DSC_PTR(sdptr) + STR_DSC_LEN(sdptr);
                     bzptr = STR_DSC_PTR(sdptr) + STR_DSC_SIZE(sdptr);
                  }
               }
               else
                  QuoteIfSpace = UnderlineIfSpace = false;
            }

            if (HtmlEscape)
            {
               /**********************/
               /* HTML-escape string */
               /**********************/

               while (*sptr && fw && ofw)
               {
                  fw--;
                  ofw--;
                  if (Utf8Encode && (*sptr & 0x80))
                  {
                     cptr = String;
                     cptr[0] = ((*sptr & 0xc0) >> 6) | 0xc0;
                     cptr[1] = (*sptr++ & 0x3f) | 0x80;
                     cptr[2] = '\0';
                  }
                  else
                  {
                     if (ForceLowerCase)
                        ch = TOLO(*sptr++);
                     else
                     if (ForceUpperCase)
                        ch = TOUP(*sptr++);
                     else
                     ch = *sptr++;
                     switch (ch)
                     {
                        case '<' :
                           cptr = "&lt;";
                           break;
                        case '>' :
                           cptr = "&gt;";
                           break;
                        case '&' :
                           cptr = "&amp;";
                           break;
                        case '\"' :
                           if (QuoteIfSpace)
                              cptr = "\\&quot;";
                           else
                              cptr = "&quot;";
                           break;
                        default :
                           /* insert this character as-is */
                           *(cptr = OneChar) = ch;
                     }
                  }
                  /* add the character(s) */
                  while (*cptr)
                  {
                     if (bptr < bzptr)
                     {
                        *bptr++ = *cptr++;
                        continue;
                     }
                     if (STR_DSC_IS_EXTERNAL(sdptr)) goto FaolSAKovf;
                     STR_DSC_LEN(sdptr) = bptr - STR_DSC_PTR(sdptr);
                     sdptr = StrDscBuffer (StrDscPtr);
                     bptr = STR_DSC_PTR(sdptr) + STR_DSC_LEN(sdptr);
                     bzptr = STR_DSC_PTR(sdptr) + STR_DSC_SIZE(sdptr);
                  }
               }
            }
            else
            if (UrlEncode)
            {
               /*********************/
               /* URL-encode string */
               /*********************/

               while (*sptr && fw && ofw)
               {
                  fw--;
                  ofw--;
                  cptr = NULL;
                  if (Utf8Encode && (*sptr & 0x80))
                  {
                     cptr = String;
                     ch = ((*sptr & 0xc0) >> 6) | 0xc0;
                     cptr[0]= FaoUrlEncodeTable[(uchar)ch][0];
                     cptr[1]= FaoUrlEncodeTable[(uchar)ch][1];
                     cptr[2]= FaoUrlEncodeTable[(uchar)ch][2];
                     ch = (*sptr++ & 0x3f) | 0x80;
                     cptr[3]= FaoUrlEncodeTable[(uchar)ch][0];
                     cptr[4]= FaoUrlEncodeTable[(uchar)ch][1];
                     cptr[5]= FaoUrlEncodeTable[(uchar)ch][2];
                     cptr[6] = '\0';
                  }
                  else
                  {
                     if (ForceLowerCase)
                        ch = TOLO(*sptr++);
                     else
                     if (ForceUpperCase)
                        ch = TOUP(*sptr++);
                     else
                        ch = *sptr++;

                     if (QuoteIfSpace && ch == '\"')
                        cptr = "%5c%22";
                     else
                        cptr = FaoUrlEncodeTable[(uchar)ch];
                  }

                  while (*cptr)
                  {
                     if (bptr < bzptr)
                     {
                        *bptr++ = *cptr++;
                        continue;
                     }
                     if (STR_DSC_IS_EXTERNAL(sdptr)) goto FaolSAKovf;
                     STR_DSC_LEN(sdptr) = bptr - STR_DSC_PTR(sdptr);
                     sdptr = StrDscBuffer (StrDscPtr);
                     bptr = STR_DSC_PTR(sdptr) + STR_DSC_LEN(sdptr);
                     bzptr = STR_DSC_PTR(sdptr) + STR_DSC_SIZE(sdptr);
                  }
               }
            }
            else
            {
               /******************/
               /* literal string */
               /******************/

               ch = '\0';
               while (*sptr && fw && ofw)
               {
                  if (bptr < bzptr)
                  {
                     if (ch)
                     {
                        *bptr++ = ch;
                        ch = '\0';
                        sptr++;
                     }
                     else
                     if (QuoteIfSpace && *sptr == '\"')
                     {
                        *bptr++ = '\\';
                        ch = *sptr;
                     }
                     else
                     if (PeriodNonPrintable && !isprint(*sptr))
                     {
                        *bptr++ = '.';
                        sptr++;
                     }
                     else
                     if (SlashNonPrintable && !isprint(*sptr))
                     {
                        switch (*sptr)
                        {
                           case '\a' : ch = 'a'; break;
                           case '\b' : ch = 'b'; break;
                           case '\f' : ch = 'f'; break;
                           case '\n' : ch = 'n'; break;
                           case '\r' : ch = 'r'; break;
                           case '\t' : ch = 't'; break;
                           case '\v' : ch = 'v'; break;
                           default : ch = '.';
                        }
                        *bptr++ = '\\';
                     }
                     else
                     if (Utf8Encode && (*sptr & 0x80))
                     {
                        *bptr++ = ((*sptr & 0xc0) >> 6) | 0xc0;
                        if (bptr < bzptr)
                           *bptr++ = (*sptr++ & 0x3f) | 0x80;
                        else
                           ch = (*sptr & 0x3f) | 0x80;
                     }
                     else
                     if (ForceLowerCase)
                        *bptr++ = TOLO(*sptr++);
                     else
                     if (ForceUpperCase)
                        *bptr++ = TOUP(*sptr++);
                     else
                        *bptr++ = *sptr++;
                     fw--;
                     ofw--;
                     continue;
                  }
                  if (STR_DSC_IS_EXTERNAL(sdptr)) goto FaolSAKovf;
                  STR_DSC_LEN(sdptr) = bptr - STR_DSC_PTR(sdptr);
                  sdptr = StrDscBuffer (StrDscPtr);
                  bptr = STR_DSC_PTR(sdptr) + STR_DSC_LEN(sdptr);
                  bzptr = STR_DSC_PTR(sdptr) + STR_DSC_SIZE(sdptr);
               }
            }

            if (QuoteIfSpace || TrailingNewlineString || UnderlineIfSpace)
            {
               if (QuoteIfSpace)
                  cptr = "\"";
               else
               if (TrailingNewlineString)
                  cptr = "\n";
               else
                  cptr = "</u>";
               while (*cptr)
               {
                  if (bptr < bzptr)
                  {
                     *bptr++ = *cptr++;
                     continue;
                  }
                  if (STR_DSC_IS_EXTERNAL(sdptr)) goto FaolSAKovf;
                  STR_DSC_LEN(sdptr) = bptr - STR_DSC_PTR(sdptr);
                  sdptr = StrDscBuffer (StrDscPtr);
                  bptr = STR_DSC_PTR(sdptr) + STR_DSC_LEN(sdptr);
                  bzptr = STR_DSC_PTR(sdptr) + STR_DSC_SIZE(sdptr);
               }
            }
         }

         /***************************************************/
         /* if positive field-width, right-fill with spaces */
         /***************************************************/

         if (fw <= 0) continue;

         while (fw > 0 && ofw)
         {
            if (bptr < bzptr)
            {
               *bptr++ = ' ';
               fw--;
               ofw--;
               continue;
            }
            if (STR_DSC_IS_EXTERNAL(sdptr)) goto FaolSAKovf;
            STR_DSC_LEN(sdptr) = bptr - STR_DSC_PTR(sdptr);
            sdptr = StrDscBuffer (StrDscPtr);
            bptr = STR_DSC_PTR(sdptr) + STR_DSC_LEN(sdptr);
            bzptr = STR_DSC_PTR(sdptr) + STR_DSC_SIZE(sdptr);
         }
      }

      /*********************/
      /* end format string */
      /*********************/

      if (VMSnok (status)) break;

      /* if there are still pointers on the format stack */
      if (DEBUG_FAOSAK) fprintf (stdout, "idx: %d\n", idx);
      if (!idx) break;
      idx--;
   }

   /*****************************/
   /* end nested format strings */
   /*****************************/

   if (VMSnok (status))
   {
      if (WATCHMOD (rqptr, WATCH_MOD_FAO))
         WatchThis (WATCHITM(rqptr), WATCH_MOD_FAO, "ERROR !AZ", LastFptr);

      /* if an error was detected append from the offending directive */
      fptr = LastFptr;
      while (*fptr)
      {
         if (bptr < bzptr)
         {
            *bptr++ = *fptr++;
            fw--;
            ofw--;
            continue;
         }
         if (STR_DSC_IS_EXTERNAL(sdptr)) goto FaolSAKovf;
         STR_DSC_LEN(sdptr) = bptr - STR_DSC_PTR(sdptr);
         sdptr = StrDscBuffer (StrDscPtr);
         bptr = STR_DSC_PTR(sdptr) + STR_DSC_LEN(sdptr);
         bzptr = STR_DSC_PTR(sdptr) + STR_DSC_SIZE(sdptr);
      }
   }

   *bptr = '\0';
   STR_DSC_LEN(sdptr) = bptr - STR_DSC_PTR(sdptr);

   if (LengthPtr)
   {
      if (STR_DSC_LEN(sdptr) > 65535)
      {
         /* indicate that we can't represent what we've written to buffer */
         *LengthPtr = 65535;
         status = SS$_BUFFEROVF;
      }
      else
         *LengthPtr = (ushort)STR_DSC_LEN(sdptr);
   }

   if (Watch.Category)
   {
      /* if we're WATCHing anything at all */
      if (VMSnok (status))
      {
         /* and an error is being reported */
         int  cnt;
         WatchThis (WATCHITM(rqptr), WATCH_MOD_FAO, "!&S", status);
         WatchDataFormatted ("!&Z\n", FormatString);
         for (cnt = 0; cnt <= idx; cnt++)
            WatchDataFormatted ("!UL !&Z\n", cnt, FormatStack[cnt]);
      }
   }

   return (status);

   FaolSAKovf:
   {
      /* writing to storage and it's overflowed */
      *bptr = '\0';
      STR_DSC_LEN(sdptr) = bptr - STR_DSC_PTR(sdptr);
      if (LengthPtr) *LengthPtr = (ushort)STR_DSC_LEN(sdptr);
      return (SS$_BUFFEROVF);
   }
}

/*****************************************************************************/
/*
Sanity bugchecks if |VectorPtr| has overflowed the |FaoVector| space. 
Should be called immedaitely before the FaoToBuffer() call for writes that have
the potential to become buggy because of code modifications (and yes, I got
caught, and yes it cost me a few hours).
*/

FaoCheck
(
int SizeOfFaoVector,
ulong *FaoVectorPtr,
ulong *VectorPtr,
char *SourceModuleName,
int SourceLineNumber
)
{
   char  String [64];

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

   if ((uchar*)VectorPtr - (uchar*)FaoVectorPtr <= SizeOfFaoVector) return;

   sprintf (String, "storage: %d vector: %d (longwords)",
            SizeOfFaoVector/ sizeof(ulong),
            ((uchar*)VectorPtr - (uchar*)FaoVectorPtr) / sizeof(ulong));

   ErrorExitVmsStatus (SS$_BUGCHECK, String,
                       SourceModuleName, SourceLineNumber);
}

/*****************************************************************************/
/*
This is a variable-argument wrapper for FaolSAK(), see that for greater
detail.  This function just gets all the arguments from the call stack and puts
them into a longword vector that FaolSAK() can use.  'LengthPtr'
provides storage for returning the length of the generated string.  The
resultant string is always null-terminated (unless an error), hence the
'BufferSize' can always only store 'BufferSize'-1 characters.  'LengthPtr'
provides storage for the number of characters minus the null-termination.  If
'BufferSize' is passed containing -1 then the 'BufferPtr' specifies a STR_DSC
structure.
*/

int FaoToBuffer
(
char *BufferPtr,
int BufferSize,
ushort *LengthPtr,
char *FormatString,
...
)
{
   int  status,
        argcnt;
   ulong  *vecptr;
   ulong  FaoVector [128];
   va_list  argptr;

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

   va_count (argcnt);

   if WATCH_MODULE(WATCH_MOD_FAO)
      WatchThis (WATCHALL, WATCH_MOD_FAO, "FaoToBuffer() !UL !8XL !SL",
                 argcnt, BufferPtr, BufferSize);

   if (argcnt > 128+4) return (SS$_OVRMAXARG);

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

   status = FaolSAK (NULL, BufferPtr, BufferSize, LengthPtr,
                     FormatString, &FaoVector);

   if WATCH_MODULE(WATCH_MOD_FAO)
      WatchThis (WATCHALL, WATCH_MOD_FAO, "!&S", status);

   return (status);
}

/*****************************************************************************/
/*
This is a variable-argument wrapper for FaolSAK(), see that for greater
detail.  This function just gets all the arguments from the call stack and puts
them into a longword vector that FaolSAK() can use.  Output is to the client
network write descriptor.
*/

int FaoToNet
(
REQUEST_STRUCT *rqptr,
char *FormatString,
...
)
{
   int  status,
        argcnt;
   ulong  *vecptr;
   ulong  FaoVector [128];
   va_list  argptr;

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

   va_count (argcnt);

   if (WATCHMOD (rqptr, WATCH_MOD_FAO))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_FAO, "FaoToNet() !UL", argcnt);

   if (argcnt > 128+2) return (SS$_OVRMAXARG);

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

   status = FaolSAK (rqptr, NULL, 0, NULL, FormatString, &FaoVector);

   if (WATCHMOD (rqptr, WATCH_MOD_FAO))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_FAO, "!&S", status);

   return (status);
}

/****************************************************************************/
/*
A fixed-size, internal buffer of 986 bytes maximum is used and the result
output as an OPCOM message.
*/

int FaoToOpcom
(
char *FormatString,
...
)
{
   static $DESCRIPTOR (OpcomDsc, "");

   int  status,
        argcnt;
   ushort  slen;
   ulong  *vecptr;
   ulong  FaoVector [128+2];
   va_list  argptr;
   struct
   {
      ulong  TargetType;
      ulong  RequestId;
      char  MsgText [986+1];
   } OpcomMsg;

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

   va_count (argcnt);

   if WATCH_MODULE(WATCH_MOD_FAO)
      WatchThis (WATCHALL, WATCH_MOD_FAO,
                 "FaoToOpcom() !UL !AZ", argcnt, FormatString);

   if (argcnt > 128+1) return (SS$_OVRMAXARG);

   vecptr = FaoVector;
   *vecptr++ = HttpdProcess.PrcNam;
   *vecptr++ = FormatString;
   va_start (argptr, FormatString);
   for (argcnt -= 1; argcnt; argcnt--)
      *vecptr++ = va_arg (argptr, ulong);
   va_end (argptr);

   status = FaolSAK (NULL, OpcomMsg.MsgText, sizeof(OpcomMsg.MsgText), &slen,
                     "Process !AZ reports\r\n!&@", &FaoVector);
   if (VMSnok (status)) FaoErrorNoticed (status, NULL, FI_LI);

   if (DEBUG_FAOSAK) 
      fprintf (stdout, "%d |%s|\n", slen, OpcomMsg.MsgText);

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

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

   status = sys$sndopr (&OpcomDsc, 0);
   if (VMSnok (status)) FaoErrorNoticed (status, NULL, FI_LI);

   return (status);
}

/****************************************************************************/
/*
A fixed-size, (fairly large) internal buffer is used and the result output to
the <stdout> stream.
*/

int FaoToStdout
(
char *FormatString,
...
)
{
   int  status,
        argcnt;
   ushort  slen;
   ulong  *vecptr;
   ulong  FaoVector [128];
   char  Buffer [32767];
   va_list  argptr;

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

   va_count (argcnt);

   if WATCH_MODULE(WATCH_MOD_FAO)
      WatchThis (WATCHALL, WATCH_MOD_FAO,
                 "FaoToStdout() !UL !AZ", argcnt, FormatString);

   if (argcnt > 128+1) return (SS$_OVRMAXARG);

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

   status = FaolSAK (NULL, Buffer, sizeof(Buffer), &slen,
                     FormatString, &FaoVector);
   if (VMSnok (status)) FaoErrorNoticed (status, NULL, FI_LI);

   fputs (Buffer, stdout);

   return (status);
}

/*****************************************************************************/
/*
If |rqptr| is NULL then to <stdout> or the network if not.  For sysPlus..().
*/

int FaoToEither
(
REQUEST_STRUCT *rqptr,
char *FormatString,
...
)
{
   int  status,
        argcnt;
   ushort  slen;
   ulong  *vecptr;
   ulong  FaoVector [128];
   char  Buffer [32767];
   va_list  argptr;

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

   va_count (argcnt);

   if WATCH_MODULE(WATCH_MOD_FAO)
      WatchThis (WATCHALL, WATCH_MOD_FAO,
                 "FaoToEither() !UL !UL", rqptr, argcnt);

   if (rqptr == (REQUEST_STRUCT*)-1) return (SS$_NORMAL);

   if (argcnt > 128+2) return (SS$_OVRMAXARG);

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

   if (rqptr)
      status = FaolSAK (rqptr, NULL, 0, NULL, FormatString, &FaoVector);
   else
   {
      status = FaolSAK (NULL, Buffer, sizeof(Buffer), &slen,
                        FormatString, &FaoVector);
      if (VMSok (status)) fputs (Buffer, stdout);
   }
   if (VMSnok (status)) FaoErrorNoticed (status, NULL, FI_LI);

   return (status);
}

/****************************************************************************/
/*
A fixed-size, (fairly large) internal buffer is used and the result output
usinf LIB$PUT_OUTPUT().  Initially for before DECC is intialised.  Can be used
for any standard output that bypasses the CRTL.
*/

int FaoLibPutOutput
(
char *FormatString,
...
)
{
   int  status,
        argcnt;
   ushort  slen;
   ulong  *vecptr;
   ulong  FaoVector [128];
   char  Buffer [32767];
   $DESCRIPTOR (BufferDsc, Buffer);
   va_list  argptr;

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

   va_count (argcnt);

   if WATCH_MODULE(WATCH_MOD_FAO)
      WatchThis (WATCHALL, WATCH_MOD_FAO,
                 "FaoLibPutOutput() !UL !AZ", argcnt, FormatString);

   if (argcnt > 128+1) return (SS$_OVRMAXARG);

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

   status = FaolSAK (NULL, Buffer, sizeof(Buffer), &slen,
                     FormatString, &FaoVector);
   if (VMSnok (status)) FaoErrorNoticed (status, NULL, FI_LI);

   BufferDsc.dsc$w_length = slen;
   status = lib$put_output (&BufferDsc);
   if (VMSnok (status)) FaoErrorNoticed (status, NULL, FI_LI);

   return (status);
}

/*****************************************************************************/
/*
ErrorNoticed() uses FaoToStdout() and FaoToOpcom().
Let's not potentially compound our problems by trying to use ErrorNoticed()
from within those functions!
*/

FaoErrorNoticed
(
int  StatusValue,
char *Explanation,
char *SourceModuleName,
int SourceLineNumber
)
{
   static char  CurrentTime64 [24],
                GetMsgBuffer [256];
   static $DESCRIPTOR (CurrentTime64Dsc, CurrentTime64);
   static $DESCRIPTOR (GetMsgDsc, GetMsgBuffer);
   static $DESCRIPTOR (TimeFaoDsc, "!20%D\0");

   ushort  slen;

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

   if WATCH_MODULE(WATCH_MOD_FAO)
      WatchThis (WATCHALL, WATCH_MOD_FAO, "FaoErrorNoticed()");

   sys$fao (&TimeFaoDsc, NULL, &CurrentTime64Dsc, 0);

   sys$getmsg (StatusValue, &slen, &GetMsgDsc, 0, 0);
   GetMsgBuffer[slen] = '\0';

   fprintf (stdout, "%%HTTPD-W-NOTICED, %s, %s:%d, %s, %%X%08.08X\n-%s\n",
            CurrentTime64, SourceModuleName, SourceLineNumber,
            Explanation, StatusValue, GetMsgBuffer+1);
}

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

