/*****************************************************************************/
/*
                                 DAVmove.c

Also see comments on MOVE in DAVWEB.C.

Implement the MOVE method for single resources (files) and collections
(directory trees).  Moves behave the same for for single resources (files) and
collections (directories).  Both are renamed (if possible), with the directory
file itself being renamed in the case of a collection.  Should the rename fail
due to the target being on another volume the move is done via a copy and
delete using the respective DAV code modules.


VERSION HISTORY
---------------
17-OCT-2018  MGD  bugfix; ensure meta directory exists
07-JUL-2018  MGD  bugfix; DavMoveMeta() OdsParseRelease() as final action
05-JUL-2018  MGD  bugfix; flawed use of lib$rename_file() using DEFAULT_ZONE
                    memory without free (context not exhausted) - use
                    DavMoveRename() instead (which should be more efficient)
                  bugfix; remove context from lib$rename_file()
28-APR-2015  MGD  bugfix; DavMoveMeta() do not report RMS$_DNF
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 "DAVMOVE"

#define TEST_DEADLOCK_DETECT 0
#define TEST_BETWEEN_DEVICES 0

/* use RMS $RENAME by default but allow older LIB$RENAME_FILE as option */
#define DAV_MOVE_USING_ODS 1

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

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

extern int  ToLowerCase[],
            ToUpperCase[];

extern unsigned long  SysPrvMask[];

extern char  ErrorSanityCheck[];

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

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

DavMoveBegin (REQUEST_STRUCT *rqptr)

{
   int  status;
   WEBDAV_TASK  *tkptr;

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

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

   tkptr = rqptr->WebDavTaskPtr;

   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,
                 "MOVE !AZ to !AZ overwrite:!&B depth:!UL path:!AZ",
                 rqptr->ParseOds.ExpFileName, tkptr->DestOds.ExpFileName,
                 tkptr->CopyData.MoveOverwrite, tkptr->ToDepth,
                 DavWebPathAccess(rqptr));

   if (!strcmp (rqptr->ParseOds.ExpFileName, tkptr->DestOds.ExpFileName))
   {
      /* case-sensitive because the client might just be changing case! */
      DavWebResponse (rqptr, 403, 0, "identical", FI_LI);
      DavWebEnd (rqptr);
      return;
   }

   if (rqptr->ParseOds.NamNameLength ||
       rqptr->ParseOds.NamTypeLength)
   {
      /********/
      /* file */
      /********/

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

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

   DavWebEnd (rqptr);
}

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

DavMoveEnd (REQUEST_STRUCT *rqptr)

{
   int  status;
   WEBDAV_TASK  *tkptr;

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

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

   tkptr = rqptr->WebDavTaskPtr;

   if (tkptr->MoveUsingCopy)
   {
      /* delete directories left over from move using copy */
      tkptr->MoveUsingCopy = false;

      status = DavDeleteParse (rqptr, true, "...]*.DIR;*");
      if (VMSnok (status))
      {
         DavWebResponse (rqptr, 0, status, NULL, FI_LI);
         DavWebEnd (rqptr);
         return;
      }

      tkptr->DeleteData.DelPhase = WEBDAV_DELETE_TREE_FILES;
      DavDeleteSearch (rqptr);

      return;
   }

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

   DavWebEnd (rqptr);
}

/*****************************************************************************/
/*
Move a single (non-.DIR) file.  This function will be called multiple times as
asynchronous testing of the DLM and meta locking occurs.
*/

DavMoveFile (REQUEST_STRUCT *rqptr)

