/*****************************************************************************/
/*
                                  DAVmeta.c

The WASD WebDAV meta-data is used to store lock and 'dead' property data
potentially for each file-system object (file and directory) accessable from
the server using WebDAV.  There are three locations used to store meta-data
files, the default and two by global or per-path configuration.

  1. the same directory as the resource (default)
  2. a specified subdirectory of the resource (e.g. [.^.dav]
  3. a completely independent directory (e.g. DKA0:[WASDAV])

Meta-data files are stored using the same name as the resource, with "__wasdav"
concatenated to the file type (note the two underscores).  Collection
(directory) meta-data is associated with the directory file (e.g.
A_Directory.DIR__wasdav).  This was done for reasons of programmatic
simplicity, file-system efficiency, reducing WebDAV latency and for the
convenience of command-line management of the meta-data files.  No specialised
tools are required.  All non-WebDAV WASD functionality ignores *.*__wasdav;
files (e.g. directory listing, file GET).

1. Same Directory

A directory listing using the default metadata location (with the resource)
containing two resources with associated meta-data looks something like:

   admin.c;1              125KB   3-JAN-2009 06:06:55.46
   admin.c__wasdav;1     0.50KB  11-JUN-2009 18:26:27.82
   admin.h;1                8KB  17-FEB-2008 15:02:53.32
   auth.c;1               142KB  27-APR-2008 08:28:59.21
   auth.c__wasdav;1      0.50KB  11-JUN-2009 18:27:17.02
   auth.h;1                18KB  29-APR-2009 21:16:31.33
   authaccess.c;1          16KB   6-AUG-2007 17:43:51.68
   authacme.c;1            24KB  30-APR-2009 06:23:51.94

2. Specified Subdirectory

When meta-data files are stored in a subdirectory (automatically created) of
the resource location the associated files will look like:

   admin.c;1                      125KB   3-JAN-2009 06:06:55.46
   admin.h;1                        8KB  17-FEB-2008 15:02:53.32
   auth.c;1                       142KB  27-APR-2008 08:28:59.21
   auth.h;1                        18KB  29-APR-2009 21:16:31.33
   authaccess.c;1                  16KB   6-AUG-2007 17:43:51.68
   authacme.c;1                    24KB  30-APR-2009 06:23:51.94
   [.^.dav]admin.c__wasdav;1     0.50KB  11-JUN-2009 18:26:27.82
   [.^.dav]auth.c__wasdav;1      0.50KB  11-JUN-2009 18:27:17.02

Note: The meta-data for a directory is contained in the meta-data subdirectory
contained within that same directory, not in the parent where the directory
file ("whatever.DIR") actually resides, in a nameless file with the type
(extension) ".DIR__wasdav".  For example, meta-data for the above directory
would be contained in "[.WHATEVER.^.DAV].DIR__wasdav".

3. Independent Directory

When meta-data files are stored in an independent directory they are
distributed into one of 16 (hexadecimally named, automatically created)
subdirectories.  The full file path of the resource is ODS-5 escaped and used
to uniquely identify the meta-data.  The above example translated into this
will look something like:

   DKA0:[WASDAV.00]wasd_root^:^[src^.httpd^]admin.c__wasdav;1
   DKA0:[WASDAV.05]wasd_root^:^[src^.httpd^]auth.c__wasdav;1

Of course this cannot be used within an ODS-2 environment (VAX) or in an
extended-capable environment on an ODS-2 volume.


META-DATA XML
-------------
Meta-data files contain XML.  WASD name-space contains zero or more WASD lock
elements and/or zero or more property elements.  If there is zero of both (as
might occur after an UNLOCK or PROPPATCH remove, the meta-data file is deleted.

The WASD element also contains the (file-system) resource the meta-data
represents (file or directory specification) and the date/time of the last
update to that meta-data.

The following is an example of this XML structure:

  <?xml version="1.0" encoding="UTF-8"?>
  <WASDAV:data xmlns:WASD="WASD.WebDAV"
  updated="2007-07-23T01:39:11Z">
  <WASDAV:lock ...>
  ...
  </WASDAV:lock>
  <WASDAV:prop>
  ...
  </WASDAV:prop>
  </WASDAV:data>


LOCK META-DATA
--------------
All lock characteristics except the potentially XML-containing owner element
are contained in attributes to the WASD lock element.  One or more lock
elements may be present representing compatible locks against the resource.

Example lock meta-data:

  <?xml version="1.0" encoding="UTF-8"?>
  <WASDAV:data xmlns:WASDAV="WASD.VMS.WebDAV"
  updated="2007-07-23T01:39:11Z">
  <WASDAV:lock token="urn:uuid:833609C-647F-6BE8-1D27-E38A0FAC75061"
  type="write" scope="exclusive" timeout="infinite" depth="0">
  <WASDAV:owner><NS:owner xmlns:NS="whatever">blah blah</NS:owner></WASDAV:owner>
  </WASDAV:lock>
  </WASDAV:data>


PROPERTY META-DATA
------------------
The property elements are stored as-supplied by client.  It is presumed that
their XML well-formedness is guaranteed by the original request XML parsing. 
For ease of processing (by WASD DAV) each element in a property contains an
'xmlns=""' attribute.

An example of such meta-data generated by a Microsoft Windows (not Internet)
Explorer client (example is intentionally not wrapped):

  <?xml version="1.0" encoding="UTF-8"?>
  <WASDAV:data xmlns:WASDAV="WASD.VMS.WebDAV"
  updated="2007-07-23T01:39:11Z">
  <WASDAV:prop>
  <NS:Win32CreationTime xmlns:NS="urn:schemas-microsoft-com:">Tue, 26 Jun 2007 02:00:48 GMT</NS:Win32CreationTime>
  </WASDAV:prop>
  <WASDAV:prop>
  <NS:Win32LastAccessTime xmlns:NS="urn:schemas-microsoft-com:">Mon, 23 Jul 2007 01:52:32 GMT</NS:Win32LastAccessTime>
  </WASDAV:prop>
  <WASDAV:prop>
  <NS:Win32LastModifiedTime xmlns:NS="urn:schemas-microsoft-com:">Mon, 23 Jul 2007 01:52:32 GMT</NS:Win32LastModifiedTime>
  </WASDAV:prop>
  <WASDAV:prop>
  <NS:Win32FileAttributes xmlns:NS="urn:schemas-microsoft-com:">00000020</NS:Win32FileAttributes>
  </WASDAV:prop>
  </WASDAV:data>


VERSION HISTORY
---------------
04-OCT-2020  MGD  DavMetaOds() ensure extended syntax only used ODS-5 volumes
24-SEP-2020  MGD  bugfix; DavMetaCreateDir() and DavMetaDeleteDir()
                    allow for non-existant meta data files
                  bugfix; DavMetaName() no meta directory
03-DEC-2018  MGD  bugfix; DavMetaDir() ACCVIO from !SAME2(mfdptr,'[.')
12-SEP-2018  MGD  bugfix; significant refactor of locking
06-SEP-2018  MGD  bugfix; DavMeta..() explicitly ->ReadOds.DeleteOnClose
17-MAY-2018  MGD  DavXmlParserCreate() instead of XML_ParserCreateNS()
09-MAY-2018  MGD  bugfix; DavMetaParseXml() detect out-of-memory
01-OCT-2014  MGD  directory meta-data goes into its own metadata (sub)directory
27-SEP-2014  MGD  bugfix; DavMetaOpenAst() retry after meta directory creation
05-JUL-2014  MGD  configurable metadata (sub)directory
07-SEP-2013  MGD  bugfix; DavMetaUpdate() abort if SetTimeout() fails
08-JUN-2010  MGD  bugfix; DavMetaReadName() and DavMetaWriteName()
                    allow for typeless file names (e.g. ]AFILE.;)
10-MAY-2010  JPP  bugfix; do not set new token when refreshing
02-SEP-2009  MGD  bugfix; scanning backwards through extended file spec
31-DEC-2006  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

#include <stdio.h>
#include <ctype.h>
#include <errno.h>

#include <lckdef.h>
#include <libdef.h>

#include "wasd.h"
#include "davweb.h"

#define WASD_MODULE "DAVMETA"

#ifndef WASD_WEBDAV
#define WASD_WEBDAV 1
#endif

#define DAVMETA_READ_CHUNK (512 * 32)

/* just reduce the amount of XML parse noise when WATCHing */
#define WATCH_XML 1

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

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

extern BOOL  OdsExtended,
             WebDavEnabled,
             WebDavLockingEnabled;

extern int  EfnWait,
            EfnNoWait;

extern int64  HttpdTime64;
                      
extern unsigned long  SysPrvMask[];

extern char  ErrorSanityCheck[],
             HttpdSoftwareIdName[],
             HttpdVersion[];

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

/*****************************************************************************/
/*
Build the data buffer containing the XML formated WASD lock and properties
associated with the resource (file).  This can then be written to disk.

The two descriptor collections, 'MetaLockDsc' containing any existing lock
elements, and 'MetaPropDsc', containing any existing property elements are
merged with the lock elements from the request contained in 'XmlLockSetDsc',
and the property elements in 'XmlPropSetDsc'.

The merging is performed by emptying any element of 'MetaLockDsc' or
'MetaPropDsc' that is to be removed, by overwriting any existing element that
is to be set, or appended to the collection any new element, both to
'MetaLockDsc' or 'MetaPropDsc' as required.
*/

int DavMetaBuild (WEBDAV_META *mtaptr)

