/*****************************************************************************
/*
                                  StrDsc.c

One of the basic constraints with standard VMS string descriptors is the
limited size - 65535 characters max (ignoring 64 bit descriptors which are not
supported on all platforms WASD can be built upon).  While 65k might seem an
awful lot there are circumstances where using a standard descriptor might be a
limitation.  WASD descriptors use 32 bit pointers and counters.

They also include pointers to allow the creation of a linked list of them. 
Where data does not need to be contiguous (as with output) this makes it
possible to build large buffers of data without memory reallocation (and the
expensive memory copying associated).  Where data does need to be contiguous
the WASD descriptor buffer can be grown through reallocating memory (and
transparently moving content to the new location as necessary - a'la the C-RTL
realloc()).  The pointed-to strings are always null-terminated and so
non-linked descriptors are amenable to processing with C-RTL functions, etc. 

A number of functions below allow these descriptors to be easily created, built
(filled with data), and disposed of.  In addition FaolSAK() understands the
structure and can write formatted data into a descriptor, linking descriptors
or growing the buffer as required over multiple formatted writes.

Even more usefully, NetWriteStrDsc() will write one or a linked list of these
descriptors to the client.

The 'itself' member of the structure is used to both detect whether a structure
has been initialised and also as a subsequent sanity check where functions
expect an initialised structure.  It does this by being set to the bit-wise
negation of address of itself (to avoid an random stack content pointer match)
and reset when disposed of.  A sanity check of this and the flags field helps
ensure integrity of descriptor usage during run-time.

Allocated storage using VmGet() and VmGetHeap() returns zeroed storage so
initialised descriptors are not a problem.  On the stack with function
automatic storage is a different situation and descriptors should be explicitly
zeroed during declaration using the STR_DSC_AUTO macro.


VERSION HISTORY
---------------
02-MAY-2022  MGD  StrDscPrintf()
09-JUN-2007  MGD  a long, long time coming but WASD string descriptors
                    (and the associated functions) have finally arrived! 
*/
/*****************************************************************************/

#ifdef WASD_VMS_V7
#undef _VMS__V6__SOURCE
#define _VMS__V6__SOURCE
#undef __VMS_VER
#undef __VMS_VER
#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 <ssdef.h>
#include <stsdef.h>

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

#define WASD_MODULE "STRDSC"

/* obviously low-level debug can't use StrDsc..() functions - aka WATCH! */
#define DEBUG_STRDSC 0

#define ITSELF(ptr) ((STR_DSC*)~(unsigned long)ptr)

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

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

extern char  ErrorSanityCheck[];

extern WATCH_STRUCT  Watch;

/*****************************************************************************/
/*
Initialise (or reinitialise) a descriptor ready for use.  If 'sdptr' is NULL
create a dynamically allocated descriptor otherwise use the supplied pointer. 
If 'size' is -1 allocate the default quantity of storage; if 0 allocate no
storage; or allocate the indicated quantity.
*/

STR_DSC* StrDscBegin
(
REQUEST_STRUCT *rqptr,
STR_DSC *sdptr,
int size
)
{
   unsigned long  flags = 0;

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

   if (DEBUG_STRDSC) fprintf (stdout, "StrDscBegin()\n");

   if (size < 0) size = STR_DSC_DEFAULT_SIZE;

   if (sdptr)
   {
      if (STR_DSC_SANITY(sdptr))
      {
         flags = sdptr->flags;
         StrDscEnd (sdptr, FI_LI);
      }
      else
         memset (sdptr, 0, sizeof(STR_DSC));
   }

   if (!sdptr || (flags & STR_DSC_FLAG_DYNAMIC))
   {
      if (rqptr)
      {
         sdptr = VmGetHeap (rqptr, sizeof(STR_DSC));
         if (sdptr->size = size) sdptr->pointer = VmGetHeap (rqptr, size);
      }
      else
      {
         sdptr = VmGet (sizeof(STR_DSC));
         if (sdptr->size = size) sdptr->pointer = VmGet (size);
#if WATCH_MOD
         if (size) StrDscWatchNotHeap (FI_LI);
#endif
      }
      sdptr->flags = STR_DSC_FLAG_FLAG | STR_DSC_FLAG_DYNAMIC |
                                         STR_DSC_FLAG_STORAGE;
   }
   else
   {
      if (rqptr)
      {
         if (sdptr->size = size) sdptr->pointer = VmGetHeap (rqptr, size);
      }
      else
      {
         if (sdptr->size = size) sdptr->pointer = VmGet (size);
#if WATCH_MOD
         if (size) StrDscWatchNotHeap (FI_LI);
#endif
      }
      sdptr->flags = STR_DSC_FLAG_FLAG | STR_DSC_FLAG_STORAGE;
   }

   sdptr->itself = ITSELF(sdptr);
   sdptr->rqptr = rqptr;

   return (sdptr);
}

