/******************************************************************************\
********************************************************************************
* Filename: RmWorkerObj.cpp
*
* Description: Implementation of CRmWorkerObj -- main worker object
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/
#include "stdafx.h"
#include "RmWorker.h"
#defineINITGUID
#include "txdtc.h"
#include "txcoord.h"
#include "xolehlp.h"
#define IID_DEFINED
#include "SimpleLog.h"
#include "SimpleLog_i.c"
#include "action.h"
#include "copyfilerm.h"
#include "RmWorkerObj.h"
#pragma warning( disable : 4786) // disable the warning "identifier was truncated to '255' characters in the debug information" generated by STL
/////////////////////////////////////////////////////////////////////////////
// CRmWorkerObj
//---------------------------------------------------------------------
// CRmWorkerObj::CRmWorkerObj
// Constructor for a resource manager file operation object. This
// constructor performs three major fuctions:
//(1) Create and initialize log object.
//(2) Establish a connection with DTC transaction manager.
//(3) Get an RM interface object.
//
CRmWorkerObj::CRmWorkerObj () : CActionList()
{
m_pLog = NULL;
m_pIResMgr = NULL;
SetState (TX_UNINITIALIZED);
m_punkDTC = NULL;
m_pEnlist = NULL;
m_restartList = NULL;
m_hFinishedTxEvent = CreateEvent(NULL, FALSE, TRUE, NULL);
} // CRmWorkerObj:CRmWorkerObj()
CRmWorkerObj::~CRmWorkerObj(void)
{
// Set the state to uninitialized
SetState (TX_UNINITIALIZED);
if (m_pLog) // not initialized for Recover
{
ULONG lLogRefCount = m_pLog -> Release();
m_pLog = NULL;
}
// Disconnect from DTC
if (0 != m_punkDTC)
{
m_punkDTC->Release ();
m_punkDTC = 0;
} // if
// Release IResourceManager interface
if (0 != m_pIResMgr)
{
m_pIResMgr->Release ();
m_pIResMgr = 0;
} // if
if (m_pEnlist)
{
m_pEnlist -> Release();
m_pEnlist = 0;
}
if (m_hFinishedTxEvent)
{
CloseHandle(m_hFinishedTxEvent);
m_hFinishedTxEvent = NULL;
}
FILECPY*cpy;
BOOLfRc = FALSE;
FILECPYLIST::iterator i;
for (i=m_lstFiles.begin();i != m_lstFiles.end(); ++i)
{
cpy = *i;
delete cpy;
}
m_lstFiles.clear();
} // CRmWorkerObj::~CRmWorkerObj
STDMETHODIMP CRmWorkerObj::AddFile(BSTR sSource, BSTR sDestination)
{
FILECPY * cpy = new FILECPY;
cpy -> sSource = sSource;
cpy -> sDest = sDestination;
m_lstFiles.insert(m_lstFiles.end(), cpy);
return S_OK;
}
//
//interate through our list of files and copy each of them.
//if any copy fails, return an error, which will abort the tx
//
STDMETHODIMP CRmWorkerObj::CopyListTx()
{
FILECPYLIST::iterator item;
HRESULT hr;
FILECPY * cpy;
// Scan the list and recover incomplete transactions -- there should only
// be one in the single threaded case.
for (item=m_lstFiles.begin();item != m_lstFiles.end(); ++item)
{
cpy = *item;
hr = FileCopyTx(cpy -> sSource.m_str, cpy -> sDest.m_str, FALSE);
if (FAILED(hr))
return hr;
}
return S_OK;
}
STDMETHODIMP CRmWorkerObj::Init(IUnknown * pUnkLog, IUnknown * pUnkDtcResMgr, IUnknown *pUnkDtc, BSTR sRecDir)
{
//-----------------------------------------------------------------
// Set the state of the resource manager as unitialized
//
SetState (TX_UNINITIALIZED);
m_sRecoverDir = sRecDir;
// make sure I have a log...
_ASSERTE(pUnkLog);
HRESULT hr;
hr = pUnkLog -> QueryInterface(IID_ISimpleLog, (void **)&m_pLog);
if (FAILED(hr))
{
_ASSERTE(0);
return E_UNEXPECTED;
}
hr = pUnkDtcResMgr -> QueryInterface(IID_IResourceManager, (void**)&m_pIResMgr);
_ASSERTE(hr == S_OK);
m_punkDTC = pUnkDtc;
m_punkDTC -> AddRef();
// Change the state of the resource manager to reflect that it is
// initialized.
SetState (TX_INITIALIZING);
return hr;
}
STDMETHODIMP CRmWorkerObj::GetTransactionId(BSTR * guidTx)
{
LPOLESTR sTemp[64];
HRESULT hr = StringFromCLSID(m_guidTx, sTemp);
if (FAILED(hr))
{
_ASSERTE(0);
return hr;
}
*guidTx = SysAllocString(sTemp[0]);
return hr;
} // CRmWorkerObj::GetTransactionId()
//---------------------------------------------------------------------
// CRmWorkerObj class implementation:
//---------------------------------------------------------------------
STDMETHODIMP CRmWorkerObj::FileCopyTx(BSTR sFromFile, BSTR sToFile, BOOL bFailIfExists)
{
TXSTATEeTxState;
HANDLEhFind;
WIN32_FIND_DATAfindData;
BOOLfReturn;
// Get the current state of the transaction.
GetState (&eTxState);
// Check if TM is down
if (TX_TMDOWN == eTxState)
{
SetStatus (RM_STATUS_TM_DOWN);
_ASSERTE(0);
return E_FAIL;
} // if
// Ensure that a transaction is enlisted
if (TX_ENLISTED != eTxState)
{
SetStatus (RM_STATUS_INVALID_STATE);
return E_FAIL;
} // if
// Source of copy does not exist -- copy is erroneous.
hFind = FindFirstFile (sFromFile, &findData);
if (INVALID_HANDLE_VALUE == hFind)
{
FindClose (hFind);
SetStatus (RM_STATUS_FILE_NOT_FOUND);
return E_FAIL;
} // if
FindClose (hFind);
// a copy over itself is a NOOP
if (lstrcmpi(sFromFile, sToFile) == 0)
{
return S_OK;
}
hFind = FindFirstFile (sToFile, &findData);
if (INVALID_HANDLE_VALUE != hFind)
{
// The target file exists. if bFailIfExists, return an error
// TODO: add GetLastError() information
if (bFailIfExists)
{
FindClose (hFind);
return E_FAIL;
}
// do a transactional delete of the target file so that
// the file destruction is recorded in the log, then copy.
if (S_OK != FileDeleteTx (sToFile))
{
// File delete not successful -- therefore copy is not
// successful.
return E_FAIL;
} // if
} // if
FindClose (hFind);
CAction * pAction = new CAction(LOG_RM_PRIVATE, RM_COPY, sFromFile, sToFile);
fReturn = PushAction (pAction);
_ASSERTE (TRUE == fReturn);
if (FALSE == fReturn)
{
SetStatus (RM_STATUS_IN_MEMORY_LIST);
return E_FAIL;
} // if
HRESULT hr = WriteLog (m_guidTx, pAction);
if (hr != S_OK)
{
fReturn = RemoveAction ();
_ASSERTE (TRUE == fReturn);
SetStatus (RM_STATUS_LOG_ERROR);
return E_FAIL;
} // if
fReturn = CopyFile (sFromFile, sToFile, bFailIfExists);
if (TRUE != fReturn)
{
fReturn = RemoveAction ();
_ASSERTE (TRUE == fReturn);
HRESULT hr = WriteLog (m_guidTx, LOG_RM_PRIVATE, RM_FORGET, 0, 0);
_ASSERTE (hr == S_OK);
// Undo the delete action
hr = UndoDelete ((TCHAR *)pAction -> m_sPath1, (TCHAR *)pAction -> m_sPath2);
_ASSERTE (S_OK == hr);
// Log the UNDO as a forget action.
hr = WriteLog (m_guidTx, LOG_RM_PRIVATE, RM_FORGET, 0, 0);
_ASSERTE (hr == S_OK);
// Remove the delete log action from the in-memory action list.
fReturn = RemoveAction ();
_ASSERTE (TRUE == fReturn);
SetStatus (RM_STATUS_COPY_FAILURE);
return E_FAIL;
} // if
// copy succeeded -- open the file to create a lock on the file
pAction -> LockFile(sToFile);
_ASSERTE(pAction -> m_hLock);
return(S_OK);
} // CRmWorkerObj::FileCopy
//
//built up fromt he FileCopyTx and FileDeleteTx primitives
//
STDMETHODIMP CRmWorkerObj::FileMoveTxt(BSTR sSource, BSTR sDestination)
{
HRESULT hr;
// copy the file
hr = FileCopyTx(sSource, sDestination, FALSE);
if (FAILED(hr))
return hr;
// delete the file
return FileDeleteTx(sSource);
} // CRmWorkerObj::FileMoveTx
STDMETHODIMP CRmWorkerObj::FileDeleteTx(BSTR sFileName)
{
BOOLfReturn;
TXSTATE eTxState;
TCHAR*pszFileName = 0;
TCHARsTmpFile[MAX_PATH];
// Get transaction state.
GetState (&eTxState);
// Check if TM is down return with TM Down status.
if (TX_TMDOWN == eTxState)
{
SetStatus (RM_STATUS_TM_DOWN);
return E_FAIL;
} // if
// Ensure that a transaction is enlisted
if (TX_ENLISTED != eTxState)
{
SetStatus (RM_STATUS_INVALID_STATE);
return E_FAIL;
} // if
memset(sTmpFile, NULL, MAX_PATH);
GetTempFile(sTmpFile);
_ASSERTE (0 != sTmpFile);
CAction * pAction = new CAction(LOG_RM_PRIVATE,RM_DELETE, sFileName, sTmpFile);
fReturn = PushAction (pAction);
_ASSERTE (TRUE == fReturn);
HRESULT hr = WriteLog (m_guidTx, LOG_RM_PRIVATE, RM_DELETE, sFileName, sTmpFile);
if (FAILED(hr))
{
_ASSERTE (0);
fReturn = RemoveAction ();
_ASSERTE (TRUE == fReturn);
SetStatus (RM_STATUS_LOG_ERROR);
return E_FAIL;
} // if
fReturn = MoveFileEx (sFileName,
sTmpFile,
MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED);
if (TRUE != fReturn)
{
fReturn = RemoveAction ();
_ASSERTE (TRUE == fReturn);
hr = WriteLog (m_guidTx, LOG_RM_PRIVATE,RM_FORGET, 0, 0);
_ASSERTE(hr == S_OK);
SetStatus (RM_STATUS_MOVE_FAILURE);
return E_FAIL;
} // if
#ifdef _DEBUG // testing PeekAction
pAction = NULL;
PeekAction(&pAction);
_ASSERTE(pAction);
_ASSERTE(pAction -> m_eFileOp == RM_DELETE);
#endif
pAction -> LockFile(sTmpFile);
_ASSERTE(pAction -> m_hLock);
return S_OK;
} // CRmWorkerObj::FileDelete
STDMETHODIMP CRmWorkerObj::FileRenameTx(BSTR sFromFile, BSTR sToFile)
{
TXSTATEeTxState;
HANDLEhFind;
WIN32_FIND_DATA findData;
BOOLfReturn;
// Get transaction state.
GetState (&eTxState);
// Check if TM is down return with TM Down status.
if (TX_TMDOWN == eTxState)
{
SetStatus (RM_STATUS_TM_DOWN);
return E_FAIL;
} // if
// Ensure that a transaction is enlisted
if (TX_ENLISTED != eTxState)
{
SetStatus (RM_STATUS_INVALID_STATE);
return E_FAIL;
} // if
// Check if target path exists.
hFind = FindFirstFile (sToFile, &findData);
if (INVALID_HANDLE_VALUE != hFind)
{
FindClose (hFind);
SetStatus (RM_STATUS_OVERWRITE_EXISTING);
return E_FAIL;
} // if
FindClose (hFind);
CAction * pAction = new CAction(LOG_RM_PRIVATE, RM_RENAME, sFromFile, sToFile);
fReturn = PushAction (pAction);
_ASSERTE (TRUE == fReturn);
HRESULT hr = WriteLog (m_guidTx, pAction);
if (hr != S_OK)
{
fReturn = RemoveAction ();
_ASSERTE (TRUE == fReturn);
SetStatus (RM_STATUS_LOG_ERROR);
return E_FAIL;
} // if
fReturn = MoveFileEx( sFromFile,
sToFile,
MOVEFILE_COPY_ALLOWED );
if (TRUE != fReturn)
{
fReturn = RemoveAction ();
_ASSERTE (TRUE == fReturn);
HRESULT hr = WriteLog (m_guidTx, RM_FORGET, RM_NONE, 0, 0);
_ASSERTE (hr == S_OK);
SetStatus (RM_STATUS_RENAME_FAILURE);
return E_FAIL;
} // if
return S_OK;
} // CRmWorkerObj::FileRename
//
//called by the RM when it is time to enlist in a Tx
//
//this can be called with multiple tx's in the same instance
//
STDMETHODIMP CRmWorkerObj::ExportTx(ULONG cbTranCookie, BYTE *rgbTranCookie)
{
HRESULThRc;
ITransactionImport*pTxImport;
// if this worker is in the process of aborting, wait until it is done
WaitTxDone();
TXSTATEeTxState;
GetState (&eTxState);
// Ensure that the resource manager is initializing
// it is possible that everything was OK up to this point
// but we blocked waiting for an Commit or an Abort to complete
// and then the TM went down
if (TX_INITIALIZING != eTxState)
{
SetStatus (RM_STATUS_INVALID_STATE);
return E_FAIL;
} // if
// Create and initialize transaction completion event handle.
// Get a pointer to the ITransactionImport interface.
hRc = m_punkDTC->QueryInterface (IID_ITransactionImport,
(LPVOID *) &pTxImport);
if (S_OK != hRc)
{
pTxImport = 0;
SetStatus (RM_STATUS_INTERFACE);
return E_FAIL;
} // if
m_pITx = 0;
hRc = pTxImport->Import (cbTranCookie,
rgbTranCookie,
(GUID *) &IID_ITransaction,
(void **) &m_pITx);
_ASSERTE (0 != m_pITx);
if (S_OK != hRc)
{
pTxImport->Release ();
pTxImport = 0;
SetStatus (RM_STATUS_IMPORT_ERROR);
return E_FAIL;
} // if
SafeRelease(pTxImport);
// The resource manager is initialized for the transaction.
SetState (TX_INITIALIZED);
return EnlistTx();
} // CRmWorkerObj::ExportTx
HRESULT CRmWorkerObj::EnlistTx()
{
HRESULThRc;
BOOLfRc;
// Ensure that the resource manager is initialized before enlisting
// transaction.
TXSTATEeTxState;
GetState (&eTxState);
if (TX_INITIALIZED != eTxState)
{
SetStatus (RM_STATUS_INVALID_STATE);
return E_FAIL;
} // if
// Create a transaction resource async object for hadling 2PC
// requests.
ITransactionResourceAsync * ptxRmAsync = NULL;
GetUnknown() -> QueryInterface(IID_ITransactionResourceAsync, (void**)&ptxRmAsync);// does an AddRef that DTC should Release()
_ASSERTE (0 != ptxRmAsync);
//
// Enlist on the transaction
//
//if m_pEnlist != NULL, that means that we are reinlisting on a new
//tx -- so we are going to get rid of this m_pEnlist and get a new one
//
if (m_pEnlist)
{
m_pEnlist -> Release();
m_pEnlist = 0;
}
hRc = m_pIResMgr->Enlist (m_pITx,
ptxRmAsync,
&m_guidTx,
&m_isoLevel,
&m_pEnlist);
if (S_OK != hRc)
{
_ASSERTE(0);
// Transaction enlistment failed -- release resource async object
//and return enlistment failure
ptxRmAsync->Release();
ptxRmAsync = NULL;
SetStatus (RM_STATUS_ENLIST_ERROR);
return E_FAIL;
} // if
// Update log and in-memory list
CAction * pAction = new CAction(LOG_RM_XACT_BEGIN, RM_NONE, 0, 0);
fRc = PushAction(pAction);
_ASSERTE (TRUE == fRc);
hRc = WriteLog (m_guidTx, pAction);
_ASSERTE (hRc == S_OK);
// Set current state to reflect enlistment
SetState (TX_ENLISTED);
// release our Async interface -- DTC did an AddRef for us
SafeRelease(ptxRmAsync);
return S_OK;
} // CRmWorkerObj::EnlistTx
HRESULT CRmWorkerObj::UndoCopy(TCHAR *sFromFile, TCHAR *sToFile)
{
BOOLfReturn;
fReturn = DeleteFile( sToFile );
if (TRUE != fReturn)
{
SetStatus (RM_STATUS_UNDO_ERROR);
return E_FAIL;
} // if
return S_OK;
} // CRmWorkerObj::UndoCopy
HRESULT CRmWorkerObj::UndoMove(TCHAR *sFromFile, TCHAR *sToFile)
{
BOOLfReturn;
fReturn = MoveFileEx( sToFile,
sFromFile,
MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED);
if (TRUE != fReturn)
{
SetStatus (RM_STATUS_UNDO_ERROR);
return E_FAIL;
} // if
return S_OK;
} // CRmWorkerObj::UndoMove
//
//move the file frome the temporary location back to its original,
// and then delete the temp file
//
HRESULT CRmWorkerObj::UndoDelete(TCHAR *sFileName, TCHAR *sTmpFile)
{
BOOLfReturn;
fReturn = MoveFileEx (sTmpFile,
sFileName,
MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED);
if (TRUE != fReturn)
{
_ASSERTE(0);
SetStatus (RM_STATUS_UNDO_ERROR);
return E_FAIL;
} // if
return S_OK;
} // CRmWorkerObj::UndoDelete()
HRESULT CRmWorkerObj::UndoRename(TCHAR *sFromFile, TCHAR *sToFile)
{
BOOLfReturn;
fReturn = MoveFileEx (sToFile,
sFromFile,
MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED);
if (TRUE != fReturn)
{
SetStatus (RM_STATUS_UNDO_ERROR);
return E_FAIL;
} // if
return S_OK;
} // CRmWorkerObj::UndoRename()
HRESULT CRmWorkerObj::RollbackTx (void)
{
BOOLfReturn = FALSE;
HRESULThr;
CAction*pAction = NULL;
ACTIONLIST::iterator item;
// scan through the list and undo each operation
// for (item = m_plistAction -> begin();item != m_plistAction -> end(); ++item)
for (item = begin();item != end(); ++item)
{
pAction = *item;
pAction -> ReleaseLock(); // we need to close the destination so that we can delete it if we have to
switch (pAction -> m_eFileOp)
{ // For each RM command take undo action.
case RM_COPY:
hr = UndoCopy (pAction -> m_sPath1, pAction -> m_sPath2);
break;
case RM_DELETE:
hr = UndoDelete (pAction -> m_sPath1, pAction -> m_sPath2);
break;
case RM_RENAME:
hr = UndoRename (pAction -> m_sPath1, pAction -> m_sPath2);
break;
case RM_MOVE:
hr = UndoMove (pAction -> m_sPath1, pAction -> m_sPath2);
break;
case RM_NONE: // it is a TX oriented item (such as BEING_TX, and no rollback needed
hr = S_OK;
break;
default:
_ASSERTE (FALSE);
SetStatus (RM_STATUS_LOG_ERROR);
hr = E_FAIL;
} // switch
if (S_OK == hr)
{
HRESULT hr = WriteLog (m_guidTx, LOG_RM_PRIVATE, RM_UNDO, 0, 0);
if (hr != S_OK)
{
_ASSERTE(0);
SetStatus (RM_STATUS_LOG_ERROR);
hr = E_FAIL;
break;
} // if
} // if
else
{
OutputDebugString(_TEXT("RMWORKER: RollbackTx failed!\n"));
_ASSERTE(0);
}
} // for
if (S_OK == hr)
{
// Write undo end record.
HRESULT hr = WriteLog (m_guidTx, LOG_RM_PRIVATE, RM_UNDO_END, 0, 0);
if (hr != S_OK)
{
_ASSERTE(0);
SetStatus (RM_STATUS_LOG_ERROR);
hr = E_FAIL;
} // if
} // if
return hr;
} // CRmWorkerObj::RollbackTx()
//
//
//
//
RECOVERYRC CRmWorkerObj::RecoverTx (IStream * pStream)
{
RECOVERYRCeRecoverRc;
RECOVERY_RECORD*precTxn;
CAction*pAction;
XACTSTATxactOutcome;
ULONGulTimeout = XACTCONST_TIMEOUTINFINITE;
ULONGcbPrepareInfo;
BYTE*pbPrepareInfo;
HRESULThRc;
HRESULTret;
HRESULThr;
eRecoverRc = RecMapTransactions (pStream);
if (REC_S_SUCCESS != eRecoverRc)
{
return eRecoverRc;
} // if
// Create a list iterator
RECOVERY_LIST::iterator crecIter;
// Scan the list and recover incomplete transactions -- there should only
// be one in the single threaded case.
for (crecIter=m_restartList -> begin();crecIter != m_restartList -> end(); ++crecIter)
{
precTxn = *crecIter;
switch (precTxn->eRecState)
{
case REC_STATE_ACTIVE:
m_guidTx = precTxn->guidTxn;
ResetActions (precTxn->actionList);
ret = AbortTx ();
if (REC_S_SUCCESS != ret)
{
_ASSERTE(0);
return REC_E_UNDO_INCOMPLETE;
} // if
break;
case REC_STATE_ABORTING:
m_guidTx = precTxn->guidTxn;
ResetActions (precTxn->actionList);
ret = RollbackTx();
if (REC_S_SUCCESS != ret)
{
_ASSERTE(0);
return REC_E_UNDO_INCOMPLETE;
} // if
CleanupAction ();
break;
case REC_STATE_DORMANT:
hr = WriteLog (precTxn->guidTxn, LOG_RM_XACT_END, RM_NONE, 0, 0);
if (hr != S_OK)
{
_ASSERTE(0);
return REC_E_LOG_FAILURE;
} // if
break;
case REC_STATE_INDOUBT:
// Must re-enlist with MS-DTC to determine the transaction outcome.
precTxn->actionList->PeekAction (&pAction);
if (LOG_RM_XACT_PREPARE != pAction -> m_eRmCommand)
{
_ASSERTE(0);
return REC_E_INDOUBT_INVALID;
} // if
// Reenlist with MS DTC to determine the outcome of the
// in-doubt transaction.
swscanf ((TCHAR *)pAction -> m_sPath1, L"%ld", &cbPrepareInfo);
pbPrepareInfo = (BYTE *) pAction -> m_sPath2;
hRc = m_pIResMgr->Reenlist (pbPrepareInfo,
cbPrepareInfo,
ulTimeout,
&xactOutcome);
if (S_OK != hRc)
{
return REC_E_REENLIST;
} // if
// Reenlistment is successful -- act on transaction outcome.
m_guidTx = precTxn->guidTxn;
precTxn->actionList->RemoveAction ();
// delete the prepare action.
ResetActions (precTxn->actionList);
if (XACTSTAT_ABORTED == xactOutcome)
{
ret = AbortTx ();
}
else if (XACTSTAT_COMMITTED == xactOutcome)
{
ret = CommitTx ();
}
else if (XACT_E_CONNECTION_DOWN == hRc)
{
// Lost connection with MS DTC return TM down exception.
return REC_E_TM_DOWN;
}
else if (E_UNEXPECTED == hRc)
{
// Something unexpected happened -- presume abort.
ret = AbortTx ();
}
else
{// Problems during re-enlistment.
return REC_E_REENLIST;
} // if
// Check outcome of transaction commit and abort.
if (REC_S_SUCCESS != ret)
{
return REC_E_OUTCOMEACTION;
} // if
// Everything is OK.
hr = WriteLog (precTxn->guidTxn, LOG_RM_XACT_END, RM_NONE, 0, 0);
if (hr != S_OK)
{
_ASSERTE(0);
return REC_E_LOG_FAILURE;
} // if
// End the re-enlistment on the resource manager.
hRc = m_pIResMgr->ReenlistmentComplete ();
_ASSERTE (S_OK == hRc);
break;
case REC_STATE_COMPLETED:
break;
default:
_ASSERTE (FALSE);
return REC_E_STATE_UNKNOWN;
} // switch
} // for -- iteration through recover node list.
return REC_S_SUCCESS;
} // CRmWorkerObj::RecoverTx
//
//scan the stream and build an action list of actions to recover from
//the stream contains the GUID that is the TxID followed by n LOG_RM_ACTIONs
//
RECOVERYRC CRmWorkerObj::RecMapTransactions (IStream * pStream)
{
BOOLfRc = TRUE;
RECOVERY_RECORD *precTxn;
m_restartList = new RECOVERY_LIST;
_ASSERTE (0 != m_restartList);
if (0 == m_restartList)
{
return REC_E_NO_MEMORY;
} // if
CAction * pAction;
ULONG ulActionSize;
ULONG cbBytesRead;
HRESULT hr;
GUID guidTx;
LARGE_INTEGER liOff;
ULARGE_INTEGER liNewPosition;
LOG_RM_COMMAND eOp;
BYTE * pByte = NULL;
// get the Tx for this stream
LISet32(liOff, 0);
// seek to the end of the stream, and back up sizeof(LOG_RM_ACTION)
hr = pStream -> Seek(liOff, STREAM_SEEK_CUR, &liNewPosition);
hr = pStream -> Read(&guidTx, sizeof(guidTx), &cbBytesRead);
// read in the actions
do
{
hr = pStream -> Read(&eOp, sizeof(eOp), &cbBytesRead);
hr = pStream -> Read(&ulActionSize, sizeof(ULONG), &cbBytesRead);
_ASSERTE(hr == S_OK);
if (hr == S_OK && cbBytesRead)
{
pAction = new CAction; // we'll delete it if we do NOT push the action
_ASSERTE(sizeof(ulActionSize) == cbBytesRead);
pByte = new BYTE[ulActionSize];
hr = pStream -> Read(pByte, ulActionSize, &cbBytesRead);
_ASSERTE(cbBytesRead == ulActionSize);
hr = pAction -> Load(pByte, ulActionSize);
_ASSERTE(hr == S_OK);
delete [] pByte;
pByte = NULL;
precTxn = 0;
precTxn = RecGetTransaction (guidTx);
// Transaction should be on list.
if (0 == precTxn && LOG_RM_XACT_BEGIN != pAction -> m_eRmCommand)
{
// Ghost transaction on transaction list.
return REC_E_ORPHANED_TRANSACTION;
} // if
// Update action list for the transaction.
switch (pAction -> m_eFileOp)
{
case RM_COPY:
case RM_DELETE:
case RM_RENAME:
case RM_MOVE:
// Transaction must be active.
if (REC_STATE_ACTIVE != precTxn->eRecState)
{
return REC_E_INVALID_STATE;
} // if
fRc = precTxn->actionList->PushAction (pAction);
_ASSERTE (TRUE == fRc);
break;
case RM_UNDO:
// Transaction must be aborting for an undo to occur.
if (REC_STATE_ABORTING != precTxn->eRecState)
{
// Transaction end sequence error.
return REC_E_UNDO_SEQUENCE;
} // if
fRc = precTxn->actionList->RemoveAction (); // removes LAST action
delete pAction;
_ASSERTE (TRUE == fRc);
break;
case RM_FORGET:
// Transaction must be active for a forget to occur.
if (REC_STATE_ACTIVE != precTxn->eRecState)
{
// Transaction end sequence error.
return REC_E_FORGET_SEQUENCE;
} // if
fRc = precTxn->actionList->RemoveAction ();
delete pAction;
_ASSERTE (TRUE == fRc);
break;
case RM_UNDO_BEGIN:
if (REC_STATE_INDOUBT == precTxn->eRecState)
{
// Transaction not in-doubt remove the prepared record
// from the action list.
fRc = precTxn->actionList->RemoveAction ();
_ASSERTE (TRUE == fRc);
}
else if (REC_STATE_ACTIVE != precTxn->eRecState)
{
// Transaction end sequence error.
return REC_E_BEGINUNDO_SEQUENCE;
} // if
precTxn->eRecState = REC_STATE_ABORTING;
delete pAction;
break;
case RM_UNDO_END:
if (REC_STATE_ABORTING != precTxn->eRecState)
{
// Transaction transaction end undo error.
return REC_E_ENDUNDO_SEQUENCE;
} // if
precTxn->eRecState = REC_STATE_DORMANT;
delete pAction;
break;
case RM_NONE:
switch (pAction -> m_eRmCommand)
{
case LOG_RM_XACT_PREPARE:
// Transaction must be active.
if (REC_STATE_ACTIVE != precTxn->eRecState)
{
// Prepare sequence error.
return REC_E_PREPARE_SEQUENCE;
} // if
precTxn->eRecState = REC_STATE_INDOUBT;
fRc = precTxn->actionList->PushAction (pAction);
_ASSERTE (TRUE == fRc);
break;
case LOG_RM_XACT_BEGIN:
if (0 != precTxn)
{
// Starting a duplicate transaction.
return REC_E_TRANSACTION_DUPLICATED;
} // if
precTxn = new RECOVERY_RECORD;
_ASSERTE (0 != precTxn);
if (0 == precTxn)
{
// Space not allocated, return with a no memory error.
return REC_E_NO_MEMORY;
} // if
precTxn->guidTxn = guidTx;
precTxn->eRecState = REC_STATE_ACTIVE;
precTxn->actionList = new CActionList;
_ASSERTE (0 != precTxn->actionList);
if (0 == precTxn->actionList)
{
// Space not allocated for action list; return with no memory error.
return REC_E_NO_MEMORY;
} // if
fRc = precTxn->actionList->PushAction (pAction);
_ASSERTE (TRUE == fRc);
m_restartList->insert (m_restartList->begin(), precTxn);
break;
case LOG_RM_XACT_END:
// Transaction must be dormant -- either committed or aborted.
if (REC_STATE_DORMANT != precTxn->eRecState)
{
// Transaction end sequence error.
return REC_E_END_TRANSACTION_SEQUENCE;
} // if
precTxn->eRecState = REC_STATE_COMPLETED;
RecDiscardTransaction (guidTx);
delete pAction;
break;
case LOG_RM_XACT_COMMIT:
if (REC_STATE_INDOUBT == precTxn->eRecState)
{
// Transaction not in doubt -- remove prepare
// action from the action list.
fRc = precTxn->actionList->RemoveAction ();
_ASSERTE (TRUE == fRc);
}
else if (REC_STATE_ACTIVE != precTxn->eRecState)
{
// Transaction commit sequence error.
return REC_E_COMMIT_SEQUENCE;
} // if
precTxn->eRecState = REC_STATE_DORMANT;
delete pAction;
break;
default:
return REC_E_INVALID_LOG_RECORD;
} // inner switch
break;
default:
_ASSERTE(0); // invalid log
delete pAction;
return REC_E_INVALID_LOG_RECORD;
} // outer switch
}
} while ((hr == S_OK) && (cbBytesRead)); // while
// Transaction remapping successful return to recovery manager.
return REC_S_SUCCESS;
} // CRmWorkerObj::RecMapTransactions()
RECOVERY_RECORD *CRmWorkerObj::RecGetTransaction (GUID& guidTxn)
{
RECOVERY_LIST::iterator crecIter;
RECOVERY_RECORD*precTxn;
LONGuuidStatus;
for (crecIter=m_restartList->begin();crecIter != m_restartList -> end(); ++crecIter)
{
precTxn = *crecIter;
if (UuidEqual (&precTxn->guidTxn, &guidTxn, &uuidStatus))
{
return precTxn;
} // if
} // for
precTxn = 0;
return precTxn;
} // CRmWorkerObj::RecGetTransaction ()
void CRmWorkerObj::RecDiscardTransaction (GUID& guidTxn)
{
RECOVERY_LIST::iterator crecIter;
RECOVERY_RECORD*precTxn;
LONGuuidStatus;
for (crecIter=m_restartList -> begin();crecIter != m_restartList -> end(); ++crecIter)
{
if (UuidEqual (&(*crecIter)->guidTxn, &guidTxn, &uuidStatus))
{
precTxn = *crecIter;
m_restartList ->remove(precTxn);
delete precTxn->actionList;
delete precTxn;
return;
} // if
} // for
} // CRmWorkerObj::RecDiscardTransaction()
void CRmWorkerObj::RecoverCleanup (void)
{
RECOVERY_RECORD*precTxn = 0;
if (!m_restartList)
return;
while (TRUE != m_restartList->empty())
{
// get the first element
precTxn = m_restartList->front();
// delete it
delete precTxn;
// remove it from our list
m_restartList->pop_front();
precTxn = 0;
} // while
} // CRmWorkerObj::RecoverCleanup
void CRmWorkerObj::SetStatus (RMSTATUS ermStatus)
{
m_eStatus = ermStatus;
} // CRmWorkerObj::SetStatus()
RMSTATUS CRmWorkerObj::GetStatus (void)
{
return m_eStatus;
} // CRmWorkerObj::GetStatus()
//---------------------------------------------------------------------
// End of $Workfile: RmWorkerObj.cpp $
//---------------------------------------------------------------------
STDMETHODIMP CRmWorkerObj::PrepareRequest(BOOL fRetaining,
DWORD grfRM,
BOOL fWantMoniker,
BOOL fSinglePhase)
{
HRESULThr = S_OK;
TXSTATEeTxState;
GetState (&eTxState);
if (TX_ENLISTED != eTxState)
{
_ASSERTE(0);
// Set the state of the transaction as invalid.
SetState (TX_INVALID_STATE);
// Transaction is in an invalid state -- return error
hr = m_pEnlist->PrepareRequestDone (E_UNEXPECTED,NULL,NULL);
_ASSERTE (S_OK == hr);
if (S_OK != hr)
{
// TO DO: Print error message.
return E_FAIL;
} // if
return E_UNEXPECTED;
} // if
//-----------------------------------------------------------------
// Transaction is in its correct state -- indicate preparing
//
SetState (TX_PREPARING);
//-----------------------------------------------------------------
// SINGLE PHASE handling.
// Check if the MS DTC coordinator specifies single-phase
// optimization. If so, attempt to commit the transaction and
// respond with S_OK.
//
// If the commit fails, rollback the transaction and respond with
// E_FAIL.
//
if (TRUE == fSinglePhase)
{
hr = CommitTx ();
if (S_OK != hr)
{
hr = AbortTx();
_ASSERTE (S_OK == hr);
hr = m_pEnlist->PrepareRequestDone (E_FAIL,NULL,NULL);
_ASSERTE (S_OK == hr);
CleanupTx ();
SetState (TX_INVALID_STATE);
return E_FAIL;
} // if -- Commit unsuccessful
CleanupTx ();
_ASSERTE(m_pEnlist);
hr = m_pEnlist->PrepareRequestDone (XACT_S_SINGLEPHASE,
NULL,
NULL);
_ASSERTE (S_OK == hr);
if (S_OK != hr)
{
// Print error message
return E_FAIL;
} // if
return S_OK;
} // if -- Single Phase
//-----------------------------------------------------------------
// NORMAL PREPARE REQUEST:
//
hr = PrepareTx ();
if (S_OK != hr)
{
hr = AbortTx ();
_ASSERTE (S_OK == hr);
SetState (TX_ABORTED);
hr = m_pEnlist->PrepareRequestDone (E_FAIL, NULL, NULL);
_ASSERTE (S_OK == hr);
CleanupTx ();
return E_FAIL;
} // if -- Prepare failed.
// we MUST set the state to PREPARED before calling PrepareRequestDone
// otherwise you could get the second phase notification before the
// PrepareRequestDone() returns
SetState (TX_PREPARED);
// Prepare was successful -- return success
hr = m_pEnlist->PrepareRequestDone (S_OK, NULL, NULL);
_ASSERTE (S_OK == hr);
return S_OK;
} // CRmWorkerObj::PrepareRequest()
STDMETHODIMP CRmWorkerObj::CommitRequest(DWORD grfRM,
XACTUOW *pNewUOW)
{
HRESULThr = S_OK;
TXSTATEeTxState;
GetState (&eTxState);
if (TX_PREPARED != eTxState)
{
_ASSERTE(0);
hr = E_UNEXPECTED;
} // if
SetState (TX_COMMITTING);
hr = CommitTx ();
_ASSERTE (S_OK == hr);
if (S_OK != hr)
{
// TO DO: report heuristic damage - print error message
hr = E_FAIL;
} // if
SetState (TX_COMMITTED);
CleanupTx ();
_ASSERTE(m_pEnlist);
hr = m_pEnlist->CommitRequestDone (S_OK);
_ASSERTE (S_OK == hr);
//
//indicate we are done with the transaction
//
SetEvent(m_hFinishedTxEvent);
return S_OK;
} // CRmWorkerObj::CommitRequest()
STDMETHODIMP CRmWorkerObj::AbortRequest(BOID *pboidReason,
BOOL fRetaining,
XACTUOW *pNewUOW)
{
HRESULThr = S_OK;
TXSTATEeTxState;
GetState (&eTxState);
if (TX_PREPARED != eTxState &&
TX_ENLISTED != eTxState )
{
// TO DO: print warning message
_ASSERTE (0);
hr = E_UNEXPECTED;
} // if
SetState (TX_ABORTING);
hr = AbortTx ();
_ASSERTE (S_OK == hr);
if (S_OK != hr)
{
// TO DO: print error message
//printf("Abort failure!\n");
hr = E_FAIL;
} // if
SetState (TX_ABORTED);
CleanupTx ();
hr = m_pEnlist->AbortRequestDone (S_OK);
_ASSERTE (S_OK == hr);
//
//indicate we are done with the transaction
//
SetEvent(m_hFinishedTxEvent);
return S_OK;
} // CRmWorkerObj::AbortRequest()
//
//in this case we ignore this TMDown (which is the ITransactionEnlistmentAsync::TmDown())
//because there is exactly 1 and only 1 Tx per worker object, and we will be notified of
//this event in IResourceManagerSink::TmDown(). Normally, you do cleanup for this enlistment
//in this method
//
STDMETHODIMP CRmWorkerObj::TMDown(void)
{
SetState (TX_TMDOWN);
SafeRelease(m_punkDTC);
SafeRelease(m_pIResMgr);
SafeRelease(m_pITx);
SafeRelease(m_pLog);
SafeRelease(m_pEnlist);
SetEvent(m_hFinishedTxEvent); // if anybody is blocked on the event, they will return with the TX_TMDOWN state
return S_OK;
} // CRmWorkerObj::TMDown
//
//pLog:a pointer to the log
//
//
#define ENUM_GRANULARITY 10
STDMETHODIMP CRmWorkerObj::Recover(IStorage * pStorage)
{
_ASSERTE(pStorage);
IEnumSTATSTG * pEnum = NULL;
IStream * pStream = NULL;
HRESULT hr;
hr = pStorage -> EnumElements(NULL, NULL, NULL, &pEnum);
_ASSERTE(hr == S_OK);
STATSTG statstg[ENUM_GRANULARITY];
ULONG lFetched = 0;
ULONG i = 0;
RECOVERYRC eRecRc = REC_S_SUCCESS;
do
{
hr = pEnum -> Next(ENUM_GRANULARITY, statstg, &lFetched);
for (i=0; i<lFetched; i++)
{
// get the stream
pStream = NULL;
hr = pStorage -> OpenStream(
statstg[i].pwcsName, //Pointer to string containing name of stream to open
NULL, //Reserved
STGM_DIRECT | STGM_SHARE_EXCLUSIVE | STGM_READWRITE, //Access mode for the new stream
NULL, //Reserved
&pStream//Indirect pointer to opened stream object
);
_ASSERTE(hr == S_OK);
eRecRc = RecoverTx (pStream);
if (REC_S_SUCCESS != eRecRc)
{
_ASSERTE(0);
RecoverCleanup ();
SetStatus (RM_STATUS_RECOVERY_FAILURE);
return E_FAIL;
} // if
pStream -> Release();
} // end for
}while (lFetched == ENUM_GRANULARITY);
pEnum -> Release();
pStorage -> Release();
RecoverCleanup ();
return S_OK;
}
//
//"dust off" the Worker -- make sure that it is ready to go
//the next call we expect is an ExportTx()
//
STDMETHODIMP CRmWorkerObj::Reset()
{
#if 0
SafeRelease(m_pEnlist);
SafeRelease(m_pITx);
SetState (TX_INITIALIZING);
#endif
return S_OK;
}
HRESULT CRmWorkerObj::GetTempFile(TCHAR * pFileName)
{
GUID guid;
HRESULT hr;
hr = CoCreateGuid(&guid);
_ASSERTE(hr == S_OK);
OLECHAR szGUID[40];
int nRet = StringFromGUID2(guid, szGUID, 39);
_ASSERTE(nRet > 0);
lstrcat(pFileName, m_sRecoverDir.m_str);
lstrcat(pFileName, szGUID);
return S_OK;
}
//
//we write the LOG_RM_ACTION to the log
//
HRESULT CRmWorkerObj::WriteLog(GUID guidTx, LOG_RM_COMMAND eOp, FILE_OPERATION fOp, BSTR sFromFile, BSTR sToFile)
{
CAction action(eOp, fOp, sFromFile, sToFile);
return WriteLog(guidTx, &action);
}
HRESULT CRmWorkerObj::WriteLog(GUID txID, CAction * pAction)
{
BYTE * pByte = NULL;
ULONG cbSize = 0;
HRESULT hr = pAction -> Save(&pByte, &cbSize);
if (FAILED(hr))
return hr;
hr = m_pLog -> WriteLog(txID, pAction -> m_eRmCommand, pByte, cbSize);
delete [] pByte;
return hr;
}