/*****************************************************************************/
#ifdef COMMENTS_WITH_COMMENTS
/*
                                  rawLIB.c

Collection of routines for WASD persistent RawSocket applications (CGIplus
scripts).  Could have named it RSLIB (raw socket library) but RS means
different things to different people :-}

A "raw" socket is a variant of, and is heavily based on, the WASD WebSocket
infrastructure.  It allows a service (listening host and port) to accept a
connection and immediately activate a configured WASD CGIplus script (in much
the same manner as a standard WebSocket script) to service that connection. 
Full-duplex, asynchronous, bidirectional input/output is supported.  While
being referred to as a "socket", and in the module as a "RawSocket", it of
course is not [BSD or any other abstraction] network socket communication, but
a read/write abstraction via mailbox I/O.  Input is a stream of octets; output
a stream of octets.  To these routines the streams are opaque.  The "RawSocket"
is just WASD nomenclature for WebSocket-style scripting without the WebSocket
protocol.

These routines allow AST-driven, multi-client RawSocket applications.  These
are *much* less complex than those in wsLIB as there is no protocol implemented
and no message/frame queue.


RAWSOCKET BASICS
----------------
The script when activated enters a CGIplus wait-service-wait loop using the
standard CGIplus mechanism.  When a request activates the script it issues a
"100 Continue" CGI/HTTP header to signal the server that the request is being
processed.  This header is NOT relayed to the client.  The request parameters
are available via the usual CGI variables.  The script can then read and write
to/from the RAWSOCKET_INPUT and  RAWSOCKET_OUTPUT devices respectively.  The
end-of-script is indicated using the same WebSocket EOF to the mailbox.  The
script activation can be a long-lived as required and the server will not run a
request down for any timeout reason.


REQUEST SYNCHRONISATION
-----------------------
rawLIB start-of-request synchronisation is based on the wait-process-EOF-wait
loop model used by CGIlib, where RawLibCgiVar("") or RawLibCgiVarNull("") are
used to wait and continue when a new request is available.  The example raw
[web]socket scripts use this approach by default.  rawLIB also provides an
AST-driven model.

The RawLibOnNextRequest() function should be called once during script
initialisation specifying the address of the function to be notified of a new
request.  At that point the CGI variables become available and remain so until
the script issues the CGIplus EOF sentinal, and the RawLibCgiVar..() family of
functions can be used to retrieve the CGI variable values.  This asynchronous
and the synchronous RawLibCgiVar("") approaches to request synchronisation are
mutually exclusive and once one is used using the other results in a bugcheck.


CGI VARIABLES
-------------
CGI variables using the RawLibCgiVar..() functions are only available after the
next request has been indicated and before the CGIplus EOF sentinal is
signalled.  Any that are required for further processing need to be copied into
separate storage from the pointer returned by the RawLibCgiVar..() functions. 
With the request synchronisation provided by RawLibCgiVar("") this is usually
obvious enough.  With the AST-driven request synchronisation provided by
RawLibOnNextRequest() it might be less obvious.  In any case the
RawLibCgiVarAvailable() function can be used to test whether CGI variable are
available at that time or not.



FUNCTIONS
---------
struct RawLibStruct* RawLibCreate (void *UserDataPtr,
                                   void *DestroyFunction)

   Allocates a RawSocket structure, sets the user data pointer
   to the supplied address, and returns a pointer to the new structure.
   All subsequent associated websocket processing requires that pointer.


struct RawLibStruct* RawLibNext (struct RawLibStruct **RawLibCtx)

   Step through the list of structures.  RawLibCtx is the address of pointer to
   RawLibStruct used to hold the context.  Set to NULL to initialise.  Returns
   non-null pointers for each structure in the list, then NULL when exhausted.
   Care must be exercised that multiple calls are not preempted by list
   modification (i.e. use within AST delivery or with ASTs disabled).


int RawLibMsgDsc (struct RawLibStruct *rawptr,
                  struct dsc$descriptor_s *MsgDsc);

   Sets the supplied descriptor to the latest error string.


int RawLibMsgLineNumber (struct RawLibStruct *rawptr)

   Return the wsLIB source code line at which the last message occured.


char* RawLibMsgString (struct RawLibStruct *rawptr)

   Returns a pointer to the latest error string.


int RawLibOpen (struct RawLibStruct *rawptr,
                void *AstFunction)

   Using the device names from the CGI variables RAWSOCKET_INPUT and
   RAWSOCKET_OUTPUT assign channels in preparation for asynchronous I/O.
   The AST function is called at RawSocket close.


int RawLibClose (struct RawLibStruct* rawptr)

   Shut the RawSocket down.


int RawLibIsClosed (struct RawLibStruct* rawptr)

   Return true if the RawSocket has been closed.


int RawLibConnected (struct RawLibStruct *rawptr)

   Return true if the input/output channels are connected.


int RawLibRead (struct RawLibStruct *rawptr,
                char *DataPtr,
                int DataSize,
                void *AstFunction)

   Read data from the RawSocket into the supplied buffer.
   Function is non-blocking if AST function is supplied, otherwise blocking.
   The buffer must continue to exist until the read completes.
   When the read completes the status can be returned by RawLibReadStatus(),
   the read count by RawLibReadCount(), a pointer to the data buffer using
   RawLibReadData(), and a pointer to the internal read string descriptor
   using RawLibReadDataDsc().


int RawLibReadDsc (struct RawLibStruct *rawptr,
                   struct dsc$descriptor_s *DataDsc,
                   void *AstFunction)

   Read data from the websocket into the buffer described by DataDsc.
   Function is non-blocking if AST function is supplied, otherwise blocking.
   When completed ReadDsc (if supplied) is modified to reflect the read.


int RawLibReadStatus (struct RawLibStruct *rawptr)

   Return the status value of the most recent read.


int RawLibReadCount (struct RawLibStruct *rawptr)

   Return the number of bytes in the most recent read.


char* RawLibReadData (struct RawLibStruct *rawptr)

   Return a pointer to char of the most recent read buffer.


struct dsc$descriptor_s* RawLibReadDataDsc (struct RawLibStruct *rawptr)

   Return a pointer to the internal descriptor for the most recent read.


ulong* RawLibReadTotal (struct RawLibStruct *rawptr)

   Return a pointer to a quadword containing the total number of bytes read.


void RawLibResetMsg (struct RawLibStruct *rawptr)

   Reset the latest message data.


unsigned int RawLibTime ();

   Return the current wsLIB time in seconds (i.e. C-RTL, Unix time).


int RawLibWrite (struct RawLibStruct *rawptr,
                 char *DataPtr,
                 int DataCount,
                 void *AstFunction)

   Write the supplied data to the websocket.
   Function is non-blocking if AST function is supplied, otherwise blocking.
   The data must continue to exist until the write completes.


int RawLibWriteDsc (struct RawLibStruct *rawptr,
                    struct dsc$descriptor_s *DataDsc,
                    void *AstFunction)

   Write the data supplied by the descriptor to the websocket.
   Function is non-blocking if AST function is supplied, otherwise blocking.


int RawLibWriteStatus (struct RawLibStruct *rawptr)

   Return the status value of the most recent write.


int RawLibWriteCount (struct RawLibStruct *rawptr)

   Return the number of bytes in the most recent write.


struct dsc$descriptor_s* RawLibWriteDataDsc (struct RawLibStruct *rawptr)

   Return a pointer to the internal descriptor for the most recent write.


ulong* RawLibWriteTotal (struct RawLibStruct *rawptr)

   Return a pointer to a quadword containing the total number of bytes written.


void RawLibSetUserData (struct RawLibStruct *rawptr,
                        void *UserDataPtr)

   Set the RawSocket structure to point to the supplied user data.


void* RawLibGetUserData (struct RawLibStruct *rawptr)

   Return the current pointer to the supplied user data.


void* RawLibSetCallout (struct RawLibStruct *rawptr,
                        void *AstFunction)

   Set/reset the callout response AST.
   Returns the previous callout pointer.


void* RawLibSetMsgCallback (struct RawLibStruct *rawptr,
                            void *CallbackFunction)

   Set/reset the rawLIB error reporting callback.
   Returns the previous callback pointer.
   The error callback function is activated with a pointer to the RawLib
   data structure that has the MsgLineNumber and timestamp set and
   provides a descriptive string.


void RawLibSetIdleSecs (struct RawLibStruct *rawptr,
                        int IdleSecs);

   Set number of seconds before a RawSocket is considered idle and closed.
   If a RawSocket is not specified then set global value.


void RawLibSetLifeSecs (int LifeSecs);

   Set number of seconds before the application is considered idle and exited.


void RawLibSetReadSecs (struct RawLibStruct *rawptr,
                        int IdleSecs);

   Set number of seconds to wait for a read from a RawSocket before close.
   If a RawSocket is not specified then set global value.


void* RawLibSetWakeCallback (struct RawLibStruct *rawptr,
                             void *CallbackFunction,
                             int WakesSecs)

   Set/reset the wsLIB periodic wakeup callback.
   Returns the previous callback pointer.
   Set the number of seconds between wakeup calls (zero defaults).


void RawLibWatchScript (struct RawLibStruct *rawptr,
                        char *SourceModuleName,
                        int SourceLineNumber,
                        char *FormatString,
                        ...)

   Outputs the $FAO-formatted string to the WASD WATCH [x]Script item.


COPYRIGHT
---------
Copyright (C) 2016-2020 Mark G.Daniel

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

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

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


VERSION HISTORY
---------------
01-DEC-2016  MGD  initial (heavily based on wsLIB)

*/
#endif /* COMMENTS_WITH_COMMENTS */
/*****************************************************************************/

#define SOFTWAREVN "1.0.0"
#define SOFTWARENM "RAWLIB"
#ifdef __ALPHA
#  define SOFTWAREID SOFTWARENM " AXP-" SOFTWAREVN
#endif
#ifdef __ia64
#  define SOFTWAREID SOFTWARENM " IA64-" SOFTWAREVN
#endif
#ifdef __VAX
#  define SOFTWAREID SOFTWARENM " VAX-" SOFTWAREVN
#endif
#ifdef __x86_64
#  define SOFTWAREID SOFTWARENM " X86-" SOFTWAREVN
#endif

#include <stdarg.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#include <descrip.h>
#include <iodef.h>
#include <lib$routines.h>
#include <ssdef.h>
#include <starlet.h>
#include <stsdef.h>
#include <unixlib.h>

