/*****************************************************************************/
/*
                                   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 ;^)


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
---------------
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_V6
#undef _VMS_V6_SOURCE
#define _VMS_V6_SOURCE
#undef __VMS_VER
#define __VMS_VER 60000000
#undef __CRTL_VER
#define __CRTL_VER 60000000
#endif

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

/* VMS related header files */
#include <descrip.h>
#include <devdef.h>
#include <dvidef.h>
#include <iodef.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  OdsExtended;

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

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

extern int  EfnWait,
            EfnNoWait,
            OpcomMessages;

extern unsigned long  SysPrvMask[];

extern char  Utility[];

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 Alpha
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 ()

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

   if (WATCH_MODULE(WATCH_MOD_ODS))
      WatchThis (NULL, FI_LI, WATCH_MOD_ODS, "OdsSetExtended()");

#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)
   {
      WriteFaoStdout ("%!AZ-W-ENAMEL, NAML problem, \
extended file specifications not supported\n",
                      Utility);
      if (OpcomMessages & OPCOM_HTTPD)
         WriteFaoOpcom ("%!AZ-W-ENAMEL, NAML problem, \
extended file specifications not supported\n",
                         Utility);
      OdsExtended = false;
      return;
   }
#endif /* ENAMEL_NAML */

#endif /* ODS_EXTENDED */

#ifndef __VAX
   if (SysInfo.VersionInteger >= 720)
      OdsExtended = true;
   else
#endif
      OdsExtended = false;

   WriteFaoStdout ("%!AZ-I-ODS5, !AZsupported by !AZ VMS !AZ\n",
                   Utility, OdsExtended ? "" : "not ",
#ifdef __ALPHA
                   "Alpha",
#endif
#ifdef __ia64
                   "IA64",
#endif
#ifdef __VAX
                   "VAX",
#endif
                   SysInfo.Version);
}

/****************************************************************************/
/*
Generic open for read.  Intended for configuration files, etc.  Completely
synchronous with a RAB connected for sequential read.
*/ 

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

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

   if (WATCH_MODULE(WATCH_MOD_ODS))
      WatchThis (NULL, FI_LI, WATCH_MOD_ODS,
                 "OdsOpenReadOnly() !&B !&Z", OdsExtended, FileSpec);

   memset (odsptr, 0, sizeof(ODS_STRUCT));

   odsptr->Fab = cc$rms_fab;
   odsptr->Fab.fab$b_fac = FAB$M_GET;
   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 (Debug) fprintf (stdout, "sys$open() %%X%08.08X\n", status);
   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;

   status = sys$connect (&odsptr->Rab, 0, 0);
   if (Debug) fprintf (stdout, "sys$connect() %%X%08.08X\n", status);
   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 (NULL, FI_LI, WATCH_MOD_ODS,
                 "OdsOpen() !&B !AZ !AZ !&A(!&X)",
                 OdsExtended, FileSpec, DefaultSpec, AstFunction, AstParam);

   memset (odsptr, 0, sizeof(ODS_STRUCT));

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

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

   if (!FileSpecLength && FileSpec)
      FileSpecLength = strlen(FileSpec);
   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);
      if (Debug) fprintf (stdout, "sys$open() %%X%08.08X\n", status);
      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);
      if (Debug) fprintf (stdout, "sys$open() %%X%08.08X\n", status);
      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;
   FAB_AST  FabAstFunction;

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

   if (WATCH_MODULE(WATCH_MOD_ODS))
      WatchThis (NULL, FI_LI, WATCH_MOD_ODS,
                 "OdsClose() !&A(!&X)", AstFunction, AstParam);

   odsptr->Fab.fab$l_ctx = AstParam;

   if (odsptr->DeleteOnClose)
   {
      odsptr->Fab.fab$l_fop |= FAB$M_DLT;
      /* use SYSPRV to ensure deletion-on-close of file */
      sys$setprv (1, &SysPrvMask, 0, 0);
   }
   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->DeleteOnClose) 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 (NULL, FI_LI, WATCH_MOD_ODS,
                 "OdsParse() !&B !&Z !&Z !&A(!&X)",
                 OdsExtended, FileSpec, DefaultSpec, AstFunction, AstParam);

   /* ensure for successive parses any previous resources are released */
   if (odsptr->ParseInUse == ODS_PARSE_IN_USE_PATTERN)
      OdsParseRelease (odsptr);

   memset (odsptr, 0, sizeof(ODS_STRUCT));

   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);
      if (Debug) fprintf (stdout, "sys$parse() %%X%08.08X\n", status);
      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);
      if (Debug) fprintf (stdout, "sys$parse() %%X%08.08X\n", status);
      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 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)
