/*****************************************************************************/
/*
                                  StmLf.c

This module implements a full multi-threaded, AST-driven, asynchronous file 
conversion from variable to stream-LF record format, by copying it into a
next-highest version.  When called it allocates a dynamic structure and creates
its own I/O-event-driven thread.  Errors are reported to the HTTPd process log,
and do not affect any other activities (including request processing) within
the server.  Will purge the pre-existing version, however if multiple versions
already existed only deletes the one copied from!

Why this module's functionality?  The WASD VMS HTTPd is significantly more
efficient in transfering stream-format than variable-format files.  The reason?
Stream files can be read using block I/O, variable must be processed
record-by-record and buffered into a larger block before network I/O, very much
more expensive.  This module allows detected variable-format files to be
converted to stream-LF "on-the-fly".  This happens in a thread separate to the
detecting and initiating request, so the original variable-format file is used
to concurrently supply that particular request, with the converted file
probably available by the time the next request for it occurs.  In addition,
the WASD HTTPd can determine the "Content-Length:" of stream-LF files and hence
can not only supply this item to the client but as a consequence participate
in a persistent connection request.


VERSION HISTORY
---------------
13-MAR-2008  MGD  StmLfLog() include timestamp
30-MAY-2006  MGD  bugfix; StmLfLog() -E- to -I- for non-status-value call
27-APR-2002  MGD  use sys$setprv()
04-AUG-2001  MGD  support module WATCHing
27-DEC-1999  MGD  support ODS-2 and ODS-5
28-JAN-1999  MGD  ensure created files are truncated-on-close
07-OCT-1998  MGD  change path specification to SET rule mapping
19-AUG-1998  MGD  confine conversions to specified paths
17-AUG-1997  MGD  SYSUAF-authenticated users security-profile
01-AUG-1996  MGD  initial, v3.3
*/
/*****************************************************************************/

#ifdef WASD_VMS_V7
#undef _VMS__V6__SOURCE
#define _VMS__V6__SOURCE
#undef __VMS_VER
#define __VMS_VER 70000000
#undef __CRTL_VER
#define __CRTL_VER 70000000
#endif

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

/* VMS related header files */
#include <descrip.h>
#include <rms.h>
#include <rmsdef.h>
#include <ssdef.h>
#include <stsdef.h>

/* application header files */
#include "wasd.h"
#include "httpd.h"
#include "stmlf.h"

#define WASD_MODULE "STMLF"

/**
#define STMLF_NOPURGE 1
**/

#define MAX_PURGE_ATTEMPTS 30

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

extern unsigned long  SysPrvMask[];

extern ACCOUNTING_STRUCT  *AccountingPtr;
extern CONFIG_STRUCT  Config;
extern WATCH_STRUCT  Watch;

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

StmLfBegin
(
char *FilePath,
char *FileName,
int FileNameLength,
BOOL OdsExtended,
BOOL VmsUserHasAccess
)
{
   int  status;
   char  *cptr, *sptr;
   struct StreamLfStruct  *stmptr;

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

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER,
                 "StmLfBegin() !&Z !&Z !UL !UL",
                 FilePath, FileName, OdsExtended, VmsUserHasAccess);

   /* allocate dynamic memory for the conversion thread */
   stmptr = (struct StreamLfStruct*) VmGet (sizeof(struct StreamLfStruct));

   /* reset this boolean so that anything but source EOF is not success */
   stmptr->ConversionOk = stmptr->DstFileCreated = false;
   /* copy the filename into thread-persistent storage */
   memcpy (stmptr->FileName, FileName, FileNameLength+1);
   /* set a limit on the number of attempts to purge the source file */
   stmptr->PurgeAttemptCount = MAX_PURGE_ATTEMPTS;

   stmptr->SrcFileFab = cc$rms_fab;
   stmptr->SrcFileFab.fab$b_fac = FAB$M_GET;
   stmptr->SrcFileFab.fab$b_shr = FAB$M_SHRGET | FAB$M_SHRPUT;