/*****************************************************************************/
/*
Free this fixed or dynamically allocated WASD string descriptor.
*/

StrDscEnd
(
STR_DSC *sdptr,
char *SourceModuleName,
int SourceLineNumber
)
{
   STR_DSC  *flink;

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

   if (DEBUG_STRDSC) fprintf (stdout, "StrDscEnd()\n");

   /* just consider it to have been unused */
   if (!STR_DSC_SANITY(sdptr)) return;

   while (sdptr)
   {
      if (!STR_DSC_SANITY(sdptr))
         ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck,
                             SourceModuleName, SourceLineNumber);

      flink = sdptr->flink;
      if (sdptr->rqptr)
      {
         if (sdptr->pointer && (sdptr->flags & STR_DSC_FLAG_STORAGE))
            VmFreeFromHeap (sdptr->rqptr, sdptr->pointer,
                            SourceModuleName, SourceLineNumber);

         if (sdptr->flags & STR_DSC_FLAG_DYNAMIC)
            VmFreeFromHeap (sdptr->rqptr, sdptr,
                            SourceModuleName, SourceLineNumber);
         else
            memset (sdptr, 0, sizeof(STR_DSC));
      }
      else
      {
         if (sdptr->pointer && (sdptr->flags & STR_DSC_FLAG_STORAGE))
            VmFree (sdptr->pointer, SourceModuleName, SourceLineNumber);

         if (sdptr->flags & STR_DSC_FLAG_DYNAMIC)
            VmFree (sdptr, SourceModuleName, SourceLineNumber);
         else
            memset (sdptr, 0, sizeof(STR_DSC));
      }
      sdptr = flink;
   }
}

/*****************************************************************************/
/*
Ensure that a descriptor has been initialised.  This is different to
StrDscBegin() in that it doesn't re-initialise.
*/

STR_DSC* StrDscIfNotBegin
(
REQUEST_STRUCT *rqptr,
STR_DSC *sdptr,
int BufferSize
)
{
   /*********/
   /* begin */
   /*********/

   if (DEBUG_STRDSC) fprintf (stdout, "StrDscIfNotBegin()\n");

   if (!sdptr || !STR_DSC_SANITY(sdptr)) 
      sdptr = StrDscBegin (rqptr, sdptr, BufferSize);

   return (sdptr);
}

/*****************************************************************************/
/*
Ensure that a collection descriptor has been initialised.
*/

STR_DSC* StrDscColIfNotBegin
(
REQUEST_STRUCT *rqptr,
STR_DSC *sdptr,
int BufferSize
)
{
   /*********/
   /* begin */
   /*********/

   if (DEBUG_STRDSC) fprintf (stdout, "StrDscColIfNotBegin()\n");

   if (!sdptr || !STR_DSC_SANITY(sdptr)) 
   {
      sdptr = StrDscBegin (rqptr, sdptr, BufferSize);
      sdptr->flags |= STR_DSC_FLAG_COLLECTION;
   }
   return (sdptr);
}

/*****************************************************************************/
/*
Reallocate memory to the descriptor.  In this case a non-zero 'grow' parameter
is the *additional* memory to allocate, otherwise '->realloc' will be used.
*/

int StrDscRealloc
(
REQUEST_STRUCT *rqptr,
STR_DSC *sdptr,
int grow,
char *SourceModuleName,
int SourceLineNumber
)
{
   /*********/
   /* begin */
   /*********/

   if (DEBUG_STRDSC) fprintf (stdout, "StrDscRealloc()\n");

   if (!grow) grow = sdptr->realloc;
   if (!grow) grow = STR_DSC_DEFAULT_SIZE;

   if (!STR_DSC_SANITY(sdptr)) 
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

   if (sdptr->rqptr)
      sdptr->pointer = VmReallocHeap (sdptr->rqptr,
                                      sdptr->pointer, sdptr->size += grow,
                                      SourceModuleName, SourceLineNumber);
   else
   {
#if WATCH_MOD
      StrDscWatchNotHeap (FI_LI);
#endif
      sdptr->pointer = VmRealloc (sdptr->pointer, sdptr->size += grow,
                                  SourceModuleName, SourceLineNumber);
   }

   return (sdptr->size);
}

