/*****************************************************************************/
/*
                                   ODS.c 

Module supports file system access for both ODS-2 and where appropriate (Alpha
VMS v7.2ff) ODS-5.  This supports transparent access to ODS-5 file system
structure for the serving of data from the file-system.  (The WASD package
files however must still be supported inside of ODS-2 conventions.)

Basically it does this by abstracting the NAM block so that either the standard
or long formats may be used without other modules needing to be aware of the
underlying structure.  The VAX version does not need to know about long NAMs,
or extended file specifications in general (excluded for code compactness and
minor efficiency reasons using the ODS_EXTENDED macro).  Alpha versions prior
to 7.2 do not know about NAMLs and so for compilation in these environments a
compatible NAML is provided (ENAMEL.H header file), allowing extended file
specification compliant code to be compiled and linked on pre-v7.2 systems. 

Runtime decision structures based on the VMS version, device ACP type, etc.,
must be used if pre-v7.2 systems are to avoid using the NAML structure with RMS
(which would of course result in runtime errors).  In this way a single set of
base-level Alpha object modules, built in either environment, will support both
pre and post 7.2 environments.

The essential accessability to and functionality of RMS is not buried too
deeply by this wrapping.  File operations are still performed using RMS data
structures and services wherever possible, only those that process using NAM
structures are, and need to be, abstracted in this way (i.e. sys$open(),
sys$parse(), sys$search()).  ASTs are called using the same conventions as the
RMS service counterpart (i.e. with a pointer to the same data structure), and
success should be checked using the same mechanisms, etc.

Even when not supplying an AST address it is advised to supply the AST
parameter (usually a request or task pointer).  The reason?  Well this is often
placed into the FAB or RAB context storage for retrieval by an AST routine
subsequently and explicitly called by the code.  Experience has shown it's easy
to forget to set up this context in the non-AST instance ;^)


ATR$C_MODDATE vs. ATR$C_REVDATE
-------------------------------
Fairly late in the piece (see version log) it was reported that the classic
ATR$C_REVDATE also reflected changes to file attributes (e.g. an ACL modified)
as well as to data.  For ODS-5 volumes the ATR$C_MODDATE attribute was added to
record exclusively when file *data* was modified.  It was suggested that this
better represents the state for directory listings and certainly for WebDAV. 
So, as of the version log, for extended file processing (ODS-5), the
ATR$C_MODDATE will be used by default.  When present, this value is retrieved
from the attributes and automatically copied onto the ATR$C_REVDATE structure
item, meaning no downstream changes.  To disable this behaviour and return a
server to providing the ATR$C_REVDATE define the logical name WASD_ODS_REVDATE
before server startup.


DISPLAYING FILE SPECIFICATIONS
------------------------------
As there are a number of ways of displaying VMS file names in mixed ODS-2 and
ODS-5 environments this module seemed the appropriate place to discuss the WASD
approach (although much of this processing is distributed through the MAPURL.C,
DIR.C and UPD.C modules).

Prior to the extended file specifications of VMS V7.2 and following WASD tended
to default all file and directory paths to lower case (as this is commonly
considered more aesthetically pleasing - and to the author).  This is obvious
in directory listings but may also be seen in UPD.C and SSI.C file naming. 
Optionally then DIR.C module would display specification in upper case (for
instance if a semicolon was used in the URL path).

Now that extended file specifications exist in Alpha VMS V7.2 and following
there exist a number of methods of displaying file names.  Files on ODS-2
volumes continue to be treated as described above.  Files from ODS-5 volumes
become more complex as a number of options are available.  As ODS-5 supports a
mixed upper and lower case character set WASD no longer forces case, names are
displayed as stored on-disk.  RMS displays many non-alpha-numeric characters
using escape sequences, of which some have multiple, possible representations
(e.g. the space, "^ ", "^_", "^20", and other possibles).

In addition, a Web URL has it's own escape syntax for forbidden characters, the
"%xx" hexadecimal representation.  WASD attempts to accept file specifications
in either RMS-escape syntax or URL-escape syntax.  Presentation is most
commonly done using URL-escape syntax, but may in directory listing be
optionally displayed in RMS ("^_") and even on-disk format (" ") (see the DIR.C
module).

16 BIT (UNICODE) FILE NAMES ARE NOT SUPPORTED!
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


VERSION HISTORY
---------------
28-FEB-2023  MGD  bugfix; OdsDirect() [again!]
                    if (odsptr->DirectWildcard[0] &&
                        !odsptr->DirectVersion0)
                       status = RMS$_NMF;
26-SEP-2022  MGD  bugfix; OdsDirect() end of records (-1) in end file block
08-JUN-2022  MGD  OdsOpenReadOnly() fab$b_shr = FAB$M_SHRGET | FAB$M_SHRUPD
                    to allow InConfigInit() to fopen(..."shr=get")
30-DEC-2021  MGD  OdsAccessCheck() and logical name WASD_ODS_ACCESS_CHECK
14-OCT-2020  MGD  OdsFileAcpInfo() ATR$C_MODDATE (date-time *data* modified)
                    supplements ATR$C_REVDATE (classic revision date-time)
25-SEP-2020  MGD  bugfix; OdsDirectSearch() appending the resultant file name
                    to the pre-filled expanded name
11-AUG-2020  MGD  bugfix; OdsDirectSearch() if wildcard specification
                    return RMS$_NMF, otherwise RMS$_FNF (seems so elementary)
30-JUN-2020  MGD  expand WASD_ODS_NODIRECT for comma-separated device names
28-NOV-2019  MGD  bugfix; OdsDirectSearch() RMS$_FNF not RMS$_NMF (per JPP)
04-AUG-2019  MGD  bugfix; OdsDirectSearch() only if not already on the block
                    boundary add one to get to next, otherwise already there!
08-JAN-2019  MGD  bugfix; OdsDirectSearch() (uint)0xffff && rlen < 508)
05-MAY-2018  MGD  OdsDirectSearch() et.al. directory parse
                    disabled by $ DEFINE /SYSTEM WASD_ODS_NODIRECT *
                  OdsStructInit() formalises initialisation of ODS structure
25-JUN-2014  MGD  bugfix; enabling SYSPRV ensure it is only disabled
                    afterwards when it wasn't previously enabled
27-SEP-2013  MGD  OdsNamBlockAst() on non-ODS_EXTENDED platforms (i.e. VAX)
                    add Nam.nam$l_ver to Nam.nam$l_type and Nam.nam$l_name 
06-JUN-2013  MGD  OdsNamBlockAst() on non-ODS_EXTENDED platforms (i.e. VAX)
                    tease-out system file name from Nam.nam$l_name and
                    Nam.nam$l_type into odsptr->SysFileName buffer
                    historically used by ODS-5 and munge for ODS-2 as well
                  bugfix; OdsParseTerminate() on non-ODS_EXTENDED platforms
                    (i.e. VAX) reset .nam$b_esl to changed expanded length
                    or it can generate RMS$_ESL errors - check it out!
30-SEP-2011  MGD  bugfix; OdsFileExists() parse NAM$M_NOCONCEAL in case of
                    multi-valued, concealed logical devices and then convert
                    returned status DNF into the functional equivalent FNF
13-MAR-2011  MGD  OdsLoadTextFile() transmogrify embedded null char to space
23-JUN-2010  MGD  bugfix; OdsNamBlockAst() odsptr->NamFileSysNamePtr 
                    always set to odsptr->SysFileName in case RMS$_FNF, etc.
08-JUN-2010  MGD  OdsParseTerminate() allow for trailing period(s)
                  bugfix; OdsNameOfDirectoryFile() allow for '^[' and '^]'
13-JUN-2009  MGD  OdsNameOfDirectoryFile() delay return argument reset
                    make cached name comparison case-sensitive (WebDAV)
18-JAN-2008  MGD  OdsOpen() and OdsCreate() support STR_DSC
                  OdsClose() refine FAB$M_DLT and FAB$M_TEF handling
10-AUG-2007  MGD  OdsReallyADir()
25-MAY-2007  MGD  OdsNameOfDirectoryFile() no longer mandatory that a
                    directory file actually exists to generate the name 
                  OdsFileAcpInfo() add ATR$C_UCHAR to attributes retrieved
                  bugfix; OdsNamBlockAst() deliver AST with 'AstParam'
                    (requiring parameter changes to *lots* of AST functions
                    called by use of OdsParse() and OdsSearch() - bugga!)
24-APR-2007  MGD  OdsCreate() to allow explicit file creation
                  bugfix; OdsNameOfDirectoryFile() allow for '^.'
02-OCT-2006  MGD  OdsVolumeStructure() originally MapOdsVolumeStruct()
07-DEC-2002  MGD  bugfix; in OdsNameOfDirectoryFile() use SYSPRV
                  around sys$parse() to ensure access to directory
05-OCT-2002  MGD  make OdsFileExists() a little more flexible
03-JUN-2002  MGD  bugfix; ensure when OdsParse() is used successively with
                  the same ODS structure that previous resources are first 
                  released (can present a problem unique to search lists)
05-JAN-2002  MGD  bugfix; VAX OdsSearchNoConceal() expanded buffer
17-NOV-2001  MGD  OdsSearchNoConceal() and modifications to OdsNamBlockAst()
                  to support "charset=(file,charset)" processing
10-OCT-2001  MGD  bugfix; sys$close() in OdsLoadTextFile()
04-AUG-2001  MGD  OdsCopyStructure(), OdsFreeTextFile(),
                  support module WATCHing,
                  bugfix; NamFileSysNamePtr/Length in OdsFileAcpInfo()
28-FEB-2001  MGD  OdsLoadTextFile(), OdsParseTextFile()
09-JUN-2000  MGD  search-list processing refined
26-DEC-1999  MGD  initial
*/
/*****************************************************************************/

#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 <armdef.h>
#include <chpdef.h>
#include <descrip.h>
#include <devdef.h>
#include <dvidef.h>
#include <iodef.h>
#include <prvdef.h>
#include <rmsdef.h>
#include <ssdef.h>
#include <stsdef.h>

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

#define WASD_MODULE "ODS"

#define ODS_PARSE_IN_USE_PATTERN 0xa5a55a5a

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

BOOL  OdsUseModDate;

#if ODS_DIRECT

BOOL  OdsDirect,
      OdsExtended,
      WasdOdsAccessCheck;

char  *OdsNoDirect;

#else

BOOL  OdsExtended;

#endif

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

extern BOOL  InConfigLoading;

extern int  EfnWait,
            EfnNoWait,
            OpcomMessages;

extern unsigned long  SysPrvMask[];

extern unsigned int  HttpdUserProfileLength;
extern unsigned char  *HttpdUserProfilePtr;
extern struct dsc$descriptor_s  HttpdUserProfileDsc;

extern ACCOUNTING_STRUCT  *AccountingPtr;
extern MSG_STRUCT  Msgs;
extern SYS_INFO  SysInfo;
extern WATCH_STRUCT  Watch;

/****************************************************************************/
/*
Set the ODS extended flag (i.e. supports ODS-5) if the architecture is not VAX
and the VMS version is 7.2ff and not explicitly disabled at the command line.
Check whether any ENAMEL.H NAML structure basically looks ok.
*/ 

OdsSetExtended ()

{
   char  *cptr;

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

   if (WATCH_MODULE(WATCH_MOD_ODS))
      WatchThis (WATCHALL, WATCH_MOD_ODS, "OdsSetExtended()");

   if (SysTrnLnm (WASD_ODS_ACCESS_CHECK)) WasdOdsAccessCheck = true;

#ifdef ODS_EXTENDED

#ifdef ENAMEL_NAML
   /* just a check for stupid errors on compilations using ENAMEL.H NAML */
   if (sizeof(struct NAML) != NAML$C_BLN)
   {
      FaoToStdout ("%HTTPD-W-ENAMEL, NAML problem, \
extended file specifications not supported\n");
      if (OpcomMessages & OPCOM_HTTPD)
         FaoToOpcom ("%HTTPD-W-ENAMEL, NAML problem, \
extended file specifications not supported\n");
      OdsExtended = false;
      return;
   }
#endif /* ENAMEL_NAML */

#endif /* ODS_EXTENDED */

   if (SysInfo.VersionInteger >= 720) OdsExtended = true;

   FaoToStdout ("%HTTPD-I-ODS5, !AZsupported by !AZ VMS !AZ\n",
                OdsExtended ? "" : "not ",
#ifdef __ALPHA
                "Alpha",
#endif
#ifdef __ia64
                "IA64",
#endif
#ifdef __x86_64
                "x86-64",
#endif
                SysInfo.Version);

   if (OdsExtended)
   {
      OdsUseModDate = (SysTrnLnm(WASD_ODS_REVDATE) == NULL);
      if (!OdsUseModDate) FaoToStdout ("%HTTPD-I-ODS, using ATR$C_REVDATE\n");
   }

#if ODS_DIRECT

   OdsDirect = true;
   OdsNoDirect = SysTrnLnm(WASD_ODS_NODIRECT);
   if (OdsNoDirect)
      if (OdsNoDirect[0] == '*')
         OdsDirect = false;
      else
         OdsNoDirect = strdup (OdsNoDirect);

   FaoToStdout ("%HTTPD-I-ODS, directory parser !AZ\n",
                OdsDirect ? "enabled" : "disabled");
   if (OdsDirect && OdsNoDirect)
      FaoToStdout ("-HTTPD-I-ODS, exclude !AZ\n", OdsNoDirect);

#endif /* ODS_DIRECT */
}

/*****************************************************************************/
/*
Initialise an ODS structure.  If on the stack then zero the memory.  Otherwise
assume the structure is already in zero-allocated memory or equivalent.
*/

void OdsStructInit
(
ODS_STRUCT *odsptr,
BOOL OnStack
)
{
   /*********/
   /* begin */
   /*********/

   if (WATCH_MODULE(WATCH_MOD_ODS))
      WatchThis (WATCHALL, WATCH_MOD_ODS, "OdsStructInit() !&B", OnStack);

   if (OnStack) memset (odsptr, 0, sizeof(ODS_STRUCT));
}

/****************************************************************************/
/*
Generic open for read.  Intended for configuration files, etc., but also used
to read the raw content of a file in block mode.  Completely synchronous with a
RAB connected for sequential read.
*/ 

int OdsOpenReadOnly
(
ODS_STRUCT *odsptr,
char *FileSpec,
BOOL BlockIO
)
{
   int  status;

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

   if (WATCH_MODULE(WATCH_MOD_ODS))
      WatchThis (WATCHALL, WATCH_MOD_ODS,
                 "OdsOpenReadOnly() !&B !&B !&B !&Z",
                 OdsExtended, BlockIO, InConfigLoading, FileSpec);

   return (OdsOpenShrUpdReadOnly (odsptr, FileSpec, BlockIO, InConfigLoading));
}

/****************************************************************************/
/*
Generic open for read with shared update.
*/ 

int OdsOpenShrUpdReadOnly
(
ODS_STRUCT *odsptr,
char *FileSpec,
BOOL BlockIO,
BOOL ShrUpd
)
{
   int  status;

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

   if (WATCH_MODULE(WATCH_MOD_ODS))
      WatchThis (WATCHALL, WATCH_MOD_ODS,
                 "OdsOpenReadOnly() !&B !&B !&B !&B !&Z",
                 OdsExtended, BlockIO, ShrUpd, InConfigLoading, FileSpec);

   OdsStructInit (odsptr, true);

   odsptr->Fab = cc$rms_fab;
   if (BlockIO)
      odsptr->Fab.fab$b_fac = FAB$M_GET | FAB$M_BIO;
   else
      odsptr->Fab.fab$b_fac = FAB$M_GET;

   /* an abundance of caution - only during inline-config loads */
   if (ShrUpd)
      odsptr->Fab.fab$b_shr = FAB$M_SHRGET | FAB$M_SHRUPD;
   else
      odsptr->Fab.fab$b_shr = FAB$M_SHRGET;

#ifdef ODS_EXTENDED
   if (OdsExtended)
   {
      odsptr->Fab.fab$l_fna = -1;  
      odsptr->Fab.fab$b_fns = 0;
      odsptr->Fab.fab$l_nam = &odsptr->Naml;

      odsptr->NamlInUse = true;
      ENAMEL_RMS_NAML(odsptr->Naml)
      odsptr->Naml.naml$l_long_filename = FileSpec;
      odsptr->Naml.naml$l_long_filename_size = strlen(FileSpec);
      odsptr->Naml.naml$l_long_expand = odsptr->ExpFileName;
      odsptr->Naml.naml$l_long_expand_alloc = sizeof(odsptr->ExpFileName)-1;
      odsptr->Naml.naml$l_long_result = odsptr->ResFileName;
      odsptr->Naml.naml$l_long_result_alloc = sizeof(odsptr->ResFileName)-1;
      odsptr->Naml.naml$l_filesys_name = odsptr->SysFileName;
      odsptr->Naml.naml$l_filesys_name_alloc = sizeof(odsptr->SysFileName)-1;
   }
   else
#endif /* ODS_EXTENDED */
   {
      odsptr->Fab.fab$l_fna = FileSpec;  
      odsptr->Fab.fab$b_fns = strlen(FileSpec);
      odsptr->Fab.fab$l_nam = &odsptr->Nam;

      odsptr->NamlInUse = false;
      odsptr->Nam = cc$rms_nam;
      odsptr->Nam.nam$l_esa = odsptr->ExpFileName;
      odsptr->Nam.nam$b_ess = ODS2_MAX_FILE_NAME_LENGTH;
      odsptr->Nam.nam$l_rsa = odsptr->ResFileName;
      odsptr->Nam.nam$b_rss = ODS2_MAX_FILE_NAME_LENGTH;
   }

   /* initialize the date, header and protection extended attribute blocks */
   odsptr->Fab.fab$l_xab = &odsptr->XabDat;
   odsptr->XabDat = cc$rms_xabdat;
   odsptr->XabDat.xab$l_nxt = &odsptr->XabFhc;
   odsptr->XabFhc = cc$rms_xabfhc;
   odsptr->XabFhc.xab$l_nxt = &odsptr->XabPro;
   odsptr->XabPro = cc$rms_xabpro;

   odsptr->Fab.fab$l_fop &= ~FAB$M_ASY;
   status = sys$open (&odsptr->Fab, 0, 0);
   if (VMSnok (status) || VMSnok (status = odsptr->Fab.fab$l_sts))
      return (status);

   /* set up the generic information from the NAM(L) block */
   odsptr->Fab.fab$l_ctx = odsptr;
   OdsNamBlockAst (&odsptr->Fab);

   /* record access block */
   odsptr->Rab = cc$rms_rab;
   odsptr->Rab.rab$l_fab = &odsptr->Fab;
   /* 2 buffers of six blocks each */
   odsptr->Rab.rab$b_mbc = 6;
   odsptr->Rab.rab$b_mbf = 2;
   /* read ahead performance option */
   odsptr->Rab.rab$l_rop = RAB$M_RAH;
   /* all synchronous (for now anyway) */
   odsptr->Rab.rab$l_rop &= ~FAB$M_ASY;
   if (BlockIO)
   {
      /* sequentially by block */
      odsptr->Rab.rab$l_rop |= RAB$M_BIO;
      odsptr->Rab.rab$l_bkt = 0;
   }

   status = sys$connect (&odsptr->Rab, 0, 0);
   if (VMSnok (status) || VMSnok (status = odsptr->Rab.rab$l_sts))
   {
      sys$close (&odsptr->Fab, 0, 0);
      return (status);
   }

   return (status);
}