*/ 

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

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

   if (WATCH_MODULE(WATCH_MOD_ODS))
      WatchThis (NULL, FI_LI, 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;
      status = sys$search (&odsptr->Fab, 0, 0);
      if (Debug) fprintf (stdout, "sys$search() %%X%08.08X\n", status);
      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$search (&odsptr->Fab, &OdsNamBlockAst, &OdsNamBlockAst);
      if (Debug) fprintf (stdout, "sys$search() %%X%08.08X\n", status);
      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 (NULL, FI_LI, 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;
      status = sys$search (&odsptr->Fab, 0, 0);
      if (Debug) fprintf (stdout, "sys$search() %%X%08.08X\n", status);
      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$search (&odsptr->Fab, &OdsNamBlockAst, &OdsNamBlockAst);
      if (Debug) fprintf (stdout, "sys$search() %%X%08.08X\n", status);
      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).
*/ 

OdsNamBlockAst (struct FAB *FabPtr)

{
   int  status;
   FAB_AST  AstFunction;
   ODS_STRUCT  *odsptr;

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

   if (WATCH_MODULE(WATCH_MOD_ODS))
      WatchThis (NULL, FI_LI, 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)
      {
         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->Naml.naml$l_filesys_name;
         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->NamFileSysNamePtr = odsptr->Nam.nam$l_name;
         odsptr->NamFileSysNameLength = odsptr->Nam.nam$b_name +
                                        odsptr->Nam.nam$b_type;
                                        odsptr->Nam.nam$b_ver;
         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->ExpFileName[odsptr->ExpFileNameLength] = '\0';
         odsptr->ResFileName[odsptr->ResFileNameLength] = '\0';
      }
   }

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

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

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

OdsParseRelease (ODS_STRUCT *odsptr)

{
   int  status;
   char  ExpFileName [16];

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

   if (WATCH_MODULE(WATCH_MOD_ODS))
      WatchThis (NULL, FI_LI, WATCH_MOD_ODS,
                 "OdsParseRelease() !&B !&B", OdsExtended, odsptr->ParseInUse);

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

   odsptr->Fab.fab$l_fna = "a:[b]c.d;";
   odsptr->Fab.fab$b_fns = 9;
   odsptr->Fab.fab$b_dns = 0;
   /* synchronous service */
   odsptr->Fab.fab$l_fop &= ~FAB$M_ASY;

#ifdef ODS_EXTENDED
   if (OdsExtended)
   {
      odsptr->Naml.naml$l_long_expand = ExpFileName;
      odsptr->Naml.naml$l_long_expand_alloc = sizeof(ExpFileName);
      odsptr->Naml.naml$l_long_result = 0;
      odsptr->Naml.naml$b_nop = NAM$M_SYNCHK;
   }
   else
#endif /* ODS_EXTENDED */
   {
      odsptr->Nam.nam$l_esa = ExpFileName;
      odsptr->Nam.nam$b_ess = sizeof(ExpFileName);
      odsptr->Nam.nam$l_rlf = 0;
      odsptr->Nam.nam$b_nop = NAM$M_SYNCHK;
   }

   status = sys$parse (&odsptr->Fab, 0, 0);

   if (WATCH_MODULE(WATCH_MOD_ODS))
      WatchThis (NULL, FI_LI, WATCH_MOD_ODS, "!&S %!-!&M", status);
}

/****************************************************************************/
/*
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 (NULL, FI_LI, 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;

#ifdef ODS_EXTENDED
         if (OdsExtended && odsptr->NamFileSysNameLength)
         {
            sptr = (cptr = odsptr->NamFileSysNamePtr) + 1;
            while (*sptr) *cptr++ = *sptr++;
            *cptr = '\0';
            odsptr->NamFileSysNameLength--;
         }
#endif /* ODS_EXTENDED */
      }
      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;

