FILERM.CPP


/******************************************************************************\
********************************************************************************
* Filename: FileRm.cpp
*
* Description: Implementation of CFileRm
*
* 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 "txdtc.h"
#include "txcoord.h"
#include "xolehlp.h"
#include "CopyFileRm.h"
#include "rmworker.h"
#include "simplelog.h"
#include "rmoptions.h"
#include "FileRm.h"
#include "rmdlg.h"
#define IID_DEFINED
#include "rmworker_i.c"

#include "rmsink.h"
#include "RmOptionsDlg.h"

#define CHECK_TM_DOWN() {if (IsTMDown()){if (InitializeRm() == S_OK)TmUp();else returnXACT_E_TMNOTAVAILABLE; }}

//
//a worker thread function to check if the TM comes back up
//

DWORD WINAPI TMDownWorkerThread( LPVOID pData);


//
//Normally if you want to have multiple instances of an RM, you would log a guid
//for each RM instance and reuse it for recovery
//

static GUID CopyFileRmGuid =
{ 0xf3ffa31a, 0x860a, 0x11d0, { 0xb1, 0x71, 0x0, 0xaa, 0x0, 0xba, 0x32, 0x58 } };


long glWorkers = 0;

STDMETHODIMP CFileRm::Connect(BSTR sAppName, long * ulConnHandle)
{

CHECK_TM_DOWN();

HRESULT hr;
IRmWorker * pWorker = NULL;
hr = CoCreateInstance(CLSID_CRmWorkerObj, NULL, CLSCTX_SERVER, IID_IRmWorker , (void **)&pWorker);
if (FAILED(hr))
{
_ASSERTE(0);
return hr;
}
hr = pWorker -> Init((IUnknown *)m_pLog, (IUnknown *)m_pIResMgr, (IUnknown *)m_punkDTC, m_options.GetRecDir());
if (FAILED(hr))
{
_ASSERTE(0);
pWorker -> Release();
return hr;
}

InterlockedIncrement(&glWorkers);
if (_Module.m_dlg)
_Module.m_dlg->SetUsageCount(glWorkers);

*ulConnHandle = (long) pWorker;
m_setWorkers.insert(*ulConnHandle);
return S_OK;
}
//
//Disconnect() works even if the TM is down, so we
//don't use the GetWorker() method
//
STDMETHODIMP CFileRm::Disconnect(long ulConnHandle)
{

IRmWorker * pWorker = NULL;
pWorker = (IRmWorker *)(*m_setWorkers.find(ulConnHandle));
if (!pWorker)
{
return E_FAIL;
}
ULONG lRef = pWorker -> Release();
pWorker = NULL;
InterlockedDecrement(&glWorkers);
if (_Module.m_dlg)
_Module.m_dlg->SetUsageCount(glWorkers);

int nEraseCount = m_setWorkers.erase(ulConnHandle);
_ASSERTE(nEraseCount == 1);
return S_OK;
}
//
//the whereabouts is the same for every worker, and is fixed for every instance
//of the RM
//
STDMETHODIMP CFileRm::GetTmWhereabouts(long lConHandle, BYTE **rgbTmAddr, ULONG *pcbTmAddr)
{

//
//NOTE: this is handle independent!
//

CHECK_TM_DOWN();
HRESULT hr = S_OK;
if (!m_cbTmAddr)
{

hr = GetMyTmWhereAbouts();
if (FAILED(hr))
return hr;
}

*pcbTmAddr = m_cbTmAddr;
*rgbTmAddr = (BYTE *) CoTaskMemAlloc (m_cbTmAddr);
memcpy(*rgbTmAddr, m_rgbTmAddr, m_cbTmAddr);
return hr;
}
STDMETHODIMP CFileRm::ExportTx(long lConHandle, ULONG cbTranCookie, BYTE *rgbTranCookie)
{

CHECK_TM_DOWN();
IRmWorker * pWorker = GetWorker(lConHandle);
if (!pWorker)
{
return E_INVALIDARG;
}
HRESULT hr = pWorker ->ExportTx(cbTranCookie, rgbTranCookie);
return hr;
}

STDMETHODIMP CFileRm::FileCopyTx(long lConHandle, BSTR sSource, BSTR sDestination, BOOL bFailIfExists)
{
CHECK_TM_DOWN();
IRmWorker * pWorker = GetWorker(lConHandle);
if (!pWorker)
{
return E_INVALIDARG;
}

HRESULT hr =pWorker -> FileCopyTx(sSource, sDestination, bFailIfExists);
return hr;
}

STDMETHODIMP CFileRm::FileRenameTx(long lConHandle, BSTR sSource, BSTR sDestination)
{

CHECK_TM_DOWN();
IRmWorker * pWorker = GetWorker(lConHandle);
if (!pWorker)
{
return E_INVALIDARG;
}

HRESULT hr =pWorker -> FileRenameTx(sSource, sDestination);
return hr;
}

STDMETHODIMP CFileRm::AddFile(long lConHandle, BSTR sSource, BSTR sDestination)
{

CHECK_TM_DOWN();
IRmWorker * pWorker = GetWorker(lConHandle);
if (!pWorker)
{
return E_INVALIDARG;
}

HRESULT hr =pWorker -> AddFile(sSource, sDestination);
return hr;
}

STDMETHODIMP CFileRm::CopyListTx(long lConHandle)
{

CHECK_TM_DOWN();
IRmWorker * pWorker = GetWorker(lConHandle);
if (!pWorker)
{
return E_INVALIDARG;
}

HRESULT hr = pWorker -> CopyListTx();
return hr;
}



STDMETHODIMP CFileRm::FileMoveTxt(long lConHandle, BSTR sSource, BSTR sDestination)
{

CHECK_TM_DOWN();
IRmWorker * pWorker = GetWorker(lConHandle);
if (!pWorker)
{
return E_INVALIDARG;
}

HRESULT hr = pWorker -> FileMoveTxt(sSource, sDestination);
return hr;
}

STDMETHODIMP CFileRm::FileDeleteTx(long lConHandle, BSTR sFileName)
{

CHECK_TM_DOWN();
IRmWorker * pWorker = GetWorker(lConHandle);
if (!pWorker)
{
return E_INVALIDARG;
}

HRESULT hr = pWorker -> FileDeleteTx(sFileName);
return hr;

}


STDMETHODIMP CFileRm::ResetConnection(long lConHandle)
{

CHECK_TM_DOWN();
IRmWorker * pWorker = GetWorker(lConHandle);
if (!pWorker)
{
return E_INVALIDARG;
}

return pWorker -> Reset();

}
//
//FinalConstruct()
//
//
HRESULT CFileRm::FinalConstruct()
{
HRESULT hRes;
hRes = OpenLog();
if (hRes == S_OK)
{
hRes = InitializeRm();
{
if (hRes == S_OK)
hRes = Recover();
}
}
return hRes;
}


//
// Perform any required recovery.
//
HRESULT CFileRm::Recover()
{
Lock();
IStorage * pStorage = NULL;
long lCount = 0;
HRESULT hr;
hr = m_pLog -> RecoveryInfo(&pStorage);
if (FAILED(hr))
{
Unlock();
return hr;
}

IRmWorker * pWorker = NULL;
hr = CoCreateInstance(CLSID_CRmWorkerObj, NULL, CLSCTX_SERVER, IID_IRmWorker , (void **)&pWorker);
if (FAILED(hr))
{
Unlock();
return hr;
}

BSTR sRecDir = m_options.GetRecDir();
_ASSERTE(sRecDir);
hr = pWorker -> Init(m_pLog, m_pIResMgr, m_punkDTC, sRecDir);
if (FAILED(hr))
{
Unlock();
return hr;
}
hr = pWorker -> Recover(pStorage);
Unlock();
SafeRelease(pWorker);
return hr;
}


HRESULT CFileRm::InitializeRm()
{
HRESULThr = E_FAIL;
IResourceManagerFactory*pIRmFactory= NULL;

//
// we make have a call to this originating from the Resource Dispenser
// and while we are in the call, the worker thread wakes up and calls it again
//

Lock();

if (m_punkDTC)
{
Unlock();
return S_OK;// while we were Locked, somebody successfully finished
}

//
// Establish contact with the MS DTC transaction manager.
//


hr = DtcGetTransactionManager (NULL,
NULL,
IID_IUnknown,
0,
0,
0,
(LPVOID *) &m_punkDTC);
if (S_OK != hr)
{

Unlock();
return hr;

}

//
//if I wan't caching the IUnknown * of DTC, I could ask for this
//interface directly from the DtcGetTransactionManager API, and
//avoid the QI
//
hr = m_punkDTC->QueryInterface(IID_IResourceManagerFactory,
(LPVOID *) &pIRmFactory);

_ASSERTE(hr == S_OK); // this can never fail


CComObject<CResourceManagerSink> * pSink = NULL;
hr = CComObject<CResourceManagerSink>::CreateInstance(&pSink);

pSink -> SetRm((IFileRm *)this);

IResourceManagerSink * pISink = NULL;
pSink -> QueryInterface(IID_IResourceManagerSink, (void **)&pISink);
_ASSERTE(pISink);

// Create instance of the resource manager interface.
hr = pIRmFactory->Create (&CopyFileRmGuid,
"CopyFileRm",
pISink,
&m_pIResMgr );
//
//DTC owns the reference (and lifetime) to the IResourceManagerSink interface
//

SafeRelease(pSink);

if (FAILED(hr))
{
SafeRelease(m_punkDTC);

}

SafeRelease(pIRmFactory);

Unlock();
return hr;



}


HRESULT CFileRm::OpenLog()
{


_Module.m_dlg = new CRmDlg(&m_options);
_ASSERTE(_Module.m_dlg);
HWND hWnd = _Module.m_dlg -> Create(NULL);
_Module.m_dlg->ShowWindow(SW_SHOWNORMAL);
_Module.m_dlg->SetUsageCount(1);
_ASSERTE(hWnd);

m_pLog = NULL;
HRESULT hr;
hr = CoCreateInstance(CLSID_CoSimpleLog, NULL, CLSCTX_SERVER, IID_ISimpleLog , (void **)&m_pLog);
if (FAILED(hr))
{
return hr;
}

CComBSTR sFileName = m_options.GetLogDir();
_ASSERTE(sFileName.m_str);
sFileName += L"FileRmLog";
hr = m_pLog -> Init(sFileName);
if (FAILED(hr))
return hr;

return hr;
}

HRESULT CFileRm::FinalRelease()
{
SafeRelease(m_punkDTC);
SafeRelease(m_pIResMgr);
return S_OK;
}
//
//the resource Manager's TM goes down
//
//we need to
//1. set a global variarable so we won't except any new connections
//2. free all RmWorkers (make sure they have a TMDown() event already
//3. start a worker thread and ping DTC until it comes up
//4. when DTC comes back up, do recovery and then accept new connections
//
//NOTE: You should NEVER block a DTC thread!
//
STDMETHODIMP CFileRm::TMDown()
{


m_bTMDown = TRUE;
WorkerSet::iterator item = m_setWorkers.begin();
IRmWorker * pWorker;
while (item != m_setWorkers.end())
{
pWorker = (IRmWorker *)(*item); // get the value
//
//if the Worker object was enlisted in DTC, then we
//are GUARANTEED that they will have gotten the TMDown()
//before the RM gets the TMDown(), but I want to make sure
//that all Workers get the TMDown, not just the enlisted ones.
//my implementation of TMDown() doesn't care if it is called
//twice, so rather than check the state and call TMDown on the
//ones that haven't gotten it yet, I'll call it for all workers
//

if (pWorker)
pWorker -> TMDown();
else
m_setWorkers.erase(item++);

item++;
}
if (m_cbTmAddr)
{
delete m_rgbTmAddr;
m_cbTmAddr = 0;
}

SafeRelease(m_punkDTC);
SafeRelease(m_pIResMgr);
SafeRelease(m_pLog);

DWORD dwThreadID;
HANDLE hThread;
hThread = CreateThread(NULL, 0, TMDownWorkerThread, (LPVOID)this, NULL, &dwThreadID);
return S_OK;
}

//
//Worker Thread that waits for the RM to come back up
//
DWORD WINAPI TMDownWorkerThread( LPVOID pData)
{


CFileRm * pRm = (CFileRm *)pData;
BOOL bGo= TRUE;
DWORD dwSleepTime = 1000 * 10;


while (bGo)
{

if (pRm -> InitializeRm() == S_OK)
bGo = FALSE;

Sleep(dwSleepTime);

}


HRESULT hRes;
hRes = pRm -> TmUp();
return hRes;

}
//
//called after the TM goes down -- resets everything to a good state
//
//
HRESULT CFileRm::TmUp()
{


HRESULT hRes;
hRes = OpenLog();
if (SUCCEEDED(hRes))
{
hRes = Recover();
}
if (SUCCEEDED(hRes))
{
_ASSERTE(GetLog());
_ASSERTE(GetIResourceManager());
_ASSERTE(GetDTCUnknown());

SetTMDown(FALSE);
WorkerSet::iterator item = m_setWorkers.begin();
IRmWorker * pWorker;
while (item != m_setWorkers.end())
{
pWorker = (IRmWorker *)(*item); // get the value
hRes = pWorker -> Init((IUnknown *)GetLog(), (IUnknown *)GetIResourceManager(), GetDTCUnknown(), m_options.GetRecDir());
_ASSERTE(hRes == S_OK);//this really can't fail...
item++;
}

}

hRes = GetMyTmWhereAbouts();

return hRes;

}


HRESULT CFileRm::GetMyTmWhereAbouts()
{

_ASSERTE(GetLog());
_ASSERTE(GetIResourceManager());
_ASSERTE(GetDTCUnknown());
ULONGcbUsed = 0;

ITransactionImportWhereabouts*pITxWhere = 0;

// Ensure that the resource manager is initializing

// Get a pointer to the ITransactionWhereabouts interface.
HRESULT hRc = m_punkDTC->QueryInterface (IID_ITransactionImportWhereabouts,
(LPVOID *) &pITxWhere);
_ASSERTE (S_OK == hRc);
if (S_OK != hRc)
{
pITxWhere = 0;
return hRc;
} // if

// Get the size of the whereabouts blob for the TM
pITxWhere->GetWhereaboutsSize (&m_cbTmAddr);
m_rgbTmAddr = new BYTE[m_cbTmAddr];
// Get the whereabouts blob
hRc = pITxWhere->GetWhereabouts (m_cbTmAddr, m_rgbTmAddr, &cbUsed);
SafeRelease(pITxWhere);
return hRc;
}