#include "rawlib.h"

/* from libmsg.h */
#define LIB$_INVSTRDES 1409572

#ifndef EFN$C_ENF
#  define EFN$C_ENF 128  /* Event No Flag (no stored state) */
#endif

#define AGN$M_READONLY 0x01
#define AGN$M_WRITEONLY 0x02

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

#define RAWLIB_STRUCT_BUFFER_SIZE 4096

#define FI_LI "RAWLIB", __LINE__

#if 1
#define WATCH_RAWLIB if(rawptr->WatchScript)RawLibWatchScript
#else
#define WATCH_RAWLIB if(0)RawLibWatchScript
#endif

/* used by RAWLIBCL.C */
static int  EfnWait,
            EfnNoWait;

static int  CalloutDone = 0,
            CgiPlusEofLength = 0,
            CgiPlusEotLength = 0,
            CgiPlusEscLength = 0,
            CgiVariablesAvailable = 0,
            StructBufferLength = 0,
            StructBufferSize = 0,
            WwwPrefix = 0;

static ushort  CgiPlusInChannel;

static ulong  CurrentTime,
              WatchDogWakeTime;
static ulong  CurrentBinTime [2];

#define DEFAULT_WATCHDOG_IDLE_SECS 120
#define DEFAULT_WATCHDOG_LIFE_SECS 120
#define DEFAULT_WATCHDOG_READ_SECS  60
#define DEFAULT_WATCHDOG_WAKE_SECS  60

static ulong  WatchDogIdleSecs = DEFAULT_WATCHDOG_IDLE_SECS,
              WatchDogLifeSecs = DEFAULT_WATCHDOG_LIFE_SECS,
              WatchDogReadSecs = DEFAULT_WATCHDOG_READ_SECS,
              WatchDogWakeSecs = DEFAULT_WATCHDOG_WAKE_SECS;

static char  *CgiPlusEofPtr = NULL,
             *CgiPlusEotPtr = NULL,
             *CgiPlusEscPtr = NULL,
             *NextVarNamePtr = NULL,
             *StructBufferPtr = NULL;

static struct RawLibStruct *ListHead; 

static void  (*RequestSynchronisation)() = NULL,
             (*WakeCallbackFunction)() = NULL;

static char  SoftwareID [] = SOFTWAREID;

#define SYI$_VERSION 4096
int sys$getsyi(__unknown_params);

/***
#define __VAX
#undef __ALPHA
***/

/*****************************************************************************/
/*
Initialise the library.
*/

void RawLibInit ()

{
   static char  GetSyiVer [8];
   static struct
   {
      ushort  buf_len;
      ushort  item;
      void  *buf_addr;
      void  *ret_len;
   } SyiItem [] =
   {
     { sizeof(GetSyiVer)-1, SYI$_VERSION, &GetSyiVer, 0 },
     { 0,0,0,0 }
   };

   int  status,
        VersionInteger;

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

   /* just the once! */
   if (EfnWait) return;

   status = sys$getsyiw (0, 0, 0, &SyiItem, 0, 0, 0);
   if (VMSnok (status)) RawLibExit (NULL, FI_LI, status);
   VersionInteger = ((GetSyiVer[1]-48) * 100) + ((GetSyiVer[3]-48) * 10);
   if (GetSyiVer[4] == '-') VersionInteger += GetSyiVer[5]-48;
   if (VersionInteger >= 700)
      EfnWait = EfnNoWait = EFN$C_ENF;
   else
   {
      if (VMSnok (status = lib$get_ef (&EfnWait)))
         RawLibExit (NULL, FI_LI, status);;
      if (VMSnok (status = lib$get_ef (&EfnNoWait)))
         RawLibExit (NULL, FI_LI, status);;
   }

   WatchDog ();
}

/*****************************************************************************/
/*
Return a pointer to the wsLIB version string.
*/

char* RawLibVersion ()

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

   return (SoftwareID);
}

/*****************************************************************************/
/*
Sanity check the incoming request.  Provide error or continue/upgrade response.
Allocate a RawSocket I/O structure and set the internal user data storage. 
Insert at the head of the list.  Return a pointer to the allocated structure.
Can only WATCH_RAWLIB after RawLibOpen().
*/

struct RawLibStruct* RawLibCreate
(
void *UserDataPtr,
void *DestroyFunction
)
{
   int  astatus,
        SecRawSocketVersion;
   char  *cptr, *sptr;
   struct RawLibStruct  *rawptr;

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

   if (!EfnWait) RawLibInit ();

   astatus = sys$setast (0); 

   rawptr = (struct RawLibStruct*) calloc (1, sizeof(struct RawLibStruct));
   if (!rawptr) RawLibExit (NULL, FI_LI, vaxc$errno);

   if ((cptr = getenv ("WASD_RAWLIB_WATCH_LOG")) != NULL)
      if ((rawptr->WatchLog = fopen (cptr, "w", "shr=get")) == NULL)
         RawLibExit (NULL, FI_LI, vaxc$errno);

   if (cptr = RawLibCgiVarNull("RAWSOCKET_INPUT_MRS"))
      rawptr->InputMrs = atoi(cptr);
   else
      RawLibExit (NULL, FI_LI, SS$_BUGCHECK);

   if (cptr = RawLibCgiVarNull("RAWSOCKET_OUTPUT_MRS"))
      rawptr->OutputMrs = atoi(cptr);
   else
      RawLibExit (NULL, FI_LI, SS$_BUGCHECK);

   /* connection acceptance response */
   fprintf (stdout, "Status: 101 Switching Protocols\r\n\r\n");
   fflush (stdout);

   rawptr->UserDataPtr = UserDataPtr;
   rawptr->DestroyAstFunction = DestroyFunction;
   rawptr->NextPtr = ListHead;
   ListHead = rawptr;

   if (astatus == SS$_WASSET) sys$setast (1);
   return (rawptr);
}

/*****************************************************************************/
/*
Remove from the list and free the allocated memory.  Return any user data.
*/

static void Destroy (struct RawLibStruct *rawptr)

{
   int  astatus, cnt, status;
   struct RawLibStruct  *wslptr;
   void  *UserDataPtr;
   FILE  *WatchLog;

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

   if (!rawptr) return;

   WATCH_RAWLIB (rawptr, FI_LI, "DESTROY");

   WatchLog = rawptr->WatchLog;

   astatus = sys$setast (0); 
   UserDataPtr = rawptr->UserDataPtr;

   if (rawptr->MsgStringSize) free (rawptr->MsgStringPtr);

   if ((wslptr = ListHead) == rawptr)
      ListHead = wslptr->NextPtr;
   else
   {
      while (wslptr->NextPtr != rawptr) wslptr = wslptr->NextPtr;
      wslptr->NextPtr = rawptr->NextPtr;
   }

   sys$dassgn (rawptr->OutputChannel);

   free (rawptr);
   if (astatus == SS$_WASSET) sys$setast (1);

   if (WatchLog) fclose (WatchLog);
}

/*****************************************************************************/
/*
Step through the list of structures.  RawLibCtx is the address of a pointer
used to hold the context.  Set to NULL to initialise.  Returns non-null
pointers for each structure in the list, then NULL when list exhausted, to
begin non-null pointers again.  Care must be exercised that multiple calls are
not preempted by a list modification (i.e. use within AST delivery or with ASTs
disabled).
*/

struct RawLibStruct* RawLibNext (struct RawLibStruct **RawLibCtx)
                                                    
{
   int  astatus;
   struct RawLibStruct  *rawptr, *wslptr;

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

   astatus = sys$setast (0); 
   /* let's be overcautious and make sure it's still in the list! */
   if (rawptr = *RawLibCtx)
   {
      for (wslptr = ListHead; wslptr; wslptr = wslptr->NextPtr)
         if (wslptr == rawptr) break;
      if (!wslptr) RawLibExit (NULL, FI_LI, SS$_BUGCHECK);
      *RawLibCtx = rawptr->NextPtr;
   }
   else
      *RawLibCtx = ListHead;
   if (astatus == SS$_WASSET) sys$setast (1);
   return (*RawLibCtx);
}

/*****************************************************************************/
/*
Using the device names from the CGI variables RAWSOCKET_INPUT and
RAWSOCKET_OUTPUT assign channels in preparation for asynchronous I/O.
*/

int RawLibOpen (struct RawLibStruct *rawptr)

{
   int  status;
   char  *cptr, *sptr, *zptr;
   $DESCRIPTOR (MbxDsc, "");

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

   rawptr->ReadDataDsc.dsc$b_class = 
      rawptr->ReadResultDsc.dsc$b_class = 
      rawptr->WriteDataDsc.dsc$b_class = DSC$K_CLASS_S;
   rawptr->ReadDataDsc.dsc$b_dtype =
      rawptr->ReadResultDsc.dsc$b_dtype =
      rawptr->WriteDataDsc.dsc$b_dtype = DSC$K_DTYPE_T;

   if (!(cptr = RawLibCgiVarNull("RAWSOCKET_INPUT"))) return (SS$_BUGCHECK);
   zptr = (sptr = rawptr->InputDevName) + sizeof(rawptr->InputDevName)-1;
   while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   *sptr = '\0';

   rawptr->InputDevDsc.dsc$b_class = DSC$K_CLASS_S;
   rawptr->InputDevDsc.dsc$b_dtype = DSC$K_DTYPE_T;
   rawptr->InputDevDsc.dsc$a_pointer = rawptr->InputDevName;
   rawptr->InputDevDsc.dsc$w_length = sptr - rawptr->InputDevName;

   if (!(cptr = RawLibCgiVarNull("RAWSOCKET_OUTPUT"))) return (SS$_BUGCHECK);
   zptr = (sptr = rawptr->OutputDevName) + sizeof(rawptr->OutputDevName)-1;
   while (*cptr && sptr < zptr) *sptr++ = *cptr++;
   *sptr = '\0';

   rawptr->OutputDevDsc.dsc$b_class = DSC$K_CLASS_S;
   rawptr->OutputDevDsc.dsc$b_dtype = DSC$K_DTYPE_T;
   rawptr->OutputDevDsc.dsc$a_pointer = rawptr->OutputDevName;
   rawptr->OutputDevDsc.dsc$w_length = sptr - rawptr->OutputDevName;

   status = sys$assign (&rawptr->InputDevDsc, &rawptr->InputChannel, 0, 0,
                        AGN$M_READONLY);
   if (VMSnok (status)) return (status);

   status = sys$assign (&rawptr->OutputDevDsc, &rawptr->OutputChannel, 0, 0,
                        AGN$M_WRITEONLY);
   if (VMSnok (status))
   {
      sys$dassgn (rawptr->InputChannel);
      rawptr->InputChannel = 0;
      return (status);
   }

   if (VMSnok (status))
   {
      sys$dassgn (rawptr->InputChannel);
      sys$dassgn (rawptr->OutputChannel);
      rawptr->InputChannel = rawptr->OutputChannel = 0;
      return (status);
   }

   if (!(rawptr->WatchScript = (rawptr->WatchLog != NULL)))
      rawptr->WatchScript = (RawLibCgiVarNull("WATCH_SCRIPT") != NULL);

   WATCH_RAWLIB (rawptr, FI_LI, "OPEN !AZ", SOFTWAREID);

   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
Initiate a close from the application end.
*/

void RawLibClose (struct RawLibStruct *rawptr)

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

   WATCH_RAWLIB (rawptr, FI_LI, "CLOSE closed:!UL", rawptr->RawSocketClosed);

   rawptr->RawSocketClosed = 1;

   Shut (rawptr);
}

