/*****************************************************************************/
/*
                                 DAVcopy.c

Also see comments on MOVE in DAVWEB.C.

Implement the COPY method for single resources (files) and collections
(directory trees).

This module is also used by a MOVE of resources between different volumes.

For files a DLM is taken out on the source file specification and any
associated meta-data tested for.  If not locked the file is copied.

For directories (collections) a DLM lock is taken out on the source directory
and target directory specifications, and then any associated META locking
tested.  Locking permitting the source tree is then walked with corresponding
target directories being created and files copied from tree-to-tree.  Source
files are deleted when closed (so the source tree shrinks as the target tree
grows).  When concluded source directory files (.DIRs) remain in the source
tree and are explicitly removed using DELETE functionality.


VERSION HISTORY
---------------
17-OCT-2018  MGD  bugfix; ensure meta directory exists
16-OCT-2018  MGD  bugfix; DavCopyNext() copy meta for single file
13-APR-2018  MGD  SS$_ABORT on ->RequestRundown
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 <ctype.h>
#include <errno.h>
#include <stdio.h>

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

#define WASD_MODULE "DAVCOPY"

#define TEST_DEADLOCK_DETECT 0

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

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

extern char  ErrorSanityCheck[];

extern unsigned long  SysPrvMask[];

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

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

DavCopyBegin (REQUEST_STRUCT *rqptr)

{
   int  status;
   WEBDAV_COPY  *cpyptr;
   WEBDAV_TASK  *tkptr;

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

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

   tkptr = rqptr->WebDavTaskPtr;
   cpyptr = &tkptr->CopyData;

   tkptr->TestLockState = 0;

   status = DavWebDestination (rqptr);
   if (VMSnok (status))
   {
      DavWebResponse (rqptr, 0, status, NULL, FI_LI);
      DavWebEnd (rqptr);
      return;
   }

   if (WATCHING (rqptr, WATCH_RESPONSE))
      WatchThis (WATCHITM(rqptr), WATCH_RESPONSE,
                 "COPY !AZ to !AZ overwrite:!&B depth:!UL path:!AZ",
                 rqptr->ParseOds.ExpFileName, tkptr->DestOds.ExpFileName,
                 tkptr->CopyData.MoveOverwrite, tkptr->ToDepth,
                 DavWebPathAccess(rqptr));

   if (strsame (rqptr->ParseOds.ExpFileName, tkptr->DestOds.ExpFileName, -1))
   {
      /* caseless compare because can't copy to the same underlying name! */
      DavWebResponse (rqptr, 403, 0, "same underlying file name", FI_LI);
      DavWebEnd (rqptr);
      return;
   }

   if (rqptr->ParseOds.NamNameLength ||
       rqptr->ParseOds.NamTypeLength)
   {
      /* copying a file */
      if (tkptr->DestOds.NamNameLength ||
          tkptr->DestOds.NamTypeLength)
      {
         /* to a file */
         DavCopyFile (rqptr);
         return;
      }
      DavWebResponse (rqptr, 400, 0, "file to directory", FI_LI);
   }
   else
   {
      /* copying a directory */
      if (!tkptr->DestOds.NamNameLength &&
          !tkptr->DestOds.NamTypeLength)
      {
         /* to a directory */
         DavCopyDirectory (rqptr);
         return;
      }
      DavWebResponse (rqptr, 400, 0, "directory to file", FI_LI);
   }

   /* file to a directory or directory to a file specification! */
   DavWebEnd (rqptr);
}

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

DavCopyEnd (WEBDAV_COPY *cpyptr)

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

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

   rqptr = cpyptr->RequestPtr;

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

   tkptr = rqptr->WebDavTaskPtr;

   if (cpyptr->CopyingDir)
      if (WATCHING (rqptr, WATCH_WEBDAV))
         WatchThis (WATCHITM(rqptr), WATCH_WEBDAV,
"COPIED dirs:!UL fail:!UL files:!UL fail:!UL meta:!UL fail:!UL",
                    cpyptr->DirCount, cpyptr->DirFailCount,
                    cpyptr->FileCount, cpyptr->FileFailCount,
                    cpyptr->MetaCount, cpyptr->MetaFailCount);

   if (rqptr->rqResponse.HttpStatus == 207)
      FaoToNet (rqptr, "</D:multistatus>\n");

   if (rqptr->rqHeader.Method == HTTP_METHOD_WEBDAV_MOVE)
   {
      /* move using copy */
      DavMoveEnd (rqptr);
      return;
   }

   if (!rqptr->rqResponse.HttpStatus)
   {
      /* in the absence of any other status */
      if (tkptr->OverwriteOccurred)
         DavWebResponse (rqptr, 204, 0, "success, overwrite", FI_LI);
      else
      {
         DavWebHref (rqptr, tkptr->DestOds.ExpFileName, 0);
         DavWebResponse201 (rqptr);
      }
   }

   DavWebEnd (rqptr);
}