{
   static $DESCRIPTOR (DstNameDsc, "");
   static $DESCRIPTOR (SrcNameDsc, "");

   BOOL  CanDoIt;
   int  status;
   unsigned long  RenameFlags;
   ODS_STRUCT  DeleteOds;
   WEBDAV_DLM  *dlmptr;
   WEBDAV_TASK  *tkptr;

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

   tkptr = rqptr->WebDavTaskPtr;

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

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

   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, DavMoveFile, 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);
            DavMoveEnd (rqptr);
            return;
         }
         /* establish a request (overall) VMS DLM lock on destination */
         tkptr->TestLockState++;

         /* we can rename the case of the same underlying name! */
         if (strsame (rqptr->ParseOds.ExpFileName,
                      tkptr->DestOds.ExpFileName, -1))
         {
            /* do NOT take up a DLM lock on the same target - deadlock!! */
            tkptr->DlmDestin.LockSb.lksb$w_status = SS$_NORMAL;
            DavMoveFile (rqptr);
         }
         else
         {
            DavWebDlmEnqueue (rqptr, &tkptr->DlmDestin,
#if TEST_DEADLOCK_DETECT
                              rqptr->ParseOds.ExpFileName, NULL,
#else
                              tkptr->DestOds.ExpFileName, NULL,
#endif
                              true, false, DavMoveFile, 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);
            DavMoveEnd (rqptr);
            return;
         }
         /* check for meta-lock on existing source */
         tkptr->TestLockState++;
         DavLockTest (rqptr, rqptr->ParseOds.ExpFileName, false,
                      DavMoveFile, rqptr);
         return;

      case 3 :

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

      case 4 :

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

   /*****************/
   /* ready to move */
   /*****************/

   if (DavMetaFile (&tkptr->DestOds) ||
       DavMetaDir (rqptr, &tkptr->DestOds))
   {
      /* this is a metadata file or contains a metadata (sub)directory */
      DavWebResponse (rqptr, 404, 0, "directory not found", FI_LI);
      DavMoveEnd (rqptr);
      return;
   }

   if (!tkptr->CopyData.MoveOverwrite)
   {
      sys$setprv (1, &SysPrvMask, 0, 0);
      status = OdsFileExists (tkptr->DestOds.ExpFileName, NULL);
      sys$setprv (0, &SysPrvMask, 0, 0);
      if (VMSok(status))
      {
         /* if really different and not just a case change (for instance) */
         if (!strsame (rqptr->ParseOds.ExpFileName,
                       tkptr->DestOds.ExpFileName, -1))
         {
            DavWebResponse (rqptr, 412, 0,
               "target file exists, no overwrite", FI_LI);
            DavMoveEnd (rqptr);
            return;
         }
      }
   }

   if (!rqptr->ParseOds.ExpFileNameLength ||
       !tkptr->DestOds.ExpFileNameLength)
   {
      ErrorNoticed (rqptr, SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
      DavWebResponse (rqptr, 0, SS$_BUGCHECK, NULL, FI_LI);
      DavMoveEnd (rqptr);
      return;
   }

#if TEST_BETWEEN_DEVICES
   status = RMS$_DEV;
#else

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "!AZ to !AZ",
                 rqptr->ParseOds.ExpFileName, tkptr->DestOds.ExpFileName);

   SrcNameDsc.dsc$a_pointer = rqptr->ParseOds.ExpFileName;
   SrcNameDsc.dsc$w_length = rqptr->ParseOds.ExpFileNameLength;

   DstNameDsc.dsc$a_pointer = tkptr->DestOds.ExpFileName;
   DstNameDsc.dsc$w_length = tkptr->DestOds.ExpFileNameLength;

   CanDoIt = AuthAccessEnable (rqptr, tkptr->DestOds.ExpFileName,
                               AUTH_ACCESS_WRITE);

#if DAV_MOVE_USING_ODS

   status = DavMoveRename (rqptr, &SrcNameDsc, &DstNameDsc);

#else /* DAV_MOVE_USING_ODS */

   RenameFlags = 0x1;
#ifdef ODS_EXTENDED
   if (rqptr->PathOdsExtended) RenameFlags += 0x4;
#endif /* ODS_EXTENDED */

   status = lib$rename_file (&SrcNameDsc, &DstNameDsc,
                             0, 0, &RenameFlags, 0, 0, 0, 0, 0, 0, 0);