#ifdef ODS_EXTENDED
   if (OdsExtended)
   {
      stmptr->SrcFileFab.fab$l_fna = -1;  
      stmptr->SrcFileFab.fab$b_fns = 0;
      stmptr->SrcFileFab.fab$l_nam = &stmptr->SrcFileNaml;

      ENAMEL_RMS_NAML(stmptr->SrcFileNaml)
      stmptr->SrcFileNaml.naml$l_long_filename = stmptr->FileName;  
      stmptr->SrcFileNaml.naml$l_long_filename_size = FileNameLength;
      stmptr->SrcFileNaml.naml$l_long_expand = stmptr->SrcExpFileName;
      stmptr->SrcFileNaml.naml$l_long_expand_alloc =
         sizeof(stmptr->SrcExpFileName)-1;
   }
   else
#endif /* ODS_EXTENDED */
   {
      stmptr->SrcFileFab.fab$l_fna = stmptr->FileName;  
      stmptr->SrcFileFab.fab$b_fns = FileNameLength;
      stmptr->SrcFileFab.fab$l_nam = &stmptr->SrcFileNam;

      stmptr->SrcFileNam = cc$rms_nam;
      stmptr->SrcFileNam.nam$l_esa = stmptr->SrcExpFileName;
      stmptr->SrcFileNam.nam$b_ess = ODS2_MAX_FILE_NAME_LENGTH;
   }

   if (VmsUserHasAccess) sys$setprv (1, &SysPrvMask, 0, 0);

   status = sys$open (&stmptr->SrcFileFab, 0, 0);

   if (VmsUserHasAccess) sys$setprv (0, &SysPrvMask, 0, 0);

   if (VMSnok (status))
   {
      StmLfLog (stmptr, "sys$open()", status);
      StmLfEnd (stmptr);
      return;
   }

#ifdef ODS_EXTENDED
   if (OdsExtended)
      stmptr->SrcFileNaml.naml$l_long_ver[stmptr->SrcFileNaml.naml$l_long_ver_size] = '\0';
   else
#endif /* ODS_EXTENDED */
      stmptr->SrcFileNam.nam$l_ver[stmptr->SrcFileNam.nam$b_ver] = '\0';

   stmptr->SrcFileRab = cc$rms_rab;
   stmptr->SrcFileRab.rab$l_fab = &stmptr->SrcFileFab;
   stmptr->SrcFileRab.rab$b_mbf = 2;
   stmptr->SrcFileRab.rab$l_rop = RAB$M_RAH | RAB$M_ASY;
   stmptr->SrcFileRab.rab$l_ubf = stmptr->Buffer;
   stmptr->SrcFileRab.rab$w_usz = sizeof(stmptr->Buffer);

   /* set the RAB context to contain the conversion thread pointer */
   stmptr->SrcFileRab.rab$l_ctx = stmptr;

   status = sys$connect (&stmptr->SrcFileRab, 0, 0);
   if (VMSnok (status))
   {
      StmLfLog (stmptr, "sys$connect() src", status);
      StmLfEnd (stmptr);
      return;
   }

   stmptr->DstFileFab = cc$rms_fab;
   stmptr->DstFileFab.fab$l_fop = FAB$M_SQO | FAB$M_TEF;
   stmptr->DstFileFab.fab$b_rfm = FAB$C_STMLF;

#ifdef ODS_EXTENDED
   if (OdsExtended)
   {
      stmptr->DstFileFab.fab$l_fna = -1;  
      stmptr->DstFileFab.fab$b_fns = 0;
      stmptr->DstFileFab.fab$l_nam = &stmptr->DstFileNaml;  

      ENAMEL_RMS_NAML(stmptr->DstFileNaml)
      stmptr->DstFileNaml.naml$l_long_filename = stmptr->FileName;  
      for (cptr = stmptr->FileName; *cptr && *cptr != ';'; cptr++);
      stmptr->DstFileNaml.naml$l_long_filename_size = cptr - stmptr->FileName;
      stmptr->DstFileNaml.naml$l_long_expand = stmptr->DstExpFileName;  
      stmptr->DstFileNaml.naml$l_long_expand_alloc = sizeof(stmptr->DstExpFileName)-1;
      stmptr->DstFileNaml.naml$l_long_result = stmptr->DstResFileName;  
      stmptr->DstFileNaml.naml$l_long_result_alloc = sizeof(stmptr->DstResFileName)-1;
   }
   else