/*****************************************************************************/
/*
Return true if the RawSocket has been closed.
*/

int RawLibIsClosed (struct RawLibStruct *rawptr)

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

   return (rawptr->RawSocketClosed);
}

/*****************************************************************************/
/*
Shutdown the websocket (this is different to the close handshake).
Cancel any outstanding I/O and deassign channels.
Call any supplied closure AST function.
This function call be called multiple times before full shutdown accomplished.
Returns success status when finally shut, abort if not (fully) shut (yet).
*/

int Shut (struct RawLibStruct *rawptr)

{
   int  astatus, status;

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

   if (rawptr->WatchLog)
      WATCH_RAWLIB (rawptr, FI_LI,
"Shut() destroy:!8XL(!UL) shut:!UL/!UL closed:!UL input:!UL output:!UL",
                    rawptr->DestroyAstFunction,
                    rawptr->DestroyAstFunction == &Destroy,
                    rawptr->RawSocketShut, rawptr->ShutCount,
                    rawptr->RawSocketClosed, rawptr->QueuedInput,
                    rawptr->QueuedOutput);

   if (rawptr->DestroyAstFunction == &Destroy) return (SS$_NORMAL);

   if (!rawptr->RawSocketShut)
   {
      if (rawptr->QueuedInput) sys$cancel (rawptr->InputChannel);

      /* advise the server that the connection is being shut down */
      astatus = sys$setast (0);
      status = sys$qio (EfnNoWait, rawptr->OutputChannel,
                        IO$_WRITEOF | IO$M_NORSWAIT, 0,
                        WriteEofAst, rawptr,
                        0, 0, 0, 0, 0, 0);
      if (VMSok(status)) rawptr->QueuedOutput++;
      if (astatus == SS$_WASSET) sys$setast (1);

      /* can be shut without having been closed (e.g. network error) */
      rawptr->RawSocketShut = rawptr->RawSocketClosed = 1;
   }
   else
   if (rawptr->QueuedOutput)
      if (rawptr->ShutCount++ > rawptr->QueuedOutput)
         sys$cancel (rawptr->OutputChannel);

   /* if outstanding I/O */
   if (rawptr->QueuedInput || rawptr->QueuedOutput) return (SS$_ABORT);

   /* deassign of output channel is handled during destroy */
   sys$dassgn (rawptr->InputChannel);
   rawptr->InputChannel = 0;

   /* first queue any client's destruction code */
   if (rawptr->DestroyAstFunction)
      sys$dclast (rawptr->DestroyAstFunction, rawptr, 0, 0);

   /* then queue the rawLIB structure destruction */
   sys$dclast ((rawptr->DestroyAstFunction = &Destroy), rawptr, 0, 0);

   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
Server has been advised of the closing connection (or t least the EOF has been
queued).  Decrement the queued I/O counter and then recall the shut function.
*/

static void WriteEofAst (struct RawLibStruct *rawptr)

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

   if (rawptr->QueuedOutput)
      rawptr->QueuedOutput--;
   else
      RawLibExit (rawptr, FI_LI, SS$_BUGCHECK);

   Shut (rawptr);
}

/*****************************************************************************/
/*
Return true if both input and output channel connected.
*/

int RawLibConnected (struct RawLibStruct *rawptr)

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

   return (rawptr->InputChannel && rawptr->OutputChannel);
}

/*****************************************************************************/
/*
Read data from the RawSocket client using a descriptor to describe the input
buffer.
*/

int RawLibReadDsc
(
struct RawLibStruct *rawptr,
struct dsc$descriptor_s *DataDsc,
struct dsc$descriptor_s *ResultDsc,
void *AstFunction
)
{
   int  status;
   struct dsc$descriptor_s  ScratchDsc;

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

   if (DataDsc->dsc$b_class != DSC$K_CLASS_S &&
       DataDsc->dsc$b_dtype != DSC$K_DTYPE_T) return (LIB$_INVSTRDES);

   rawptr->ReadDataDscPtr = DataDsc;
   rawptr->ReadResultDscPtr = ResultDsc;

   status = RawLibRead (rawptr, DataDsc->dsc$a_pointer,
                        DataDsc->dsc$w_length, AstFunction);

   return (status);
}

/*****************************************************************************/
/*
Read RawSocket data from RAWSOCKET_INPUT.  If an AST function is supplied
then the read is asynchronous, otherwise blocking.  When the read is complete
the status can be returned by RawLibReadStatus(), the read count by
RawLibReadCount(), a pointer to the data buffer using RawLibReadData(), and a
pointer to the read string descriptor with RawLibReadDataDsc().
*/

int RawLibRead
(
struct RawLibStruct *rawptr,
char *DataPtr,
int DataSize,
void *AstFunction
)
{
   int  astatus, status;
   struct RawLibIoStruct  *ioptr;

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

   WATCH_RAWLIB (rawptr, FI_LI, "READ size:!UL", DataSize);

   if (rawptr->RawSocketClosed)
   {
      MsgCallback (rawptr, __LINE__, SS$_SHUT, "can't read; closed");
      rawptr->ReadStatus = SS$_SHUT;
      if (AstFunction) ((void(*)())AstFunction)(rawptr);
      return (SS$_SHUT);
   }

   /* if descriptor is not initialised (by not using RawLibReadDsc()) */
   if (!rawptr->ReadDataDscPtr)
   {
      rawptr->ReadDataDscPtr = &rawptr->ReadDataDsc;
      rawptr->ReadDataDscPtr->dsc$b_class = DSC$K_CLASS_S;
      rawptr->ReadDataDscPtr->dsc$b_dtype = DSC$K_DTYPE_T;
      rawptr->ReadDataDscPtr->dsc$a_pointer = DataPtr;
      rawptr->ReadDataDscPtr->dsc$w_length = DataSize;
   }

   ioptr = calloc (1, sizeof(struct RawLibIoStruct));
   ioptr->RawLibPtr = rawptr;
   ioptr->DataPtr = DataPtr;
   ioptr->DataSize = DataSize;
   ioptr->DataDscPtr = rawptr->ReadDataDscPtr;
   ioptr->ResultDscPtr = rawptr->ReadResultDscPtr;

   if (ioptr->AstFunction = AstFunction)
   {
      /* asynchronous read */
      astatus = sys$setast (0);
      status = sys$qio (EfnNoWait, rawptr->InputChannel,
                        IO$_READLBLK | IO$M_STREAM | IO$M_WRITERCHECK,
                        &ioptr->IOsb, ReadDataAst, ioptr,
                        DataPtr, DataSize, 0, 0, 0, 0);
      if (VMSok(status)) rawptr->QueuedInput++;
      if (astatus == SS$_WASSET) sys$setast (1);
   }
   else
   {
      /* synchronous read */
      status = sys$qiow (EfnWait, rawptr->InputChannel,
                         IO$_READLBLK | IO$M_STREAM | IO$M_WRITERCHECK,
                         &ioptr->IOsb, 0, 0,
                         DataPtr, DataSize, 0, 0, 0, 0);
      if (VMSok(status))
      {
         if (VMSok(status)) status = ioptr->IOsb.iosb$w_status;
         rawptr->QueuedInput++;
         ReadDataAst (ioptr);
      }
   }

   return (status);
}

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

static void ReadDataAst (struct RawLibIoStruct *ioptr)

{
   int  astatus, cnt, status,
        DataCount,
        DataSize;
   char  *DataPtr;
   struct RawLibStruct  *rawptr;

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

   rawptr = ioptr->RawLibPtr;

   WATCH_RAWLIB (rawptr, FI_LI, "READ AST %X!8XL !UL bytes",
                 ioptr->IOsb.iosb$w_status, ioptr->IOsb.iosb$w_bcnt);

   if (rawptr->QueuedInput)
      rawptr->QueuedInput--;
   else
      RawLibExit (rawptr, FI_LI, SS$_BUGCHECK);

   if (rawptr->WatchDogReadSecs)
      rawptr->WatchDogReadTime = CurrentTime + rawptr->WatchDogReadSecs;
   if (rawptr->WatchDogIdleSecs)
      rawptr->WatchDogIdleTime = CurrentTime + rawptr->WatchDogIdleSecs;

   /* these remain current for the duration of any AST */
   rawptr->ReadDataPtr = ioptr->DataPtr;
   rawptr->ReadDataSize = ioptr->DataSize;
   rawptr->ReadDataDscPtr = ioptr->DataDscPtr;
   rawptr->ReadResultDscPtr = ioptr->ResultDscPtr;

   if (VMSok (rawptr->ReadStatus = ioptr->IOsb.iosb$w_status))
   {
#ifdef __VAX
      /* close enough! */
      rawptr->InputMsgCount[0]++;
#else
      *(__int64*)rawptr->InputMsgCount = *(__int64*)rawptr->InputMsgCount + 1;
#endif
   }

   rawptr->ReadDataCount = ioptr->IOsb.iosb$w_bcnt;

   /* update result length if originally provided, otherwise data length */
   if (rawptr->ReadResultDscPtr)
      rawptr->ReadResultDscPtr->dsc$w_length = ioptr->IOsb.iosb$w_bcnt;
   else
      rawptr->ReadDataDscPtr->dsc$w_length = ioptr->IOsb.iosb$w_bcnt;

   if (ioptr->AstFunction) ((void(*)())ioptr->AstFunction)(rawptr);

   /* reset after any AST has been delivered */
   rawptr->ReadDataPtr = NULL;
   rawptr->ReadDataCount = rawptr->ReadDataSize = 0;
   rawptr->ReadDataDscPtr = rawptr->ReadResultDscPtr = NULL;

   rawptr->WatchDogReadTime = 0;
   if (rawptr->WatchDogIdleSecs)
      rawptr->WatchDogIdleTime = CurrentTime + rawptr->WatchDogIdleSecs;
   if (rawptr->WatchDogWakeSecs)
      rawptr->WatchDogWakeTime = CurrentTime + rawptr->WatchDogWakeSecs;

   if (rawptr->RawSocketShut) Shut (rawptr); 

   free (ioptr);
}