#endif /* DAV_MOVE_USING_ODS */

#endif /* TEST_BETWEEN_DEVICES */

   AuthAccessEnable (rqptr, 0, 0);

   if (WATCHING (rqptr, WATCH_WEBDAV))
   {
      if (VMSok(status))
         WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "RENAMED !AZ to !AZ",
                    rqptr->ParseOds.ExpFileName, tkptr->DestOds.ExpFileName);
      else
      if (status != RMS$_DEV)
         WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "RENAME !&S !AZ to !AZ",
                    status, rqptr->ParseOds.ExpFileName,
                            tkptr->DestOds.ExpFileName);
   }

   if (VMSnok(status))
   {
      if (status == RMS$_DEV)
      {
         /*********************/
         /* to another device */
         /*********************/

         if (!CanDoIt)
         {
            DavWebResponse (rqptr, 403, 0,
               "uncertain permission to delete source file", FI_LI);
            DavMoveEnd (rqptr);
            return;
         }

         if (WATCHING (rqptr, WATCH_WEBDAV))
            WatchThis (WATCHITM(rqptr), WATCH_WEBDAV,
                       "MOVE to another device - using COPY with DELETE");

         tkptr->MoveUsingCopy = true;
         DavCopyFile (rqptr);
         return;
      }

      if (status == RMS$_RMV)
         DavWebResponse (rqptr, 403, status, "protection", FI_LI);
      else
      if (status == RMS$_DNF)
         DavWebResponse (rqptr, 409, status, "parent", FI_LI);
      else
         DavWebResponse (rqptr, 0, status, NULL, FI_LI);

      DavMoveEnd (rqptr);
      return;
   }

   /* if not just a change of case */
   if (!strsame (rqptr->ParseOds.ExpFileName, tkptr->DestOds.ExpFileName, -1))
   {
      /**********************************/
      /* delete any previous version(s) */
      /**********************************/

      OdsStructInit (&DeleteOds, true);
      OdsParse (&DeleteOds,
                rqptr->ParseOds.ExpFileName,
                rqptr->ParseOds.ExpFileNameLength,
                NULL, 0, NAM$M_SYNCHK, NULL, rqptr);

      if (VMSnok (status = DeleteOds.Fab.fab$l_sts))
         ErrorNoticed (rqptr, status, NULL, FI_LI);
      else
      {
         AuthAccessEnable (rqptr, 0, AUTH_ACCESS_SYSPRV);

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

         AuthAccessEnable (rqptr, 0, 0);

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

         if (VMSnok (status) && DeleteOds.Fab.fab$l_stv)
            status = DeleteOds.Fab.fab$l_stv;
         if (VMSok (status)) tkptr->OverwriteOccurred = true;
         if (status == SS$_NOSUCHFILE) status = SS$_NORMAL;
         if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);
      }

      OdsParseRelease (&DeleteOds);

      /*****************/
      /* move any meta */
      /*****************/

      DavMoveMeta (rqptr, rqptr->ParseOds.ExpFileName,
                          tkptr->DestOds.ExpFileName);
   }

   DavMoveEnd (rqptr);
}

/*****************************************************************************/
/*
Move a directory (tree).  This function will be called multiple times as
asynchronous testing of the DLM and meta locking occurs.
*/

DavMoveDirectory (REQUEST_STRUCT *rqptr)