/*****************************************************************************/
/*
Copy a single file (depth:0).
*/

DavCopyFile (REQUEST_STRUCT *rqptr)

{
   int  status;
   WEBDAV_COPY  *cpyptr;
   WEBDAV_TASK  *tkptr;

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

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

   tkptr = rqptr->WebDavTaskPtr;
   cpyptr = &tkptr->CopyData;
   cpyptr->RequestPtr = rqptr;

   if (!cpyptr->MoveOverwrite)
   {
      sys$setprv (1, &SysPrvMask, 0, 0);
      status = OdsFileExists (tkptr->DestOds.ExpFileName, NULL);
      sys$setprv (0, &SysPrvMask, 0, 0);
      if (VMSok(status))
      {
         DavWebResponse (rqptr, 412, 0,
             "target file exists, no overwrite", FI_LI);
         DavCopyEnd (cpyptr);
         return;
      }
   }

   DavCopyFile2 (cpyptr);
}

/*****************************************************************************/
/*
Test if the source or destination are locked and continue copy if not.  This
function will be called multiple times as asynchronous testing of the DLM and
meta locking occurs. 
*/

DavCopyFile2 (WEBDAV_COPY *cpyptr)

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

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

   rqptr = cpyptr->RequestPtr;
   tkptr = rqptr->WebDavTaskPtr;

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
                 "DavCopyFile2() !UL", tkptr->TestLockState);

   /***********/
   /* locking */
   /***********/

   /* if it's a move then locking has already been processed */
   if (rqptr->rqHeader.Method == HTTP_METHOD_WEBDAV_MOVE)
      tkptr->TestLockState = 999;

   switch (tkptr->TestLockState)
   {
      case 0 :

         /* establish a request (overall) VMS DLM lock on source */
         tkptr->TestLockState++;
         DavWebDlmEnqueue (rqptr, &tkptr->DlmSource,
                           rqptr->ParseOds.ExpFileName, NULL,
                           true, false, DavCopyFile2, cpyptr);
         return;

      case 1 :

         if (VMSnok (status = tkptr->DlmSource.LockSb.lksb$w_status))
         {
            /* ordinarily shouldn't return any errors */
            DavWebResponse (rqptr, 500, status, NULL, FI_LI);
            DavCopyEnd (cpyptr);
            return;
         }
         /* establish a request (overall) VMS DLM lock on destination */
         tkptr->TestLockState++;

         DavWebDlmEnqueue (rqptr, &tkptr->DlmDestin,
#if TEST_DEADLOCK_DETECT
                           rqptr->ParseOds.ExpFileName, NULL,
#else
                           tkptr->DestOds.ExpFileName, NULL,
#endif
                           true, false, DavCopyFile2, cpyptr);
         return;

      case 2 :

         if (VMSnok (status = tkptr->DlmDestin.LockSb.lksb$w_status))
         {
            /* ordinarily shouldn't return any errors */
            DavWebResponse (rqptr, 500, status, NULL, FI_LI);
            DavCopyEnd (cpyptr);
            return;
         }
         /* check for meta-lock on existing source */
         tkptr->TestLockState++;
         DavLockTest (rqptr, rqptr->ParseOds.ExpFileName, false,
                      DavCopyFile2, cpyptr);
         return;

      case 3 :

         if (VMSnok (tkptr->TestLockStatus))
         {
            DavWebResponse (rqptr, 423, 0, "source locked", FI_LI);
            DavCopyEnd (cpyptr);
            return;
         }
         /* check for meta-lock on existing destination */
         tkptr->TestLockState++;
         DavLockTest (rqptr, tkptr->DestOds.ExpFileName, false,
                      DavCopyFile2, cpyptr);
         return;

      case 4 :

         if (VMSnok (tkptr->TestLockStatus))
         {
            DavWebResponse (rqptr, 423, 0, "destination locked", FI_LI);
            DavCopyEnd (cpyptr);
            return;
         }
   }

   /**************/
   /* not locked */
   /**************/

   StrDscText (rqptr, &cpyptr->ReadNameDsc,
               rqptr->ParseOds.ExpFileName,
               rqptr->ParseOds.ExpFileNameLength);

   StrDscText (rqptr, &cpyptr->WriteNameDsc,
               tkptr->DestOds.ExpFileName,
               tkptr->DestOds.ExpFileNameLength);

   cpyptr->ReadOdsPtr = (ODS_STRUCT*)VmGetHeap (rqptr, sizeof(ODS_STRUCT));
   OdsStructInit (cpyptr->ReadOdsPtr, false);
   cpyptr->WriteOdsPtr = (ODS_STRUCT*)VmGetHeap (rqptr, sizeof(ODS_STRUCT));
   OdsStructInit (cpyptr->WriteOdsPtr, false);

   DavCopyReadOpen (cpyptr);
}