/****************************************************************************/
/*
RMS open the supplied file specification (using appropriate standard or long
NAM structure).  OdsNamBlockAst() is always called, which sets a number of
pointers and other data from the NAM block so that calling functions do not
need to know which was used.
*/ 

int OdsOpen
(
ODS_STRUCT *odsptr,
char *FileSpec,
int FileSpecLength,
char *DefaultSpec,
int DefaultSpecLength,
int FabFac,
int FabFop,
int FabShr,
GENERAL_AST AstFunction,
unsigned long AstParam
)
{
   int  status;

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

   if (WATCH_MODULE(WATCH_MOD_ODS))
      WatchThis (WATCHALL, WATCH_MOD_ODS,
                 "OdsOpen() !&B !AZ !AZ !&A(!&X)",
                 OdsExtended,
                 FileSpecLength == -1 ?
                    STR_DSC_PTR((STR_DSC*)FileSpec) : FileSpec,
                 DefaultSpecLength == -1 ?
                    STR_DSC_PTR((STR_DSC*)DefaultSpec) : DefaultSpec,
                 AstFunction, AstParam);

   OdsStructInit (odsptr, true);

   odsptr->AstFunction = AstFunction;
   odsptr->AstParam = AstParam;

   if (FabFop & FAB$M_DLT)
      odsptr->DeleteOnClose = true;
   else
      odsptr->DeleteOnClose = false;

   if (FileSpecLength == -1)
   {
      /* use the WASD descriptor */
      FileSpecLength = STR_DSC_LEN((STR_DSC*)FileSpec);
      FileSpec = STR_DSC_PTR((STR_DSC*)FileSpec);
   }
   else
   if (!FileSpecLength && FileSpec)
      FileSpecLength = strlen(FileSpec);

   if (DefaultSpecLength == -1)
   {
      /* use the WASD descriptor */
      DefaultSpecLength = STR_DSC_LEN((STR_DSC*)DefaultSpec);
      DefaultSpec = STR_DSC_PTR((STR_DSC*)DefaultSpec);
   }
   else
   if (!DefaultSpecLength && DefaultSpec)
      DefaultSpecLength = strlen(DefaultSpec);

   odsptr->Fab = cc$rms_fab;
   odsptr->Fab.fab$l_ctx = odsptr;
   odsptr->Fab.fab$b_fac = FabFac;
   odsptr->Fab.fab$l_fop = FabFop;
   odsptr->Fab.fab$b_shr = FabShr;

#ifdef ODS_EXTENDED
   if (OdsExtended)
   {
      odsptr->Fab.fab$l_fna = -1;  
      odsptr->Fab.fab$b_fns = 0;
      odsptr->Fab.fab$l_nam = &odsptr->Naml;

      odsptr->NamlInUse = true;
      ENAMEL_RMS_NAML(odsptr->Naml)
      odsptr->Naml.naml$l_long_defname = DefaultSpec;
      odsptr->Naml.naml$l_long_defname_size = DefaultSpecLength;
      odsptr->Naml.naml$l_long_filename = FileSpec;
      odsptr->Naml.naml$l_long_filename_size = FileSpecLength;
      odsptr->Naml.naml$l_long_expand = odsptr->ExpFileName;
      odsptr->Naml.naml$l_long_expand_alloc = sizeof(odsptr->ExpFileName)-1;
      odsptr->Naml.naml$l_long_result = odsptr->ResFileName;
      odsptr->Naml.naml$l_long_result_alloc = sizeof(odsptr->ResFileName)-1;
      odsptr->Naml.naml$l_filesys_name = odsptr->SysFileName;
      odsptr->Naml.naml$l_filesys_name_alloc = sizeof(odsptr->SysFileName)-1;
   }
   else
#endif /* ODS_EXTENDED */
   {
      odsptr->Fab.fab$l_dna = DefaultSpec;  
      odsptr->Fab.fab$b_dns = DefaultSpecLength;
      odsptr->Fab.fab$l_fna = FileSpec;  
      odsptr->Fab.fab$b_fns = FileSpecLength;
      odsptr->Fab.fab$l_nam = &odsptr->Nam;

      odsptr->NamlInUse = false;
      odsptr->Nam = cc$rms_nam;
      odsptr->Nam.nam$l_esa = odsptr->ExpFileName;
      odsptr->Nam.nam$b_ess = ODS2_MAX_FILE_NAME_LENGTH;
      odsptr->Nam.nam$l_rsa = odsptr->ResFileName;
      odsptr->Nam.nam$b_rss = ODS2_MAX_FILE_NAME_LENGTH;
   }

   /* initialize the date, header and protection extended attribute blocks */
   odsptr->Fab.fab$l_xab = &odsptr->XabDat;
   odsptr->XabDat = cc$rms_xabdat;
   odsptr->XabDat.xab$l_nxt = &odsptr->XabFhc;
   odsptr->XabFhc = cc$rms_xabfhc;
   odsptr->XabFhc.xab$l_nxt = &odsptr->XabPro;
   odsptr->XabPro = cc$rms_xabpro;

   if (!AstFunction)
   {
      /* synchronous service */
      odsptr->Fab.fab$l_fop &= ~FAB$M_ASY;
      status = sys$open (&odsptr->Fab, 0, 0);
      OdsNamBlockAst (&odsptr->Fab);
      if (VMSok (status)) status = odsptr->Fab.fab$l_sts;
      return (status);
   }
   else
   {
      /* asynchronous service */
      odsptr->Fab.fab$l_fop |= FAB$M_ASY;
      status = sys$open (&odsptr->Fab, &OdsNamBlockAst, &OdsNamBlockAst);
      return (status);
   }
}

/****************************************************************************/
/*
RMS create the supplied file specification (using appropriate standard or long
NAM structure).  OdsNamBlockAst() is always called, which sets a number of
pointers and other data from the NAM block so that calling functions do not
need to know which was used.  If 'FabPtr' is supplied this function attempts to
clove the file attributes of the pointed to FAB.
*/ 

int OdsCreate
(
ODS_STRUCT *odsptr,
char *FileSpec,
int FileSpecLength,
char *DefaultSpec,
int DefaultSpecLength,
int FabFac,
int FabFop,
int FabShr,
int FabRfm,
int FabRat,
struct FAB *FabPtr,
GENERAL_AST AstFunction,
unsigned long AstParam
)
{
   int  status;

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

   if (WATCH_MODULE(WATCH_MOD_ODS))
      WatchThis (WATCHALL, WATCH_MOD_ODS,
                 "OdsCreate() !&B !AZ !AZ !&A(!&X)",
                 OdsExtended,
                 FileSpecLength == -1 ?
                    STR_DSC_PTR((STR_DSC*)FileSpec) : FileSpec,
                 DefaultSpecLength == -1 ?
                    STR_DSC_PTR((STR_DSC*)DefaultSpec) : DefaultSpec,
                 AstFunction, AstParam);

   OdsStructInit (odsptr, true);

   odsptr->AstFunction = AstFunction;
   odsptr->AstParam = AstParam;

   if (FabFop & FAB$M_DLT)
      odsptr->DeleteOnClose = true;
   else
      odsptr->DeleteOnClose = false;

   if (FileSpecLength == -1)
   {
      /* use the WASD descriptor */
      FileSpecLength = STR_DSC_LEN((STR_DSC*)FileSpec);
      FileSpec = STR_DSC_PTR((STR_DSC*)FileSpec);
   }
   else
   if (!FileSpecLength && FileSpec)
      FileSpecLength = strlen(FileSpec);

   if (DefaultSpecLength == -1)
   {
      /* use the WASD descriptor */
      DefaultSpecLength = STR_DSC_LEN((STR_DSC*)DefaultSpec);
      DefaultSpec = STR_DSC_PTR((STR_DSC*)DefaultSpec);
   }
   else
   if (!DefaultSpecLength && DefaultSpec)
      DefaultSpecLength = strlen(DefaultSpec);

   odsptr->Fab = cc$rms_fab;
   if (FabPtr)
   {
      /* 'clone' from supplied FAB */
      odsptr->Fab.fab$l_alq = FabPtr->fab$l_alq;
      odsptr->Fab.fab$w_deq = FabPtr->fab$w_deq;
      odsptr->Fab.fab$b_bks = FabPtr->fab$b_bks;
      odsptr->Fab.fab$b_fac = FabPtr->fab$b_fac;
      odsptr->Fab.fab$l_fop = FabPtr->fab$l_fop;
      odsptr->Fab.fab$b_fsz = FabPtr->fab$b_fsz;
      odsptr->Fab.fab$w_gbc = FabPtr->fab$w_gbc;
      odsptr->Fab.fab$l_mrn = FabPtr->fab$l_mrn;
      odsptr->Fab.fab$w_mrs = FabPtr->fab$w_mrs;
      odsptr->Fab.fab$b_org = FabPtr->fab$b_org;
      odsptr->Fab.fab$b_rat = FabPtr->fab$b_rat;
      odsptr->Fab.fab$b_rfm = FabPtr->fab$b_rfm;
      odsptr->Fab.fab$b_shr = FabPtr->fab$b_shr;
   }
   if (FabFac) odsptr->Fab.fab$b_fac = FabFac;
   if (FabFop) odsptr->Fab.fab$l_fop = FabFop;
   if (FabShr) odsptr->Fab.fab$b_shr = FabShr;
   if (FabRfm) odsptr->Fab.fab$b_rfm = FabRfm;
   if (FabRat) odsptr->Fab.fab$b_rat = FabRat;
   odsptr->Fab.fab$l_ctx = odsptr;

#ifdef ODS_EXTENDED
   if (OdsExtended)
   {
      odsptr->Fab.fab$l_fna = -1;  
      odsptr->Fab.fab$b_fns = 0;
      odsptr->Fab.fab$l_nam = &odsptr->Naml;

      odsptr->NamlInUse = true;
      ENAMEL_RMS_NAML(odsptr->Naml)
      odsptr->Naml.naml$l_long_defname = DefaultSpec;
      odsptr->Naml.naml$l_long_defname_size = DefaultSpecLength;
      odsptr->Naml.naml$l_long_filename = FileSpec;
      odsptr->Naml.naml$l_long_filename_size = FileSpecLength;
      odsptr->Naml.naml$l_long_expand = odsptr->ExpFileName;
      odsptr->Naml.naml$l_long_expand_alloc = sizeof(odsptr->ExpFileName)-1;
      odsptr->Naml.naml$l_long_result = odsptr->ResFileName;
      odsptr->Naml.naml$l_long_result_alloc = sizeof(odsptr->ResFileName)-1;
      odsptr->Naml.naml$l_filesys_name = odsptr->SysFileName;
      odsptr->Naml.naml$l_filesys_name_alloc = sizeof(odsptr->SysFileName)-1;
   }
   else
#endif /* ODS_EXTENDED */
   {
      odsptr->Fab.fab$l_dna = DefaultSpec;  
      odsptr->Fab.fab$b_dns = DefaultSpecLength;
      odsptr->Fab.fab$l_fna = FileSpec;  
      odsptr->Fab.fab$b_fns = FileSpecLength;
      odsptr->Fab.fab$l_nam = &odsptr->Nam;

      odsptr->NamlInUse = false;
      odsptr->Nam = cc$rms_nam;
      odsptr->Nam.nam$l_esa = odsptr->ExpFileName;
      odsptr->Nam.nam$b_ess = ODS2_MAX_FILE_NAME_LENGTH;
      odsptr->Nam.nam$l_rsa = odsptr->ResFileName;
      odsptr->Nam.nam$b_rss = ODS2_MAX_FILE_NAME_LENGTH;
   }

   /* initialize the date, header and protection extended attribute blocks */
   odsptr->Fab.fab$l_xab = &odsptr->XabDat;
   odsptr->XabDat = cc$rms_xabdat;
   odsptr->XabDat.xab$l_nxt = &odsptr->XabFhc;
   odsptr->XabFhc = cc$rms_xabfhc;
   odsptr->XabFhc.xab$l_nxt = &odsptr->XabPro;
   odsptr->XabPro = cc$rms_xabpro;

   if (!AstFunction)
   {
      /* synchronous service */
      odsptr->Fab.fab$l_fop &= ~FAB$M_ASY;
      status = sys$create (&odsptr->Fab, 0, 0);
      OdsNamBlockAst (&odsptr->Fab);
      if (VMSok (status)) status = odsptr->Fab.fab$l_sts;
      return (status);
   }
   else
   {
      /* asynchronous service */
      odsptr->Fab.fab$l_fop |= FAB$M_ASY;
      status = sys$create (&odsptr->Fab, &OdsNamBlockAst, &OdsNamBlockAst);
      return (status);
   }
}

/****************************************************************************/
/*
Close the currently open file (with delete if required).  If an AST function is
supplied this is called regardless.
*/ 

int OdsClose
(
ODS_STRUCT *odsptr,
REQUEST_AST AstFunction,
unsigned long AstParam
)
{
   int  status;
   unsigned long  PrvPrv [2];
   FAB_AST  FabAstFunction;

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

   if (WATCH_MODULE(WATCH_MOD_ODS))
      WatchThis (WATCHALL, WATCH_MOD_ODS,
                 "OdsClose() !&B !&A(!&X)",
                 odsptr->DeleteOnClose ||
                   odsptr->Fab.fab$l_fop & FAB$M_DLT,
                 AstFunction, AstParam);

   odsptr->Fab.fab$l_ctx = AstParam;

   if (odsptr->DeleteOnClose ||
       odsptr->Fab.fab$l_fop & FAB$M_DLT)
   {
      odsptr->Fab.fab$l_fop |= FAB$M_DLT;
      /* use SYSPRV to ensure deletion-on-close of file */
      sys$setprv (1, &SysPrvMask, 0, &PrvPrv);
   }
   else
   if (odsptr->Fab.fab$l_fop & FAB$M_TEF)
   {
      /* use SYSPRV to ensure file truncate */
      sys$setprv (1, &SysPrvMask, 0, &PrvPrv);
   }

   if (AstFunction)
      odsptr->Fab.fab$l_fop |= FAB$M_ASY;
   else
      odsptr->Fab.fab$l_fop &= ~FAB$M_ASY;
   status = sys$close (&odsptr->Fab, AstFunction, AstFunction);

   if (odsptr->Fab.fab$l_fop & FAB$M_DLT ||
       odsptr->Fab.fab$l_fop & FAB$M_TEF)
      if (!(PrvPrv[0] & PRV$M_SYSPRV))
         sys$setprv (0, &SysPrvMask, 0, 0);

   return (status);
}

/****************************************************************************/
/*
RMS parse the supplied file specification into an appropriate standard or long
NAM structure.  OdsNamBlockAst() is always called, which sets a number of
pointers and other data from the NAM block so that calling functions do not
need to know which was used.
*/ 