{
   static $DESCRIPTOR (DstNameDsc, "");
   static $DESCRIPTOR (SrcNameDsc, "");

   BOOL  CanDoIt;
   int  status,
        DstNameLength,
        SrcNameLength;
   unsigned long  RenameFlags;
   char  *cptr, *sptr, *zptr;
   char  DstName [ODS_MAX_FILE_NAME_LENGTH+1],
         SrcName [ODS_MAX_FILE_NAME_LENGTH+1];
   WEBDAV_TASK  *tkptr;

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

   tkptr = rqptr->WebDavTaskPtr;

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

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

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

   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, DavMoveDirectory, 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);
            DavMoveEnd (rqptr);
            return;
         }
         /* establish a request (overall) VMS DLM lock on destination */
         tkptr->TestLockState++;

         /* we can rename the case of the same underlying name! */
         if (strsame (rqptr->ParseOds.ExpFileName,
                      tkptr->DestOds.ExpFileName, -1))
         {
            /* do NOT take up a DLM lock on the same target - deadlock!! */
            tkptr->DlmDestin.LockSb.lksb$w_status = SS$_NORMAL;
            DavMoveDirectory (rqptr);
         }
         else
         {
            DavWebDlmEnqueue (rqptr, &tkptr->DlmDestin,
#if TEST_DEADLOCK_DETECT
                              rqptr->ParseOds.ExpFileName, NULL,
#else
                              tkptr->DestOds.ExpFileName, NULL,
#endif
                              true, false, DavMoveDirectory, 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);
            DavMoveEnd (rqptr);
            return;
         }
         /* check for meta-lock on existing source */
         tkptr->TestLockState++;
         DavLockTest (rqptr, rqptr->ParseOds.ExpFileName, false,
                      DavMoveDirectory, rqptr);
         return;

      case 3 :

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

      case 4 :

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

   /*****************/
   /* ready to move */
   /*****************/

   if (DavMetaFile (&tkptr->DestOds) ||
       DavMetaDir (rqptr, &tkptr->DestOds))
   {
      /* this is a metadata file or contains a metadata (sub)directory */
      DavWebResponse (rqptr, 404, 0, "directory not found", FI_LI);
      DavMoveEnd (rqptr);
      return;
   }

   /* all collection moves must act as if the depth is infinity */
   tkptr->ToDepth = WEBDAV_DEPTH_INFINITY;

   OdsNameOfDirectoryFile (tkptr->DestOds.ExpFileName,
                           tkptr->DestOds.ExpFileNameLength,
                           DstName, &DstNameLength);

   sys$setprv (1, &SysPrvMask, 0, 0);
   status = OdsFileExists (DstName, NULL);
   sys$setprv (0, &SysPrvMask, 0, 0);
   if (tkptr->CopyData.MoveOverwrite)
   {
      /* collection COPY does not (yet) support overwrite */
      if (status != RMS$_FNF && status != RMS$_DNF)
      {
         /* not completely accurate but will do in the interim */
         DavWebResponse (rqptr, 409, 0,
"directory MOVE does not support overwrite", FI_LI);
         DavMoveEnd (rqptr);
         return;
      }
   }
   else
   {
      if (VMSok(status))
      {
         /* if really different and not just a case change (for instance) */
         if (!strsame (rqptr->ParseOds.ExpFileName,
                       tkptr->DestOds.ExpFileName, -1))
         {
            DavWebResponse (rqptr, 412, 0,
"target directory exists, overwrite not requested", FI_LI);
            DavMoveEnd (rqptr);
            return;
         }
      }
   }

   OdsNameOfDirectoryFile (rqptr->ParseOds.ExpFileName,
                           rqptr->ParseOds.ExpFileNameLength,
                           SrcName, &SrcNameLength);

   if (!rqptr->ParseOds.ExpFileNameLength ||
       !tkptr->DestOds.ExpFileNameLength)
   {
      ErrorNoticed (rqptr, SS$_BUGCHECK, ErrorSanityCheck, FI_LI);
      DavWebResponse (rqptr, 0, SS$_BUGCHECK, NULL, FI_LI);
      DavMoveEnd (rqptr);
      return;
   }

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV,
                 "!AZ to !AZ", SrcName, DstName);

   SrcNameDsc.dsc$a_pointer = SrcName;
   SrcNameDsc.dsc$w_length = SrcNameLength;

   DstNameDsc.dsc$a_pointer = DstName;
   DstNameDsc.dsc$w_length = DstNameLength;

   CanDoIt = AuthAccessEnable (rqptr, tkptr->DestOds.ExpFileName,
                               AUTH_ACCESS_WRITE);