#ifdef ODS_EXTENDED
         if (OdsExtended && odsptr->NamFileSysNameLength)
         {
            cptr = odsptr->NamFileSysNamePtr + odsptr->NamFileSysNameLength;
            while (cptr > odsptr->NamFileSysNamePtr && *cptr != '.') cptr--;
            if (*cptr == '.')
            {
               sptr = cptr + 1;
               while (*sptr) *cptr++ = *sptr++;
               *cptr = '\0';
               odsptr->NamFileSysNameLength = cptr - odsptr->NamFileSysNamePtr;
            }
         }
#endif /* ODS_EXTENDED */
      }
      else
      {
         /* no type and no version supplied */
         (odsptr->NamVersionPtr = odsptr->NamTypePtr)[0] = '\0';
         odsptr->NamTypeLength = odsptr->NamVersionLength = 0;

#ifdef ODS_EXTENDED
         if (OdsExtended && odsptr->NamFileSysNameLength)
         {
            cptr = odsptr->NamFileSysNamePtr + odsptr->NamFileSysNameLength;
            while (cptr > odsptr->NamFileSysNamePtr && *cptr != '.') cptr--;
            if (*cptr == '.')
            {
               *cptr = '\0';
               odsptr->NamFileSysNameLength = cptr - odsptr->NamFileSysNamePtr;
            }
         }
#endif /* ODS_EXTENDED */
      }
   }
   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;

#ifdef ODS_EXTENDED
      if (OdsExtended && odsptr->NamFileSysNameLength)
      {
         cptr = odsptr->NamFileSysNamePtr + odsptr->NamFileSysNameLength;
         while (cptr > odsptr->NamFileSysNamePtr && *cptr != ';') cptr--;
         if (*cptr == ';')
         {
            *cptr = '\0';
            odsptr->NamFileSysNameLength = cptr - odsptr->NamFileSysNamePtr;
         }
      }
#endif /* ODS_EXTENDED */
   }

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

   if (WATCH_MODULE(WATCH_MOD_ODS))
      WatchThis (NULL, FI_LI, 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 (NULL, FI_LI, WATCH_MOD_ODS,
                 "OdsFileAcpInfo() !&B !&A(!&X)",
                 OdsExtended, 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->NamFileSysNamePtr;
      fqptr->FileNameDsc.dsc$w_length = odsptr->NamFileSysNameLength;

      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);
   }

   atptr = &fqptr->FileAtr;
   atptr->atr$w_size = sizeof(fqptr->CdtBinTime);
   atptr->atr$w_type = ATR$C_CREDATE;
   atptr->atr$l_addr = &fqptr->CdtBinTime;
   atptr++;
   atptr->atr$w_size = sizeof(fqptr->RdtBinTime);
   atptr->atr$w_type = ATR$C_REVDATE;
   atptr->atr$l_addr = &fqptr->RdtBinTime;
   atptr++;
   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->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 (NULL, FI_LI, 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;
   }
   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);
}