int OdsParse
(
ODS_STRUCT *odsptr,
char *FileSpec,
int FileSpecLength,
char *DefaultSpec,
int DefaultSpecLength,
int NamNop,
GENERAL_AST AstFunction,
unsigned long AstParam
)
{
   int  status;

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

   if (WATCH_MODULE(WATCH_MOD_ODS))
      WatchThis (WATCHALL, WATCH_MOD_ODS,
                 "OdsParse() !&B !&Z !&Z !&A(!&X)",
                 OdsExtended, FileSpec, DefaultSpec, AstFunction, AstParam);

   /* ensure for successive parses any previous resources are released */
   OdsParseRelease (odsptr);

   OdsStructInit (odsptr, true);

   odsptr->AstFunction = AstFunction;
   odsptr->AstParam = AstParam;
   /* a pattern allows for less frequent false releases on uninited structs */
   odsptr->ParseInUse = ODS_PARSE_IN_USE_PATTERN;

   if (!FileSpecLength && FileSpec)
      FileSpecLength = strlen(FileSpec);
   if (!DefaultSpecLength && DefaultSpec)
      DefaultSpecLength = strlen(DefaultSpec);

   odsptr->Fab = cc$rms_fab;
   odsptr->Fab.fab$l_ctx = odsptr;

#ifdef ODS_EXTENDED
   if (OdsExtended)
   {
      odsptr->Fab.fab$l_fna = odsptr->Fab.fab$l_dna = -1;  
      odsptr->Fab.fab$b_fns = odsptr->Fab.fab$b_dns = 0;
      odsptr->Fab.fab$l_nam = &odsptr->Naml;

      odsptr->NamlInUse = true;
      ENAMEL_RMS_NAML(odsptr->Naml)
      odsptr->Naml.naml$l_long_filename = FileSpec;
      odsptr->Naml.naml$l_long_filename_size = FileSpecLength;
      odsptr->Naml.naml$l_long_defname = DefaultSpec;
      odsptr->Naml.naml$l_long_defname_size = DefaultSpecLength;
      odsptr->Naml.naml$l_long_expand = odsptr->ExpFileName;
      odsptr->Naml.naml$l_long_expand_alloc = sizeof(odsptr->ExpFileName)-1;
      odsptr->Naml.naml$l_filesys_name = odsptr->SysFileName;
      odsptr->Naml.naml$l_filesys_name_alloc = sizeof(odsptr->SysFileName)-1;
      odsptr->Naml.naml$b_nop = NamNop;
   }
   else
#endif /* ODS_EXTENDED */
   {
      odsptr->Fab.fab$l_fna = FileSpec;  
      odsptr->Fab.fab$b_fns = FileSpecLength;
      odsptr->Fab.fab$l_dna = DefaultSpec;  
      odsptr->Fab.fab$b_dns = DefaultSpecLength;
      odsptr->Fab.fab$l_nam = &odsptr->Nam;

      odsptr->NamlInUse = false;
      odsptr->Nam = cc$rms_nam;
      odsptr->Nam.nam$l_esa = odsptr->ExpFileName;
      odsptr->Nam.nam$b_ess = ODS2_MAX_FILE_NAME_LENGTH;
      odsptr->Nam.nam$b_nop = NamNop;
   }

   if (!AstFunction)
   {
      /* synchronous service */
      odsptr->Fab.fab$l_fop &= ~FAB$M_ASY;
      status = sys$parse (&odsptr->Fab, 0, 0);
      OdsNamBlockAst (&odsptr->Fab);
      if (VMSok (status)) status = odsptr->Fab.fab$l_sts;
      return (status);
   }
   else
   {
      /* asynchronous service */
      odsptr->Fab.fab$l_fop |= FAB$M_ASY;
      status = sys$parse (&odsptr->Fab, &OdsNamBlockAst, &OdsNamBlockAst);
      return (status);
   }
}

/****************************************************************************/
/*
Do an RMS sys$search().  OdsNamBlockAst() is always called to set up generic
pointers and other data of the various components of interest regardless of
whether a standard or long NAM structure is in use.

*** THIS FUNCTION IS (NO LONGER) SIGNIFICANTLY BROKEN ***

It does not use the 'AstParam' parameter as was intended.  It always delivers
with a pointer to FAB.  It should be fixed one day.  (MGD 05-DEC-2000)

FIXED 25-MAY-2007!!
*/ 

int OdsSearch
(
ODS_STRUCT *odsptr,
GENERAL_AST AstFunction,
unsigned long AstParam
)
{
   int  status;

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

   if (WATCH_MODULE(WATCH_MOD_ODS))
      WatchThis (WATCHALL, WATCH_MOD_ODS,
                 "OdsSearch() !&B !AZ !&A(!&X)",
                 OdsExtended, odsptr->ExpFileName, AstFunction, AstParam);

   odsptr->AstFunction = AstFunction;
   odsptr->AstParam = AstParam;

   odsptr->Fab.fab$l_ctx = odsptr;

#ifdef ODS_EXTENDED
   if (OdsExtended)
   {
      odsptr->Naml.naml$l_long_result = odsptr->ResFileName;
      odsptr->Naml.naml$l_long_result_alloc = sizeof(odsptr->ResFileName)-1;
      odsptr->Naml.naml$l_filesys_name = odsptr->SysFileName;
      odsptr->Naml.naml$l_filesys_name_alloc = sizeof(odsptr->SysFileName)-1;
   }
   else
#endif /* ODS_EXTENDED */
   {
      odsptr->Nam.nam$l_rsa = odsptr->ResFileName;
      odsptr->Nam.nam$b_rss = ODS2_MAX_FILE_NAME_LENGTH;
   }

   if (!AstFunction)
   {
      /* synchronous service */
      odsptr->Fab.fab$l_fop &= ~FAB$M_ASY;
#if ODS_DIRECT
      if (OdsDirect && !odsptr->DirectRetry)
      {
         status = OdsDirectSearch (odsptr);
         if (status == SS$_RETRY)
         {
            /* status to indicate the directory parser declines the call */
            status = sys$search (&odsptr->Fab, 0, 0);
            OdsNamBlockAst (&odsptr->Fab);
            if (VMSok (status)) status = odsptr->Fab.fab$l_sts;
         }
      }
      else
#endif /* ODS_DIRECT */
      {
         status = sys$search (&odsptr->Fab, 0, 0);
         OdsNamBlockAst (&odsptr->Fab);
         if (VMSok (status)) status = odsptr->Fab.fab$l_sts;
      }
      return (status);
   }
   else
   {
      /* asynchronous service */
      odsptr->Fab.fab$l_fop |= FAB$M_ASY;
#if ODS_DIRECT
      if (OdsDirect && !odsptr->DirectRetry)
      {
         status = OdsDirectSearch (odsptr);
         if (status == SS$_RETRY)
            status = sys$search (&odsptr->Fab, &OdsNamBlockAst,
                                               &OdsNamBlockAst);
      }
      else
#endif /* ODS_DIRECT */
         status = sys$search (&odsptr->Fab, &OdsNamBlockAst, &OdsNamBlockAst);
      return (status);
   }
}

/****************************************************************************/
/*
Performs the same functionality as OdsSearch() but using the NOCONCEAL option. 
This results in a concealed device/search list being resolved into actual file
name in the result file name storage, but leave the expanded file untouched.
OdsNamBlockAst() detects the absence of expanded file name storage and adjusts
behaviour for terminating the result file name only.
*/ 

int OdsSearchNoConceal
(
ODS_STRUCT *odsptr,
GENERAL_AST AstFunction,
unsigned long AstParam
)
{
   int  status;

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

   if (WATCH_MODULE(WATCH_MOD_ODS))
      WatchThis (WATCHALL, WATCH_MOD_ODS,
                 "OdsSearchNoConceal() !&B !AZ !&A(!&X)",
                 OdsExtended, odsptr->ExpFileName, AstFunction, AstParam);

   odsptr->AstFunction = AstFunction;
   odsptr->AstParam = AstParam;

   odsptr->Fab.fab$l_ctx = odsptr;

#ifdef ODS_EXTENDED
   if (OdsExtended)
   {
      odsptr->Naml.naml$b_nop |= NAM$M_NOCONCEAL;
      odsptr->Naml.naml$l_long_result = odsptr->ResFileName;
      odsptr->Naml.naml$l_long_result_alloc = sizeof(odsptr->ResFileName)-1;
      odsptr->Naml.naml$l_filesys_name = odsptr->SysFileName;
      odsptr->Naml.naml$l_filesys_name_alloc = sizeof(odsptr->SysFileName)-1;
   }
   else
#endif /* ODS_EXTENDED */
   {
      odsptr->Nam.nam$b_nop |= NAM$M_NOCONCEAL;
      odsptr->Nam.nam$l_rsa = odsptr->ResFileName;
      odsptr->Nam.nam$b_rss = ODS2_MAX_FILE_NAME_LENGTH;
   }

   if (!AstFunction)
   {
      /* synchronous service */
      odsptr->Fab.fab$l_fop &= ~FAB$M_ASY;
#if ODS_DIRECT
      if (OdsDirect && !odsptr->DirectRetry)
      {
         status = OdsDirectSearch (odsptr);
         if (status == SS$_RETRY)
         {
            /* status to indicate the directory parser declines the call */
            status = sys$search (&odsptr->Fab, 0, 0);
            OdsNamBlockAst (&odsptr->Fab);
            if (VMSok (status)) status = odsptr->Fab.fab$l_sts;
         }
      }
      else
#endif /* ODS_DIRECT */
      {
         status = sys$search (&odsptr->Fab, 0, 0);
         OdsNamBlockAst (&odsptr->Fab);
         if (VMSok (status)) status = odsptr->Fab.fab$l_sts;
      }
      return (status);
   }
   else
   {
      /* asynchronous service */
      odsptr->Fab.fab$l_fop |= FAB$M_ASY;
#if ODS_DIRECT
      if (OdsDirect && !odsptr->DirectRetry)
      {
         status = OdsDirectSearch (odsptr);
         if (status == SS$_RETRY)
            status = sys$search (&odsptr->Fab, &OdsNamBlockAst,
                                               &OdsNamBlockAst);
      }
      else
#endif /* ODS_DIRECT */
         status = sys$search (&odsptr->Fab, &OdsNamBlockAst, &OdsNamBlockAst);
      return (status);
   }
}

/****************************************************************************/
/*
Open/Parse/Search is complete.  If successful set the generic pointers to the
various NAM components of interest.  If an AST has been provided declare that
(can be called as an AST or directly).
*/ 

void OdsNamBlockAst (struct FAB *FabPtr)

{
   int  status;
   FAB_AST  AstFunction;
   ODS_STRUCT  *odsptr;

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

   if (WATCH_MODULE(WATCH_MOD_ODS))
      WatchThis (WATCHALL, WATCH_MOD_ODS, "OdsNamBlockAst() !&F !&B !&S",
                 &OdsNamBlockAst, OdsExtended, FabPtr->fab$l_sts);

   odsptr = (ODS_STRUCT*)FabPtr->fab$l_ctx;
   FabPtr->fab$l_ctx = odsptr->AstParam;

   if (VMSok (FabPtr->fab$l_sts) ||
       FabPtr->fab$l_sts == RMS$_FNF ||
       FabPtr->fab$l_sts == RMS$_DNF ||
       FabPtr->fab$l_sts == RMS$_DEV)
   {
      /* file name has been parsed under these status */
#ifdef ODS_EXTENDED
      if (OdsExtended)
      {
         if (odsptr->Naml.naml$l_filesys_name &&
             odsptr->Naml.naml$l_filesys_name != odsptr->SysFileName)
            ErrorNoticed (NULL, FabPtr->fab$l_sts,
                          "naml$l_filesys_name", FI_LI);         

         odsptr->NamNodePtr = odsptr->Naml.naml$l_long_node;
         odsptr->NamNodeLength = odsptr->Naml.naml$l_long_node_size;
         odsptr->NamDevicePtr = odsptr->Naml.naml$l_long_dev;
         odsptr->NamDeviceLength = odsptr->Naml.naml$l_long_dev_size;
         odsptr->NamDirectoryPtr = odsptr->Naml.naml$l_long_dir;
         odsptr->NamDirectoryLength = odsptr->Naml.naml$l_long_dir_size;
         odsptr->NamNamePtr = odsptr->Naml.naml$l_long_name;
         odsptr->NamNameLength = odsptr->Naml.naml$l_long_name_size;
         odsptr->NamTypePtr = odsptr->Naml.naml$l_long_type;
         odsptr->NamTypeLength = odsptr->Naml.naml$l_long_type_size;
         odsptr->NamVersionPtr = odsptr->Naml.naml$l_long_ver;
         odsptr->NamVersionLength = odsptr->Naml.naml$l_long_ver_size;
         odsptr->ExpFileNameLength = odsptr->Naml.naml$l_long_expand_size;
         odsptr->ResFileNameLength = odsptr->Naml.naml$l_long_result_size;
         odsptr->Nam_fnb = odsptr->Naml.naml$l_fnb;

         odsptr->NamFileSysNamePtr = odsptr->SysFileName;
         odsptr->NamFileSysNameLength = odsptr->Naml.naml$l_filesys_name_size;
         odsptr->NamFileSysNamePtr[odsptr->NamFileSysNameLength] = '\0';

         odsptr->ExpFileName[odsptr->ExpFileNameLength] = '\0';
         odsptr->ResFileName[odsptr->ResFileNameLength] = '\0';
      }
      else
#endif /* ODS_EXTENDED */
      {
         odsptr->NamNodePtr = odsptr->Nam.nam$l_node;
         odsptr->NamNodeLength = odsptr->Nam.nam$b_node;
         odsptr->NamDevicePtr = odsptr->Nam.nam$l_dev;
         odsptr->NamDeviceLength = odsptr->Nam.nam$b_dev;
         odsptr->NamDirectoryPtr = odsptr->Nam.nam$l_dir;
         odsptr->NamDirectoryLength = odsptr->Nam.nam$b_dir;
         odsptr->NamNamePtr = odsptr->Nam.nam$l_name;
         odsptr->NamNameLength = odsptr->Nam.nam$b_name;
         odsptr->NamTypePtr = odsptr->Nam.nam$l_type;
         odsptr->NamTypeLength = odsptr->Nam.nam$b_type;
         odsptr->NamVersionPtr = odsptr->Nam.nam$l_ver;
         odsptr->NamVersionLength = odsptr->Nam.nam$b_ver;
         odsptr->ExpFileNameLength = odsptr->Nam.nam$b_esl;
         odsptr->ResFileNameLength = odsptr->Nam.nam$b_rsl;
         odsptr->Nam_fnb = odsptr->Nam.nam$l_fnb;

         odsptr->NamFileSysNamePtr = odsptr->SysFileName;
         odsptr->NamFileSysNameLength = odsptr->Nam.nam$b_name +
                                        odsptr->Nam.nam$b_type +
                                        odsptr->Nam.nam$b_ver;
         memcpy (odsptr->NamFileSysNamePtr,
                 odsptr->Nam.nam$l_name,
                 odsptr->NamFileSysNameLength);
         odsptr->NamFileSysNamePtr[odsptr->NamFileSysNameLength] = '\0';

         odsptr->ExpFileName[odsptr->ExpFileNameLength] = '\0';
         odsptr->ResFileName[odsptr->ResFileNameLength] = '\0';
      }
   }

   if (WATCH_MODULE(WATCH_MOD_ODS))
      WatchThis (WATCHALL, WATCH_MOD_ODS,
                 "!&Z !&Z !&Z", odsptr->ExpFileName,
                 odsptr->ResFileName, odsptr->NamFileSysNamePtr);

   if (!odsptr->AstFunction) return;
   AstFunction = odsptr->AstFunction;
   odsptr->AstFunction = NULL;
   (AstFunction)(odsptr->AstParam);
}

/****************************************************************************/
/*
Ensure parse internal data structures are released.
*/ 

OdsParseRelease (ODS_STRUCT *odsptr)

{
   int  status;
   char  ExpFileName [16];

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

   if (WATCH_MODULE(WATCH_MOD_ODS))
      WatchThis (WATCHALL, WATCH_MOD_ODS, "OdsParseRelease() !&B !8XL",
                 OdsExtended, odsptr->ParseInUse);

   if (odsptr->ParseInUse != ODS_PARSE_IN_USE_PATTERN) return;
   odsptr->ParseInUse = 0;

   /* synchronous service */
   odsptr->Fab.fab$l_fop &= ~FAB$M_ASY;

   odsptr->Fab.fab$l_fna = "a:[b]c.d;";
   odsptr->Fab.fab$b_fns = 9;

#ifdef ODS_EXTENDED
   if (OdsExtended)
   {
      odsptr->Naml.naml$l_long_defname_size = 0;
      odsptr->Naml.naml$b_nop = NAM$M_SYNCHK;
   }
   else
#endif /* ODS_EXTENDED */
   {
      odsptr->Fab.fab$b_dns = 0;
      odsptr->Nam.nam$b_nop = NAM$M_SYNCHK;
   }
   status = sys$parse (&odsptr->Fab, 0, 0);

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

#if ODS_DIRECT
   /* release any directory file (global) memory */
   if (odsptr->DataPtr)
   {
      if (WATCH_CATEGORY(WATCH_INTERNAL))
         WatchThis (WATCHALL, WATCH_INTERNAL, "DIRECTory called:!UL",
                    odsptr->DirectCallCount);

      VmFree (odsptr->DataPtr, FI_LI);
      odsptr->DataPtr = NULL;
      odsptr->DataLength = 0;
   }
#endif /* ODS_DIRECT */
}