/*****************************************************************************/
/*
Copy a directory tree (depth:infinity).
*/

DavCopyDirectory (REQUEST_STRUCT *rqptr)

{
   int  status;
   WEBDAV_COPY  *cpyptr;
   WEBDAV_TASK  *tkptr;

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

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

   tkptr = rqptr->WebDavTaskPtr;
   cpyptr = &tkptr->CopyData;
   cpyptr->RequestPtr = rqptr;

   if (tkptr->ToDepth != WEBDAV_DEPTH_ZERO &&
       tkptr->ToDepth != WEBDAV_DEPTH_INFINITY)
   {
      DavWebResponse (rqptr, 400, 0, "inappropriate depth", FI_LI);
      DavCopyEnd (cpyptr);
      return;
   }

   if (cpyptr->MoveOverwrite)
   {
      sys$setprv (1, &SysPrvMask, 0, 0);
      status = OdsFileExists (tkptr->DestOds.ExpFileName, "*.*");
      sys$setprv (0, &SysPrvMask, 0, 0);
      if (status != RMS$_FNF && status != RMS$_DNF)
      {
         /* not completely accurate but will do in the interim */
         DavWebResponse (rqptr, 409, 0,
"directory COPY does not support overwrite", FI_LI);
         DavCopyEnd (cpyptr);
         return;
      }
   }
   else
   {
      sys$setprv (1, &SysPrvMask, 0, 0);
      status = OdsFileExists (tkptr->DestOds.ExpFileName, NULL);
      sys$setprv (0, &SysPrvMask, 0, 0);
      if (VMSok(status))
      {
         DavWebResponse (rqptr, 412, 0,
"target directory exists, overwrite not requested", FI_LI);
         DavCopyEnd (cpyptr);
         return;
      }
   }

   if (VMSnok (DavWebParentExists (NULL, tkptr->DestOds.ExpFileName)))
   {
      DavWebResponse (rqptr, 412, 0, "parent does not exist", FI_LI);
      DavCopyEnd (rqptr);
      return;
   }

   DavCopyDirectory2 (rqptr);
}

/*****************************************************************************/
/*
Test if the source or destination are locked and continue copy if not.  This
function will be called multiple times as asynchronous testing of the DLM and
meta locking occurs. 
*/

DavCopyDirectory2 (REQUEST_STRUCT *rqptr)