{
   int  status,
        ItemCount;
   char  *cptr, *czptr;
   char  DateTime [32];
   STR_DSC  *sdptr, *wdptr;
   WEBDAV_LOCK  *lckptr;
   REQUEST_STRUCT  *rqptr;
   WEBDAV_TASK  *tkptr;

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

   rqptr = mtaptr->RequestPtr;

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavMetaBuild()");

   tkptr = rqptr->WebDavTaskPtr;

   /*************************/
   /* remove/set properties */
   /*************************/

   StrDscColIfNotBegin (rqptr, &mtaptr->PropDsc, 256);

   if (STR_DSC_LEN(&mtaptr->PropDsc))
   {
      /* remove properties specified in the request XML */
      STR_DSC_ITERATE (sdptr, &tkptr->XmlData.PropRemoveDsc)
      {
         if (!STR_DSC_LEN(sdptr)) continue;
         /*  advise that DAV properties cannot be removed by clients */
         for (czptr = (cptr = STR_DSC_PTR(sdptr)) + STR_DSC_LEN(sdptr);
              cptr < czptr && (*cptr != 'x' || !MATCH6(cptr,"xmlns:"));
              cptr++);
         if (cptr < czptr && MATCH9(cptr+6,"NS=\"DAV:\""))
            return (SS$_INCOMPAT);
         DavMetaRemove (rqptr, &mtaptr->PropDsc, sdptr);
      }
   }

   if (STR_DSC_LEN(&tkptr->XmlData.PropSetDsc))
   {
      /* set properties specified in the request XML */
      STR_DSC_ITERATE (sdptr, &tkptr->XmlData.PropSetDsc)
      {
         if (!STR_DSC_LEN(sdptr)) continue;
         /* ensure DAV properties are not overridden by (nefarious?) clients */
         for (czptr = (cptr = STR_DSC_PTR(sdptr)) + STR_DSC_LEN(sdptr);
              cptr < czptr && (*cptr != 'x' || !MATCH6(cptr,"xmlns:"));
              cptr++);
         if (cptr < czptr && MATCH9(cptr+6,"NS=\"DAV:\""))
            return (SS$_INCOMPAT);
         DavMetaSet (rqptr, &mtaptr->PropDsc, sdptr);
      }
   }

   if (WATCHING (rqptr, WATCH_WEBDAV))
   {
      if (STR_DSC_LEN(&mtaptr->LockDsc))
      {
         WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "LOCK built");
         STR_DSC_ITERATE (sdptr, &mtaptr->LockDsc)
            if (STR_DSC_LEN(sdptr))
               WatchDataFormatted ("!AZ\n", STR_DSC_PTR(sdptr));
      }
      if (STR_DSC_LEN(&mtaptr->PropDsc))
      {
         WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "PROP built");
         STR_DSC_ITERATE (sdptr, &mtaptr->PropDsc)
         if (STR_DSC_LEN(sdptr))
            WatchDataFormatted ("!AZ\n", STR_DSC_PTR(sdptr));
      }
   }

   /******************/
   /* build meta XML */
   /******************/

   /* write will be by block and so must be evenly divisible by 512! */
   wdptr = StrDscBegin (rqptr, &mtaptr->WriteDsc, 4096);

   ItemCount = 0;

   DavWebDateTimeTo3339 (DateTime, &HttpdTime64);

   FaoToBuffer (wdptr, -1, NULL,
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
<WASDAV:data xmlns:WASDAV=\"" WEBDAV_NS_WASDAV "\"\n\
updated=\"!AZ !20%D\">\n",
                DateTime, &HttpdTime64);

   /* step through each lock in the list generating an XML text equivalent */
   LIST_ITERATE (lckptr, &mtaptr->LockList)
   {
      DavLockXml (rqptr, lckptr, wdptr);
      ItemCount++;
   }

   /* step through each property in the list adding the XML */
   STR_DSC_ITERATE (sdptr, &mtaptr->PropDsc)
   {
      if (!STR_DSC_LEN(sdptr)) continue;
      StrDscBuild (wdptr, NULL, "<WASDAV:prop>\n");
      StrDscBuild (wdptr, sdptr, NULL);
      StrDscNewLine (wdptr);
      StrDscBuild (wdptr, NULL, "</WASDAV:prop>\n");
      ItemCount++;
   }

   StrDscBuild (wdptr, NULL, "</WASDAV:data>\n");

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "!&Z", STR_DSC_PTR(wdptr));

   if (ItemCount) return (SS$_NORMAL);

   /* return this code to indicate the file is empty and should be deleted */
   return (SS$_FORGET);
}

/*****************************************************************************/
/*
Add the XML element specified in 'adptr' to the descriptor collection specified
by 'cdptr'.  Over-write an existing identical element in the collection. 
Returns a pointer to the new (or over-written) element.
*/

STR_DSC* DavMetaSet
(
REQUEST_STRUCT *rqptr,
STR_DSC *cdptr,
STR_DSC *adptr
)
{
   STR_DSC  *sdptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavMetaSet()");

   if (sdptr = DavMetaSearch (rqptr, cdptr, adptr))
   {
      /* overwite existing element */
      STR_DSC_LEN(sdptr) = 0;
      sdptr = StrDscBuild (sdptr, adptr, NULL);
   }
   else
      sdptr = StrDscBuild (cdptr, adptr, NULL);

   STR_DSC_SET_FROZEN(sdptr)
   return (sdptr);
}

/*****************************************************************************/
/*
Remove the XML element specified in 'rdptr' from the descriptor collection
specified by 'cdptr'.  Returns true if removed, false if not found.
*/

BOOL DavMetaRemove
(
REQUEST_STRUCT *rqptr,
STR_DSC *cdptr,
STR_DSC *rdptr
)
{
   STR_DSC  *sdptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavMetaRemove()");

   if (sdptr = DavMetaSearch (rqptr, cdptr, rdptr))
   {
      STR_DSC_LEN(sdptr) = 0;
      return (true);
   }
   return (false);
}

/*****************************************************************************/
/*
Search the collection specified by 'cdptr' for the element specified by
'edptr'.  Return a pointer to the descriptor if found, otherwise return NULL.
*/

STR_DSC* DavMetaSearch
(
REQUEST_STRUCT *rqptr,
STR_DSC *cdptr,
STR_DSC *edptr
)
{
   char  *cptr, *sptr;
   STR_DSC  *sdptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavMetaSearch()");

   if (!cdptr || !edptr) return (NULL);
   STR_DSC_ITERATE (sdptr, cdptr)
   {
      if (!(cptr = STR_DSC_PTR(edptr))) continue;
      if (!(sptr = STR_DSC_PTR(sdptr))) continue;
      while (*cptr && *sptr && *cptr != '>' && *sptr != '>' && *cptr == *sptr)
      {
         cptr++;
         sptr++;
      }
      /* if the two elements matched then it's a hit */
      if (*cptr == '>' && *sptr == '>') return (sdptr);
   }
   return (NULL);
}

/*****************************************************************************/
/*
(EXPAT) XML parse the text read from the database file.  This will parse the
lock elements into 'MetaLockDsc' and the property elements into 'MetaPropDsc'.
*/

int DavMetaParseXml (WEBDAV_META *mtaptr)

{
   int  status, xmlsts;
   STR_DSC  *sdptr;
   REQUEST_STRUCT  *rqptr;
   WEBDAV_LOCK  *lckptr;
   WEBDAV_TASK  *tkptr;

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

   rqptr = mtaptr->RequestPtr;

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavMetaParseXml()");

   sdptr = &mtaptr->ReadDsc;

   /* shouldn't be calling this without content to parse! */
   if (!(STR_DSC_SANITY(sdptr) && STR_DSC_LEN(sdptr))) return (SS$_BUGCHECK);

   tkptr = rqptr->WebDavTaskPtr;

   /* successful unless an error is encountered :-) */
   status = SS$_NORMAL;

   StrDscColIfNotBegin (rqptr, &mtaptr->PropDsc, 256);

   mtaptr->ParseData = mtaptr->ParseLock =
      mtaptr->ParseLockExpires = mtaptr->ParseLockOwner =
      mtaptr->ParseProp = WEBDAV_STATE_NOT_PARSING;

   tkptr->XmlData.ParserPtr = DavXmlParserCreate (rqptr);
   if (!tkptr->XmlData.ParserPtr) return (vaxc$errno);

   mtaptr->ElementDepth = 0;

   XML_SetUserData (tkptr->XmlData.ParserPtr, mtaptr);

   XML_SetElementHandler (tkptr->XmlData.ParserPtr, DavMetaStartElement,
                                                    DavMetaEndElement);

   XML_SetCharacterDataHandler (tkptr->XmlData.ParserPtr, DavMetaCharData);

   xmlsts = XML_Parse (tkptr->XmlData.ParserPtr,
                       STR_DSC_PTR(sdptr), STR_DSC_LEN(sdptr), 1);

   if (xmlsts == XML_STATUS_ERROR)
   {
      DavXmlParseError (rqptr, tkptr->XmlData.ParserPtr,
                        sdptr, &mtaptr->ParseErrorDsc);
      if (DavXmlMemoryError (rqptr, tkptr->XmlData.ParserPtr))
         status = LIB$_INSVIRMEM;
   }

   if (tkptr->XmlData.ParserPtr) XML_ParserFree (tkptr->XmlData.ParserPtr);

   if (xmlsts == XML_STATUS_ERROR)
   {
      /* shouldn't be getting parse errors from our own data */
      ErrorNoticed (rqptr, status = SS$_BUGCHECK,
                    STR_DSC_PTR(&mtaptr->ParseErrorDsc), FI_LI);
   }
   else
   {
      if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      {
         if (STR_DSC_LEN(&mtaptr->LockDsc))
         {
            WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "LOCKS current");
            STR_DSC_ITERATE (sdptr, &mtaptr->LockDsc)
               if (STR_DSC_LEN(sdptr))
                  WatchDataFormatted ("!AZ\n", STR_DSC_PTR(sdptr));
         }
         if (STR_DSC_LEN(&mtaptr->PropDsc))
         {
            WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "META current");
            STR_DSC_ITERATE (sdptr, &mtaptr->PropDsc)
               if (STR_DSC_LEN(sdptr))
                  WatchDataFormatted ("!AZ\n", STR_DSC_PTR(sdptr));
         }
      }
   }

   return (status);
}

/*****************************************************************************/
/*
(EXPAT) This function and DavMetaEndElement() set various flags to indicate the
state of the current XML element.  From those various flags indicating
properties to be manipulated are set.

This pretty-much assumes that the parsing library is ensuring document
well-formedness, so the checks on element nesting are very elementary.
*/