/****************************************************************************/
/*
Place a terminating null in the 'ExpFileName' appropriate to whether it is a
directory specific, file specification without version, or with version, and
adjust '...Length's as necessary.  This function can only be used after parse
using OdsParse() or open using OdsOpen().
*/ 

int OdsParseTerminate (ODS_STRUCT *odsptr)

{
   char  *cptr, *sptr;

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

   if (WATCH_MODULE(WATCH_MOD_ODS))
      WatchThis (WATCHALL, WATCH_MOD_ODS,
                 "OdsParseTerminate() !&B !&Z !&Z",
                 OdsExtended, odsptr->ExpFileName, odsptr->NamFileSysNamePtr);

   if (!(odsptr->ExpFileNameLength || odsptr->ResFileNameLength))
      return (SS$_ABORT);

   if (odsptr->NamNamePtr == odsptr->NamTypePtr &&
       odsptr->NamTypeLength <= 1)
   {
      /* no name, no type; a directory specification (with possible version) */
      if (odsptr->Nam_fnb & NAM$M_EXP_VER ||
          odsptr->Nam_fnb & NAM$M_WILD_VER)
      {
         /* with version, remove that annoying single period */
         sptr = (cptr = odsptr->NamNamePtr) + 1;
         while (*sptr) *cptr++ = *sptr++;
         *cptr = '\0';
         odsptr->NamVersionPtr = odsptr->NamNamePtr;
         odsptr->NamTypeLength = 0;

         if (odsptr->NamFileSysNameLength)
         {
            sptr = (cptr = odsptr->NamFileSysNamePtr) + 1;
            while (*sptr) *cptr++ = *sptr++;
            *cptr = '\0';
            odsptr->NamFileSysNameLength--;
         }
      }
      else
      {
         /* just lop it off at the directory terminator */
         odsptr->NamNamePtr[0] = '\0';
         odsptr->NamFileSysNamePtr = "";
         odsptr->NamTypeLength = odsptr->NamVersionLength =
            odsptr->NamFileSysNameLength = 0;
      }
   }
   else
   if (odsptr->NamTypeLength <= 1)
   {
      /* name but no type (with possible version) */
      if (odsptr->Nam_fnb & NAM$M_EXP_VER ||
          odsptr->Nam_fnb & NAM$M_WILD_VER)
      {
         /* remove that annoying single period */
         sptr = (cptr = odsptr->NamTypePtr) + 1;
         while (*sptr) *cptr++ = *sptr++;
         *cptr = '\0';
         odsptr->NamVersionPtr = odsptr->NamTypePtr;
         odsptr->NamTypeLength = 0;

         if (odsptr->NamFileSysNameLength)
         {
            cptr = odsptr->NamFileSysNamePtr + odsptr->NamFileSysNameLength;
            while (cptr > odsptr->NamFileSysNamePtr &&
                   *cptr != '.' && !SAME2(cptr,'^.')) cptr--;
            if (*cptr == '.')
            {
               sptr = cptr + 1;
               while (*sptr) *cptr++ = *sptr++;
               *cptr = '\0';
               odsptr->NamFileSysNameLength = cptr - odsptr->NamFileSysNamePtr;
            }
         }
      }
      else
      {
         if (!(odsptr->Nam_fnb & NAM$M_EXP_TYPE ||
               odsptr->Nam_fnb & NAM$M_WILD_TYPE))
         {
            /* no type and no version supplied */
            (odsptr->NamVersionPtr = odsptr->NamTypePtr)[0] = '\0';
            odsptr->NamTypeLength = odsptr->NamVersionLength = 0;
         }
         else
         {
            /* type is a period only! */
            odsptr->NamVersionPtr[0] = '\0';
            odsptr->NamVersionLength = 0;
         }

         if (odsptr->NamFileSysNameLength)
         {
            cptr = odsptr->NamFileSysNamePtr + odsptr->NamFileSysNameLength;
            while (cptr > odsptr->NamFileSysNamePtr &&
                   *cptr != '.' && !SAME2(cptr-1,'^.')) cptr--;
            if (*cptr == '.')
            {
               *cptr = '\0';
               odsptr->NamFileSysNameLength = cptr - odsptr->NamFileSysNamePtr;
            }
         }
      }
   }
   else
   if (odsptr->Nam_fnb & NAM$M_EXP_VER ||
       odsptr->Nam_fnb & NAM$M_WILD_VER)
   {
      /* a type and a version supplied */
      odsptr->NamVersionPtr[odsptr->NamVersionLength] = '\0';
   }
   else
   {
      /* a type but no version supplied */
      odsptr->NamVersionPtr[0] = '\0';
      odsptr->NamVersionLength = 0;

      if (odsptr->NamFileSysNameLength)
      {
         cptr = odsptr->NamFileSysNamePtr + odsptr->NamFileSysNameLength;
         while (cptr > odsptr->NamFileSysNamePtr &&
                *cptr != ';' && !SAME2(cptr-1,'^;')) cptr--;
         if (*cptr == ';')
         {
            *cptr = '\0';
            odsptr->NamFileSysNameLength = cptr - odsptr->NamFileSysNamePtr;
         }
      }
   }

   if (odsptr->ResFileNameLength)
      odsptr->ResFileNameLength = odsptr->NamVersionPtr -
                                  odsptr->ResFileName;
   else
      odsptr->ExpFileNameLength = odsptr->NamVersionPtr -
                                  odsptr->ExpFileName;

#ifndef ODS_EXTENDED
   /*
      06-JUN-2013  MGD  VAX VMS V7.3 seems to require this (these)
                        adjusted or it generates RMS$_ESL errors (!?)
   */
   if (odsptr->ResFileNameLength)
      odsptr->Nam.nam$b_rsl = odsptr->ResFileNameLength;
   else
      odsptr->Nam.nam$b_esl = odsptr->ExpFileNameLength;
#endif

   if (WATCH_MODULE(WATCH_MOD_ODS))
      WatchThis (WATCHALL, WATCH_MOD_ODS,
                 "!&Z !&Z", odsptr->ExpFileName, odsptr->NamFileSysNamePtr);

   return (SS$_NORMAL);
}

/****************************************************************************/
/*
Using the information in the ODS structure NAM field queue an ACP I/O to
retrieve file information.  Of course, this function can only be used after
parse using OdsParse().  For simplicity retrieves all information of interest
to all users of the function regardless of whether an individual user is
interested in all information!  Any AST routine *MUST* deassign the channel
after use and before continuing.

This function uses the ACP-QIO interface detailed in the "OpenVMS I/O User's 
Reference Manual", and is probably as fast as we can get for this type of file
system functionality!
*/ 

int OdsFileAcpInfo
(
ODS_STRUCT *odsptr,
GENERAL_AST AstFunction,
unsigned long AstParam
)

{
   static $DESCRIPTOR (DeviceDsc, "");

   int  status;
   FILE_QIO  *fqptr;
   ATRDEF  *atptr;

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

   if (WATCH_MODULE(WATCH_MOD_ODS))
      WatchThis (WATCHALL, WATCH_MOD_ODS,
                 "OdsFileAcpInfo() !&B !&Z !&A(!&X)",
                 OdsExtended, odsptr->NamDevicePtr, AstFunction, AstParam);

   if (WasdOdsAccessCheck)
   {
      if (WATCH_MODULE(WATCH_MOD_ODS))
         WatchThis (WATCHALL, WATCH_MOD_ODS,
                    "AuthAccessEnable() SYSPRV-enabled:!&B",
                    AuthAccessEnable(NULL,NULL,0));

      if (!AuthAccessEnable (NULL, NULL, 0))
      {
         status = OdsAccessCheck (odsptr);
         if (VMSnok (status))
         {
            if (AstFunction) SysDclAst (AstFunction, AstParam);
            return (status);
         }
      }
   }

   fqptr = &odsptr->FileQio;

   /* assign a channel to the disk device containing the file */
   DeviceDsc.dsc$a_pointer = odsptr->NamDevicePtr;
   DeviceDsc.dsc$w_length = odsptr->NamDeviceLength;

   status = sys$assign (&DeviceDsc, &fqptr->AcpChannel, 0, 0, 0);
   if (VMSnok (status))
   {
      fqptr->IOsb.Status = status; 
      if (!AstFunction) return (status);
      SysDclAst (AstFunction, AstParam);
      return (status);
   }

   /* set up the File Information Block for the ACP interface */
   fqptr->FibDsc.dsc$w_length = sizeof(fqptr->Fib);
   fqptr->FibDsc.dsc$a_pointer = &fqptr->Fib;

   memset (&fqptr->Fib, 0, sizeof(fqptr->Fib));

   fqptr->FileNameDsc.dsc$a_pointer = odsptr->NamFileSysNamePtr;
   fqptr->FileNameDsc.dsc$w_length = odsptr->NamFileSysNameLength;

#ifdef ODS_EXTENDED
   if (OdsExtended)
   {
      memcpy (&fqptr->Fib.fib$w_did, &odsptr->Naml.naml$w_did, 6);
      fqptr->Fib.fib$b_name_format_in = FIB$C_ISO_LATIN;
      fqptr->Fib.fib$b_name_format_out = FIB$C_ISO_LATIN;
      fqptr->Fib.fib$w_nmctl = FIB$M_NAMES_8BIT;
   }
   else
#endif /* ODS_EXTENDED */
      memcpy (&fqptr->Fib.fib$w_did, &odsptr->Nam.nam$w_did, 6);

   atptr = &fqptr->FileAtr;
   atptr->atr$w_size = sizeof(fqptr->CdtTime64);
   atptr->atr$w_type = ATR$C_CREDATE;
   atptr->atr$l_addr = &fqptr->CdtTime64;
   atptr++;
   atptr->atr$w_size = sizeof(fqptr->RdtTime64);
   atptr->atr$w_type = ATR$C_REVDATE;
   atptr->atr$l_addr = &fqptr->RdtTime64;
   atptr++;
#ifdef ODS_EXTENDED
   if (OdsExtended)
   {
      atptr->atr$w_size = sizeof(fqptr->ModTime64);
      atptr->atr$w_type = ATR$C_MODDATE;
      atptr->atr$l_addr = &fqptr->ModTime64;
      atptr++;
   }
#endif /* ODS_EXTENDED */
   atptr->atr$w_size = sizeof(fqptr->AtrUic);
   atptr->atr$w_type = ATR$C_UIC;
   atptr->atr$l_addr = &fqptr->AtrUic;
   atptr++;
   atptr->atr$w_size = sizeof(fqptr->AtrUchar);
   atptr->atr$w_type = ATR$C_UCHAR;
   atptr->atr$l_addr = &fqptr->AtrUchar;
   atptr++;
   atptr->atr$w_size = sizeof(fqptr->AtrFpro);
   atptr->atr$w_type = ATR$C_FPRO;
   atptr->atr$l_addr = &fqptr->AtrFpro;
   atptr++;
   atptr->atr$w_size = sizeof(fqptr->RecAttr);
   atptr->atr$w_type = ATR$C_RECATTR;
   atptr->atr$l_addr = &fqptr->RecAttr;
   atptr++;
   atptr->atr$w_size = atptr->atr$w_type = atptr->atr$l_addr = 0;

   if (WATCH_MODULE(WATCH_MOD_ODS))
      WatchThis (WATCHALL, WATCH_MOD_ODS,
                 "!#AZ did:!UL,!UL,!UL {!UL}!-!#AZ",
                 DeviceDsc.dsc$w_length, DeviceDsc.dsc$a_pointer,
                 fqptr->Fib.fib$w_did[0],
                 fqptr->Fib.fib$w_did[1],
                 fqptr->Fib.fib$w_did[2],
                 fqptr->FileNameDsc.dsc$w_length,
                 fqptr->FileNameDsc.dsc$a_pointer);

   if (!AstFunction)
   {
      status = sys$qiow (EfnWait, fqptr->AcpChannel, IO$_ACCESS,
                         &fqptr->IOsb, 0, 0, 
                         &fqptr->FibDsc,
                         &fqptr->FileNameDsc, 0, 0,
                         &fqptr->FileAtr, 0);

      sys$dassgn (fqptr->AcpChannel);
      fqptr->AcpChannel = 0;

      if (VMSok (status)) status = fqptr->IOsb.Status;

      if (OdsUseModDate) OdsFileAcpInfoRdt (odsptr);
   }
   else
   if (OdsUseModDate)
   {
      odsptr->AstFunction = AstFunction;
      odsptr->AstParam = AstParam;

      status = sys$qio (EfnNoWait, fqptr->AcpChannel, IO$_ACCESS,
                        &fqptr->IOsb, OdsFileAcpInfoRdt, odsptr, 
                        &fqptr->FibDsc,
                        &fqptr->FileNameDsc, 0, 0,
                        &fqptr->FileAtr, 0);

      if (VMSnok (status))
      {
         fqptr->IOsb.Status = status;
         if (odsptr->AstFunction)
         {
            SysDclAst (odsptr->AstFunction, odsptr->AstParam);
            odsptr->AstFunction = odsptr->AstParam = NULL;
         }
      }
   }
   else
   {
      status = sys$qio (EfnNoWait, fqptr->AcpChannel, IO$_ACCESS,
                        &fqptr->IOsb, AstFunction, AstParam, 
                        &fqptr->FibDsc,
                        &fqptr->FileNameDsc, 0, 0,
                        &fqptr->FileAtr, 0);

      if (VMSnok (status))
      {
         fqptr->IOsb.Status = status;
         if (AstFunction) SysDclAst (AstFunction, AstParam);
      }
   }

   return (status);
}

/****************************************************************************/
/*
Called by OdsFileAcpInfo() directly or as an AST to move the ATR$C_MODDATE onto
the ATR$C_REVDATE.
*/ 

void OdsFileAcpInfoRdt (ODS_STRUCT *odsptr)

{
   FILE_QIO  *fqptr;

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

   fqptr = &odsptr->FileQio;

   if (WATCH_MODULE(WATCH_MOD_ODS))
      WatchThis (WATCHALL, WATCH_MOD_ODS, "OdsFileAcpInfoRdt() mod:!%D rdt:!%D",
                 &fqptr->ModTime64, &fqptr->RdtTime64);

   if (fqptr->ModTime64) fqptr->RdtTime64 = fqptr->ModTime64;

   if (odsptr->AstFunction)
   {
      SysDclAst (odsptr->AstFunction, odsptr->AstParam);
      odsptr->AstFunction = odsptr->AstParam = NULL;
   }
}

/****************************************************************************/
/*
Using the information in the ODS structure NAM field queue an ACP I/O to modify
selected file information.  This function can only be used after parse using
OdsParse().  Any AST *MUST* deassign the channel after use and before
continuing.

This function uses the ACP-QIO interface detailed in the "OpenVMS I/O User's 
Reference Manual", and is probably as fast as we can get for this type of file
system functionality!
*/ 

int OdsFileAcpModify
(
ODS_STRUCT *odsptr,
unsigned short *ProtectionMaskPtr,
unsigned short *VersionLimitPtr,
GENERAL_AST AstFunction,
unsigned long AstParam
)