/*****************************************************************************/
/*
Set the descriptor so it points at some external (array of char) buffer.
*/

STR_DSC* StrDscExternal
(
REQUEST_STRUCT *rqptr,
STR_DSC *sdptr,
char *BufferPtr,
int BufferSize
)
{
   /*********/
   /* begin */
   /*********/

   if (DEBUG_STRDSC) fprintf (stdout, "StrDscExternal()\n");

   sdptr = StrDscBegin (rqptr, sdptr, 0);
   sdptr->flags &= ~STR_DSC_FLAG_STORAGE;
   sdptr->flags |= STR_DSC_FLAG_EXTERNAL;
   sdptr->pointer = BufferPtr;
   sdptr->size = BufferSize;
   return (sdptr);
}

/*****************************************************************************/
/*
Set the descriptor so it points at some external, null-terminated text. 
Intended for constants or other text requiring a descriptor but be careful not
to otherwise pull the text out from under it!

NOTE: this seems to have an issue with the likes of StrDscThis(..,"\n",1) and I
haven't been able to determined why.
*/

STR_DSC* StrDscThis
(
REQUEST_STRUCT *rqptr,
STR_DSC *sdptr,
char *TextPtr,
int TextLength
)
{
   /*********/
   /* begin */
   /*********/

   if (DEBUG_STRDSC) fprintf (stdout, "StrDscThis()\n");

   if (TextLength <= 0 && TextPtr) TextLength = strlen(TextPtr);
   sdptr = StrDscBegin (rqptr, sdptr, 0);
   sdptr->flags &= ~STR_DSC_FLAG_STORAGE;
   sdptr->flags |= STR_DSC_FLAG_EXTERNAL;
   sdptr->pointer = TextPtr;
   sdptr->size = (sdptr->length = TextLength) + 1;
   return (sdptr);
}

/*****************************************************************************/
/*
This function is intended for quick, ad hoc, string descriptor variables, often
on automatic storage, or where the text must persist.
*/

STR_DSC* StrDscText
(
REQUEST_STRUCT *rqptr,
STR_DSC *sdptr,
char *TextPtr,
int TextLength
)
{
   /*********/
   /* begin */
   /*********/

   if (DEBUG_STRDSC) fprintf (stdout, "StrDscText()\n");

   if (TextLength <= 0) TextLength = strlen(TextPtr);
   sdptr = StrDscBegin (rqptr, sdptr, TextLength+1);
   memcpy (sdptr->pointer, TextPtr, TextLength);
   sdptr->length = TextLength;

   return (sdptr);
}

/*****************************************************************************/
/*
Given a pointer to a (potential) list of descriptors, find the first one with
space remaining and return a pointer to that.  The exact byte within that
descriptor can then be calculated using its pointer and length.  If there is no
descriptor with space remaining then either reallocate extra space for the
current descriptor, or create another one and add it to the list.  Request and
non-request related descriptors are distinguished by the presence or absence of
a request pointer in the structure.
*/

STR_DSC* StrDscBuffer (STR_DSC *sd1ptr)