/*****************************************************************************/
/*
Return the (most recent) read status value.
*/

int RawLibReadStatus (struct RawLibStruct *rawptr)

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

   return (rawptr->ReadStatus);
}

/*****************************************************************************/
/*
Return the (most recent) read buffer size (longword).
*/

int RawLibReadSize (struct RawLibStruct *rawptr)

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

   return (rawptr->ReadDataSize);
}

/*****************************************************************************/
/*
Return the (most recent) read count value (longword).
*/

int RawLibReadCount (struct RawLibStruct *rawptr)

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

   return (rawptr->ReadDataCount);
}

/*****************************************************************************/
/*
Return a pointer to the (most recent) read buffer.
*/

char* RawLibReadData (struct RawLibStruct *rawptr)

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

   return (rawptr->ReadDataPtr);
}

/*****************************************************************************/
/*
Return a pointer to the (most recent) read data descriptor.
*/

struct dsc$descriptor_s* RawLibReadDataDsc (struct RawLibStruct *rawptr)

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

   return (&rawptr->ReadDataDsc);
}

/*****************************************************************************/
/*
Return a pointer to the total read count value (quadword).
*/

ulong* RawLibReadTotal (struct RawLibStruct *rawptr)

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

   return ((ulong*)&rawptr->InputCount);
}

/*****************************************************************************/
/*
Return a pointer to the total messages read value (quadword).
*/

ulong* RawLibReadMsgTotal (struct RawLibStruct *rawptr)

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

   return ((ulong*)&rawptr->InputMsgCount);
}

/*****************************************************************************/
/*
Write the data pointed to by the supplied string descriptor.
*/

int RawLibWriteDsc
(
struct RawLibStruct *rawptr,
struct dsc$descriptor_s *DataDsc,
void *AstFunction
)
{
   int  status;

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

   if (DataDsc->dsc$b_class != DSC$K_CLASS_S &&
       DataDsc->dsc$b_dtype != DSC$K_DTYPE_T) return (LIB$_INVSTRDES);

   rawptr->WriteDataDscPtr = DataDsc;

   status = RawLibWrite (rawptr, DataDsc->dsc$a_pointer,
                         DataDsc->dsc$w_length, AstFunction);

   return (status);
}

/*****************************************************************************/
/*
Queue a write to the client RAWSOCKET_OUTPUT mailbox.  If an AST function is
supplied then the write is asynchronous, otherwise blocking.
*/

int RawLibWrite
(
struct RawLibStruct *rawptr,
char *DataPtr,
int DataCount,
void *AstFunction
)
{
   int  astatus, status;
   uchar  *cptr, *czptr, *sptr;
   struct RawLibIoStruct  *ioptr;

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

   WATCH_RAWLIB (rawptr, FI_LI, "WRITE !UL bytes", DataCount);

   if (rawptr->RawSocketClosed)
   {
      MsgCallback (rawptr, __LINE__, SS$_SHUT, "can't write; closed");
      rawptr->WriteStatus = SS$_SHUT;
      if (AstFunction && AstFunction != RAWLIB_ASYNCH)
         ((void(*)())AstFunction)(rawptr);
      return (SS$_SHUT);
   }

   ioptr = calloc (1, sizeof(struct RawLibIoStruct));
   ioptr->RawLibPtr = rawptr;
   ioptr->DataPtr = DataPtr;
   ioptr->DataCount = DataCount;
   ioptr->DataDscPtr = rawptr->WriteDataDscPtr;

   if (ioptr->AstFunction = AstFunction)
   {
      /* asynchronous */
      astatus = sys$setast (0);
      status = sys$qio (EfnNoWait, rawptr->OutputChannel,
                        IO$_WRITELBLK | IO$M_READERCHECK,
                        &ioptr->IOsb, WriteDataAst, ioptr, 
                        DataPtr, DataCount, 0, 0, 0, 0);
      if (VMSok(status)) rawptr->QueuedOutput++;
      if (astatus == SS$_WASSET) sys$setast (1);
   }
   else
   {
      /* synchronous */
      status = sys$qiow (EfnWait, rawptr->OutputChannel,
                         IO$_WRITELBLK | IO$M_READERCHECK,
                         &ioptr->IOsb, 0, 0,
                         DataPtr, DataCount, 0, 0, 0, 0);
      if (VMSok(status)) status = ioptr->IOsb.iosb$w_status;
      rawptr->QueuedOutput++;
      WriteDataAst (ioptr);
   }

   return (status);
}

/*****************************************************************************/
/*
Write the message to the RawSocket.  Message may be automatically fragmented.
Can AST back to this function one or more times with asycnchronous I/O.
*/

static void WriteDataAst (struct RawLibIoStruct *ioptr)

{
   int  astatus, cnt, count, hcnt, length, status,
        DataCount;
   char  *DataPtr;
   struct RawLibStruct  *rawptr;

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

   rawptr = ioptr->RawLibPtr;

   WATCH_RAWLIB (rawptr, FI_LI, "WRITE AST %X!8XL",
                 ioptr->IOsb.iosb$w_status);

   if (rawptr->QueuedOutput)
      rawptr->QueuedOutput--;
   else
      RawLibExit (rawptr, FI_LI, SS$_BUGCHECK);

   if (rawptr->WatchDogIdleSecs)
      rawptr->WatchDogIdleTime = CurrentTime + rawptr->WatchDogIdleSecs;

   /* these remain current for the duration of any AST */
   rawptr->WriteDataPtr = ioptr->DataPtr;
   rawptr->WriteDataCount = ioptr->DataCount;
   rawptr->WriteDataDscPtr = ioptr->DataDscPtr;

   if (VMSok (rawptr->WriteStatus = ioptr->IOsb.iosb$w_status))
   {
#ifdef __VAX
      /* close enough! */
      rawptr->OutputMsgCount[0]++;
#else
      *(__int64*)rawptr->OutputMsgCount = *(__int64*)rawptr->OutputMsgCount + 1;
#endif
   }

   if (rawptr->WatchDogIdleSecs)
      rawptr->WatchDogIdleTime = CurrentTime + rawptr->WatchDogIdleSecs;
   if (rawptr->WatchDogWakeSecs)
      rawptr->WatchDogWakeTime = CurrentTime + rawptr->WatchDogWakeSecs;

   if (ioptr->AstFunction && ioptr->AstFunction != RAWLIB_ASYNCH)
      ((void(*)())ioptr->AstFunction)(rawptr);

   free (ioptr);
}

/*****************************************************************************/
/*
Return the (most recent) write status value.
*/

int RawLibWriteStatus (struct RawLibStruct *rawptr)

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

   return (rawptr->WriteStatus);
}

/*****************************************************************************/
/*
Return the (most recent) write count value (longword).
*/

int RawLibWriteCount (struct RawLibStruct *rawptr)

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

   return (rawptr->WriteDataCount);
}

/*****************************************************************************/
/*
Return a pointer to the (most recent) write data descriptor.
*/

struct dsc$descriptor_s* RawLibWriteDataDsc (struct RawLibStruct *rawptr)

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

   return (&rawptr->WriteDataDsc);
}

/*****************************************************************************/
/*
Return a pointer to the total write count value (quadword).
*/

ulong* RawLibWriteTotal (struct RawLibStruct *rawptr)

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

   return ((ulong*)&rawptr->OutputCount);
}

/*****************************************************************************/
/*
Return a pointer to the total write message value (quadword).
*/

ulong* RawLibWriteMsgTotal (struct RawLibStruct *rawptr)

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

   return ((ulong*)&rawptr->OutputMsgCount);
}

/*****************************************************************************/
/*
Set the pointer to the user data associated with the RawSocket structure.
*/

void RawLibSetUserData
(
struct RawLibStruct *rawptr,
void *UserDataPtr
)
{
   /*********/
   /* begin */
   /*********/

   rawptr->UserDataPtr = UserDataPtr;
}

/*****************************************************************************/
/*
Return the pointer to the user data.
*/

void* RawLibGetUserData (struct RawLibStruct *rawptr)

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

   return (rawptr->UserDataPtr);
}

/*****************************************************************************/
/*
Set/reset the callout response AST.  Returns the previous callout pointer.
*/

void* RawLibSetCallout
(
struct RawLibStruct *rawptr,
void *AstFunction
)
{
   void  *PrevCallout;

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

   PrevCallout = rawptr->CalloutAstFunction;
   rawptr->CalloutAstFunction = AstFunction;
   return (PrevCallout);
}

/*****************************************************************************/
/*
Set number of seconds before the application is considered idle and exited.
*/

void RawLibSetLifeSecs (int LifeSecs)

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

   if (!(WatchDogLifeSecs = LifeSecs))
     WatchDogLifeSecs = DEFAULT_WATCHDOG_IDLE_SECS;
}

/*****************************************************************************/
/*
Set number of seconds before a RawSocket is considered idle and closed.
If a RawSocket is not specified then set global value.
*/

void RawLibSetIdleSecs
(
struct RawLibStruct *rawptr,
int IdleSecs
)
{
   /*********/
   /* begin */
   /*********/

   if (rawptr)
   {
      /* set RawSocket values */
      if (!(rawptr->WatchDogIdleSecs = IdleSecs))
         rawptr->WatchDogIdleSecs = WatchDogIdleSecs;
      rawptr->WatchDogIdleTime = CurrentTime + rawptr->WatchDogIdleSecs;
   }
   else
   {
      /* set global values */
      if (!(WatchDogIdleSecs = IdleSecs))
        WatchDogIdleSecs = DEFAULT_WATCHDOG_IDLE_SECS;
   }
}