void XMLCALL DavMetaStartElement
(
WEBDAV_META *mtaptr,
char *ElementPtr,
char **AttrPtr
)
{
   int  idx, len, status;
   char  *cptr, *sptr, *zptr;
   char  ResourceBuffer [ODS_MAX_FILE_NAME_LENGTH+1];
   STR_DSC  *sdptr;
   STR_DSC_AUTO (ScratchDsc);
   REQUEST_STRUCT  *rqptr;
   WEBDAV_LOCK  *lckptr;

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

   rqptr = mtaptr->RequestPtr;

   if (WATCH_XML && WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
                 "DavMetaStartElement() !UL !&Z",
                 mtaptr->ElementDepth+1, ElementPtr);

   mtaptr->ElementDepth++;

   if (WATCH_XML && WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
   {
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "ELEMENT !AZ", ElementPtr);
      for (idx = 0; AttrPtr[idx]; idx += 2)
         WatchDataFormatted ("!AZ=\"!AZ\"\n", AttrPtr[idx], AttrPtr[idx+1]);
   }

   sdptr = NULL;
   if (WEBDAV_IS_PARSING(mtaptr->ParseLock))
   {
      /* the most recently added lock will be at the head of the list */
      lckptr = LIST_GET_HEAD(&mtaptr->LockList);
      if (WEBDAV_IS_PARSING(mtaptr->ParseLockOwner))
         sdptr = &lckptr->OwnerDsc;
      else
         sdptr = &lckptr->LockDsc;
   }
   else
   if (WEBDAV_IS_PARSING(mtaptr->ParseProp))
      sdptr = &mtaptr->PropDsc;

   if (WEBDAV_IS_WASDAVEL (ElementPtr, "data"))
   {
      /* should only be the one data element per meta-data file! */
      if (WEBDAV_IS_PARSED(mtaptr->ParseData))
      {
         if (WATCHING (rqptr, WATCH_WEBDAV))
            WatchThis (WATCHITM(rqptr), WATCH_WEBDAV,
                       "META multiple <WASDAV:data> (ignoring subsequent)");
         return;
      }
      WEBDAV_PARSING(mtaptr->ParseData);
   }
   else
   if (WEBDAV_IS_WASDAVEL (ElementPtr, "lock"))
   {
      lckptr = (WEBDAV_LOCK*)VmGetHeap (rqptr, sizeof(WEBDAV_LOCK));
      ListAddHead (&mtaptr->LockList, lckptr, LIST_ENTRY_TYPE_DAVLOCK);

      StrDscColIfNotBegin (rqptr, &lckptr->LockDsc, 256);
      StrDscBegin (rqptr, &lckptr->OwnerDsc, 256);

      WEBDAV_PARSING(mtaptr->ParseLock);
      sdptr = &lckptr->LockDsc;
      sdptr = StrDscBuild (sdptr, NULL, "<WASDAV:lock");
      for (idx = 0; AttrPtr[idx]; idx += 2)
      {
         StrDscBuild (sdptr, NULL, " ");
         StrDscBuild (sdptr, NULL, AttrPtr[idx]);
         StrDscBuild (sdptr, NULL, "=\"");
         StrDscBuild (sdptr, NULL, AttrPtr[idx+1]);
         StrDscBuild (sdptr, NULL, "\"");

         if (WEBDAV_IS_PARSING(mtaptr->ParseLock) &&
             !WEBDAV_IS_PARSING(mtaptr->ParseLockOwner))
         {
            if (!strcmp (AttrPtr[idx], "depth"))
            {
               if (!strcmp (AttrPtr[idx+1], "0"))
                  lckptr->Depth = WEBDAV_DEPTH_ZERO;
               else
               if (!strcmp (AttrPtr[idx+1], "1"))
                  lckptr->Depth = WEBDAV_DEPTH_ONE;
               else
               if (!strcmp (AttrPtr[idx+1], "infinity"))
                  lckptr->Depth = WEBDAV_DEPTH_INFINITY;
               else
                  ErrorNoticed (rqptr, SS$_BUGCHECK, AttrPtr[idx+1], FI_LI);
            }
            else
            if (!strcmp (AttrPtr[idx], "expires"))
            {
               zptr = (sptr = lckptr->ExpiresString) +
                      sizeof(lckptr->ExpiresString)-1;
               for (cptr = AttrPtr[idx+1];
                    *cptr && sptr < zptr;
                    *sptr++ = *cptr++);
               if (sptr < zptr)
               {
                  *sptr = '\0';
                  status = DavWebDateTimeFrom3339 (lckptr->ExpiresString,
                                                   &lckptr->ExpiresTime64);
                  if (VMSnok (status))
                     ErrorNoticed (rqptr, status, AttrPtr[idx+1], FI_LI);
               }
               else
               {
                  lckptr->ExpiresTime64 = 0;
                  lckptr->ExpiresString[0] = '\0';
                  ErrorNoticed (rqptr, SS$_BUGCHECK, AttrPtr[idx+1], FI_LI);
               }
            }
            else
            if (!strcmp (AttrPtr[idx], "token"))
            {
               zptr = (sptr = lckptr->TokenString) +
                      sizeof(lckptr->TokenString);
               for (cptr = AttrPtr[idx+1];
                    *cptr && sptr < zptr;
                    *sptr++ = *cptr++);
               if (sptr < zptr)
                  *sptr = '\0';
               else
               {
                  lckptr->TokenString[0] = '\0';
                  ErrorNoticed (rqptr, SS$_BUGCHECK, AttrPtr[idx+1], FI_LI);
               }
            }
            else
            if (!strcmp (AttrPtr[idx], "type"))
            {
               if (!strcmp (AttrPtr[idx+1], "write"))
                  lckptr->Type = WEBDAV_LOCK_TYPE_WRITE;
               else
                  ErrorNoticed (rqptr, SS$_BUGCHECK, AttrPtr[idx+1], FI_LI);
            }
            else
            if (!strcmp (AttrPtr[idx], "scope"))
            {
               if (!strcmp (AttrPtr[idx+1], "exclusive"))
                  lckptr->Scope = WEBDAV_LOCK_SCOPE_EXCLUSIVE;
               else
               if (!strcmp (AttrPtr[idx+1], "shared"))
                  lckptr->Scope = WEBDAV_LOCK_SCOPE_SHARED;
               else
                  ErrorNoticed (rqptr, SS$_BUGCHECK, AttrPtr[idx+1], FI_LI);
            }
            else
            if (!strcmp (AttrPtr[idx], "timeout"))
            {
               if (!strcmp (AttrPtr[idx+1], "infinite"))
               {
                  lckptr->Timeout = WEBDAV_TIMEOUT_INFINITE;
                  strzcpy (lckptr->TimeoutString, "infinite",
                           sizeof(lckptr->TimeoutString));
               }
               else
               if (!strncmp (AttrPtr[idx+1], "Second-", 7) &&
                   isdigit(AttrPtr[idx+1][7]))
               {
                  lckptr->Timeout = atoi(AttrPtr[idx+1]+7);
                  sprintf (lckptr->TimeoutString, "Second-%d", lckptr->Timeout);
               }
               else
                  ErrorNoticed (rqptr, SS$_BUGCHECK, AttrPtr[idx+1], FI_LI);
            }
         }
      }
      StrDscBuild (sdptr, NULL, ">\n");
      sdptr = NULL;
   }
   else
   if (WEBDAV_IS_WASDAVEL (ElementPtr, "owner"))
   {
      if (WEBDAV_IS_PARSING(mtaptr->ParseLock))
         WEBDAV_PARSING(mtaptr->ParseLockOwner);
      else
         ErrorNoticed (rqptr, SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
   }
   else
   if (WEBDAV_IS_WASDAVEL (ElementPtr, "prop"))
      WEBDAV_PARSING(mtaptr->ParseProp);

   if (sdptr)
   {
      for (cptr = ElementPtr; *cptr && *cptr != ' '; cptr++);
      sdptr = StrDscBuild (sdptr, NULL, "<NS:");
      StrDscBuild (sdptr, NULL, cptr+1);

      for (idx = 0; AttrPtr[idx]; idx += 2)
      {
         StrDscBuild (sdptr, NULL, " ");
         StrDscBuild (sdptr, NULL, AttrPtr[idx]);
         StrDscBuild (sdptr, NULL, "=\"");
         StrDscBuild (sdptr, NULL, AttrPtr[idx+1]);
         StrDscBuild (sdptr, NULL, "\"");
      }

      StrDscBuild (sdptr, NULL, " xmlns:NS=\"");
      StrDscThis (NULL, &ScratchDsc, ElementPtr, cptr - ElementPtr);
      StrDscBuild (sdptr, &ScratchDsc, NULL);
      StrDscBuild (sdptr, NULL, "\">");
      WEBDAV_PARSING(mtaptr->ParseCharData);
   }
   else
      WEBDAV_NOT_PARSING(mtaptr->ParseCharData);
}

/*****************************************************************************/
/*
(EXPAT) This function and DavMetaStartElement() set various flags to indicate
the state of the current XML element.
*/

void XMLCALL DavMetaEndElement
(
WEBDAV_META *mtaptr,
char *ElementPtr
)
{
   char  *cptr, *sptr;
   STR_DSC  *sdptr;
   STR_DSC_AUTO (ScratchDsc);
   REQUEST_STRUCT  *rqptr;
   WEBDAV_LOCK  *lckptr;

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

   rqptr = mtaptr->RequestPtr;

   if (WATCH_XML && WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
                 "DavMetaEndElement() !UL !&Z",
                  mtaptr->ElementDepth, ElementPtr);

   if (!WEBDAV_IS_PARSING(mtaptr->ParseData)) return;

   if (WEBDAV_IS_PARSING(mtaptr->ParseLock))
   {
      if (WEBDAV_IS_WASDAVEL (ElementPtr, "lock"))
      {
         /* no longer parsing the lock, freeze current content */ 
         WEBDAV_PARSED(mtaptr->ParseLock);
         /* the most recently added lock will be at the head of the list */
         lckptr = LIST_GET_HEAD(&mtaptr->LockList);
         sdptr = &lckptr->LockDsc;
         sdptr = StrDscBuild (sdptr, NULL, "</WASDAV:lock>");
         StrDscTailFrozen (&lckptr->LockDsc);
      }
      else
      if (WEBDAV_IS_WASDAVEL (ElementPtr, "owner"))
         WEBDAV_PARSED(mtaptr->ParseLockOwner);
   }
   else
   if (WEBDAV_IS_PARSING(mtaptr->ParseProp))
   {
      if (WEBDAV_IS_WASDAVEL (ElementPtr, "prop"))
      {
         /* no longer parsing this property, freeze current content */ 
         WEBDAV_PARSED(mtaptr->ParseProp);
         StrDscTailFrozen (&mtaptr->PropDsc);
      }
   }
   else
   /* as this is the level 0 element it MUST be the last checked! */
   if (WEBDAV_IS_PARSING(mtaptr->ParseData))
   {
      if (WEBDAV_IS_WASDAVEL (ElementPtr, "data"))
         WEBDAV_PARSED(mtaptr->ParseData);
   }

   sdptr = NULL;
   if (WEBDAV_IS_PARSING(mtaptr->ParseLock))
   {
      /* the most recently added lock will be at the head of the list */
      lckptr = LIST_GET_HEAD(&mtaptr->LockList);
      if (WEBDAV_IS_PARSING(mtaptr->ParseLockOwner))
         sdptr = &lckptr->OwnerDsc;
      else
         sdptr = &lckptr->LockDsc;
   }
   else
   if (WEBDAV_IS_PARSING(mtaptr->ParseProp))
      sdptr = &mtaptr->PropDsc;

   if (sdptr)
   {
      for (cptr = ElementPtr; *cptr && *cptr != ' '; cptr++);
      sdptr = StrDscBuild (sdptr, NULL, "</NS:");
      StrDscBuild (sdptr, NULL, cptr+1);
      StrDscBuild (sdptr, NULL, ">");

      if (mtaptr->ElementDepth == 3)
      {
         if (WEBDAV_IS_PARSING(mtaptr->ParseProp))
         {
            /* end of this property, freeze current content */ 
            STR_DSC_SET_FROZEN (sdptr)
         }
         WEBDAV_NOT_PARSING(mtaptr->ParseCharData);
      }
      else
         WEBDAV_PARSING(mtaptr->ParseCharData);
   }
   else
      WEBDAV_NOT_PARSING(mtaptr->ParseCharData);

   if (mtaptr->ElementDepth) mtaptr->ElementDepth--;
}

/*****************************************************************************/
/*
(EXPAT) XML character data.
*/

void XMLCALL DavMetaCharData
(
WEBDAV_META *mtaptr,
XML_Char *DataPtr,
int DataLength
)
{
   STR_DSC  *sdptr;
   STR_DSC_AUTO (CharDataDsc);
   REQUEST_STRUCT  *rqptr;
   WEBDAV_LOCK  *lckptr;

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

   rqptr = mtaptr->RequestPtr;

   if (WATCH_XML && WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
                 "DavMetaCharData() !&B !UL |!#AZ",
                 mtaptr->ParseCharData, DataLength, DataLength, DataPtr);

   if (!WEBDAV_IS_PARSING(mtaptr->ParseCharData)) return;

   sdptr = NULL;
   if (WEBDAV_IS_PARSING(mtaptr->ParseLock))
   {
      /* the most recently added lock will be at the head of the list */
      lckptr = LIST_GET_HEAD(&mtaptr->LockList);
      if (WEBDAV_IS_PARSING(mtaptr->ParseLockOwner))
         sdptr = &lckptr->OwnerDsc;
      else
         sdptr = &lckptr->LockDsc;
   }
   else
   if (WEBDAV_IS_PARSING(mtaptr->ParseProp))
      sdptr = &mtaptr->PropDsc;

   if (!sdptr) return;

   StrDscThis (NULL, &CharDataDsc, DataPtr, DataLength);
   StrDscBuild (sdptr, &CharDataDsc, NULL);
}

/*****************************************************************************/
/*
Generate the name of the meta-data file.  If ReadWrite is 'r' then it generates
the read metadata filename.  If 'w' then the write name.
*/

DavMetaName
(
WEBDAV_META *mtaptr,
char *FileSpec,
int ReadWrite
)
{
   static char  HexDigits [] = "0123456789ABCDEF";

   int  status,
        BufferLength;
   unsigned long  BufferHash [4];
   char  *aptr, *cptr, *sptr, *zptr,
         *BufferPtr,
         *FilePtr,
         *HexPtr;
   REQUEST_STRUCT  *rqptr;

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

   rqptr = mtaptr->RequestPtr;

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
                 "DavMetaName() !&Z !&C", FileSpec, ReadWrite);

   if (ReadWrite == 'r')
      zptr = (sptr = mtaptr->ReadMetaName) + sizeof(mtaptr->ReadMetaName);
   else
   if (ReadWrite == 'w')
      zptr = (sptr = mtaptr->WriteMetaName) + sizeof(mtaptr->WriteMetaName);
   else
      ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
   zptr -= 3;

   BufferPtr = FilePtr = sptr;
   mtaptr->MetaDirNamePtr = NULL;
   mtaptr->MetaDirNameLength = 0;
   HexPtr = NULL;

   if (mtaptr->TaskPtr->MetaFileDirPtr &&
       SAME2(mtaptr->TaskPtr->MetaFileDirPtr,'[.'))
   {
      /****************/
      /* subdirectory */
      /****************/

      /* subdirectory of file directory (e.g. "[.^.DAV]", "[.WHATEVER]") */
      for (cptr = FileSpec;
           *cptr && (*cptr != ']' || SAME2(cptr-1,'^]') || SAME2(cptr,']['))
                 && sptr < zptr;
           cptr++)
      {
         if (SAME2(cptr,']['))
            cptr++;
         else
            *sptr++ = to_lower(*cptr);
      }

      if (*cptr == ']')
      {
         /* if the file type is ".DIR" then turn it into an effective ']' */
         for (aptr = cptr; *aptr; aptr++);
         while (aptr > cptr && (*aptr != '.' || SAME2(aptr-1,'^.'))) aptr--;
         if (*aptr == '.'  &&
             (MATCH5(aptr,".DIR;") || MATCH5(aptr,".DIR") ||
              MATCH5(aptr,".dir;") || MATCH5(aptr,".dir")))
         {
            /* insert the file name as part of the directory structure */
            if (sptr < zptr) *sptr++ = '.';
            for (cptr++; cptr < aptr && sptr < zptr; *sptr++ = *cptr++);
            /* no (file) name anymore */
            cptr = "";
         }

         /* insert the metadata subdirectory */
         for (aptr = mtaptr->TaskPtr->MetaFileDirPtr+1;
              *aptr && sptr < zptr;
              *sptr++ = to_lower(*aptr++));
         mtaptr->MetaDirNamePtr = BufferPtr;
         mtaptr->MetaDirNameLength = sptr - BufferPtr;

         /* append the file name and type */
         for (cptr++; *cptr; *sptr++ = to_lower(*cptr++));
      }

   }
   else
   if (mtaptr->TaskPtr->MetaFileDirPtr)
   {
      /******************/
      /* meta directory */
      /******************/

      /* independent metadata directory (e.g. "DKA0:[WASDAV_META]" */
      for (cptr = mtaptr->TaskPtr->MetaFileDirPtr;
           *cptr && sptr < zptr;
           *sptr++ = to_lower(*cptr++));
      *(sptr-1) = '.';
      HexPtr = sptr;
      *sptr++ = '0';
      *sptr++ = '0';
      *sptr++ = ']';
      mtaptr->MetaDirNamePtr = BufferPtr;
      mtaptr->MetaDirNameLength = sptr - BufferPtr;
      /* hash is generated excluding the metadata directory */
      FilePtr = sptr;
      for (cptr = FileSpec; *cptr && sptr < zptr; *sptr++ = to_lower(*cptr++))
      {
         if ((*cptr == ':' && !SAME2(cptr-1,'^:')) ||
             (*cptr == '[' && !SAME2(cptr-1,'^[')) ||
             (*cptr == ']' && !SAME2(cptr-1,'^]')) ||
             (*cptr == '.' && !SAME2(cptr-1,'^.')))
         {
            *sptr++ = '^';
            if (*cptr == ']' && *(cptr+1) != '[') break;
         }
      }
      while (*cptr && sptr < zptr) *sptr++ = to_lower(*cptr++);
   }
   else
   {
      /******************/
      /* same directory */
      /******************/

      /* same directory as resource */
      for (cptr = FileSpec; *cptr && sptr < zptr; *sptr++ = to_lower(*cptr++));
   }
   *sptr = '\0';

   /**********************/
   /* check ODS extended */
   /**********************/

   for (cptr = BufferPtr; *cptr && *cptr != '^'; cptr++);
   /* if uses extended syntax */
   if (*cptr)
      if (DavMetaOds (BufferPtr) != 5)
      {
         /* disable the use of this meta-data file name */
         *BufferPtr = '*';
         ErrorNoticed (rqptr, SS$_NOTF11ODS5, FileSpec, FI_LI);
      }

   /************/
   /* sentinal */
   /************/

   while (sptr > BufferPtr &&
          (*sptr != ';' || SAME2(sptr-1,'^;')) &&
          (*sptr != '.' || SAME2(sptr-1,'^.')) &&
          (*sptr != ']' || SAME2(sptr-1,'^]'))) sptr--;
   if (*sptr == ']')
   {
      while (*sptr) sptr++;
      if (*(sptr-1) != ']' && !SAME2(sptr-2,'^]') && sptr < zptr)
         *sptr++ = '.';
   }
   else
   if (*sptr != ';')
      while (*sptr) sptr++;
   *sptr = '\0';

   if (*(sptr-1) == ']')
   {
      /* special case - meta-data for a directory - no file name plus this */
      for (cptr = ".DIR__wasdav"; *cptr && sptr < zptr; *sptr++ = *cptr++);
      mtaptr->MetaForDir = true;
   }
   else
   {
      if (!mtaptr->TaskPtr->MetaFileDirPtr ||
          !mtaptr->TaskPtr->MetaFileDirPtr[0])
      {
         /* in the subdirectory not the current directory */
         if (sptr > BufferPtr-4 && (MATCH5(sptr-4,".DIR") ||
                                    MATCH5(sptr-4,".dir")))
         {
            sptr -= 4;
            *sptr-- = ']';
            while (sptr > BufferPtr && *sptr != ']') sptr--;
            *sptr++ = '.';
            while (*sptr != ']') sptr++;
            sptr++;
            for (cptr = ".dir"; *cptr && sptr < zptr; *sptr++ = *cptr++);
         }
      }
      /* append the WASD WebDAV sentinal to the current file type */
      for (cptr = "__wasdav"; *cptr && sptr < zptr; *sptr++ = *cptr++);
   }
   *sptr = '\0';
   BufferLength = sptr - BufferPtr;

   if (sptr >= zptr)
   {
      /* disable the use of this meta-data file name */
      *BufferPtr = '*';
      ErrorNoticed (rqptr, SS$_BUFFEROVF, FileSpec, FI_LI);
   }

   /******************/
   /* digest for DLM */
   /******************/

   Md5Digest (FilePtr, BufferLength-(FilePtr-BufferPtr), BufferHash);

   if (HexPtr)
   {
      /* seamless expansion from 16 to 256 subdirectories (!) if necessary */
      /** HexPtr[0] = HexDigits[BufferHash[0] & 0xf0]; **/
      HexPtr[1] = HexDigits[BufferHash[0] & 0x0f];
   }

   if (ReadWrite == 'r')
   {
      mtaptr->ReadMetaNameLength = BufferLength;
      mtaptr->ReadHash[0] = BufferHash[0];
      mtaptr->ReadHash[1] = BufferHash[1];
      mtaptr->ReadHash[2] = BufferHash[2];
      mtaptr->ReadHash[3] = BufferHash[3];
   }
   else
   {
      mtaptr->WriteMetaNameLength = BufferLength;
      mtaptr->WriteHash[0] = BufferHash[0];
      mtaptr->WriteHash[1] = BufferHash[1];
      mtaptr->WriteHash[2] = BufferHash[2];
      mtaptr->WriteHash[3] = BufferHash[3];
   }

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "!&Z !8XL!8XL!8XL!8XL",
                 BufferPtr, BufferHash[0], BufferHash[1],
                            BufferHash[2], BufferHash[3]);
}