{
   int  status,
        ParentNameLength;
   char  *cptr, *sptr, *zptr;
   char  ParentName [ODS_MAX_FILE_NAME_LENGTH+1];
   WEBDAV_COPY  *cpyptr;
   WEBDAV_TASK  *tkptr;

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

   tkptr = rqptr->WebDavTaskPtr;

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
                 "DavCopyDirectory2() !UL", tkptr->TestLockState);

   cpyptr = &tkptr->CopyData;

   /***********/
   /* locking */
   /***********/

   /* if it's a move then locking has already been processed */
   if (rqptr->rqHeader.Method == HTTP_METHOD_WEBDAV_MOVE)
      tkptr->TestLockState = 999;

   switch (tkptr->TestLockState)
   {
      case 0 :

         /* establish a request (overall) VMS DLM lock on source */
         tkptr->TestLockState++;
         DavWebDlmEnqueue (rqptr, &tkptr->DlmSource,
                           rqptr->ParseOds.ExpFileName, NULL,
                           true, false, DavCopyDirectory2, rqptr);
         return;

      case 1 :

         if (VMSnok (status = tkptr->DlmSource.LockSb.lksb$w_status))
         {
            /* ordinarily shouldn't return any errors */
            DavWebResponse (rqptr, 500, status, NULL, FI_LI);
            DavCopyEnd (cpyptr);
            return;
         }
         /* establish a request (overall) VMS DLM lock on destination */
         tkptr->TestLockState++;

         DavWebDlmEnqueue (rqptr, &tkptr->DlmDestin,
#if TEST_DEADLOCK_DETECT
                           rqptr->ParseOds.ExpFileName, NULL,
#else
                           tkptr->DestOds.ExpFileName, NULL,
#endif
                           true, false, DavCopyDirectory2, rqptr);
         return;

      case 2 :

         if (VMSnok (status = tkptr->DlmDestin.LockSb.lksb$w_status))
         {
            /* ordinarily shouldn't return any errors */
            DavWebResponse (rqptr, 500, status, NULL, FI_LI);
            DavCopyEnd (cpyptr);
            return;
         }
         /* check for meta-lock on existing source */
         tkptr->TestLockState++;
         DavLockTest (rqptr, rqptr->ParseOds.ExpFileName, false,
                      DavCopyDirectory2, rqptr);
         return;

      case 3 :

         if (VMSnok (tkptr->TestLockStatus))
         {
            DavWebResponse (rqptr, 423, 0, "source locked", FI_LI);
            DavCopyEnd (cpyptr);
            return;
         }
         /* check for meta-lock on existing destination */
         tkptr->TestLockState++;
         DavLockTest (rqptr, tkptr->DestOds.ExpFileName, false,
                      DavCopyDirectory2, rqptr);
         return;

      case 4 :

         if (VMSnok (tkptr->TestLockStatus))
         {
            DavWebResponse (rqptr, 423, 0, "destination locked", FI_LI);
            DavCopyEnd (cpyptr);
            return;
         }

      /* now ready to copy */
   }

   /**************/
   /* not locked */
   /**************/

   OdsNameOfDirectoryFile (tkptr->DestOds.ExpFileName,
                           tkptr->DestOds.ExpFileNameLength,
                           ParentName, &ParentNameLength);

   sys$setprv (1, &SysPrvMask, 0, 0);
   status = OdsFileExists (ParentName, NULL);
   sys$setprv (0, &SysPrvMask, 0, 0);
   if (status == RMS$_DNF)
   {
      /* parent or other ascendent directory does not exist */
      DavWebResponse (rqptr, 409, status, "parent", FI_LI);
      DavCopyEnd (cpyptr);
      return;
   }

   /******************************/
   /* create top-level directory */
   /******************************/

   status = DavWebCreateDir (rqptr, tkptr->DestOds.ExpFileName,
                                    tkptr->DestOds.ExpFileNameLength);
   if (VMSok(status))
      cpyptr->DirCount++;
   else
   {
      cpyptr->DirFailCount++;
      DavWebResponse (rqptr, 0, status, NULL, FI_LI);
      DavCopyEnd (cpyptr);
      return;
   }

   if (tkptr->ToDepth == WEBDAV_DEPTH_ZERO)
   {
      /* depth of zero essentially only creates the directory */
      DavWebHref (rqptr, tkptr->DestOds.ExpFileName, 0);
      DavWebResponse201 (rqptr);
      DavCopyEnd (cpyptr);
      return;
   }

   /***********************/
   /* directory tree spec */
   /***********************/

   cpyptr->CopyingDir = true;

   zptr = (sptr = tkptr->SearchSpec) + sizeof(tkptr->SearchSpec)-1;
   for (cptr = rqptr->ParseOds.NamDevicePtr;
        cptr < rqptr->ParseOds.NamNamePtr-1 && sptr < zptr;
        *sptr++ = *cptr++);
   for (cptr = "...]*.*;"; *cptr && sptr < zptr; *sptr++ = *cptr++);
   if (sptr >= zptr)
   {
      ErrorNoticed (rqptr, SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
      DavWebResponse (rqptr, 0, SS$_BUGCHECK, NULL, FI_LI);
      return;
   }
   tkptr->SearchSpecLength = sptr - tkptr->SearchSpec;
   *sptr = '\0';

   /* suitable to search against (i.e. no NAM$_SYNCHK) */
   OdsStructInit (&tkptr->SearchOds, false);
   OdsParse (&tkptr->SearchOds, tkptr->SearchSpec, tkptr->SearchSpecLength,
             NULL, 0, 0, NULL, 0);

   if (VMSnok (status = tkptr->SearchOds.Fab.fab$l_sts))
   {
      ErrorNoticed (rqptr, status, NULL, FI_LI);
      DavWebResponse (rqptr, 500, status, NULL, FI_LI);
      return;
   }

   /****************/
   /* some storage */
   /****************/

   StrDscText (rqptr, &cpyptr->ReadNameDsc,
               rqptr->ParseOds.ExpFileName,
               rqptr->ParseOds.ExpFileNameLength);

   StrDscText (rqptr, &cpyptr->WriteNameDsc,
               tkptr->DestOds.ExpFileName,
               tkptr->DestOds.ExpFileNameLength);

   cpyptr->ReadOdsPtr = (ODS_STRUCT*)VmGetHeap (rqptr, sizeof(ODS_STRUCT));
   OdsStructInit (cpyptr->ReadOdsPtr, false);
   cpyptr->WriteOdsPtr = (ODS_STRUCT*)VmGetHeap (rqptr, sizeof(ODS_STRUCT));
   OdsStructInit (cpyptr->WriteOdsPtr, false);

   DavCopyNext (cpyptr);
}

/*****************************************************************************/
/*
This function is a common target from the file copy functions when the copy
fails or completes.  If a single file copy it just calls DavCopyEnd() and
that's that!  For a directory (tree) copy, a search for the next file is
performed which ASTs to DavCopyNextAst().
*/

DavCopyNext (WEBDAV_COPY *cpyptr)