{
   static $DESCRIPTOR (DeviceDsc, "");

   int  status;
   FILE_QIO  *fqptr;
   ATRDEF  *atptr;

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

   if (WATCH_MODULE(WATCH_MOD_ODS))
      WatchThis (WATCHALL, WATCH_MOD_ODS,
                 "OdsFileAcpModify() !&B !&X:!4XL !&X:!UL !&A(!&X)",
                 OdsExtended, ProtectionMaskPtr,
                 !ProtectionMaskPtr ? 0 : *ProtectionMaskPtr,
                 VersionLimitPtr,
                 !VersionLimitPtr ? 0 : *VersionLimitPtr,
                 AstFunction, AstParam);

   fqptr = &odsptr->FileQio;

   /* assign a channel to the disk device containing the file */
   DeviceDsc.dsc$a_pointer = odsptr->NamDevicePtr;
   DeviceDsc.dsc$w_length = odsptr->NamDeviceLength;

   status = sys$assign (&DeviceDsc, &fqptr->AcpChannel, 0, 0, 0);
   if (VMSnok (status))
   {
      fqptr->IOsb.Status = status; 
      if (!AstFunction) return (status);
      SysDclAst (AstFunction, AstParam);
      return (status);
   }

   /* set up the File Information Block for the ACP interface */
   fqptr->FibDsc.dsc$w_length = sizeof(fqptr->Fib);
   fqptr->FibDsc.dsc$a_pointer = &fqptr->Fib;

   memset (&fqptr->Fib, 0, sizeof(fqptr->Fib));

#ifdef ODS_EXTENDED
   if (OdsExtended)
   {
      fqptr->FileNameDsc.dsc$a_pointer = odsptr->Naml.naml$l_filesys_name;
      fqptr->FileNameDsc.dsc$w_length = odsptr->Naml.naml$l_filesys_name_size;

      memcpy (&fqptr->Fib.fib$w_did, &odsptr->Naml.naml$w_did, 6);

      fqptr->Fib.fib$b_name_format_in = FIB$C_ISO_LATIN;
      fqptr->Fib.fib$b_name_format_out = FIB$C_ISO_LATIN;
      fqptr->Fib.fib$w_nmctl = FIB$M_NAMES_8BIT;
   }
   else
#endif /* ODS_EXTENDED */
   {
      fqptr->FileNameDsc.dsc$a_pointer = odsptr->NamNamePtr;
      fqptr->FileNameDsc.dsc$w_length = odsptr->NamNameLength +
                                        odsptr->NamTypeLength +
                                        odsptr->NamVersionLength;
      memcpy (&fqptr->Fib.fib$w_did, &odsptr->Nam.nam$w_did, 6);
   }

   if (VersionLimitPtr) fqptr->Fib.fib$w_verlimit = *VersionLimitPtr;

   atptr = &fqptr->FileAtr;
   if (ProtectionMaskPtr)
   {
      atptr->atr$w_size = sizeof(*ProtectionMaskPtr);
      atptr->atr$w_type = ATR$C_FPRO;
      atptr->atr$l_addr = ProtectionMaskPtr;
      atptr++;
   }
   atptr->atr$w_size = atptr->atr$w_type = atptr->atr$l_addr = 0;

   if (WATCH_MODULE(WATCH_MOD_ODS))
      WatchThis (WATCHALL, WATCH_MOD_ODS,
                 "!#AZ did:!UL,!UL,!UL {!UL}!-!#AZ",
                 DeviceDsc.dsc$w_length, DeviceDsc.dsc$a_pointer,
                 fqptr->Fib.fib$w_did[0],
                 fqptr->Fib.fib$w_did[1],
                 fqptr->Fib.fib$w_did[2],
                 fqptr->FileNameDsc.dsc$w_length,
                 fqptr->FileNameDsc.dsc$a_pointer);

   if (!AstFunction)
   {
      status = sys$qiow (EfnWait, fqptr->AcpChannel, IO$_MODIFY,
                         &fqptr->IOsb, 0, 0, 
                         &fqptr->FibDsc,
                         &fqptr->FileNameDsc, 0, 0,
                         &fqptr->FileAtr, 0);
      sys$dassgn (fqptr->AcpChannel);
      fqptr->AcpChannel = 0;
      if (VMSok (status)) status = fqptr->IOsb.Status;
   }
   else
      status = sys$qio (EfnNoWait, fqptr->AcpChannel, IO$_MODIFY,
                        &fqptr->IOsb, AstFunction, AstParam, 
                        &fqptr->FibDsc,
                        &fqptr->FileNameDsc, 0, 0,
                        &fqptr->FileAtr, 0);

   if (VMSnok (status))
   {
      fqptr->IOsb.Status = status;
      if (AstFunction) SysDclAst (AstFunction, AstParam);
   }
   return (status);
}

/*****************************************************************************/
/*
Return success status if the specified file name exists in the specified
directory, error otherwise.  This function completes synchronously!
*/ 

int OdsFileExists
(
char *Directory,
char *FileName
)
{
   int  status,
        DirectoryLength,
        FileNameLength;
   char  ExpFileName [ODS_MAX_FILE_NAME_LENGTH+1];
   struct FAB  SearchFab;
   struct NAM  SearchNam;
#ifdef ODS_EXTENDED
   struct NAML  SearchNaml;
#endif

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

   if (WATCH_MODULE(WATCH_MOD_ODS))
      WatchThis (WATCHALL, WATCH_MOD_ODS,
                 "OdsFileExists() !&B !&Z !&Z",
                 OdsExtended, Directory, FileName);

   if (Directory)
      DirectoryLength = strlen(Directory);
   else
      DirectoryLength = 0;
   if (FileName)
      FileNameLength = strlen(FileName);
   else
      FileNameLength = 0;

   SearchFab = cc$rms_fab;
   SearchFab.fab$l_fop = FAB$M_NAM;
   /* synchronous service */
   SearchFab.fab$l_fop &= ~FAB$M_ASY;

#ifdef ODS_EXTENDED
   if (OdsExtended)
   {
      SearchFab.fab$l_fna = SearchFab.fab$l_dna = -1;  
      SearchFab.fab$b_fns = SearchFab.fab$b_dns = 0;
      SearchFab.fab$l_nam = &SearchNaml;

      ENAMEL_RMS_NAML(SearchNaml)
      SearchNaml.naml$b_nop = NAM$M_NOCONCEAL;
      SearchNaml.naml$l_long_defname = Directory;
      SearchNaml.naml$l_long_defname_size = DirectoryLength;
      SearchNaml.naml$l_long_filename = FileName;
      SearchNaml.naml$l_long_filename_size = FileNameLength;
      SearchNaml.naml$l_long_expand = ExpFileName;
      SearchNaml.naml$l_long_expand_alloc = sizeof(ExpFileName)-1;
   }
   else
#endif /* ODS_EXTENDED */
   {
      SearchFab.fab$l_dna = Directory;
      SearchFab.fab$b_dns = DirectoryLength;
      SearchFab.fab$l_fna = FileName;
      SearchFab.fab$b_fns = FileNameLength;
      SearchFab.fab$l_nam = &SearchNam;

      SearchNam = cc$rms_nam;
      SearchNam.nam$b_nop = NAM$M_NOCONCEAL;
      SearchNam.nam$l_esa = ExpFileName;
      SearchNam.nam$b_ess = ODS2_MAX_FILE_NAME_LENGTH;
   }

   status = sys$parse (&SearchFab, 0, 0);
   if (WATCH_MODULE(WATCH_MOD_ODS))
      WatchThis (WATCHALL, WATCH_MOD_ODS,  "sys$parse() !&S", status);
   if (VMSok (status))
   {
      status = sys$search (&SearchFab, 0, 0);
      if (WATCH_MODULE(WATCH_MOD_ODS))
         WatchThis (WATCHALL, WATCH_MOD_ODS, "sys$search() !&S", status);
      if (status == RMS$_DNF) status = RMS$_FNF;
   }

   /* release parse and search internal data structures */
   SearchFab.fab$l_fna = "a:[b]c.d;";
   SearchFab.fab$b_fns = 9;
   SearchFab.fab$b_dns = 0;
#ifdef ODS_EXTENDED
   if (OdsExtended)
   {
      SearchNaml.naml$l_long_result = 0;
      SearchNaml.naml$b_nop = NAM$M_SYNCHK;
   }
   else
#endif /* ODS_EXTENDED */
   {
      SearchNam.nam$l_rlf = 0;
      SearchNam.nam$b_nop = NAM$M_SYNCHK;
   }
   sys$parse (&SearchFab, 0, 0);

   return (status);
}

/*****************************************************************************/
/*
Given a file or directory specification in 'FileName' generate the file name
of the directory file.

(e.g. "DEVICE:[DIR1]DIR2.DIR" from "DEVICE:[DIR1.DIR2]FILE.EXT",
      "DEVICE:[DIR1]DIR2.DIR" from "DEVICE:[DIR1.DIR2]",
 and  "DEVICE:[000000]DIR1.DIR" from "DEVICE:[DIR1]", etc.)

Attempts to improve the perfomance of this function by storing the previous
file specification processed and the previous directory file name result.  If
this file specification directory part is the same as the previous directory
part then just return the previous result!
*/ 
 