/*****************************************************************************/
/*
Cache the ODS for individual device meta data.
The ODS integer (2 or 5) is stored as the first byte preceding the string.
*/

int DavMetaOds (char *FileSpec)

{
   static int  VolOdsCount;
   static char  VolOds [WEBDAV_META_VOLODS][65];

   int  idx, ods;
   char  *cptr, *sptr, *zptr;

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

#if WATCH_MOD
   WatchThis (WATCHALL, WATCH_MOD_WEBDAV, "DavMetaOds() !&Z", FileSpec);
#endif

   for (idx = 0; idx < VolOdsCount; idx++)
   {
       cptr = FileSpec;
       sptr = VolOds[idx];
       ods = *sptr++;
       while (*cptr && *cptr != ':' && *sptr && *sptr != ':')
       {
          if (*cptr != *sptr) break;
          cptr++;
          sptr++;
       }
#if WATCH_MOD
       WatchThis (WATCHALL, WATCH_MOD_WEBDAV,
                  "[!UL] !UL !&Z", idx, (uchar)VolOds[idx][0], VolOds[idx]+1);
#endif
       /* if hit */
       if (*cptr == *sptr) return (ods);
   }

   sys$setprv (1, &SysPrvMask, 0, 0);
   ods = OdsVolumeStructure (FileSpec);
   sys$setprv (0, &SysPrvMask, 0, 0);

   if (VolOdsCount >= WEBDAV_META_VOLODS)
   {
      VolOdsCount = 0;
      ErrorNoticed (NULL, 0, "VolOds[] overflow", FI_LI); 
   }
   idx = VolOdsCount++;
   zptr = (sptr = VolOds[idx]) + sizeof(VolOds[idx])-1;
   *sptr++ = ods;
   for (cptr = FileSpec;
        *cptr && *cptr != ':' && sptr < zptr;
        *sptr++ = *cptr++);
   *sptr++ = ':';
   *sptr = '\0';

#if WATCH_MOD
    WatchThis (WATCHALL, WATCH_MOD_WEBDAV,
               "[!UL] !UL !&Z", idx, (uchar)VolOds[idx][0], VolOds[idx]+1);
#endif

   return (ods);
}