{
   REQUEST_STRUCT  *rqptr;
   WEBDAV_TASK  *tkptr;

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

   rqptr = cpyptr->RequestPtr;

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

   tkptr = rqptr->WebDavTaskPtr;

   if (!cpyptr->CopyingDir)
   {
      /* no next if we're copying the one file (depth:0) */
      if (cpyptr->CopyingMeta)
         DavCopyEnd (cpyptr);
      else
         DavCopyMeta (rqptr, rqptr->ParseOds.ExpFileName,
                             tkptr->DestOds.ExpFileName);
      return;
   }

   AuthAccessEnable (rqptr, 0, AUTH_ACCESS_SYSPRV);

   OdsSearch (&tkptr->SearchOds, &DavCopyNextAst, cpyptr);

   AuthAccessEnable (rqptr, 0, 0);
}

/*****************************************************************************/
/*
Directry (tree) copy search has completed.  If no more files are found then
it's the end of the tree copy.  Otherwise initiate the copy of this file.
*/

DavCopyNextAst (WEBDAV_COPY *cpyptr)

{
   BOOL  IsDirectory;
   int  status,
        FileNameLength;
   char  *cptr, *sptr, *zptr;
   char  DirName [ODS_MAX_FILE_NAME_LENGTH+1],
         FileName [ODS_MAX_FILE_NAME_LENGTH+1],
         FromDirName [ODS_MAX_FILE_NAME_LENGTH+1];
   STR_DSC_AUTO (FromDsc);
   STR_DSC_AUTO (ToDsc);
   REQUEST_STRUCT  *rqptr;
   WEBDAV_TASK  *tkptr;

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

   rqptr = cpyptr->RequestPtr;

   tkptr = rqptr->WebDavTaskPtr;

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
                 "DavCopyNextAst() sts:!&S stv:!&S",
                 tkptr->SearchOds.Fab.fab$l_sts,
                 tkptr->SearchOds.Fab.fab$l_stv);

   if (rqptr->RequestState >= REQUEST_STATE_ABORT)
   {
      DavCopyEnd (cpyptr);
      return;
   }

   if (VMSnok (status = tkptr->SearchOds.Fab.fab$l_sts))
   {
      if (status == RMS$_NMF || status == RMS$_FNF)
      {
         cpyptr->VmsStatus = SS$_NORMAL;
         if (!cpyptr->CopyingMeta)
         {
            DavCopyMeta (rqptr, rqptr->ParseOds.ExpFileName,
                                tkptr->DestOds.ExpFileName);
            return;
         }
      }
      else
         cpyptr->VmsStatus = status;

      DavCopyEnd (cpyptr);
      return;
   }

   IsDirectory = false;
   if (tkptr->SearchOds.NamTypeLength == 4 &&
       (MATCH5 (tkptr->SearchOds.NamTypePtr, ".DIR;") ||
        MATCH5 (tkptr->SearchOds.NamTypePtr, ".dir;")))
   {
      status = OdsReallyADir (rqptr, &tkptr->SearchOds);
      if (VMSok(status)) IsDirectory = true;
   }

   if (IsDirectory)
   {
      /*************/
      /* directory */
      /*************/

      /* derived from the original plus the descendent tree */
      zptr = (sptr = DirName) + sizeof(DirName)-1;
      for (cptr = tkptr->DestOds.ExpFileName;
           *cptr && sptr < zptr;
           *sptr++ = *cptr++);
      /* eliminate the trailing ']' */
      sptr--;
      if (sptr < zptr) *sptr++ = '.';
      /* append (any) descendent tree from the search results */
      cptr = tkptr->SearchOds.ResFileName +
             rqptr->ParseOds.ExpFileNameLength - 1;
      while (cptr < tkptr->SearchOds.NamTypePtr && sptr < zptr)
      {
         if (*cptr == ']')
            *sptr++ = '.';
         else
            *sptr++ = *cptr;
         cptr++;
      }
      if (sptr < zptr) *sptr++ = ']';
      if (sptr >= zptr)
      {
         ErrorNoticed (rqptr, SS$_RESULTOVF, NULL, FI_LI);
         DavCopyEnd (cpyptr);
         return;
      }
      *sptr = '\0';

      status = DavWebCreateDir (rqptr, DirName, sptr-DirName);

      if (VMSok(status))
         cpyptr->DirCount++;
      else
      {
         cpyptr->DirFailCount++;
         DavWebMultiStatus (rqptr, status, DirName);
      }

      DavCopyNext (cpyptr);
      return;
   }

   /***************************/
   /* source and target names */
   /***************************/

   /* the source file name comes directly from the search results */
   StrDscText (rqptr, &cpyptr->ReadNameDsc,
               tkptr->SearchOds.ResFileName,
               tkptr->SearchOds.ResFileNameLength);

   /* the target name is derived from the original plus the descendent tree */
   zptr = (sptr = FileName) + ODS_MAX_FILE_NAME_LENGTH;
   for (cptr = tkptr->DestOds.ExpFileName;
        *cptr && sptr < zptr;
        *sptr++ = *cptr++);
   /* eliminate the trailing ']' */
   sptr--;
   /* append (any) descendent tree from the search results */
   for (cptr = tkptr->SearchOds.ResFileName +
               rqptr->ParseOds.ExpFileNameLength - 2;
        *cptr && sptr < zptr;
        *sptr++ = *cptr++);
   if (sptr >= zptr)
   {
      ErrorNoticed (rqptr, SS$_RESULTOVF, NULL, FI_LI);
      DavCopyEnd (cpyptr);
      return;
   }
   *sptr = '\0';
   FileNameLength = sptr - FileName;

   StrDscText (rqptr, &cpyptr->WriteNameDsc, FileName, FileNameLength);

   DavCopyReadOpen (cpyptr);
}