int OdsNameOfDirectoryFile
(
char *FileName,
int FileNameLength,
char *DirFileNamePtr,
int *DirFileLengthPtr
)
{
   static int  DirFileNameBufferLength = 0;
   static char  FileNameBuffer [ODS_MAX_FILE_NAME_LENGTH+1],
                DirFileNameBuffer [ODS_MAX_FILE_NAME_LENGTH+1];

   int  status;
   unsigned long  Nam_fnb;
   unsigned long  PrvPrv [2];
   char  *cptr, *fptr, *sptr, *zptr;
   char  ExpFileName [ODS_MAX_FILE_NAME_LENGTH+1];
   struct FAB  ParseFab;
   struct NAM  ParseNam;
#ifdef ODS_EXTENDED
   struct NAML  ParseNaml;
#endif

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

   if (WATCH_MODULE(WATCH_MOD_ODS))
      WatchThis (WATCHALL, WATCH_MOD_ODS,
                 "OdsNameOfDirectoryFile() !&B !&Z !&Z !&Z",
                 OdsExtended, FileName, FileNameBuffer, DirFileNameBuffer);

   if (!FileNameLength) FileNameLength = strlen(FileName);

   /* check if this has the same directory components as the last one */
   sptr = FileNameBuffer;
   cptr = FileName;
   while (*cptr == *sptr &&
          (*sptr != ']' || SAME2(sptr-1,'^]')) &&
          (*cptr != ']' || SAME2(cptr-1,'^]')))
   {
      *sptr++;
      *cptr++;
   }
   if (*cptr == ']' && *sptr == ']' && *(sptr-1) != '.' && *(cptr-1) != '.')
   {
      /* it does! return the result of the last one */
      strzcpy (DirFileNamePtr, DirFileNameBuffer, 255);
      *DirFileLengthPtr = DirFileNameBufferLength;
      return (SS$_NORMAL);
   }

   ParseFab = cc$rms_fab;

#ifdef ODS_EXTENDED
   if (OdsExtended)
   {
      ParseFab.fab$l_fna = -1;
      ParseFab.fab$b_fns = 0;
      ParseFab.fab$l_nam = &ParseNaml;

      ENAMEL_RMS_NAML(ParseNaml)
      ParseNaml.naml$l_long_filename = FileName;
      ParseNaml.naml$l_long_filename_size = FileNameLength;
      ParseNaml.naml$l_long_expand = ExpFileName;
      ParseNaml.naml$l_long_expand_alloc = sizeof(ExpFileName)-1;
      ParseNaml.naml$b_nop = NAM$M_NOCONCEAL | NAM$M_SYNCHK;

      /* use SYSPRV to ensure access to directory */
      sys$setprv (1, &SysPrvMask, 0, &PrvPrv);
      status = sys$parse (&ParseFab, 0, 0);
      if (!(PrvPrv[0] & PRV$M_SYSPRV)) sys$setprv (0, &SysPrvMask, 0, 0);
      if (VMSnok (status))
      {
         DirFileNamePtr[*DirFileLengthPtr = 0] = '\0';
         return (status);
      }

      ParseNaml.naml$l_long_ver[ParseNaml.naml$l_long_ver_size] = '\0';
      fptr = ParseNaml.naml$l_long_name - 1;
   }
   else
#endif /* ODS_EXTENDED */
   {
      ParseFab.fab$l_fna = FileName;
      ParseFab.fab$b_fns = FileNameLength;
      ParseFab.fab$l_nam = &ParseNam;

      ParseNam = cc$rms_nam;
      ParseNam.nam$l_esa = ExpFileName;
      ParseNam.nam$b_ess = ODS2_MAX_FILE_NAME_LENGTH;
      ParseNam.nam$b_nop = NAM$M_NOCONCEAL | NAM$M_SYNCHK;

      /* use SYSPRV to ensure access to directory */
      sys$setprv (1, &SysPrvMask, 0, &PrvPrv);
      status = sys$parse (&ParseFab, 0, 0);
      if (!(PrvPrv[0] & PRV$M_SYSPRV)) sys$setprv (0, &SysPrvMask, 0, 0);
      if (VMSnok (status))
      {
         DirFileNamePtr[*DirFileLengthPtr = 0] = '\0';
         return (status);
      }

      ParseNam.nam$l_ver[ParseNam.nam$b_ver] = '\0';
      fptr = ParseNam.nam$l_name - 1;
   }

#ifdef ODS_EXTENDED
   if (OdsExtended)
      Nam_fnb = ParseNaml.naml$l_fnb;
   else
#endif /* ODS_EXTENDED */
      Nam_fnb = ParseNam.nam$l_fnb;

   if (Nam_fnb & NAM$M_SEARCH_LIST)
   {
      /* search list, look for the actual file/directory */
      status = sys$search (&ParseFab, 0, 0);
      if (VMSok (status)) status = ParseFab.fab$l_sts;
      /* if the directory  doesn't actually exist then that's OK too! */
      if (status == RMS$_FNF) status = SS$_NORMAL;
      if (VMSnok (status))
      {
         /* release parse and search internal data structures */
         ParseFab.fab$l_fna = "a:[b]c.d;";
         ParseFab.fab$b_fns = 9;
         ParseFab.fab$b_dns = 0;
#ifdef ODS_EXTENDED
         if (OdsExtended)
         {
            ParseNaml.naml$l_long_result = 0;
            ParseNaml.naml$b_nop = NAM$M_SYNCHK;
         }
         else
#endif /* ODS_EXTENDED */
         {
            ParseNam.nam$l_rlf = 0;
            ParseNam.nam$b_nop = NAM$M_SYNCHK;
         }
         sys$parse (&ParseFab, 0, 0);
         return (status);
      }
   }

   if (WATCH_MODULE(WATCH_MOD_ODS))
      WatchThis (WATCHALL, WATCH_MOD_ODS, "!&Z", ExpFileName);

   while (fptr > ExpFileName)
   {
      if (*fptr == '[' && !SAME2(fptr-1,'^[')) break;
      if (*fptr == '.' && !SAME2(fptr-1,'^.')) break;
      fptr--;
   }

   if (fptr > ExpFileName && fptr[-1] == ']')
   {
      /* concealed, logical device */
      fptr -= 2;
      if (MATCH10 (fptr, ".][000000]"))
      {
         fptr--;
         while (fptr > ExpFileName && *fptr != '[' &&
                *fptr != '.' && fptr[-1] != '^') fptr--;
      }
   }

   zptr = (sptr = DirFileNameBuffer) + sizeof(DirFileNameBuffer);
   if (MATCH8 (fptr, "[000000]") || MATCH0 (fptr, "[000000.]", 9))
   {
#ifdef ODS_EXTENDED
      if (OdsExtended)
      {
         for (cptr = ParseNaml.naml$l_long_dev;
              cptr < fptr && sptr < zptr;
              *sptr++ = *cptr++);
      }
      else
#endif /* ODS_EXTENDED */
      {
         for (cptr = ParseNam.nam$l_dev;
              cptr < fptr && sptr < zptr;
              *sptr++ = *cptr++);
      }
      for (cptr = "[000000]000000.DIR";
           *cptr && sptr < zptr;
           *sptr++ = *cptr++);
      if (sptr >= zptr) sptr--;
      *sptr = '\0';
   }
   else
   if (*fptr == '[')
   {
#ifdef ODS_EXTENDED
      if (OdsExtended)
      {
         for (cptr = ParseNaml.naml$l_long_dev;
              cptr < fptr && sptr < zptr;
              *sptr++ = *cptr++);
      }
      else
#endif /* ODS_EXTENDED */
      {
         for (cptr = ParseNam.nam$l_dev;
              cptr < fptr && sptr < zptr;
              *sptr++ = *cptr++);
      }
      for (cptr = "[000000]"; *cptr && sptr < zptr; *sptr++ = *cptr++);
      if (fptr[0] == '.' && fptr[1] == ']')
         fptr += 3;
      else
         fptr++;
      while (*fptr && *fptr != '.' && *fptr != ']' && sptr < zptr)
      {
         if (*fptr == '^') *sptr++ = *fptr++;
         if (sptr < zptr) *sptr++ = *fptr++;
      }
      for (cptr = ".DIR"; *cptr && sptr < zptr; *sptr++ = *cptr++);
      if (sptr >= zptr) sptr--;
      *sptr = '\0';
   }
   else
   {
#ifdef ODS_EXTENDED
      if (OdsExtended)
      {
         for (cptr = ParseNaml.naml$l_long_dev;
              cptr < fptr && sptr < zptr;
              *sptr++ = *cptr++);
      }
      else
#endif /* ODS_EXTENDED */
      {
         for (cptr = ParseNam.nam$l_dev;
              cptr < fptr && sptr < zptr;
              *sptr++ = *cptr++);
      }
      if (sptr < zptr) *sptr++ = ']';
      if (fptr[0] == '.' && fptr[1] == ']')
         fptr += 3;
      else
         fptr++;
      while (*fptr && *fptr != ']' && *fptr != '.' && sptr < zptr)
      {
         if (*fptr == '^') *sptr++ = *fptr++;
         if (sptr < zptr) *sptr++ = *fptr++;
      }
      for (cptr = ".DIR"; *cptr && sptr < zptr; *sptr++ = *cptr++);
      if (sptr >= zptr) sptr--;
      *sptr = '\0';
   }

   /* buffer this (soon to be the previous) file name */
   strzcpy (FileNameBuffer, FileName, 255);

   /* copy out the generated directory file name */
   strzcpy (DirFileNamePtr, DirFileNameBuffer, 255);
   *DirFileLengthPtr = DirFileNameBufferLength = sptr - DirFileNameBuffer;

   if (WATCH_MODULE(WATCH_MOD_ODS))
      WatchThis (WATCHALL, WATCH_MOD_ODS,
                 "{!UL}!-!#AZ", *DirFileLengthPtr, DirFileNamePtr);

   /* release parse and search internal data structures */
   ParseFab.fab$l_fna = "a:[b]c.d;";
   ParseFab.fab$b_fns = 9;
   ParseFab.fab$b_dns = 0;
#ifdef ODS_EXTENDED
   if (OdsExtended)
   {
      ParseNaml.naml$l_long_result = 0;
      ParseNaml.naml$b_nop = NAM$M_SYNCHK;
   }
   else
#endif /* ODS_EXTENDED */
   {
      ParseNam.nam$l_rlf = 0;
      ParseNam.nam$b_nop = NAM$M_SYNCHK;
   }
   sys$parse (&ParseFab, 0, 0);

   if (WATCH_MODULE(WATCH_MOD_ODS))
      WatchThis (WATCHALL, WATCH_MOD_ODS,
                 "!AZ !AZ", FileNameBuffer, DirFileNameBuffer);

   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
Using sys$check_access() check file read access permission for the supplied
file specification against the server security profile.  This is intended
primarily for sites that have
 
  $ SHOW AUDIT
  8< snip 8<
    FILE access:
      Failure:     read,write,execute,delete,control

enabled and receive LOTS of alarms as the server image traverses directories.
Enabled using the logical name WASD_ODS_ACCESS_CHECK.
*/

int OdsAccessCheck (ODS_STRUCT *odsptr)

{
   static unsigned long  ArmAccess = ARM$M_READ;
   static unsigned long  ArmControlAccess = ARM$M_CONTROL;
   static $DESCRIPTOR (ClassNameDsc, "FILE");
   static $DESCRIPTOR (FileNameDsc, "");
   static $DESCRIPTOR (UserProfileDsc, "");
   static VMS_ITEM_LIST3  ArmAccessItem [] = 
   {
      { sizeof(ArmAccess), CHP$_ACCESS, &ArmAccess, 0 },
      { 0, 0, 0, 0 }
   };
   static VMS_ITEM_LIST3 ControlAccessItem [] =
   {
      { sizeof(ArmControlAccess), CHP$_ACCESS, &ArmControlAccess, 0 },
      { 0, 0, 0, 0 }
   };

   int  status;

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

   if (WATCH_MODULE(WATCH_MOD_ODS))
      WatchThis (WATCHALL, WATCH_MOD_ODS, "OdsAccessCheck()");

   FileNameDsc.dsc$a_pointer = odsptr->NamDevicePtr;
   FileNameDsc.dsc$w_length = odsptr->NamDeviceLength +
                              odsptr->NamDirectoryLength +
                              odsptr->NamNameLength +
                              odsptr->NamTypeLength +
                              odsptr->NamVersionLength;

   /* against the HTTPd server account */
   if (!HttpdUserProfileLength) AuthVmsCreateHttpdProfile ();
   UserProfileDsc.dsc$a_pointer = HttpdUserProfilePtr;
   UserProfileDsc.dsc$w_length = HttpdUserProfileLength;

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

   status = sys$check_access (0, &FileNameDsc, 0, &ArmAccessItem,
                              0, &ClassNameDsc, 0, &UserProfileDsc);

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

   if (WATCH_MODULE(WATCH_MOD_ODS))
      WatchThis (WATCHALL, WATCH_MOD_ODS,
                 "sys$check_access() !AZ !&S",
                 FileNameDsc.dsc$a_pointer, status);

   if (VMSnok (status))
   {
      /* will be shoved into a word-sized status */
      if (status == RMS$_FNF || status == RMS$_DNF)
         status = SS$_NOSUCHFILE;
      else
      if (status & 0xffff0000)
         status = SS$_FISH;
      odsptr->FileQio.IOsb.Status = status;
   }

   return (status);
}

/*****************************************************************************/
/*
ODS (FILES-11) directory parser.  Two orders of magnitude faster than $SEARCH
and NO repeated elevation to EXEC mode.  At the first call takes an in-memory
snapshot of the directory file content and retains that, parsing the content,
for the duration of the search.  The memory is released by OdsParseRelease().
Successive calls parse the next directory name from the directory file and
return as would OdsSearch().

Returns SS$_RETRY to indicate the parser declines to process the initial call 
and the caller should return to using $SEARCH (reasons obvious in code). 
Otherwise returns a VMS success or non-success status code.

Always allow for the extended specification escape character when copying file
specification elements.  This is not a syntax check function and will present
no issue with a non-extended file specification.

THE FOLLOWING ARE NOT MY WORDS:

A files-11 directory file holds a sequence of records.  There is one record
for each filename in the directory.  Each record consists of a HEADER,
followed by one or more SUBRECORDs - one SUBRECORD for each version of that
file.  The HEADER is laid out as follows:

  word:size             size of this record, excluding itself
  word:verlimit         version limit for this file
  byte:flags            flags : 0 = DIR$C_FID = usual case
  byte:namecount        # of characters in next field
  byte:name[namecount]  filename
  byte:pad              null byte, if reqd, to pad to next word boundary

The SUBRECORDs are laid out as follows:

  word:version       version number
  six-byte:fid       fid of this version

The SUBRECORDs are ordered by decreasing version number.

Records are dense within each 512-byte block of the directory file (ie, 
there are no 'gaps' between records).  However, not all blocks of the 
directory file need be full.  The 'end-of-data' for a block is marked by a
HEADER whose size field is 0xFFFF.

Finally, a file may well have so many versions that there is not enough
room within a 512-byte block to hold the entire record for that filename.
Say the first block had enough room for versions 1000 down to 950 for 
filename "A.A".  The Files-11 XQP inserts a 0xFFFF 'end-of-block', at
that point, then starts afresh in the next block with a HEADER, for the 
same filename, "A.A", but with SUBRECORDs continuing for version 944 and
downwards.  [put another way, records don't span block boundaries]

Very finally, a HEADER record must have at least one following VERSION
subrecord in that block.  Backup code assumes this to hold true.  If
it doesn't hold true, then typical symptoms are failures in trying to
access FOO.BAR;-1 when there's only one version of that file.

Here's a tiny example, of a directory which holds just 3 files, called
A.A;5, A.A;2 and MINE.DAT;6 ...

  record
    HEADER
  word:size             0018
  word:verlimit         7fff
  byte:flags            00 = DIR$C_FID = regular case
      byte:namecount        03
      byte:name[namecount]  412e41 = 'A.A'
      byte:pad              00
    SUBRECORD-1
      word:version      0005
      six-byte:fid      1524-0049-0000 = (5412,73,0)
    SUBRECORD-2
      word:version      0002
      six-byte:fid      16bc-003f-0000 = (5820,63,0)
  record
    HEADER
      word:size         0014
      word:verlimit     7fff
      byte:flags        00 = DIR$C_FID = regular case
      byte:namecount        08
      byte:name[namecount]  4d494e452e444154 = 'MINE.DAT'
  byte:pad                  none required
    SUBRECORD-1
      word:version      0006
      six-byte:fid      190f-0057-0000 = (6415,87,0)
  record
    HEADER
      word:size         ffff => end-of-block
*/ 

#if ODS_DIRECT

int OdsDirectSearch (ODS_STRUCT *odsptr)

{
   static char  VersionString [16];
   static $DESCRIPTOR (VersionStringDsc, VersionString);
   static $DESCRIPTOR (VersionFaoDsc, ";!UL\0");

   BOOL  WildMatch;
   uint  status, flags, length, nlen, rlen, vlim, vnum;
   char  ch;
   char  *cptr, *czptr, *dzptr, *fptr, *rptr, *rzptr, *sptr, *zptr;
   uchar  *nptr;
   uchar  fid [6];
   char DeviceName [64+1];

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

   if (WATCH_MODULE(WATCH_MOD_ODS))
      WatchThis (WATCHALL, WATCH_MOD_ODS, "OdsDirectSearch() !&X", odsptr);

   /* this bogus status indicates the parser declines the call */
   if (!OdsDirect)
   {
      odsptr->DirectRetry = true;
      return (SS$_RETRY);
   }

   InstanceGblSecIncrLong (&AccountingPtr->OdsDirectCallCount);
   odsptr->DirectCallCount++;

   /* this function only handles single, flat directories */
   if (((odsptr->Nam_fnb & NAM$M_SEARCH_LIST) != 0) ||
        ((odsptr->Nam_fnb & NAM$M_WILD_DIR) != 0))
   {
      InstanceGblSecIncrLong (&AccountingPtr->OdsDirectRetryCount);
      /* only do this the once when the initial call declined */
      odsptr->DirectRetry = true;
      return (SS$_RETRY);
   }

   if (!odsptr->DataPtr)
   {
      /**************/
      /* initialise */
      /**************/

      if (OdsNoDirect)
      {
         zptr = (sptr = DeviceName) + sizeof(DeviceName)-1;
         czptr = (cptr = odsptr->ExpFileName) + odsptr->ExpFileNameLength;
         while (cptr < czptr && sptr < zptr && *cptr != ':') *sptr++ = *cptr++;
         if (*cptr && sptr < zptr) *sptr++ = *cptr++;
         *sptr = '\0';

         if (WATCH_MODULE(WATCH_MOD_ODS))
            WatchThis (WATCHALL, WATCH_MOD_ODS, "!&Z !&Z",
                       DeviceName, OdsNoDirect);

         /* list of comma-separated device names */
         cptr = OdsNoDirect;
         while (*cptr)
         {
            sptr = cptr;
            while (*cptr && *cptr != ',') cptr++;
            ch = *cptr;
            *cptr = '\0';
            if (!strcasecmp (DeviceName, sptr))
            {
               *cptr = ch;
               sptr = NULL;
               break;
            }
            if (*cptr = ch) cptr++;
         }

         if (!sptr)
         {
            /* hit one of the device names in the logical name value */
            InstanceGblSecIncrLong (&AccountingPtr->OdsDirectRetryCount);
            odsptr->DirectRetry = true;
            return (SS$_RETRY);
         }
      }

      InstanceGblSecIncrLong (&AccountingPtr->OdsDirectLoadCount);

      /* pre-fill result file name up to and including directory */
      zptr = (sptr = odsptr->ResFileName) + sizeof(odsptr->ResFileName)-1;
      czptr = (cptr = odsptr->ExpFileName) + odsptr->ExpFileNameLength;
      while (cptr < czptr && sptr < zptr && *cptr != ']')
      {
         if (MATCH10(cptr,".][000000]"))
         {
            cptr += 9;
            continue;
         }
         if (SAME2(cptr,'.]'))
         {
            *sptr++ = *cptr++;
            cptr += 2;
            continue;
         }
         if (*cptr == '^') *sptr++ = *cptr++;
         if (cptr < czptr && sptr < zptr) *sptr++ = *cptr++; 
      }
      if (cptr < czptr && sptr < zptr) *sptr++ = *cptr++;
      *sptr = '\0';

      if (cptr < czptr && sptr < zptr)
      {
         /* fill various pointers and lengths */
         length = sptr - odsptr->ResFileName;
         czptr = (cptr = odsptr->ResFileName) + length;
         odsptr->NamNodePtr = cptr;
         odsptr->NamNodeLength = 0;
         odsptr->NamDevicePtr = cptr;
         while (cptr < czptr && *cptr != ':') cptr++;
         while (cptr < czptr) cptr++;
         odsptr->NamDeviceLength = cptr - odsptr->NamDevicePtr;
         odsptr->NamDirectoryPtr = cptr;
         while (cptr < czptr && *cptr != ']')
         {
            if (*cptr == '^') cptr++;
            if (cptr < czptr) cptr++;
         }
         while (cptr < czptr) cptr++;
         odsptr->NamDirectoryLength = cptr - odsptr->NamDirectoryPtr;
         /* the parsed file name will be appended to this pointer */
         odsptr->NamNamePtr = cptr;

         /* reproduce the pre-fill in the expanded file name */
         zptr = (sptr = odsptr->ExpFileName) + sizeof(odsptr->ExpFileName)-1;
         czptr = (cptr = odsptr->ResFileName) + (int)odsptr->NamNamePtr;
         for (cptr = odsptr->ResFileName;
              cptr < czptr && sptr < zptr;
              *sptr++ = *cptr++);
         *sptr = '\0';

         /* searches for highest version and *.DIR may be optimised */
         odsptr->DirectDotDir =
            odsptr->DirectVersion0 =
            odsptr->DirectVersionHit0 = false;
         odsptr->DirectWildcard[0] = '\0';

         zptr = (sptr = odsptr->DirectWildcard) +
                sizeof(odsptr->DirectWildcard)-1;
         for (cptr = odsptr->NamFileSysNamePtr;
              *cptr && sptr < zptr;
              *sptr++ = *cptr++);
         *sptr = '\0';

         if (!odsptr->DirectWildcard[0])
            odsptr->DirectVersion0 = true;
         else
         if (MATCH7 (odsptr->DirectWildcard, "*.DIR;"))
         {
            /* optimise WASD's "Index of" DirBeginDirectories() */
            odsptr->DirectDotDir = odsptr->DirectVersion0 = true;
            odsptr->DirectWildcard[0] = '\0';
         }
         else
         if (MATCH5 (odsptr->DirectWildcard, "*.*;") ||
             MATCH6 (odsptr->DirectWildcard, "*.*;0"))
         {
            odsptr->DirectVersion0 = true;
            odsptr->DirectWildcard[0] = '\0';
         }
         else
         if (MATCH6 (odsptr->DirectWildcard, "*.*;*"))
            odsptr->DirectWildcard[0] = '\0';
         else
         if (*(USHORTPTR)(sptr-2) == ';0')
            odsptr->DirectVersion0 = true;
         else
         if (*(sptr-1) == ';')
         {
            odsptr->DirectVersion0 = true;
            strzcat (odsptr->DirectWildcard, "*",
                     sizeof(odsptr->DirectWildcard));
         }
         else
         if (!odsptr->NamVersionLength)
         {
            odsptr->DirectVersion0 = true;
            strzcat (odsptr->DirectWildcard, ";*",
                     sizeof(odsptr->DirectWildcard));
         }

         if (WATCH_MODULE(WATCH_MOD_ODS))
            WatchThis (WATCHALL, WATCH_MOD_ODS,
                       "dotdir:!&B version0:!&B dotdir:!&B wildcard:!&Z",
                       odsptr->DirectDotDir, odsptr->DirectVersion0,
                       odsptr->DirectDotDir, odsptr->DirectWildcard);

         /* read the directory file into (global) memory */
         status = OdsLoadDirect (odsptr, odsptr->NamDevicePtr);

         if (VMSnok (status))
         {
            if (status != SS$_NOSUCHFILE)
               ErrorNoticed (NULL, status, odsptr->NamDevicePtr, FI_LI);
            return (SS$_RETRY);
         }

         odsptr->DirectTheLength =
            odsptr->DirectTheRecord =
            odsptr->DirectSubRecord = 0;

         if (WATCH_CATEGORY(WATCH_INTERNAL))
            WatchThis (WATCHALL, WATCH_INTERNAL,
"DIRECTory !UL blocks !UL bytes version0:!&B dotdir:!&B !&Z !&Z",
                       (odsptr->DataLength / 512) + 1, odsptr->DataLength,
                       odsptr->DirectVersion0, odsptr->DirectDotDir,
                       odsptr->NamDevicePtr, odsptr->DirectWildcard);
      }
      else
         status = SS$_RESULTOVF;
   }
   else
      status = SS$_NORMAL;

   /* loop(s) just to allow convenient /break;/s from the processing */
   for (;;)
   {
      /*******************/
      /* parse directory */
      /*******************/

      /* if an error from the initialisation */
      if (VMSnok (status)) break;

      if (odsptr->DirectSubRecord)
         rptr = odsptr->DataPtr + odsptr->DirectSubRecord;
      else
         rptr = odsptr->DataPtr + odsptr->DirectTheRecord;
      rzptr = odsptr->DataPtr + odsptr->DirectTheRecord +
                                odsptr->DirectTheLength;
      dzptr = odsptr->DataPtr + odsptr->DataLength;

      if (WATCH_MODULE(WATCH_MOD_ODS))
         WatchThis (WATCHALL, WATCH_MOD_ODS,
                    "!UL !UL  !UL !UL !UL  !UL !UL !UL",
                    odsptr->DataPtr, odsptr->DataLength,
                    odsptr->DirectTheRecord, odsptr->DirectTheLength,
                    odsptr->DirectSubRecord, rptr, rzptr, dzptr);

      for (;;)
      {      
         /* if just parsing subrecords (versions) */
         if (odsptr->DirectSubRecord) break;
 
         for (;;)
         {
            rlen = (uint)*(USHORTPTR)rptr;
            rptr += sizeof(ushort);

            if (WATCH_MODULE(WATCH_MOD_ODS))
            {
               if (rlen == (uint)0xffff)
                  WatchThis (WATCHALL, WATCH_MOD_ODS, "0xFFFF end of record");
               else
               {
                  WatchThis (WATCHALL, WATCH_MOD_ODS,
                             "RECORD block:!UL byte:!UL rlen:!UL",
                             ((rptr - odsptr->DataPtr) / 512) + 1,
                             ((rptr - odsptr->DataPtr) % 512),
                             rlen);
                  WatchDataDump (rptr, rlen);
               }
            }

            /* if not end of records (in this block) sentinal */
            if (rlen && rlen != (uint)0xffff && rlen < 508)
            {
               /* records are always on even byte (word) boundaries */
               rzptr = rptr + rlen;
               if ((int)rzptr & 1) rzptr++;

               /* break from loop to process record */
               break;
            }
            else
            if (rlen && rlen != (uint)0xffff && rlen > 508)
            {
               status = SS$_BUGCHECK;
               ErrorNoticed (NULL, status,
                             "block:!UL byte:!UL length:!UL !&Z",
                             FI_LI,
                             ((rptr - odsptr->DataPtr) / 512) + 1,
                             ((rptr - odsptr->DataPtr) % 512),
                             rlen, odsptr->NamDevicePtr); 
               InstanceGblSecIncrLong (&AccountingPtr->OdsDirectBugcheckCount);
               break;
            }

            if (rlen == (uint)0xffff && dzptr - rptr < 512)
            {
               /* end of records in end file block */
               odsptr->DirectTheLength =
                  odsptr->DirectTheRecord =
                  odsptr->DirectSubRecord = 0;
               if (odsptr->DirectWildcard[0] &&
                   !odsptr->DirectVersion0)
                  status = RMS$_NMF;
               else
                  status = RMS$_FNF;
               if (WATCH_MODULE(WATCH_MOD_ODS))
               {
                  WatchThis (WATCHALL, WATCH_MOD_ODS, "end of directory");
                  WatchDataDump (rptr, dzptr - rptr);
               }
               break;
            }

            /* step the record pointer to the next block */
            rptr -= odsptr->DataPtr; 
            /* if not already on the block boundary add one to get to next */
            if ((ulong)rptr % 512)
               rptr = (((ulong)rptr / 512) + 1) * 512;
            else
               rptr = ((ulong)rptr / 512) * 512;
            rptr += (ulong)odsptr->DataPtr; 

            if (WATCH_MODULE(WATCH_MOD_ODS))
            {
               WatchThis (WATCHALL, WATCH_MOD_ODS,
                          "DISK block:!UL byte:!UL",
                          ((rptr - odsptr->DataPtr) / 512) + 1,
                          ((rptr - odsptr->DataPtr) % 512));
               WatchDataDump (rptr, 512);
            }

            /* continue to parse if not at end of content */
            if (rptr < dzptr) continue;

            status = SS$_BUGCHECK;
            ErrorNoticed (NULL, status, NULL, FI_LI);
            break;
         }

         if (VMSnok (status)) break;

         /****************/
         /* got a record */
         /****************/

         if (WATCH_MODULE(WATCH_MOD_ODS))
            WatchThis (WATCHALL, WATCH_MOD_ODS, "rlen:!UL", rlen);

         vlim = *(USHORTPTR)rptr;
         rptr += sizeof(ushort);
         flags = *(uchar*)rptr++;
         flags &= 0x07;
         nlen = *(uchar*)rptr++;
         nptr = rptr;
         rptr += nlen;
         if ((int)rptr & 1) rptr++;

         if (WATCH_MODULE(WATCH_MOD_ODS))
            WatchThis (WATCHALL, WATCH_MOD_ODS, "flags:!2XL !UL !#AZ",
                       flags, nlen, nlen, nptr);

         if (flags)
         {
            rptr = rzptr;
            continue;
         }

/**
Andrew C. Goldstein
VAX/VMS Software Devlopment
11-Jan-1985

https://web-docs.gsi.de/~kraemer/COLLECTION/VMS/ods2.txt

4.2.3  DIR$B_FLAGS - 1 Byte   Flags

          This byte contains the type code of the directory entry  and
          assorted flag bits.  It contains the following subfields and
          status bits:

          DIR$V_TYPE       The type code is contained in the three low
                           bits  of  the flags byte.  It is one of the
                           following values:

          DIR$C_FID        The  value  field  is  a  list  of  version
                           numbers and 48 bit File Id's.

          DIR$C_LINKNAME   The value field is a symbolic  link  string
                           (i.e.,  an  alphabetic file specification).
                           [Symbolic links are not  yet  supported  by
                           any implementation of Files-11.]

                           [No status bits are defined at present.]

DIR$C_FID = 0;                  !  normal file ID
DIR$C_LINKNAME = 1;             !  symbolic name
!  Filename encoding codes.
DIR$C_ODS2 = 0;                 !  ODS-2 legal ASCII
DIR$C_ISL1 = 1;                 !  ODS-2 illegal ASCII or ISO LATIN-1
DIR$$_TYPE_RESERVED = 2;
DIR$C_UCS2 = 3;                 !  Unicode USC-2 (16 bit characters)
**/

         /* note the current record */
         odsptr->DirectTheRecord = rptr - odsptr->DataPtr;
         odsptr->DirectTheLength = rzptr - rptr;
         odsptr->DirectSubRecord = 0;

         /* buffer the on-disk file name */
         zptr = (sptr = odsptr->SysFileName) + sizeof(odsptr->SysFileName)-1;
         while (nlen-- && sptr < zptr) *sptr++ = *nptr++;
         *sptr = '\0';
         odsptr->NamFileSysNamePtr = odsptr->SysFileName;
         odsptr->NamFileSysNameLength = sptr - odsptr->SysFileName;
         /* note where the version begins */
         odsptr->DirectSysFileNameLength = odsptr->NamFileSysNameLength;
         if (sptr >= zptr)
         {
            status = SS$_RESULTOVF;
            ErrorNoticed (NULL, status, odsptr->SysFileName, FI_LI);
            InstanceGblSecIncrLong (&AccountingPtr->OdsDirectBugcheckCount);
         }
         break;
      }

      if (VMSnok (status)) break;

      if (odsptr->DirectVersion0 &&
          !odsptr->DirectSubRecord)
      {
         /****************************/
         /* only most-recent version */
         /****************************/

         /* note current file name in case spans multiple blocks */
         if (odsptr->NamFileSysNameLength != odsptr->DirectCurrentLength ||
             !strsame (odsptr->NamFileSysNamePtr, odsptr->DirectCurrent, -1))
         {
            zptr = (sptr = odsptr->DirectCurrent) +
                    sizeof(odsptr->DirectCurrent)-1;
            nlen = odsptr->NamFileSysNameLength;
            nptr = odsptr->NamFileSysNamePtr;
            while (nlen-- && sptr < zptr) *sptr++ = *nptr++;
            *sptr = '\0';
            if (sptr >= zptr)
            {
               status = SS$_RESULTOVF;
               ErrorNoticed (NULL, status, odsptr->DirectCurrent, FI_LI);
               InstanceGblSecIncrLong (&AccountingPtr->OdsDirectBugcheckCount);
            }
            odsptr->DirectCurrentLength = sptr - odsptr->DirectCurrent;
            odsptr->DirectVersionHit0 = false;
         }
         else
         if (odsptr->DirectVersionHit0)
         {
            /* same name and already hit version zero so skip this record */
            odsptr->DirectTheRecord = rzptr - odsptr->DataPtr;
            odsptr->DirectTheLength = odsptr->DirectSubRecord = 0;
            continue;
         }
         else
            odsptr->DirectVersionHit0 = true;
      }

      if (VMSok (status))
      {
         /*******************/
         /* version and FID */
         /*******************/

         vnum = *(USHORTPTR)rptr;
         rptr += sizeof(ushort);

         memcpy (fid, rptr, sizeof(fid));
         rptr += sizeof(fid);

         if (rptr < rzptr)
         {
            /* next sub-record (version) */
            odsptr->DirectSubRecord = rptr - odsptr->DataPtr;
         }
         else
         {
            /* subrecords exhausted, next record please Mr Music */
            odsptr->DirectTheLength = odsptr->DirectSubRecord = 0;
            odsptr->DirectTheRecord = rptr - odsptr->DataPtr;
         }
      }

      if (VMSok (status))
      {
         /**************************/
         /* version 0 already hit? */
         /**************************/

         if (odsptr->DirectVersionHit0) continue;
      }

      if (VMSok (status))
      {
         /***********************/
         /* report this version */
         /***********************/

#ifdef ODS_EXTENDED
         if (OdsExtended)
            memcpy (&odsptr->Naml.naml$w_fid, &fid, sizeof(fid));
         else
#endif /* ODS_EXTENDED */
            memcpy (&odsptr->Nam.nam$w_fid, &fid, sizeof(fid));

         sys$fao (&VersionFaoDsc, 0, &VersionStringDsc, vnum);

         /* append the version number */
         zptr = (sptr = odsptr->SysFileName) + sizeof(odsptr->SysFileName)-1;
         sptr += odsptr->DirectSysFileNameLength;
         for (cptr = VersionString; *cptr && sptr < zptr; *sptr++ = *cptr++);
         *sptr = '\0';
         odsptr->NamFileSysNameLength = sptr - odsptr->SysFileName;
         if (sptr >= zptr)
         {
            status = SS$_RESULTOVF;
            ErrorNoticed (NULL, status, odsptr->SysFileName, FI_LI);
            InstanceGblSecIncrLong (&AccountingPtr->OdsDirectBugcheckCount);
         }
      }

      if (VMSnok (status)) break;

      if (odsptr->DirectDotDir)
      {
         /* small optimisation when looking only for directories */
         if (*(sptr-1) != '1' ||
             *(sptr-2) != ';' ||
             to_upper(*(sptr-3)) != 'R' ||
             to_upper(*(sptr-4)) != 'I' ||
             to_upper(*(sptr-5)) != 'D' ||
             *(sptr-6) != '.')
            continue;
      }
      else
      if (odsptr->DirectWildcard[0])
      {
         /******************/
         /* match wildcard */
         /******************/

         WildMatch = StringMatchAndRegex (NULL,
                                          odsptr->SysFileName,
                                          odsptr->DirectWildcard,
                                          SMATCH_GREEDY, NULL, NULL);
         if (WATCH_MODULE(WATCH_MOD_ODS))
             WatchThis (WATCHALL, WATCH_MOD_ODS, "WILDCARD !&Z !&Z !AZ",
                        odsptr->SysFileName,
                        odsptr->DirectWildcard,
                        WildMatch ? "YES" : "NO");
         if (!WildMatch) continue;
      }

      break;
   }

   if (VMSok (status))
   {
      /************************/
      /* complete result name */
      /************************/

      /* append the pre-filled elements with the name.type;version */
      zptr = odsptr->ResFileName + sizeof(odsptr->ResFileName)-1;
      sptr = odsptr->NamNamePtr;
      odsptr->NamVersionPtr = odsptr->NamTypePtr = NULL;
      /* escape non-ODS-2 characters (after all, if not there not ODS-5!) */
      nptr = odsptr->NamFileSysNamePtr;
      for (nlen = odsptr->NamFileSysNameLength; nlen && sptr < zptr; nlen--)
      {
         if ((*nptr >= 'A' && *nptr <= 'Z') ||
             (*nptr >= 'a' && *nptr <= 'z') ||
             (*nptr >= '0' && *nptr <= '9') ||
             (*nptr == '$' || *nptr == '-' || *nptr == '_') ||
             (*nptr >= 0xa0 && *nptr <= 0xff))
            *(uchar*)sptr++ = *nptr++;
         else
         {
            if (*nptr == '.')
            {
               /* look ahead for a following period */
               czptr = nptr + nlen;
               for (cptr = nptr+1; cptr < czptr && *cptr != '.'; cptr++);
               if (cptr < czptr)
               {
                  /* found a following period so escape this one */
                  *sptr++ = '^';
               }
               else
               {
                  /* no following period so end of name and begining of type */
                  odsptr->NamNameLength = sptr - odsptr->NamNamePtr;
                  odsptr->NamTypePtr = sptr;
               }
            }
            else
            /* semicolon permitted in names so only after a type period */
            if (*nptr == ';' && odsptr->NamTypePtr)
            {
               /* version delimiting semicolon so end of type */
               if (!odsptr->NamTypePtr) odsptr->NamTypePtr = sptr;
               odsptr->NamTypeLength = sptr - odsptr->NamTypePtr;
               odsptr->NamVersionPtr = sptr;
            }
            else
               *sptr++ = '^';
            if (sptr < zptr) *sptr++ = *nptr++;
         }
      }
      *sptr = '\0';
      if (!odsptr->NamTypePtr)
      {
         odsptr->NamTypePtr = sptr;
         odsptr->NamTypeLength = sptr - odsptr->NamTypePtr;
      }
      if (!odsptr->NamVersionPtr) odsptr->NamVersionPtr = sptr;
      odsptr->NamVersionLength = sptr - odsptr->NamVersionPtr;
      odsptr->ResFileNameLength = sptr - odsptr->ResFileName;

      /* also append the resultant file name to the pre-filled expanded name */
      zptr = (sptr = odsptr->ExpFileName) + sizeof(odsptr->ExpFileName)-1;
      sptr += (odsptr->NamNamePtr - odsptr->ResFileName);
      czptr = odsptr->ResFileName + odsptr->ResFileNameLength;
      for (cptr = odsptr->NamNamePtr;
           cptr < czptr && sptr < zptr;
           *sptr++ = *cptr++);
      *sptr = '\0';
      odsptr->ExpFileNameLength = sptr - odsptr->ExpFileName;

      if (WATCH_MODULE(WATCH_MOD_ODS))
         WatchThis (WATCHALL, WATCH_MOD_ODS,
                    "fid:(!UL,!UL,!UL) (!UL)!AZ (!UL)!AZ (!UL)!#AZ",
                    (fid[0] + (fid[1] << 8) + (fid[5] << 16)),
                    (fid[2] + (fid[3] << 8)),
                    (fid[4]),
                    odsptr->ExpFileNameLength, odsptr->ExpFileName,
                    odsptr->ResFileNameLength, odsptr->ResFileName,
                    odsptr->NamFileSysNameLength,
                    odsptr->NamFileSysNameLength,
                    odsptr->NamFileSysNamePtr);

      if (odsptr->DirectVersion0) odsptr->DirectVersionHit0 = true;
   }

   if (VMSnok (status))
   {
      if (WATCH_MODULE(WATCH_MOD_ODS))
         WatchThis (WATCHALL, WATCH_MOD_ODS, "!&S", status);

      /* release any directory file (global) memory */
      if (odsptr->DataPtr)
      {
         if (WATCH_CATEGORY(WATCH_INTERNAL))
            WatchThis (WATCHALL, WATCH_INTERNAL, "DIRECTory called:!UL",
                       odsptr->DirectCallCount);

         VmFree (odsptr->DataPtr, FI_LI);
         odsptr->DataPtr = NULL;
         odsptr->DataLength = 0;
      }
   }

   /*******************/
   /* deliver any AST */
   /*******************/

   odsptr->Fab.fab$l_sts = status;

   if (odsptr->AstFunction)
   {
      SysDclAst (odsptr->AstFunction, odsptr->AstParam);
      odsptr->AstFunction = odsptr->AstParam = NULL;
   }

   return (status);
}

#endif  /* ODS_DIRECT */

/*****************************************************************************/
/*
Called by AdminReportServerStats().
*/ 

#if ODS_DIRECT

char* OdsDirectReport (REQUEST_STRUCT* rqptr)

{
   static char buf [128];

   int  average;

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

   if (WATCH_MODULE(WATCH_MOD_ODS))
      WatchThis (WATCHALL, WATCH_MOD_ODS, "OdsDirectReport()");

   if (AccountingPtr->OdsDirectLoadCount)
      average = (AccountingPtr->OdsDirectCallCount -
                 AccountingPtr->OdsDirectRetryCount) /
                 AccountingPtr->OdsDirectLoadCount;
   else
      average = 0;

   FaoToBuffer (buf, sizeof(buf), NULL,
"<tr><th>ODS directory parser:</th>\
<td colspan=\"2\">!&L &divide; !&L &equals; !&L &nbsp;(!&L, !&L)</td></tr>\n",
                AccountingPtr->OdsDirectCallCount,
                AccountingPtr->OdsDirectLoadCount, average,
                AccountingPtr->OdsDirectRetryCount,
                AccountingPtr->OdsDirectBugcheckCount);

   return (buf);
}

#endif /* ODS_DIRECT */

/*****************************************************************************/
/*
Load the specified directory name into dynamically allocated memory.  'DataPtr'
and 'DataLength' are set to reflect the file contents.  'DataPtr' needs to be
freed when finished with.
*/ 

#if ODS_DIRECT

int OdsLoadDirect
(
ODS_STRUCT *odsptr,
char *DirectName
)
{
   int  status,
        DirFileNameLength;
   char  DirFileName [ODS_MAX_FILE_NAME_LENGTH+1];
   ODS_STRUCT  LoadOds;

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

   if (WATCH_MODULE(WATCH_MOD_ODS))
      WatchThis (WATCHALL, WATCH_MOD_ODS,
                 "OdsLoadDirect() !&Z", DirectName);

   /* release any directory file (global) memory */
   if (odsptr->DataPtr)
   {
      VmFree (odsptr->DataPtr, FI_LI);
      odsptr->DataPtr = NULL;
      odsptr->DataLength = 0;
   }

   status = OdsNameOfDirectoryFile (DirectName, strlen(DirectName),
                                    DirFileName, &DirFileNameLength);
   if (VMSnok (status)) return (status);

   OdsStructInit (&LoadOds, true);

   /* ensure we can read this directory */
   sys$setprv (1, &SysPrvMask, 0, 0);

   /* OdsParse() NAM$M_NOCONCEAL before OdsSearchNoConceal() */
   OdsParse (&LoadOds,
             NULL, 0, DirFileName, DirFileNameLength, NAM$M_NOCONCEAL,
             NULL, NULL);

   if (VMSok (status = OdsReallyADir (NULL, &LoadOds)))
   {
      if (VMSok (status = OdsLoadContent (&LoadOds, DirFileName)))
      {
         /* purloin the loaded directory content */
         odsptr->DataPtr = LoadOds.DataPtr;
         odsptr->DataLength = LoadOds.DataLength;
         LoadOds.DataPtr = NULL;
         LoadOds.DataLength = 0;
         OdsParseRelease (&LoadOds);

         if (0 && WATCH_MODULE(WATCH_MOD_ODS))
            WatchDataDump (odsptr->DataPtr, odsptr->DataLength);
      }
   }

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

   return (status);
}

#endif /* ODS_DIRECT */

/*****************************************************************************/
/*
Load the specified file name raw content into dynamically allocated memory. 
'DataPtr' and  'DataLength' are set to reflect the file contents.  'DataPtr'
needs to be freed when finished with.
*/ 

int OdsLoadContent
(
ODS_STRUCT *odsptr,
char *FileName
)
{
   int  status,
        SizeInBytes;

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

   if (WATCH_MODULE(WATCH_MOD_ODS))
      WatchThis (WATCHALL, WATCH_MOD_ODS,
                 "OdsLoadContent() !AZ", FileName);

   status = OdsOpenReadOnly (odsptr, FileName, true);
   if (VMSnok (status)) return (status);

   if (odsptr->XabFhc.xab$l_ebk <= 1)
      SizeInBytes = odsptr->XabFhc.xab$w_ffb;
   else
      SizeInBytes = ((odsptr->XabFhc.xab$l_ebk-1) << 9) +
                    odsptr->XabFhc.xab$w_ffb;
   /* round-up to ensure we have a full complement of blocks allocated */
   odsptr->DataPtr = (char*)VmGet (((SizeInBytes/512)+1)*512);
   odsptr->DataLength = 0;

   if (WATCH_MODULE(WATCH_MOD_ODS))
      WatchThis (WATCHALL, WATCH_MOD_ODS, "ebk:!UL ffb:!UL SizeInBytes:!UL",
                 odsptr->XabFhc.xab$l_ebk, odsptr->XabFhc.xab$w_ffb,
                 SizeInBytes);

   odsptr->Rab.rab$l_ubf = odsptr->DataPtr;
   for (;;)
   {
      if (SizeInBytes > 512*127)
         odsptr->Rab.rab$w_usz = 512*127;
      else
      if (SizeInBytes > 0)
         odsptr->Rab.rab$w_usz = SizeInBytes;
      else
         break;

      status = sys$read (&odsptr->Rab, 0, 0);
      if (VMSnok (status)) break;

      odsptr->DataLength += odsptr->Rab.rab$w_rsz;
      odsptr->Rab.rab$l_ubf += odsptr->Rab.rab$w_rsz;
      SizeInBytes -= odsptr->Rab.rab$w_rsz;
   }

   sys$close (&odsptr->Fab, 0, 0); 

   if (status == RMS$_EOF) status = SS$_NORMAL;
   if (VMSnok (status))
   {
      VmFree (odsptr->DataPtr, FI_LI);
      odsptr->DataPtr = NULL;
      odsptr->DataLength = 0;
   }

   return (status);
}

/*****************************************************************************/
/*
Load the specified file name into dynamically allocated memory.  'DataPtr' and
'DataLength' are set to reflect the file contents.  Records are delimited by
newline characters and the file is terminated with a null character.  Other
file related data remains set in the ODS structure.  A VMS status code is
returned.  This is a fully synchronous function mainly designed for loading
configuration files, etc.  'DataPtr' needs to be freed when finished with.
*/ 

int OdsLoadTextFile
(
ODS_STRUCT *odsptr,
char *FileName
)
{
   int  status,
        SizeInBytes;
   char  *cptr, *sptr;

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

   if (WATCH_MODULE(WATCH_MOD_ODS))
      WatchThis (WATCHALL, WATCH_MOD_ODS,
                 "OdsLoadTextFile() !AZ", FileName);

   status = OdsOpenReadOnly (odsptr, FileName, false);
   if (VMSnok (status)) return (status);

   if (odsptr->XabFhc.xab$l_ebk <= 1)
      SizeInBytes = odsptr->XabFhc.xab$w_ffb;
   else
      SizeInBytes = ((odsptr->XabFhc.xab$l_ebk-1) << 9) +
                    odsptr->XabFhc.xab$w_ffb;
   odsptr->DataPtr = odsptr->DataParsePtr = cptr = (char*)VmGet(SizeInBytes+1);
   odsptr->DataLength = odsptr->DataLineLength = 0;

   for (;;)
   {
      odsptr->Rab.rab$l_ubf = cptr;
      if (SizeInBytes > 32767)
         odsptr->Rab.rab$w_usz = 32767;
      else
         odsptr->Rab.rab$w_usz = SizeInBytes;

      status = sys$get (&odsptr->Rab, 0, 0);
      if (VMSnok (status)) break;

      cptr[odsptr->Rab.rab$w_rsz] = '\n';
      sptr = cptr;
      cptr += odsptr->Rab.rab$w_rsz + 1;
      while (sptr < cptr)
      {
         if (!*sptr) *sptr = ' ';
         sptr++;
      }
      odsptr->DataLength += odsptr->Rab.rab$w_rsz + 1;
      SizeInBytes -= odsptr->Rab.rab$w_rsz + 1;
   }

   sys$close (&odsptr->Fab, 0, 0); 

   if (status == RMS$_EOF) status = SS$_NORMAL;
   if (VMSok (status))
      *cptr = '\0';
   else
   {
      VmFree (odsptr->DataPtr, FI_LI);
      odsptr->DataPtr = odsptr->DataLinePtr = odsptr->DataParsePtr = NULL;
      odsptr->DataLength = odsptr->DataLineLength = 0;
   }

   return (status);
}

/*****************************************************************************/
/*
Free any memory allocated to contain the loaded text file.
Reset associated pointers and storage to empty.
*/ 

void OdsFreeTextFile (ODS_STRUCT *odsptr)

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

   if (WATCH_MODULE(WATCH_MOD_ODS))
      WatchThis (WATCHALL, WATCH_MOD_ODS, "OdsFreeTextFile()");

   if (odsptr->DataPtr)
   {
      VmFree (odsptr->DataPtr, FI_LI);
      odsptr->DataPtr = odsptr->DataLinePtr = odsptr->DataParsePtr = NULL;
      odsptr->DataLength = odsptr->DataLineLength = 0;
   }
}