/*****************************************************************************/
/*
Set number of seconds a RawSocket read will wait on the client.
If a RawSocket is not specified then set global value.
*/

void RawLibSetReadSecs
(
struct RawLibStruct *rawptr,
int ReadSecs
)
{
   /*********/
   /* begin */
   /*********/

   if (rawptr)
   {
      /* set RawSocket values */
      if (!(rawptr->WatchDogReadSecs = ReadSecs))
         rawptr->WatchDogReadSecs = WatchDogReadSecs;
   }
   else
   {
      /* set global values */
      if (!(WatchDogReadSecs = ReadSecs))
        WatchDogReadSecs = DEFAULT_WATCHDOG_IDLE_SECS;
   }
}

/*****************************************************************************/
/*
Set/reset the wake callback function to the specified number of seconds
(defaults to the global setting).  Returns the previous callback pointer.
*/

void* RawLibSetWakeCallback
(
struct RawLibStruct *rawptr,
void *AstFunction,
int WakeSecs
)
{
   void  *PrevCallback;

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

   if (rawptr)
   {
      /* set RawSocket values */
      if (!(rawptr->WatchDogWakeSecs = WakeSecs))
         rawptr->WatchDogWakeSecs = WatchDogWakeSecs;
      rawptr->WatchDogWakeTime = CurrentTime + rawptr->WatchDogWakeSecs;

      PrevCallback = rawptr->WakeCallbackFunction;
      rawptr->WakeCallbackFunction = AstFunction;
   }
   else
   {
      /* set global values */
      if (!(WatchDogWakeSecs = WakeSecs))
         WatchDogWakeSecs = DEFAULT_WATCHDOG_WAKE_SECS;
      WatchDogWakeTime = CurrentTime + WatchDogWakeSecs;

      PrevCallback = WakeCallbackFunction;
      WakeCallbackFunction = AstFunction;
   }
   return (PrevCallback);
}

/*****************************************************************************/
/*
Set/reset the error callback function.  Returns the previous callback pointer.
*/

void* RawLibSetMsgCallback
(
struct RawLibStruct *rawptr,
void *AstFunction
)
{
   void  *PrevCallback;

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

   PrevCallback = rawptr->MsgCallbackFunction;
   rawptr->MsgCallbackFunction = AstFunction;
   return (PrevCallback);
}

/*****************************************************************************/
/*
Set wsLIB message data.  The 'FormatString' must be a $FAO compiliant
null-terminated string, with following parametersif required.  Activate any set
message callback.
*/

static void MsgCallback
(
struct RawLibStruct *rawptr,
int LineNumber,
int VmsStatus,
char *FormatString,
...
)
{
   static $DESCRIPTOR (FormatFaoDsc, "");

   int  argcnt, cnt, status;
   ushort  slen = 0;
   ulong  *vecptr;
   ulong  FaoVector [32];
   char  *cptr, *sptr, *zptr;
   char  FormatBuffer [128];
   va_list  argptr;

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

   va_count (argcnt);

   /* bit of a sanity check */
   if (argcnt-4 > 32) RawLibExit (rawptr, FI_LI, SS$_BUGCHECK);
   cnt = 0;
   /* some VERY basic sanity checking */
   for (cptr = FormatString; *cptr; cptr++)
   {
      if (*cptr != '!') continue;
      cptr++;
      if (*cptr == '!') continue;
      cnt++;
      if (*cptr == '#') cnt++;
      if (*(USHORTPTR)cptr == '%T') cnt++; 
      if (*(USHORTPTR)cptr == '%D') cnt++; 
   }
   if (argcnt-4 != cnt) RawLibExit (rawptr, FI_LI, SS$_BUGCHECK);

   zptr = (sptr = FormatBuffer) + sizeof(FormatBuffer)-1;
   for (cptr = "%X!8XL "; *cptr; *sptr++ = *cptr++);
   for (cptr = FormatString; *cptr && sptr < zptr; *sptr++ = *cptr++);
   *sptr = '\0';

   FormatFaoDsc.dsc$a_pointer = FormatBuffer;
   FormatFaoDsc.dsc$w_length = sptr - FormatBuffer;

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

   rawptr->MsgDsc.dsc$b_class = DSC$K_CLASS_S;
   rawptr->MsgDsc.dsc$b_dtype = DSC$K_DTYPE_T;

   for (;;)
   {
      if (rawptr->MsgStringSize)
      {
         rawptr->MsgDsc.dsc$a_pointer = rawptr->MsgStringPtr;
         rawptr->MsgDsc.dsc$w_length = rawptr->MsgStringSize;

         status = sys$faol (&FormatFaoDsc, &slen, &rawptr->MsgDsc, &FaoVector);
         if (VMSnok (status)) RawLibExit (NULL, FI_LI, status);
         if (status != SS$_BUFFEROVF) break;
      }

      if (rawptr->MsgStringSize) free (rawptr->MsgStringPtr);
      rawptr->MsgStringSize += 127;
      rawptr->MsgStringPtr = calloc (1, rawptr->MsgStringSize+1);
      if (!rawptr->MsgStringPtr) RawLibExit (NULL, FI_LI, vaxc$errno);
   }

   rawptr->MsgStringPtr[rawptr->MsgStringLength=slen] = '\0';
   rawptr->MsgDsc.dsc$w_length = slen;

   rawptr->MsgLineNumber = LineNumber;
   sys$gettim (&rawptr->MsgBinTime);

   if (rawptr->MsgCallbackFunction) (*rawptr->MsgCallbackFunction)(rawptr);
}

/****************************************************************************/
/*
Return a pointer to the string descriptor of the latest error string.
*/

struct dsc$descriptor_s* RawLibMsgDsc (struct RawLibStruct *rawptr)

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

   return (&rawptr->MsgDsc);
}

/****************************************************************************/
/*
Return a pointer to the latest error string.
*/

char* RawLibMsgString (struct RawLibStruct *rawptr)

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

   return (rawptr->MsgStringPtr);
}

/****************************************************************************/
/*
Return the wsLIB source code line at which the last error occured.
*/

int RawLibMsgLineNumber (struct RawLibStruct *rawptr)

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

   return (rawptr->MsgLineNumber);
}

/*****************************************************************************/
/*
Return the current wsLIB time in seconds (i.e. C-RTL, Unix time).
*/

unsigned int RawLibTime ()

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

   return (CurrentTime);
}

/*****************************************************************************/
/*
Called every second to maintain various events in the RawSocket and application
life-cycle.
*/

static void WatchDog ()

{
   static int  DebugWatchDog = 0;
   static ulong  OneSecondDelta [2] = { -10000000, -1 };
   static ulong  ExitTime;

   int  status,
        StringLength;
   char  StringBuffer [256];
   struct RawLibStruct  *rawptr;

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

   sys$gettim (&CurrentBinTime);
   CurrentTime = decc$fix_time (&CurrentBinTime);

   if (DebugWatchDog && ListHead)
      RawLibWatchScript (ListHead, FI_LI, "WATCHDOG !UL", CurrentTime);

   if (ListHead)
      ExitTime = 0;
   else
   if (!ExitTime)
      ExitTime = CurrentTime + WatchDogLifeSecs;
   else
   if (ExitTime < CurrentTime)
      exit (SS$_NORMAL);

   if (WatchDogWakeTime &&
       WatchDogWakeTime < CurrentTime)
   {
      /* wake globally if requested */
      WatchDogWakeTime = CurrentTime + WatchDogWakeSecs - 1;
      if (WakeCallbackFunction) sys$dclast (WakeCallbackFunction, 0, 0, 0);
   }

   for (rawptr = ListHead; rawptr; rawptr = rawptr->NextPtr)
   {
      if (DebugWatchDog)
         RawLibWatchScript (ListHead, FI_LI, "WATCHDOG 0x!8XL !UL !UL !UL",
                            rawptr,
                            rawptr->WatchDogIdleTime,
                            rawptr->WatchDogReadTime,
                            rawptr->WatchDogWakeTime);

      /* flush any watch log to disk every second */
      if (rawptr->WatchLog) fsync (fileno(rawptr->WatchLog));

      if (rawptr->WatchDogReadTime &&
          rawptr->WatchDogReadTime < CurrentTime)
      {
         if (rawptr->WatchScript)
            WATCH_RAWLIB (rawptr, FI_LI, "READ expired 0x!8XL", rawptr);
         RawLibClose (rawptr);
      }
      else
      if (rawptr->WatchDogIdleTime &&
          rawptr->WatchDogIdleTime < CurrentTime)
      {
         if (rawptr->WatchScript)
            WATCH_RAWLIB (rawptr, FI_LI, "IDLE expired 0x!8XL", rawptr);
         RawLibClose (rawptr);
      }
      else
      if (rawptr->WatchDogWakeTime &&
          rawptr->WatchDogWakeTime < CurrentTime)
      {
         /* wake if requested */
         rawptr->WatchDogWakeTime = CurrentTime + rawptr->WatchDogWakeSecs - 1;
         if (rawptr->WakeCallbackFunction)
         {
            if (rawptr->WatchScript)
               WATCH_RAWLIB (rawptr, FI_LI, "WAKE expired 0x!8XL", rawptr);
            sys$dclast (rawptr->WakeCallbackFunction, rawptr, 0, 0);
         }
      }
   }

   status = sys$setimr (0, &OneSecondDelta, WatchDog, 0, 0);
   if (VMSnok(status)) RawLibExit (NULL, FI_LI, status);
}                            

/*****************************************************************************/
/*
Exit from the current image reporting to the server RAWSOCKET_OUTPUT stream (if
a channel assigned) or (last-ditch) to <stdout> the exit code module name and
line number.
*/

void RawLibExit
(
struct RawLibStruct *rawptr,
char *SourceModuleName,
int SourceLineNumber,
int status
)
{
   static ulong  OneSecondDelta [2] = { -10000000, -1 };
   static $DESCRIPTOR (FaoDsc, "BYE-BYE [!AZ:!UL] %X!8XL\n\0");

   short  slen;
   char  MsgBuffer [256];
   $DESCRIPTOR (MsgDsc, MsgBuffer);

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

   sys$fao (&FaoDsc, &slen, &MsgDsc,
            SourceModuleName, SourceLineNumber, status);

   if (rawptr && rawptr->OutputChannel)
      sys$qiow (EfnWait, rawptr->OutputChannel,
                IO$_WRITELBLK | IO$M_READERCHECK, 0, 0, 0,
                MsgBuffer, slen-1, 0, 0, 0, 0);
   else
   {
      fputs (MsgBuffer, stdout);
      fflush (stdout);
   }

   sys$schdwk (0, 0, OneSecondDelta, 0);
   sys$hiber ();
   sys$delprc (0, 0, 0);
}