#endif /* ODS_EXTENDED */
   {
      stmptr->DstFileFab.fab$l_fna = stmptr->FileName;  
      for (cptr = stmptr->FileName; *cptr && *cptr != ';'; cptr++);
      stmptr->DstFileFab.fab$b_fns = cptr - stmptr->FileName;
      stmptr->DstFileFab.fab$l_nam = &stmptr->DstFileNam;  

      stmptr->DstFileNam = cc$rms_nam;
      stmptr->DstFileNam.nam$l_esa = stmptr->DstExpFileName;  
      stmptr->DstFileNam.nam$b_ess = ODS2_MAX_FILE_NAME_LENGTH;
      stmptr->DstFileNam.nam$l_rsa = stmptr->DstResFileName;  
      stmptr->DstFileNam.nam$b_rss = ODS2_MAX_FILE_NAME_LENGTH;
   }

   /* use SYSPRV to ensure creation of file (provided protection is S:RWED) */
   sys$setprv (1, &SysPrvMask, 0, 0);

   status = sys$create (&stmptr->DstFileFab, 0, 0);

   sys$setprv (0, &SysPrvMask, 0, 0);

   /* check the status of the file create */
   if (VMSnok (status))
   {
      StmLfLog (stmptr, "sys$create()", status);
      StmLfEnd (stmptr);
      return;
   }

   stmptr->DstFileCreated = true;

#ifdef ODS_EXTENDED
   if (OdsExtended)
      stmptr->DstFileNaml.naml$l_long_ver[stmptr->DstFileNaml.naml$l_long_ver_size] = '\0'; 
   else
#endif /* ODS_EXTENDED */
      stmptr->DstFileNam.nam$l_ver[stmptr->DstFileNam.nam$b_ver] = '\0'; 

   stmptr->DstFileRab = cc$rms_rab;
   stmptr->DstFileRab.rab$l_fab = &stmptr->DstFileFab;
   stmptr->DstFileRab.rab$b_mbf = 2;
   stmptr->DstFileRab.rab$l_rop = RAB$M_WBH | RAB$M_ASY;
   stmptr->DstFileRab.rab$l_rbf = stmptr->Buffer;

   /* set the RAB context to contain the conversion thread pointer */
   stmptr->DstFileRab.rab$l_ctx = stmptr;

   status = sys$connect (&stmptr->DstFileRab, 0, 0);
   if (VMSnok (status))
   {
      StmLfLog (stmptr, "sys$connect() dst", status);
      StmLfEnd (stmptr);
      return;
   }

   /* get the first reacord */
   status = sys$get (&stmptr->SrcFileRab,
                     &StmLfNextRecordAst, &StmLfNextRecordAst);
}

/****************************************************************************/
/*
Close source and destination files.  If successfully converted attempt to
purge the version it was copied from.  If the file is still locked due to
a still continuing transfer to a (the) client then set a timer and try again
in one second.  Do this a number of times before giving up!  If not
successfully copied then delete any new version created in the attempt.
*/

StmLfEnd (struct StreamLfStruct *stmptr)