{
   REQUEST_STRUCT  *rqptr;
   STR_DSC  *sd2ptr;

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

   if (DEBUG_STRDSC) fprintf (stdout, "StrDscBuffer()\n");

   if (!STR_DSC_SANITY(sd1ptr))
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

   if (sd1ptr->flags & STR_DSC_FLAG_EXTERNAL) return (sd1ptr);

   if (!(sd1ptr->flags & STR_DSC_FLAG_FROZEN))
      if (sd1ptr->pointer && sd1ptr->length < sd1ptr->size)
         return (sd1ptr);

   rqptr = sd1ptr->rqptr;

   /* find the next descriptor with available storage space (if any) */
   for (sd2ptr = sd1ptr; sd2ptr; sd2ptr = sd2ptr->flink)
   {
      if (!STR_DSC_SANITY(sd2ptr))
         ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
      if (sd2ptr->flags & STR_DSC_FLAG_FROZEN) continue;
      if (sd2ptr->pointer && sd2ptr->length < sd2ptr->size) return (sd2ptr);
   }

   if (!sd1ptr->size) sd1ptr->size = STR_DSC_DEFAULT_SIZE;

   if (rqptr)
   {
      /* request storage */
      if (sd1ptr->pointer)
      {
         if (sd1ptr->realloc && !(sd1ptr->flags & STR_DSC_FLAG_FROZEN))
         {
            StrDscRealloc (rqptr, sd1ptr, sd1ptr->realloc, FI_LI);
            return (sd1ptr);
         }
         else
         {
            /* subsequent allocation of buffer */
            sd2ptr = VmGetHeap (rqptr, sizeof(STR_DSC));
            sd2ptr->itself = ITSELF(sd2ptr);
            sd2ptr->pointer = VmGetHeap (rqptr, sd2ptr->size = sd1ptr->size);
            sd2ptr->flags = STR_DSC_FLAG_FLAG | STR_DSC_FLAG_DYNAMIC |
                                                STR_DSC_FLAG_STORAGE;
            sd2ptr->rqptr = rqptr;
            /* append it to the end of the list */
            while (sd1ptr->flink) sd1ptr = sd1ptr->flink;
            sd1ptr->flink = sd2ptr;
            sd2ptr->blink = sd1ptr;
            return (sd2ptr);
         }
      }
      else
      {
         /* initial allocation of buffer */
         sd1ptr->itself = ITSELF(sd1ptr);
         sd1ptr->pointer = VmGetHeap (rqptr, sd1ptr->size);
         sd1ptr->flags |= STR_DSC_FLAG_STORAGE;
         sd1ptr->length = 0;
         return (sd1ptr);
      }
   }
   else
   {
      /* non-request storage */
#if WATCH_MOD
      StrDscWatchNotHeap (FI_LI);
#endif
      if (sd1ptr->pointer)
      {
         if (sd1ptr->realloc && !(sd1ptr->flags & STR_DSC_FLAG_FROZEN))
         {
            StrDscRealloc (NULL, sd1ptr, sd1ptr->realloc, FI_LI);
            return (sd1ptr);
         }
         else
         {
            /* subsequent allocation of buffer */
            sd2ptr = VmGet (sizeof(STR_DSC));
            sd2ptr->itself = ITSELF(sd2ptr);
            sd2ptr->pointer = VmGet (sd2ptr->size = sd1ptr->size);
            sd2ptr->flags = STR_DSC_FLAG_FLAG | STR_DSC_FLAG_DYNAMIC |
                                                STR_DSC_FLAG_STORAGE;
            sd1ptr->flink = sd2ptr;
            sd2ptr->blink = sd1ptr;
            return (sd2ptr);
         }
      }
      else
      {
         /* initial allocation of buffer */
         sd1ptr->itself = ITSELF(sd1ptr);
         sd1ptr->pointer = VmGet (sd1ptr->size);
         sd1ptr->flags |= STR_DSC_FLAG_STORAGE;
         sd1ptr->length = 0;
         return (sd1ptr);
      }
   }
}

/*****************************************************************************/
/*
Return the text length of a (potentially) list of descriptors.
*/

int StrDscLength (STR_DSC *sdptr)

{
   int  len;

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

   if (DEBUG_STRDSC) fprintf (stdout, "StrDscLength()\n");

   len = 0;
   while (sdptr)
   { 
      if (!STR_DSC_SANITY(sdptr))
         ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
      len += sdptr->length;
      sdptr = sdptr->flink;
   }
   return (len);
}

/*****************************************************************************/
/*
Return a pointer to the final entry in a list of descriptors.
*/

STR_DSC* StrDscTail (STR_DSC *sdptr)

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

   if (DEBUG_STRDSC) fprintf (stdout, "StrDscTail()\n");

   if (!sdptr) return (NULL);

   if (!STR_DSC_SANITY(sdptr))
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

   while (sdptr->flink)
   { 
      sdptr = sdptr->flink;
      if (!STR_DSC_SANITY(sdptr))
         ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
   }
   return (sdptr);
}

/*****************************************************************************/
/*
Freeze the contents of the final entry in a list of descriptors.
*/

STR_DSC* StrDscTailFrozen (STR_DSC *sdptr)

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

   if (DEBUG_STRDSC) fprintf (stdout, "StrDscTailFrozen()\n");

   if (!sdptr) return (NULL);

   if (!STR_DSC_SANITY(sdptr))
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

   while (sdptr->flink)
   {
      sdptr = sdptr->flink;
      if (!STR_DSC_SANITY(sdptr))
         ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
   }

   sdptr->flags |= STR_DSC_FLAG_FROZEN;
   return (sdptr);
}

/*****************************************************************************/
/*
Effectively move the sd2ptr (head-of-list) descriptor to sd1ptr overwriting and
losing it's contents including orphaning any memory it points to.  This is
intended for a fixed descriptor rather than a pointer to descriptor which can
of course be merely assigned.
*/