#if TEST_BETWEEN_DEVICES
   status = RMS$_DEV;
#else

#if DAV_MOVE_USING_ODS

   status = DavMoveRename (rqptr, &SrcNameDsc, &DstNameDsc);

#else /* DAV_MOVE_USING_ODS */

   RenameFlags = 0x1;
#ifdef ODS_EXTENDED
   if (rqptr->PathOdsExtended) RenameFlags += 0x4;
#endif /* ODS_EXTENDED */

   status = lib$rename_file (&SrcNameDsc, &DstNameDsc,
                             0, 0, &RenameFlags, 0, 0, 0, 0,
                             0, 0, 0);

#endif /* DAV_MOVE_USING_ODS */

#endif /* TEST_BETWEEN_DEVICES */

   AuthAccessEnable (rqptr, 0, 0);

   if (WATCHING (rqptr, WATCH_WEBDAV))
   {
      if (VMSok(status))
         WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "RENAMED !AZ to !AZ",
                    SrcName, DstName);
      else
      if (status != RMS$_DEV)
         WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "RENAME !&S !AZ to !AZ",
                    status, SrcName, DstName);
   }

   if (VMSnok (status))
   {
      if (status == RMS$_DEV)
      {
         /*********************/
         /* to another device */
         /*********************/

         if (!CanDoIt)
         {
            DavWebResponse (rqptr, 403, 0,
               "uncertain permission to delete source directory", FI_LI);
            DavMoveEnd (rqptr);
            return;
         }

         if (WATCHING (rqptr, WATCH_WEBDAV))
            WatchThis (WATCHITM(rqptr), WATCH_WEBDAV,
                       "MOVE to another device - using COPY with DELETE");

         tkptr->MoveUsingCopy = true;
         DavCopyDirectory (rqptr);
         return;
      }

      if (WATCHING (rqptr, WATCH_WEBDAV))
         WatchThis (WATCHITM(rqptr), WATCH_WEBDAV,
                    "MOVE rename !AZ to !AZ !&S",
                    SrcNameDsc.dsc$a_pointer,
                    DstNameDsc.dsc$a_pointer,
                    status);

      if (status == RMS$_RMV)
         DavWebResponse (rqptr, 403, status, "protection", FI_LI);
      else
      if (status == RMS$_IDR)
         DavWebResponse (rqptr, 403, status, "dependency", FI_LI);
      else
      if (status == RMS$_DNF)
         DavWebResponse (rqptr, 409, status, "parent", FI_LI);
      else
         DavWebResponse (rqptr, 0, status, NULL, FI_LI);

      DavMoveEnd (rqptr);
      return;
   }

   /* if not just a change of case */
   if (!strsame (rqptr->ParseOds.ExpFileName, tkptr->DestOds.ExpFileName, -1))
   {
      /*****************/
      /* move any meta */
      /*****************/

      DavMoveMeta (rqptr, rqptr->ParseOds.ExpFileName,
                          tkptr->DestOds.ExpFileName);
   }

   DavMoveEnd (rqptr);
}

/*****************************************************************************/
/*
Move any meta file associated with the source to the destination.  If there
were multiple versions of the that meta file (because of gerfingerpokin) then
delete remaining version.  Assumes everything's locked by the calling routine.
*/