/*****************************************************************************/
/*
The 'FormatString' must be a $FAO compliant null-terminated string, with
following parameters.
*/

void RawLibCallout
(
struct RawLibStruct *rawptr,
char *FormatString,
...
)
{
   static $DESCRIPTOR (ErrorFaoDsc, "!!WATCH: $FAO %X!8XL");

   int  argcnt, astatus, cnt, status;
   ushort  slen = 0;
   ulong  *vecptr;
   ulong  FaoVector [32];
   char  *aptr, *cptr;
   char  CalloutBuffer [1024];
   va_list  argptr;
   $DESCRIPTOR (CalloutBufferDsc, CalloutBuffer);
   $DESCRIPTOR (FormatDsc, "");

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

   if (!CgiPlusEscLength && !CgiPlusEotLength) return;

   if (!rawptr->OutputChannel) return;

   va_count (argcnt);

   /* can't call callout while in a callout response delivery */
   if (rawptr->CalloutInProgress) RawLibExit (rawptr, FI_LI, SS$_BUGCHECK);

   /* bit of a sanity check */
   if (argcnt-2 > 32) RawLibExit (rawptr, FI_LI, SS$_BUGCHECK);
   cnt = 0;
   for (cptr = FormatString; *cptr; cptr++)
      if (*cptr == '!' && *(USHORTPTR)cptr != '!!') cnt++;
   if (argcnt-2 != cnt) RawLibExit (rawptr, FI_LI, SS$_BUGCHECK);

   FormatDsc.dsc$a_pointer = FormatString;
   FormatDsc.dsc$w_length = strlen(FormatString);

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

   status = sys$faol (&FormatDsc, &slen, &CalloutBufferDsc, &FaoVector);
   if (VMSnok(status))
      status = sys$fao (&ErrorFaoDsc, &slen, &CalloutBufferDsc, status);

   /* allocate a pointer plus a buffer (freed by WriteFreeAst()) */ 
   aptr = calloc (1, sizeof(struct RawLibStruct*) + slen);
   if (!aptr) RawLibExit (rawptr, FI_LI, vaxc$errno);
   *(struct RawLibStruct**)aptr = rawptr;
   cptr = aptr + sizeof(struct RawLibStruct*);
   memcpy (cptr, CalloutBuffer, slen);

   astatus = sys$setast (0);
   status = sys$qio (EfnNoWait, rawptr->OutputChannel,
                     IO$_WRITELBLK | IO$M_READERCHECK,
                     0, WriteOutputAst, rawptr,
                     CgiPlusEscPtr, CgiPlusEscLength, 0, 0, 0, 0);
   if (VMSok(status))
   {
      rawptr->QueuedOutput++;
      status = sys$qio (EfnNoWait, rawptr->OutputChannel,
                        IO$_WRITELBLK | IO$M_READERCHECK,
                        0, WriteFreeAst, aptr,
                        cptr, slen, 0, 0, 0, 0);
      if (VMSok(status))
      {
         rawptr->QueuedOutput++;
         status = sys$qio (EfnNoWait, rawptr->OutputChannel,
                           IO$_WRITELBLK | IO$M_READERCHECK,
                           0, WriteOutputAst, rawptr,
                           CgiPlusEotPtr, CgiPlusEotLength, 0, 0, 0, 0);
         if (VMSok(status)) rawptr->QueuedOutput++;
      }
   }
   if (astatus == SS$_WASSET) sys$setast (1);
}

/*****************************************************************************/
/*
Send a script "WATCH:" callout to the server (if [x]script enabled).  The
'SourceModuleName' can be NULL.  The 'FormatString' must be a $FAO compiliant
null-terminated string, with following parameters.  A specific channel is
assigned for WATCH output so that it can be deassigned as late in request
processing as possible.
*/

void RawLibWatchScript
(
struct RawLibStruct *rawptr,
char *SourceModuleName,
int SourceLineNumber,
char *FormatString,
...
)
{
   static $DESCRIPTOR (ErrorFaoDsc, "!!WATCH: $FAO %X!8XL");
   static $DESCRIPTOR (TimeFaoDsc, "!%T\0");
   static $DESCRIPTOR (Watch1FaoDsc, "!!!!WATCH: [!AZ:!4ZL] !AZ");
   static $DESCRIPTOR (Watch2FaoDsc, "!!!!WATCH: !AZ");
   static struct RawLibStruct  FakeIt;

   int  argcnt, astatus, cnt, status;
   ushort  slen = 0;
   ulong  *vecptr;
   ulong  FaoVector [32];
   char  *aptr, *cptr;
   char  TimeBuffer [32],
         WatchBuffer [1024],
         WatchFao [256];
   va_list  argptr;
   $DESCRIPTOR (FaoDsc, WatchFao);
   $DESCRIPTOR (TimeBufferDsc, TimeBuffer);
   $DESCRIPTOR (WatchBufferDsc, WatchBuffer);

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

   if (!rawptr)
   {
      rawptr = &FakeIt;

      if (!rawptr->WatchScript)
      {
         rawptr->WatchScript = 1;
         if ((cptr = getenv ("WASD_RAWLIB_WATCH_LOG")) != NULL)
            if ((rawptr->WatchLog = fopen (cptr, "w", "shr=get")) != NULL)
               RawLibExit (NULL, FI_LI, vaxc$errno);
      }

      if (!rawptr->WatchLog) return;
   }
   else
   {
      if (!rawptr->WatchScript) return;

      if (!rawptr->OutputChannel) return;

      /* can't call callout while in a callout response delivery */
      if (rawptr->CalloutInProgress) RawLibExit (rawptr, FI_LI, SS$_BUGCHECK);
   }

   va_count (argcnt);

   /* bit of a sanity check */
   if (argcnt-4 > 32) RawLibExit (rawptr, FI_LI, SS$_BUGCHECK);
   cnt = 0;
   for (cptr = FormatString; *cptr; cptr++)
   {
      if (*cptr != '!') continue;
      cptr++;
      if (*cptr == '!') continue;
      cnt++;
      if (*cptr == '#') cnt++;
      if (*(USHORTPTR)cptr == '%T') cnt++; 
      if (*(USHORTPTR)cptr == '%D') cnt++; 
   }
   if (argcnt-4 != cnt) RawLibExit (rawptr, FI_LI, SS$_BUGCHECK);

   if (SourceModuleName)
      sys$fao (&Watch1FaoDsc, &slen, &FaoDsc,
               SourceModuleName, SourceLineNumber, FormatString);
   else
      sys$fao (&Watch2FaoDsc, &slen, &FaoDsc, FormatString);
    FaoDsc.dsc$w_length = slen;

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

   status = sys$faol (&FaoDsc, &slen, &WatchBufferDsc, &FaoVector);
   if (!(status & 1))
      status = sys$fao (&ErrorFaoDsc, &slen, &WatchBufferDsc, status);

   if (!CgiPlusEscLength && !CgiPlusEotLength)
   {
      fprintf (stdout, "%*.*s\n", slen, slen, WatchBuffer);
      return;
   }

   if (rawptr->WatchLog)
   {
      sys$fao (&TimeFaoDsc, 0, &TimeBufferDsc, 0);
      fprintf (rawptr->WatchLog, "%s %*.*s\n",
               TimeBuffer, slen-8, slen-8, WatchBuffer+8);
      return;
   }

   /* allocate a pointer plus a buffer (freed by WriteFreeAst()) */ 
   aptr = calloc (1, sizeof(struct RawLibStruct*) + slen);
   if (!aptr) RawLibExit (rawptr, FI_LI, vaxc$errno);
   *(struct RawLibStruct**)aptr = rawptr;
   cptr = aptr + sizeof(struct RawLibStruct*);
   memcpy (cptr, WatchBuffer, slen);

   astatus = sys$setast (0);
   status = sys$qio (EfnNoWait, rawptr->OutputChannel,
                     IO$_WRITELBLK | IO$M_READERCHECK,
                     0, WriteOutputAst, rawptr,
                     CgiPlusEscPtr, CgiPlusEscLength, 0, 0, 0, 0);
   if (VMSok(status))
   {
      rawptr->QueuedOutput++;
      status = sys$qio (EfnNoWait, rawptr->OutputChannel,
                        IO$_WRITELBLK | IO$M_READERCHECK,
                        0, WriteFreeAst, aptr,
                        cptr, slen, 0, 0, 0, 0);
      if (VMSok(status))
      {
         rawptr->QueuedOutput++;
         status = sys$qio (EfnNoWait, rawptr->OutputChannel,
                           IO$_WRITELBLK | IO$M_READERCHECK,
                           0, WriteOutputAst, rawptr,
                           CgiPlusEotPtr, CgiPlusEotLength, 0, 0, 0, 0);
         if (VMSok(status)) rawptr->QueuedOutput++;
       }
   }
   if (VMSnok (status)) RawLibExit (rawptr, FI_LI, status);
   if (astatus == SS$_WASSET) sys$setast (1);
}

/*****************************************************************************/
/*
Just decrement the queued output counter.
*/

void WriteOutputAst (struct RawLibStruct *rawptr)

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

   if (rawptr->QueuedOutput)
      rawptr->QueuedOutput--;
   else
      RawLibExit (rawptr, FI_LI, SS$_BUGCHECK);
   if (rawptr->RawSocketShut) Shut (rawptr);
}

/*****************************************************************************/
/*
The parameter is an address to allocated memory.  The memory contains a pointer
to a RawLib structure and after that opaque data (actually just a write buffer
but what the hey).  Get the pointer to the owning RawLib structure.  Free the
allocated memory.  Decrement the RawLib structure's queued output counter.
*/

void WriteFreeAst (char *aptr)

{
   struct RawLibStruct  *rawptr;

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

   rawptr = *(struct RawLibStruct**)aptr;
   free (aptr);
   if (rawptr->QueuedOutput)
      rawptr->QueuedOutput--;
   else
      RawLibExit (rawptr, FI_LI, SS$_BUGCHECK);
   if (rawptr->RawSocketShut) Shut (rawptr);
}