/*****************************************************************************/
/*
Attempt to read a meta-data file corresponding to the file name provided in the
parameter.  We're not sure whether such a meta-data file exists at this stage
so it might just end up a file-not-found at the AST.
*/

DavMetaRead
(
REQUEST_STRUCT *rqptr,
WEBDAV_META *mtaptr,
char *FileSpec,
REQUEST_AST AstFunction,
unsigned long AstParam
)
{
   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
                 "DavMetaRead() !&Z", FileSpec);

   InstanceGblSecIncrLong (&AccountingPtr->WebDavMetaReadAttemptCount);

   /* dispose of dynamic buffers from any previous call */
   StrDscEnd (&mtaptr->ReadDsc, FI_LI);

   memset (mtaptr, 0, sizeof(WEBDAV_META));

   mtaptr->RequestPtr = rqptr;
   mtaptr->TaskPtr = rqptr->WebDavTaskPtr;

   DavMetaName (mtaptr, FileSpec, 'r'); 

   mtaptr->UpdateLocked = false;
   mtaptr->ReadAstFunction = AstFunction;
   mtaptr->ReadAstParam = AstParam;

   DavWebDlmEnqueue (rqptr, &mtaptr->DlmData, mtaptr->ReadMetaName,
                     &mtaptr->ReadHash, false, false, DavMetaReadOpen, mtaptr);
}

/*****************************************************************************/
/*
AST from VMS lock enqueue attempt.
*/

DavMetaReadOpen (WEBDAV_META *mtaptr)

{
   REQUEST_STRUCT  *rqptr;

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

   rqptr = mtaptr->RequestPtr;

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavMetaReadOpen() !UL !&Z",
                 mtaptr->ReadMetaNameLength, mtaptr->ReadMetaName);

   mtaptr->VmsStatus = mtaptr->DlmData.LockSb.lksb$w_status;
   if (VMSnok (mtaptr->VmsStatus))
   {
      /* VMS DLM enqueue failed, explicitly call any AST */
      if (WATCHING (rqptr, WATCH_WEBDAV))
         WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "META read !AZ !&S",
                    mtaptr->ReadMetaName, mtaptr->VmsStatus);
      SysDclAst (mtaptr->ReadAstFunction, mtaptr->ReadAstParam);
      return;
   }

   /* turn on SYSPRV to allow access to the file */
   sys$setprv (1, &SysPrvMask, 0, 0);

   OdsOpen (&mtaptr->ReadOds,
            mtaptr->ReadMetaName, mtaptr->ReadMetaNameLength, NULL, 0,
            FAB$M_GET + FAB$M_BIO, 0, FAB$M_SHRGET,
            &DavMetaOpenAst, mtaptr);

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

/*****************************************************************************/
/*
Set up the meta-data creation attributes then queue an exclusive VMS LDM lock
on the meta-data hash.  When granted this VMS DLM lock *queues* and blocks all
subsequent requests to modify the meta-data (and hence represented resource)
for the duration of the VMS DLM lock.
*/

DavMetaLock
(
REQUEST_STRUCT *rqptr,
WEBDAV_META *mtaptr,
char *FileSpec,
REQUEST_AST AstFunction,
unsigned long AstParam
)
{
   /*********/
   /* begin */
   /*********/

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
                 "DavMetaLock() !&Z", FileSpec);

   InstanceGblSecIncrLong (&AccountingPtr->WebDavMetaReadAttemptCount);

   /* dispose of dynamic buffers from any previous call */
   StrDscEnd (&mtaptr->ReadDsc, FI_LI);

   memset (mtaptr, 0, sizeof(WEBDAV_META));

   mtaptr->RequestPtr = rqptr;
   mtaptr->TaskPtr = rqptr->WebDavTaskPtr;

   DavMetaName (mtaptr, FileSpec, 'r'); 

   mtaptr->UpdateLocked = true;
   mtaptr->ReadAstFunction = AstFunction;
   mtaptr->ReadAstParam = AstParam;

   DavWebDlmEnqueue (rqptr, &mtaptr->DlmData, mtaptr->ReadMetaName,
                     &mtaptr->ReadHash, true, false, DavMetaLockAst, mtaptr);
}

/*****************************************************************************/
/*
Check the VMS DLM status value for success or failure.  If failed then just
queue the AST with the status code set. For success, open a file for exclusive
access.  Create it if it does not already exist. We're not sure whether such a
meta-data file exists at this stage so it might just end up a file-not-found at
the AST.
*/

DavMetaLockAst (WEBDAV_META *mtaptr)

{
   REQUEST_STRUCT  *rqptr;
   WEBDAV_DLM  *dlmptr;

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

   rqptr = mtaptr->RequestPtr;

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavMetaLockAst()");

   dlmptr = &mtaptr->DlmData;

   mtaptr->VmsStatus = dlmptr->LockSb.lksb$w_status;
   if (VMSnok (mtaptr->VmsStatus))
   {
      /* VMS DLM enqueue failed, explicitly call any AST */
      if (WATCHING (rqptr, WATCH_WEBDAV))
         WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "META read !AZ !&S",
                    mtaptr->ReadMetaName, mtaptr->VmsStatus);
      SysDclAst (mtaptr->ReadAstFunction, mtaptr->ReadAstParam);
      return;
   }

   /* turn on SYSPRV to allow access to the file */
   sys$setprv (1, &SysPrvMask, 0, 0);

   OdsCreate (&mtaptr->ReadOds,
              mtaptr->ReadMetaName, mtaptr->ReadMetaNameLength, NULL, 0,
              FAB$M_PUT + FAB$M_GET + FAB$M_BIO + FAB$M_TRN,
              FAB$M_CIF + FAB$M_TEF,
              FAB$M_NIL, FAB$C_STMLF, FAB$M_CR, NULL,
              &DavMetaOpenAst, mtaptr);

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

/*****************************************************************************/
/*
If the open fails (for any reason, including file-not-found) explicitly call
the AST function.  If the open succeeds allocate a dynamic buffer the same
size of the file and $READ that as blocks (if larger than a possible single
$READ) from the file call DavMetaReadAst() when complete.
*/

DavMetaOpenAst (WEBDAV_META *mtaptr)