/*****************************************************************************/
/*
Open the source file.
*/

DavCopyReadOpen (WEBDAV_COPY *cpyptr)

{
   int  status;
   REQUEST_STRUCT  *rqptr;

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

   rqptr = cpyptr->RequestPtr;

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

   AuthAccessEnable (rqptr, STR_DSC_PTR(&cpyptr->ReadNameDsc),
                     AUTH_ACCESS_READ);

   OdsOpen (cpyptr->ReadOdsPtr,
            &cpyptr->ReadNameDsc, -1, NULL, 0, 
            FAB$M_GET + FAB$M_BIO, 0, FAB$M_SHRGET,
            DavCopyReadOpenAst, cpyptr);  

   AuthAccessEnable (rqptr, 0, 0);
}

/*****************************************************************************/
/*
AST from source file open.  Check status and abandon copy if it failed.  If
successful connect a RAB and attempt to create the target file.
*/

DavCopyReadOpenAst (WEBDAV_COPY *cpyptr)

{
   int  status;
   REQUEST_STRUCT  *rqptr;

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

   rqptr = cpyptr->RequestPtr;

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavCopyReadOpenAst() !&S",
                 cpyptr->ReadOdsPtr->Fab.fab$l_sts);

   cpyptr->VmsStatus = status = cpyptr->ReadOdsPtr->Fab.fab$l_sts;
   if (VMSnok (status))
   {
      if (WATCHING (rqptr, WATCH_WEBDAV))
         WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "COPY read open !AZ !&S",
                    STR_DSC_PTR(&cpyptr->ReadNameDsc), status);

      if (!cpyptr->CopyingMeta)
      {
         cpyptr->FileFailCount++;

         if (VMSnok (status))
            DavWebMultiStatus (rqptr, status, STR_DSC_PTR(&cpyptr->ReadNameDsc));
      }

      DavCopyNext (cpyptr);
      return;
   }

   cpyptr->ReadOdsPtr->Rab = cc$rms_rab;
   cpyptr->ReadOdsPtr->Rab.rab$l_ctx = cpyptr;
   cpyptr->ReadOdsPtr->Rab.rab$l_fab = &cpyptr->ReadOdsPtr->Fab;

   status = sys$connect (&cpyptr->ReadOdsPtr->Rab, 0, 0);

   if (VMSnok (status))
   {
      cpyptr->VmsStatus = status;
      OdsClose (cpyptr->ReadOdsPtr, NULL, NULL);
      DavCopyNext (cpyptr);
      return;
   }

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
                 "!&Z", STR_DSC_PTR(&cpyptr->WriteNameDsc));

   AuthAccessEnable (rqptr, STR_DSC_PTR(&cpyptr->WriteNameDsc),
                     AUTH_ACCESS_WRITE);

   OdsCreate (cpyptr->WriteOdsPtr,
              &cpyptr->WriteNameDsc, -1, NULL, 0, 
              FAB$M_PUT + FAB$M_GET + FAB$M_BIO, FAB$M_CIF, FAB$M_NIL,
              0, 0,
              &cpyptr->ReadOdsPtr->Fab,
              DavCopyWriteOpenAst, cpyptr);  

   AuthAccessEnable (rqptr, 0, 0);
}

/*****************************************************************************/
/*
AST from target file create.  Check status and abandon copy if it failed.  If
successful connect a RAB and populate the RABs ready for an asynchronous set of
reads and writes.
*/

DavCopyWriteOpenAst (WEBDAV_COPY *cpyptr)