/*****************************************************************************/
/*
If a CGIplus environment then output the end-of-request sentinal.
*/

void RawLibCgiPlusEof ()

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

   if (!CgiPlusEofLength) return;
   fflush (stdout);
   fputs (CgiPlusEofPtr, stdout);
   fflush (stdout);
}

/*****************************************************************************/
/*
Output the callout end sentinal.
*/

void RawLibCgiPlusEot ()

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

   if (!CgiPlusEotLength) return;
   fflush (stdout);
   fputs (CgiPlusEotPtr, stdout);
   fflush (stdout);
}

/*****************************************************************************/
/*
Output the callout begin sentinal.
*/

void RawLibCgiPlusEsc ()

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

   if (!CgiPlusEscLength) return;
   fflush (stdout);
   fputs (CgiPlusEscPtr, stdout);
   fflush (stdout);
}

/*****************************************************************************/
/*
Output the callout begin sentinal.
*/

void RawLibCalloutStart ()

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

   if (!CgiPlusEscLength) return;
   fflush (stdout);
   fputs (CgiPlusEscPtr, stdout);
   fflush (stdout);
}

/*****************************************************************************/
/*
Return true if it's a CGIplus execution environment.
*/

int RawLibIsCgiPlus ()

{
   static int  InitIs;

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

   if (!InitIs)
   {
      InitIs = 1;
      if (CgiPlusEofPtr = getenv("CGIPLUSEOF"))
         CgiPlusEofLength = strlen(CgiPlusEofPtr);
      if (CgiPlusEscPtr = getenv("CGIPLUSESC"))
         CgiPlusEscLength = strlen(CgiPlusEscPtr);
      if (CgiPlusEotPtr = getenv("CGIPLUSEOT"))
         CgiPlusEotLength = strlen(CgiPlusEotPtr);
   }
   return (CgiPlusEofLength);
}

/*****************************************************************************/
/*
Are CGI variables currently available?  Returns true or false.
*/

int RawLibCgiVarAvailable ()
{
   /*********/
   /* begin */
   /*********/

   return (CgiVariablesAvailable);
}

/*****************************************************************************/
/*
String descriptor equivalent of RawLibCgiVar() with the same requirements and
constraints.  Set 'ValueDsc' to the value of the CGI variable (or empty if it
doesn't exist).  Return the length of the value of -1 if the CGI variable name
does not exist.
*/

int RawLibCgiVarDsc
(
struct dsc$descriptor_s *NameDsc,
struct dsc$descriptor_s *ValueDsc
)
{
   char  *cptr, *czptr, *sptr, *zptr;
   char  VarName [256];

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

   if (NameDsc->dsc$b_class != DSC$K_CLASS_S &&
       ValueDsc->dsc$b_dtype != DSC$K_DTYPE_T) return (LIB$_INVSTRDES);

   if (ValueDsc->dsc$b_class != DSC$K_CLASS_S &&
       ValueDsc->dsc$b_dtype != DSC$K_DTYPE_T) return (LIB$_INVSTRDES);

   zptr = (sptr = VarName) + sizeof(VarName)-1;
   czptr = (cptr = NameDsc->dsc$a_pointer) + NameDsc->dsc$w_length;
   while (cptr < czptr && sptr < zptr) *sptr++ = *cptr++;
   *sptr = '\0';

   if (cptr = RawLibCgiVarNull (VarName))
   {
      for (sptr = cptr; *sptr; sptr++);
      ValueDsc->dsc$w_length = sptr - cptr;
      ValueDsc->dsc$a_pointer = cptr;
      return (SS$_NORMAL);
   }
   else
      return (SS$_ITEMNOTFOUND);
}

/*****************************************************************************/
/*
Return empty string rather than NULL if the CGI variable does not exist.
*/

char* RawLibCgiVar (char* VarName)
{
   char  *cptr;

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

   if (cptr = RawLibCgiVarNull (VarName))
      return (cptr);
   else
      return ("");
}

/*****************************************************************************/
/*
Return the value of a CGI variable regardless of whether it is used in a
standard CGI environment or a WASD CGIplus environment.  Automatically switches
WASD V7.2 into 'struct' mode for significantly improved performance.

Call with 'VarName' empty ("") to synchronize CGIplus requests.  This waits for
a CGIplus variable stream, checks if it's in 'record' or 'struct' mode, reads
the stream appropriately before returning ready to provide variables.

DO NOT modify the character string returned by this function.  Copy to other
storage if this is necessary.  The behaviour is indeterminate if the returned
values are modified in any way!

All CGIplus variables may be returned by making successive calls using a
'VarName' of "*" (often useful when debugging).  NULL is returned when
variables exhausted.
*/

char* RawLibCgiVarNull (char *VarName)
{
#define SOUS sizeof(ushort)

   static FILE  *CgiPlusIn;

   int  Length;
   char  WwwVarName [256];
   char  *bptr, *cptr, *sptr;

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

   /* if not using the AST-driven RawLibOnNextRequest() */
   if (!CgiPlusInChannel)
   {
      if (!StructBufferSize)
      {
         /**************/
         /* initialise */
         /**************/

         if (RawLibIsCgiPlus())
         {
            if ((cptr = getenv ("WASD_RAWLIB_STRUCT_BUFFER_SIZE")) != NULL)
               StructBufferSize = atoi(cptr);
            if (StructBufferSize < RAWLIB_STRUCT_BUFFER_SIZE)
               StructBufferSize = RAWLIB_STRUCT_BUFFER_SIZE;
            StructBufferPtr = calloc (1, StructBufferSize);
            if (!StructBufferPtr) RawLibExit (NULL, FI_LI, vaxc$errno);
            StructBufferLength = 0;
         }
         else
            StructBufferSize = -1;
      }

      if (VarName == NULL || !VarName[0])
      {
         /* initialize */
         StructBufferLength = WwwPrefix = 0;
         NextVarNamePtr = StructBufferPtr;
         if (CgiPlusEofPtr == NULL)
            WwwPrefix = (getenv ("WWW_SERVER_SOFTWARE") != NULL);
         if (VarName == NULL) return (NULL);
      }
   }

   if (VarName[0])
   {
      /***************************/
      /* return a variable value */
      /***************************/

      if (*(ULONGPTR)VarName == 'WWW_' && !WwwPrefix)
         VarName += 4;
      else
      if (*(ULONGPTR)VarName != 'WWW_' && WwwPrefix)
      {
         strcpy (WwwVarName, "WWW_");
         strcpy (WwwVarName+4, VarName);
         VarName = WwwVarName;
      }

      if (CgiPlusEofPtr == NULL)
      {
         /* standard CGI environment */
         CgiVariablesAvailable = 1;
         return (getenv (VarName));
      }

      if (!CgiVariablesAvailable) return (NULL);

      if (VarName[0] == '*')
      {
         /* return each CGIplus variable in successive calls */
         if (!(Length = *(USHORTPTR)NextVarNamePtr))
         {
            NextVarNamePtr = StructBufferPtr;
            return (NULL);
         }
         sptr = (NextVarNamePtr += SOUS);
         NextVarNamePtr += Length;
         return (sptr);
      }

      /* return a pointer to this CGIplus variable's value */
      for (bptr = StructBufferPtr;
           Length = *(USHORTPTR)bptr;
           bptr += Length)
      {
         sptr = (bptr += SOUS);
         for (cptr = VarName; *cptr && *sptr && *sptr != '='; cptr++, sptr++)
            if (toupper(*cptr) != toupper(*sptr)) break;
         /* if found return a pointer to the value */
         if (!*cptr && *sptr == '=') return (sptr+1);
      }
      /* not found */
      return (NULL);
   }

   /*****************************/
   /* get the CGIplus variables */
   /*****************************/

   if (RequestSynchronisation == NULL)
      RequestSynchronisation = (void*)RawLibCgiVarNull;
   else
   if (RequestSynchronisation != (void*)RawLibCgiVarNull)
      RawLibExit (NULL, FI_LI, SS$_BUGCHECK);

   /* cannot "sync" in a non-CGIplus environment */
   if (CgiPlusEofPtr == NULL) return (NULL);

   /* the CGIPLUSIN stream can be left open */
   if (CgiPlusIn == NULL)
      if ((CgiPlusIn = fopen (getenv("CGIPLUSIN"), "r")) == NULL)
         RawLibExit (NULL, FI_LI, vaxc$errno);

   CgiVariablesAvailable = WwwPrefix = 0;
 
   /* get the starting record (the essentially discardable one) */
   for (;;)
   {
      cptr = fgets (StructBufferPtr, StructBufferSize, CgiPlusIn);
      if (cptr == NULL) RawLibExit (NULL, FI_LI, vaxc$errno);
      /* if the starting sentinal is detected then break */
      if (*(USHORTPTR)cptr == '!\0' ||
          *(USHORTPTR)cptr == '!\n' ||
          (*(USHORTPTR)cptr == '!!' && isdigit(*(cptr+2)))) break;
   }

   if (*(USHORTPTR)cptr == '!!')
   {
      /********************/
      /* CGIplus 'struct' */
      /********************/

      /* get the size of the binary structure */
      StructBufferLength = atoi(cptr+2);
      if (StructBufferLength <= 0) RawLibExit (NULL, FI_LI, SS$_BUGCHECK);

      if (StructBufferLength > StructBufferSize)
      {
         while (StructBufferLength > StructBufferSize) StructBufferSize *= 2;
         free (StructBufferPtr);
         StructBufferPtr = calloc (1, StructBufferSize);
         if (!StructBufferPtr) RawLibExit (NULL, FI_LI, vaxc$errno);
         NextVarNamePtr = StructBufferPtr;
      }

      if (!fread (StructBufferPtr, 1, StructBufferLength, CgiPlusIn))
         RawLibExit (NULL, FI_LI, vaxc$errno);
   }
   else
   {
      /*********************/
      /* CGIplus 'records' */
      /*********************/

      /* reconstructs the original 'struct'ure from the records */
      sptr = (bptr = StructBufferPtr) + StructBufferSize;
      while (fgets (bptr+SOUS, sptr-(bptr+SOUS), CgiPlusIn) != NULL)
      {
         /* first empty record (line) terminates variables */
         if (bptr[SOUS] == '\n') break;
         /* note the location of the length word */
         cptr = bptr;
         for (bptr += SOUS; *bptr && *bptr != '\n'; bptr++);
         if (*bptr != '\n') RawLibExit (NULL, FI_LI, SS$_BUGCHECK);
         *bptr++ = '\0';
         if (bptr >= sptr) RawLibExit (NULL, FI_LI, SS$_BUGCHECK);
         /* update the length word */
         *(USHORTPTR)cptr = bptr - (cptr + SOUS);
      }
      if (bptr >= sptr) RawLibExit (NULL, FI_LI, SS$_BUGCHECK);
      /* terminate with a zero-length entry */
      *(USHORTPTR)bptr = 0;
      StructBufferLength = (bptr + SOUS) - StructBufferPtr;
   }

   /***********************/
   /* variables available */
   /***********************/

   CgiVariablesAvailable = 1;

   sptr = StructBufferPtr + SOUS;
   if (*(ULONGPTR)sptr == 'WWW_') WwwPrefix = 1;

   if (!CalloutDone)
   {
      /* provide the CGI callout to set CGIplus into 'struct' mode */
      fflush (stdout);
      fputs (CgiPlusEscPtr, stdout);
      fflush (stdout);
      /* the leading '!' indicates we're not going to read the response */
      fputs ("!CGIPLUS: struct", stdout);
      fflush (stdout);
      fputs (CgiPlusEotPtr, stdout);
      fflush (stdout);
      /* don't need to do this again (the '!!' tells us what mode) */
      CalloutDone = 1;
   }

   return (NULL);

#  undef SOUS
}