/****************************************************************************/
/*
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 (NULL, FI_LI, 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;
   if (Debug)
      fprintf (stdout, "device |%*.*s|\n",
               DeviceDsc.dsc$w_length, DeviceDsc.dsc$w_length,
               DeviceDsc.dsc$a_pointer);

   status = sys$assign (&DeviceDsc, &fqptr->AcpChannel, 0, 0, 0);
   if (Debug) fprintf (stdout, "sys$assign() %%X%08.08X\n", status);
   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 (NULL, FI_LI, 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 (Debug) fprintf (stdout, "sys$qio() %%X%08.08X\n", status);
   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 (NULL, FI_LI, 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$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$l_esa = ExpFileName;
      SearchNam.nam$b_ess = ODS2_MAX_FILE_NAME_LENGTH;
   }

   status = sys$parse (&SearchFab, 0, 0);
   if (VMSok (status)) status = sys$search (&SearchFab, 0, 0);

   if (WATCH_MODULE(WATCH_MOD_ODS))
      WatchThis (NULL, FI_LI, WATCH_MOD_ODS,
                 "sys$parse/search() !&S", status);

   /* 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;
   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 (NULL, FI_LI, WATCH_MOD_ODS,
                 "OdsNameOfDirectoryFile() !&B !&Z !&Z !&Z",
                 OdsExtended, FileName, FileNameBuffer, DirFileNameBuffer);

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

   DirFileNamePtr[*DirFileLengthPtr = 0] = '\0';

   /* check if this has the same directory components as the last one */
   sptr = FileNameBuffer;
   cptr = FileName;
   while (toupper(*cptr) == toupper(*sptr) && *sptr != ']' && *cptr != ']')
   {
      *sptr++;
      *cptr++;
   }
   if (*cptr == ']' && *sptr == ']' && *(sptr-1) != '.' && *(cptr-1) != '.')
   {
      /* it does! return the result of the last one */
      strcpy (DirFileNamePtr, DirFileNameBuffer);
      *DirFileLengthPtr = DirFileNameBufferLength;
      if (Debug) fprintf (stdout, "buffer! |%s|\n", DirFileNamePtr);
      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;

      /* use SYSPRV to ensure access to directory */
      sys$setprv (1, &SysPrvMask, 0, 0);
      status = sys$parse (&ParseFab, 0, 0);
      sys$setprv (0, &SysPrvMask, 0, 0);
      if (VMSnok (status)) return (status);

      ParseNaml.naml$l_long_ver[ParseNaml.naml$l_long_ver_size] = '\0';
      if (Debug) fprintf (stdout, "ExpFileName |%s|\n", ExpFileName);
      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;

      /* use SYSPRV to ensure access to directory */
      sys$setprv (1, &SysPrvMask, 0, 0);
      status = sys$parse (&ParseFab, 0, 0);
      sys$setprv (0, &SysPrvMask, 0, 0);
      if (VMSnok (status)) return (status);

      ParseNam.nam$l_ver[ParseNam.nam$b_ver] = '\0';
      if (Debug) fprintf (stdout, "ExpFileName |%s|\n", ExpFileName);
      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 (Debug) fprintf (stdout, "sys$search() %%X%08.08X\n", status);
      if (VMSok (status)) status = ParseFab.fab$l_sts;
      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);
      }
   }

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

   zptr = (sptr = DirFileNameBuffer) + sizeof(DirFileNameBuffer);
   if (!memcmp (fptr, "[000000]", 8) || !memcmp (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)
         *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)
         *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 */
   strcpy (FileNameBuffer, FileName);

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

   if (WATCH_MODULE(WATCH_MOD_ODS))
      WatchThis (NULL, FI_LI, 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 (NULL, FI_LI, WATCH_MOD_ODS,
                 "!AZ !AZ", FileNameBuffer, DirFileNameBuffer);

   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
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 (NULL, FI_LI, 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);
#ifdef ODS_EXTENDED
   if (OdsExtended)
      odsptr1->NamFileSysNamePtr = odsptr1->SysFileName;
   else
#endif /* ODS_EXTENDED */
      odsptr1->NamFileSysNamePtr = odsptr1->NamNamePtr;

   if (Debug)
      fprintf (stdout, "|%s|\n|%s|\n|%s|\n|%s|\n|%s|\n|%s|\n|%s|\n",
         odsptr1->NamNodePtr, odsptr1->NamDevicePtr,
         odsptr1->NamDirectoryPtr, odsptr1->NamNamePtr,
         odsptr1->NamTypePtr, odsptr1->NamVersionPtr,
         odsptr1->NamFileSysNamePtr);
}

/*****************************************************************************/
/*
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;

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

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

   memset (odsptr, 0, sizeof(ODS_STRUCT));

   status = OdsOpenReadOnly (odsptr, FileName);
   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;
   if (Debug)
      fprintf (stdout, "%d %d = %d bytes\n",
         odsptr->XabFhc.xab$l_ebk, odsptr->XabFhc.xab$w_ffb, SizeInBytes); 

   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';
      cptr += odsptr->Rab.rab$w_rsz + 1;
      odsptr->DataLength += odsptr->Rab.rab$w_rsz + 1;
      SizeInBytes -= odsptr->Rab.rab$w_rsz + 1;
   }
   if (Debug) fprintf (stdout, "sys$get() %%X%08.08X\n", status);

   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;
   }
   if (Debug) fprintf (stdout, "DataLength: %d\n", odsptr->DataLength);

   return (status);
}

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

OdsFreeTextFile (ODS_STRUCT *odsptr)

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

   if (WATCH_MODULE(WATCH_MOD_ODS))
      WatchThis (NULL, FI_LI, 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 (Debug) fprintf (stdout, "OdsParseTextFile() %d\n", 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' && *(unsigned short*)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' && *(unsigned short*)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++;
   if (Debug)
      fprintf (stdout, "%d |%s|\n",
               odsptr->DataLineNumber, odsptr->DataLinePtr);

   return (odsptr->DataLinePtr);
}

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