{
   int  status;
   ODS_STRUCT  *rodsptr, *wodsptr;
   REQUEST_STRUCT  *rqptr;
   WEBDAV_TASK  *tkptr;

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

   rqptr = cpyptr->RequestPtr;

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavCopyWriteOpenAst() !&S",
                 cpyptr->WriteOdsPtr->Fab.fab$l_sts);

   tkptr = rqptr->WebDavTaskPtr;

   rodsptr = cpyptr->ReadOdsPtr;
   wodsptr = cpyptr->WriteOdsPtr;

   cpyptr->VmsStatus = status = wodsptr->Fab.fab$l_sts;
   if (VMSnok (status))
   {
      if (WATCHING (rqptr, WATCH_WEBDAV))
         WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "COPY write open !AZ !&S",
                    STR_DSC_PTR(&cpyptr->WriteNameDsc), status);

      cpyptr->FileFailCount++;

      if (VMSnok (status))
         DavWebMultiStatus (rqptr, status, STR_DSC_PTR(&cpyptr->WriteNameDsc));

      OdsClose (rodsptr, NULL, NULL);
      DavCopyNext (cpyptr);
      return;
   }

   if (cpyptr->VmsStatus != RMS$_CREATED) tkptr->OverwriteOccurred = true;

   /* unless the copy completes successfully */
   wodsptr->DeleteOnClose = true;

   wodsptr->Rab = cc$rms_rab;
   wodsptr->Rab.rab$l_ctx = cpyptr;
   wodsptr->Rab.rab$l_fab = &wodsptr->Fab;

   status = sys$connect (&wodsptr->Rab, 0, 0);

   if (VMSnok (status))
   {
      cpyptr->VmsStatus = status;

      /* ensure delete-on-close (if required) */
      AuthAccessEnable (rqptr, 0, AUTH_ACCESS_SYSPRV);

      OdsClose (rodsptr, NULL, NULL);
      OdsClose (wodsptr, NULL, NULL);

      AuthAccessEnable (rqptr, 0, 0);

      DavCopyNext (cpyptr);
      return;
   }

   /* maximise block buffer size against file size */
   if (rodsptr->XabFhc.xab$l_ebk < 127)
      cpyptr->BucketSize = rodsptr->XabFhc.xab$l_ebk;
   else
      cpyptr->BucketSize = 127;

   cpyptr->DataSize = cpyptr->BucketSize * 512;
   cpyptr->DataPtr = VmGetHeap (rqptr, cpyptr->DataSize);

   /* set up read RAB */
   rodsptr->Rab.rab$l_ctx = cpyptr;
   rodsptr->Rab.rab$l_rop = RAB$M_BIO | RAB$M_ASY;
   rodsptr->Rab.rab$l_ubf = cpyptr->DataPtr;
   rodsptr->Rab.rab$w_usz = cpyptr->DataSize;
   rodsptr->Rab.rab$l_bkt = 1;

   /* set up write RAB */
   wodsptr->Rab.rab$l_ctx = cpyptr;
   wodsptr->Rab.rab$l_rop = RAB$M_BIO | RAB$M_ASY;

   sys$read (&rodsptr->Rab, &DavCopyReadAst, &DavCopyReadAst);
}

/*****************************************************************************/
/*
AST from source read.  Check status and abandon copy if it failed.  If
successful write the just-read buffer.
*/

DavCopyReadAst (struct RAB *RabPtr)

{
   BOOL  IsMetaFile;
   int  status;
   char  *cptr;
   ODS_STRUCT  *rodsptr, *wodsptr;
   REQUEST_STRUCT  *rqptr;
   STR_DSC_AUTO (FromDsc);
   STR_DSC_AUTO (ToDsc);
   WEBDAV_COPY  *cpyptr;
   WEBDAV_TASK  *tkptr;

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

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

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
                 "DavCopyReadAst() read: !&S !UL write: !&S !UL",
                 cpyptr->ReadOdsPtr->Rab.rab$l_sts,
                 cpyptr->ReadOdsPtr->Rab.rab$w_rsz,
                 cpyptr->WriteOdsPtr->Rab.rab$l_sts,
                 cpyptr->WriteOdsPtr->Rab.rab$w_rsz);

   tkptr = rqptr->WebDavTaskPtr;
   rodsptr = cpyptr->ReadOdsPtr;
   wodsptr = cpyptr->WriteOdsPtr;

   status = rodsptr->Rab.rab$l_sts;
   if (VMSnok (status))
   {
      if (status == RMS$_EOF)
      {
         /* successful copy */
         if (WATCHING (rqptr, WATCH_WEBDAV))
            WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "COPIED !AZ",
                       STR_DSC_PTR(&cpyptr->ReadNameDsc));

         /* delete original if moving using copy */
         if (rqptr->rqHeader.Method == HTTP_METHOD_WEBDAV_MOVE)
            rodsptr->DeleteOnClose = true;

         /* undo delete unless successful */
         wodsptr->DeleteOnClose = false;

         cpyptr->VmsStatus = status = SS$_NORMAL;
      }
      else
         cpyptr->VmsStatus = status;

      /* only need this expense when WATCHing */
      if (WATCHING (rqptr, WATCH_WEBDAV))
      {
         IsMetaFile = DavMetaFile (cpyptr->ReadOdsPtr);

         if (VMSok(status))
            if (IsMetaFile)
               cpyptr->MetaCount++;
            else
               cpyptr->FileCount++;
         else
            if (IsMetaFile)
               cpyptr->MetaFailCount++;
            else
               cpyptr->FileFailCount++;
      }

      if (VMSnok (status))
      {
         if (WATCHING (rqptr, WATCH_WEBDAV))
            WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "COPY read !AZ !&S",
                       STR_DSC_PTR(&cpyptr->ReadNameDsc), status);

         DavWebMultiStatus (rqptr, status, STR_DSC_PTR(&cpyptr->ReadNameDsc));
      }

      /* ensure delete-on-close (if required) */
      AuthAccessEnable (rqptr, 0, AUTH_ACCESS_SYSPRV);

      OdsClose (rodsptr, NULL, NULL);
      OdsClose (wodsptr, NULL, NULL);

      AuthAccessEnable (rqptr, 0, 0);

      DavCopyNext (cpyptr);
      return;
   }

   wodsptr->Rab.rab$l_rbf = rodsptr->Rab.rab$l_ubf;
   wodsptr->Rab.rab$w_rsz = rodsptr->Rab.rab$w_rsz;
   wodsptr->Rab.rab$l_bkt = rodsptr->Rab.rab$l_bkt;

   sys$write (&wodsptr->Rab, &DavCopyWriteAst, &DavCopyWriteAst);
}