{
   int  status;
   REQUEST_STRUCT  *rqptr;
   WEBDAV_DLM  *dlmptr;
   WEBDAV_TASK  *tkptr;

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

   rqptr = mtaptr->RequestPtr;

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
                 "DavMetaOpenAst() !&S", mtaptr->ReadOds.Fab.fab$l_sts);

   tkptr = rqptr->WebDavTaskPtr;
   dlmptr = &mtaptr->DlmData;

   if (VMSok (mtaptr->VmsStatus = mtaptr->ReadOds.Fab.fab$l_sts) &&
       (mtaptr->ReadOds.Fab.fab$b_rfm == FAB$C_VAR ||
        mtaptr->ReadOds.Fab.fab$b_rfm == FAB$C_VFC))
   {
      if (WATCHING (rqptr, WATCH_WEBDAV))
         WatchThis (WATCHITM(rqptr), WATCH_WEBDAV,
                    "META ***** MUST BE STREAM-LF FORMAT!! *****");
      mtaptr->VmsStatus = SS$_ABORT;
   }

   if (VMSnok(mtaptr->VmsStatus))
   {
      if (mtaptr->MetaDirNameLength &&
          mtaptr->VmsStatus == RMS$_DNF &&
          /* meta-file directory created only when opened for write */
          (mtaptr->ReadOds.Fab.fab$b_fac & FAB$M_PUT) &&
          !mtaptr->MetaDirCreate)
      {
         DavMetaCreateDir (mtaptr);
         /* retry the access (just the once) */
         mtaptr->MetaDirCreate = true;
         DavMetaLockAst (mtaptr);
         return;
      }

      /* open failed, explicitly call any AST */
      if (mtaptr->VmsStatus != RMS$_FNF || mtaptr->VmsStatus != RMS$_DNF)
         if (WATCHING (rqptr, WATCH_WEBDAV))
            WatchThis (WATCHITM(rqptr), WATCH_WEBDAV,
                       "META read open !AZ !&S",
                       mtaptr->ReadMetaName,
                       mtaptr->VmsStatus);

      mtaptr->UpdateLocked = false;
      DavWebDequeue (&mtaptr->DlmData);
      SysDclAst (mtaptr->ReadAstFunction, mtaptr->ReadAstParam);
      return;
   }

   mtaptr->ReadOds.Rab = cc$rms_rab;
   mtaptr->ReadOds.Rab.rab$l_fab = &mtaptr->ReadOds.Fab;

   status = sys$connect (&mtaptr->ReadOds.Rab, 0, 0);
   if (VMSnok (status))
      ErrorNoticed (rqptr, status, mtaptr->ReadOds.ExpFileName, FI_LI); 

   mtaptr->ReadOds.Rab.rab$l_ctx = mtaptr;
   mtaptr->ReadOds.Rab.rab$l_sts = mtaptr->ReadOds.Rab.rab$l_stv = 0;

   if (mtaptr->VmsStatus == RMS$_CREATED)
   {
      /* if newly created, obviously nothing to read - fake the status */
      mtaptr->MetaCreated = true;
      mtaptr->ReadOds.Rab.rab$l_sts = RMS$_EOF;
      mtaptr->ReadOds.DeleteOnClose = true;
      SysDclAst (mtaptr->ReadAstFunction, mtaptr->ReadAstParam);
      return;
   }

   /* access to the meta data was successful */
   InstanceGblSecIncrLong (&AccountingPtr->WebDavMetaReadCount);

   DavMetaReadAst (&mtaptr->ReadOds.Rab);
}

/*****************************************************************************/
/*
Create the metadata directory using lib$create_dir()!
*/ 
 
int DavMetaCreateDir (WEBDAV_META *mtaptr)

{
   /* ensure SYSPRV can control the directory */
   static unsigned short  ProtEnable = 0x000f,
                          ProtValue = 0x0000;  /* S:RWED */
   static $DESCRIPTOR (DirNameDsc, "");

   int  status;
   char  ch;
   REQUEST_STRUCT  *rqptr;

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

   rqptr = mtaptr->RequestPtr;

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
                 "DavMetaCreateDir() !UL !#AZ",
                 mtaptr->MetaDirNameLength, mtaptr->MetaDirNameLength,
                 mtaptr->MetaDirNamePtr);

   if (!mtaptr->MetaDirNameLength) return (SS$_NORMAL);

   ch = mtaptr->MetaDirNamePtr[mtaptr->MetaDirNameLength];
   mtaptr->MetaDirNamePtr[mtaptr->MetaDirNameLength] = '\0';

   DirNameDsc.dsc$a_pointer = mtaptr->MetaDirNamePtr;
   DirNameDsc.dsc$w_length = mtaptr->MetaDirNameLength;

   AuthAccessEnable (rqptr, DirNameDsc.dsc$a_pointer, AUTH_ACCESS_WRITE);

   status = lib$create_dir (&DirNameDsc, 0, &ProtEnable, &ProtValue, 0, 0);

   AuthAccessEnable (rqptr, 0, 0);

   if (WATCHING (rqptr, WATCH_WEBDAV))
      if (status == SS$_NORMAL)
         WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "META EXISTS !AZ",
                    mtaptr->MetaDirNamePtr);
      else
      if (status == SS$_CREATED)
         WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "META CREATED !AZ",
                    mtaptr->MetaDirNamePtr);
      else
         WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "META FAILED !AZ !&S",
                    mtaptr->MetaDirNamePtr, status);

   mtaptr->MetaDirNamePtr[mtaptr->MetaDirNameLength] = ch;

   return (status);
}

/*****************************************************************************/
/*
Delete a metadata directory (allowed only if it is empty of course!)
It is intended to be used during WebDAV DELETE operations.  When deleting a
directory file this function should be called to proactively delete any
[WebDavMetaDir] specified metadata directory file prior to trying to delete the
directory file itself.
*/ 
 
int DavMetaDeleteDir (WEBDAV_META *mtaptr)

{
   int  status,
        DirFileNameLength;
   char  ch;
   char  DirFileName [ODS_MAX_FILE_NAME_LENGTH+1];
   REQUEST_STRUCT  *rqptr;
   ODS_STRUCT  DeleteOds;

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

   rqptr = mtaptr->RequestPtr;

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
                 "DavMetaDeleteDir() !UL !#AZ",
                 mtaptr->MetaDirNameLength, mtaptr->MetaDirNameLength,
                 mtaptr->MetaDirNamePtr);

   if (!mtaptr->MetaDirNameLength) return (SS$_NORMAL);

   ch = mtaptr->MetaDirNamePtr[mtaptr->MetaDirNameLength];
   mtaptr->MetaDirNamePtr[mtaptr->MetaDirNameLength] = '\0';

   OdsNameOfDirectoryFile (mtaptr->MetaDirNamePtr,
                           mtaptr->MetaDirNameLength,
                           DirFileName, &DirFileNameLength);

   mtaptr->MetaDirNamePtr[mtaptr->MetaDirNameLength] = ch;

   OdsStructInit (&DeleteOds, true);
   OdsParse (&DeleteOds, DirFileName, DirFileNameLength,
             NULL, 0, NAM$M_SYNCHK, NULL, rqptr);
 
   /* only if it is a metadata subdirectory */
   if (DavMetaDir (rqptr, &DeleteOds))
   {
      if (VMSok (status = DeleteOds.Fab.fab$l_sts))
      {
         DeleteOds.NamVersionPtr[0] = '\0'; 

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

         status = sys$erase (&DeleteOds.Fab, 0, 0);

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

         if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
            WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
                       "sys$erase() !&Z !&S", DeleteOds.ExpFileName, status);

         if (VMSnok (status) &&
             status != RMS$_MKD &&
             status != SS$_DIRNOTEMPTY) 
            ErrorNoticed (rqptr, status, DeleteOds.ExpFileName, FI_LI); 
      }
   }

   OdsParseRelease (&DeleteOds);

   return (status);
}

/*****************************************************************************/
/*
The asynchronous $READ has completed.  Check the status and the proportion of
the file read.  If another $READ is required then queue that.  Once the file
has been completely read, or at an error status, call the AST function.
*/

DavMetaReadAst (struct RAB *RabPtr)