/*****************************************************************************/
/*
Parses each newline-delimited 'line' from a text file pointed to by 'DataPtr'. 
Stores each of these lines between calls pointed to by 'DataLinePtr' and
'DataLineLength'.  Current position in text file pointed to by 'DataParsePtr'
Designed for configuration files where a trailing backslash is used to continue
a line.  'DataLinePtr' needs to be freed if the file content is not read to
completion.  'BackslashMeans' can be '\\' meaning the backslash/newline are
absorbed, can be '\n' meaning a single newline is substituted, or anything else
the backslash is just part of the line.
*/ 

char* OdsParseTextFile
(
ODS_STRUCT *odsptr,
char BackslashMeans
)

{
   int  DataLineSize;
   char  *cptr, *sptr;

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

   if (WATCH_MODULE(WATCH_MOD_ODS))
      WatchThis (WATCHALL, WATCH_MOD_ODS,
                 "OdsParseTextFile() !UL", BackslashMeans);

   if (!odsptr->DataParsePtr) return (NULL);
   if (!*(cptr = sptr = odsptr->DataParsePtr))
   {
      if (odsptr->DataLinePtr)
      {
         VmFree (odsptr->DataLinePtr, FI_LI);
         odsptr->DataLinePtr = NULL;
         odsptr->DataLineLength = odsptr->DataLineSize = 0;
      }
      return (NULL);
   }
   while (*sptr && *sptr != '\n')
   {
      sptr = odsptr->DataParsePtr;
      while (*sptr && *sptr != '\n' && !SAME2(sptr,'\\\n')) sptr++;
      if (*sptr == '\\') sptr += 2;
      odsptr->DataParsePtr = sptr;
   }
   if (sptr - cptr + 1 > odsptr->DataLineSize)
   {
      if (sptr - cptr + 1 < 512)
         DataLineSize = 512;
      else
         DataLineSize = sptr - cptr + 1;
      if (DataLineSize > odsptr->DataLineSize)
      {
         if (odsptr->DataLinePtr) VmFree (odsptr->DataLinePtr, FI_LI);
         odsptr->DataLinePtr = VmGet (odsptr->DataLineSize = DataLineSize);
      }
   }
   if (*sptr) sptr++;
   odsptr->DataParsePtr = sptr;
   sptr = odsptr->DataLinePtr;
   while (*cptr && *cptr != '\n')
   {
      while (*cptr && *cptr != '\n' && !SAME2(cptr,'\\\n'))
         *sptr++ = *cptr++;
      if (*cptr == '\\')
      {
         odsptr->DataLineNumber++;
         switch (BackslashMeans)
         {
            case '\\' : cptr += 2; break;
            case '\n' : *sptr++ = '\n'; cptr += 2; break;
            default : *sptr++ = *cptr++;
         }
      }
   }
   *sptr = '\0';
   odsptr->DataLineLength = sptr - odsptr->DataLinePtr;
   odsptr->DataLineNumber++;

   return (odsptr->DataLinePtr);
}

