blob: ec98289923e5cb1021e55f93418e69fcfbed7744 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
//
// This module provides the backend recovery driver for Windows resource managers based on
// the IDtcToXaHelperSinglePipe interface. The dll is loaded (LoadLibrary) directly into DTC
// itself and runs at a different protection level from the resource manager instance, which
// runs inside the application.
//
// The DTC dynamically loads this file, calls GetXaSwitch() to access the XA interface
// implementation and unloads the dll when done.
//
// This DTC plugin is only called for registration and recovery. Each time the application
// registers the Qpid resource manager with DTC, the plugin is loaded and a successful
// connection via xa_open is confirmed before completing registration and saving the DSN
// connection string in the DTC log for possible recovery. On recovery, the DSN is re-used to
// re-establish a new connection with the broker and perform recovery.
//
// Because this plugin is not involved in coordinating any active transactions it only needs to
// partially implement the XA interface.
//
// For the same reason, the locking strategy is simple. A single global lock is used.
// Whenever networking activity is about to take place, the lock is relinquished and retaken
// soon thereafter.
#include <windows.h>
#include <transact.h>
#include <xolehlp.h>
#include <txdtc.h>
#include <xa.h>
#include "qpid/client/AsyncSession.h"
#include "qpid/client/Connection.h"
#include "qpid/framing/FieldValue.h"
#include <map>
#include <iostream>
#include <fstream>
namespace Apache {
namespace Qpid {
namespace DtcPlugin {
using namespace qpid::client;
using namespace qpid::framing;
using namespace qpid::framing::dtx;
class ResourceManager
{
private:
Connection qpidConnection;
Session qpidSession;
bool active;
std::string host;
int port;
std::string username;
std::string password;
bool ssl;
bool saslPlain;
int rmid;
std::vector<qpid::framing::Xid> inDoubtXids;
// current scan position, or -1 if no scan
int cursor;
public:
ResourceManager(int id, std::string h, int p, bool sslP, bool saslPlainP, std::string uname, std::string pass)
: rmid(id), host(h), port(p), ssl(sslP), saslPlain(saslPlainP), username(uname), password(pass),
active(false), cursor(-1) {}
~ResourceManager() {}
INT open();
INT close();
INT commit(XID *xid);
INT rollback(XID *xid);
INT recover(XID *xids, long count, long flags);
};
CRITICAL_SECTION rmLock;
std::map<int, ResourceManager*> rmMap;
HMODULE thisDll = NULL;
bool memLocked = false;
#define QPIDHMCHARS 512
void pinDll() {
if (!memLocked) {
char thisDllName[QPIDHMCHARS];
HMODULE ignore;
DWORD nc = GetModuleFileName(thisDll, thisDllName, QPIDHMCHARS);
if ((nc > 0) && (nc < QPIDHMCHARS)) {
memLocked = GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_PIN, thisDllName, &ignore);
}
}
}
void XaToQpid(XID &winXid, Xid &qpidXid) {
// convert from XA defined structure XID to the Qpid framing structure
qpidXid.setFormat((uint32_t) winXid.formatID);
int bqualPos = 0;
if (winXid.gtrid_length > 0) {
qpidXid.setGlobalId(std::string(winXid.data, winXid.gtrid_length));
bqualPos = winXid.gtrid_length;
}
if (winXid.bqual_length > 0) {
qpidXid.setBranchId(std::string(winXid.data + bqualPos, winXid.bqual_length));
}
}
// this function assumes that the qpidXid has already been validated for the memory copy
void QpidToXa(Xid &qpidXid, XID &winXid) {
// convert from the Qpid framing structure to the XA defined structure XID
winXid.formatID = qpidXid.getFormat();
const std::string& global_s = qpidXid.getGlobalId();
size_t gl = global_s.size();
winXid.gtrid_length = (long) gl;
if (gl > 0)
global_s.copy(winXid.data, gl);
const std::string branch_s = qpidXid.getBranchId();
size_t bl = branch_s.size();
winXid.bqual_length = (long) bl;
if (bl > 0)
branch_s.copy(winXid.data + gl, bl);
}
static char *dsnHeader = "QPIDdsnV2";
const char* nextDot(const char *p) {
while (*p && (*p != '.'))
p++;
return p;
}
int getHexChar (char c) {
if ((c >= '0') && (c <= '9'))
return c - '0';
if ((c >= 'a') && (c <= 'f'))
return 10 + (c - 'a');
if ((c >= 'A') && (c <= 'F'))
return 10 + (c - 'A');
return -1;
}
bool parseFromHex(const char* start, const char* end, std::string& target)
{
const char *p = start;
while ((p + 1) < end) {
int nibble = getHexChar(*p++);
if (nibble < 0)
return false;
int byte = (nibble << 4);
nibble = getHexChar(*p++);
if (nibble < 0)
return false;
byte += nibble;
target.append (1, (char) byte & 0xFF);
}
return (p == end);
}
// parse string from AmqpConnection::DataSourcename
// "QPIDdsnV2.port.host.instance_id.SSL_tf.SASL_mech.username.password"
//
// parse strictly and return false if the dsn is in a bad format
bool parseDsn (const char *dsn, std::string& host, int& port, bool& ssl, bool& saslPlain,
std::string& username, std::string& password) {
if (dsn == NULL)
return false;
size_t len = strnlen(dsn, 1025);
if (len > 1024)
return false;
if (strncmp(dsn, dsnHeader, strlen(dsnHeader)))
return false;
const char *endp = dsn + len;
const char *tokenp = dsn + strlen(dsnHeader);
if (*tokenp != '.')
return false;
// port
tokenp++;
if (tokenp >= endp)
return false;
if (*tokenp == '.')
return false; // null port not allowed
const char *token_end = nextDot(tokenp);
if ((token_end - tokenp) > 5)
return false;
port = 0;
for (const char *p = tokenp; p < token_end; p++) {
if ((*p < '0') || (*p > '9'))
return false;
port = (10 * port) + (*p - '0');
}
if (port > 65535)
return false;
// host
tokenp = token_end + 1;
if (tokenp >= endp)
return false;
if (*tokenp == '.')
return false; // null host not allowed
token_end = nextDot(tokenp);
if (!parseFromHex(tokenp, token_end, host))
return false;
// skip the RM identifier, but verify it exists
tokenp = token_end + 1;
if (tokenp >= endp)
return false;
token_end = nextDot (tokenp);
if ((token_end - tokenp) < 3)
return false;
// ssl: look for T or F
tokenp = token_end + 1;
if (tokenp >= endp)
return false;
if (*tokenp == 'T')
ssl = true;
else if (*tokenp == 'F')
ssl = false;
else
return false;
if (*++tokenp != '.')
return false;
// sasl mechanism: A = anonymous, P = plain. More to come...
++tokenp;
if (tokenp >= endp)
return false;
if (*(tokenp+1) != '.')
return false;
if (*tokenp == 'A') {
saslPlain = false;
tokenp += 2;
// no auth tokens
}
else if (*tokenp == 'P') {
saslPlain = true;
tokenp += 2;
if (tokenp >= endp)
return false;
token_end = nextDot (tokenp);
if (!parseFromHex(tokenp, token_end, username))
return false;
tokenp = token_end + 1;
if (tokenp >= endp)
return false;
token_end = nextDot (tokenp);
if (!parseFromHex(tokenp, token_end, password))
return false;
tokenp = token_end + 1;
}
else
return false;
return (tokenp == endp);
}
INT ResourceManager::open() {
INT rv = XAER_RMERR; // placeholder until we successfully connect to resource
active = true;
LeaveCriticalSection(&rmLock);
try {
ConnectionSettings settings;
settings.host = this->host;
settings.port = this->port;
if (ssl)
settings.protocol = "ssl";
if (saslPlain) {
settings.username = this->username;
settings.password = this->password;
settings.mechanism = "PLAIN";
}
qpidConnection.open(settings);
qpidSession = qpidConnection.newSession();
rv = XA_OK;
/*
TODO: logging
} catch (const qpid::Exception& error) {
// log it
} catch (const std::exception& e2) {
// log it
*/
} catch (...) {
// TODO: log it
}
EnterCriticalSection(&rmLock);
active = false;
return rv;
}
INT ResourceManager::close() {
// should never be called when already sending other commands to broker
if (active)
return XAER_PROTO;
INT rv = XAER_RMERR; // placeholder until we successfully close resource
active = true;
LeaveCriticalSection(&rmLock);
try {
if (qpidSession.isValid()) {
qpidSession.close();
}
if (qpidConnection.isOpen()) {
qpidConnection.close();
}
} catch (...) {
// TODO: log it
}
EnterCriticalSection(&rmLock);
active = false;
if (!qpidConnection.isOpen()) {
rv = XA_OK;
}
return rv;
}
INT ResourceManager::commit(XID *xid) {
if (active)
return XAER_PROTO;
INT rv = XAER_RMFAIL;
active = true;
LeaveCriticalSection(&rmLock);
try {
qpid::framing::Xid qpidXid;
XaToQpid(*xid, qpidXid);
XaResult xaResult = qpidSession.dtxCommit(qpidXid, false, true);
if (xaResult.hasStatus()) {
uint16_t status = xaResult.getStatus();
switch ((XaStatus) status) {
case XA_STATUS_XA_OK:
case XA_STATUS_XA_RDONLY:
case XA_STATUS_XA_HEURCOM:
rv = XA_OK;
break;
default:
// commit failed and a retry won't fix
rv = XAER_RMERR;
break;
}
}
} catch (...) {
// TODO: log it
}
EnterCriticalSection(&rmLock);
active = false;
return rv;
}
INT ResourceManager::rollback(XID *xid) {
if (active)
return XAER_PROTO;
INT rv = XAER_RMFAIL;
active = true;
LeaveCriticalSection(&rmLock);
try {
qpid::framing::Xid qpidXid;
XaToQpid(*xid, qpidXid);
XaResult xaResult = qpidSession.dtxRollback(qpidXid, true);
if (xaResult.hasStatus()) {
uint16_t status = xaResult.getStatus();
switch ((XaStatus) status) {
case XA_STATUS_XA_OK:
case XA_STATUS_XA_HEURRB:
rv = XA_OK;
break;
default:
// RM internal error
rv = XA_RBPROTO;
break;
}
}
} catch (...) {
// TODO: log it
}
EnterCriticalSection(&rmLock);
active = false;
return rv;
}
INT ResourceManager::recover(XID *xids, long count, long flags) {
if (active)
return XAER_PROTO;
if ((xids == NULL) && (count != 0))
return XAER_INVAL;
if (count < 0)
return XAER_INVAL;
if (!(flags & TMSTARTRSCAN) && (cursor == -1))
// no existing scan and no scan requested
return XAER_INVAL;
INT status = XA_OK;
if (flags & TMSTARTRSCAN) {
// start a fresh scan
cursor = -1;
inDoubtXids.clear();
active = true;
LeaveCriticalSection(&rmLock);
try {
// status if we can't talk to the broker
status = XAER_RMFAIL;
DtxRecoverResult dtxrr = qpidSession.dtxRecover(true);
// status if we can't process the xids
status = XAER_RMERR;
std::vector<std::string> wireFormatXids(dtxrr.getInDoubt().size());
std::transform(dtxrr.getInDoubt().begin(), dtxrr.getInDoubt().end(), wireFormatXids.begin(), Array::get<std::string, Array::ValuePtr>);
size_t nXids = wireFormatXids.size();
if (nXids > 0) {
StructHelper decoder;
Xid qpidXid;
for (size_t i = 0; i < nXids; i++) {
decoder.decode (qpidXid, wireFormatXids[i]);
inDoubtXids.push_back(qpidXid);
}
// if we got here the decoder validated the Xids
status = XA_OK;
// make sure none are too big, just in case
for (size_t i = 0; i < nXids; i++) {
Xid& xid = inDoubtXids[i];
size_t l1 = xid.hasGlobalId() ? xid.getGlobalId().size() : 0;
size_t l2 = xid.hasBranchId() ? xid.getBranchId().size() : 0;
if ((l1 > MAXGTRIDSIZE) || (l2 > MAXBQUALSIZE) ||
((l1 + l2) > XIDDATASIZE)) {
status = XAER_RMERR;
break;
}
}
}
else {
// nXids == 0, the previously cleared inDoubtXids is correctly populated
status = XA_OK;
}
if (status == XA_OK)
cursor = 0;
} catch (...) {
// TODO: log it
}
EnterCriticalSection(&rmLock);
active = false;
}
else {
// TMSTARTRSCAN not set, is there an existing scan to work from?
if (cursor == -1)
return XAER_INVAL;
}
if (status != XA_OK)
return status;
INT actualCount = count;
if (count > 0) {
int nAvailable = (int) inDoubtXids.size() - cursor;
if (nAvailable < count)
actualCount = nAvailable;
for (int i = 0; i < actualCount; i++) {
Xid& qpidXid = inDoubtXids[i + cursor];
QpidToXa(qpidXid, xids[i]);
}
}
if (flags & TMENDRSCAN) {
cursor = -1;
inDoubtXids.clear();
}
return actualCount;
}
// Call with lock held
ResourceManager* findRm(int rmid) {
if (rmMap.find(rmid) == rmMap.end()) {
return NULL;
}
return rmMap[rmid];
}
INT __cdecl xa_open (char *xa_info, int rmid, long flags) {
if (flags & TMASYNC)
return XAER_ASYNC;
INT rv = XAER_RMERR;
EnterCriticalSection(&rmLock);
ResourceManager* rmp = findRm(rmid);
if (rmp != NULL) {
// error: already in use
rv = XAER_PROTO;
}
else {
std::string brokerHost;
int brokerPort;
std::string username;
std::string password;
bool ssl;
bool saslPlain;
if (parseDsn(xa_info, brokerHost, brokerPort, ssl, saslPlain, username, password)) {
try {
rmp = new ResourceManager(rmid, brokerHost, brokerPort, ssl, saslPlain, username, password);
rv = rmp->open();
if (rv != XA_OK) {
delete (rmp);
}
else {
rmMap[rmid] = rmp;
}
} catch (...) {}
}
else {
rv = XAER_INVAL;
}
}
LeaveCriticalSection(&rmLock);
return rv;
}
INT __cdecl xa_close (char *xa_info, int rmid, long flags) {
if (flags & TMASYNC)
return XAER_ASYNC;
INT rv = XAER_RMERR;
EnterCriticalSection(&rmLock);
ResourceManager* rmp = findRm(rmid);
if (rmp == NULL) {
// can close multiple times
rv = XA_OK;
}
else {
rv = rmp->close();
rmMap.erase(rmid);
try {
delete (rmp);
} catch (...) {
// TODO: log it
}
}
LeaveCriticalSection(&rmLock);
return rv;
}
INT __cdecl xa_commit (XID *xid, int rmid, long flags) {
if (flags & TMASYNC)
return XAER_ASYNC;
INT rv = XAER_RMFAIL;
EnterCriticalSection(&rmLock);
ResourceManager* rmp = findRm(rmid);
if (rmp == NULL) {
rv = XAER_INVAL;
}
else {
rv = rmp->commit(xid);
}
LeaveCriticalSection(&rmLock);
return rv;
}
INT __cdecl xa_rollback (XID *xid, int rmid, long flags) {
if (flags & TMASYNC)
return XAER_ASYNC;
INT rv = XAER_RMFAIL;
EnterCriticalSection(&rmLock);
ResourceManager* rmp = findRm(rmid);
if (rmp == NULL) {
rv = XAER_INVAL;
}
else {
rv = rmp->rollback(xid);
}
LeaveCriticalSection(&rmLock);
return rv;
}
INT __cdecl xa_recover (XID *xids, long count, int rmid, long flags) {
INT rv = XAER_RMFAIL;
EnterCriticalSection(&rmLock);
ResourceManager* rmp = findRm(rmid);
if (rmp == NULL) {
rv = XAER_PROTO;
}
else {
rv = rmp->recover(xids, count, flags);
}
LeaveCriticalSection(&rmLock);
return rv;
}
INT __cdecl xa_start (XID *xid, int rmid, long flags) {
// not used in recovery
return XAER_PROTO;
}
INT __cdecl xa_end (XID *xid, int rmid, long flags) {
// not used in recovery
return XAER_PROTO;
}
INT __cdecl xa_prepare (XID *xid, int rmid, long flags) {
// not used in recovery
return XAER_PROTO;
}
INT __cdecl xa_forget (XID *xid, int rmid, long flags) {
// not used in recovery
return XAER_PROTO;
}
INT __cdecl xa_complete (int *handle, int *retval, int rmid, long flags) {
// not used in recovery
return XAER_PROTO;
}
xa_switch_t xaSwitch;
HRESULT __cdecl GetQpidXaSwitch (DWORD XaSwitchFlags, xa_switch_t ** ppXaSwitch)
{
// needed for now due to implicit use of FreeLibrary in WSACleanup() in qpid/cpp/src/qpid/sys/windows/Socket.cpp
pinDll();
if (xaSwitch.xa_open_entry != xa_open) {
xaSwitch.xa_open_entry = xa_open;
xaSwitch.xa_close_entry = xa_close;
xaSwitch.xa_start_entry = xa_start;
xaSwitch.xa_end_entry = xa_end;
xaSwitch.xa_prepare_entry = xa_prepare;
xaSwitch.xa_commit_entry = xa_commit;
xaSwitch.xa_rollback_entry = xa_rollback;
xaSwitch.xa_recover_entry = xa_recover;
xaSwitch.xa_forget_entry = xa_forget;
xaSwitch.xa_complete_entry = xa_complete;
strcpy_s(xaSwitch.name, RMNAMESZ, "qpidxarm");
xaSwitch.flags = TMNOMIGRATE;
xaSwitch.version = 0;
}
*ppXaSwitch = &xaSwitch;
return S_OK;
}
}}} // namespace Apache::Qpid::DtcPlugin
// GetXaSwitch
extern "C" {
__declspec(dllexport) HRESULT __cdecl GetXaSwitch (DWORD XaSwitchFlags, xa_switch_t ** ppXaSwitch)
{
return Apache::Qpid::DtcPlugin::GetQpidXaSwitch (XaSwitchFlags, ppXaSwitch);
}
}
// dllmain
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
InitializeCriticalSection(&Apache::Qpid::DtcPlugin::rmLock);
Apache::Qpid::DtcPlugin::thisDll = hModule;
break;
case DLL_PROCESS_DETACH:
DeleteCriticalSection(&Apache::Qpid::DtcPlugin::rmLock);
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
break;
}
return TRUE;
}