/*****************************************************************************/
/*
Asynchronous notification of the next request becoming available.  This
function should be called once during script initialisation specifying the
address of the function to be notified of a new request.  Until the script
issues the CGIplus EOF sentinal the RawLibCgiVar..() family of functions can be
used to retrieve the CGI variable values.  This asynchronous and the
synchronous RawLibCgiVar("") approaches to request synchronisation are mutually
exclusive and once one is used, using the other results in a bugcheck.
*/

void RawLibOnNextRequest (void *AstFunction)
{
#define SOUS sizeof(ushort)

   static int  CGIplusStruct = 0,
               StartSentinal = 0;
   static char  SentinalBuffer [256];
   static $DESCRIPTOR (CgiPlusInDsc, "");
   static struct RawLibIOsb  IOsb;
   static void  *RequestAstFunction;

   int  bcnt, status;
   char  *bptr, *cptr;

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

   if (RequestSynchronisation == NULL)
      RequestSynchronisation = RawLibOnNextRequest;
   else
   if (RequestSynchronisation != RawLibOnNextRequest)
      RawLibExit (NULL, FI_LI, SS$_BUGCHECK);

   if (!CgiPlusInChannel)
   {
      /**************/
      /* initialise */
      /**************/

      /* ensure initialisation */
      if (!RawLibIsCgiPlus())
      {
         /* hmmm, standard CGI environment - do something sensible */
         sys$dclast (AstFunction, 0, 0, 0);
         return;
      }

      if (!AstFunction) RawLibExit (NULL, FI_LI, SS$_BUGCHECK);
      RequestAstFunction = AstFunction;

      if ((cptr = getenv("CGIPLUSIN")) == NULL)
         RawLibExit (NULL, FI_LI, SS$_BUGCHECK);

      CgiPlusInDsc.dsc$a_pointer = cptr;
      CgiPlusInDsc.dsc$w_length = strlen(cptr);

      status = sys$assign (&CgiPlusInDsc, &CgiPlusInChannel, 0, 0);
      if (VMSnok(status)) RawLibExit (NULL, FI_LI, status);

      if ((cptr = getenv ("WASD_RAWLIB_STRUCT_BUFFER_SIZE")) != NULL)
         StructBufferSize = atoi(cptr);
      if (StructBufferSize < RAWLIB_STRUCT_BUFFER_SIZE)
         StructBufferSize = RAWLIB_STRUCT_BUFFER_SIZE;
      StructBufferPtr = calloc (1, StructBufferSize+2);
      if (!StructBufferPtr) RawLibExit (NULL, FI_LI, vaxc$errno);
      StructBufferLength = 0;

      status = sys$qio (EfnNoWait, CgiPlusInChannel,
                        IO$_READLBLK, &IOsb,
                        RawLibOnNextRequest, NULL,
                        SentinalBuffer, sizeof(SentinalBuffer)-1,
                        0, 0, 0, 0);
      if (VMSnok(status)) RawLibExit (NULL, FI_LI, status);

      return;
   }
   else
   if (AstFunction)
   {
      /* modify AST function */
      RequestAstFunction = AstFunction;
      return;
   }

   /***************/
   /* read stream */
   /***************/

   if (!CgiPlusInChannel) RawLibExit (NULL, FI_LI, SS$_BUGCHECK);
   if (AstFunction) RawLibExit (NULL, FI_LI, SS$_BUGCHECK);

   if (IOsb.iosb$w_status && VMSnok(IOsb.iosb$w_status))
      RawLibExit (NULL, FI_LI, IOsb.iosb$w_status);

   if (!StartSentinal)
   {
      cptr = SentinalBuffer;
      cptr[IOsb.iosb$w_bcnt] = '\0';

      /* if the starting sentinal is not detected then ignore */
      if (!(*(USHORTPTR)cptr == '!\0' ||
            *(USHORTPTR)cptr == '!\n' ||
            (*(USHORTPTR)cptr == '!!' && isdigit(*(cptr+2)))))
      {
         status = sys$qio (EfnNoWait, CgiPlusInChannel,
                           IO$_READLBLK, &IOsb,
                           RawLibOnNextRequest, NULL,
                           SentinalBuffer, sizeof(SentinalBuffer)-1,
                           0, 0, 0, 0);
         if (VMSnok(status)) RawLibExit (NULL, FI_LI, status);
         return;
      }

      StartSentinal = 1;
      bcnt = CgiVariablesAvailable = StructBufferLength = 0;

      if (*(USHORTPTR)cptr == '!!' && isdigit(*(cptr+2)))
      {
         /* get the size of the binary structure */
         StructBufferLength = atoi(cptr+2);
         if (StructBufferLength <= 0)
            RawLibExit (NULL, FI_LI, SS$_BUGCHECK);
         if (StructBufferLength > StructBufferSize)
         {
            while (StructBufferLength > StructBufferSize)
               StructBufferSize *= 2;
            free (StructBufferPtr);
            StructBufferPtr = calloc (1, StructBufferSize+2);
            if (!StructBufferPtr) RawLibExit (NULL, FI_LI, vaxc$errno);
            NextVarNamePtr = StructBufferPtr;
         }
         CGIplusStruct = 1;
         /* drop through to the structure processing code */
      }
      else
      {
         /* read the first variable record */
         status = sys$qio (EfnNoWait, CgiPlusInChannel,
                           IO$_READLBLK, &IOsb,
                           RawLibOnNextRequest, NULL,
                           StructBufferPtr + SOUS,
                           StructBufferSize - SOUS,
                           0, 0, 0, 0);
         if (VMSnok(status)) RawLibExit (NULL, FI_LI, status);
         CGIplusStruct = 0;
         return;
      }
   }
   else
   if (CGIplusStruct)
   {
      cptr = StructBufferPtr + StructBufferLength;
      cptr[bcnt = IOsb.iosb$w_bcnt] = '\0';
   }
   else
   {
      cptr = StructBufferPtr + StructBufferLength + SOUS;
      cptr[bcnt = IOsb.iosb$w_bcnt] = '\0';
   }

   if (CGIplusStruct)
   {
      /********************/
      /* CGIplus 'struct' */
      /********************/

      if (bcnt < StructBufferLength)
      {
         status = sys$qio (EfnNoWait, CgiPlusInChannel,
                           IO$_READLBLK, &IOsb,
                           RawLibOnNextRequest, NULL,
                           StructBufferPtr,
                           StructBufferLength, 0, 0, 0, 0);
         if (VMSnok(status)) RawLibExit (NULL, FI_LI, status);
         return;
      }

      if (bcnt > StructBufferLength)
         RawLibExit (NULL, FI_LI, SS$_BUGCHECK);
   }
   else
   {
      /*********************/
      /* CGIplus 'records' */
      /*********************/

      /* first empty record terminates variables */
      if (bcnt)
      {
         /* reconstructs the original 'struct'ure from the records */
         bptr = StructBufferPtr + StructBufferLength;
         /* update the length word */
         *(USHORTPTR)bptr = bcnt;
         /* terminate with a zero-length entry */
         bptr += bcnt + SOUS;
         *(USHORTPTR)bptr = 0;
         StructBufferLength = bptr - StructBufferPtr;

         status = sys$qio (EfnNoWait, CgiPlusInChannel,
                           IO$_READLBLK, &IOsb,
                           RawLibOnNextRequest, NULL,
                           StructBufferPtr + StructBufferLength + SOUS,
                           StructBufferSize - StructBufferLength - SOUS,
                           0, 0, 0, 0);
         if (VMSnok(status)) RawLibExit (NULL, FI_LI, status);

         return;
      }
   }

   /***********************/
   /* variables available */
   /***********************/

   bptr = StructBufferPtr + SOUS;
   if (*(ULONGPTR)bptr == 'WWW_') WwwPrefix = 1;

   if (!CalloutDone)
   {
      /* provide the CGI callout to set CGIplus into 'struct' mode */
      fflush (stdout);
      fputs (CgiPlusEscPtr, stdout);
      fflush (stdout);
      /* the leading '!' indicates we're not going to read the response */
      fputs ("!CGIPLUS: struct", stdout);
      fflush (stdout);
      fputs (CgiPlusEotPtr, stdout);
      fflush (stdout);
      /* don't need to do this again */
      CalloutDone = 1;
   }

   CgiVariablesAvailable = 1;

   sys$dclast (RequestAstFunction, 0, 0, 0);

   /********************************/
   /* queue ready for next request */
   /********************************/

   StartSentinal = 0;

   status = sys$qio (EfnNoWait, CgiPlusInChannel,
                     IO$_READLBLK, &IOsb,
                     RawLibOnNextRequest, NULL,
                     SentinalBuffer, sizeof(SentinalBuffer)-1,
                     0, 0, 0, 0);
   if (VMSnok(status)) RawLibExit (NULL, FI_LI, status);

#  undef SOUS
}

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