{
   int  status,
        BucketNumber,
        SizeInBytes;
   REQUEST_STRUCT *rqptr;
   WEBDAV_LOCK  *lckptr;
   WEBDAV_META  *mtaptr;
   WEBDAV_TASK  *tkptr;

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

   mtaptr = RabPtr->rab$l_ctx;
   rqptr = mtaptr->RequestPtr;

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
                 "DavMetaReadAst() sts:!&X stv:!&X rsz:!UL",
                 RabPtr->rab$l_sts, RabPtr->rab$l_stv, RabPtr->rab$w_rsz);

   tkptr = rqptr->WebDavTaskPtr;

   if (mtaptr->ReadOds.Rab.rab$l_sts)
   {
      /*******************/
      /* subsequent read */
      /*******************/

      mtaptr->VmsStatus = mtaptr->ReadOds.Rab.rab$l_sts;
      if (VMSok (mtaptr->VmsStatus))
      {
         STR_DSC_LEN(&mtaptr->ReadDsc) += RabPtr->rab$w_rsz;
         /* artifically generate an EOF as we've got all there is to get */
         if (STR_DSC_LEN(&mtaptr->ReadDsc) >= STR_DSC_SIZE(&mtaptr->ReadDsc))
            mtaptr->VmsStatus = RMS$_EOF;
      }
   }
   else
   {
      /****************/
      /* initial read */
      /****************/

      if (mtaptr->ReadOds.XabFhc.xab$l_ebk <= 1)
         SizeInBytes = mtaptr->ReadOds.XabFhc.xab$w_ffb;
      else
         SizeInBytes = ((mtaptr->ReadOds.XabFhc.xab$l_ebk-1) << 9) +
                       mtaptr->ReadOds.XabFhc.xab$w_ffb;

      /* unlikely to be legitimate meta-data if less than this */
      if (SizeInBytes < 128)
      {
         /* can happen if the server exits without closing RMS$M_DLT files */
         mtaptr->VmsStatus = SS$_ENDOFFILE;
      }
      else
      {
         StrDscBegin (rqptr, &mtaptr->ReadDsc, SizeInBytes);

         /* asynchronous block $READs */
         mtaptr->ReadOds.Rab.rab$l_rop = RAB$M_BIO | RAB$M_ASY;

         mtaptr->VmsStatus = SS$_NORMAL;
      }
   }

   if (VMSok (mtaptr->VmsStatus))
   {
      /*************/
      /* read more */
      /*************/

      SizeInBytes = STR_DSC_SIZE(&mtaptr->ReadDsc) -
                    STR_DSC_LEN(&mtaptr->ReadDsc);
      if (SizeInBytes > DAVMETA_READ_CHUNK) SizeInBytes = DAVMETA_READ_CHUNK;
      BucketNumber = (STR_DSC_LEN(&mtaptr->ReadDsc) / 512) + 1;

      if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
         WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "!UL-!UL=!UL !UL !UL",
                    STR_DSC_SIZE(&mtaptr->ReadDsc),
                    STR_DSC_LEN(&mtaptr->ReadDsc),
                    STR_DSC_SIZE(&mtaptr->ReadDsc) -
                       STR_DSC_LEN(&mtaptr->ReadDsc),
                    BucketNumber, SizeInBytes);

      mtaptr->ReadOds.Rab.rab$l_ubf = STR_DSC_PTR(&mtaptr->ReadDsc) +
                                      STR_DSC_LEN(&mtaptr->ReadDsc);
      mtaptr->ReadOds.Rab.rab$w_usz = SizeInBytes;
      mtaptr->ReadOds.Rab.rab$l_bkt = BucketNumber;

      sys$read (&mtaptr->ReadOds.Rab, &DavMetaReadAst, &DavMetaReadAst);
      return;
   }
   
   /*****************/
   /* read finished */
   /*****************/

   /* if we retrieved the entire file then that's a good thing! */
   if (mtaptr->VmsStatus == RMS$_EOF) mtaptr->VmsStatus = SS$_NORMAL;

   if (WATCHING (rqptr, WATCH_WEBDAV))
   {
      WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "META read !AZ !&S",
                 mtaptr->ReadOds.ExpFileName, mtaptr->VmsStatus);
      if (VMSok(mtaptr->VmsStatus))
         if (STR_DSC_LEN(&mtaptr->ReadDsc))
            WatchDataFormatted ("!#AZ", STR_DSC_LEN(&mtaptr->ReadDsc),
                                        STR_DSC_PTR(&mtaptr->ReadDsc));
   }

   if (VMSnok (mtaptr->VmsStatus))
   {
      /*******/
      /* nbg */
      /*******/

      /* a fatal error so ensure the metadata file is deleted */
      mtaptr->ReadOds.DeleteOnClose = true;

      status = OdsClose (&mtaptr->ReadOds, NULL, NULL);
   }
   else
   {
      /*********/
      /* parse */
      /*********/

      if (!mtaptr->UpdateLocked)
      {
         /* do NOT modify any meta newly SS$_CREATED setting */
         status = OdsClose (&mtaptr->ReadOds, NULL, NULL);
         if (VMSnok (status))
            ErrorNoticed (rqptr, status, mtaptr->ReadMetaName, FI_LI); 
      }

      mtaptr->VmsStatus = DavMetaParseXml (mtaptr);

      if (VMSok (mtaptr->VmsStatus)) DavLockPreProcess (rqptr);
   }

   if (!mtaptr->UpdateLocked && !mtaptr->CopyLocked)
      DavWebDequeue (&mtaptr->DlmData);

   SysDclAst (mtaptr->ReadAstFunction, mtaptr->ReadAstParam);
}

/*****************************************************************************/
/*
The file has been opened for write exclusively locked by DavMetaRead().  If it
was an existing meta-data file the content can be updated or if it was created
the content can be 'updated' for the first time.  If there are no properties
data to be written to the file then flag it for delete-on-close and close the
file, otherwise build a buffer of the properties data and queue an asynchronous
$WRITE.
*/

DavMetaUpdate
(
WEBDAV_META *mtaptr,
REQUEST_AST AstFunction,
unsigned long AstParam
)
{
   int  status,
        MaxKbytes,
        PropCount,
        SizeInBytes;
   STR_DSC  *sdptr;
   REQUEST_STRUCT  *rqptr;
   WEBDAV_LOCK  *lckptr;
   WEBDAV_TASK  *tkptr;

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

   rqptr = mtaptr->RequestPtr;

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
                 "DavMetaUpdate() !&S", mtaptr->VmsStatus);

   tkptr = rqptr->WebDavTaskPtr;

   mtaptr->UpdateAstFunction = AstFunction;
   mtaptr->UpdateAstParam = AstParam;

   if (VMSnok (mtaptr->VmsStatus)) goto DavMetaUpdateAbort;

   lckptr = NULL;

   if (rqptr->rqHeader.Method == HTTP_METHOD_WEBDAV_LOCK)
   {
      /********/
      /* lock */
      /********/

      /* if the request didn't supply complete lock data */
      if (!(tkptr->LockData.Type && tkptr->LockData.Scope))
      {
         DavWebResponse (rqptr, 412, 0, "LOCK type/scope undefined", FI_LI);
         goto DavMetaUpdateAbort;
      }

      /* search for an existing exclusive lock that matches an If: token */
      if (lckptr = DavLockFindIf (rqptr, WEBDAV_LOCK_SCOPE_EXCLUSIVE))
         if (lckptr == NOTPTR)
            goto DavMetaUpdateAbort;

      /* if DavLockFindIf() returned lock then it's an update */
      if (lckptr)
      {
         /* check existing lock compatibility */
         if (!(lckptr->Type == WEBDAV_LOCK_SCOPE_SHARED &&
               tkptr->LockData.Type == WEBDAV_LOCK_SCOPE_SHARED))
         {
             DavWebResponse (rqptr, 412, 0, "LOCKS incomapatible", FI_LI);
             goto DavMetaUpdateAbort;
         }
      }
      else
      {
          /* new lock - add it to the list and generate a token */
          ListAddHead (&mtaptr->LockList, lckptr = &tkptr->LockData,
                       LIST_ENTRY_TYPE_DAVLOCK);
          DavLockSetUrnUuidToken (rqptr, lckptr);
          mtaptr->LockCreated = true;
      }

      /* set or update (existing lock) the timeout value */
      if (DavLockSetTimeout (rqptr, lckptr)) goto DavMetaUpdateAbort;
   }
   else
   if (rqptr->rqHeader.Method == HTTP_METHOD_WEBDAV_UNLOCK)
   {
      /**********/
      /* unlock */
      /**********/

      /* must be an existing lock */
      lckptr = DavLockFindToken (rqptr);
      if (!lckptr || lckptr == NOTPTR) goto DavMetaUpdateAbort;

      /* simply remove the lock from the list */
      ListRemove (&mtaptr->LockList, lckptr);
   }
   else
   {
      /******************/
      /* something else */
      /******************/

      if (!mtaptr->MetaCreated)
      {
         /* if existing metadata */
         if (lckptr = DavLockFindIf (rqptr, WEBDAV_LOCK_SCOPE_EXCLUSIVE))
            if (lckptr == NOTPTR)
               {
                  mtaptr->VmsStatus = SS$_ACCONFLICT;
                  goto DavMetaUpdateAbort;
               }

         /* if didn't return an exclusive lock look for a shared one */
         if (!lckptr)
            if (lckptr = DavLockFindIf (rqptr, WEBDAV_LOCK_SCOPE_SHARED))
               if (lckptr == NOTPTR)
               {
                  mtaptr->VmsStatus = SS$_ACCONFLICT;
                  goto DavMetaUpdateAbort;
               }
      }
   }

   /*******************/
   /* build meta-data */
   /*******************/

   status = DavMetaBuild (mtaptr);

   if (status == SS$_INCOMPAT)
   {
      /**********************/
      /* forbidden metadata */
      /**********************/

      DavWebResponse (rqptr, 403, 0, "cannot patch property", FI_LI);
      tkptr->MetaData.VmsStatus = status;
      goto DavMetaUpdateAbort;
   }

   if (VMSnok(status) || status == SS$_FORGET)
   {
      /********************/
      /* delete meta-data */
      /********************/

      tkptr->MetaData.VmsStatus = status;
      mtaptr->ReadOds.DeleteOnClose = true;
      goto DavMetaUpdateAbort;
   }

   /*******************/
   /* limit meta size */
   /*******************/

   /* to something reasonable (say 1/32 of the maximum PUTable) */
   SizeInBytes = 0;
   for (sdptr = &mtaptr->WriteDsc; sdptr; sdptr = STR_DSC_NEXT(sdptr))
       SizeInBytes += STR_DSC_LEN(sdptr);

   if (!(MaxKbytes = rqptr->rqPathSet.PutMaxKbytes))
      MaxKbytes = Config.cfMisc.PutMaxKbytes;

   if ((SizeInBytes >> 10) > (MaxKbytes >> 5))
   {    
      DavWebResponse (rqptr, 412, 0, "META too large", FI_LI);
      mtaptr->ReadOds.DeleteOnClose = true;
      goto DavMetaUpdateAbort;
   }

   /********************/
   /* set up the write */
   /********************/

   mtaptr->ReadOds.Rab.rab$l_ctx = mtaptr;
   mtaptr->ReadOds.Rab.rab$l_sts = mtaptr->ReadOds.Rab.rab$l_stv = 0;

   DavMetaUpdateWrite (&mtaptr->ReadOds.Rab);

   return;

   DavMetaUpdateAbort:
   {
      /********************/
      /* abort the update */
      /********************/

      if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
         WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
                    "DavMetaUpdateAbort() !&B !&S",
                    mtaptr->ReadOds.DeleteOnClose, mtaptr->VmsStatus);

      if (mtaptr->VmsStatus != SS$_FORGET &&
          mtaptr->VmsStatus != SS$_INCOMPAT &&
          mtaptr->VmsStatus != SS$_ACCONFLICT) mtaptr->VmsStatus = SS$_ABORT;

      status = OdsClose (&mtaptr->ReadOds, NULL, NULL);
      if (VMSnok (status))
         ErrorNoticed (rqptr, status, mtaptr->ReadMetaName, FI_LI); 

      DavWebDequeue (&mtaptr->DlmData);

      SysDclAst (mtaptr->UpdateAstFunction, mtaptr->UpdateAstParam);
   }
}

