/*****************************************************************************/ /* DAVdelete.c Also see comments on DELETE in DAVWEB.C. Implement the DELETE method individual resources (files) and collections (directory trees). This module is also used by a MOVE of resources between different volumes. After a successful MOVE the source is then DELETEd. It does not meet the RFC 2518 8.6.2 requirement that if any child resource deletion would fail then the deletion of the collection not proceed at all (returning a 424). So, in the case of WASD WebDAV it's just best-effort and if something down the tree won't disappear, it just reports the failure in the 207 response and carries merrily on through the tree regardless. This IS acceptable WebDAV server behaviour! Tree deletion occurs in three phases: 1) Files (those files not ending in .DIR). A single pass. Deletion errors are reported in a 207 multistatus response. 2) Directories (those files ending in .DIR). One of more passes, until all directory that can be deleted are deleted. Deletion errors other than SS$_DIRNOTEMPTY are reported in a 207 multistatus response. 3) Root (parent) directory. VERSION HISTORY --------------- 19-OCT-2018 MGD bugfix; suppress DIRNOTEMPTY as note above requires 13-APR-2018 MGD SS$_ABORT on ->RequestRundown 29-APR-2015 MGD bugfix; DavDeleteParse() enable access around OdsParse() 29-AUG-2009 MGD DavDeleteErase() VMS status processing DavDeleteBegin() concession to Microsoft collection depth 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 #include #include "wasd.h" #include "davweb.h" #define WASD_MODULE "DAVDELETE" #ifndef WASD_WEBDAV #define WASD_WEBDAV 1 #endif /******************/ /* global storage */ /******************/ /********************/ /* external storage */ /********************/ extern int HttpdTickSecond; extern int ToLowerCase[]; extern unsigned long SysPrvMask[]; extern char ErrorSanityCheck[]; extern ACCOUNTING_STRUCT *AccountingPtr; extern CONFIG_STRUCT Config; extern WATCH_STRUCT Watch; /*****************************************************************************/ /* Deletes a collection (directory) or single resource (file). */ DavDeleteBegin (REQUEST_STRUCT *rqptr) { int status; WEBDAV_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavDeleteBegin()"); if (!rqptr->RemoteUser[0]) ErrorExitVmsStatus (SS$_BUGCHECK, ErrorSanityCheck, FI_LI); tkptr = rqptr->WebDavTaskPtr; tkptr->TestLockState = 0; OdsStructInit (&tkptr->SearchOds, false); if (!(rqptr->ParseOds.NamNameLength > 0 || rqptr->ParseOds.NamTypeLength > 1)) { /* delete MUST act as if infinity depth */ if (tkptr->ToDepth != WEBDAV_DEPTH_INFINITY) { if (!tkptr->MicrosoftAgent) { DavWebResponse (rqptr, 400, 0, "inappropriate depth", FI_LI); DavWebEnd (rqptr); return; } } } if (WATCHING (rqptr, WATCH_RESPONSE)) WatchThis (WATCHITM(rqptr), WATCH_RESPONSE, "DELETE !AZ depth:!UL path:!AZ", rqptr->ParseOds.ExpFileName, tkptr->ToDepth, DavWebPathAccess(rqptr)); DavDeleteBegin2 (rqptr); } /*****************************************************************************/ /* Entry point for MOVE using rename that needs to delete residual directory files. This function will be called multiple times as asynchronous testing of the DLM and meta locking occurs. */ DavDeleteBegin2 (REQUEST_STRUCT *rqptr) { BOOL IsDir; int status; WEBDAV_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavDeleteBegin2() !UL", rqptr->WebDavTaskPtr->TestLockState); tkptr = rqptr->WebDavTaskPtr; /***********/ /* 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, DavDeleteBegin2, 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); DavDeleteEnd (rqptr); return; } /* check for meta-lock on existing source */ tkptr->TestLockState++; DavLockTest (rqptr, rqptr->ParseOds.ExpFileName, false, DavDeleteBegin2, rqptr); return; case 2 : if (VMSnok (tkptr->TestLockStatus)) { DavWebResponse (rqptr, 423, 0, "source locked", FI_LI); DavDeleteEnd (rqptr); return; } } /**************/ /* not locked */ /**************/ if (rqptr->ParseOds.NamNameLength > 0 || rqptr->ParseOds.NamTypeLength > 1) { /* delete single file */ status = DavDeleteParse (rqptr, false, ";*"); tkptr->DeleteData.DelPhase = WEBDAV_DELETE_SINGLE_FILE; } else { /* delete collection */ status = DavDeleteParse (rqptr, true, "...]*.*;*"); tkptr->DeleteData.DelPhase = WEBDAV_DELETE_TREE_FILES; /* set counter that will be used when time to delete directory tree */ tkptr->DeleteData.TreeDirCount = -1; } if (VMSnok (status)) { DavWebResponse (rqptr, 0, status, NULL, FI_LI); DavWebEnd (rqptr); return; } DavDeleteSearch (rqptr); } /*****************************************************************************/ /* As necessary, close the multistatus XML. */ DavDeleteEnd (REQUEST_STRUCT *rqptr) { int status; WEBDAV_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavDeleteEnd() !&F", &DavDeleteEnd); if (rqptr->rqHeader.Method == HTTP_METHOD_WEBDAV_MOVE) { /* have been cleaning up residual directories from move using copy */ DavMoveEnd (rqptr); return; } tkptr = rqptr->WebDavTaskPtr; if (WATCHING (rqptr, WATCH_WEBDAV)) if (tkptr->DeleteData.DirCount || tkptr->DeleteData.DirFailCount || tkptr->DeleteData.FileCount || tkptr->DeleteData.FileFailCount || tkptr->DeleteData.MetaCount || tkptr->DeleteData.MetaFailCount) WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "DELETED dir:!UL fail:!UL file:!UL fail:!UL meta:!UL fail:!UL", tkptr->DeleteData.DirCount, tkptr->DeleteData.DirFailCount + tkptr->DeleteData.TreeDirNotEmptyCount, tkptr->DeleteData.FileCount, tkptr->DeleteData.FileFailCount, tkptr->DeleteData.MetaCount, tkptr->DeleteData.MetaFailCount); if (rqptr->rqHeader.Method == HTTP_METHOD_WEBDAV_MOVE) { /* move to different device via copy and delete */ DavDeleteEnd (rqptr); return; } /* if an 'empty' multistatus response was generated */ if (!rqptr->rqResponse.HttpStatus) DavWebResponse (rqptr, 204, 0, "successful delete", FI_LI); else if (rqptr->rqResponse.HttpStatus == 207) FaoToNet (rqptr, "\n"); DavWebEnd (rqptr); } /*****************************************************************************/ /* AST function to invoke another $SEARCH call. */ DavDeleteSearch (REQUEST_STRUCT *rqptr) { WEBDAV_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavDeleteSearch() !&F !&S", &DavDeleteSearch, rqptr->NetIoPtr->WriteStatus); tkptr = rqptr->WebDavTaskPtr; sys$setprv (1, &SysPrvMask, 0, 0); OdsSearch (&tkptr->SearchOds, &DavDeleteSearchAst, rqptr); sys$setprv (0, &SysPrvMask, 0, 0); } /*****************************************************************************/ /* AST from completed $SEARCH. */ DavDeleteSearchAst (REQUEST_STRUCT *rqptr) { int status, DirectoryFileLength; char DirectoryFile [ODS_MAX_FILE_NAME_LENGTH+1]; WEBDAV_TASK *tkptr; /*********/ /* begin */ /*********/ #if WATCH_MOD HttpdCheckPriv (FI_LI); #endif /* WATCH_MOD */ if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavDeleteSearchAst() !&F sts:!&S stv:!&S", &DavDeleteSearchAst, rqptr->WebDavTaskPtr->SearchOds.Fab.fab$l_sts, rqptr->WebDavTaskPtr->SearchOds.Fab.fab$l_stv); if (rqptr->RequestState >= REQUEST_STATE_ABORT) { DavDeleteEnd (rqptr); return; } tkptr = rqptr->WebDavTaskPtr; if (VMSnok (status = tkptr->SearchOds.Fab.fab$l_sts)) { if (status == RMS$_NMF || status == RMS$_FNF) { /*****************/ /* no more files */ /*****************/ if (tkptr->DeleteData.DelPhase == WEBDAV_DELETE_SINGLE_FILE) { /***************/ /* single file */ /***************/ DavWebResponse (rqptr, 0, status, NULL, FI_LI); DavDeleteEnd (rqptr); return; } if (tkptr->DeleteData.DelPhase == WEBDAV_DELETE_TREE_FILES) tkptr->DeleteData.DelPhase = WEBDAV_DELETE_TREE_DIRS; if (tkptr->DeleteData.DelPhase == WEBDAV_DELETE_TREE_DIRS) { /**************************/ /* now delete directories */ /**************************/ /* may require multiple passes */ if (tkptr->DeleteData.TreeDirCount) { /* reset counter which if non-zero results in another pass */ tkptr->DeleteData.TreeDirCount = tkptr->DeleteData.TreeDirNotEmptyCount = 0; status = DavDeleteParse (rqptr, true, "...]*.DIR;*"); if (VMSnok (status)) { ErrorNoticed (rqptr, status, NULL, FI_LI); DavDeleteEnd (rqptr); return; } DavDeleteSearch (rqptr); return; } /* otherwise fall through to delete parent */ } if (tkptr->DeleteData.DelPhase == WEBDAV_DELETE_TREE_DIRS) { /*********************/ /* now delete parent */ /*********************/ tkptr->DeleteData.DelPhase = WEBDAV_DELETE_TREE_PARENT; /* get name of directory in parent */ OdsNameOfDirectoryFile (rqptr->ParseOds.NamDevicePtr, rqptr->ParseOds.NamNamePtr - rqptr->ParseOds.NamDevicePtr, DirectoryFile, &DirectoryFileLength); OdsParse (&rqptr->ParseOds, DirectoryFile, DirectoryFileLength, NULL, 0, NULL, NULL, rqptr); if (VMSok (status = rqptr->ParseOds.Fab.fab$l_sts)) if (VMSok (status = OdsParseTerminate (&rqptr->ParseOds))) status = DavDeleteParse (rqptr, false, ";*"); if (VMSnok(status)) { ErrorNoticed (rqptr, status, NULL, FI_LI); DavDeleteEnd (rqptr); return; } DavDeleteSearch (rqptr); return; } if (tkptr->DeleteData.DelPhase == WEBDAV_DELETE_TREE_PARENT) { /***************/ /* end of task */ /***************/ DavDeleteEnd (rqptr); return; } ErrorNoticed (rqptr, SS$_BUGCHECK, NULL, FI_LI); return; } DavWebMultiStatus (rqptr, status, tkptr->SearchOds.ExpFileName); } if (tkptr->DeleteData.DelPhase == WEBDAV_DELETE_TREE_FILES) { if (tkptr->SearchOds.NamTypeLength == 4 && (MATCH5 (tkptr->SearchOds.NamTypePtr, ".DIR;") || MATCH5 (tkptr->SearchOds.NamTypePtr, ".dir;"))) { status = OdsReallyADir (rqptr, &tkptr->SearchOds); if (VMSok (status)) { /* this is a directory file, search for next */ DavDeleteSearch (rqptr); return; } } } else if (tkptr->DeleteData.DelPhase == WEBDAV_DELETE_TREE_DIRS || tkptr->DeleteData.DelPhase == WEBDAV_DELETE_TREE_PARENT) { status = OdsReallyADir (rqptr, &tkptr->SearchOds); if (VMSnok (status)) { /* NOT a directory file (bit strange, should be deleted above) */ tkptr->DeleteData.FileFailCount++; ErrorNoticed (rqptr, SS$_BUGCHECK, NULL, FI_LI); DavDeleteSearch (rqptr); return; } } if (DavMetaFile (&tkptr->SearchOds) || DavMetaDir (rqptr, &tkptr->SearchOds)) { /* this is a metadata file or contains a metadata (sub)directory */ DavDeleteSearch (rqptr); return; } tkptr->TestLockState = 0; DavDeleteErase (rqptr); } /*****************************************************************************/ /* Remove the file and any associated metadata. Remove metadata contingent on the file successfully erased, unless it's an actual directory file in which case the metadata and any container directory need to be removed prior. */ void DavDeleteErase (REQUEST_STRUCT *rqptr) { BOOL IsDir; int status, DeleteStatus, EraseCount; char *cptr, *sptr, *zptr; ODS_STRUCT DeleteOds; WEBDAV_META *mtaptr; WEBDAV_TASK *tkptr; /*********/ /* begin */ /*********/ #if WATCH_MOD HttpdCheckPriv (FI_LI); #endif /* WATCH_MOD */ tkptr = rqptr->WebDavTaskPtr; if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavDeleteErase() sts:!&S stv:!&S !&Z", tkptr->SearchOds.Fab.fab$l_sts, tkptr->SearchOds.Fab.fab$l_stv, tkptr->SearchOds.ResFileName); /* when move using copy residual directory deletion do not check locks */ if (rqptr->rqHeader.Method != HTTP_METHOD_WEBDAV_MOVE) { if (tkptr->TestLockState++ == 0) { /* asynchronous test for lock then AST straight back to check */ if (tkptr->DeleteData.DelPhase == WEBDAV_DELETE_TREE_DIRS) DavLockTest (rqptr, tkptr->SearchOds.ResFileName, true, DavDeleteErase, rqptr); else DavLockTest (rqptr, tkptr->SearchOds.ResFileName, false, DavDeleteErase, rqptr); return; } else { if (VMSnok (tkptr->TestLockStatus)) { tkptr->DeleteData.FileFailCount++; /* just report the lock in the 207 and continue */ if (tkptr->ToDepth == WEBDAV_DEPTH_INFINITY) DavWebMultiStatus (rqptr, RMS$_FLK, rqptr->ParseOds.ResFileName); else DavWebResponse (rqptr, 0, RMS$_FLK, NULL, FI_LI); DavDeleteSearch (rqptr); return; } } } mtaptr = &rqptr->WebDavTaskPtr->MetaData; mtaptr->TaskPtr = rqptr->WebDavTaskPtr; /***************/ /* delete file */ /***************/ OdsStructInit (&DeleteOds, true); OdsParse (&DeleteOds, tkptr->SearchOds.ResFileName, tkptr->SearchOds.ResFileNameLength, NULL, 0, NAM$M_SYNCHK, NULL, rqptr); if (VMSnok (status = DeleteOds.Fab.fab$l_sts)) { if (tkptr->ToDepth == WEBDAV_DEPTH_INFINITY) DavWebMultiStatus (rqptr, status, rqptr->ParseOds.ResFileName); else DavWebResponse (rqptr, 0, status, NULL, FI_LI); DavDeleteSearch (rqptr); return; } DeleteOds.NamVersionPtr[0] = '\0'; if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "!&Z", DeleteOds.ExpFileName); DavMetaName (mtaptr, DeleteOds.ExpFileName, 'r'); /* if it's a directory file then it likely has a metadata directory */ if (IsDir = (MATCH5 (DeleteOds.NamTypePtr, ".DIR") || MATCH5 (DeleteOds.NamTypePtr, ".dir"))) { /* need to delete metadata prior to deleting the directory file */ DavDeleteMetaErase (mtaptr); DavMetaDeleteDir (mtaptr); } AuthAccessEnable (rqptr, DeleteOds.ExpFileName, AUTH_ACCESS_WRITE); EraseCount = 0; while (VMSok (status = sys$erase (&DeleteOds.Fab, 0, 0))) { EraseCount++; if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "!UL sys$erase() !&S", EraseCount, status); } if (status == RMS$_FNF && EraseCount) status = SS$_NORMAL; AuthAccessEnable (rqptr, 0, 0); if (VMSnok (status) && DeleteOds.Fab.fab$l_stv) status = DeleteOds.Fab.fab$l_stv; DeleteStatus = status; if (WATCHING (rqptr, WATCH_WEBDAV)) if (VMSok (status)) WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "DELETED !AZ", DeleteOds.ExpFileName); else WatchThis (WATCHITM(rqptr), WATCH_WEBDAV, "DELETE !AZ !&S", DeleteOds.ExpFileName, status); if (tkptr->DeleteData.DelPhase == WEBDAV_DELETE_SINGLE_FILE || tkptr->DeleteData.DelPhase == WEBDAV_DELETE_TREE_FILES) { /* only need this expense when WATCHing */ if (WATCHING (rqptr, WATCH_WEBDAV)) { if (VMSok (status)) tkptr->DeleteData.FileCount++; else tkptr->DeleteData.FileFailCount++; } } else if (tkptr->DeleteData.DelPhase == WEBDAV_DELETE_TREE_DIRS || tkptr->DeleteData.DelPhase == WEBDAV_DELETE_TREE_PARENT) { if (VMSok (status)) { tkptr->DeleteData.DirCount++; tkptr->DeleteData.TreeDirCount++; } else if (status == SS$_DIRNOTEMPTY) tkptr->DeleteData.TreeDirNotEmptyCount++; else tkptr->DeleteData.DirFailCount++; } /* if not the directory file then delete metadata now */ if (!IsDir && VMSok (DeleteStatus)) DavDeleteMetaErase (mtaptr); /*************/ /* finish up */ /*************/ if (VMSnok (DeleteStatus) && DeleteStatus != SS$_DIRNOTEMPTY) { if (tkptr->ToDepth == WEBDAV_DEPTH_INFINITY) DavWebMultiStatus (rqptr, DeleteStatus, DeleteOds.ExpFileName); else DavWebResponse (rqptr, 0, DeleteStatus, NULL, FI_LI); } OdsParseRelease (&DeleteOds); DavWebDequeue (&tkptr->MetaData.DlmData); if (tkptr->DeleteData.DelPhase == WEBDAV_DELETE_SINGLE_FILE) { DavDeleteEnd (rqptr); return; } SysDclAst (DavDeleteSearch, rqptr); } /*****************************************************************************/ /* Remove the metadata file. The metadata read file name must already have been initialised using DavMetaName() before calling. */ int DavDeleteMetaErase (WEBDAV_META *mtaptr) { int status, EraseCount; ODS_STRUCT DeleteOds; REQUEST_STRUCT *rqptr; WEBDAV_TASK *tkptr; /*********/ /* begin */ /*********/ #if WATCH_MOD HttpdCheckPriv (FI_LI); #endif /* WATCH_MOD */ tkptr = mtaptr->TaskPtr; rqptr = tkptr->RequestPtr; if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavDeleteMetaErase() !&Z", mtaptr->ReadMetaName); OdsStructInit (&DeleteOds, true); OdsParse (&DeleteOds, mtaptr->ReadMetaName, mtaptr->ReadMetaNameLength, NULL, 0, NAM$M_SYNCHK, NULL, rqptr); /* delete any multiples */ if (VMSok (status = DeleteOds.Fab.fab$l_sts)) { DeleteOds.NamVersionPtr[0] = '\0'; sys$setprv (1, &SysPrvMask, 0, 0); EraseCount = 0; while (VMSok (status = sys$erase (&DeleteOds.Fab, 0, 0))) EraseCount++; if (status == RMS$_FNF && EraseCount) status = SS$_NORMAL; 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 (VMSok(status)) tkptr->DeleteData.MetaCount++; else if (status != RMS$_FNF && status != RMS$_DNF) { tkptr->DeleteData.MetaFailCount++; ErrorNoticed (rqptr, status, NULL, FI_LI); } } return (status); } /*****************************************************************************/ /* Copies the request file specification into the WebDAV task search specification, substituting the supplied wildcard specification according to whether it is a directory or individual file, and then parses the resulting search specification. Returns a VMS status value. */ int DavDeleteParse ( REQUEST_STRUCT *rqptr, BOOL IsDirectory, char *WildSpec ) { int status; char *cptr, *czptr, *sptr, *zptr; WEBDAV_TASK *tkptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_WEBDAV)) WatchThis (WATCHITM(rqptr), WATCH_MOD_WEBDAV, "DavDeleteParse() !&Z !&B !&Z", rqptr->ParseOds.NamDevicePtr, IsDirectory, WildSpec); tkptr = rqptr->WebDavTaskPtr; zptr = (sptr = tkptr->SearchSpec) + sizeof(tkptr->SearchSpec)-1; if (IsDirectory) czptr = rqptr->ParseOds.NamNamePtr - 1; else czptr = rqptr->ParseOds.NamVersionPtr; for (cptr = rqptr->ParseOds.NamDevicePtr; cptr < czptr && sptr < zptr; *sptr++ = *cptr++); for (cptr = WildSpec; *cptr && sptr < zptr; *sptr++ = *cptr++); if (sptr >= zptr) return (SS$_RESULTOVF); tkptr->SearchSpecLength = sptr - tkptr->SearchSpec; *sptr = '\0'; sys$setprv (1, &SysPrvMask, 0, 0); OdsParse (&tkptr->SearchOds, tkptr->SearchSpec, tkptr->SearchSpecLength, NULL, 0, 0, NULL, rqptr); sys$setprv (0, &SysPrvMask, 0, 0); if (VMSnok (status = tkptr->SearchOds.Fab.fab$l_sts)) return (status); return (SS$_NORMAL); } /*****************************************************************************/