int DavMoveMeta
(
REQUEST_STRUCT *rqptr,
char *SrcFileName,
char *DstFileName
)
{
   static $DESCRIPTOR (DstNameDsc, "");
   static $DESCRIPTOR (SrcNameDsc, "");

   int  status;
   unsigned long  RenameFlags;
   char  *cptr, *sptr, *zptr;
   ODS_STRUCT  DeleteOds;
   WEBDAV_META  *mtaptr;
   WEBDAV_TASK  *tkptr;

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

   tkptr = rqptr->WebDavTaskPtr;

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

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

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

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

   SrcNameDsc.dsc$a_pointer = mtaptr->ReadMetaName;
   SrcNameDsc.dsc$w_length = mtaptr->ReadMetaNameLength;

   DstNameDsc.dsc$a_pointer = mtaptr->WriteMetaName;
   DstNameDsc.dsc$w_length = mtaptr->WriteMetaNameLength;

   DavMetaCreateDir (mtaptr);

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

#if DAV_MOVE_USING_ODS

   status = DavMoveRename (rqptr, &SrcNameDsc, &DstNameDsc);

#else /* DAV_MOVE_USING_ODS */

   RenameFlags = 0x1;
#ifdef ODS_EXTENDED
   if (rqptr->PathOdsExtended) RenameFlags += 0x4;
#endif /* ODS_EXTENDED */

   status = lib$rename_file (&SrcNameDsc, &DstNameDsc,
                             0, 0, &RenameFlags, 0, 0, 0, 0, 0, 0, 0);

#endif /* DAV_MOVE_USING_ODS */

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

   if (WATCHING (rqptr, WATCH_WEBDAV))
   {
      if (VMSok(status))
         WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "RENAMED !AZ to !AZ",
                    mtaptr->ReadMetaName, mtaptr->WriteMetaName);
      else
      if (status != RMS$_FNF && status != RMS$_DNF)
         WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "RENAME !&S !AZ to !AZ",
                    status, mtaptr->ReadMetaName, mtaptr->WriteMetaName);
   }

   if (VMSnok (status) && status != RMS$_DNF && status != RMS$_FNF)
   {
      ErrorNoticed (rqptr, status, NULL, FI_LI);
      return (status);
   }

   /* purge all (remaining) previous meta version(s) */
   OdsStructInit (&DeleteOds, true);
   OdsParse (&DeleteOds,
             mtaptr->ReadMetaName,
             mtaptr->ReadMetaNameLength,
             NULL, 0, NAM$M_SYNCHK, NULL, rqptr);

   if (VMSnok (status = DeleteOds.Fab.fab$l_sts))
   {
      ErrorNoticed (rqptr, status, NULL, FI_LI);
      return (status);
   }

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

   while (VMSok (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() !&S !AZ", status, DeleteOds.ExpFileName);

   if (VMSnok (status) && DeleteOds.Fab.fab$l_stv)
      status = DeleteOds.Fab.fab$l_stv;
   if (status == SS$_NOSUCHFILE) status = SS$_NORMAL;
   if (VMSnok (status)) ErrorNoticed (rqptr, status, NULL, FI_LI);

   OdsParseRelease (&DeleteOds);

   return (status);
}

/*****************************************************************************/
/*
Rename a single version of a single file.
*/

#if DAV_MOVE_USING_ODS

int DavMoveRename
(
REQUEST_STRUCT *rqptr,
struct dsc$descriptor *SrcDscPtr,
struct dsc$descriptor *DstDscPtr
)
{
   int  status;
   ODS_STRUCT  DstOds,
               SrcOds;

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

   if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
      WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavMoveRename() !AZ !AZ",
                 SrcDscPtr->dsc$a_pointer,  DstDscPtr->dsc$a_pointer);

   OdsStructInit (&SrcOds, true);
   status = OdsParse (&SrcOds, SrcDscPtr->dsc$a_pointer,
                               SrcDscPtr->dsc$w_length,
                      NULL, 0, NAM$M_NOCONCEAL, NULL, rqptr);

   if (VMSok (status))
   {
      OdsStructInit (&DstOds, true);
      status = OdsParse (&DstOds, DstDscPtr->dsc$a_pointer,
                                  DstDscPtr->dsc$w_length,
                         NULL, 0, NAM$M_NOCONCEAL, NULL, rqptr);
   }

   if (VMSok (status))
   {
      status = sys$rename (&SrcOds.Fab, 0, 0, &DstOds.Fab);
      if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV))
         WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "$rename() !&S", status);
   }

   return (status);
}

#endif /* !DAV_MOVE_USING_ODS */

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