{
   /* one second between purge attempts */
   static unsigned long  PurgeDeltaTime64 [2] = { -10000000, -1 };

   int  status;

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

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER, "StmLfEnd() !8XL", stmptr);

   if (stmptr->SrcFileFab.fab$w_ifi) sys$close (&stmptr->SrcFileFab, 0, 0);

   if (stmptr->ConversionOk)
   {
      InstanceGblSecIncrLong (&AccountingPtr->StreamLfConversionCount);

      if (stmptr->DstFileFab.fab$w_ifi) sys$close (&stmptr->DstFileFab, 0, 0);

#ifndef STMLF_NOPURGE
      stmptr->SrcFileFab.fab$l_fop = FAB$M_NAM;

      /* use SYSPRV to ensure erasure of file */
      sys$setprv (1, &SysPrvMask, 0, 0);

      status = sys$erase (&stmptr->SrcFileFab, 0, 0);

      sys$setprv (0, &SysPrvMask, 0, 0);

      if (VMSnok (status))
      {
         if (status == RMS$_FLK)
         {
            if (stmptr->PurgeAttemptCount--)
            {
               status = sys$setimr (0, &PurgeDeltaTime64,
                                    &StmLfEnd, stmptr, 0);
               if (VMSok (status)) return;
               StmLfLog (stmptr, NULL, status);
            }
            else
               StmLfLog (stmptr, "sys$erase()", status);
         }
         else
            StmLfLog (stmptr, "sys$erase() src", status);
      }
#endif

      StmLfLog (stmptr, "converted", 0);
   }
   else
   if (stmptr->DstFileCreated)
   {
      sys$close (&stmptr->DstFileFab, 0, 0);
      stmptr->DstFileFab.fab$l_fop = FAB$M_NAM;

      /* use SYSPRV to ensure erasure of file */
      sys$setprv (1, &SysPrvMask, 0, 0);

      status = sys$erase (&stmptr->DstFileFab, 0, 0);

      sys$setprv (0, &SysPrvMask, 0, 0);

      if (VMSnok (status)) StmLfLog (stmptr, "sys$erase() dst", status);
   }

   VmFree (stmptr, FI_LI);
}

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

StmLfNextRecord (struct RAB *RabPtr)

{
   int  status;
   struct StreamLfStruct  *stmptr;

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

   stmptr = RabPtr->rab$l_ctx;

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER,
                 "StmLfNextRecord() !&F !8XL sts:!&X stv:!&X\n",
                 &StmLfNextRecord, stmptr,
                 RabPtr->rab$l_sts, RabPtr->rab$l_stv);

   if (VMSnok (stmptr->DstFileRab.rab$l_sts))
   {
      StmLfEnd (stmptr);
      return;
   }

   status = sys$get (&stmptr->SrcFileRab,
                     &StmLfNextRecordAst, &StmLfNextRecordAst);
}

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

StmLfNextRecordAst (struct RAB *RabPtr)

{
   int  status;
   struct StreamLfStruct  *stmptr;

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

   stmptr = RabPtr->rab$l_ctx;

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER,
                 "StmLfNextRecordAst() !&F !8XL sts:!&X stv:!&X\n",
                 &StmLfNextRecordAst, stmptr,
                 RabPtr->rab$l_sts, RabPtr->rab$l_stv, RabPtr->rab$w_rsz);

   if (VMSnok (stmptr->SrcFileRab.rab$l_sts))
   {
      if (stmptr->SrcFileRab.rab$l_sts == RMS$_EOF)
      {
         /* only place this boolean gets set */
         stmptr->ConversionOk = true;
         StmLfEnd (stmptr);
         return;
      }
      StmLfLog (stmptr, "sys$get()", stmptr->SrcFileRab.rab$l_sts);
      StmLfEnd (stmptr);
      return;
   }

   stmptr->DstFileRab.rab$l_rbf = stmptr->SrcFileRab.rab$l_ubf;
   stmptr->DstFileRab.rab$w_rsz = stmptr->SrcFileRab.rab$w_rsz;
   status = sys$put (&stmptr->DstFileRab,
                     &StmLfNextRecord, &StmLfNextRecord);
}

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

StmLfLog
(
struct StreamLfStruct *stmptr,
char *Explanation,
int StatusValue
)
{
   int  status;
   char  *FileNamePtr;

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

   if (WATCH_MODULE(WATCH_MOD__OTHER))
      WatchThis (WATCHALL, WATCH_MOD__OTHER, "StmLfLog() !8XL", stmptr);

   if (stmptr)
      FileNamePtr = stmptr->FileName;
   else
      FileNamePtr = "";

   if (StatusValue)
      FaoToStdout ("%HTTPD-E-STREAMLF, !20%D, !&X !AZ !AZ\n",
                   0, StatusValue, Explanation, FileNamePtr);
   else
      FaoToStdout ("%HTTPD-I-STREAMLF, !20%D, !AZ !AZ\n",
                   0, Explanation, FileNamePtr);
}

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