StrDscMove
(
STR_DSC *sd1ptr,
STR_DSC *sd2ptr
)
{
   unsigned long  flags;

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

   if (DEBUG_STRDSC) fprintf (stdout, "StrDscMove()\n");

   /* sd1ptr does not need to be sanity checked */
   if (!STR_DSC_SANITY(sd2ptr))
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

   /* there is no back-link for head-of-list descriptors */
   if (sd2ptr->flink) sd2ptr->flink->blink = sd1ptr;

   flags = sd1ptr->flags;

   memcpy (sd1ptr, sd2ptr, sizeof(STR_DSC));
   sd1ptr->itself = ITSELF(sd1ptr);
   memset (sd2ptr, 0, sizeof(STR_DSC));

   /* ensure the dynamic flag is propgated */
   if (flags & STR_DSC_FLAG_DYNAMIC)
      sd1ptr->flags |= STR_DSC_FLAG_DYNAMIC;
   else
      sd1ptr->flags &= ~STR_DSC_FLAG_DYNAMIC;
} 

/*****************************************************************************/
/*
Effectively swap these two (head-of-list) descriptors (which may be fixed
rather than allocated descriptors and therefore not amenable to mere pointer
swapping).
*/

StrDscSwap
(
STR_DSC *sd1ptr,
STR_DSC *sd2ptr
)
{
   STR_DSC  TmpDsc;

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

   if (DEBUG_STRDSC) fprintf (stdout, "StrDscSwap()\n");

   if (!STR_DSC_SANITY(sd1ptr))
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
   if (!STR_DSC_SANITY(sd2ptr))
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

   /* must be head of list descriptors */
   if (sd1ptr->blink || sd2ptr->blink)
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

   if (sd1ptr->flink) sd1ptr->flink->blink = sd2ptr;
   if (sd2ptr->flink) sd2ptr->flink->blink = sd1ptr;

   memcpy (&TmpDsc, sd1ptr, sizeof(STR_DSC));
   memcpy (sd1ptr, sd2ptr, sizeof(STR_DSC));
   sd1ptr->itself = ITSELF(sd1ptr);
   memcpy (sd2ptr, &TmpDsc, sizeof(STR_DSC));
   sd2ptr->itself = ITSELF(sd2ptr);
}

/*****************************************************************************/
/*
Swap the storage of these two descriptors (CAREFUL!).
*/

StrDscStorageSwap
(
STR_DSC *sd1ptr,
STR_DSC *sd2ptr
)
{
   STR_DSC  TmpDsc;

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

   if (DEBUG_STRDSC) fprintf (stdout, "StrDscStroageSwap()\n");

   if (!STR_DSC_SANITY(sd1ptr))
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
   if (!STR_DSC_SANITY(sd2ptr))
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

   TmpDsc.pointer = sd1ptr->pointer;
   sd1ptr->pointer = sd2ptr->pointer;
   sd2ptr->pointer = TmpDsc.pointer;

   TmpDsc.length = sd1ptr->length;
   sd1ptr->length = sd2ptr->length;
   sd2ptr->length = TmpDsc.length;

   TmpDsc.size = sd1ptr->size;
   sd1ptr->size = sd2ptr->size;
   sd2ptr->size = TmpDsc.size;
}

/*****************************************************************************/
/*
Duplicate the descriptor (potentially a list of) provided by 'sd2ptr' in the
descriptor 'sd1ptr'.  If 'sd1ptr' is NULL a new dynamic descriptor is
allocated.  A pointer to 'sd1ptr' (existing or new) is returned.
*/