/*****************************************************************************/
/*
AST from target write.  Check status and abandon copy if it failed.  If
successful queue another source read.
*/

DavCopyWriteAst (struct RAB *RabPtr)

{
   int  status;
   ODS_STRUCT  *rodsptr, *wodsptr;
   REQUEST_STRUCT  *rqptr;
   WEBDAV_COPY  *cpyptr;
   WEBDAV_TASK  *tkptr;

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

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

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavCopyWriteAst() !&S !UL",
                 cpyptr->WriteOdsPtr->Rab.rab$l_sts,
                 cpyptr->WriteOdsPtr->Rab.rab$w_rsz);

   tkptr = rqptr->WebDavTaskPtr;
   rodsptr = cpyptr->ReadOdsPtr;
   wodsptr = cpyptr->WriteOdsPtr;

   status = wodsptr->Rab.rab$l_sts;
   if (VMSnok (status))
   {
      cpyptr->VmsStatus = status;
      cpyptr->FileFailCount++;

      if (WATCHING (rqptr, WATCH_WEBDAV))
         WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "COPY write !AZ !&S",
                    STR_DSC_PTR(&cpyptr->WriteNameDsc), status);

      DavWebMultiStatus (rqptr, cpyptr->VmsStatus,
                         STR_DSC_PTR(&cpyptr->WriteNameDsc));

      /* ensure delete-on-close (if required) */
      AuthAccessEnable (rqptr, 0, AUTH_ACCESS_SYSPRV);

      OdsClose (rodsptr, NULL, NULL);
      OdsClose (wodsptr, NULL, NULL);

      AuthAccessEnable (rqptr, 0, 0);

      DavCopyNext (cpyptr);
      return;
   }

   rodsptr->Rab.rab$l_bkt += cpyptr->BucketSize;

   sys$read (&rodsptr->Rab, &DavCopyReadAst, &DavCopyReadAst);
}

/*****************************************************************************/
/*
Copy any meta file associated with the source to the destination.
Assumes everything's locked by the calling routine.
*/

DavCopyMeta
(
REQUEST_STRUCT *rqptr,
char *SrcFileName,
char *DstFileName
)
{
   int  status;
   WEBDAV_COPY  *cpyptr;
   WEBDAV_META  *mtaptr;
   WEBDAV_TASK  *tkptr;

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

   tkptr = rqptr->WebDavTaskPtr;

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavCopyMeta() !AZ !AZ",
                 SrcFileName, DstFileName);

   cpyptr = &tkptr->CopyData;

   cpyptr->CopyingMeta = true;

   mtaptr = &tkptr->MetaData;
   mtaptr->TaskPtr = rqptr->WebDavTaskPtr;

   DavMetaName (mtaptr, SrcFileName, 'r'); 
   DavMetaName (mtaptr, DstFileName, 'w');

   StrDscText (rqptr, &cpyptr->ReadNameDsc,
               mtaptr->ReadMetaName,
               mtaptr->ReadMetaNameLength);

   StrDscText (rqptr, &cpyptr->WriteNameDsc,
               mtaptr->WriteMetaName,
               mtaptr->WriteMetaNameLength);

   DavMetaCreateDir (mtaptr);

   DavCopyReadOpen (cpyptr);
}

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