/*****************************************************************************/
/*
This function queues $WRITEs of the meta-data and acts as it's own AST,
assessing the status of the previous write and queuing then next, if required.
When the write has completed (successfully or unsuccessfully) the
'MetaUpdateAstFunction' is called which should (as always) check 'MetaStatus'.
*/

DavMetaUpdateWrite (struct RAB *RabPtr)

{
   int  status;
   REQUEST_STRUCT *rqptr;
   STR_DSC  *sdptr;
   WEBDAV_META  *mtaptr;
   WEBDAV_TASK  *tkptr;

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

   mtaptr = RabPtr->rab$l_ctx;
   rqptr = mtaptr->RequestPtr;

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
                 "DavMetaUpdateWrite() sts:!&S stv:!&S",
                 RabPtr->rab$l_sts, RabPtr->rab$l_stv);

   tkptr = rqptr->WebDavTaskPtr;

   if (mtaptr->ReadOds.Rab.rab$l_sts)
   {
      /********************/
      /* subsequent write */
      /********************/

      status = mtaptr->VmsStatus = mtaptr->ReadOds.Rab.rab$l_sts;
      if (VMSnok (status))
      {
         if (WATCHING (rqptr, WATCH_WEBDAV))
            WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "META update !AZ !&S",
                       mtaptr->ReadMetaName, status);

         ErrorNoticed (rqptr, status, mtaptr->ReadMetaName, FI_LI); 

         /* a fatal error so ensure the metadata file is deleted */
         mtaptr->ReadOds.DeleteOnClose = true;

         status = OdsClose (&mtaptr->ReadOds, NULL, NULL);
         if (VMSnok (status))
            ErrorNoticed (rqptr, status, mtaptr->ReadMetaName, FI_LI); 

         DavWebDequeue (&mtaptr->DlmData);

         SysDclAst (mtaptr->UpdateAstFunction, mtaptr->UpdateAstParam);
         return;
      }
   }
   else
   {
      /*****************/
      /* initial write */
      /*****************/

      InstanceGblSecIncrLong (&AccountingPtr->WebDavMetaWriteAttemptCount);

      /* check the data is acceptable for $WRITE */
      for (sdptr = &mtaptr->WriteDsc; sdptr; sdptr = STR_DSC_NEXT(sdptr))
      {
         /* all but the last descriptor must be a full block */
         if (STR_DSC_LEN(sdptr) > 0xffff ||
             (STR_DSC_NEXT(sdptr) && STR_DSC_LEN(sdptr) % 512))
         {
            mtaptr->VmsStatus = SS$_BUGCHECK;
            ErrorNoticed (rqptr, SS$_BUGCHECK, ErrorSanityCheck, FI_LI); 

            /* a fatal error so ensure the metadata file is deleted */
            mtaptr->ReadOds.DeleteOnClose = true;

            status = OdsClose (&mtaptr->ReadOds, NULL, NULL);
            if (VMSnok (status))
               ErrorNoticed (rqptr, status, mtaptr->ReadMetaName, FI_LI); 

            DavWebDequeue (&mtaptr->DlmData);

            SysDclAst (mtaptr->UpdateAstFunction, mtaptr->UpdateAstParam);
            return;
         }
      }

      mtaptr->RabBucket = 1;
      mtaptr->ReadOds.Rab.rab$l_rop = RAB$M_BIO | RAB$M_ASY | RAB$M_TPT;
   }

   /* find the next descriptor to be written */
   for (sdptr = &mtaptr->WriteDsc; sdptr; sdptr = STR_DSC_NEXT(sdptr))
      if (!STR_DSC_IS_NOTED(sdptr)) break;

   if (!sdptr)
   {
      /***************/
      /* end of data */
      /***************/

      if (WATCHING (rqptr, WATCH_WEBDAV))
      {
         WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "META update !AZ !&S",
                    mtaptr->ReadOds.ExpFileName, SS$_NORMAL);
         if (STR_DSC_LEN(&mtaptr->WriteDsc))
            WatchDataFormatted ("!#AZ", STR_DSC_LEN(&mtaptr->WriteDsc),
                                        STR_DSC_PTR(&mtaptr->WriteDsc));
      }

      mtaptr->VmsStatus = SS$_NORMAL;

      /* success so need to undo any meta newly SS$_CREATED */
      mtaptr->ReadOds.DeleteOnClose = false;

      status = OdsClose (&mtaptr->ReadOds, NULL, NULL);

      if (VMSok (status))
         InstanceGblSecIncrLong (&AccountingPtr->WebDavMetaWriteCount);
      else
         ErrorNoticed (rqptr, status, mtaptr->ReadMetaName, FI_LI); 

      DavWebDequeue (&mtaptr->DlmData);

      SysDclAst (mtaptr->UpdateAstFunction, mtaptr->UpdateAstParam);
      return;
   }

   /**************/
   /* write data */
   /**************/

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
                 "rsz: !UL", STR_DSC_LEN(sdptr));

   mtaptr->ReadOds.Rab.rab$l_rbf = STR_DSC_PTR(sdptr);
   mtaptr->ReadOds.Rab.rab$w_rsz = STR_DSC_LEN(sdptr);
   mtaptr->ReadOds.Rab.rab$l_bkt = mtaptr->RabBucket;

   mtaptr->RabBucket += STR_DSC_LEN(sdptr) / 512;
   STR_DSC_SET_NOTED(sdptr)

   sys$write (&mtaptr->ReadOds.Rab, &DavMetaUpdateWrite,
                                    &DavMetaUpdateWrite);
}

/*****************************************************************************/
/*
WASD files containing WebDAV metadata have the same name with "__wasdav"
appended to the file type.  Return true if the supplied file complies.
*/ 

BOOL DavMetaFile (ODS_STRUCT *odsptr)

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

   if (WATCH_MODULE (WATCH_MOD_WEBDAV))
      WatchThis (WATCHALL, WATCH_MOD_WEBDAV,
                 "DavMetaFile() !&Z", odsptr->NamDevicePtr);

   if (odsptr->NamTypeLength < 8) return (false);
   if (MATCH8 (odsptr->NamVersionPtr-8, "__wasdav")) return (true);
   if (MATCH8 (odsptr->NamVersionPtr-8, "__WASDAV")) return (true);
   return (false);
}

/*****************************************************************************/
/*
Return true if this specification is the meta-subdirectory or contains a
metadata subdirectory anywhere in the directory structure, and false if it
doesn't.  If the metadata subdirectory is for example "[.meta]" then match
"[meta]", "[meta.", ".meta]" and "[.meta.", or if "[.^.dav]" then this function
must match file specifications containing "[^.dav]", "[^.dav.", ".^.dav." and
".^.dav]".
*/ 

BOOL DavMetaDir
(
REQUEST_STRUCT *rqptr,
ODS_STRUCT *odsptr
)
{
   char  *cptr, *mfdptr, *sptr, *zptr;
   WEBDAV_TASK  *tkptr;

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

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
                 "DavMetaDir() !&Z", odsptr->NamDevicePtr);

   if (tkptr = rqptr->WebDavTaskPtr)
      mfdptr = tkptr->MetaFileDirPtr;
   else
   if (rqptr->rqPathSet.WebDavMetaDirPtr)
      mfdptr = rqptr->rqPathSet.WebDavMetaDirPtr;
   else
   if (Config.cfWebDav.MetaFileDirectoryLength)
      mfdptr = Config.cfWebDav.MetaFileDirectory;
   else
      return (false);

   if (!mfdptr) return (false);

   if (!SAME2(mfdptr,'[.')) return (false);

   /* first check if it is the directory file itself */
   cptr = odsptr->NamNamePtr;
   sptr = mfdptr + 2;
   if (MATCH2(cptr,sptr))
   {
      while (*cptr && *cptr != '.' && (to_lower(*cptr) == to_lower(*sptr)))
      {
         if (*cptr == '^' && *(cptr+1)) cptr++;
         if (*sptr == '^' && *(sptr+1)) sptr++;
         cptr++;
         sptr++;
      }
      if (*cptr == '.' && *sptr == ']' &&
          (MATCH5(cptr,".DIR;") || MATCH5(cptr,".dir;") ||
           MATCH5(cptr,".DIR") || MATCH5(cptr,".dir")))
      {
         if (WATCHING (rqptr, WATCH_THIS))
            WatchThis (WATCHITM(rqptr), WATCH_WEBDAV,
                       "METADATA !AZ", odsptr->NamNamePtr);
         return (true);
      }
   }

   /* then check if directory name occurs anywhere in the specification */
   for (cptr = odsptr->NamDirectoryPtr; *cptr; cptr++)
   {
      if (*cptr == '^')
      {
         /* absorb escaped characters */
         if (*++cptr) cptr++;
         continue;
      }

      if (*cptr == ']')
      {
         /* if concealed continue otherwise end of directory */
         if (SAME3(cptr,'][.'))
            cptr += 2;
         else
            break;
      }
      else
      if (!(*cptr == '[' || *cptr == '.'))
         /* just another non-significant character */
         continue;

      /* beginning of a directory */
      cptr++;
      sptr = mfdptr + 2;
      while (*cptr && *cptr != '.' && *cptr != ']' &&
             (to_lower(*cptr) == to_lower(*sptr)))
      {
         if (*cptr == '^' && *(cptr+1)) cptr++;
         if (*sptr == '^' && *(sptr+1)) sptr++;
         cptr++;
         sptr++;
      }
      /* if it matched the meta directory */
      if ((*cptr == ']' || *cptr == '.') && *sptr == ']')
      {
         if (WATCHING (rqptr, WATCH_THIS))
            WatchThis (WATCHITM(rqptr), WATCH_WEBDAV,
                       "METADATA !AZ CONTAINS !AZ",
                        odsptr->NamDirectoryPtr, mfdptr);
         return (true);
      }

      /* span to the end of the directory */
      while (*cptr)
      {
         if (*cptr == '^')
         {
            if (*++cptr) cptr++;
            continue;
         }
         if (*(cptr+1) == '.' || *(cptr+1) == ']') break;
         cptr++;
      }
   }

   return (false);
}

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