STR_DSC* StrDscDup
(
STR_DSC *sd1ptr,
STR_DSC *sd2ptr
)
{
   REQUEST_STRUCT  *rqptr;
   STR_DSC  *sdptr;

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

   if (DEBUG_STRDSC) fprintf (stdout, "StrDscDup()\n");

   /* sd1ptr does not need to be sanity checked */
   if (!STR_DSC_SANITY(sd2ptr))
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

   rqptr = sd2ptr->rqptr;

   sd1ptr = StrDscBegin (rqptr, sd1ptr, sd2ptr->size);

   /* initial descriptor */
   if (sd1ptr->length = sd2ptr->length)
   {
      /* including the terminating null */
      memcpy (sd1ptr->pointer, sd2ptr->pointer, sd2ptr->length+1);
   }

   /* subsequent descriptors */
   sdptr = sd1ptr;
   for (sd2ptr = sd2ptr->flink; sd2ptr; sd2ptr = sd2ptr->flink)
   {
      if (rqptr)
         sdptr->flink = VmGetHeap (rqptr, sizeof(STR_DSC));
      else
      {
#if WATCH_MOD
         StrDscWatchNotHeap (FI_LI);
#endif
         sdptr->flink = VmGet (sizeof(STR_DSC));
      }

      /* careful! */
      sdptr->flink->blink = sdptr;
      sdptr = sdptr->flink;

      sdptr->itself = ITSELF(sdptr);
      sdptr->rqptr = rqptr;
      sdptr->size = sd2ptr->size;
      sdptr->flags = STR_DSC_FLAG_FLAG | STR_DSC_FLAG_DYNAMIC |
                                         STR_DSC_FLAG_STORAGE;
      if (rqptr)
         sdptr->pointer = VmGetHeap (rqptr, sdptr->size);
      else
         sdptr->pointer = VmGet (sdptr->size);

      /* including the terminating null */
      if (sd1ptr->length = sd2ptr->length)
      {
         /* including the terminating null */
         memcpy (sd1ptr->pointer, sd2ptr->pointer, sd2ptr->length+1);
      }
   }

   return (sd1ptr);
}

/*****************************************************************************/
/*
Comparison of the descriptor content.
*/

BOOL StrDscSame
(
STR_DSC *sd1ptr,
STR_DSC *sd2ptr,
BOOL CaseLess
)
{
   int  len1, len2;
   char  *cptr, *sptr;
   STR_DSC  *sdptr;

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

   if (DEBUG_STRDSC) fprintf (stdout, "StrDscSame()\n");

   if (!sd1ptr->pointer || !sd2ptr->pointer) return (false);

   if (!STR_DSC_SANITY(sd1ptr))
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
   if (!STR_DSC_SANITY(sd2ptr))
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

   len1 = len2 = 0;
   for (sdptr = sd1ptr; sdptr; sdptr = sdptr->flink)
      len1 += sdptr->length;
   for (sdptr = sd2ptr; sdptr; sdptr = sdptr->flink)
      len2 += sdptr->length;

   if (len1 != len2) return (false);
   if (!len1 && !len2) return (true);

   cptr = sd1ptr->pointer;
   len1 = sd1ptr->length;
   sptr = sd2ptr->pointer;
   len2 = sd2ptr->length;
   for (;;)
   {
      if (!len1)
      {
         if (!(sd1ptr = sd1ptr->flink)) break;
         cptr = sd1ptr->pointer;
         len1 = sd1ptr->length;
      }
      if (!len2)
      {
         if (!(sd2ptr = sd2ptr->flink)) break;
         sptr = sd2ptr->pointer;
         len2 = sd2ptr->length;
      }
      if (CaseLess && to_lower(*cptr) != to_lower(*sptr)) break;
      if (!CaseLess && *cptr != *sptr) break;
      cptr++;
      sptr++;
      len1--;
      len2--;
   }
   if (len1 || len2) return (false);
   return (true);
}

/*****************************************************************************/
/*
Reset a (potentially linked list of) descriptor buffer(s) to no content.
*/

StrDscNoContent (STR_DSC *sdptr)

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

   if (DEBUG_STRDSC) fprintf (stdout, "StrDscNoContent()\n");

   while (sdptr)
   {
      if (!STR_DSC_SANITY(sdptr))
         ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
      sdptr->length = 0;
      sdptr = sdptr->flink;
   }
}

/*****************************************************************************/
/*
Ensure the descriptor has a trailing newline.
Returns a pointer to the current descriptor.
*/

STR_DSC* StrDscNewLine (STR_DSC *sdptr)

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

   if (DEBUG_STRDSC) fprintf (stdout, "StrDscNewLine()\n");

   if (!STR_DSC_SANITY(sdptr))
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

   /* find the latest descriptor */
   if (!sdptr->pointer || sdptr->flags & STR_DSC_FLAG_FROZEN)
      sdptr = StrDscBuffer (sdptr);
   else
   {
      while (sdptr->length >= sdptr->size)
      {
         if (sdptr->itself != ITSELF(sdptr)) 
            ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
         if (!sdptr->flink) break;
         sdptr = sdptr->flink;
      }
   }

   if (sdptr->length && sdptr->pointer[sdptr->length-1] == '\n')
      return (sdptr);

   if (sdptr->length >= sdptr->size) sdptr = StrDscBuffer (sdptr);

   sdptr->pointer[sdptr->length++] = '\n';
   sdptr->pointer[sdptr->length] = '\0';

   return (sdptr);
}