/*****************************************************************************/
/*
Synchronous check on the file characteristics directory bit to confirm a file
ending in '.DIR' is really a directory.  The file must have been parsed or
searched-for.  Returns SS$_NORMAL if it is, SS$_ABORT if it is not, or another
error status for whatever reason.
*/

int OdsReallyADir
(
REQUEST_STRUCT *rqptr,
ODS_STRUCT *odsptr
)
{
#define FCH$M_DIRECTORY 0x2000

   int  status;
   unsigned long PrvPrv [2];

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

   if (WATCH_MODULE(WATCH_MOD_ODS))
      WatchThis (WATCHALL, WATCH_MOD_ODS, "OdsReallyADir()");

   /* use SYSPRV to ensure access to perform this check */
   sys$setprv (1, &SysPrvMask, 0, &PrvPrv);
   status = OdsFileAcpInfo (odsptr, NULL, 0);
   if (!(PrvPrv[0] & PRV$M_SYSPRV)) sys$setprv (0, &SysPrvMask, 0, 0);

   if (WATCH_MODULE(WATCH_MOD_ODS))
       WatchThis (WATCHALL, WATCH_MOD_ODS, "!&S !&B",
                  status, odsptr->FileQio.AtrUchar & FCH$M_DIRECTORY);

   if (VMSnok (status)) return (status);
   if (odsptr->FileQio.AtrUchar & FCH$M_DIRECTORY) return (SS$_NORMAL);
   return (SS$_ABORT);
}

/*****************************************************************************/
/*
Copies ODS structure 2 to structure 1 adjusting the various pointers.
*/

OdsCopyStructure
(
ODS_STRUCT *odsptr1,
ODS_STRUCT *odsptr2
)
{
   char  *cptr;

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

   if (WATCH_MODULE(WATCH_MOD_ODS))
      WatchThis (WATCHALL, WATCH_MOD_ODS,
                 "OdsCopyStructure() !&B", OdsExtended);

   /* copy the entire on-disk structure from file to cache */
   memcpy (odsptr1, odsptr2, sizeof(ODS_STRUCT));

   /* now adjust the pointers in this new structure */
   odsptr1->NamNodePtr = cptr = odsptr1->ExpFileName;
   odsptr1->NamDevicePtr = (cptr += odsptr1->NamNodeLength);
   odsptr1->NamDirectoryPtr = (cptr += odsptr1->NamDeviceLength);
   odsptr1->NamNamePtr = (cptr += odsptr1->NamDirectoryLength);
   odsptr1->NamTypePtr = (cptr += odsptr1->NamNameLength);
   odsptr1->NamVersionPtr = (cptr += odsptr1->NamTypeLength);
   odsptr1->NamFileSysNamePtr = odsptr1->SysFileName;
}

/****************************************************************************/
/*
Get the volume's underlying file system (ODS-2 or ODS-5), by sys$getdvi() the
ACPTYPE for the device.  Return the appropriate WASD value for detected file
system or zero to indicate some other condition.  Return 0 if it cannot be
determined for some reason, or 2 or 5 representing each of the underlying
file-system structures.
*/ 

/* if pre-7.2 Alpha then define this */
#ifndef DVI$C_ACP_F11V5
#define DVI$C_ACP_F11V5 11
#endif

int OdsVolumeStructure (char *DeviceName)

{
   static unsigned long  DevAcpType,
                         DevChar;
   static $DESCRIPTOR (DevNameDsc, "");
   static struct {
      short  buf_len;
      short  item;
      char   *buf_addr;
      short  *ret_len;
   }
   AcpTypeItemList [] = 
   {
      { sizeof(DevAcpType), DVI$_ACPTYPE, &DevAcpType, 0 },
      { sizeof(DevChar), DVI$_DEVCHAR, &DevChar, 0 },
      { 0, 0, 0, 0 }
   };

   int  status;
   char  *cptr;
   struct {
      unsigned short  Status;
      unsigned short  Count;
      unsigned long  Unused;
   } IOsb;

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

   if (WATCH_MODULE(WATCH_MOD_ODS))
      WatchThis (WATCHALL, WATCH_MOD_ODS,
                 "OdsVolumeStructure() !&Z", DeviceName);

   for (cptr = DeviceName; *cptr && *cptr != ':'; cptr++);
   /* if we can't find a device in it then assume it's ODS-2 */
   if (!*cptr) return (0);

   DevNameDsc.dsc$a_pointer = DeviceName;
   DevNameDsc.dsc$w_length = cptr - DeviceName;

   DevChar = DevAcpType = 0;

   status = sys$getdviw (EfnWait, 0, &DevNameDsc, &AcpTypeItemList,
                         &IOsb, 0, 0, 0);
   if (WATCH_MODULE(WATCH_MOD_ODS))
       WatchDataFormatted ("sys$getdviw() !#AZ !&S !&S !UL\n",
                           DevNameDsc.dsc$w_length, DevNameDsc.dsc$a_pointer,
                           status, IOsb.Status, DevAcpType);

   if (VMSok (status)) status = IOsb.Status;
   /* if error */
   if (VMSnok(status)) return (0); 
   /* if volume not mounted */
   if (!(DevChar & DEV$M_MNT)) return (0);
   if (DevAcpType == DVI$C_ACP_F11V2) return (2);
   if (DevAcpType == DVI$C_ACP_F11V5) return (5);
   return (0);
}

/*****************************************************************************/
/*
Performance of OdsDirectSearch() compared to OdsSearch().
Compile with module WATCHing.
$ HTTPD /TESTS ODS=SEARCH=<filespec>  !use OdsSearch() i.e. $SEARCH
$ HTTPD /TESTS ODS=DIRECT=<filespec>  !use OdsSearchNext()
Adding an ACP file access provides a bit more of a realistic load.
$ HTTPD /TESTS ODS=SEARCH=ACP=<filespec>
$ HTTPD /TESTS ODS=DIRECT=ACP=<filespec>
*/

#if ODS_DIRECT

void OdsDirectPerfOut
(
struct dsc$descriptor_s *dscptr,
int fcount
)
{
   fprintf (stdout, "%d files\t%*.*s\n",
            fcount,
            dscptr->dsc$w_length, dscptr->dsc$w_length, dscptr->dsc$a_pointer);
}

void OdsDirectPerf (char *param)

{
   BOOL  DoAcp, DoShow;
   int  dlen, fcount, flen, status;
   char  *dptr, *fptr;
   ODS_STRUCT  SearchOds;

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

   if (WATCH_MODULE(WATCH_MOD_ODS))
      WatchThis (WATCHALL, WATCH_MOD_ODS, "OdsDirectPerf() !AZ", param);

   if (strsame (param, "DIRECT=", 7))
   {
      OdsDirect = true;
      param += 7;
   }
   else
   if (strsame (param, "SEARCH=", 7))
      param += 7;
   else
      exit (SS$_ABORT);

   if (strsame (param, "ACP=", 4))
   {
      DoAcp = true;
      param += 4;
   }
   else
      DoAcp = false;

   if (*param == '+')
   {
      DoShow = true;
      param++;
   }
   else
      DoShow = false;

   dlen = flen = 0;
   for (dptr = fptr = param; *fptr; fptr++);
   while (fptr > dptr && *fptr != ']') fptr--;
   if (fptr > dptr) fptr++;
   flen = strlen(fptr);
   dlen = fptr - dptr;

   status = OdsParse (&SearchOds, fptr, flen, dptr, dlen,
                      NAM$M_NOCONCEAL, 0, 0);
   if (VMSnok (status)) exit (status);
   fcount = 0;
   lib$init_timer();
   while (VMSok (status = OdsSearchNoConceal (&SearchOds, 0, 0)))
   {
      if (DoShow)
         fprintf (stdout, "%s\n", SearchOds.ResFileName);
      if (DoAcp)
         if (VMSnok (status = OdsFileAcpInfo (&SearchOds, NULL, 0)))
            break;
      if (!(++fcount % 1000))
         lib$show_timer(0, 0, OdsDirectPerfOut, fcount);
   }
   lib$show_timer(0, 0, OdsDirectPerfOut, fcount);

   exit (status);
}

#endif /* ODS_DIRECT */

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