/*****************************************************************************/
/*
Copy descriptor 2 or null-terminated string to descriptor 1 dynamically
expanding buffer space as required.  Returns a pointer to the current
descriptor.
*/

STR_DSC* StrDscBuild
(
STR_DSC *sd1ptr,
STR_DSC *sd2ptr,
char *AscizPtr
)
{
   char  *cptr, *czptr, *sptr, *zptr;
   STR_DSC  *sdptr;

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

   if (DEBUG_STRDSC) fprintf (stdout, "StrDscBuild()\n");

   if (!STR_DSC_SANITY(sd1ptr))
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
   if (sd2ptr && !STR_DSC_SANITY(sd2ptr))
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

   /* find the latest descriptor */
   if (!sd1ptr->pointer || sd1ptr->flags & STR_DSC_FLAG_FROZEN)
      sd1ptr = StrDscBuffer (sd1ptr);
   else
   {
      while (sd1ptr->length >= sd1ptr->size)
      {
         if (sd1ptr->itself != ITSELF(sd1ptr)) 
            ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
         if (!sd1ptr->flink) break;
         sd1ptr = sd1ptr->flink;
      }
   }

   if (AscizPtr)
      cptr = AscizPtr;
   else
      czptr = (cptr = sd2ptr->pointer) + sd2ptr->length;

   for (;;)
   {
      if ((AscizPtr && !*cptr) || (!AscizPtr && cptr >= czptr)) break;

      zptr = (sptr = sd1ptr->pointer) + sd1ptr->size;
      sptr += sd1ptr->length;
      if (AscizPtr)
         while (*cptr && sptr < zptr) *sptr++ = *cptr++;
      else
         while (cptr < czptr && sptr < zptr) *sptr++ = *cptr++;
      *sptr = '\0';
      sd1ptr->length = sptr - sd1ptr->pointer;

      if ((AscizPtr && !*cptr) || (!AscizPtr && cptr >= czptr)) break;

      sd1ptr = StrDscBuffer (sd1ptr);
   }

   return (sd1ptr);
}

/*****************************************************************************/
/*
Copy descriptor 2 or null-terminated string to descriptor 1 dynamically
expanding buffer space as required, HTML-escaping the '<', '>' and '&'
characters.  Returns a pointer to the current descriptor.
*/

STR_DSC* StrDscBuildHtmlEscape
(
STR_DSC *sd1ptr,
STR_DSC *sd2ptr,
char *AscizPtr
)
{
   static char  ebuf [] = "\0";

   char  *cptr, *czptr, *eptr, *sptr, *zptr;
   STR_DSC  *sdptr;

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

   if (DEBUG_STRDSC) fprintf (stdout, "StrDscBuildHtmlEscape()\n");

   if (!sd1ptr->pointer) sd1ptr = StrDscBuffer (sd1ptr);

   if (!STR_DSC_SANITY(sd1ptr))
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
   if (sd2ptr && !STR_DSC_SANITY(sd2ptr))
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);

   /* find the latest descriptor */
   if (sd1ptr->flags & STR_DSC_FLAG_FROZEN)
      sd1ptr = StrDscBuffer (sd1ptr);
   else
   {
      while (sd1ptr->length >= sd1ptr->size)
      {
         if (sd1ptr->itself != ITSELF(sd1ptr)) 
            ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
         if (!sd1ptr->flink) break;
         sd1ptr = sd1ptr->flink;
      }
   }

   if (AscizPtr)
      cptr = AscizPtr;
   else
      czptr = (cptr = sd2ptr->pointer) + sd2ptr->length;

   zptr = (sptr = sd1ptr->pointer) + sd1ptr->size;
   sptr += sd1ptr->length;

   for (;;)
   {
      /* buffer non-forbidden chars and handle end-of-source */
      if (AscizPtr)
      {
         while (*cptr && *cptr != '<' && *cptr != '>' &&
                *cptr != '&' && sptr < zptr) *sptr++ = *cptr++;
         if (!*cptr) break;
      }
      else
      {
         while (cptr < czptr && *cptr != '<' && *cptr != '>' &&
                *cptr != '&' && sptr < zptr) *sptr++ = *cptr++;
         if (cptr >= czptr) break;
      }
      /* forbidden character and buffer space exhausted drop through */

      switch (*cptr)
      {
         case '<' : eptr = "&lt;"; break;
         case '>' : eptr = "&gt;"; break;
         case '&' : eptr = "&amp;"; break;
         /* non-forbidden character (buffer space exhausted) */
         default  : *(eptr = ebuf) = *cptr;
      }
      cptr++;

      while (*eptr)
      {
         while (*eptr && sptr < zptr) *sptr++ = *eptr++;
         if (sptr >= zptr)
         {
            /* buffer space exhaustion */
            *sptr = '\0';
            sd1ptr->length = sptr - sd1ptr->pointer;
            sd1ptr = StrDscBuffer (sd1ptr);
            zptr = (sptr = sd1ptr->pointer) + sd1ptr->size;
            sptr += sd1ptr->length;
         }
      }
   }
   *sptr = '\0';
   sd1ptr->length = sptr - sd1ptr->pointer;

   return (sd1ptr);
}

/*****************************************************************************/
/*
This function acts as a snprintf() for WASD string descriptors.
When used multiple times (without reset) it builds a composite string.
*/

STR_DSC* StrDscPrintf
(
REQUEST_STRUCT *rqptr,
STR_DSC *sdptr,
int ChunkSize,
char *FormatPtr,
...
)
{
   int  count, size;
   char  *cptr;
   va_list  aptr, aptr2;

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

   if (DEBUG_STRDSC) fprintf (stdout, "StrDscPrintf()\n");

   if (ChunkSize <= 0) ChunkSize = STR_DSC_DEFAULT_SIZE;
   if (!sdptr) sdptr = StrDscBegin (rqptr, sdptr, ChunkSize);

   for (;;)
   {
      cptr = sdptr->pointer + sdptr->length;
      size = sdptr->size - sdptr->length;
      va_start (aptr, FormatPtr);
      count = vsnprintf (cptr, size, (const char*)FormatPtr, aptr);
      va_end(aptr);
      if (count < 0)
      {
         StrDscEnd (sdptr, FI_LI);
         return (NULL);
      }
      if (count > size)
         StrDscRealloc (rqptr, sdptr, count + ChunkSize, FI_LI);
      else
      {
         sdptr->length += count;
         sdptr->pointer[sdptr->length] = '\0';
         break;
      }
   }

   return (sdptr);
}

/*****************************************************************************/
/*
Has happened often enough during development to warrant a sanity check.
Avoid using standard WATCH which uses FaoToBuffer() which in turn uses
FaolSAK() which in turn uses StrDsc..()!
*/

#if WATCH_MOD

StrDscWatchNotHeap
(
char *SourceModuleName,
int SourceLineNumber
)
{
   int  len;
   char  NotHeap [128]; 

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

   len = sprintf (NotHeap, "|%11s %8s %04u %4s %10s %s|\n",
                  "", "STRDSC  ", SourceLineNumber, "", "",

                  "+++++  W A R N I N G  +++++ not request heap");
   if (Watch.LogFile) fputs (NotHeap, stdout);
   if (Watch.SecondsToGo) NetWrite (Watch.RequestPtr, 0, NotHeap, len);
}

#endif

/*****************************************************************************/
/*
Always seems a character flaw that perfect code cannot be written first-up!
*/

#if WATCH_MOD

StrDscDebug
(
STR_DSC *sdptr,
char *WhereAt
)
{
   /*********/
   /* begin */
   /*********/

   if (sdptr)
      fprintf (stdout,
"++++++++++ %s\n\
    sdptr: %08.08X\n\
           %08.08X%08.08X%08.08X%08.08X%08.08X%08.08X%08.08X\n\
 ->itself: %08.08X (%08.08X) %s\n\
  ->flags: %08.08X\n\
   ->size: %d\n\
 ->length: %d\n\
->pointer: %08.08X\n\
->realloc: %d\n\
  ->rqptr: %08.08X\n\
  ->flink: %08.08X\n\
  ->blink: %08.08X\n\
++++++++++\n",
            WhereAt ? WhereAt : "",
            sdptr,
            ((ULONGPTR)sdptr)[0], ((ULONGPTR)sdptr)[1],
            ((ULONGPTR)sdptr)[2], ((ULONGPTR)sdptr)[3],
            ((ULONGPTR)sdptr)[4], ((ULONGPTR)sdptr)[5],
            ((ULONGPTR)sdptr)[6],
            sdptr->itself, ~(unsigned long)sdptr,
            STR_DSC_SANITY(sdptr) ? "SANE" : "*",
            sdptr->flags,
            sdptr->size,
            sdptr->length,
            sdptr->pointer,
            sdptr->realloc,
            sdptr->rqptr,
            sdptr->flink,
            sdptr->blink);
   else
      fprintf (stdout,
"++++++++++ %s\n\
    sdptr: 00000000\n\
++++++++++\n",
            WhereAt ? WhereAt : "");
}

#endif

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

