/* * PROGRAM: JRD Remote Interface * MODULE: interface.cpp * DESCRIPTION: User visible entrypoints remote interface * * The contents of this file are subject to the Interbase Public * License Version 1.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.Inprise.com/IPL.html * * Software distributed under the License is distributed on an * "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express * or implied. See the License for the specific language governing * rights and limitations under the License. * * The Original Code was created by Inprise Corporation * and its predecessors. Portions created by Inprise Corporation are * Copyright (C) Inprise Corporation. * * All Rights Reserved. * Contributor(s): ______________________________________. * * 2002.10.27 Sean Leyne - Code Cleanup, removed obsolete "Ultrix" port * * 2002.10.28 Sean Leyne - Code cleanup, removed obsolete "MPEXL" port * 2002.10.28 Sean Leyne - Code cleanup, removed obsolete "DecOSF" port * * 2002.10.29 Sean Leyne - Removed support for obsolete IPX/SPX Protocol * 2002.10.29 Sean Leyne - Removed obsolete "Netware" port * */ #include "firebird.h" #include #include #include #include "../remote/remote.h" #include "../common/gdsassert.h" #include "../common/isc_proto.h" #include #ifndef NO_NFS #include #endif #include "ibase.h" #include "../common/ThreadStart.h" #include "../jrd/license.h" #include "../remote/inet_proto.h" #include "../remote/merge_proto.h" #include "../remote/parse_proto.h" #include "../remote/remot_proto.h" #include "../remote/proto_proto.h" #include "../common/cvt.h" #include "../yvalve/gds_proto.h" #include "../common/isc_f_proto.h" #include "../common/classes/ClumpletWriter.h" #include "../common/classes/BatchCompletionState.h" #include "../common/config/config.h" #include "../common/utils_proto.h" #include "../common/classes/DbImplementation.h" #include "../common/Auth.h" #include "../common/classes/GetPlugins.h" #include "firebird/Interface.h" #include "../common/StatementMetadata.h" #include "../common/IntlParametersBlock.h" #include "../common/status.h" #include "../common/db_alias.h" #include "../common/classes/auto.h" #include "../auth/SecurityDatabase/LegacyClient.h" #include "../auth/SecureRemotePassword/client/SrpClient.h" #include "../auth/trusted/AuthSspi.h" #include "../plugins/crypt/arc4/Arc4.h" #include "BlrFromMessage.h" #include "../dsql/DsqlBatch.h" #ifdef HAVE_UNISTD_H #include #endif #ifdef WIN_NT #include #endif #if defined(WIN_NT) #include "../common/isc_proto.h" #include "../remote/os/win32/xnet_proto.h" #endif const char* const PROTOCOL_INET = "inet"; const char* const PROTOCOL_INET4 = "inet4"; const char* const PROTOCOL_INET6 = "inet6"; #ifdef WIN_NT const char* const PROTOCOL_XNET = "xnet"; #endif const char* const INET_SEPARATOR = "/"; const char* const INET_LOCALHOST = "localhost"; using namespace Firebird; namespace { void handle_error(ISC_STATUS code) { Arg::Gds(code).raise(); } template inline void CHECK_HANDLE(T* blk, ISC_STATUS error) { if (!blk || !blk->checkHandle()) { handle_error(error); } } inline void CHECK_LENGTH(rem_port* port, size_t length) { if (length > MAX_USHORT && port->port_protocol < PROTOCOL_VERSION13) status_exception::raise(Arg::Gds(isc_imp_exc) << Arg::Gds(isc_blktoobig)); } class SaveString { public: SaveString(cstring& toSave, ULONG newLength, UCHAR* newBuffer) : ptr(&toSave), oldValue(*ptr) { ptr->cstr_address = newBuffer; ptr->cstr_allocated = newLength; } ~SaveString() { *ptr = oldValue; } private: cstring* ptr; cstring oldValue; }; class ClientPortsCleanup : public PortsCleanup { public: ClientPortsCleanup() : PortsCleanup() {} explicit ClientPortsCleanup(MemoryPool& p) : PortsCleanup(p) {} void closePort(rem_port* port) override; void delay() override { Thread::sleep(50); } }; GlobalPtr outPorts; } namespace Remote { // Provider stuff class Attachment; class Statement; class Blob final : public RefCntIface > { public: // IBlob implementation int release() override; void getInfo(CheckStatusWrapper* status, unsigned int itemsLength, const unsigned char* items, unsigned int bufferLength, unsigned char* buffer) override; int getSegment(CheckStatusWrapper* status, unsigned int bufferLength, void* buffer, unsigned int* segmentLength) override; void putSegment(CheckStatusWrapper* status, unsigned int length, const void* buffer) override; void cancel(CheckStatusWrapper* status) override; void close(CheckStatusWrapper* status) override; int seek(CheckStatusWrapper* status, int mode, int offset) override; // returns position void deprecatedCancel(Firebird::CheckStatusWrapper* status) override; void deprecatedClose(Firebird::CheckStatusWrapper* status) override; public: explicit Blob(Rbl* handle) : blob(handle) { blob->rbl_self = &blob; } private: void freeClientData(CheckStatusWrapper* status, bool force = false); void internalCancel(Firebird::CheckStatusWrapper* status); void internalClose(Firebird::CheckStatusWrapper* status); Rbl* blob; }; int Blob::release() { if (--refCounter != 0) { return 1; } if (blob) { LocalStatus ls; CheckStatusWrapper status(&ls); freeClientData(&status, true); } delete this; return 0; } class Transaction final : public RefCntIface > { public: // ITransaction implementation int release() override; void getInfo(CheckStatusWrapper* status, unsigned int itemsLength, const unsigned char* items, unsigned int bufferLength, unsigned char* buffer) override; void prepare(CheckStatusWrapper* status, unsigned int msg_length = 0, const unsigned char* message = 0) override; void commit(CheckStatusWrapper* status) override; void commitRetaining(CheckStatusWrapper* status) override; void rollback(CheckStatusWrapper* status) override; void rollbackRetaining(CheckStatusWrapper* status) override; void disconnect(CheckStatusWrapper* status) override; ITransaction* join(CheckStatusWrapper* status, ITransaction* tra) override; Transaction* validate(CheckStatusWrapper* status, IAttachment* attachment) override; Transaction* enterDtc(CheckStatusWrapper* status) override; void deprecatedCommit(Firebird::CheckStatusWrapper* status) override; void deprecatedRollback(Firebird::CheckStatusWrapper* status) override; void deprecatedDisconnect(Firebird::CheckStatusWrapper* status) override; public: Transaction(Rtr* handle, Attachment* a) : remAtt(a), transaction(handle) { transaction->rtr_self = &transaction; } Rtr* getTransaction() { return transaction; } void clear() { transaction = NULL; } private: Transaction(Transaction* from) : remAtt(from->remAtt), transaction(from->transaction) { } void freeClientData(CheckStatusWrapper* status, bool force = false); void internalCommit(Firebird::CheckStatusWrapper* status); void internalRollback(Firebird::CheckStatusWrapper* status); void internalDisconnect(Firebird::CheckStatusWrapper* status); Attachment* remAtt; Rtr* transaction; }; int Transaction::release() { if (--refCounter != 0) return 1; if (transaction) { LocalStatus ls; CheckStatusWrapper status(&ls); freeClientData(&status, true); // ASF: Rollback - is this correct for reconnected transactions? } delete this; return 0; } class ResultSet final : public RefCntIface > { public: // IResultSet implementation int release() override; int fetchNext(CheckStatusWrapper* status, void* message) override; int fetchPrior(CheckStatusWrapper* status, void* message) override; int fetchFirst(CheckStatusWrapper* status, void* message) override; int fetchLast(CheckStatusWrapper* status, void* message) override; int fetchAbsolute(CheckStatusWrapper* status, int position, void* message) override; int fetchRelative(CheckStatusWrapper* status, int offset, void* message) override; FB_BOOLEAN isEof(CheckStatusWrapper* status) override; FB_BOOLEAN isBof(CheckStatusWrapper* status) override; IMessageMetadata* getMetadata(CheckStatusWrapper* status) override; void close(CheckStatusWrapper* status) override; void deprecatedClose(CheckStatusWrapper* status) override; void setDelayedOutputFormat(CheckStatusWrapper* status, IMessageMetadata* format) override; void getInfo(CheckStatusWrapper* status, unsigned int itemsLength, const unsigned char* items, unsigned int bufferLength, unsigned char* buffer) override; ResultSet(Statement* s, IMessageMetadata* outFmt, unsigned f) : stmt(s), flags(f), tmpStatement(false), delayedFormat(outFmt == DELAYED_OUT_FORMAT) { if (!delayedFormat) outputFormat = outFmt; } private: bool fetch(CheckStatusWrapper* status, void* message, P_FETCH operation, int position = 0); void releaseStatement(); void freeClientData(CheckStatusWrapper* status, bool force = false); void internalClose(CheckStatusWrapper* status); Statement* stmt; const unsigned flags; RefPtr outputFormat; public: bool tmpStatement, delayedFormat; }; int ResultSet::release() { if (--refCounter != 0) return 1; if (stmt) { LocalStatus ls; CheckStatusWrapper status(&ls); freeClientData(&status, true); } delete this; return 0; } class Batch final : public RefCntIface > { public: static const ULONG DEFER_BATCH_LIMIT = 64; Batch(Statement* s, IMessageMetadata* inFmt, unsigned parLength, const unsigned char* par); // IBatch implementation int release() override; void add(Firebird::CheckStatusWrapper* status, unsigned count, const void* inBuffer) override; void addBlob(Firebird::CheckStatusWrapper* status, unsigned length, const void* inBuffer, ISC_QUAD* blobId, unsigned parLength, const unsigned char* par) override; void appendBlobData(Firebird::CheckStatusWrapper* status, unsigned length, const void* inBuffer) override; void addBlobStream(Firebird::CheckStatusWrapper* status, unsigned length, const void* inBuffer) override; void registerBlob(Firebird::CheckStatusWrapper* status, const ISC_QUAD* existingBlob, ISC_QUAD* blobId) override; Firebird::IBatchCompletionState* execute(Firebird::CheckStatusWrapper* status, Firebird::ITransaction* transaction) override; void cancel(Firebird::CheckStatusWrapper* status) override; unsigned getBlobAlignment(Firebird::CheckStatusWrapper* status) override; void setDefaultBpb(Firebird::CheckStatusWrapper* status, unsigned parLength, const unsigned char* par) override; Firebird::IMessageMetadata* getMetadata(Firebird::CheckStatusWrapper* status) override; void close(Firebird::CheckStatusWrapper* status) override; void deprecatedClose(Firebird::CheckStatusWrapper* status) override; void getInfo(CheckStatusWrapper* status, unsigned int itemsLength, const unsigned char* items, unsigned int bufferLength, unsigned char* buffer) override; private: void freeClientData(CheckStatusWrapper* status, bool force = false); void internalClose(Firebird::CheckStatusWrapper* status); void releaseStatement(); void setServerInfo(); void cleanup() { if (blobPolicy != BLOB_NONE) blobStream = blobStreamBuffer; sizePointer = nullptr; messageStream = 0; } void genBlobId(ISC_QUAD* blobId) { if (++genId.gds_quad_low == 0) ++genId.gds_quad_high; memcpy(blobId, &genId, sizeof(genId)); } bool batchHasData() { return batchActive; } // working with message stream buffer void putMessageData(ULONG count, const void* p) { fb_assert(messageStreamBuffer); const UCHAR* ptr = reinterpret_cast(p); while(count) { ULONG remainSpace = messageBufferSize - messageStream; ULONG step = MIN(count, remainSpace); if (step == messageBufferSize) { // direct packet sent sendMessagePacket(step, ptr, false); } else { // use buffer memcpy(&messageStreamBuffer[messageStream * alignedSize], ptr, step * alignedSize); messageStream += step; if (messageStream == messageBufferSize) { sendMessagePacket(messageBufferSize, messageStreamBuffer, false); messageStream = 0; } } count -= step; ptr += step * alignedSize; } } // working with blob stream buffer void newBlob() { setServerInfo(); alignBlobBuffer(blobAlign); fb_assert(blobStream - blobStreamBuffer <= blobBufferSize); ULONG space = blobBufferSize - (blobStream - blobStreamBuffer); if (space < Rsr::BatchStream::SIZEOF_BLOB_HEAD) { sendBlobPacket(blobStream - blobStreamBuffer, blobStreamBuffer, false); blobStream = blobStreamBuffer; } } void alignBlobBuffer(unsigned alignment, ULONG* bs = NULL) { fb_assert(alignment); ULONG align = FB_ALIGN(blobStream, alignment) - blobStream; if (bs) *bs += align; FB_UINT64 zeroFill = 0; putBlobData(align, &zeroFill); } void putBlobData(ULONG size, const void* p) { fb_assert(blobStreamBuffer); const UCHAR* ptr = reinterpret_cast(p); while(size) { ULONG space = blobBufferSize - (blobStream - blobStreamBuffer); ULONG step = MIN(size, space); if (step == blobBufferSize) { // direct packet sent sendBlobPacket(blobBufferSize, ptr, false); } else { // use buffer memcpy(blobStream, ptr, step); blobStream += step; if (blobStream - blobStreamBuffer == blobBufferSize) { sendBlobPacket(blobBufferSize, blobStreamBuffer, false); blobStream = blobStreamBuffer; sizePointer = nullptr; } } size -= step; ptr += step; } } void setSizePointer() { fb_assert(FB_ALIGN(blobStream, sizeof(*sizePointer)) == blobStream); sizePointer = reinterpret_cast(blobStream); } void putSegment(ULONG size, const void* ptr) { if (!sizePointer) { newBlob(); ISC_QUAD quadZero = {0, 0}; putBlobData(sizeof quadZero, &quadZero); setSizePointer(); ULONG longZero = 0; putBlobData(sizeof longZero, &longZero); putBlobData(sizeof longZero, &longZero); } *sizePointer += size; if (segmented) { if (size > MAX_USHORT) { (Arg::Gds(isc_imp_exc) << Arg::Gds(isc_blobtoobig) << Arg::Gds(isc_big_segment) << Arg::Num(size)).raise(); } *sizePointer += sizeof(USHORT); alignBlobBuffer(BLOB_SEGHDR_ALIGN, sizePointer); USHORT segSize = size; putBlobData(sizeof segSize, &segSize); } putBlobData(size, ptr); } void flashBatch() { if (blobPolicy != BLOB_NONE) { setServerInfo(); alignBlobBuffer(blobAlign); ULONG size = blobStream - blobStreamBuffer; if (size) { sendBlobPacket(size, blobStreamBuffer, messageStream == 0); blobStream = blobStreamBuffer; } } if (messageStream) { sendMessagePacket(messageStream, messageStreamBuffer, true); messageStream = 0; } batchActive = false; blobCount = messageCount = 0; } void sendBlobPacket(unsigned size, const UCHAR* ptr, bool flash); void sendMessagePacket(unsigned size, const UCHAR* ptr, bool flash); void sendDeferredPacket(IStatus* status, rem_port* port, PACKET* packet, bool flash); Firebird::AutoPtr messageStreamBuffer, blobStreamBuffer; ULONG messageStream; UCHAR* blobStream; ULONG* sizePointer; ULONG messageSize, alignedSize, blobBufferSize, messageBufferSize, flags; Statement* stmt; RefPtr format; ISC_QUAD genId; int blobAlign; UCHAR blobPolicy; bool segmented, defSegmented, batchActive; ULONG messageCount, blobCount, serverSize, blobHeadSize; public: bool tmpStatement; }; int Batch::release() { if (--refCounter != 0) return 1; if (stmt) { LocalStatus ls; CheckStatusWrapper status(&ls); freeClientData(&status, true); } delete this; return 0; } class Replicator final : public RefCntIface > { public: // IReplicator implementation int release() override; void process(CheckStatusWrapper* status, unsigned length, const unsigned char* data) override; void close(CheckStatusWrapper* status) override; void deprecatedClose(CheckStatusWrapper* status) override; explicit Replicator(Attachment* att) : attachment(att) {} private: void freeClientData(CheckStatusWrapper* status, bool force = false); void internalClose(CheckStatusWrapper* status); Attachment* attachment; }; int Replicator::release() { if (--refCounter != 0) return 1; if (attachment) { LocalStatus ls; CheckStatusWrapper status(&ls); freeClientData(&status, true); } delete this; return 0; } class Statement final : public RefCntIface > { public: // IStatement implementation int release() override; void getInfo(CheckStatusWrapper* status, unsigned int itemsLength, const unsigned char* items, unsigned int bufferLength, unsigned char* buffer) override; unsigned getType(CheckStatusWrapper* status) override; const char* getPlan(CheckStatusWrapper* status, FB_BOOLEAN detailed) override; Firebird::IMessageMetadata* getInputMetadata(CheckStatusWrapper* status) override; Firebird::IMessageMetadata* getOutputMetadata(CheckStatusWrapper* status) override; ISC_UINT64 getAffectedRecords(CheckStatusWrapper* status) override; ITransaction* execute(CheckStatusWrapper* status, ITransaction* tra, IMessageMetadata* inMetadata, void* inBuffer, IMessageMetadata* outMetadata, void* outBuffer) override; ResultSet* openCursor(CheckStatusWrapper* status, ITransaction* tra, IMessageMetadata* inMetadata, void* inBuffer, IMessageMetadata* outFormat, unsigned int flags) override; void setCursorName(CheckStatusWrapper* status, const char* name) override; void free(CheckStatusWrapper* status) override; void deprecatedFree(CheckStatusWrapper* status) override; unsigned getFlags(CheckStatusWrapper* status) override; unsigned int getTimeout(CheckStatusWrapper* status) override { if (statement->rsr_rdb->rdb_port->port_protocol < PROTOCOL_STMT_TOUT) { status->setErrors(Arg::Gds(isc_wish_list).value()); return 0; } return statement->rsr_timeout; } void setTimeout(CheckStatusWrapper* status, unsigned int timeOut) override { if (timeOut && statement->rsr_rdb->rdb_port->port_protocol < PROTOCOL_STMT_TOUT) { status->setErrors(Arg::Gds(isc_wish_list).value()); return; } statement->rsr_timeout = timeOut; } Batch* createBatch(CheckStatusWrapper* status, IMessageMetadata* inMetadata, unsigned parLength, const unsigned char* par) override; public: Statement(Rsr* handle, Attachment* a, unsigned aDialect) : metadata(getPool(), this, NULL), remAtt(a), statement(handle), dialect(aDialect) { statement->rsr_self = &statement; } Rsr* getStatement() { return statement; } Attachment* getAttachment() { return remAtt; } void parseMetadata(const Array& buffer) { metadata.clear(); metadata.parse((ULONG) buffer.getCount(), buffer.begin()); } unsigned getDialect() const { return dialect; } private: void freeClientData(CheckStatusWrapper* status, bool force = false); void internalFree(CheckStatusWrapper* status); StatementMetadata metadata; Attachment* remAtt; Rsr* statement; unsigned dialect; }; int Statement::release() { if (--refCounter != 0) return 1; if (statement) { LocalStatus ls; CheckStatusWrapper status(&ls); freeClientData(&status, true); } delete this; return 0; } class Request final : public RefCntIface > { public: // IRequest implementation int release() override; void receive(CheckStatusWrapper* status, int level, unsigned int msg_type, unsigned int length, void* message) override; void send(CheckStatusWrapper* status, int level, unsigned int msg_type, unsigned int length, const void* message) override; void getInfo(CheckStatusWrapper* status, int level, unsigned int itemsLength, const unsigned char* items, unsigned int bufferLength, unsigned char* buffer) override; void start(CheckStatusWrapper* status, Firebird::ITransaction* tra, int level) override; void startAndSend(CheckStatusWrapper* status, Firebird::ITransaction* tra, int level, unsigned int msg_type, unsigned int length, const void* message) override; void unwind(CheckStatusWrapper* status, int level) override; void free(CheckStatusWrapper* status) override; void deprecatedFree(CheckStatusWrapper* status) override; public: Request(Rrq* handle, Attachment* a) : remAtt(a), rq(handle) { rq->rrq_self = &rq; } private: void freeClientData(CheckStatusWrapper* status, bool force = false); void internalFree(CheckStatusWrapper* status); Attachment* remAtt; Rrq* rq; }; int Request::release() { if (--refCounter != 0) return 1; if (rq) { LocalStatus ls; CheckStatusWrapper status(&ls); freeClientData(&status, true); } delete this; return 0; } class Events final : public RefCntIface > { public: // IEvents implementation int release() override; void cancel(CheckStatusWrapper* status) override; void deprecatedCancel(CheckStatusWrapper* status) override; public: Events(Rvnt* handle) : rvnt(handle), rdb(rvnt->rvnt_rdb) { rvnt->rvnt_self = &rvnt; } private: void freeClientData(CheckStatusWrapper* status, bool force = false); void internalCancel(CheckStatusWrapper* status); Rvnt* rvnt; Rdb* rdb; }; int Events::release() { int rc = --refCounter; if (rc != 0) { fb_assert(rc > 0); return 1; } if (rvnt) { LocalStatus ls; CheckStatusWrapper status(&ls); freeClientData(&status, true); } delete this; return 0; } class Attachment final : public RefCntIface > { public: // IAttachment implementation int release() override; void getInfo(CheckStatusWrapper* status, unsigned int itemsLength, const unsigned char* items, unsigned int bufferLength, unsigned char* buffer) override; Firebird::ITransaction* startTransaction(CheckStatusWrapper* status, unsigned int tpbLength, const unsigned char* tpb) override; Firebird::ITransaction* reconnectTransaction(CheckStatusWrapper* status, unsigned int length, const unsigned char* id) override; Firebird::IRequest* compileRequest(CheckStatusWrapper* status, unsigned int blr_length, const unsigned char* blr) override; void transactRequest(CheckStatusWrapper* status, ITransaction* transaction, unsigned int blr_length, const unsigned char* blr, unsigned int in_msg_length, const unsigned char* in_msg, unsigned int out_msg_length, unsigned char* out_msg) override; Firebird::IBlob* createBlob(CheckStatusWrapper* status, ITransaction* transaction, ISC_QUAD* id, unsigned int bpbLength = 0, const unsigned char* bpb = 0) override; Firebird::IBlob* openBlob(CheckStatusWrapper* status, ITransaction* transaction, ISC_QUAD* id, unsigned int bpbLength = 0, const unsigned char* bpb = 0) override; int getSlice(CheckStatusWrapper* status, ITransaction* transaction, ISC_QUAD* id, unsigned int sdl_length, const unsigned char* sdl, unsigned int param_length, const unsigned char* param, int sliceLength, unsigned char* slice) override; void putSlice(CheckStatusWrapper* status, ITransaction* transaction, ISC_QUAD* id, unsigned int sdl_length, const unsigned char* sdl, unsigned int param_length, const unsigned char* param, int sliceLength, unsigned char* slice) override; void executeDyn(CheckStatusWrapper* status, ITransaction* transaction, unsigned int length, const unsigned char* dyn) override; Statement* prepare(CheckStatusWrapper* status, ITransaction* transaction, unsigned int stmtLength, const char* sqlStmt, unsigned dialect, unsigned int flags) override; Firebird::ITransaction* execute(CheckStatusWrapper* status, ITransaction* transaction, unsigned int stmtLength, const char* sqlStmt, unsigned dialect, IMessageMetadata* inMetadata, void* inBuffer, IMessageMetadata* outMetadata, void* outBuffer) override; Firebird::IResultSet* openCursor(CheckStatusWrapper* status, ITransaction* transaction, unsigned int stmtLength, const char* sqlStmt, unsigned dialect, IMessageMetadata* inMetadata, void* inBuffer, Firebird::IMessageMetadata* outMetadata, const char* cursorName, unsigned int cursorFlags) override; Firebird::IEvents* queEvents(CheckStatusWrapper* status, Firebird::IEventCallback* callback, unsigned int length, const unsigned char* events) override; void cancelOperation(CheckStatusWrapper* status, int option) override; void ping(CheckStatusWrapper* status) override; void detach(CheckStatusWrapper* status) override; void dropDatabase(CheckStatusWrapper* status) override; void deprecatedDetach(Firebird::CheckStatusWrapper* status) override; void deprecatedDropDatabase(Firebird::CheckStatusWrapper* status) override; unsigned int getIdleTimeout(CheckStatusWrapper* status) override; void setIdleTimeout(CheckStatusWrapper* status, unsigned int timeOut) override; unsigned int getStatementTimeout(CheckStatusWrapper* status) override; void setStatementTimeout(CheckStatusWrapper* status, unsigned int timeOut) override; Batch* createBatch(Firebird::CheckStatusWrapper* status, ITransaction* transaction, unsigned stmtLength, const char* sqlStmt, unsigned dialect, IMessageMetadata* inMetadata, unsigned parLength, const unsigned char* par) override; Replicator* createReplicator(Firebird::CheckStatusWrapper* status) override; public: Attachment(Rdb* handle, const PathName& path) : replicator(nullptr), rdb(handle), dbPath(getPool(), path) { } Rdb* getRdb() { return rdb; } const PathName& getDbPath() { return dbPath; } Rtr* remoteTransaction(ITransaction* apiTra); Transaction* remoteTransactionInterface(ITransaction* apiTra); Statement* createStatement(CheckStatusWrapper* status, unsigned dialect); Replicator* replicator; private: void execWithCheck(CheckStatusWrapper* status, const string& stmt); void freeClientData(CheckStatusWrapper* status, bool force = false); void internalDetach(Firebird::CheckStatusWrapper* status); void internalDropDatabase(Firebird::CheckStatusWrapper* status); SLONG getSingleInfo(CheckStatusWrapper* status, UCHAR infoItem); Rdb* rdb; const PathName dbPath; }; int Attachment::release() { if (--refCounter != 0) return 1; if (rdb) { LocalStatus ls; CheckStatusWrapper status(&ls); freeClientData(&status, true); } delete this; return 0; } class Service final : public RefCntIface > { public: // IService implementation int release() override; void detach(CheckStatusWrapper* status) override; void deprecatedDetach(CheckStatusWrapper* status) override; void query(CheckStatusWrapper* status, unsigned int sendLength, const unsigned char* sendItems, unsigned int receiveLength, const unsigned char* receiveItems, unsigned int bufferLength, unsigned char* buffer) override; void start(CheckStatusWrapper* status, unsigned int spbLength, const unsigned char* spb) override; void cancel(CheckStatusWrapper* status) override; public: Service(Rdb* handle) : rdb(handle) { } private: void freeClientData(CheckStatusWrapper* status, bool force = false); void internalDetach(CheckStatusWrapper* status); Rdb* rdb; }; int Service::release() { if (--refCounter != 0) return 1; if (rdb) { LocalStatus ls; CheckStatusWrapper status(&ls); freeClientData(&status, true); } delete this; return 0; } class RProvider : public StdPlugin > { public: explicit RProvider(IPluginConfig*) : cryptCallback(NULL) { } RProvider() : cryptCallback(NULL) { } // IProvider implementation IAttachment* attachDatabase(CheckStatusWrapper* status, const char* fileName, unsigned int dpbLength, const unsigned char* dpb); IAttachment* createDatabase(CheckStatusWrapper* status, const char* fileName, unsigned int dpbLength, const unsigned char* dpb); IService* attachServiceManager(CheckStatusWrapper* status, const char* service, unsigned int spbLength, const unsigned char* spb); void shutdown(CheckStatusWrapper* status, unsigned int timeout, const int reason); void setDbCryptCallback(CheckStatusWrapper* status, ICryptKeyCallback* cryptCallback); protected: IAttachment* attach(CheckStatusWrapper* status, const char* filename, unsigned int dpb_length, const unsigned char* dpb, bool loopback); IAttachment* create(CheckStatusWrapper* status, const char* filename, unsigned int dpb_length, const unsigned char* dpb, bool loopback); IService* attachSvc(CheckStatusWrapper* status, const char* service, unsigned int spbLength, const unsigned char* spb, bool loopback); private: Firebird::ICryptKeyCallback* cryptCallback; }; void RProvider::shutdown(CheckStatusWrapper* status, unsigned int /*timeout*/, const int /*reason*/) { status->init(); try { outPorts->closePorts(); } catch (const Exception& ex) { ex.stuffException(status); } } void RProvider::setDbCryptCallback(CheckStatusWrapper* status, ICryptKeyCallback* callback) { status->init(); cryptCallback = callback; } class Loopback : public IProviderBaseImpl { public: explicit Loopback(IPluginConfig*) { } // IProvider implementation IAttachment* attachDatabase(CheckStatusWrapper* status, const char* fileName, unsigned int dpbLength, const unsigned char* dpb); IAttachment* createDatabase(CheckStatusWrapper* status, const char* fileName, unsigned int dpbLength, const unsigned char* dpb); IService* attachServiceManager(CheckStatusWrapper* status, const char* service, unsigned int spbLength, const unsigned char* spb); }; namespace { SimpleFactory remoteFactory; SimpleFactory loopbackFactory; } void registerRedirector(Firebird::IPluginManager* iPlugin) { iPlugin->registerPluginFactory(IPluginManager::TYPE_PROVIDER, "Remote", &remoteFactory); iPlugin->registerPluginFactory(IPluginManager::TYPE_PROVIDER, "Loopback", &loopbackFactory); Auth::registerLegacyClient(iPlugin); Auth::registerSrpClient(iPlugin); #ifdef TRUSTED_AUTH Auth::registerTrustedClient(iPlugin); #endif Crypt::registerArc4(iPlugin); } } // namespace Remote /* extern "C" FB_DLL_EXPORT void FB_PLUGIN_ENTRY_POINT(IMaster* master) { IPluginManager* pi = master->getPluginManager(); registerRedirector(pi); pi->release(); } */ namespace Remote { static Rvnt* add_event(rem_port*); static void add_other_params(rem_port*, ClumpletWriter&, const ParametersSet&); static void add_working_directory(ClumpletWriter&, const PathName&); static rem_port* analyze(ClntAuthBlock& cBlock, PathName& attach_name, unsigned flags, ClumpletWriter& pb, const ParametersSet& parSet, PathName& node_name, PathName* ref_db_name, Firebird::ICryptKeyCallback* cryptCb); static void batch_gds_receive(rem_port*, struct rmtque *, USHORT); static void batch_dsql_fetch(rem_port*, struct rmtque *, USHORT); static void clear_queue(rem_port*); static void clear_stmt_que(rem_port*, Rsr*); static void finalize(rem_port* port); static void disconnect(rem_port*, bool rmRef = true); static void enqueue_receive(rem_port*, t_rmtque_fn, Rdb*, void*, Rrq::rrq_repeat*); static void dequeue_receive(rem_port*); static THREAD_ENTRY_DECLARE event_thread(THREAD_ENTRY_PARAM); static Rvnt* find_event(rem_port*, SLONG); static bool get_new_dpb(ClumpletWriter&, const ParametersSet&, bool); static void info(CheckStatusWrapper*, Rdb*, P_OP, USHORT, USHORT, USHORT, const UCHAR*, USHORT, const UCHAR*, ULONG, UCHAR*); static bool init(CheckStatusWrapper*, ClntAuthBlock&, rem_port*, P_OP, PathName&, ClumpletWriter&, IntlParametersBlock&, ICryptKeyCallback* cryptCallback); static Rtr* make_transaction(Rdb*, USHORT); static void mov_dsql_message(const UCHAR*, const rem_fmt*, UCHAR*, const rem_fmt*); static void move_error(const Arg::StatusVector& v); static void receive_after_start(Rrq*, USHORT); static void receive_packet(rem_port*, PACKET *); static void receive_packet_noqueue(rem_port*, PACKET *); static void receive_queued_packet(rem_port*, USHORT); static void receive_response(IStatus*, Rdb*, PACKET *); static void release_blob(Rbl*); static void release_event(Rvnt*); static void release_object(IStatus*, Rdb*, P_OP, USHORT); static void release_request(Rrq*); static void release_statement(Rsr**); static void release_sql_request(Rsr*); static void release_transaction(Rtr*); static void send_and_receive(IStatus*, Rdb*, PACKET *); static void send_blob(CheckStatusWrapper*, Rbl*, USHORT, const UCHAR*); static void send_packet(rem_port*, PACKET *); static void send_partial_packet(rem_port*, PACKET *); static void server_death(rem_port*); static void svcstart(CheckStatusWrapper*, Rdb*, P_OP, USHORT, USHORT, USHORT, const UCHAR*); static void unsupported(); static void zap_packet(PACKET *); static void cleanDpb(Firebird::ClumpletWriter&, const ParametersSet*); static void authFillParametersBlock(ClntAuthBlock& authItr, ClumpletWriter& dpb, const ParametersSet* tags, rem_port* port); static void authReceiveResponse(bool havePacket, ClntAuthBlock& authItr, rem_port* port, Rdb* rdb, IStatus* status, PACKET* packet, bool checkKeys); static AtomicCounter remote_event_id; static const unsigned ANALYZE_USER_VFY = 0x01; static const unsigned ANALYZE_LOOPBACK = 0x02; static const unsigned ANALYZE_MOUNTS = 0x04; static const unsigned ANALYZE_EMP_NAME = 0x08; inline static void reset(IStatus* status) throw() { status->init(); } #define SET_OBJECT(rdb, object, id) rdb->rdb_port->setHandle(object, id) inline static void defer_packet(rem_port* port, PACKET* packet, bool sent = false) { fb_assert(port->port_flags & PORT_lazy); fb_assert(port->port_deferred_packets); // hvlad: passed packet often is rdb->rdb_packet and therefore can be // changed inside clear_queue. To not confuse caller we must preserve // packet content rem_que_packet p; p.packet = *packet; p.sent = sent; clear_queue(port); *packet = p.packet; // don't use string references in P_RESP structure copied from another packet memset(&p.packet.p_resp, 0, sizeof(p.packet.p_resp)); port->port_deferred_packets->add(p); } IAttachment* RProvider::attach(CheckStatusWrapper* status, const char* filename, unsigned int dpb_length, const unsigned char* dpb, bool loopback) { /************************************** * * g d s _ a t t a c h _ d a t a b a s e * ************************************** * * Functional description * Connect to an old, grungy database, corrupted by user data. * **************************************/ try { reset(status); ClumpletWriter newDpb(ClumpletReader::dpbList, MAX_DPB_SIZE, dpb, dpb_length); unsigned flags = ANALYZE_MOUNTS; if (get_new_dpb(newDpb, dpbParam, loopback)) flags |= ANALYZE_USER_VFY; if (loopback) flags |= ANALYZE_LOOPBACK; PathName expanded_name(filename); resolveAlias(filename, expanded_name, nullptr); ClntAuthBlock cBlock(&expanded_name, &newDpb, &dpbParam); PathName node_name; rem_port* port = analyze(cBlock, expanded_name, flags, newDpb, dpbParam, node_name, NULL, cryptCallback); if (!port) { Arg::Gds(isc_unavailable).copyTo(status); return NULL; } RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); // The client may have set a parameter for dummy_packet_interval. Add that to the // the DPB so the server can pay attention to it. add_other_params(port, newDpb, dpbParam); add_working_directory(newDpb, node_name); IntlDpb intl; HANDSHAKE_DEBUG(fprintf(stderr, "Cli: call init for DB='%s'\n", expanded_name.c_str())); if (!init(status, cBlock, port, op_attach, expanded_name, newDpb, intl, cryptCallback)) return NULL; Attachment* a = FB_NEW Attachment(port->port_context, filename); a->addRef(); return a; } catch (const Exception& ex) { ex.stuffException(status); return NULL; } } IAttachment* RProvider::attachDatabase(CheckStatusWrapper* status, const char* filename, unsigned int dpb_length, const unsigned char* dpb) { /************************************** * * g d s _ a t t a c h _ d a t a b a s e * ************************************** * * Functional description * Connect to an old, grungy database, corrupted by user data. * **************************************/ return attach(status, filename, dpb_length, dpb, false); } IAttachment* Loopback::attachDatabase(CheckStatusWrapper* status, const char* filename, unsigned int dpb_length, const unsigned char* dpb) { /************************************** * * g d s _ a t t a c h _ d a t a b a s e * ************************************** * * Functional description * Connect to an old, grungy database, corrupted by user data. * **************************************/ return attach(status, filename, dpb_length, dpb, true); } void Blob::getInfo(CheckStatusWrapper* status, unsigned int itemsLength, const unsigned char* items, unsigned int bufferLength, unsigned char* buffer) { /************************************** * * g d s _ b l o b _ i n f o * ************************************** * * Functional description * Provide information on blob object. * **************************************/ try { reset(status); CHECK_HANDLE(blob, isc_bad_segstr_handle); Rdb* rdb = blob->rbl_rdb; CHECK_HANDLE(rdb, isc_bad_db_handle); rem_port* port = rdb->rdb_port; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); info(status, rdb, op_info_blob, blob->rbl_id, 0, itemsLength, items, 0, 0, bufferLength, buffer); } catch (const Exception& ex) { ex.stuffException(status); } } void Blob::freeClientData(CheckStatusWrapper* status, bool force) { /************************************** * * g d s _ c a n c e l _ b l o b * ************************************** * * Functional description * Abort a partially completed blob. * **************************************/ try { if (!blob) { return; } CHECK_HANDLE(blob, isc_bad_segstr_handle); Rdb* rdb = blob->rbl_rdb; CHECK_HANDLE(rdb, isc_bad_db_handle); rem_port* port = rdb->rdb_port; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); try { release_object(status, rdb, op_cancel_blob, blob->rbl_id); } catch (const Exception&) { if (!force) throw; } release_blob(blob); blob = NULL; } catch (const Exception& ex) { ex.stuffException(status); } } void Blob::internalCancel(CheckStatusWrapper* status) { /************************************** * * g d s _ c a n c e l _ b l o b * ************************************** * * Functional description * Abort a partially completed blob. * **************************************/ reset(status); freeClientData(status); } void Blob::cancel(CheckStatusWrapper* status) { internalCancel(status); if (status->isEmpty()) release(); } void Blob::deprecatedCancel(CheckStatusWrapper* status) { internalCancel(status); } void Blob::internalClose(CheckStatusWrapper* status) { /************************************** * * g d s _ c l o s e _ b l o b * ************************************** * * Functional description * Close a completed blob. * **************************************/ try { reset(status); CHECK_HANDLE(blob, isc_bad_segstr_handle); Rdb* rdb = blob->rbl_rdb; CHECK_HANDLE(rdb, isc_bad_db_handle); rem_port* port = rdb->rdb_port; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); if ((blob->rbl_flags & Rbl::CREATE) && blob->rbl_ptr != blob->rbl_buffer) { send_blob(status, blob, 0, NULL); } release_object(status, rdb, op_close_blob, blob->rbl_id); release_blob(blob); blob = NULL; } catch (const Exception& ex) { ex.stuffException(status); } } void Blob::close(CheckStatusWrapper* status) { internalClose(status); if (status->isEmpty()) release(); } void Blob::deprecatedClose(CheckStatusWrapper* status) { internalClose(status); } void Events::freeClientData(CheckStatusWrapper* status, bool force) { /************************************** * * g d s _ $ c a n c e l _ e v e n t s * ************************************** * * Functional description * Cancel an outstanding event. * **************************************/ RefPtr callback; try { CHECK_HANDLE(rdb, isc_bad_db_handle); rem_port* port = rdb->rdb_port; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); if (!rvnt) { return; } CHECK_HANDLE(rvnt, isc_bad_events_handle); try { // Tell the remote server to cancel it and delete it from the list PACKET* packet = &rdb->rdb_packet; // Set the various parameters for the packet: // remote operation to perform, which database, // and which event. packet->p_operation = op_cancel_events; packet->p_event.p_event_database = rdb->rdb_id; const SLONG save_id = packet->p_event.p_event_rid = rvnt->rvnt_id; // Send the packet, and if that worked, get a response try { LocalStatus ls; CheckStatusWrapper dummy(&ls); send_packet(rdb->rdb_port, packet); receive_response(&dummy, rdb, packet); } catch (const Exception&) { } // Get ready to fire the event. if (rvnt->rvnt_id == save_id) { callback = rvnt->rvnt_callback; rvnt->rvnt_id = 0; } } catch (const Exception&) { if (!force) throw; } rvnt = NULL; } catch (const Exception& ex) { ex.stuffException(status); } // If the event has never been fired, fire it off with a length of 0. // Note: it is job of person being notified to check that counts // actually changed and that they were not woken up because of // server death. if (callback) callback->eventCallbackFunction(0, NULL); } void Events::internalCancel(CheckStatusWrapper* status) { /************************************** * * g d s _ $ c a n c e l _ e v e n t s * ************************************** * * Functional description * Cancel an outstanding event. * **************************************/ reset(status); freeClientData(status); } void Events::cancel(CheckStatusWrapper* status) { internalCancel(status); if (status->isEmpty()) release(); } void Events::deprecatedCancel(CheckStatusWrapper* status) { internalCancel(status); } void Transaction::internalCommit(CheckStatusWrapper* status) { /************************************** * * g d s _ c o m m i t * ************************************** * * Functional description * Commit a transaction. * **************************************/ try { reset(status); CHECK_HANDLE(transaction, isc_bad_trans_handle); Rdb* rdb = transaction->rtr_rdb; CHECK_HANDLE(rdb, isc_bad_db_handle); rem_port* port = rdb->rdb_port; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); release_object(status, rdb, op_commit, transaction->rtr_id); REMOTE_cleanup_transaction(transaction); release_transaction(transaction); transaction = NULL; } catch (const Exception& ex) { ex.stuffException(status); } } void Transaction::commit(CheckStatusWrapper* status) { internalCommit(status); if (status->isEmpty()) release(); } void Transaction::deprecatedCommit(CheckStatusWrapper* status) { internalCommit(status); } void Transaction::commitRetaining(CheckStatusWrapper* status) { /************************************** * * g d s _ c o m m i t _ r e t a i n i n g * ************************************** * * Functional description * **************************************/ try { reset(status); CHECK_HANDLE(transaction, isc_bad_trans_handle); Rdb* rdb = transaction->rtr_rdb; CHECK_HANDLE(rdb, isc_bad_db_handle); rem_port* port = rdb->rdb_port; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); release_object(status, rdb, op_commit_retaining, transaction->rtr_id); } catch (const Exception& ex) { ex.stuffException(status); } } ITransaction* Transaction::join(CheckStatusWrapper* status, ITransaction* tra) { /************************************** * * I T r a n s a c t i o n :: j o i n * ************************************** * * Functional description * Join this and passed transactions * into single distributed transaction * **************************************/ try { reset(status); CHECK_HANDLE(transaction, isc_bad_trans_handle); return DtcInterfacePtr()->join(status, this, tra); } catch (const Exception& ex) { ex.stuffException(status); } return NULL; } Transaction* Transaction::validate(CheckStatusWrapper* /*status*/, IAttachment* testAtt) { return (transaction && remAtt == testAtt) ? this : NULL; } Transaction* Transaction::enterDtc(CheckStatusWrapper* status) { try { Transaction* copy = FB_NEW Transaction(this); copy->addRef(); transaction = NULL; return copy; } catch (const Exception& ex) { ex.stuffException(status); } return NULL; } Firebird::IRequest* Attachment::compileRequest(CheckStatusWrapper* status, unsigned int blr_length, const unsigned char* blr) { /************************************** * * g d s _ c o m p i l e * ************************************** * * Functional description * **************************************/ try { reset(status); // Check and validate handles, etc. CHECK_HANDLE(rdb, isc_bad_db_handle); rem_port* port = rdb->rdb_port; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); // Validate data length CHECK_LENGTH(port, blr_length); // Parse the request in case blr_d_float must be converted to blr_double const UCHAR* new_blr = blr; // Make up a packet for the remote guy PACKET* packet = &rdb->rdb_packet; packet->p_operation = op_compile; P_CMPL* compile = &packet->p_cmpl; compile->p_cmpl_database = rdb->rdb_id; compile->p_cmpl_blr.cstr_length = blr_length; compile->p_cmpl_blr.cstr_address = new_blr; send_and_receive(status, rdb, packet); // Parse the request to find the messages RMessage* next; RMessage* message = PARSE_messages(blr, blr_length); USHORT max_msg = 0; for (next = message; next; next = next->msg_next) max_msg = MAX(max_msg, next->msg_number); // Allocate request block Rrq* request = FB_NEW Rrq(max_msg + 1); request->rrq_rdb = rdb; request->rrq_id = packet->p_resp.p_resp_object; request->rrq_max_msg = max_msg; SET_OBJECT(rdb, request, request->rrq_id); request->rrq_next = rdb->rdb_requests; rdb->rdb_requests = request; // when the messages are parsed, they are linked together; we need // to place the messages in the tail of the request block and create // a queue of length 1 for each message number for (; message; message = next) { next = message->msg_next; message->msg_next = message; Rrq::rrq_repeat * tail = &request->rrq_rpt[message->msg_number]; tail->rrq_message = message; tail->rrq_xdr = message; tail->rrq_format = (rem_fmt*) message->msg_address; message->msg_address = NULL; } Firebird::IRequest* r = FB_NEW Request(request, this); r->addRef(); return r; } catch (const Exception& ex) { // deallocate new_blr here??? ex.stuffException(status); } return NULL; } IBlob* Attachment::createBlob(CheckStatusWrapper* status, ITransaction* apiTra, ISC_QUAD* blob_id, unsigned int bpb_length, const unsigned char* bpb) { /************************************** * * g d s _ c r e a t e _ b l o b 2 * ************************************** * * Functional description * Open an existing blob. * **************************************/ try { reset(status); CHECK_HANDLE(rdb, isc_bad_db_handle); rem_port* port = rdb->rdb_port; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); Rtr* transaction = remoteTransaction(apiTra); CHECK_HANDLE(transaction, isc_bad_trans_handle); // Validate data length CHECK_LENGTH(port, bpb_length); PACKET* packet = &rdb->rdb_packet; packet->p_operation = op_create_blob2; P_BLOB* p_blob = &packet->p_blob; p_blob->p_blob_transaction = transaction->rtr_id; p_blob->p_blob_bpb.cstr_length = bpb_length; fb_assert(!p_blob->p_blob_bpb.cstr_allocated || p_blob->p_blob_bpb.cstr_allocated < p_blob->p_blob_bpb.cstr_length); // CVC: Should we ensure here that cstr_allocated < bpb_length??? // Otherwise, xdr_cstring() calling alloc_string() to decode would // cause memory problems on the client side for SS, as the client // would try to write to the application's provided R/O buffer. p_blob->p_blob_bpb.cstr_address = bpb; try { send_and_receive(status, rdb, packet); } catch (const Exception&) { p_blob->p_blob_bpb.cstr_length = 0; p_blob->p_blob_bpb.cstr_address = NULL; throw; } p_blob->p_blob_bpb.cstr_length = 0; p_blob->p_blob_bpb.cstr_address = NULL; Rbl* blob = FB_NEW Rbl(); *blob_id = packet->p_resp.p_resp_blob_id; blob->rbl_rdb = rdb; blob->rbl_rtr = transaction; blob->rbl_id = packet->p_resp.p_resp_object; blob->rbl_flags |= Rbl::CREATE; SET_OBJECT(rdb, blob, blob->rbl_id); blob->rbl_next = transaction->rtr_blobs; transaction->rtr_blobs = blob; Firebird::IBlob* b = FB_NEW Blob(blob); b->addRef(); return b; } catch (const Exception& ex) { ex.stuffException(status); } return NULL; } Firebird::IAttachment* RProvider::create(CheckStatusWrapper* status, const char* filename, unsigned int dpb_length, const unsigned char* dpb, bool loopback) { /************************************** * * g d s _ c r e a t e _ d a t a b a s e * ************************************** * * Functional description * Create a nice, squeeky clean database, uncorrupted by user data. * **************************************/ try { reset(status); ClumpletWriter newDpb(ClumpletReader::dpbList, MAX_DPB_SIZE, reinterpret_cast(dpb), dpb_length); unsigned flags = ANALYZE_MOUNTS; if (get_new_dpb(newDpb, dpbParam, loopback)) flags |= ANALYZE_USER_VFY; if (loopback) flags |= ANALYZE_LOOPBACK; PathName expanded_name(filename); resolveAlias(filename, expanded_name, nullptr); ClntAuthBlock cBlock(&expanded_name, &newDpb, &dpbParam); PathName node_name; rem_port* port = analyze(cBlock, expanded_name, flags, newDpb, dpbParam, node_name, NULL, cryptCallback); if (!port) { Arg::Gds(isc_unavailable).copyTo(status); return NULL; } RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); Rdb* rdb = port->port_context; // The client may have set a parameter for dummy_packet_interval. Add that to the // the DPB so the server can pay attention to it. Note: allocation code must // ensure sufficient space has been added. add_other_params(port, newDpb, dpbParam); add_working_directory(newDpb, node_name); IntlDpb intl; if (!init(status, cBlock, port, op_create, expanded_name, newDpb, intl, cryptCallback)) return NULL; Firebird::IAttachment* a = FB_NEW Attachment(rdb, filename); a->addRef(); return a; } catch (const Exception& ex) { ex.stuffException(status); } return NULL; } IAttachment* RProvider::createDatabase(CheckStatusWrapper* status, const char* fileName, unsigned int dpbLength, const unsigned char* dpb) { /************************************** * * g d s _ c r e a t e _ d a t a b a s e * ************************************** * * Functional description * Create a nice, squeeky clean database, uncorrupted by user data. * **************************************/ return create(status, fileName, dpbLength, dpb, false); } IAttachment* Loopback::createDatabase(CheckStatusWrapper* status, const char* fileName, unsigned int dpbLength, const unsigned char* dpb) { /************************************** * * g d s _ c r e a t e _ d a t a b a s e * ************************************** * * Functional description * Create a nice, squeeky clean database, uncorrupted by user data. * **************************************/ return create(status, fileName, dpbLength, dpb, true); } void Attachment::getInfo(CheckStatusWrapper* status, unsigned int item_length, const unsigned char* items, unsigned int buffer_length, unsigned char* buffer) { /************************************** * * g d s _ d a t a b a s e _ i n f o * ************************************** * * Functional description * Provide information on database object. * **************************************/ try { reset(status); HalfStaticArray temp; CHECK_HANDLE(rdb, isc_bad_db_handle); rem_port* port = rdb->rdb_port; USHORT protocol = memchr(items, fb_info_protocol_version, item_length) ? port->port_protocol : 0; protocol &= FB_PROTOCOL_MASK; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); UCHAR* temp_buffer = temp.getBuffer(buffer_length); info(status, rdb, op_info_database, rdb->rdb_id, 0, item_length, items, 0, 0, buffer_length, temp_buffer); string version; port->versionInfo(version); MERGE_database_info(temp_buffer, buffer, buffer_length, DbImplementation::current.backwardCompatibleImplementation(), 3, 1, reinterpret_cast(version.c_str()), reinterpret_cast(port->port_host->str_data), protocol); } catch (const Exception& ex) { ex.stuffException(status); } } void Attachment::executeDyn(CheckStatusWrapper* status, ITransaction* apiTra, unsigned int length, const unsigned char* dyn) { /************************************** * * g d s _ d d l * ************************************** * * Functional description * **************************************/ try { reset(status); CHECK_HANDLE(rdb, isc_bad_db_handle); rem_port* port = rdb->rdb_port; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); Rtr* transaction = remoteTransaction(apiTra); CHECK_HANDLE(transaction, isc_bad_trans_handle); // Validate data length CHECK_LENGTH(port, length); // Make up a packet for the remote guy PACKET* packet = &rdb->rdb_packet; packet->p_operation = op_ddl; P_DDL* ddl = &packet->p_ddl; ddl->p_ddl_database = rdb->rdb_id; ddl->p_ddl_transaction = transaction->rtr_id; ddl->p_ddl_blr.cstr_length = length; ddl->p_ddl_blr.cstr_address = dyn; send_and_receive(status, rdb, packet); } catch (const Exception& ex) { ex.stuffException(status); } } void Attachment::freeClientData(CheckStatusWrapper* status, bool force) { /************************************** * * g d s _ d e t a c h * ************************************** * * Functional description * Close down a database. * **************************************/ try { CHECK_HANDLE(rdb, isc_bad_db_handle); rem_port* port = rdb->rdb_port; RemotePortGuard portGuard(port, FB_FUNCTION); try { if (!(port->port_flags & (PORT_rdb_shutdown | PORT_detached))) { release_object(status, rdb, op_detach, rdb->rdb_id); } } catch (const status_exception& ex) { // If something other than a network error occurred, just return. Otherwise // we need to free up the associated structures, close the socket and // scream. By the way, we should probably create an entry in the log // telling the user that an unrecoverable network error occurred and that // if there was any uncommitted work, its gone...... Oh well.... ex.stuffException(status); if (!fb_utils::isNetworkError(status->getErrors()[1]) && (!force)) { return; } } while (rdb->rdb_events) release_event(rdb->rdb_events); while (rdb->rdb_requests) release_request(rdb->rdb_requests); while (rdb->rdb_sql_requests) release_sql_request(rdb->rdb_sql_requests); while (rdb->rdb_transactions) release_transaction(rdb->rdb_transactions); if (port->port_statement) release_statement(&port->port_statement); // If there is a network error, don't try to send another packet, just // free the packet and disconnect the port. Put something into firebird.log // informing the user of the following. if (status->getState() & Firebird::IStatus::STATE_ERRORS) { iscLogStatus("REMOTE INTERFACE/gds__detach: Unsuccessful detach from " "database.\n\tUncommitted work may have been lost.", status); reset(status); } disconnect(port); rdb = NULL; } catch (const Exception& ex) { ex.stuffException(status); } } void Attachment::internalDetach(CheckStatusWrapper* status) { /************************************** * * g d s _ d e t a c h * ************************************** * * Functional description * Close down a database. * **************************************/ reset(status); freeClientData(status); } void Attachment::detach(CheckStatusWrapper* status) { internalDetach(status); if (status->isEmpty()) release(); } void Attachment::deprecatedDetach(CheckStatusWrapper* status) { internalDetach(status); } void Attachment::internalDropDatabase(CheckStatusWrapper* status) { /************************************** * * i s c _ d r o p _ d a t a b a s e * ************************************** * * Functional description * Close down and purge a database. * **************************************/ try { reset(status); CHECK_HANDLE(rdb, isc_bad_db_handle); rem_port* port = rdb->rdb_port; RemotePortGuard portGuard(port, FB_FUNCTION); try { release_object(status, rdb, op_drop_database, rdb->rdb_id); } catch (const status_exception& ex) { ex.stuffException(status); if (ex.value()[1] != isc_drdb_completed_with_errs) { return; } } while (rdb->rdb_events) release_event(rdb->rdb_events); while (rdb->rdb_requests) release_request(rdb->rdb_requests); while (rdb->rdb_sql_requests) release_sql_request(rdb->rdb_sql_requests); while (rdb->rdb_transactions) release_transaction(rdb->rdb_transactions); if (port->port_statement) release_statement(&port->port_statement); disconnect(port); rdb = NULL; } catch (const Exception& ex) { ex.stuffException(status); } } void Attachment::dropDatabase(CheckStatusWrapper* status) { internalDropDatabase(status); if (status->isEmpty()) release(); } void Attachment::deprecatedDropDatabase(CheckStatusWrapper* status) { internalDropDatabase(status); } SLONG Attachment::getSingleInfo(CheckStatusWrapper* status, UCHAR infoItem) { UCHAR buff[16]; getInfo(status, 1, &infoItem, sizeof(buff), buff); if (status->getState() & IStatus::STATE_ERRORS) return 0; const UCHAR* p = buff; const UCHAR* const end = buff + sizeof(buff); UCHAR item; while ((item = *p++) != isc_info_end && p < end - 1) { const SLONG length = gds__vax_integer(p, 2); p += 2; if (item == infoItem) return gds__vax_integer(p, (SSHORT)length); fb_assert(false); p += length; } return 0; } void Attachment::execWithCheck(CheckStatusWrapper* status, const string& stmt) { /************************************** * * Used to execute "SET xxx TIMEOUT" statements. Checks for protocol version * and convert expected SQL error into isc_wish_list error. The only possible * case is when modern network server works with legacy engine. * **************************************/ if (rdb->rdb_port->port_protocol >= PROTOCOL_STMT_TOUT) { execute(status, NULL, stmt.length(), stmt.c_str(), SQL_DIALECT_CURRENT, NULL, NULL, NULL, NULL); if (!(status->getState() & IStatus::STATE_ERRORS)) return; // handle isc_dsql_token_unk_err const ISC_STATUS* errs = status->getErrors(); if (!fb_utils::containsErrorCode(errs, isc_sqlerr) || !fb_utils::containsErrorCode(errs, isc_dsql_token_unk_err)) { return; } status->init(); } status->setErrors(Arg::Gds(isc_wish_list).value()); } unsigned int Attachment::getIdleTimeout(CheckStatusWrapper* status) { if (rdb->rdb_port->port_protocol >= PROTOCOL_STMT_TOUT) return getSingleInfo(status, fb_info_ses_idle_timeout_att); status->setErrors(Arg::Gds(isc_wish_list).value()); return 0; } void Attachment::setIdleTimeout(CheckStatusWrapper* status, unsigned int timeOut) { string stmt; stmt.printf("SET SESSION IDLE TIMEOUT %lu", timeOut); execWithCheck(status, stmt); } unsigned int Attachment::getStatementTimeout(CheckStatusWrapper* status) { if (rdb->rdb_port->port_protocol >= PROTOCOL_STMT_TOUT) return getSingleInfo(status, fb_info_statement_timeout_att); status->setErrors(Arg::Gds(isc_wish_list).value()); return 0; } void Attachment::setStatementTimeout(CheckStatusWrapper* status, unsigned int timeOut) { string stmt; stmt.printf("SET STATEMENT TIMEOUT %lu", timeOut); execWithCheck(status, stmt); } Batch* Attachment::createBatch(CheckStatusWrapper* status, ITransaction* transaction, unsigned stmtLength, const char* sqlStmt, unsigned dialect, IMessageMetadata* inMetadata, unsigned parLength, const unsigned char* par) { /************************************** * * c r e a t e B a t c h * ************************************** * * Functional description * Create jdbc-style batch for SQL statement. * **************************************/ Statement* stmt = prepare(status, transaction, stmtLength, sqlStmt, dialect, 0); if (status->getState() & Firebird::IStatus::STATE_ERRORS) { return NULL; } Batch* rc = stmt->createBatch(status, inMetadata, parLength, par); if (status->getState() & Firebird::IStatus::STATE_ERRORS) { stmt->release(); return NULL; } rc->tmpStatement = true; return rc; } Batch* Statement::createBatch(CheckStatusWrapper* status, IMessageMetadata* inMetadata, unsigned parLength, const unsigned char* par) { /************************************** * * c r e a t e B a t c h * ************************************** * * Functional description * Create jdbc-style batch for prepared statement. * **************************************/ try { reset(status); // Check and validate handles, etc. CHECK_HANDLE(statement, isc_bad_req_handle); Rdb* rdb = statement->rsr_rdb; CHECK_HANDLE(rdb, isc_bad_db_handle); rem_port* port = rdb->rdb_port; if (port->port_protocol < PROTOCOL_VERSION16) unsupported(); // Build input BLR RefPtr meta; if (!inMetadata) { meta.assignRefNoIncr(getInputMetadata(status)); check(status); inMetadata = meta; } BlrFromMessage inBlr(inMetadata, dialect, port->port_protocol); const unsigned int in_blr_length = inBlr.getLength(); const UCHAR* const in_blr = inBlr.getBytes(); // Validate data length CHECK_LENGTH(port, in_blr_length); RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); delete statement->rsr_bind_format; statement->rsr_bind_format = NULL; if (port->port_statement) { delete port->port_statement->rsr_select_format; port->port_statement->rsr_select_format = NULL; } // Parse the blr describing the message, if there is any. if (in_blr_length) statement->rsr_bind_format = PARSE_msg_format(in_blr, in_blr_length); RMessage* message = NULL; if (!statement->rsr_buffer) { statement->rsr_buffer = message = FB_NEW RMessage(0); statement->rsr_message = message; message->msg_next = message; statement->rsr_fmt_length = 0; } else message = statement->rsr_message = statement->rsr_buffer; statement->rsr_flags.clear(Rsr::FETCHED); statement->rsr_format = statement->rsr_bind_format; statement->rsr_batch_stream.blobRemaining = 0; statement->clearException(); // set up the packet for the other guy... PACKET* packet = &rdb->rdb_packet; packet->p_operation = op_batch_create; P_BATCH_CREATE* batch = &packet->p_batch_create; batch->p_batch_statement = statement->rsr_id; batch->p_batch_blr.cstr_length = in_blr_length; batch->p_batch_blr.cstr_address = in_blr; batch->p_batch_msglen = inMetadata->getMessageLength(status); check(status); batch->p_batch_pb.cstr_length = parLength; batch->p_batch_pb.cstr_address = par; if (port->port_flags & PORT_lazy) { send_partial_packet(port, packet); defer_packet(port, packet, true); } else { send_and_receive(status, rdb, packet); } message->msg_address = NULL; Batch* b = FB_NEW Batch(this, inMetadata, parLength, par); b->addRef(); return b; } catch (const Exception& ex) { ex.stuffException(status); } return NULL; } Batch::Batch(Statement* s, IMessageMetadata* inFmt, unsigned parLength, const unsigned char* par) : messageStream(0), blobStream(nullptr), sizePointer(nullptr), messageSize(0), alignedSize(0), blobBufferSize(0), messageBufferSize(0), flags(0), stmt(s), format(inFmt), blobAlign(0), blobPolicy(BLOB_NONE), segmented(false), defSegmented(false), batchActive(false), messageCount(0), blobCount(0), serverSize(0), blobHeadSize(0), tmpStatement(false) { LocalStatus ls; CheckStatusWrapper st(&ls); messageSize = format->getMessageLength(&st); check(&st); alignedSize = format->getAlignedLength(&st); check(&st); memset(&genId, 0, sizeof(genId)); ClumpletReader rdr(ClumpletReader::WideTagged, par, parLength); for (rdr.rewind(); !rdr.isEof(); rdr.moveNext()) { UCHAR t = rdr.getClumpTag(); switch (t) { case TAG_MULTIERROR: case TAG_RECORD_COUNTS: if (rdr.getInt()) flags |= (1 << t); else flags &= ~(1 << t); break; case TAG_BLOB_POLICY: blobPolicy = rdr.getInt(); switch (blobPolicy) { case BLOB_ID_ENGINE: case BLOB_ID_USER: case BLOB_STREAM: break; default: blobPolicy = BLOB_NONE; break; } break; } } s->getStatement()->rsr_batch_flags = flags; // allocate buffers Rsr* statement = stmt->getStatement(); CHECK_HANDLE(statement, isc_bad_req_handle); Rdb* rdb = statement->rsr_rdb; CHECK_HANDLE(rdb, isc_bad_db_handle); rem_port* port = rdb->rdb_port; blobBufferSize = port->getPortConfig()->getClientBatchBuffer(); messageBufferSize = blobBufferSize / alignedSize; if (!messageBufferSize) messageBufferSize = 1; messageStreamBuffer.reset(FB_NEW UCHAR[messageBufferSize * alignedSize]); if (blobPolicy != BLOB_NONE) { blobStreamBuffer.reset(FB_NEW UCHAR[blobBufferSize]); blobStream = blobStreamBuffer; } } void Batch::add(CheckStatusWrapper* status, unsigned count, const void* inBuffer) { try { // Check and validate handles, etc. if (!stmt) { Arg::Gds(isc_bad_req_handle).raise(); } Rsr* statement = stmt->getStatement(); CHECK_HANDLE(statement, isc_bad_req_handle); Rdb* rdb = statement->rsr_rdb; CHECK_HANDLE(rdb, isc_bad_db_handle); rem_port* port = rdb->rdb_port; if (count == 0) return; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); putMessageData(count, inBuffer); batchActive = true; } catch (const Exception& ex) { ex.stuffException(status); } } void Batch::sendMessagePacket(unsigned count, const UCHAR* ptr, bool flash) { Rsr* statement = stmt->getStatement(); CHECK_HANDLE(statement, isc_bad_req_handle); Rdb* rdb = statement->rsr_rdb; CHECK_HANDLE(rdb, isc_bad_db_handle); rem_port* port = rdb->rdb_port; PACKET* packet = &rdb->rdb_packet; packet->p_operation = op_batch_msg; P_BATCH_MSG* batch = &packet->p_batch_msg; batch->p_batch_statement = statement->rsr_id; batch->p_batch_messages = count; batch->p_batch_data.cstr_address = const_cast(ptr); statement->rsr_batch_size = alignedSize; sendDeferredPacket(nullptr, port, packet, flash); messageCount += count; } void Batch::addBlob(CheckStatusWrapper* status, unsigned length, const void* inBuffer, ISC_QUAD* blobId, unsigned parLength, const unsigned char* par) { try { // Check and validate handles, etc. if (!stmt) { Arg::Gds(isc_bad_req_handle).raise(); } Rsr* statement = stmt->getStatement(); CHECK_HANDLE(statement, isc_bad_req_handle); Rdb* rdb = statement->rsr_rdb; CHECK_HANDLE(rdb, isc_bad_db_handle); rem_port* port = rdb->rdb_port; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); // Policy check switch (blobPolicy) { case IBatch::BLOB_ID_ENGINE: genBlobId(blobId); break; case IBatch::BLOB_ID_USER: break; default: (Arg::Gds(isc_batch_policy) << "addBlob").raise(); } // Build blob HDR in stream newBlob(); putBlobData(sizeof *blobId, blobId); setSizePointer(); putBlobData(sizeof parLength, &parLength); putBlobData(sizeof parLength, &parLength); putBlobData(parLength, par); segmented = parLength ? fb_utils::isBpbSegmented(parLength, par) : defSegmented; // Store blob data putSegment(length, inBuffer); batchActive = true; } catch (const Exception& ex) { ex.stuffException(status); } } void Batch::appendBlobData(CheckStatusWrapper* status, unsigned length, const void* inBuffer) { try { // Check and validate handles, etc. if (!stmt) { Arg::Gds(isc_bad_req_handle).raise(); } // Policy check switch (blobPolicy) { case IBatch::BLOB_ID_USER: case IBatch::BLOB_ID_ENGINE: break; default: (Arg::Gds(isc_batch_policy) << "appendBlobData").raise(); } Rsr* statement = stmt->getStatement(); CHECK_HANDLE(statement, isc_bad_req_handle); Rdb* rdb = statement->rsr_rdb; CHECK_HANDLE(rdb, isc_bad_db_handle); rem_port* port = rdb->rdb_port; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); // Store blob data putSegment(length, inBuffer); } catch (const Exception& ex) { ex.stuffException(status); } } void Batch::addBlobStream(CheckStatusWrapper* status, unsigned length, const void* inBuffer) { try { // Check and validate handles, etc. if (!stmt) { Arg::Gds(isc_bad_req_handle).raise(); } // Policy check if (blobPolicy != IBatch::BLOB_STREAM) { (Arg::Gds(isc_batch_policy) << "addBlobStream").raise(); } Rsr* statement = stmt->getStatement(); CHECK_HANDLE(statement, isc_bad_req_handle); Rdb* rdb = statement->rsr_rdb; CHECK_HANDLE(rdb, isc_bad_db_handle); rem_port* port = rdb->rdb_port; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); // Store stream data putBlobData(length, inBuffer); batchActive = true; } catch (const Exception& ex) { ex.stuffException(status); } } void Batch::sendBlobPacket(unsigned size, const UCHAR* ptr, bool flash) { Rsr* statement = stmt->getStatement(); Rdb* rdb = statement->rsr_rdb; rem_port* port = rdb->rdb_port; setServerInfo(); fb_assert(!(size % blobAlign)); PACKET* packet = &rdb->rdb_packet; packet->p_operation = op_batch_blob_stream; P_BATCH_BLOB* batch = &packet->p_batch_blob; batch->p_batch_statement = statement->rsr_id; batch->p_batch_blob_data.cstr_address = const_cast(ptr); batch->p_batch_blob_data.cstr_length = size; sendDeferredPacket(nullptr, port, packet, flash); blobCount += size; } void Batch::sendDeferredPacket(IStatus* status, rem_port* port, PACKET* packet, bool flash) { if (port->port_flags & PORT_lazy) { send_partial_packet(port, packet); defer_packet(port, packet, true); if ((port->port_protocol >= PROTOCOL_VERSION17) && ((port->port_deferred_packets->getCount() >= DEFER_BATCH_LIMIT) || flash)) { packet->p_operation = op_batch_sync; send_packet(port, packet); receive_packet(port, packet); LocalStatus warning; port->checkResponse(&warning, packet, false); Rsr* statement = stmt->getStatement(); if (statement->haveException()) { cleanup(); statement->raiseException(); } } } else if (status) { send_and_receive(status, port->port_context, packet); } else { LocalStatus local; send_and_receive(&local, port->port_context, packet); } } void Batch::setDefaultBpb(CheckStatusWrapper* status, unsigned parLength, const unsigned char* par) { try { // Check and validate handles, etc. if (!stmt) Arg::Gds(isc_bad_req_handle).raise(); Rsr* statement = stmt->getStatement(); CHECK_HANDLE(statement, isc_bad_req_handle); Rdb* rdb = statement->rsr_rdb; CHECK_HANDLE(rdb, isc_bad_db_handle); rem_port* port = rdb->rdb_port; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); // Check for presence of any data in batch buffers if (batchHasData()) Arg::Gds(isc_batch_defbpb).raise(); // Set default segmentation flag defSegmented = fb_utils::isBpbSegmented(parLength, par); // Prepare and send the packet PACKET* packet = &rdb->rdb_packet; packet->p_operation = op_batch_set_bpb; P_BATCH_SETBPB* batch = &packet->p_batch_setbpb; batch->p_batch_statement = statement->rsr_id; batch->p_batch_blob_bpb.cstr_address = par; batch->p_batch_blob_bpb.cstr_length = parLength; sendDeferredPacket(status, port, packet, true); } catch (const Exception& ex) { ex.stuffException(status); } } unsigned Batch::getBlobAlignment(CheckStatusWrapper* status) { try { setServerInfo(); } catch (const Exception& ex) { ex.stuffException(status); } return blobAlign; } void Batch::setServerInfo() { if (blobAlign) return; // Check and validate handles, etc. if (!stmt) { Arg::Gds(isc_bad_req_handle).raise(); } Rsr* statement = stmt->getStatement(); CHECK_HANDLE(statement, isc_bad_req_handle); Rdb* rdb = statement->rsr_rdb; CHECK_HANDLE(rdb, isc_bad_db_handle); rem_port* port = rdb->rdb_port; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); LocalStatus ls; CheckStatusWrapper s(&ls); if (port->port_protocol < PROTOCOL_VERSION17) { UCHAR item = isc_info_sql_stmt_blob_align; UCHAR buffer[16]; info(&s, rdb, op_info_sql, statement->rsr_id, 0, 1, &item, 0, 0, sizeof(buffer), buffer); check(&s); // Extract from buffer if (buffer[0] != item) Arg::Gds(isc_batch_align).raise(); int len = gds__vax_integer(&buffer[1], 2); statement->rsr_batch_stream.alignment = blobAlign = gds__vax_integer(&buffer[3], len); if (!blobAlign) Arg::Gds(isc_batch_align).raise(); return; } // Perform info call to server UCHAR items[] = {IBatch::INF_BLOB_ALIGNMENT, IBatch::INF_BUFFER_BYTES_SIZE, IBatch::INF_BLOB_HEADER}; UCHAR buffer[64]; info(&s, rdb, op_info_batch, statement->rsr_id, 0, sizeof(items), items, 0, 0, sizeof(buffer), buffer); check(&s); // Extract from buffer ClumpletReader out(ClumpletReader::InfoResponse, buffer, sizeof(buffer)); for (out.rewind(); !out.isEof(); out.moveNext()) { UCHAR item = out.getClumpTag(); if (item == isc_info_end) break; switch(item) { case IBatch::INF_BLOB_ALIGNMENT: statement->rsr_batch_stream.alignment = blobAlign = out.getInt(); break; case IBatch::INF_BUFFER_BYTES_SIZE: serverSize = out.getInt(); break; case IBatch::INF_BLOB_HEADER: blobHeadSize = out.getInt(); break; case isc_info_error: (Arg::Gds(isc_batch_align) << Arg::Gds(out.getInt())).raise(); case isc_info_truncated: (Arg::Gds(isc_batch_align) << Arg::Gds(isc_random) << "truncated").raise(); default: { string msg; msg.printf("Wrong info item %u", item); (Arg::Gds(isc_batch_align) << Arg::Gds(isc_random) << msg).raise(); } } } if (! (blobAlign && serverSize && blobHeadSize)) Arg::Gds(isc_batch_align).raise(); } IMessageMetadata* Batch::getMetadata(CheckStatusWrapper* status) { reset(status); format->addRef(); return format; } void Batch::registerBlob(CheckStatusWrapper* status, const ISC_QUAD* existingBlob, ISC_QUAD* blobId) { try { // Check and validate handles, etc. if (!stmt) { Arg::Gds(isc_bad_req_handle).raise(); } Rsr* statement = stmt->getStatement(); CHECK_HANDLE(statement, isc_bad_req_handle); Rdb* rdb = statement->rsr_rdb; CHECK_HANDLE(rdb, isc_bad_db_handle); rem_port* port = rdb->rdb_port; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); if (blobPolicy == IBatch::BLOB_ID_ENGINE) genBlobId(blobId); PACKET* packet = &rdb->rdb_packet; packet->p_operation = op_batch_regblob; P_BATCH_REGBLOB* batch = &packet->p_batch_regblob; batch->p_batch_statement = statement->rsr_id; batch->p_batch_exist_id = *existingBlob; batch->p_batch_blob_id = *blobId; sendDeferredPacket(status, port, packet, true); } catch (const Exception& ex) { ex.stuffException(status); } } IBatchCompletionState* Batch::execute(CheckStatusWrapper* status, ITransaction* apiTra) { try { // Check and validate handles, etc. if (!stmt) { Arg::Gds(isc_bad_req_handle).raise(); } Rsr* statement = stmt->getStatement(); CHECK_HANDLE(statement, isc_bad_req_handle); Rdb* rdb = statement->rsr_rdb; CHECK_HANDLE(rdb, isc_bad_db_handle); rem_port* port = rdb->rdb_port; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); Rtr* transaction = NULL; Transaction* rt = stmt->getAttachment()->remoteTransactionInterface(apiTra); if (rt) { transaction = rt->getTransaction(); CHECK_HANDLE(transaction, isc_bad_trans_handle); } // Sanity checks complete - flash data in buffers flashBatch(); // Prepare and send execute packet PACKET* packet = &rdb->rdb_packet; packet->p_operation = op_batch_exec; P_BATCH_EXEC* batch = &packet->p_batch_exec; batch->p_batch_statement = statement->rsr_id; batch->p_batch_transaction = transaction->rtr_id; send_packet(port, packet); statement->rsr_batch_size = alignedSize; AutoPtr cs(FB_NEW BatchCompletionState(flags & (1 << IBatch::TAG_RECORD_COUNTS), 256)); statement->rsr_batch_cs = cs; receive_packet(port, packet); statement->rsr_batch_cs = nullptr; if (packet->p_operation == op_batch_cs) { // when working with 4.0.0 server we could not raise it in advance... statement->clearException(); return cs.release(); } REMOTE_check_response(status, rdb, packet); } catch (const Exception& ex) { ex.stuffException(status); } return nullptr; } void Batch::cancel(CheckStatusWrapper* status) { try { // Check and validate handles, etc. if (!stmt) { Arg::Gds(isc_dsql_cursor_err).raise(); } Rsr* statement = stmt->getStatement(); CHECK_HANDLE(statement, isc_bad_req_handle); Rdb* rdb = statement->rsr_rdb; rem_port* port = rdb->rdb_port; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); // Cleanup local data cleanup(); batchActive = false; // Prepare packet PACKET* packet = &rdb->rdb_packet; packet->p_operation = op_batch_cancel; P_BATCH_FREE_CANCEL* batch = &packet->p_batch_free_cancel; batch->p_batch_statement = statement->rsr_id; send_and_receive(status, rdb, packet); batchActive = false; } catch (const Exception& ex) { ex.stuffException(status); } } void Batch::freeClientData(CheckStatusWrapper* status, bool force) { try { // Check and validate handles, etc. if (!stmt) { Arg::Gds(isc_dsql_cursor_err).raise(); } Rsr* statement = stmt->getStatement(); CHECK_HANDLE(statement, isc_bad_req_handle); Rdb* rdb = statement->rsr_rdb; rem_port* port = rdb->rdb_port; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); PACKET* packet = &rdb->rdb_packet; packet->p_operation = op_batch_rls; P_BATCH_FREE_CANCEL* batch = &packet->p_batch_free_cancel; batch->p_batch_statement = statement->rsr_id; if (rdb->rdb_port->port_flags & PORT_lazy) { defer_packet(rdb->rdb_port, packet); packet->p_resp.p_resp_object = statement->rsr_id; } else { try { send_and_receive(status, rdb, packet); } catch (const Exception&) { if (!force) throw; } } releaseStatement(); } catch (const Exception& ex) { ex.stuffException(status); } } void Batch::internalClose(CheckStatusWrapper* status) { reset(status); freeClientData(status); } void Batch::close(CheckStatusWrapper* status) { internalClose(status); if (status->isEmpty()) release(); } void Batch::deprecatedClose(CheckStatusWrapper* status) { internalClose(status); } void Batch::getInfo(CheckStatusWrapper* status, unsigned int itemsLength, const unsigned char* items, unsigned int bufferLength, unsigned char* buffer) { try { ClumpletReader it(ClumpletReader::InfoItems, items, itemsLength); ClumpletWriter out(ClumpletReader::InfoResponse, bufferLength - 1); // place for isc_info_end / isc_info_truncated for (it.rewind(); !it.isEof(); it.moveNext()) { UCHAR item = it.getClumpTag(); if (item == isc_info_end) break; try { switch(item) { case IBatch::INF_BUFFER_BYTES_SIZE: setServerInfo(); if (serverSize) out.insertInt(item, serverSize); break; case IBatch::INF_DATA_BYTES_SIZE: out.insertInt(item, (messageCount + messageStream) * alignedSize); break; case IBatch::INF_BLOBS_BYTES_SIZE: if (blobStream) out.insertInt(item, blobCount + (blobStream - blobStreamBuffer)); break; case IBatch::INF_BLOB_ALIGNMENT: setServerInfo(); out.insertInt(item, blobAlign); break; case IBatch::INF_BLOB_HEADER: setServerInfo(); out.insertInt(item, blobHeadSize); break; default: out.insertInt(isc_info_error, isc_infunk); break; } } catch(const fatal_exception&) { // here it's sooner of all caused by writer overflow but anyway check that if (out.hasOverflow()) { memcpy(buffer, out.getBuffer(), out.getBufferLength()); buffer += out.getBufferLength(); *buffer++ = isc_info_truncated; if (out.getBufferLength() <= bufferLength - 2) *buffer++ = isc_info_end; return; } else throw; } } memcpy(buffer, out.getBuffer(), out.getBufferLength()); buffer += out.getBufferLength(); *buffer++ = isc_info_end; } catch (const Exception& ex) { ex.stuffException(status); } } void Batch::releaseStatement() { if (tmpStatement) { stmt->release(); } stmt = NULL; } Replicator* Attachment::createReplicator(CheckStatusWrapper* status) { /************************************** * * c r e a t e R e p l i c a t o r * ************************************** * * Functional description * Create data replication interface. * **************************************/ try { reset(status); // Check and validate handles, etc. CHECK_HANDLE(rdb, isc_bad_db_handle); rem_port* port = rdb->rdb_port; if (port->port_protocol < PROTOCOL_VERSION16) unsupported(); if (!replicator) replicator = FB_NEW Replicator(this); replicator->addRef(); return replicator; } catch (const Exception& ex) { ex.stuffException(status); } return NULL; } void Replicator::process(CheckStatusWrapper* status, unsigned length, const unsigned char* data) { try { reset(status); Rdb* rdb = attachment->getRdb(); CHECK_HANDLE(rdb, isc_bad_db_handle); rem_port* port = rdb->rdb_port; if (port->port_protocol < PROTOCOL_VERSION16) unsupported(); // Validate data length CHECK_LENGTH(port, length); PACKET* packet = &rdb->rdb_packet; packet->p_operation = op_repl_data; P_REPLICATE* repl = &packet->p_replicate; repl->p_repl_database = rdb->rdb_id; repl->p_repl_data.cstr_length = length; repl->p_repl_data.cstr_address = data; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); send_and_receive(status, rdb, packet); } catch (const Exception& ex) { ex.stuffException(status); } } void Replicator::internalClose(CheckStatusWrapper* status) { reset(status); freeClientData(status); } void Replicator::close(CheckStatusWrapper* status) { internalClose(status); if (status->isEmpty()) release(); } void Replicator::deprecatedClose(CheckStatusWrapper* status) { internalClose(status); } void Replicator::freeClientData(CheckStatusWrapper* status, bool force) { try { reset(status); if (attachment && attachment->replicator) { Rdb* rdb = attachment->getRdb(); CHECK_HANDLE(rdb, isc_bad_db_handle); rem_port* port = rdb->rdb_port; if (port->port_protocol < PROTOCOL_VERSION16) unsupported(); PACKET* packet = &rdb->rdb_packet; packet->p_operation = op_repl_data; P_REPLICATE* repl = &packet->p_replicate; repl->p_repl_database = rdb->rdb_id; repl->p_repl_data.cstr_length = 0; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); try { send_and_receive(status, rdb, packet); } catch (const Exception&) { if (!force) throw; } attachment->replicator = NULL; } } catch (const Exception& ex) { ex.stuffException(status); } } ITransaction* Statement::execute(CheckStatusWrapper* status, ITransaction* apiTra, IMessageMetadata* inMetadata, void* inBuffer, IMessageMetadata* outMetadata, void* outBuffer) { /************************************** * * d s q l _ e x e c u t e 2 * ************************************** * * Functional description * Execute a non-SELECT dynamic SQL statement. * **************************************/ try { reset(status); // Check and validate handles, etc. CHECK_HANDLE(statement, isc_bad_req_handle); Rdb* rdb = statement->rsr_rdb; CHECK_HANDLE(rdb, isc_bad_db_handle); rem_port* port = rdb->rdb_port; BlrFromMessage inBlr(inMetadata, dialect, port->port_protocol); const unsigned int in_blr_length = inBlr.getLength(); const UCHAR* const in_blr = inBlr.getBytes(); const unsigned int in_msg_length = inBlr.getMsgLength(); UCHAR* const in_msg = static_cast(inBuffer); BlrFromMessage outBlr(outMetadata, dialect, port->port_protocol); const unsigned int out_blr_length = outBlr.getLength(); const UCHAR* const out_blr = outBlr.getBytes(); const unsigned int out_msg_length = outBlr.getMsgLength(); UCHAR* const out_msg = static_cast(outBuffer); // Validate data length CHECK_LENGTH(port, in_blr_length); CHECK_LENGTH(port, in_msg_length); CHECK_LENGTH(port, out_blr_length); CHECK_LENGTH(port, out_msg_length); RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); Rtr* transaction = NULL; Transaction* rt = remAtt->remoteTransactionInterface(apiTra); if (rt) { transaction = rt->getTransaction(); CHECK_HANDLE(transaction, isc_bad_trans_handle); } // 24-Mar-2004 Nickolay Samofatov // Unconditionally deallocate existing formats that are left from // previous executions (possibly with different statement if // isc_dsql_prepare is called multiple times). // This should cure SF#919246 delete statement->rsr_bind_format; statement->rsr_bind_format = NULL; if (port->port_statement) { delete port->port_statement->rsr_select_format; port->port_statement->rsr_select_format = NULL; } // Parse the blr describing the message, if there is any. if (in_blr_length) statement->rsr_bind_format = PARSE_msg_format(in_blr, in_blr_length); // Parse the blr describing the output message. This is not the fetch // message! That comes later. if (out_blr_length) { if (!port->port_statement) port->port_statement = FB_NEW Rsr; port->port_statement->rsr_select_format = PARSE_msg_format(out_blr, out_blr_length); if (!port->port_statement->rsr_buffer) { RMessage* message2 = FB_NEW RMessage(0); port->port_statement->rsr_buffer = message2; port->port_statement->rsr_message = message2; message2->msg_next = message2; port->port_statement->rsr_fmt_length = 0; } } RMessage* message = NULL; if (!statement->rsr_buffer) { statement->rsr_buffer = message = FB_NEW RMessage(0); statement->rsr_message = message; message->msg_next = message; statement->rsr_fmt_length = 0; } else { message = statement->rsr_message = statement->rsr_buffer; } message->msg_address = const_cast(in_msg); statement->rsr_flags.clear(Rsr::FETCHED); statement->rsr_format = statement->rsr_bind_format; statement->clearException(); // set up the packet for the other guy... PACKET* packet = &rdb->rdb_packet; packet->p_operation = out_msg_length ? op_execute2 : op_execute; P_SQLDATA* sqldata = &packet->p_sqldata; sqldata->p_sqldata_statement = statement->rsr_id; sqldata->p_sqldata_transaction = transaction ? transaction->rtr_id : 0; sqldata->p_sqldata_blr.cstr_length = in_blr_length; sqldata->p_sqldata_blr.cstr_address = const_cast(in_blr); // safe, see protocol.cpp and server.cpp sqldata->p_sqldata_message_number = 0; sqldata->p_sqldata_messages = (statement->rsr_bind_format) ? 1 : 0; sqldata->p_sqldata_out_blr.cstr_length = out_blr_length; sqldata->p_sqldata_out_blr.cstr_address = const_cast(out_blr); sqldata->p_sqldata_out_message_number = 0; // out_msg_type sqldata->p_sqldata_timeout = statement->rsr_timeout; sqldata->p_sqldata_cursor_flags = 0; send_packet(port, packet); // Set up the response packet. We may receive an SQL response followed // by a normal response packet or simply a response packet. message->msg_address = NULL; if (out_msg_length) port->port_statement->rsr_message->msg_address = out_msg; receive_packet(port, packet); if (packet->p_operation != op_sql_response) REMOTE_check_response(status, rdb, packet); else { port->port_statement->rsr_message->msg_address = NULL; receive_response(status, rdb, packet); } if (transaction && !packet->p_resp.p_resp_object) { REMOTE_cleanup_transaction(transaction); release_transaction(transaction); transaction = NULL; rt->clear(); statement->rsr_rtr = NULL; return NULL; } else if (!transaction && packet->p_resp.p_resp_object) { transaction = make_transaction(rdb, packet->p_resp.p_resp_object); statement->rsr_rtr = transaction; Transaction* newTrans = FB_NEW Transaction(transaction, remAtt); newTrans->addRef(); return newTrans; } } catch (const Exception& ex) { ex.stuffException(status); } return apiTra; } ResultSet* Statement::openCursor(CheckStatusWrapper* status, Firebird::ITransaction* apiTra, IMessageMetadata* inMetadata, void* inBuffer, IMessageMetadata* outFormat, unsigned int flags) { /************************************** * * d s q l _ e x e c u t e 2 * ************************************** * * Functional description * Execute a non-SELECT dynamic SQL statement. * **************************************/ try { reset(status); // Check and validate handles, etc. CHECK_HANDLE(statement, isc_bad_req_handle); Rdb* rdb = statement->rsr_rdb; CHECK_HANDLE(rdb, isc_bad_db_handle); rem_port* port = rdb->rdb_port; BlrFromMessage inBlr(inMetadata, dialect, port->port_protocol); const unsigned int in_blr_length = inBlr.getLength(); const UCHAR* const in_blr = inBlr.getBytes(); const unsigned int in_msg_length = inBlr.getMsgLength(); UCHAR* const in_msg = static_cast(inBuffer); RefPtr defaultOutputFormat; if (!outFormat) { defaultOutputFormat.assignRefNoIncr(this->getOutputMetadata(status)); if (status->getState() & Firebird::IStatus::STATE_ERRORS) { return NULL; } if (defaultOutputFormat) { outFormat = defaultOutputFormat; } } BlrFromMessage outBlr((outFormat == DELAYED_OUT_FORMAT ? NULL : outFormat), dialect, port->port_protocol); const unsigned int out_blr_length = outBlr.getLength(); const UCHAR* const out_blr = outBlr.getBytes(); // Validate data length CHECK_LENGTH(port, in_blr_length); CHECK_LENGTH(port, in_msg_length); CHECK_LENGTH(port, out_blr_length); RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); Rtr* transaction = NULL; Transaction* rt = remAtt->remoteTransactionInterface(apiTra); if (rt) { transaction = rt->getTransaction(); CHECK_HANDLE(transaction, isc_bad_trans_handle); } // 24-Mar-2004 Nickolay Samofatov // Unconditionally deallocate existing formats that are left from // previous executions (possibly with different statement if // isc_dsql_prepare is called multiple times). // This should cure SF#919246 delete statement->rsr_bind_format; statement->rsr_bind_format = NULL; if (port->port_statement) { delete port->port_statement->rsr_select_format; port->port_statement->rsr_select_format = NULL; } // Parse the blr describing the message, if there is any. if (in_blr_length) statement->rsr_bind_format = PARSE_msg_format(in_blr, in_blr_length); RMessage* message = NULL; if (!statement->rsr_buffer) { statement->rsr_buffer = message = FB_NEW RMessage(0); statement->rsr_message = message; message->msg_next = message; statement->rsr_fmt_length = 0; } else { message = statement->rsr_message = statement->rsr_buffer; } message->msg_address = const_cast(in_msg); statement->rsr_flags.clear(Rsr::FETCHED); statement->rsr_format = statement->rsr_bind_format; statement->clearException(); // set up the packet for the other guy... PACKET* packet = &rdb->rdb_packet; packet->p_operation = op_execute; P_SQLDATA* sqldata = &packet->p_sqldata; sqldata->p_sqldata_statement = statement->rsr_id; sqldata->p_sqldata_transaction = transaction ? transaction->rtr_id : 0; sqldata->p_sqldata_blr.cstr_length = in_blr_length; sqldata->p_sqldata_blr.cstr_address = const_cast(in_blr); // safe, see protocol.cpp and server.cpp sqldata->p_sqldata_message_number = 0; sqldata->p_sqldata_messages = (statement->rsr_bind_format) ? 1 : 0; sqldata->p_sqldata_out_blr.cstr_length = out_blr_length; sqldata->p_sqldata_out_blr.cstr_address = const_cast(out_blr); sqldata->p_sqldata_out_message_number = 0; // out_msg_type sqldata->p_sqldata_timeout = statement->rsr_timeout; sqldata->p_sqldata_cursor_flags = flags; { Firebird::Cleanup msgClean([&message] { message->msg_address = NULL; }); if (statement->rsr_flags.test(Rsr::DEFER_EXECUTE)) { send_partial_packet(port, packet); defer_packet(port, packet, true); } else { send_and_receive(status, rdb, packet); } } ResultSet* rs = FB_NEW ResultSet(this, outFormat, flags); rs->addRef(); return rs; } catch (const Exception& ex) { ex.stuffException(status); } return NULL; } IResultSet* Attachment::openCursor(CheckStatusWrapper* status, ITransaction* transaction, unsigned int stmtLength, const char* sqlStmt, unsigned dialect, IMessageMetadata* inMetadata, void* inBuffer, IMessageMetadata* outMetadata, const char* cursorName, unsigned int cursorFlags) { Statement* stmt = prepare(status, transaction, stmtLength, sqlStmt, dialect, (outMetadata ? 0 : IStatement::PREPARE_PREFETCH_OUTPUT_PARAMETERS)); if (status->getState() & Firebird::IStatus::STATE_ERRORS) { return NULL; } ResultSet* rc = stmt->openCursor(status, transaction, inMetadata, inBuffer, outMetadata, cursorFlags); if (status->getState() & Firebird::IStatus::STATE_ERRORS) { stmt->release(); return NULL; } if (cursorName) { stmt->setCursorName(status, cursorName); if (status->getState() & Firebird::IStatus::STATE_ERRORS) { rc->release(); stmt->release(); return NULL; } } rc->tmpStatement = true; return rc; } ITransaction* Attachment::execute(CheckStatusWrapper* status, ITransaction* apiTra, unsigned int stmtLength, const char* sqlStmt, unsigned int dialect, IMessageMetadata* inMetadata, void* inBuffer, IMessageMetadata* outMetadata, void* outBuffer) { /************************************** * * d s q l _ e x e c u t e _ i m m e d i a t e 2 * ************************************** * * Functional description * Prepare and execute a statement. * **************************************/ try { // Check and validate handles, etc. CHECK_HANDLE(rdb, isc_bad_db_handle); rem_port* port = rdb->rdb_port; BlrFromMessage inBlr(inMetadata, dialect, port->port_protocol); const unsigned int in_blr_length = inBlr.getLength(); const UCHAR* const in_blr = inBlr.getBytes(); const unsigned int in_msg_length = inBlr.getMsgLength(); UCHAR* const in_msg = static_cast(inBuffer); BlrFromMessage outBlr(outMetadata, dialect, port->port_protocol); const unsigned int out_blr_length = outBlr.getLength(); const UCHAR* const out_blr = outBlr.getBytes(); const unsigned int out_msg_length = outBlr.getMsgLength(); UCHAR* const out_msg = static_cast(outBuffer); // Validate data length CHECK_LENGTH(port, in_blr_length); CHECK_LENGTH(port, in_msg_length); CHECK_LENGTH(port, out_blr_length); CHECK_LENGTH(port, out_msg_length); if (sqlStmt && !stmtLength) stmtLength = static_cast(strlen(sqlStmt)); // Validate string length CHECK_LENGTH(port, stmtLength); RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); Rtr* transaction = NULL; Transaction* rt = remoteTransactionInterface(apiTra); if (rt) { transaction = rt->getTransaction(); CHECK_HANDLE(transaction, isc_bad_trans_handle); } if (dialect > 10) { // dimitr: adjust dialect received after // a multi-hop transmission to be // redirected in its original value. dialect /= 10; } reset(status); Rsr* statement = port->port_statement; if (!statement) { statement = port->port_statement = FB_NEW Rsr; } // reset statement buffers clear_queue(rdb->rdb_port); REMOTE_reset_statement(statement); delete statement->rsr_bind_format; statement->rsr_bind_format = NULL; delete statement->rsr_select_format; statement->rsr_select_format = NULL; if (in_msg_length || out_msg_length) { if (in_blr_length) statement->rsr_bind_format = PARSE_msg_format(in_blr, in_blr_length); if (out_blr_length) statement->rsr_select_format = PARSE_msg_format(out_blr, out_blr_length); } RMessage* message = 0; if (!statement->rsr_buffer) { statement->rsr_buffer = message = FB_NEW RMessage(0); statement->rsr_message = message; message->msg_next = message; statement->rsr_fmt_length = 0; } else { message = statement->rsr_message = statement->rsr_buffer; } message->msg_address = const_cast(in_msg); statement->clearException(); // set up the packet for the other guy... PACKET* packet = &rdb->rdb_packet; packet->p_operation = (in_msg_length || out_msg_length) ? op_exec_immediate2 : op_exec_immediate; P_SQLST* ex_now = &packet->p_sqlst; ex_now->p_sqlst_transaction = transaction ? transaction->rtr_id : 0; ex_now->p_sqlst_SQL_dialect = dialect; ex_now->p_sqlst_SQL_str.cstr_length = stmtLength; ex_now->p_sqlst_SQL_str.cstr_address = reinterpret_cast(sqlStmt); ex_now->p_sqlst_items.cstr_length = 0; ex_now->p_sqlst_buffer_length = 0; ex_now->p_sqlst_blr.cstr_length = in_blr_length; ex_now->p_sqlst_blr.cstr_address = const_cast(in_blr); ex_now->p_sqlst_message_number = 0; ex_now->p_sqlst_messages = (in_msg_length && statement->rsr_bind_format) ? 1 : 0; ex_now->p_sqlst_out_blr.cstr_length = out_blr_length; ex_now->p_sqlst_out_blr.cstr_address = const_cast(out_blr); ex_now->p_sqlst_out_message_number = 0; // out_msg_type send_packet(port, packet); // SEND could have changed the message message = statement->rsr_message; // Set up the response packet. We may receive an SQL response followed // by a normal response packet or simply a response packet. if (in_msg_length || out_msg_length) port->port_statement->rsr_message->msg_address = out_msg; receive_packet(rdb->rdb_port, packet); if (packet->p_operation != op_sql_response) REMOTE_check_response(status, rdb, packet); else { message->msg_address = NULL; receive_response(status, rdb, packet); } if (transaction && !packet->p_resp.p_resp_object) { REMOTE_cleanup_transaction(transaction); release_transaction(transaction); transaction = NULL; rt->clear(); return NULL; } else if (!transaction && packet->p_resp.p_resp_object) { transaction = make_transaction(rdb, packet->p_resp.p_resp_object); Firebird::ITransaction* newTrans = FB_NEW Transaction(transaction, this); newTrans->addRef(); return newTrans; } } catch (const Exception& ex) { ex.stuffException(status); } return apiTra; } void Statement::freeClientData(CheckStatusWrapper* status, bool force) { /************************************** * * d s q l _ f r e e _ s t a t e m e n t * ************************************** * * Functional description * Release request for a Dynamic SQL statement * **************************************/ try { // Check and validate handles, etc. if (!statement) { return; } CHECK_HANDLE(statement, isc_bad_req_handle); Rdb* rdb = statement->rsr_rdb; CHECK_HANDLE(rdb, isc_bad_db_handle); rem_port* port = rdb->rdb_port; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); fb_assert(statement->haveException() == 0); statement->clearException(); if (statement->rsr_flags.test(Rsr::LAZY)) { release_sql_request(statement); statement = NULL; return; } PACKET* packet = &rdb->rdb_packet; packet->p_operation = op_free_statement; P_SQLFREE* free_stmt = &packet->p_sqlfree; free_stmt->p_sqlfree_statement = statement->rsr_id; free_stmt->p_sqlfree_option = DSQL_drop; if (rdb->rdb_port->port_flags & PORT_lazy) { send_packet(rdb->rdb_port, packet); defer_packet(rdb->rdb_port, packet, true); packet->p_resp.p_resp_object = statement->rsr_id; } else { try { send_and_receive(status, rdb, packet); } catch (const Exception&) { if (!force) throw; } } if (packet->p_resp.p_resp_object == INVALID_OBJECT) { release_sql_request(statement); } else { statement->rsr_flags.clear(Rsr::FETCHED); statement->rsr_rtr = NULL; clear_queue(rdb->rdb_port); REMOTE_reset_statement(statement); } statement = NULL; } catch (const Exception& ex) { ex.stuffException(status); } } void Statement::internalFree(CheckStatusWrapper* status) { /************************************** * * d s q l _ f r e e _ s t a t e m e n t * ************************************** * * Functional description * Release request for a Dynamic SQL statement * **************************************/ reset(status); freeClientData(status); } void Statement::free(CheckStatusWrapper* status) { internalFree(status); if (status->isEmpty()) release(); } void Statement::deprecatedFree(CheckStatusWrapper* status) { internalFree(status); } Statement* Attachment::createStatement(CheckStatusWrapper* status, unsigned dialect) { reset(status); Rsr* statement = NULL; if (rdb->rdb_port->port_flags & PORT_lazy) { statement = FB_NEW Rsr; statement->rsr_rdb = rdb; statement->rsr_id = INVALID_OBJECT; statement->rsr_flags.set(Rsr::LAZY); } else { PACKET* packet = &rdb->rdb_packet; packet->p_operation = op_allocate_statement; packet->p_rlse.p_rlse_object = rdb->rdb_id; send_and_receive(status, rdb, packet); // Allocate SQL request block statement = FB_NEW Rsr; statement->rsr_rdb = rdb; statement->rsr_id = packet->p_resp.p_resp_object; // register the object SET_OBJECT(rdb, statement, statement->rsr_id); } statement->rsr_next = rdb->rdb_sql_requests; rdb->rdb_sql_requests = statement; Statement* s = FB_NEW Statement(statement, this, dialect); s->addRef(); return s; } Statement* Attachment::prepare(CheckStatusWrapper* status, ITransaction* apiTra, unsigned int stmtLength, const char* sqlStmt, unsigned int dialect, unsigned int flags) { /************************************** * * d s q l _ p r e p a r e * ************************************** * * Functional description * Prepare a dynamic SQL statement for execution. * **************************************/ Statement* stmt = NULL; try { reset(status); // Check and validate handles, etc. CHECK_HANDLE(rdb, isc_bad_db_handle); rem_port* port = rdb->rdb_port; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); Rtr* transaction = NULL; if (apiTra) { transaction = remoteTransaction(apiTra); CHECK_HANDLE(transaction, isc_bad_trans_handle); } if (sqlStmt && !stmtLength) stmtLength = static_cast(strlen(sqlStmt)); // Validate string length CHECK_LENGTH(port, stmtLength); if (dialect > 10) { // dimitr: adjust dialect received after // a multi-hop transmission to be // redirected in its original value. dialect /= 10; } // create new statement stmt = createStatement(status, dialect); Rsr* statement = stmt->getStatement(); // reset current statement clear_queue(rdb->rdb_port); REMOTE_reset_statement(statement); // set up the packet for the other guy... PACKET* packet = &rdb->rdb_packet; if (statement->rsr_flags.test(Rsr::LAZY)) { packet->p_operation = op_allocate_statement; packet->p_rlse.p_rlse_object = rdb->rdb_id; send_partial_packet(rdb->rdb_port, packet); } Array items, buffer; buffer.resize(StatementMetadata::buildInfoItems(items, flags)); // Validate data length CHECK_LENGTH(port, items.getCount()); CHECK_LENGTH(port, buffer.getCount()); packet->p_operation = op_prepare_statement; P_SQLST* prepare = &packet->p_sqlst; prepare->p_sqlst_transaction = transaction ? transaction->rtr_id : 0; prepare->p_sqlst_statement = statement->rsr_id; prepare->p_sqlst_SQL_dialect = dialect; prepare->p_sqlst_SQL_str.cstr_length = stmtLength; prepare->p_sqlst_SQL_str.cstr_address = reinterpret_cast(sqlStmt); prepare->p_sqlst_items.cstr_length = (ULONG) items.getCount(); prepare->p_sqlst_items.cstr_address = items.begin(); prepare->p_sqlst_buffer_length = (ULONG) buffer.getCount(); send_packet(rdb->rdb_port, packet); statement->rsr_flags.clear(Rsr::DEFER_EXECUTE); // Set up for the response packet. if (statement->rsr_flags.test(Rsr::LAZY)) { receive_response(status, rdb, packet); statement->rsr_id = packet->p_resp.p_resp_object; SET_OBJECT(rdb, statement, statement->rsr_id); statement->rsr_flags.clear(Rsr::LAZY); } P_RESP* response = &packet->p_resp; SaveString temp(response->p_resp_data, buffer.getCount(), buffer.begin()); try { receive_response(status, rdb, packet); stmt->parseMetadata(buffer); } catch (const Exception& ex) { ex.stuffException(status); } if (rdb->rdb_port->port_flags & PORT_lazy) { if (response->p_resp_object & STMT_DEFER_EXECUTE) { statement->rsr_flags.set(Rsr::DEFER_EXECUTE); } } else { fb_assert(!response->p_resp_object); response->p_resp_object = 0; } if (!(status->getState() & Firebird::IStatus::STATE_ERRORS)) { return stmt; } } catch (const Exception& ex) { ex.stuffException(status); } // free statement in case of error if (stmt) { stmt->release(); } return NULL; } void Statement::getInfo(CheckStatusWrapper* status, unsigned int itemsLength, const unsigned char* items, unsigned int bufferLength, unsigned char* buffer) { /************************************** * * d s q l _ s q l _ i n f o * ************************************** * * Functional description * Provide information on sql object. * **************************************/ try { reset(status); // Check and validate handles, etc. CHECK_HANDLE(statement, isc_bad_req_handle); Rdb* rdb = statement->rsr_rdb; rem_port* port = rdb->rdb_port; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); statement->raiseException(); if (!metadata.fillFromCache(itemsLength, items, bufferLength, buffer)) { info(status, rdb, op_info_sql, statement->rsr_id, 0, itemsLength, items, 0, 0, bufferLength, buffer); metadata.parse(bufferLength, buffer); } statement->raiseException(); } catch (const Exception& ex) { ex.stuffException(status); } } unsigned Statement::getType(CheckStatusWrapper* status) { try { reset(status); // Check and validate handles, etc. CHECK_HANDLE(statement, isc_bad_req_handle); Rdb* rdb = statement->rsr_rdb; rem_port* port = rdb->rdb_port; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); statement->raiseException(); return metadata.getType(); } catch (const Exception& ex) { ex.stuffException(status); } return 0; } unsigned Statement::getFlags(CheckStatusWrapper* status) { try { reset(status); // Check and validate handles, etc. CHECK_HANDLE(statement, isc_bad_req_handle); Rdb* rdb = statement->rsr_rdb; rem_port* port = rdb->rdb_port; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); statement->raiseException(); if (port->port_protocol >= PROTOCOL_VERSION13) { // we are in luck - use flags from server return metadata.getFlags(); } // Need to guess flags based on statement type unsigned value = IStatement::FLAG_REPEAT_EXECUTE; switch (metadata.getType()) { case isc_info_sql_stmt_ddl: value &= ~IStatement::FLAG_REPEAT_EXECUTE; break; case isc_info_sql_stmt_select: case isc_info_sql_stmt_select_for_upd: value |= IStatement::FLAG_HAS_CURSOR; break; } return value; } catch (const Exception& ex) { ex.stuffException(status); } return 0; } const char* Statement::getPlan(CheckStatusWrapper* status, FB_BOOLEAN detailed) { try { reset(status); // Check and validate handles, etc. CHECK_HANDLE(statement, isc_bad_req_handle); Rdb* rdb = statement->rsr_rdb; rem_port* port = rdb->rdb_port; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); statement->raiseException(); return metadata.getPlan(detailed); } catch (const Exception& ex) { ex.stuffException(status); } return NULL; } IMessageMetadata* Statement::getInputMetadata(CheckStatusWrapper* status) { try { reset(status); // Check and validate handles, etc. CHECK_HANDLE(statement, isc_bad_req_handle); Rdb* rdb = statement->rsr_rdb; rem_port* port = rdb->rdb_port; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); statement->raiseException(); return metadata.getInputMetadata(); } catch (const Exception& ex) { ex.stuffException(status); } return NULL; } IMessageMetadata* Statement::getOutputMetadata(CheckStatusWrapper* status) { try { reset(status); // Check and validate handles, etc. CHECK_HANDLE(statement, isc_bad_req_handle); Rdb* rdb = statement->rsr_rdb; rem_port* port = rdb->rdb_port; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); statement->raiseException(); return metadata.getOutputMetadata(); } catch (const Exception& ex) { ex.stuffException(status); } return NULL; } ISC_UINT64 Statement::getAffectedRecords(CheckStatusWrapper* status) { try { reset(status); // Check and validate handles, etc. CHECK_HANDLE(statement, isc_bad_req_handle); Rdb* rdb = statement->rsr_rdb; rem_port* port = rdb->rdb_port; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); statement->raiseException(); return metadata.getAffectedRecords(); } catch (const Exception& ex) { ex.stuffException(status); } return 0; } void Statement::setCursorName(CheckStatusWrapper* status, const char* cursor) { /***************************************** * * d s q l _ s e t _ c u r s o r * ***************************************** * * Functional Description * Declare a cursor for a dynamic request. * * Note: prior to version 6.0, this function terminated the * cursor name at the first blank. With delimited cursor * name support that is no longer sufficient. We now pass * the entire NULL-Terminated cursor name to the server, and let * the server deal with blank termination or not. * NOTE: THIS NOW MEANS THAT IF CURSOR is NOT null terminated * we will have inconsistant results with version 5.x. The only * "normal" way this happens is if this API is called from a * non-C host language. If that results in a later problem we * must provide a new API that takes a "cursor_name_length" * parameter. * *****************************************/ try { reset(status); // Check and validate handles, etc. Rsr* statement = getStatement(); CHECK_HANDLE(statement, isc_bad_req_handle); Rdb* rdb = statement->rsr_rdb; rem_port* port = rdb->rdb_port; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); statement->raiseException(); // set up the packet for the other guy... PACKET* packet = &rdb->rdb_packet; if (statement->rsr_flags.test(Rsr::LAZY)) { packet->p_operation = op_allocate_statement; packet->p_rlse.p_rlse_object = rdb->rdb_id; send_partial_packet(rdb->rdb_port, packet); } packet->p_operation = op_set_cursor; P_SQLCUR* sqlcur = &packet->p_sqlcur; sqlcur->p_sqlcur_statement = statement->rsr_id; const ULONG name_l = static_cast(strlen(cursor)); sqlcur->p_sqlcur_cursor_name.cstr_length = name_l + 1; sqlcur->p_sqlcur_cursor_name.cstr_address = reinterpret_cast(cursor); sqlcur->p_sqlcur_type = 0; // type send_packet(port, packet); if (statement->rsr_flags.test(Rsr::LAZY)) { receive_response(status, rdb, packet); statement->rsr_id = packet->p_resp.p_resp_object; SET_OBJECT(rdb, statement, statement->rsr_id); statement->rsr_flags.clear(Rsr::LAZY); } receive_response(status, rdb, packet); statement->raiseException(); } catch (const Exception& ex) { ex.stuffException(status); } } void ResultSet::setDelayedOutputFormat(CheckStatusWrapper* status, IMessageMetadata* format) { try { reset(status); // Check and validate handles, etc. if (!delayedFormat) { (Arg::Gds(isc_dsql_cursor_err) << Arg::Gds(isc_bad_req_handle)).raise(); } outputFormat = format; delayedFormat = false; } catch (const Exception& ex) { ex.stuffException(status); } } bool ResultSet::fetch(CheckStatusWrapper* status, void* buffer, P_FETCH operation, int position) { /************************************** * * d s q l _ f e t c h * ************************************** * * Functional description * Fetch next record from a dynamic SQL cursor. * **************************************/ reset(status); // Check and validate handles, etc. if (delayedFormat || !stmt) { (Arg::Gds(isc_dsql_cursor_err) << Arg::Gds(isc_bad_req_handle)).raise(); } Rsr* const statement = stmt->getStatement(); CHECK_HANDLE(statement, isc_bad_req_handle); Rdb* const rdb = statement->rsr_rdb; CHECK_HANDLE(rdb, isc_bad_db_handle); rem_port* const port = rdb->rdb_port; // Scrolling is not available in older protocols if (operation != fetch_next && port->port_protocol < PROTOCOL_FETCH_SCROLL) unsupported(); // Whether we're fetching relatively to the current position const bool relative = (operation == fetch_next || operation == fetch_prior || operation == fetch_relative); BlrFromMessage outBlr(outputFormat, stmt->getDialect(), port->port_protocol); unsigned int blr_length = outBlr.getLength(); const UCHAR* blr = outBlr.getBytes(); const unsigned int msg_length = outBlr.getMsgLength(); UCHAR* msg = static_cast(buffer); // Validate data length CHECK_LENGTH(port, blr_length); CHECK_LENGTH(port, msg_length); RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); if (!statement->rsr_flags.test(Rsr::FETCHED)) { // On first fetch, clear the end-of-stream flag & reset the message buffers statement->raiseException(); statement->rsr_flags.clear(Rsr::STREAM_END | Rsr::PAST_END | Rsr::STREAM_ERR); statement->rsr_rows_pending = 0; statement->rsr_fetch_operation = operation; statement->rsr_fetch_position = position; statement->clearException(); RMessage* message = statement->rsr_message; if (message) { statement->rsr_buffer = message; while (true) { message->msg_address = NULL; message = message->msg_next; if (message == statement->rsr_message) break; } } } else if (!relative) { // Clear the end-of-stream flag if the fetch is positioned absolutely statement->rsr_flags.clear(Rsr::STREAM_END | Rsr::PAST_END); } else if (statement->rsr_flags.test(Rsr::PAST_END)) { // If we're already at BOF/EOF and the requested fetch operation // cannot change our position, just do nothing if (operation == fetch_relative && position == 0) return false; if ((operation == fetch_next || (operation == fetch_relative && position > 0)) && statement->rsr_flags.test(Rsr::PAST_EOF)) { return false; } if ((operation == fetch_prior || (operation == fetch_relative && position < 0)) && statement->rsr_flags.test(Rsr::PAST_BOF)) { return false; } } // Parse the blr describing the message, if there is any. if (blr_length) { if (statement->rsr_user_select_format && statement->rsr_user_select_format != statement->rsr_select_format) { delete statement->rsr_user_select_format; } statement->rsr_user_select_format = PARSE_msg_format(blr, blr_length); if (statement->rsr_flags.test(Rsr::FETCHED)) blr_length = 0; else { delete statement->rsr_select_format; statement->rsr_select_format = statement->rsr_user_select_format; } } if (!statement->rsr_buffer) { statement->rsr_buffer = FB_NEW RMessage(0); statement->rsr_message = statement->rsr_buffer; statement->rsr_message->msg_next = statement->rsr_message; statement->rsr_fmt_length = 0; } RMessage* message = statement->rsr_message; #ifdef DEBUG fprintf(stdout, "Rows Pending in REM_fetch=%lu\n", statement->rsr_rows_pending); #endif // If the fetch direction was changed, we don't need the batched rows anymore. // Swallow them and reset the stream for subsequent fetches. if (operation != statement->rsr_fetch_operation || position != statement->rsr_fetch_position) { while (statement->rsr_rows_pending) receive_queued_packet(port, statement->rsr_id); if (statement->rsr_flags.test(Rsr::STREAM_ERR)) { statement->rsr_flags.clear(Rsr::STREAM_ERR); // hvlad: prevent subsequent fetches statement->rsr_flags.set(Rsr::STREAM_END); statement->raiseException(); } const SLONG adjustment = statement->getCursorAdjustment(); statement->rsr_flags.clear(Rsr::STREAM_END | Rsr::PAST_END); // We have some messages in the queue. Reset them for reuse. if (statement->rsr_msgs_waiting) { fb_assert(statement->rsr_fetch_operation == fetch_next || statement->rsr_fetch_operation == fetch_prior); RMessage* message = statement->rsr_message; if (message) { statement->rsr_buffer = message; while (true) { message->msg_address = NULL; message = message->msg_next; if (message == statement->rsr_message) break; } } statement->rsr_msgs_waiting = 0; } // If we had some rows batched and the requested scrolling is relative, // then move the server cursor to the actual client's position before proceeding. // We don't know the absolute client's position, but it's not really necessary. // rsr_msgs_waiting shows how much we're ahead the server, so we may re-position // the cursor relatively. if (relative && adjustment) { const bool isAhead = (statement->rsr_fetch_operation == fetch_next); PACKET* packet = &rdb->rdb_packet; packet->p_operation = op_fetch_scroll; P_SQLDATA* sqldata = &packet->p_sqldata; sqldata->p_sqldata_statement = statement->rsr_id; sqldata->p_sqldata_blr.cstr_length = 0; sqldata->p_sqldata_blr.cstr_address = nullptr; sqldata->p_sqldata_message_number = 0; // msg_type sqldata->p_sqldata_messages = statement->rsr_select_format ? 1 : 0; sqldata->p_sqldata_fetch_op = fetch_relative; sqldata->p_sqldata_fetch_pos = adjustment; send_packet(port, packet); // Receive response packets. If everything is OK, there should be two of them: // first with packet->p_sqldata.p_sqldata_messages == 1 and second with // packet->p_sqldata.p_sqldata_messages == 0 (end-of-batch). do { receive_packet(rdb->rdb_port, packet); // If we get an error, handle it if (packet->p_operation != op_fetch_response) { statement->rsr_flags.set(Rsr::STREAM_ERR); REMOTE_check_response(status, rdb, packet); break; } // If we get end-of-stream, something went seriously wrong, thus punt if (packet->p_sqldata.p_sqldata_status == 100) Arg::Gds(isc_req_sync).raise(); // We should get either the requested row or the end-of-batch marker fb_assert(packet->p_sqldata.p_sqldata_messages == 0 || packet->p_sqldata.p_sqldata_messages == 1); // Release the received message, we don't need it const auto message = statement->rsr_message; if (message && message->msg_address) { statement->rsr_message = message->msg_next; message->msg_address = NULL; } } while (packet->p_sqldata.p_sqldata_messages); } // These are the necessary conditions to continue fetching (see below) fb_assert(!statement->rsr_flags.test(Rsr::STREAM_END | Rsr::STREAM_ERR)); fb_assert(!statement->rsr_message->msg_address); fb_assert(!statement->rsr_rows_pending); } // Check to see if data is waiting. If not, solicite data. if ((!statement->rsr_flags.test(Rsr::STREAM_END | Rsr::STREAM_ERR) && !statement->rsr_message->msg_address && !statement->rsr_rows_pending) || ( // Low in inventory (statement->rsr_rows_pending <= statement->rsr_reorder_level) && (statement->rsr_msgs_waiting <= statement->rsr_reorder_level) && // Pipelining causes both server & client to // write at the same time. In XNET, writes // block for the other end to read - and so when both // attempt to write simultaneously, they end up // waiting indefinitely for the other end to read. (port->port_type != rem_port::XNET) && // We're fetching either forward or backward (operation == fetch_next || operation == fetch_prior) && // We've reached end-of-stream or there was an error !statement->rsr_flags.test(Rsr::STREAM_END | Rsr::STREAM_ERR) && // No error pending !statement->haveException() )) { // set up the packet for the other guy... PACKET* packet = &rdb->rdb_packet; packet->p_operation = (operation == fetch_next) ? op_fetch : op_fetch_scroll; P_SQLDATA* sqldata = &packet->p_sqldata; sqldata->p_sqldata_statement = statement->rsr_id; sqldata->p_sqldata_blr.cstr_length = blr_length; sqldata->p_sqldata_blr.cstr_address = const_cast(blr); sqldata->p_sqldata_message_number = 0; // msg_type sqldata->p_sqldata_messages = statement->rsr_select_format ? 1 : 0; sqldata->p_sqldata_fetch_op = operation; sqldata->p_sqldata_fetch_pos = position; if (statement->rsr_select_format) { if (operation == fetch_next || operation == fetch_prior) { sqldata->p_sqldata_messages = REMOTE_compute_batch_size( port, 0, op_fetch_response, statement->rsr_select_format); } // Reorder data when the local buffer is half empty statement->rsr_reorder_level = sqldata->p_sqldata_messages / 2; #ifdef DEBUG fprintf(stdout, "Recalculating Rows Pending in REM_fetch=%lu\n", statement->rsr_rows_pending); #endif } statement->rsr_rows_pending += sqldata->p_sqldata_messages; // We've either got data, or some is on the way, or we have an error, or we have EOF if (!(statement->rsr_msgs_waiting || statement->rsr_rows_pending || statement->haveException() || statement->rsr_flags.test(Rsr::STREAM_END))) { // We were asked to fetch from the statement, not ready for it. // Give up before sending something to the server. Arg::Gds(isc_req_sync).raise(); } // Make the batch request - and force the packet over the wire send_packet(port, packet); statement->rsr_batch_count++; statement->rsr_fetch_operation = operation; statement->rsr_fetch_position = position; // Queue up receipt of the pending data enqueue_receive(port, batch_dsql_fetch, rdb, statement, NULL); fb_assert(statement->rsr_rows_pending || !statement->rsr_select_format); } // Receive queued responses until we have some data for this cursor // or an error status has been received. // We've either got data, or some is on the way, or we have an error, or we have EOF fb_assert(statement->rsr_msgs_waiting || statement->rsr_rows_pending || statement->haveException() || statement->rsr_flags.test(Rsr::STREAM_END)); while (!statement->haveException() && // received a database error !statement->rsr_flags.test(Rsr::STREAM_END) && // reached end of stream statement->rsr_msgs_waiting < 2 && // Have looked ahead for end of batch statement->rsr_rows_pending) { // Hit end of batch receive_queued_packet(port, statement->rsr_id); } if (!statement->rsr_msgs_waiting) { if (statement->rsr_flags.test(Rsr::STREAM_END)) { // hvlad: we may have queued fetch packet but received end-of-stream before start // handling of this packet. Handle it now. clear_stmt_que(port, statement); // hvlad: as we processed all queued packets at code above we can leave Rsr::EOF_SET flag. // It allows us to return EOF for all subsequent isc_dsql_fetch calls until statement // will be re-executed (and without roundtrip to remote server). //statement->rsr_flags.clear(Rsr::STREAM_END); if (statement->rsr_flags.test(Rsr::BOF_SET)) statement->rsr_flags.set(Rsr::PAST_BOF); if (statement->rsr_flags.test(Rsr::EOF_SET)) statement->rsr_flags.set(Rsr::PAST_EOF); return false; } if (statement->rsr_flags.test(Rsr::STREAM_ERR)) { // The previous batch of receives ended with an error status. // We're all done returning data in the local queue. // Return that error status vector to the user. // Stuff in the error result to the user's vector statement->rsr_flags.clear(Rsr::STREAM_ERR); // hvlad: prevent subsequent fetches statement->rsr_flags.set(Rsr::STREAM_END); statement->raiseException(); } } statement->rsr_msgs_waiting--; message = statement->rsr_message; statement->rsr_message = message->msg_next; if (statement->rsr_user_select_format->fmt_length != msg_length) { status_exception::raise(Arg::Gds(isc_port_len) << Arg::Num(msg_length) << Arg::Num(statement->rsr_user_select_format->fmt_length)); } if (statement->rsr_user_select_format == statement->rsr_select_format) { if (!msg || !message->msg_address) { move_error(Arg::Gds(isc_dsql_sqlda_err)); // Msg 263 SQLDA missing or wrong number of variables } memcpy(msg, message->msg_address, msg_length); } else { mov_dsql_message(message->msg_address, statement->rsr_select_format, msg, statement->rsr_user_select_format); } message->msg_address = NULL; return true; } int ResultSet::fetchNext(CheckStatusWrapper* user_status, void* buffer) { try { return fetch(user_status, buffer, fetch_next) ? IStatus::RESULT_OK : IStatus::RESULT_NO_DATA; } catch (const Exception& ex) { ex.stuffException(user_status); } return IStatus::RESULT_ERROR; } int ResultSet::fetchPrior(CheckStatusWrapper* user_status, void* buffer) { try { if (!(flags & IStatement::CURSOR_TYPE_SCROLLABLE)) (Arg::Gds(isc_invalid_fetch_option) << Arg::Str("PRIOR")).raise(); return fetch(user_status, buffer, fetch_prior) ? IStatus::RESULT_OK : IStatus::RESULT_NO_DATA; } catch (const Exception& ex) { ex.stuffException(user_status); } return IStatus::RESULT_ERROR; } int ResultSet::fetchFirst(CheckStatusWrapper* user_status, void* buffer) { try { if (!(flags & IStatement::CURSOR_TYPE_SCROLLABLE)) (Arg::Gds(isc_invalid_fetch_option) << Arg::Str("FIRST")).raise(); return fetch(user_status, buffer, fetch_first) ? IStatus::RESULT_OK : IStatus::RESULT_NO_DATA; } catch (const Exception& ex) { ex.stuffException(user_status); } return IStatus::RESULT_ERROR; } int ResultSet::fetchLast(CheckStatusWrapper* user_status, void* buffer) { try { if (!(flags & IStatement::CURSOR_TYPE_SCROLLABLE)) (Arg::Gds(isc_invalid_fetch_option) << Arg::Str("LAST")).raise(); return fetch(user_status, buffer, fetch_last) ? IStatus::RESULT_OK : IStatus::RESULT_NO_DATA; } catch (const Exception& ex) { ex.stuffException(user_status); } return IStatus::RESULT_ERROR; } int ResultSet::fetchAbsolute(CheckStatusWrapper* user_status, int position, void* buffer) { try { if (!(flags & IStatement::CURSOR_TYPE_SCROLLABLE)) (Arg::Gds(isc_invalid_fetch_option) << Arg::Str("ABSOLUTE")).raise(); return fetch(user_status, buffer, fetch_absolute, position) ? IStatus::RESULT_OK : IStatus::RESULT_NO_DATA; } catch (const Exception& ex) { ex.stuffException(user_status); } return IStatus::RESULT_ERROR; } int ResultSet::fetchRelative(CheckStatusWrapper* user_status, int offset, void* buffer) { try { if (!(flags & IStatement::CURSOR_TYPE_SCROLLABLE)) (Arg::Gds(isc_invalid_fetch_option) << Arg::Str("RELATIVE")).raise(); return fetch(user_status, buffer, fetch_relative, offset) ? IStatus::RESULT_OK : IStatus::RESULT_NO_DATA; } catch (const Exception& ex) { ex.stuffException(user_status); } return IStatus::RESULT_ERROR; } FB_BOOLEAN ResultSet::isEof(CheckStatusWrapper* status) { try { reset(status); // Check and validate handles, etc. if (!stmt) { Arg::Gds(isc_dsql_cursor_err).raise(); } Rsr* statement = stmt->getStatement(); CHECK_HANDLE(statement, isc_bad_req_handle); if (!statement->rsr_flags.test(Rsr::FETCHED)) return FB_FALSE; return statement->rsr_flags.test(Rsr::PAST_EOF) ? FB_TRUE : FB_FALSE; } catch (const Exception& ex) { ex.stuffException(status); } return FB_FALSE; } FB_BOOLEAN ResultSet::isBof(CheckStatusWrapper* status) { try { reset(status); // Check and validate handles, etc. if (!stmt) { Arg::Gds(isc_dsql_cursor_err).raise(); } Rsr* statement = stmt->getStatement(); CHECK_HANDLE(statement, isc_bad_req_handle); if (!statement->rsr_flags.test(Rsr::FETCHED)) return FB_TRUE; return statement->rsr_flags.test(Rsr::PAST_BOF) ? FB_TRUE : FB_FALSE; } catch (const Exception& ex) { ex.stuffException(status); } return FB_FALSE; } IMessageMetadata* ResultSet::getMetadata(CheckStatusWrapper* status) { if (!outputFormat) { status->setErrors(Arg::Gds(isc_no_output_format).value()); return NULL; } reset(status); outputFormat->addRef(); return outputFormat; } void ResultSet::getInfo(CheckStatusWrapper* status, unsigned int itemsLength, const unsigned char* items, unsigned int bufferLength, unsigned char* buffer) { try { reset(status); // Check and validate handles, etc. if (!stmt) Arg::Gds(isc_dsql_cursor_err).raise(); const auto statement = stmt->getStatement(); CHECK_HANDLE(statement, isc_bad_req_handle); const auto rdb = statement->rsr_rdb; const auto port = rdb->rdb_port; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); if (port->port_protocol < PROTOCOL_FETCH_SCROLL) unsupported(); info(status, rdb, op_info_cursor, statement->rsr_id, 0, itemsLength, items, 0, 0, bufferLength, buffer); } catch (const Exception& ex) { ex.stuffException(status); } } void ResultSet::freeClientData(CheckStatusWrapper* status, bool force) { /************************************** * * d s q l _ f r e e _ s t a t e m e n t * ************************************** * * Functional description * Close SQL cursor * **************************************/ try { // Check and validate handles, etc. if (!stmt) { Arg::Gds(isc_dsql_cursor_err).raise(); } Rsr* statement = stmt->getStatement(); CHECK_HANDLE(statement, isc_bad_req_handle); Rdb* rdb = statement->rsr_rdb; rem_port* port = rdb->rdb_port; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); statement->clearException(); if (statement->rsr_flags.test(Rsr::LAZY)) { statement->rsr_flags.clear(Rsr::FETCHED); statement->rsr_rtr = NULL; clear_queue(rdb->rdb_port); REMOTE_reset_statement(statement); releaseStatement(); return; } PACKET* packet = &rdb->rdb_packet; packet->p_operation = op_free_statement; P_SQLFREE* free_stmt = &packet->p_sqlfree; free_stmt->p_sqlfree_statement = statement->rsr_id; free_stmt->p_sqlfree_option = DSQL_close; if (rdb->rdb_port->port_flags & PORT_lazy) { defer_packet(rdb->rdb_port, packet); packet->p_resp.p_resp_object = statement->rsr_id; statement->clearException(); } else { try { send_and_receive(status, rdb, packet); } catch (const Exception&) { if (!force) throw; } } statement->rsr_flags.clear(Rsr::FETCHED); statement->rsr_rtr = NULL; clear_queue(rdb->rdb_port); REMOTE_reset_statement(statement); releaseStatement(); } catch (const Exception& ex) { ex.stuffException(status); } } void ResultSet::internalClose(CheckStatusWrapper* status) { /************************************** * * d s q l _ f r e e _ s t a t e m e n t * ************************************** * * Functional description * Close SQL cursor * **************************************/ reset(status); freeClientData(status); } void ResultSet::close(CheckStatusWrapper* status) { internalClose(status); if (status->isEmpty()) release(); } void ResultSet::deprecatedClose(CheckStatusWrapper* status) { internalClose(status); } void ResultSet::releaseStatement() { if (tmpStatement) { stmt->release(); } stmt = NULL; } int Blob::getSegment(CheckStatusWrapper* status, unsigned int bufferLength, void* buffer, unsigned int* segmentLength) { /************************************** * * g d s _ g e t _ s e g m e n t * ************************************** * * Functional description * Buffer segments of a blob and pass * them one by one to the caller. * **************************************/ try { reset(status); UCHAR* bufferPtr = static_cast(buffer); // Sniff out handles, etc, and find the various blocks. CHECK_HANDLE(blob, isc_bad_segstr_handle); Rdb* rdb = blob->rbl_rdb; CHECK_HANDLE(rdb, isc_bad_db_handle); rem_port* port = rdb->rdb_port; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); // Build the primary packet to get the operation started. PACKET* packet = &rdb->rdb_packet; P_SGMT* segment = &packet->p_sgmt; P_RESP* response = &packet->p_resp; SaveString temp(response->p_resp_data, bufferLength, bufferPtr); // Handle a blob that has been created rather than opened (this should yield an error) if (blob->rbl_flags & Rbl::CREATE) { packet->p_operation = op_get_segment; segment->p_sgmt_length = bufferLength; segment->p_sgmt_blob = blob->rbl_id; segment->p_sgmt_segment.cstr_length = 0; send_packet(port, packet); receive_response(status, rdb, packet); if (segmentLength) *segmentLength = response->p_resp_data.cstr_length; return IStatus::RESULT_OK; } // New protocol -- ask for a 1K chunk of blob and // fill segment requests from it until its time to // get the next section. In other words, get a bunch, // pass it out piece by piece, then when there isn't // enough left, ask for more. unsigned int length = 0; // if we're already done, stop now if (blob->rbl_flags & Rbl::EOF_SET) { if (segmentLength) *segmentLength = length; return IStatus::RESULT_NO_DATA; } // Here's the loop, passing out data from our basket & refilling it. // Our buffer (described by the structure blob) is counted strings // ... int code = IStatus::RESULT_OK; while (true) { // If there's data to be given away, give some away (p points to the local data) if (blob->rbl_length) { UCHAR* p = blob->rbl_ptr; // If there was a fragment left over last time use it USHORT l = blob->rbl_fragment_length; if (l) { blob->rbl_fragment_length = 0; } else { // otherwise pick up the count word as the length, & decrement the local length l = *p++; l += *p++ << 8; blob->rbl_length -= 2; } // Now check that what we've got fits. // If not, set up the fragment pointer and set the status vector if (l > bufferLength) { blob->rbl_fragment_length = l - bufferLength; l = bufferLength; code = IStatus::RESULT_SEGMENT; } // and, just for yucks, see if we're exactly using up the fragment // part of a previous incomplete read - if so mark this as an // incomplete read if (l == bufferLength && l == blob->rbl_length && (blob->rbl_flags & Rbl::SEGMENT)) { code = IStatus::RESULT_SEGMENT; } // finally set up the return length, decrement the current length, // copy the data, and indicate where to start next time. length += l; blob->rbl_length -= l; blob->rbl_offset += l; bufferLength -= l; if (l) { memcpy(bufferPtr, p, l); } bufferPtr += l; p += l; blob->rbl_ptr = p; // return if we've filled up the caller's buffer, or completed a segment if (!bufferLength || blob->rbl_length || !(blob->rbl_flags & Rbl::SEGMENT)) { break; } } // We're done with buffer. If this was the last, we're done if (blob->rbl_flags & Rbl::EOF_PENDING) { blob->rbl_flags |= Rbl::EOF_SET; code = IStatus::RESULT_NO_DATA; break; } // Preparatory to asking for more data, use input buffer length // to cue more efficient blob buffering. // Allocate 2 extra bytes to handle the special case where the // segment size of blob in the database is equal to the buffer // size that the user has passed. // Do not go into this loop if we already have a buffer // of size 65535 or 65534. if (bufferLength > blob->rbl_buffer_length - sizeof(USHORT) && blob->rbl_buffer_length <= MAX_USHORT - sizeof(USHORT)) { ULONG new_size = bufferLength + sizeof(USHORT); if (new_size > MAX_USHORT) // Check if we've overflown new_size = bufferLength; blob->rbl_ptr = blob->rbl_buffer = blob->rbl_data.getBuffer(new_size); blob->rbl_buffer_length = (USHORT) new_size; } // We need more data. Ask for it politely packet->p_operation = op_get_segment; segment->p_sgmt_length = blob->rbl_buffer_length; segment->p_sgmt_blob = blob->rbl_id; segment->p_sgmt_segment.cstr_length = 0; send_packet(rdb->rdb_port, packet); response->p_resp_data.cstr_allocated = blob->rbl_buffer_length; response->p_resp_data.cstr_address = blob->rbl_buffer; receive_response(status, rdb, packet); blob->rbl_length = (USHORT) response->p_resp_data.cstr_length; blob->rbl_ptr = blob->rbl_buffer; blob->rbl_flags &= ~Rbl::SEGMENT; if (response->p_resp_object == 1) blob->rbl_flags |= Rbl::SEGMENT; else if (response->p_resp_object == 2) blob->rbl_flags |= Rbl::EOF_PENDING; } if (segmentLength) *segmentLength = length; return code; } catch (const Exception& ex) { ex.stuffException(status); } return IStatus::RESULT_ERROR; } int Attachment::getSlice(CheckStatusWrapper* status, ITransaction* apiTra, ISC_QUAD* array_id, unsigned int sdl_length, const unsigned char* sdl, unsigned int param_length, const unsigned char* param, int slice_length, unsigned char* slice) { /************************************** * * g d s _ g e t _ s l i c e * ************************************** * * Functional description * Snatch a slice of an array. * **************************************/ try { reset(status); CHECK_HANDLE(rdb, isc_bad_db_handle); rem_port* port = rdb->rdb_port; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); Rtr* transaction = remoteTransaction(apiTra); CHECK_HANDLE(transaction, isc_bad_trans_handle); // Validate data length CHECK_LENGTH(port, sdl_length); CHECK_LENGTH(port, param_length); // Parse the sdl in case blr_d_float must be converted to blr_double const UCHAR* new_sdl = sdl; // CVC: Modified this horrible idea: don't touch input parameters! // The modified (perhaps) sdl is send to the remote connection. The // original sdl is used to process the slice data when it is received. // (This is why both 'new_sdl' and 'sdl' are saved in the packet.) HalfStaticArray sdl_buffer; UCHAR* old_sdl = sdl_buffer.getBuffer(sdl_length); memcpy(old_sdl, sdl, sdl_length); PACKET* packet = &rdb->rdb_packet; packet->p_operation = op_get_slice; P_SLC* data = &packet->p_slc; data->p_slc_transaction = transaction->rtr_id; data->p_slc_id = *array_id; data->p_slc_length = slice_length; data->p_slc_sdl.cstr_length = sdl_length; data->p_slc_sdl.cstr_address = const_cast(new_sdl); data->p_slc_parameters.cstr_length = param_length; data->p_slc_parameters.cstr_address = const_cast(param); data->p_slc_slice.lstr_length = 0; data->p_slc_slice.lstr_address = slice; P_SLR* response = &packet->p_slr; response->p_slr_sdl = old_sdl; //const_cast(sdl); response->p_slr_sdl_length = sdl_length; response->p_slr_slice.lstr_address = slice; response->p_slr_slice.lstr_length = slice_length; send_packet(rdb->rdb_port, packet); receive_packet(rdb->rdb_port, packet); if (packet->p_operation != op_slice) { REMOTE_check_response(status, rdb, packet); } return response->p_slr_length; } catch (const Exception& ex) { ex.stuffException(status); } return 0; } IBlob* Attachment::openBlob(CheckStatusWrapper* status, ITransaction* apiTra, ISC_QUAD* id, unsigned int bpb_length, const unsigned char* bpb) { /************************************** * * g d s _ o p e n _ b l o b 2 * ************************************** * * Functional description * Open an existing blob. * **************************************/ try { reset(status); CHECK_HANDLE(rdb, isc_bad_db_handle); rem_port* port = rdb->rdb_port; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); Rtr* transaction = remoteTransaction(apiTra); CHECK_HANDLE(transaction, isc_bad_trans_handle); // Validate data length CHECK_LENGTH(port, bpb_length); PACKET* packet = &rdb->rdb_packet; packet->p_operation = op_open_blob2; P_BLOB* p_blob = &packet->p_blob; p_blob->p_blob_transaction = transaction->rtr_id; p_blob->p_blob_id = *id; p_blob->p_blob_bpb.cstr_length = bpb_length; fb_assert(!p_blob->p_blob_bpb.cstr_allocated || p_blob->p_blob_bpb.cstr_allocated < p_blob->p_blob_bpb.cstr_length); // CVC: Should we ensure here that cstr_allocated < bpb_length??? // Otherwise, xdr_cstring() calling alloc_string() to decode would // cause memory problems on the client side for SS, as the client // would try to write to the application's provided R/O buffer. p_blob->p_blob_bpb.cstr_address = bpb; send_and_receive(status, rdb, packet); // CVC: It's not evident to me why these two lines that I've copied // here as comments are only found in create_blob calls. // I think they should be enabled to avoid whatever buffer corruption. //p_blob->p_blob_bpb.cstr_length = 0; //p_blob->p_blob_bpb.cstr_address = NULL; Rbl* blob = FB_NEW Rbl; blob->rbl_rdb = rdb; blob->rbl_rtr = transaction; blob->rbl_id = packet->p_resp.p_resp_object; SET_OBJECT(rdb, blob, blob->rbl_id); blob->rbl_next = transaction->rtr_blobs; transaction->rtr_blobs = blob; Firebird::IBlob* b = FB_NEW Blob(blob); b->addRef(); return b; } catch (const Exception& ex) { ex.stuffException(status); } return NULL; } void Transaction::prepare(CheckStatusWrapper* status, unsigned int msg_length, const unsigned char* msg) { /************************************** * * g d s _ p r e p a r e * ************************************** * * Functional description * Prepare a transaction for commit. First phase of a two * phase commit. * **************************************/ try { reset(status); CHECK_HANDLE(transaction, isc_bad_trans_handle); Rdb* rdb = transaction->rtr_rdb; CHECK_HANDLE(rdb, isc_bad_db_handle); rem_port* port = rdb->rdb_port; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); // Validate data length CHECK_LENGTH(port, msg_length); PACKET* packet = &rdb->rdb_packet; packet->p_operation = op_prepare2; packet->p_prep.p_prep_transaction = transaction->rtr_id; packet->p_prep.p_prep_data.cstr_length = msg_length; packet->p_prep.p_prep_data.cstr_address = msg; send_packet(rdb->rdb_port, packet); receive_response(status, rdb, packet); } catch (const Exception& ex) { ex.stuffException(status); } } void Blob::putSegment(CheckStatusWrapper* status, unsigned int segment_length, const void* segment) { /************************************** * * g d s _ p u t _ s e g m e n t * ************************************** * * Functional description * Emit a blob segment. If the protocol allows, * the segment is buffered locally for a later * batch put. * **************************************/ try { reset(status); const UCHAR* segmentPtr = static_cast(segment); // Sniff out handles, etc, and find the various blocks. CHECK_HANDLE(blob, isc_bad_segstr_handle); Rdb* rdb = blob->rbl_rdb; CHECK_HANDLE(rdb, isc_bad_db_handle); rem_port* port = rdb->rdb_port; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); // Handle a blob that has been opened rather than created (this should yield an error) if (!(blob->rbl_flags & Rbl::CREATE)) { send_blob(status, blob, segment_length, segmentPtr); fb_assert(false); } // If the buffer can't hold the complete incoming segment, flush out the // buffer. If the incoming segment is too large to fit into the blob // buffer, just send it as a single segment. UCHAR* p = blob->rbl_ptr; const unsigned int l = blob->rbl_buffer_length - (p - blob->rbl_buffer); if (segment_length + 2 > l) { if (blob->rbl_ptr > blob->rbl_buffer) { send_blob(status, blob, 0, NULL); } if ((ULONG) segment_length + 2 > blob->rbl_buffer_length) { send_blob(status, blob, segment_length, segmentPtr); return; } p = blob->rbl_buffer; } // Move segment length and data into blob buffer *p++ = (UCHAR) segment_length; *p++ = segment_length >> 8; if (segment_length) { memcpy(p, segmentPtr, segment_length); } blob->rbl_ptr = p + segment_length; } catch (const Exception& ex) { ex.stuffException(status); } } void Attachment::putSlice(CheckStatusWrapper* status, ITransaction* apiTra, ISC_QUAD* id, unsigned int sdl_length, const unsigned char* sdl, unsigned int param_length, const unsigned char* param, int sliceLength, unsigned char* slice) { /************************************** * * g d s _ p u t _ s l i c e * ************************************** * * Functional description * Store a slice of an array. * **************************************/ try { reset(status); CHECK_HANDLE(rdb, isc_bad_db_handle); rem_port* port = rdb->rdb_port; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); Rtr* transaction = remoteTransaction(apiTra); CHECK_HANDLE(transaction, isc_bad_trans_handle); // Validate data length CHECK_LENGTH(port, sdl_length); CHECK_LENGTH(port, param_length); // Parse the sdl in case blr_d_float must be converted to blr_double const UCHAR* new_sdl = sdl; // CVC: Modified this horrible idea: don't touch input parameters! // The modified (perhaps) sdl is sent to the remote connection. The // original sdl is used to process the slice data before it is sent. // (This is why both 'new_sdl' and 'sdl' are saved in the packet.) HalfStaticArray sdl_buffer; UCHAR* old_sdl = sdl_buffer.getBuffer(sdl_length); memcpy(old_sdl, sdl, sdl_length); PACKET* packet = &rdb->rdb_packet; packet->p_operation = op_put_slice; P_SLC* data = &packet->p_slc; data->p_slc_transaction = transaction->rtr_id; data->p_slc_id = *id; data->p_slc_length = sliceLength; data->p_slc_sdl.cstr_length = sdl_length; data->p_slc_sdl.cstr_address = const_cast(new_sdl); data->p_slc_parameters.cstr_length = param_length; data->p_slc_parameters.cstr_address = const_cast(param); data->p_slc_slice.lstr_length = sliceLength; data->p_slc_slice.lstr_address = slice; P_SLR* response = &packet->p_slr; response->p_slr_sdl = old_sdl; //const_cast(sdl); response->p_slr_sdl_length = sdl_length; response->p_slr_slice.lstr_address = slice; response->p_slr_slice.lstr_length = sliceLength; send_and_receive(status, rdb, packet); *id = packet->p_resp.p_resp_blob_id; } catch (const Exception& ex) { ex.stuffException(status); } } Firebird::IEvents* Attachment::queEvents(CheckStatusWrapper* status, Firebird::IEventCallback* callback, unsigned int length, const unsigned char* events) { /************************************** * * g d s _ $ q u e _ e v e n t s * ************************************** * * Functional description * Queue a request for event notification. * **************************************/ try { reset(status); CHECK_HANDLE(rdb, isc_bad_db_handle); rem_port* port = rdb->rdb_port; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); // Validate data length CHECK_LENGTH(port, length); PACKET* packet = &rdb->rdb_packet; // If there isn't a auxiliary asynchronous port, make one now if (!port->port_async) { packet->p_operation = op_connect_request; P_REQ* request = &packet->p_req; request->p_req_object = rdb->rdb_id; request->p_req_type = P_REQ_async; send_packet(port, packet); receive_response(status, rdb, packet); port->connect(packet); rem_port* port_async = port->port_async; port_async->port_events_threadId = Thread::start(event_thread, port_async, THREAD_high, &port_async->port_events_thread); port_async->port_context = rdb; } // Add event block to port's list of active remote events Rvnt* rem_event = add_event(port); rem_event->rvnt_callback = callback; rem_event->rvnt_port = port->port_async; rem_event->rvnt_length = length; rem_event->rvnt_rdb = rdb; // Build the primary packet to get the operation started. packet = &rdb->rdb_packet; packet->p_operation = op_que_events; P_EVENT* event = &packet->p_event; event->p_event_database = rdb->rdb_id; event->p_event_items.cstr_length = length; event->p_event_items.cstr_address = events; event->p_event_ast = 0; event->p_event_arg = 0; event->p_event_rid = rem_event->rvnt_id; send_packet(port, packet); receive_response(status, rdb, packet); Firebird::IEvents* rc = FB_NEW Events(rem_event); rc->addRef(); return rc; } catch (const Exception& ex) { ex.stuffException(status); } return NULL; } void Request::receive(CheckStatusWrapper* status, int level, unsigned int msg_type, unsigned int msg_length, void* msg) { /************************************** * * g d s _ r e c e i v e * ************************************** * * Functional description * Give a client program a record. Ask the * Remote server to send it to us if necessary. * **************************************/ try { reset(status); // Check handles and environment, then set up error handling CHECK_HANDLE(rq, isc_bad_req_handle); Rrq* request = REMOTE_find_request(rq, level); Rdb* rdb = request->rrq_rdb; CHECK_HANDLE(rdb, isc_bad_db_handle); rem_port* port = rdb->rdb_port; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); Rrq::rrq_repeat* tail = &request->rrq_rpt[msg_type]; RMessage* message = tail->rrq_message; #ifdef DEBUG fprintf(stdout, "Rows Pending in REM_receive=%d\n", tail->rrq_rows_pending); #endif // Check to see if data is waiting. If not, solicit data. // Solicit data either when we've run out, or there's a low // inventory of messages in local buffers & no shipments on the // ether being sent to us. if (request->rrqStatus.isSuccess() && // No error pending ((!message->msg_address && tail->rrq_rows_pending == 0) || // No message waiting (tail->rrq_rows_pending <= tail->rrq_reorder_level && // Low in inventory tail->rrq_msgs_waiting <= tail->rrq_reorder_level && // Pipelining causes both server & client to // write at the same time. In XNET, writes // block for the other end to read - and so when both // attempt to write simultaenously, they end up // waiting indefinetly for the other end to read. (port->port_type != rem_port::XNET) && request->rrq_max_msg <= 1))) { // there's only one message type #ifdef DEBUG fprintf(stderr, "Rows Pending %d\n", tail->rrq_rows_pending); if (!message->msg_address) fprintf(stderr, "Out of data - reordering\n"); else fprintf(stderr, "Low on inventory - reordering\n"); #endif // Format a request for data PACKET *packet = &rdb->rdb_packet; packet->p_operation = op_receive; P_DATA* data = &packet->p_data; data->p_data_request = request->rrq_id; data->p_data_message_number = msg_type; data->p_data_incarnation = level; // Compute how many to send in a batch. While this calculation // is the same for each batch (June 1996), perhaps in the future it // could dynamically adjust batching sizes based on fetch patterns data->p_data_messages = REMOTE_compute_batch_size(port, 0, op_send, tail->rrq_format); tail->rrq_reorder_level = data->p_data_messages / 2; tail->rrq_rows_pending += data->p_data_messages; #ifdef DEBUG fprintf(stdout, "Recalculating Rows Pending in REM_receive=%d\n", tail->rrq_rows_pending); #endif #ifdef DEBUG fprintf(stderr, "port_flags %d max_msg %d\n", port->port_flags, request->rrq_max_msg); fprintf(stderr, "Fetch: Req One batch of %d messages\n", data->p_data_messages); #endif send_packet(port, packet); tail->rrq_batch_count++; #ifdef DEBUG fprintf(stderr, "Rows Pending %d\n", tail->rrq_rows_pending); #endif // Queue up receipt of the pending data enqueue_receive(port, batch_gds_receive, rdb, request, tail); } // Receive queued responses until we have some data for this cursor // or an error status has been received. // We've either got data, or some is on the way, or we have an error fb_assert(message->msg_address || tail->rrq_rows_pending > 0 || (!request->rrqStatus.isSuccess())); while (!message->msg_address && request->rrqStatus.isSuccess()) { receive_queued_packet(port, request->rrq_id); } if (!message->msg_address && !request->rrqStatus.isSuccess()) { // The previous batch of receives ended with an error status. // We're all done returning data in the local queue. // Return that error status vector to the user. // Stuff in the error result to the user's vector request->rrqStatus.raise(); } // Copy data from the message buffer to the client buffer if (tail->rrq_format->fmt_length != msg_length) { status_exception::raise(Arg::Gds(isc_port_len) << Arg::Num(msg_length) << Arg::Num(tail->rrq_format->fmt_length)); } message = tail->rrq_message; memcpy(msg, message->msg_address, msg_length); // Move the head-of-full-buffer-queue pointer forward tail->rrq_message = message->msg_next; // Mark the buffer the message came from as available for reuse message->msg_address = NULL; tail->rrq_msgs_waiting--; } catch (const Exception& ex) { ex.stuffException(status); } } Firebird::ITransaction* Attachment::reconnectTransaction(CheckStatusWrapper* status, unsigned int length, const unsigned char* id) { /************************************** * * g d s _ r e c o n n e c t * ************************************** * * Functional description * **************************************/ try { reset(status); CHECK_HANDLE(rdb, isc_bad_db_handle); rem_port* port = rdb->rdb_port; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); // Validate data length CHECK_LENGTH(port, length); PACKET* packet = &rdb->rdb_packet; packet->p_operation = op_reconnect; P_STTR* trans = &packet->p_sttr; trans->p_sttr_database = rdb->rdb_id; trans->p_sttr_tpb.cstr_length = length; trans->p_sttr_tpb.cstr_address = id; send_and_receive(status, rdb, packet); Firebird::ITransaction* t = FB_NEW Transaction(make_transaction(rdb, packet->p_resp.p_resp_object), this); t->addRef(); return t; } catch (const Exception& ex) { ex.stuffException(status); } return NULL; } void Request::freeClientData(CheckStatusWrapper* status, bool force) { /************************************** * * g d s _ r e l e a s e _ r e q u e s t * ************************************** * * Functional description * Release a request. * **************************************/ try { CHECK_HANDLE(rq, isc_bad_req_handle); Rdb* rdb = rq->rrq_rdb; CHECK_HANDLE(rdb, isc_bad_db_handle); rem_port* port = rdb->rdb_port; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); try { release_object(status, rdb, op_release, rq->rrq_id); } catch (const Exception&) { if (!force) throw; } release_request(rq); rq = NULL; } catch (const Exception& ex) { ex.stuffException(status); } } void Request::internalFree(CheckStatusWrapper* status) { /************************************** * * g d s _ r e l e a s e _ r e q u e s t * ************************************** * * Functional description * Release a request. * **************************************/ reset(status); freeClientData(status); } void Request::free(CheckStatusWrapper* status) { internalFree(status); if (status->isEmpty()) release(); } void Request::deprecatedFree(CheckStatusWrapper* status) { internalFree(status); } void Request::getInfo(CheckStatusWrapper* status, int level, unsigned int itemsLength, const unsigned char* items, unsigned int bufferLength, unsigned char* buffer) { /************************************** * * g d s _ r e q u e s t _ i n f o * ************************************** * * Functional description * Provide information on request object. * **************************************/ try { reset(status); CHECK_HANDLE(rq, isc_bad_req_handle); Rrq* request = REMOTE_find_request(rq, level); CHECK_HANDLE(request, isc_bad_req_handle); Rdb* rdb = request->rrq_rdb; CHECK_HANDLE(rdb, isc_bad_db_handle); rem_port* port = rdb->rdb_port; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); // Check for buffered message. If there is, report on it locally. const Rrq::rrq_repeat* tail= request->rrq_rpt.begin(); for (const Rrq::rrq_repeat* const end = tail + request->rrq_max_msg; tail <= end; tail++) { RMessage* msg = tail->rrq_message; if (!msg || !msg->msg_address) { continue; } // We've got a pending message, respond locally const rem_fmt* format = tail->rrq_format; UCHAR* out = buffer; const UCHAR* infoItems = items; const UCHAR* const endItems = infoItems + itemsLength; while (infoItems < endItems) { USHORT data = 0; const UCHAR item = *infoItems++; switch (item) { case isc_info_end: break; case isc_info_state: data = isc_info_req_send; break; case isc_info_message_number: data = msg->msg_number; break; case isc_info_message_size: data = format->fmt_length; break; default: goto punt; } *out++ = item; if (item == isc_info_end) break; *out++ = 2; *out++ = 2 >> 8; *out++ = (UCHAR) data; *out++ = data >> 8; } } // No message pending, request status from other end punt: info(status, rdb, op_info_request, request->rrq_id, level, itemsLength, items, 0, 0, bufferLength, buffer); } catch (const Exception& ex) { ex.stuffException(status); } } void Transaction::rollbackRetaining(CheckStatusWrapper* status) { /************************************** * * i s c _ r o l l b a c k _ r e t a i n i n g * ************************************** * * Functional description * Abort a transaction but keep its environment valid * **************************************/ try { reset(status); CHECK_HANDLE(transaction, isc_bad_trans_handle); Rdb* rdb = transaction->rtr_rdb; CHECK_HANDLE(rdb, isc_bad_db_handle); rem_port* port = rdb->rdb_port; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); release_object(status, rdb, op_rollback_retaining, transaction->rtr_id); } catch (const Exception& ex) { ex.stuffException(status); } } void Transaction::freeClientData(CheckStatusWrapper* status, bool force) { /************************************** * * g d s _ r o l l b a c k * ************************************** * * Functional description * Abort a transaction. * **************************************/ try { CHECK_HANDLE(transaction, isc_bad_trans_handle); Rdb* rdb = transaction->rtr_rdb; CHECK_HANDLE(rdb, isc_bad_db_handle); rem_port* port = rdb->rdb_port; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); try { release_object(status, rdb, op_rollback, transaction->rtr_id); } catch (const Exception&) { if (!force) throw; } REMOTE_cleanup_transaction(transaction); release_transaction(transaction); transaction = NULL; } catch (const Exception& ex) { ex.stuffException(status); } } void Transaction::internalRollback(CheckStatusWrapper* status) { /************************************** * * g d s _ r o l l b a c k * ************************************** * * Functional description * Abort a transaction. * **************************************/ reset(status); freeClientData(status); } void Transaction::rollback(CheckStatusWrapper* status) { internalRollback(status); if (status->isEmpty()) release(); } void Transaction::deprecatedRollback(CheckStatusWrapper* status) { internalRollback(status); } void Transaction::internalDisconnect(CheckStatusWrapper* status) { try { reset(status); CHECK_HANDLE(transaction, isc_bad_trans_handle); Rdb* rdb = transaction->rtr_rdb; CHECK_HANDLE(rdb, isc_bad_db_handle); // ASF: Looks wrong that this method is ignored in the engine and remote providers. } catch (const Exception& ex) { ex.stuffException(status); } } void Transaction::disconnect(CheckStatusWrapper* status) { internalDisconnect(status); if (status->isEmpty()) release(); } void Transaction::deprecatedDisconnect(CheckStatusWrapper* status) { internalDisconnect(status); } int Blob::seek(CheckStatusWrapper* status, int mode, int offset) { /************************************** * * g d s _ s e e k _ b l o b * ************************************** * * Functional description * Seek into a blob. * **************************************/ try { reset(status); CHECK_HANDLE(blob, isc_bad_segstr_handle); Rdb* rdb = blob->rbl_rdb; CHECK_HANDLE(rdb, isc_bad_db_handle); rem_port* port = rdb->rdb_port; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); PACKET* packet = &rdb->rdb_packet; packet->p_operation = op_seek_blob; P_SEEK* seek = &packet->p_seek; seek->p_seek_blob = blob->rbl_id; seek->p_seek_mode = mode; seek->p_seek_offset = offset; if (mode == 1) { seek->p_seek_mode = 0; seek->p_seek_offset = blob->rbl_offset + offset; } send_and_receive(status, rdb, packet); blob->rbl_offset = packet->p_resp.p_resp_blob_id.gds_quad_low; blob->rbl_length = 0; blob->rbl_fragment_length = 0; blob->rbl_flags &= ~(Rbl::EOF_SET | Rbl::EOF_PENDING | Rbl::SEGMENT); return blob->rbl_offset; } catch (const Exception& ex) { ex.stuffException(status); } return 0; } void Request::send(CheckStatusWrapper* status, int level, unsigned int msg_type, unsigned int /*length*/, const void* msg) { /************************************** * * g d s _ s e n d * ************************************** * * Functional description * Send a message to the server. * **************************************/ try { reset(status); CHECK_HANDLE(rq, isc_bad_req_handle); Rrq* request = REMOTE_find_request(rq, level); Rdb* rdb = request->rrq_rdb; CHECK_HANDLE(rdb, isc_bad_db_handle); rem_port* port = rdb->rdb_port; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); if (msg_type > request->rrq_max_msg) { handle_error(isc_badmsgnum); } RMessage* message = request->rrq_rpt[msg_type].rrq_message; // We are lying here, but the interface shows for years this param as const message->msg_address = const_cast(static_cast(msg)); PACKET* packet = &rdb->rdb_packet; packet->p_operation = op_send; P_DATA* data = &packet->p_data; data->p_data_request = request->rrq_id; data->p_data_message_number = msg_type; data->p_data_incarnation = level; send_packet(port, packet); // Bump up the message pointer to resync with rrq_xdr (rrq_xdr // was incremented by xdr_request in the SEND call). message->msg_address = NULL; request->rrq_rpt[msg_type].rrq_message = message->msg_next; receive_response(status, rdb, packet); } catch (const Exception& ex) { ex.stuffException(status); } } Firebird::IService* RProvider::attachSvc(CheckStatusWrapper* status, const char* service, unsigned int spbLength, const unsigned char* spb, bool loopback) { /************************************** * * g d s _ s e r v i c e _ a t t a c h * ************************************** * * Functional description * Connect to a Firebird service. * **************************************/ try { reset(status); PathName node_name, expanded_name(service); ClumpletWriter newSpb(ClumpletReader::spbList, MAX_DPB_SIZE, spb, spbLength); const bool user_verification = get_new_dpb(newSpb, spbParam, loopback); ClntAuthBlock cBlock(NULL, &newSpb, &spbParam); unsigned flags = 0; if (user_verification) flags |= ANALYZE_USER_VFY; if (loopback) flags |= ANALYZE_LOOPBACK; flags |= ANALYZE_EMP_NAME; PathName refDbName; if (newSpb.find(isc_spb_expected_db)) newSpb.getPath(refDbName); rem_port* port = analyze(cBlock, expanded_name, flags, newSpb, spbParam, node_name, &refDbName, cryptCallback); RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); Rdb* rdb = port->port_context; // The client may have set a parameter for dummy_packet_interval. Add that to the // the SPB so the server can pay attention to it. Note: allocation code must // ensure sufficient space has been added. add_other_params(port, newSpb, spbParam); IntlSpb intl; if (!init(status, cBlock, port, op_service_attach, expanded_name, newSpb, intl, cryptCallback)) return NULL; Firebird::IService* s = FB_NEW Service(rdb); s->addRef(); return s; } catch (const Exception& ex) { ex.stuffException(status); } return NULL; } Firebird::IService* RProvider::attachServiceManager(CheckStatusWrapper* status, const char* service, unsigned int spbLength, const unsigned char* spb) { /************************************** * * g d s _ s e r v i c e _ a t t a c h * ************************************** * * Functional description * Connect to a Firebird service. * **************************************/ return attachSvc(status, service, spbLength, spb, false); } Firebird::IService* Loopback::attachServiceManager(CheckStatusWrapper* status, const char* service, unsigned int spbLength, const unsigned char* spb) { /************************************** * * g d s _ s e r v i c e _ a t t a c h * ************************************** * * Functional description * Connect to a Firebird service. * **************************************/ return attachSvc(status, service, spbLength, spb, true); } void Service::freeClientData(CheckStatusWrapper* status, bool force) { /************************************** * * g d s _ s e r v i c e _ d e t a c h * ************************************** * * Functional description * Close down a connection to a Firebird service. * **************************************/ try { reset(status); // Check and validate handles, etc. CHECK_HANDLE(rdb, isc_bad_svc_handle); rem_port* port = rdb->rdb_port; RemotePortGuard portGuard(port, FB_FUNCTION); if (!(port->port_flags & PORT_detached)) { try { release_object(status, rdb, op_service_detach, rdb->rdb_id); } catch (const Exception&) { if (!force) throw; } } disconnect(port); rdb = NULL; } catch (const Exception& ex) { ex.stuffException(status); } } void Service::internalDetach(CheckStatusWrapper* status) { /************************************** * * g d s _ s e r v i c e _ d e t a c h * ************************************** * * Functional description * Close down a connection to a Firebird service. * **************************************/ reset(status); freeClientData(status); } void Service::detach(CheckStatusWrapper* status) { internalDetach(status); if (status->isEmpty()) release(); } void Service::deprecatedDetach(CheckStatusWrapper* status) { internalDetach(status); } void Service::query(CheckStatusWrapper* status, unsigned int sendLength, const unsigned char* sendItems, unsigned int receiveLength, const unsigned char* receiveItems, unsigned int bufferLength, unsigned char* buffer) { /************************************** * * g d s _ s e r v i c e _ q u e r y * ************************************** * * Functional description * Provide information on service object. * **************************************/ try { reset(status); // Check and validate handles, etc. CHECK_HANDLE(rdb, isc_bad_svc_handle); rem_port* port = rdb->rdb_port; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); info(status, rdb, op_service_info, rdb->rdb_id, 0, sendLength, sendItems, receiveLength, receiveItems, bufferLength, buffer); } catch (const Exception& ex) { ex.stuffException(status); } } void Service::cancel(CheckStatusWrapper* status) { try { reset(status); // Check and validate handles, etc. CHECK_HANDLE(rdb, isc_bad_svc_handle); /* rem_port* port = rdb->rdb_port; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); */ Arg::Gds(isc_wish_list).raise(); } catch (const Exception& ex) { ex.stuffException(status); } } void Service::start(CheckStatusWrapper* status, unsigned int spbLength, const unsigned char* spb) { /************************************** * * g d s _ s e r v i c e _ s t a r t * ************************************** * * Functional description * Start a Firebird service * **************************************/ try { reset(status); // Check and validate handles, etc. CHECK_HANDLE(rdb, isc_bad_svc_handle); rem_port* port = rdb->rdb_port; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); svcstart(status, rdb, op_service_start, rdb->rdb_id, 0, spbLength, spb); } catch (const Exception& ex) { ex.stuffException(status); } } void Request::startAndSend(CheckStatusWrapper* status, Firebird::ITransaction* apiTra, int level, unsigned int msg_type, unsigned int /*length*/, const void* msg) { /************************************** * * g d s _ s t a r t _ a n d _ s e n d * ************************************** * * Functional description * Get a record from the host program. * **************************************/ try { reset(status); CHECK_HANDLE(rq, isc_bad_req_handle); Rrq* request = REMOTE_find_request(rq, level); Rtr* transaction = remAtt->remoteTransaction(apiTra); CHECK_HANDLE(transaction, isc_bad_trans_handle); Rdb* rdb = request->rrq_rdb; CHECK_HANDLE(rdb, isc_bad_db_handle); rem_port* port = rdb->rdb_port; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); if (msg_type > request->rrq_max_msg) { handle_error(isc_badmsgnum); } if (transaction->rtr_rdb != rdb) { Arg::Gds(isc_trareqmis).raise(); } clear_queue(rdb->rdb_port); REMOTE_reset_request(request, 0); RMessage* message = request->rrq_rpt[msg_type].rrq_message; message->msg_address = const_cast(static_cast(msg)); PACKET* packet = &rdb->rdb_packet; packet->p_operation = op_start_send_and_receive; P_DATA* data = &packet->p_data; data->p_data_request = request->rrq_id; data->p_data_transaction = transaction->rtr_id; data->p_data_message_number = msg_type; data->p_data_incarnation = level; send_packet(port, packet); // Bump up the message pointer to resync with rrq_xdr (rrq_xdr // was incremented by xdr_request in the SEND call). message->msg_address = NULL; request->rrq_rpt[msg_type].rrq_message = message->msg_next; receive_response(status, rdb, packet); // Save the request's transaction. request->rrq_rtr = transaction; if (packet->p_operation == op_response_piggyback) { receive_after_start(request, packet->p_resp.p_resp_object); } } catch (const Exception& ex) { ex.stuffException(status); } } void Request::start(CheckStatusWrapper* status, Firebird::ITransaction* apiTra, int level) { /************************************** * * g d s _ s t a r t * ************************************** * * Functional description * Get a record from the host program. * **************************************/ try { reset(status); CHECK_HANDLE(rq, isc_bad_req_handle); Rrq* request = REMOTE_find_request(rq, level); Rtr* transaction = remAtt->remoteTransaction(apiTra); CHECK_HANDLE(transaction, isc_bad_trans_handle); Rdb* rdb = request->rrq_rdb; CHECK_HANDLE(rdb, isc_bad_db_handle); rem_port* port = rdb->rdb_port; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); if (transaction->rtr_rdb != rdb) { Arg::Gds(isc_trareqmis).raise(); } clear_queue(rdb->rdb_port); REMOTE_reset_request(request, 0); PACKET* packet = &rdb->rdb_packet; packet->p_operation = op_start_and_receive; P_DATA* data = &packet->p_data; data->p_data_request = request->rrq_id; data->p_data_transaction = transaction->rtr_id; data->p_data_message_number = 0; data->p_data_incarnation = level; send_and_receive(status, rdb, packet); // Save the request's transaction. request->rrq_rtr = transaction; if (packet->p_operation == op_response_piggyback) { receive_after_start(request, packet->p_resp.p_resp_object); } } catch (const Exception& ex) { ex.stuffException(status); } } Firebird::ITransaction* Attachment::startTransaction(CheckStatusWrapper* status, unsigned int tpbLength, const unsigned char* tpb) { /************************************** * * g d s _ t r a n s a c t i o n * ************************************** * * Functional description * Start a transaction. * **************************************/ try { reset(status); CHECK_HANDLE(rdb, isc_bad_db_handle); rem_port* port = rdb->rdb_port; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); if (/***tpbLength < 0 ||***/ (tpbLength > 0 && !tpb)) { status_exception::raise(Arg::Gds(isc_bad_tpb_form)); } // Validate data length CHECK_LENGTH(port, tpbLength); PACKET* packet = &rdb->rdb_packet; packet->p_operation = op_transaction; P_STTR* trans = &packet->p_sttr; trans->p_sttr_database = rdb->rdb_id; trans->p_sttr_tpb.cstr_length = tpbLength; trans->p_sttr_tpb.cstr_address = tpb; send_and_receive(status, rdb, packet); Firebird::ITransaction* t = FB_NEW Transaction(make_transaction(rdb, packet->p_resp.p_resp_object), this); t->addRef(); return t; } catch (const Exception& ex) { ex.stuffException(status); } return NULL; } void Attachment::transactRequest(CheckStatusWrapper* status, ITransaction* apiTra, unsigned int blr_length, const unsigned char* blr, unsigned int in_msg_length, const unsigned char* in_msg, unsigned int out_msg_length, unsigned char* out_msg) { /************************************** * * i s c _ t r a n s a c t _ r e q u e s t * ************************************** * * Functional description * Execute a procedure on remote host. * **************************************/ try { reset(status); CHECK_HANDLE(rdb, isc_bad_db_handle); rem_port* port = rdb->rdb_port; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); Rtr* transaction = remoteTransaction(apiTra); CHECK_HANDLE(transaction, isc_bad_trans_handle); // Validate data length CHECK_LENGTH(port, blr_length); CHECK_LENGTH(port, in_msg_length); CHECK_LENGTH(port, out_msg_length); Rpr* procedure = port->port_rpr; if (!procedure) { procedure = port->port_rpr = FB_NEW Rpr; } // Parse the blr describing the messages delete procedure->rpr_in_msg; procedure->rpr_in_msg = NULL; delete procedure->rpr_in_format; procedure->rpr_in_format = NULL; delete procedure->rpr_out_msg; procedure->rpr_out_msg = NULL; delete procedure->rpr_out_format; procedure->rpr_out_format = NULL; RMessage* message = PARSE_messages(blr, blr_length); while (message) { switch (message->msg_number) { case 0: procedure->rpr_in_msg = message; procedure->rpr_in_format = (rem_fmt*) message->msg_address; message->msg_address = const_cast(in_msg); message = message->msg_next; procedure->rpr_in_msg->msg_next = NULL; break; case 1: procedure->rpr_out_msg = message; procedure->rpr_out_format = (rem_fmt*) message->msg_address; message->msg_address = out_msg; message = message->msg_next; procedure->rpr_out_msg->msg_next = NULL; break; default: RMessage* temp = message; message = message->msg_next; delete temp; break; } } PACKET* packet = &rdb->rdb_packet; packet->p_operation = op_transact; P_TRRQ* trrq = &packet->p_trrq; trrq->p_trrq_database = rdb->rdb_id; trrq->p_trrq_transaction = transaction->rtr_id; trrq->p_trrq_blr.cstr_length = blr_length; trrq->p_trrq_blr.cstr_address = const_cast(blr); trrq->p_trrq_messages = in_msg_length ? 1 : 0; send_packet(port, packet); // Two types of responses are possible, op_transact_response or // op_response. When there is an error op_response packet is returned // and it modifies the status vector to indicate the error which occurred. // But when success occurs a packet with op_transact_response comes back // which does not change the status vector. receive_packet(port, packet); if (packet->p_operation != op_transact_response) { REMOTE_check_response(status, rdb, packet); } } catch (const Exception& ex) { ex.stuffException(status); } } void Transaction::getInfo(CheckStatusWrapper* status, unsigned int itemsLength, const unsigned char* items, unsigned int bufferLength, unsigned char* buffer) { /************************************** * * g d s _ t r a n s a c t i o n _ i n f o * ************************************** * * Functional description * **************************************/ Array newItemsBuffer; try { reset(status); CHECK_HANDLE(transaction, isc_bad_trans_handle); Rdb* rdb = transaction->rtr_rdb; CHECK_HANDLE(rdb, isc_bad_db_handle); rem_port* port = rdb->rdb_port; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); fb_utils::getDbPathInfo(itemsLength, items, bufferLength, buffer, newItemsBuffer, remAtt->getDbPath()); info(status, rdb, op_info_transaction, transaction->rtr_id, 0, itemsLength, items, 0, 0, bufferLength, buffer); } catch (const Exception& ex) { ex.stuffException(status); } } void Request::unwind(CheckStatusWrapper* status, int level) { /************************************** * * g d s _ u n w i n d * ************************************** * * Functional description * Unwind a running request. * **************************************/ try { reset(status); Rrq* request = REMOTE_find_request(rq, level); CHECK_HANDLE(request, isc_bad_req_handle); Rdb* rdb = request->rrq_rdb; CHECK_HANDLE(rdb, isc_bad_db_handle); rem_port* port = rdb->rdb_port; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); } catch (const Exception& ex) { ex.stuffException(status); } } void Attachment::ping(CheckStatusWrapper* status) { /************************************** * * p i n g * ************************************** * * Functional description * Check the attachment handle for persistent errors. * **************************************/ try { reset(status); CHECK_HANDLE(rdb, isc_bad_db_handle); rem_port* port = rdb->rdb_port; RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); // Make sure protocol support action if (rdb->rdb_port->port_protocol < PROTOCOL_VERSION13) unsupported(); PACKET* packet = &rdb->rdb_packet; packet->p_operation = op_ping; send_and_receive(status, rdb, packet); } catch (const Exception& ex) { ex.stuffException(status); } } static Rvnt* add_event( rem_port* port) { /************************************* * * a d d _ e v e n t * ************************************** * * Functional description * Add remote event block to active chain. * **************************************/ Rdb* rdb = port->port_context; // Find unused event block or, if necessary, a new one Rvnt* event; for (event = rdb->rdb_events; event; event = event->rvnt_next) { if (!event->rvnt_id) break; } if (!event) { event = FB_NEW Rvnt; event->rvnt_next = rdb->rdb_events; rdb->rdb_events = event; } event->rvnt_id = ++remote_event_id; return event; } static void add_other_params(rem_port* port, ClumpletWriter& dpb, const ParametersSet& par) { /************************************** * * a d d _ o t h e r _ p a r a m s * ************************************** * * Functional description * Add parameters to a dpb to describe client-side * settings that the server should know about. * **************************************/ if (port->port_flags & PORT_dummy_pckt_set) { dpb.deleteWithTag(par.dummy_packet_interval); dpb.insertInt(par.dummy_packet_interval, port->port_dummy_packet_interval); } // Older version of engine not understand new tags and may process whole // DPB incorrectly. Check for protocol version is an poor attempt to make // guess about remote engine's version if (port->port_protocol >= PROTOCOL_VERSION11) { dpb.deleteWithTag(par.process_id); dpb.insertInt(par.process_id, getpid()); if (!dpb.find(par.process_name)) { PathName path(fb_utils::get_process_name()); ISC_systemToUtf8(path); ISC_escape(path); if (!dpb.find(isc_dpb_utf8_filename)) ISC_utf8ToSystem(path); dpb.insertString(par.process_name, path); } } if (port->port_protocol >= PROTOCOL_VERSION13) { dpb.deleteWithTag(par.client_version); dpb.insertString(par.client_version, FB_VERSION); } } static void add_working_directory(ClumpletWriter& dpb, const PathName& node_name) { /************************************************ * * a d d _ w o r k i n g _ d i r e c t o r y * ************************************************ * * Functional description * Add parameters to a dpb or spb to describe client-side * settings that the server should know about. * ************************************************/ if (dpb.find(isc_dpb_working_directory)) { return; } PathName cwd; // for WNet local node_name should be compared with "\\\\." ? if (node_name == "localhost") { fb_utils::getCwd(cwd); ISC_systemToUtf8(cwd); ISC_escape(cwd); if (!dpb.find(isc_dpb_utf8_filename)) ISC_utf8ToSystem(cwd); } dpb.insertString(isc_dpb_working_directory, cwd); } static void authenticateStep0(ClntAuthBlock& cBlock) { LocalStatus ls; CheckStatusWrapper s(&ls); for (; cBlock.plugins.hasData(); cBlock.plugins.next()) { HANDSHAKE_DEBUG(fprintf(stderr, "Cli: authenticateStep0(%s)\n", cBlock.plugins.name())); switch(cBlock.plugins.plugin()->authenticate(&s, &cBlock)) { case IAuth::AUTH_SUCCESS: case IAuth::AUTH_MORE_DATA: return; case IAuth::AUTH_FAILED: if (s.getState() & Firebird::IStatus::STATE_ERRORS) { iscLogStatus("Authentication, client plugin:", &s); } (Arg::Gds(isc_login_error) #ifdef DEV_BUILD << Arg::StatusVector(&s) #endif ).raise(); break; // compiler silencer } } } static void secureAuthentication(ClntAuthBlock& cBlock, rem_port* port) { HANDSHAKE_DEBUG(fprintf(stderr, "Cli: secureAuthentication\n")); if (!port) return; Rdb* rdb = port->port_context; fb_assert(rdb); PACKET* packet = &rdb->rdb_packet; HANDSHAKE_DEBUG(fprintf(stderr, "Cli: secureAuthentication: port OK, op=%d\n", packet->p_operation)); if (packet->p_operation == op_cond_accept) { LocalStatus ls; CheckStatusWrapper st(&ls); authReceiveResponse(true, cBlock, port, rdb, &st, packet, true); if (st.getState() & Firebird::IStatus::STATE_ERRORS) status_exception::raise(&st); } else { // try to start crypt cBlock.tryNewKeys(port); } } static rem_port* analyze(ClntAuthBlock& cBlock, PathName& attach_name, unsigned flags, ClumpletWriter& pb, const ParametersSet& parSet, PathName& node_name, PathName* ref_db_name, Firebird::ICryptKeyCallback* cryptCb) { /************************************** * * a n a l y z e * ************************************** * * Functional description * Analyze an attach specification and determine whether * a remote server is required, and if so, what protocol * to use. If the target can be accessed via the * remote subsystem, return address of a port block * with which to communicate with the server. * Otherwise, return NULL. * * NOTE: The file name must have been expanded prior to this call. * **************************************/ rem_port* port = NULL; int inet_af = AF_UNSPEC; cBlock.loadClnt(pb, &parSet); pb.deleteWithTag(parSet.auth_block); authenticateStep0(cBlock); bool needFile = !(flags & ANALYZE_EMP_NAME); #ifdef WIN_NT if (ISC_analyze_protocol(PROTOCOL_XNET, attach_name, node_name, NULL, needFile)) port = XNET_analyze(&cBlock, attach_name, flags & ANALYZE_USER_VFY, cBlock.getConfig(), ref_db_name); else #endif if (ISC_analyze_protocol(PROTOCOL_INET4, attach_name, node_name, INET_SEPARATOR, needFile)) inet_af = AF_INET; else if (ISC_analyze_protocol(PROTOCOL_INET6, attach_name, node_name, INET_SEPARATOR, needFile)) inet_af = AF_INET6; if (inet_af != AF_UNSPEC || ISC_analyze_protocol(PROTOCOL_INET, attach_name, node_name, INET_SEPARATOR, needFile) || ISC_analyze_tcp(attach_name, node_name, needFile)) { if (node_name.isEmpty()) node_name = INET_LOCALHOST; else { ISC_unescape(node_name); ISC_utf8ToSystem(node_name); } port = INET_analyze(&cBlock, attach_name, node_name.c_str(), flags & ANALYZE_USER_VFY, pb, cBlock.getConfig(), ref_db_name, cryptCb, inet_af); } // We have a local connection string. If it's a file on a network share, // try to connect to the corresponding host remotely. if (flags & ANALYZE_MOUNTS) { #ifdef WIN_NT if (!port) { PathName expanded_name = attach_name; if (ISC_analyze_pclan(expanded_name, node_name)) { ISC_unescape(node_name); ISC_utf8ToSystem(node_name); port = INET_analyze(&cBlock, expanded_name, node_name.c_str(), flags & ANALYZE_USER_VFY, pb, cBlock.getConfig(), ref_db_name, cryptCb); } } #endif #ifndef NO_NFS if (!port) { PathName expanded_name = attach_name; if (ISC_analyze_nfs(expanded_name, node_name)) { ISC_unescape(node_name); ISC_utf8ToSystem(node_name); port = INET_analyze(&cBlock, expanded_name, node_name.c_str(), flags & ANALYZE_USER_VFY, pb, cBlock.getConfig(), ref_db_name, cryptCb); } } #endif } if ((flags & ANALYZE_LOOPBACK) && !port) { // We have a local connection string. // If we are in loopback mode attempt connect to a localhost. if (node_name.isEmpty()) { #ifdef WIN_NT if (!port) { port = XNET_analyze(&cBlock, attach_name, flags & ANALYZE_USER_VFY, cBlock.getConfig(), ref_db_name); } #endif if (!port) { port = INET_analyze(&cBlock, attach_name, INET_LOCALHOST, flags & ANALYZE_USER_VFY, pb, cBlock.getConfig(), ref_db_name, cryptCb); } } } if (!port) Arg::Gds(isc_unavailable).raise(); try { secureAuthentication(cBlock, port); } catch (const Exception&) { disconnect(port, false); throw; } outPorts->registerPort(port); return port; } static void clear_stmt_que(rem_port* port, Rsr* statement) { /************************************** * * c l e a r _ s t m t _ q u e * ************************************** * * Functional description * * Receive and handle all queued packets for a completely fetched statement. * There must be no more than one such packet. * **************************************/ fb_assert(statement->rsr_batch_count <= 1); while (statement->rsr_batch_count) receive_queued_packet(port, statement->rsr_id); // hvlad: clear isc_req_sync error as it is received because of our batch // fetching code, not because of wrong client application. // dimitr: modern engine versions do not pass isc_req_sync to the client, // but it's possible if we're connected to the older one. if (statement->haveException() == isc_req_sync) statement->clearException(); } static void batch_dsql_fetch(rem_port* port, rmtque* que_inst, USHORT id) { /************************************** * * b a t c h _ d s q l _ f e t c h * ************************************** * * Functional description * Receive a batch of messages that were queued * on the wire. * * This function will be invoked whenever we need to wait * for something to come over on the wire, and there are * items in the queue for receipt. * * Note on error handing: Actual networking errors * need to be reported to status - which is bubbled * upwards to the API call which initiated this receive. * A status vector being returned as part of the cursor * fetch needs to be stored away for later return to the * client in the proper place in the stream. * **************************************/ fb_assert(port); fb_assert(que_inst); fb_assert(que_inst->rmtque_function == batch_dsql_fetch); Rdb* rdb = que_inst->rmtque_rdb; Rsr* statement = static_cast(que_inst->rmtque_parm); PACKET* packet = &rdb->rdb_packet; fb_assert(port == rdb->rdb_port); // Setup the packet structures so it knows what statement we // are trying to receive at this point in time packet->p_sqldata.p_sqldata_statement = statement->rsr_id; // We'll either receive the whole batch, until end-of-batch is seen, // or we'll just fetch one. We'll fetch one when we've run out of // local data to return to the client, so we grab one "hot off the wire" // to handoff to them. We'll grab the whole batch when we need to // receive a response for a DIFFERENT network request on the wire, // so we have to clear the wire before the response can be received // In addition to the above we grab all the records in case of XNET as // we need to clear the queue. const bool clear_queue = (id != statement->rsr_id || port->port_type == rem_port::XNET); statement->rsr_flags.set(Rsr::FETCHED); while (true) { LocalStatus ls; CheckStatusWrapper status(&ls); // Swallow up data. If a buffer isn't available, allocate another. RMessage* message = statement->rsr_buffer; if (message->msg_address) { RMessage* new_msg = FB_NEW RMessage(statement->rsr_fmt_length); statement->rsr_buffer = new_msg; new_msg->msg_next = message; while (message->msg_next != new_msg->msg_next) message = message->msg_next; message->msg_next = new_msg; } try { receive_packet_noqueue(port, packet); } catch (const Exception&) { // Must be a network error statement->rsr_rows_pending = 0; --statement->rsr_batch_count; dequeue_receive(port); throw; } if (packet->p_operation != op_fetch_response) { statement->rsr_flags.set(Rsr::STREAM_ERR); try { REMOTE_check_response(&status, rdb, packet); statement->saveException(&status, false); } catch (const Exception& ex) { // Queue errors within the batched request statement->saveException(ex, false); } statement->rsr_rows_pending = 0; --statement->rsr_batch_count; dequeue_receive(port); break; } // See if we're at end of the batch if (packet->p_sqldata.p_sqldata_status || !packet->p_sqldata.p_sqldata_messages) { if (packet->p_sqldata.p_sqldata_status == 100) { const auto operation = statement->rsr_fetch_operation; const auto position = statement->rsr_fetch_position; const bool forward = (operation == fetch_next || operation == fetch_last || ((operation == fetch_absolute || operation == fetch_relative) && position > 0)); if (forward) statement->rsr_flags.set(Rsr::EOF_SET); else statement->rsr_flags.set(Rsr::BOF_SET); statement->rsr_rows_pending = 0; #ifdef DEBUG fprintf(stdout, "Resetting Rows Pending in batch_dsql_fetch=%lu\n", statement->rsr_rows_pending); #endif } if (--statement->rsr_batch_count == 0) statement->rsr_rows_pending = 0; dequeue_receive(port); // clear next queued batch(es) if present if (packet->p_sqldata.p_sqldata_status == 100) { try { clear_stmt_que(port, statement); } catch (const Exception&) { } } break; } statement->rsr_msgs_waiting++; statement->rsr_rows_pending--; #ifdef DEBUG fprintf(stdout, "Decrementing Rows Pending in batch_dsql_fetch=%lu\n", statement->rsr_rows_pending); #endif if (!clear_queue) break; } } static void batch_gds_receive(rem_port* port, rmtque* que_inst, USHORT id) { /************************************** * * b a t c h _ g d s _ r e c e i v e * ************************************** * * Functional description * Receive a batch of messages that were queued * on the wire. * * This function will be invoked whenever we need to wait * for something to come over on the wire, and there are * items in the queue for receipt. * * Note on error handing: Actual networking errors * need to be reported to status - which is bubbled * upwards to the API call which initiated this receive. * A status vector being returned as part of the cursor * fetch needs to be stored away for later return to the * client in the proper place in the stream. * **************************************/ fb_assert(port); fb_assert(que_inst); fb_assert(que_inst->rmtque_function == batch_gds_receive); Rdb* rdb = que_inst->rmtque_rdb; Rrq* request = static_cast(que_inst->rmtque_parm); Rrq::rrq_repeat* tail = que_inst->rmtque_message; PACKET *packet = &rdb->rdb_packet; fb_assert(port == rdb->rdb_port); bool clear_queue = false; // indicates whether queue is just being emptied, not retrieved // always clear the complete queue for XNET, as we might // have incomplete packets if (id != request->rrq_id || port->port_type == rem_port::XNET) { clear_queue = true; } // Receive the whole batch of records, until end-of-batch is seen while (true) { RMessage* message = tail->rrq_xdr; // First free buffer // If the buffer queue is full, allocate a new message and // place it in the queue--if we are clearing the queue, don't // read records into messages linked list so that we don't // mess up the record cache for scrolling purposes. if (message->msg_address) { const rem_fmt* format = tail->rrq_format; RMessage* new_msg = FB_NEW RMessage(format->fmt_length); tail->rrq_xdr = new_msg; new_msg->msg_next = message; new_msg->msg_number = message->msg_number; // Walk the que until we find the predecessor of message while (message->msg_next != new_msg->msg_next) { message = message->msg_next; } message->msg_next = new_msg; } // Note: not receive_packet try { receive_packet_noqueue(rdb->rdb_port, packet); } catch (const Exception&) { // Must be a network error tail->rrq_rows_pending = 0; --tail->rrq_batch_count; dequeue_receive(port); throw; } if (packet->p_operation != op_send) { tail->rrq_rows_pending = 0; --tail->rrq_batch_count; try { LocalStatus ls; CheckStatusWrapper status(&ls); REMOTE_check_response(&status, rdb, packet); #ifdef DEBUG fprintf(stderr, "End of batch. rows pending = %d\n", tail->rrq_rows_pending); #endif request->saveStatus(&status); } catch (const Exception& ex) { #ifdef DEBUG fprintf(stderr, "Got batch error %ld Max message = %d\n", ex->value()[1], request->rrq_max_msg); #endif // Queue errors within the batched request request->saveStatus(ex); } dequeue_receive(port); break; } tail->rrq_msgs_waiting++; tail->rrq_rows_pending--; #ifdef DEBUG fprintf(stdout, "Decrementing Rows Pending in batch_gds_receive=%d\n", tail->rrq_rows_pending); #endif // See if we're at end of the batch if (!packet->p_data.p_data_messages) { if (!(--tail->rrq_batch_count)) tail->rrq_rows_pending = 0; #ifdef DEBUG fprintf(stderr, "End of batch waiting %d\n", tail->rrq_rows_pending); #endif dequeue_receive(port); break; } // one packet is enough unless we are trying to clear the queue if (!clear_queue) break; } } static void clear_queue(rem_port* port) { /************************************** * * c l e a r _ q u e u e * ************************************** * * Functional description * Clear the queue of batched packets - in preparation * for waiting for a specific response, or when we are * about to reuse an internal request. * Return codes: * true - no errors. * false - Network error occurred, error code in status **************************************/ while (port->port_receive_rmtque) { receive_queued_packet(port, (USHORT) -1); } } static void finalize(rem_port* port) { /************************************** * * f i n a l i z e * ************************************** * * Functional description * Disconnect remote port. * **************************************/ // no need to do something if port already detached if (port->port_flags & PORT_detached) return; // Avoid async send during finalize RefMutexGuard guard(*port->port_write_sync, FB_FUNCTION); // recheck with mutex taken if (port->port_flags & PORT_detached) return; // Send a disconnect to the server so that it // gracefully terminates. Rdb* rdb = port->port_context; if (rdb) { PACKET* packet = &rdb->rdb_packet; // Deliver the pending deferred packets if (port->port_deferred_packets) { for (rem_que_packet* p = port->port_deferred_packets->begin(); p < port->port_deferred_packets->end(); p++) { if (!p->sent) port->send(&p->packet); } } packet->p_operation = op_disconnect; port->send(packet); REMOTE_free_packet(port, packet); } // Cleanup the queue delete port->port_deferred_packets; port->port_deferred_packets = nullptr; port->port_flags &= ~PORT_lazy; port->port_flags |= PORT_detached; } static void disconnect(rem_port* port, bool rmRef) { /************************************** * * d i s c o n n e c t * ************************************** * * Functional description * Disconnect a port and free its memory. * **************************************/ finalize(port); Rdb* rdb = port->port_context; port->port_context = nullptr; // Clear context reference for the associated event handler // to avoid SEGV during shutdown if (port->port_async) { port->port_async->port_context = NULL; port->port_async->port_flags |= PORT_disconnect; } // Perform physical network disconnect and release // memory for remote database context. port->port_flags |= PORT_disconnect; port->disconnect(); delete rdb; // Remove from active ports if (rmRef) outPorts->unRegisterPort(port); } static THREAD_ENTRY_DECLARE event_thread(THREAD_ENTRY_PARAM arg) { /************************************** * * e v e n t _ t h r e a d * ************************************** * * Functional description * Wait on auxilary mailbox for event notification. * **************************************/ rem_port* port = (rem_port*)arg; // Reference portRef(*port); PACKET packet; while (!(port->port_flags & PORT_disconnect)) { // zero packet zap_packet(&packet); // read what should be an event message rem_port* stuff = NULL; P_OP operation = op_void; { // scope RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); try { stuff = port->receive(&packet); } catch(status_exception&) { // ignore } operation = packet.p_operation; if (!stuff || operation == op_exit || operation == op_disconnect) { // Actually, the remote server doing the watching died. // Clean up and leave. REMOTE_free_packet(port, &packet); server_death(port); break; } } // end scope // If the packet was an event, we handle it if (operation == op_event) { P_EVENT* pevent = &packet.p_event; Rvnt* event = NULL; { // scope RefMutexGuard portGuard(*port->port_sync, FB_FUNCTION); event = find_event(port, pevent->p_event_rid); } if (event) { // Call the asynchronous event routine associated // with this event const ULONG length = pevent->p_event_items.cstr_length; if (length <= event->rvnt_length) { event->rvnt_callback->eventCallbackFunction(length, pevent->p_event_items.cstr_address); } //else {.... //This is error condition, but we have absolutely no ways to report it. //Therefore simply ignore such bad packet. // Finished processing this event // Callback above should release event and another thread could reuse it meanwhile. // Make sure we don't release such reused event. if (event->rvnt_id == pevent->p_event_rid) event->rvnt_id = 0; } } // end of event handling for op_event REMOTE_free_packet(port, &packet); } // end of infinite for loop // to make compilers happy return 0; } static Rvnt* find_event( rem_port* port, SLONG id) { /************************************* * * f i n d _ e v e n t * ************************************** * * Functional description * Find event with specified event_id. * **************************************/ Rdb* rdb = port->port_context; if (rdb && !(port->port_flags & PORT_disconnect)) { for (Rvnt* event = rdb->rdb_events; event; event = event->rvnt_next) { if (event->rvnt_id == id) return event; } } return NULL; } static bool get_new_dpb(ClumpletWriter& dpb, const ParametersSet& par, bool loopback) { /************************************** * * g e t _ n e w _ d p b * ************************************** * * Functional description * Fetch user_string out of dpb. * Analyze and prepare dpb for attachment to remote server. * **************************************/ bool redirection = Config::getRedirection(); if (((loopback || !redirection) && dpb.find(par.address_path)) || dpb.find(par.map_attach)) { status_exception::raise(Arg::Gds(isc_unavailable)); } return dpb.find(par.user_name); } static void info(CheckStatusWrapper* status, Rdb* rdb, P_OP operation, USHORT object, USHORT incarnation, USHORT item_length, const UCHAR* items, USHORT recv_item_length, const UCHAR* recv_items, ULONG buffer_length, UCHAR* buffer) { /************************************** * * i n f o * ************************************** * * Functional description * Solicit and receive information. * **************************************/ // Build the primary packet to get the operation started. PACKET* packet = &rdb->rdb_packet; packet->p_operation = operation; P_INFO* information = &packet->p_info; information->p_info_object = object; information->p_info_incarnation = incarnation; information->p_info_items.cstr_length = item_length; information->p_info_items.cstr_address = items; if (operation == op_service_info) { information->p_info_recv_items.cstr_length = recv_item_length; information->p_info_recv_items.cstr_address = recv_items; } information->p_info_buffer_length = buffer_length; send_packet(rdb->rdb_port, packet); // Set up for the response packet. P_RESP* response = &packet->p_resp; SaveString temp(response->p_resp_data, buffer_length, buffer); receive_response(status, rdb, packet); } static bool useLegacyAuth(const char* nm, int protocol, ClumpletWriter& dpb) { LegacyPlugin legacyAuth = REMOTE_legacy_auth(nm, protocol); if (!legacyAuth) return false; int requestedAuth = dpb.find(isc_dpb_user_name) ? PLUGIN_LEGACY : PLUGIN_TRUSTED; return legacyAuth == requestedAuth; } // Let plugins try to add data to DPB in order to avoid extra network roundtrip static void authFillParametersBlock(ClntAuthBlock& cBlock, ClumpletWriter& dpb, const ParametersSet* tags, rem_port* port) { if (cBlock.authComplete) return; // Already authenticated LocalStatus ls; CheckStatusWrapper s(&ls); cBlock.resetDataFromPlugin(); for (; cBlock.plugins.hasData(); cBlock.plugins.next()) { if (port->port_protocol >= PROTOCOL_VERSION13 || useLegacyAuth(cBlock.plugins.name(), port->port_protocol, dpb)) { // OK to use plugin cBlock.resetDataFromPlugin(); HANDSHAKE_DEBUG(fprintf(stderr, "Cli: authFillParametersBlock(%s)\n", cBlock.plugins.name())); int authRc = cBlock.plugins.plugin()->authenticate(&s, &cBlock); switch (authRc) { case IAuth::AUTH_SUCCESS: case IAuth::AUTH_MORE_DATA: HANDSHAKE_DEBUG(fprintf(stderr, "Cli: authFillParametersBlock: plugin %s is OK\n", cBlock.plugins.name())); cleanDpb(dpb, tags); cBlock.extractDataFromPluginTo(dpb, tags, port->port_protocol); return; case IAuth::AUTH_CONTINUE: continue; case IAuth::AUTH_FAILED: HANDSHAKE_DEBUG(fprintf(stderr, "Cli: authFillParametersBlock: plugin %s FAILED\n", cBlock.plugins.name())); (Arg::Gds(isc_login) << Arg::StatusVector(&s)).raise(); break; // compiler silencer } } HANDSHAKE_DEBUG(fprintf(stderr, "Cli: authFillParametersBlock: try next plugin, %s skipped\n", cBlock.plugins.name())); } } #ifdef NOT_USED_OR_REPLACED static CSTRING* REMOTE_dup_string(const CSTRING* from) { if (from && from->cstr_length) { CSTRING* rc = FB_NEW_POOL(*getDefaultMemoryPool()) CSTRING; memset(rc, 0, sizeof(CSTRING)); rc->cstr_length = from->cstr_length; rc->cstr_allocated = rc->cstr_length; rc->cstr_address = FB_NEW_POOL(*getDefaultMemoryPool()) UCHAR[rc->cstr_length]; memcpy(rc->cstr_address, from->cstr_address, rc->cstr_length); return rc; } return NULL; } static void REMOTE_free_string(CSTRING* tmp) { if (tmp) { if (tmp->cstr_address) { fb_assert(tmp->cstr_allocated >= tmp->cstr_length); delete[] tmp->cstr_address; } delete tmp; } } #endif // NOT_USED_OR_REPLACED static void authReceiveResponse(bool havePacket, ClntAuthBlock& cBlock, rem_port* port, Rdb* rdb, IStatus* status, PACKET* packet, bool checkKeys) { LocalStatus ls; CheckStatusWrapper s(&ls); for (;;) { // Get response if (!havePacket) receive_packet(port, packet); else fb_assert(packet->p_operation == op_cond_accept); havePacket = false; // havePacket means first packet is already received // Check response cstring* n = NULL; cstring* d = NULL; switch(packet->p_operation) { case op_trusted_auth: HANDSHAKE_DEBUG(fprintf(stderr, "Cli: authReceiveResponse: trusted_auth\n")); d = &packet->p_trau.p_trau_data; break; case op_cont_auth: d = &packet->p_auth_cont.p_data; n = &packet->p_auth_cont.p_name; port->addServerKeys(&packet->p_auth_cont.p_keys); HANDSHAKE_DEBUG(fprintf(stderr, "Cli: authReceiveResponse: cont_auth d=%d n=%d '%.*s' 0x%x\n", d->cstr_length, n->cstr_length, n->cstr_length, n->cstr_address, n->cstr_address ? n->cstr_address[0] : 0)); break; case op_cond_accept: d = &packet->p_acpd.p_acpt_data; n = &packet->p_acpd.p_acpt_plugin; port->addServerKeys(&packet->p_acpd.p_acpt_keys); HANDSHAKE_DEBUG(fprintf(stderr, "Cli: authReceiveResponse: cond_accept d=%d n=%d '%.*s' 0x%x\n", d->cstr_length, n->cstr_length, n->cstr_length, n->cstr_address, n->cstr_address ? n->cstr_address[0] : 0)); if (packet->p_acpd.p_acpt_type & pflag_compress) { port->initCompression(); port->port_flags |= PORT_compressed; } packet->p_acpd.p_acpt_type &= ptype_MASK; break; default: HANDSHAKE_DEBUG(fprintf(stderr, "Cli: authReceiveResponse: Default answer\n")); REMOTE_check_response(status, rdb, packet, checkKeys); // successfully attached HANDSHAKE_DEBUG(fprintf(stderr, "Cli: authReceiveResponse: OK!\n")); cBlock.authComplete = true; rdb->rdb_id = packet->p_resp.p_resp_object; // try to start crypt cBlock.tryNewKeys(port); return; } if (n && n->cstr_length && cBlock.plugins.hasData()) { // if names match, do not change instance if (strlen(cBlock.plugins.name()) == n->cstr_length && memcmp(cBlock.plugins.name(), n->cstr_address, n->cstr_length) == 0) { n = NULL; } } if (n && n->cstr_length) { // switch to other plugin PathName tmp(n->cstr_address, n->cstr_length); if (!cBlock.checkPluginName(tmp)) { break; } cBlock.plugins.set(tmp.c_str()); } if (!cBlock.plugins.hasData()) { break; } cBlock.resetDataFromPlugin(); cBlock.storeDataForPlugin(d->cstr_length, d->cstr_address); HANDSHAKE_DEBUG(fprintf(stderr, "Cli: receiveResponse: authenticate(%s)\n", cBlock.plugins.name())); if (cBlock.plugins.plugin()->authenticate(&s, &cBlock) == IAuth::AUTH_FAILED) { break; } // send answer (may be empty) to server if (port->port_protocol >= PROTOCOL_VERSION13) { packet->p_operation = op_cont_auth; cBlock.extractDataFromPluginTo(&packet->p_auth_cont); } else { packet->p_operation = op_trusted_auth; cBlock.extractDataFromPluginTo(&packet->p_trau.p_trau_data); } send_packet(port, packet); REMOTE_free_packet(port, packet, true); memset(&packet->p_auth_cont, 0, sizeof packet->p_auth_cont); } // If we have exited from the cycle, this mean auth failed (Arg::Gds(isc_login) << Arg::StatusVector(&s)).raise(); } static bool init(CheckStatusWrapper* status, ClntAuthBlock& cBlock, rem_port* port, P_OP op, PathName& file_name, ClumpletWriter& dpb, IntlParametersBlock& intlParametersBlock, ICryptKeyCallback* cryptCallback) { /************************************** * * i n i t * ************************************** * * Functional description * Initialize for database access. First call from both CREATE and * OPEN. * **************************************/ try { Rdb* rdb = port->port_context; PACKET* packet = &rdb->rdb_packet; MemoryPool& pool = *getDefaultMemoryPool(); port->port_deferred_packets = FB_NEW_POOL(pool) PacketQueue(pool); if (port->port_protocol < PROTOCOL_VERSION12) { // This is FB < 2.5. Lets remove that not recognized DPB/SPB and convert the UTF8 // strings to the OS codepage. intlParametersBlock.fromUtf8(dpb); ISC_unescape(file_name); ISC_utf8ToSystem(file_name); } const ParametersSet* const ps = (op == op_service_attach ? &spbParam : &dpbParam); HANDSHAKE_DEBUG(fprintf(stderr, "Cli: init calls authFillParametersBlock\n")); authFillParametersBlock(cBlock, dpb, ps, port); port->port_client_crypt_callback = cryptCallback; cBlock.createCryptCallback(&port->port_client_crypt_callback); // Make attach packet P_ATCH* attach = &packet->p_atch; packet->p_operation = op; attach->p_atch_file.cstr_length = (ULONG) file_name.length(); attach->p_atch_file.cstr_address = reinterpret_cast(file_name.c_str()); attach->p_atch_dpb.cstr_length = (ULONG) dpb.getBufferLength(); attach->p_atch_dpb.cstr_address = dpb.getBuffer(); send_packet(port, packet); authReceiveResponse(false, cBlock, port, rdb, status, packet, true); return true; } catch (const Exception& ex) { // report primary init error ex.stuffException(status); } try { disconnect(port); } catch (const Exception&) { // ignore secondary error } return false; } static Rtr* make_transaction( Rdb* rdb, USHORT id) { /************************************** * * m a k e _ t r a n s a c t i o n * ************************************** * * Functional description * Create a local transaction handle. * **************************************/ Rtr* transaction = FB_NEW Rtr; transaction->rtr_rdb = rdb; transaction->rtr_id = id; transaction->rtr_next = rdb->rdb_transactions; rdb->rdb_transactions = transaction; SET_OBJECT(rdb, transaction, id); return transaction; } static void mov_dsql_message(const UCHAR* from_msg, const rem_fmt* from_fmt, UCHAR* to_msg, const rem_fmt* to_fmt) { /************************************** * * m o v _ d s q l _ m e s s a g e * ************************************** * * Functional description * Move data using formats. * **************************************/ if (!from_msg || !from_fmt || !to_msg || !to_fmt || from_fmt->fmt_desc.getCount() != to_fmt->fmt_desc.getCount()) { move_error(Arg::Gds(isc_dsql_sqlda_err)); // Msg 263 SQLDA missing or wrong number of variables } const dsc* from_desc = from_fmt->fmt_desc.begin(); const dsc* to_desc = to_fmt->fmt_desc.begin(); for (const dsc* const end_desc = to_fmt->fmt_desc.end(); to_desc < end_desc; from_desc++, to_desc++) { dsc from = *from_desc; dsc to = *to_desc; // Safe const cast, we are going to move from it to anywhere. from.dsc_address = const_cast(from_msg) + (IPTR) from.dsc_address; to.dsc_address = to_msg + (IPTR) to.dsc_address; CVT_move(&from, &to, DecimalStatus(FB_DEC_Errors), move_error); } } static void move_error(const Arg::StatusVector& v) { /************************************** * * m o v e _ e r r o r * ************************************** * * Functional description * A conversion error occurred. Complain. * **************************************/ Arg::Gds status_vector(isc_random); status_vector << "Dynamic SQL Error" << Arg::Gds(isc_sqlerr) << Arg::Num(-303); // append any other arguments which may have been handed to us, then post the error status_vector.append(v); status_exception::raise(status_vector); } static void receive_after_start(Rrq* request, USHORT msg_type) { /***************************************** * * r e c e i v e _ a f t e r _ s t a r t * ***************************************** * * Functional Description * Some opcodes, such as "start_and_send" automatically start the * cursor being started, under protcol 8 we then receive the first * batch of records without having to ask for them. * * Note: if a network error occurs during this receive, we do not * recognize it in the "gds_start" API call that initiated this * action. It will be stored with the queue of records for the * cursor that is being fetched. This is not ideal - but compabile * with how the code worked prior to pipelining work done * 1996-Jul-15 David Schnepper * *****************************************/ // Check to see if any data is waiting to happen Rdb* rdb = request->rrq_rdb; PACKET* packet = &rdb->rdb_packet; Rrq::rrq_repeat* tail = &request->rrq_rpt[msg_type]; // CVC: I commented this line because it's overwritten immediately in the loop. // RMessage* message = tail->rrq_message; const rem_fmt* format = tail->rrq_format; // Swallow up data. If a buffer isn't available, allocate another while (true) { RMessage* message = tail->rrq_xdr; if (message->msg_address) { RMessage* new_msg = FB_NEW RMessage(format->fmt_length); tail->rrq_xdr = new_msg; new_msg->msg_next = message; new_msg->msg_number = message->msg_number; while (message->msg_next != new_msg->msg_next) message = message->msg_next; message->msg_next = new_msg; } // Note: not receive_packet try { receive_packet_noqueue(rdb->rdb_port, packet); } catch (const Exception& ex) { request->saveStatus(ex); return; } // Did an error response come back ? if (packet->p_operation != op_send) { try { LocalStatus ls; CheckStatusWrapper status(&ls); REMOTE_check_response(&status, rdb, packet); request->saveStatus(&status); } catch (const Exception& ex) { request->saveStatus(ex); } return; } tail->rrq_msgs_waiting++; // Reached end of batch if (!packet->p_data.p_data_messages) break; } } static void receive_packet(rem_port* port, PACKET* packet) { /************************************** * * r e c e i v e _ p a c k e t * ************************************** * * Functional description * Clear the queue of any pending receives, then receive the * response to a sent request, blocking if necessary until * the response is present. * * Return codes: * true - no errors. * false - Network error occurred, error code in status * **************************************/ // Must clear the wire of any queued receives before fetching // the desired packet clear_queue(port); receive_packet_noqueue(port, packet); } static void receive_packet_with_callback(rem_port* port, PACKET* packet) { /************************************** * * r e c e i v e _ p a c k e t _ w i t h _ c a l l b a c k * ************************************** * * Functional description * If received packet is request from callback info from user, * send requested info (or no data if callback is not set) and * wait for next packet. * **************************************/ UCharBuffer buf; for (;;) { if (!port->receive(packet)) { Arg::Gds(isc_net_read_err).raise(); } switch (packet->p_operation) { case op_crypt_key_callback: { P_CRYPT_CALLBACK* cc = &packet->p_cc; Cleanup ccData([&cc]() { cc->p_cc_data.cstr_length = 0; cc->p_cc_data.cstr_address = nullptr; }); if (port->port_client_crypt_callback) { if (cc->p_cc_reply <= 0) { cc->p_cc_reply = 1; } UCHAR* reply = buf.getBuffer(cc->p_cc_reply); unsigned l = port->port_client_crypt_callback->callback(cc->p_cc_data.cstr_length, cc->p_cc_data.cstr_address, cc->p_cc_reply, reply); REMOTE_free_packet(port, packet, true); cc->p_cc_data.cstr_length = l; cc->p_cc_data.cstr_address = reply; } else { REMOTE_free_packet(port, packet, true); cc->p_cc_data.cstr_length = 0; } packet->p_operation = op_crypt_key_callback; cc->p_cc_reply = 0; port->send(packet); } break; default: return; } } } static void receive_packet_noqueue(rem_port* port, PACKET* packet) { /************************************** * * r e c e i v e _ p a c k e t _ n o q u e u e * ************************************** * * Functional description * Receive a packet and check for a network * error on the receive. * Note: SOME of the network lower level protocols * will set up a status vector when errors * occur, but other ones won't. * So this routine sets up an error result * for the vector prior to going into the * network layer. Note that we can't * RESET the status vector as one thing * that can be received is a new status vector * * See also cousin routine: send_packet, send_partial_packet * **************************************/ // Receive responses for all deferred packets that were already sent if (port->port_deferred_packets) { while (port->port_deferred_packets->getCount()) { rem_que_packet* const p = port->port_deferred_packets->begin(); if (!p->sent) break; OBJCT stmt_id = 0; bool bCheckResponse = false, bFreeStmt = false, bAssign = false; switch (p->packet.p_operation) { case op_execute: stmt_id = p->packet.p_sqldata.p_sqldata_statement; bCheckResponse = true; bAssign = true; break; case op_batch_msg: stmt_id = p->packet.p_batch_msg.p_batch_statement; bCheckResponse = true; break; case op_batch_create: stmt_id = p->packet.p_batch_create.p_batch_statement; bCheckResponse = true; break; case op_free_statement: stmt_id = p->packet.p_sqlfree.p_sqlfree_statement; bFreeStmt = (p->packet.p_sqlfree.p_sqlfree_option == DSQL_drop); break; } receive_packet_with_callback(port, &p->packet); Rsr* statement = NULL; if (bCheckResponse || bFreeStmt) statement = port->port_objects[stmt_id]; if (bCheckResponse) { try { Rdb* rdb = port->port_context; LocalStatus ls; CheckStatusWrapper status(&ls); REMOTE_check_response(&status, rdb, &p->packet); statement->saveException(&status, false); } catch (const Exception& ex) { // save error within the corresponding statement statement->saveException(ex, false); bAssign = false; } if (bAssign) { // assign statement to transaction const OBJCT tran_id = p->packet.p_sqldata.p_sqldata_transaction; Rtr* transaction = port->port_objects[tran_id]; statement->rsr_rtr = transaction; } } if (bFreeStmt && p->packet.p_resp.p_resp_object == INVALID_OBJECT) release_sql_request(statement); // free only part of packet we worked with REMOTE_free_packet(port, &p->packet, true); port->port_deferred_packets->remove(p); } } receive_packet_with_callback(port, packet); } static void receive_queued_packet(rem_port* port, USHORT id) { /************************************** * * r e c e i v e _ q u e u e d_ p a c k e t * ************************************** * * Functional description * We're marked as having pending receives on the * wire. Grab the first pending receive and return. * **************************************/ // Trivial case, nothing pending on the wire if (!port->port_receive_rmtque) { return; } // Grab first queue entry rmtque* que_inst = port->port_receive_rmtque; // Receive the data (que_inst->rmtque_function) (port, que_inst, id); } static void enqueue_receive(rem_port* port, t_rmtque_fn fn, Rdb* rdb, void* parm, Rrq::rrq_repeat* parm1) { /************************************** * * e n q u e u e _ r e c e i v e * ************************************** * * Functional description * **************************************/ rmtque* const que_inst = FB_NEW rmtque; // Prepare a queue entry que_inst->rmtque_next = NULL; que_inst->rmtque_function = fn; que_inst->rmtque_parm = parm; que_inst->rmtque_message = parm1; que_inst->rmtque_rdb = rdb; // Walk to the end of the current queue rmtque** queptr = &port->port_receive_rmtque; while (*queptr) queptr = &(*queptr)->rmtque_next; // Add the new entry to the end of the queue *queptr = que_inst; } static void dequeue_receive( rem_port* port) { /************************************** * * d e q u e u e _ r e c e i v e * ************************************** * * Functional description * **************************************/ // Grab first queue entry & de-queue it rmtque* que_inst = port->port_receive_rmtque; port->port_receive_rmtque = que_inst->rmtque_next; que_inst->rmtque_next = NULL; // Add queue entry onto free queue delete que_inst; } static void receive_response(IStatus* status, Rdb* rdb, PACKET* packet) { /************************************** * * r e c e i v e _ r e s p o n s e * ************************************** * * Functional description * Check response to a remote call. * **************************************/ receive_packet(rdb->rdb_port, packet); REMOTE_check_response(status, rdb, packet); } static void release_blob( Rbl* blob) { /************************************** * * r e l e a s e _ b l o b * ************************************** * * Functional description * Release a blob block and friends. * **************************************/ Rtr* transaction = blob->rbl_rtr; Rdb* rdb = blob->rbl_rdb; rdb->rdb_port->releaseObject(blob->rbl_id); for (Rbl** p = &transaction->rtr_blobs; *p; p = &(*p)->rbl_next) { if (*p == blob) { *p = blob->rbl_next; break; } } delete blob; } static void release_event( Rvnt* event) { /************************************** * * r e l e a s e _ e v e n t * ************************************** * * Functional description * Release an event block. * **************************************/ Rdb* rdb = event->rvnt_rdb; for (Rvnt** p = &rdb->rdb_events; *p; p = &(*p)->rvnt_next) { if (*p == event) { *p = event->rvnt_next; break; } } delete event; } static void release_object(IStatus* status, Rdb* rdb, P_OP op, USHORT id) { /************************************** * * r e l e a s e _ o b j e c t * ************************************** * * Functional description * Tell the server to zap an object. This doesn't necessary * release the object, but usually does. * **************************************/ PACKET* packet = &rdb->rdb_packet; packet->p_operation = op; packet->p_rlse.p_rlse_object = id; if (rdb->rdb_port->port_flags & PORT_lazy) { switch (op) { case op_close_blob: case op_cancel_blob: case op_release: defer_packet(rdb->rdb_port, packet); return; default: break; } } send_packet(rdb->rdb_port, packet); receive_response(status, rdb, packet); } static void release_request( Rrq* request) { /************************************** * * r e l e a s e _ r e q u e s t * ************************************** * * Functional description * Release a request block and friends. * **************************************/ Rdb* rdb = request->rrq_rdb; rdb->rdb_port->releaseObject(request->rrq_id); REMOTE_release_request(request); } static void release_statement( Rsr** statement) { /************************************** * * r e l e a s e _ s t a t e m e n t * ************************************** * * Functional description * Release a GDML or SQL statement block ? * **************************************/ delete (*statement)->rsr_bind_format; if ((*statement)->rsr_user_select_format && (*statement)->rsr_user_select_format != (*statement)->rsr_select_format) { delete (*statement)->rsr_user_select_format; } delete (*statement)->rsr_select_format; (*statement)->releaseException(); REMOTE_release_messages((*statement)->rsr_message); delete *statement; *statement = NULL; } static void release_sql_request( Rsr* statement) { /************************************** * * r e l e a s e _ s q l _ r e q u e s t * ************************************** * * Functional description * Release an SQL request block. * **************************************/ Rdb* rdb = statement->rsr_rdb; rdb->rdb_port->releaseObject(statement->rsr_id); for (Rsr** p = &rdb->rdb_sql_requests; *p; p = &(*p)->rsr_next) { if (*p == statement) { *p = statement->rsr_next; break; } } release_statement(&statement); } static void release_transaction( Rtr* transaction) { /************************************** * * r e l e a s e _ t r a n s a c t i o n * ************************************** * * Functional description * Release a transaction block and friends. * **************************************/ Rdb* rdb = transaction->rtr_rdb; rdb->rdb_port->releaseObject(transaction->rtr_id); while (transaction->rtr_blobs) release_blob(transaction->rtr_blobs); for (Rtr** p = &rdb->rdb_transactions; *p; p = &(*p)->rtr_next) { if (*p == transaction) { *p = transaction->rtr_next; break; } } delete transaction; } static void send_and_receive(IStatus* status, Rdb* rdb, PACKET* packet) { /************************************** * * s e n d _ a n d _ r e c e i v e * ************************************** * * Functional description * Send a packet, check status, receive a packet, and check status. * **************************************/ send_packet(rdb->rdb_port, packet); receive_response(status, rdb, packet); } static void send_blob(CheckStatusWrapper* status, Rbl* blob, USHORT buffer_length, const UCHAR* buffer) { /************************************** * * s e n d _ b l o b * ************************************** * * Functional description * Actually send blob data (which might be buffered) * **************************************/ Rdb* rdb = blob->rbl_rdb; PACKET* packet = &rdb->rdb_packet; packet->p_operation = op_put_segment; // If we aren't passed a buffer address, this is a batch send. Pick up the // address and length from the blob buffer and blast away if (!buffer) { buffer = blob->rbl_buffer; buffer_length = blob->rbl_ptr - buffer; blob->rbl_ptr = blob->rbl_buffer; packet->p_operation = op_batch_segments; } P_SGMT* segment = &packet->p_sgmt; CSTRING_CONST temp = segment->p_sgmt_segment; segment->p_sgmt_blob = blob->rbl_id; segment->p_sgmt_segment.cstr_length = buffer_length; segment->p_sgmt_segment.cstr_address = buffer; segment->p_sgmt_length = buffer_length; send_packet(rdb->rdb_port, packet); // restore the string; "buffer" is not referenced anymore, hence no // possibility to overwrite it accidentally. segment->p_sgmt_segment = temp; // Set up for the response packet. receive_response(status, rdb, packet); } static void send_packet(rem_port* port, PACKET* packet) { /************************************** * * s e n d _ p a c k e t * ************************************** * * Functional description * Send a packet and check for a network error * on the send. * Make up a status vector for any error. * Note: SOME of the network lower level protocols * will set up a status vector when errors * occur, but other ones won't. * So this routine sets up an error result * for the vector and resets it to true * if the packet send occurred. * * See also cousin routine: receive_packet * **************************************/ RefMutexGuard guard(*port->port_write_sync, FB_FUNCTION); if (port->port_flags & PORT_detached || port->port_state == rem_port::BROKEN) { (Arg::Gds(isc_net_write_err) #ifdef DEV_BUILD << Arg::Gds(isc_random) << "port detached" #endif ).raise(); } // Send packets that were deferred if (port->port_deferred_packets) { for (rem_que_packet* p = port->port_deferred_packets->begin(); p < port->port_deferred_packets->end(); ++p) { if (!p->sent) { if (!port->send_partial(&p->packet)) (Arg::Gds(isc_net_write_err) << Arg::Gds(isc_random) << "send_packet/send_partial").raise(); p->sent = true; } } } if (!port->send(packet)) { (Arg::Gds(isc_net_write_err)<< Arg::Gds(isc_random) << "send_packet/send").raise(); } } static void send_partial_packet(rem_port* port, PACKET* packet) { /************************************** * * s e n d _ p a r t i a l _ p a c k e t * ************************************** * * Functional description * Send a packet and check for a network error * on the send. * Make up a status vector for any error. * Note: SOME of the network lower level protocols * will set up a status vector when errors * occur, but other ones won't. * So this routine sets up an error result * for the vector and resets it to true * if the packet send occurred. * * See also cousin routine: receive_packet, send_packet * **************************************/ RefMutexGuard guard(*port->port_write_sync, FB_FUNCTION); if (port->port_flags & PORT_detached || port->port_state == rem_port::BROKEN) { (Arg::Gds(isc_net_write_err) #ifdef DEV_BUILD << Arg::Gds(isc_random) << "port detached" #endif ).raise(); } // Send packets that were deferred if (port->port_deferred_packets) { for (rem_que_packet* p = port->port_deferred_packets->begin(); p < port->port_deferred_packets->end(); p++) { if (!p->sent) { if (!port->send_partial(&p->packet)) { (Arg::Gds(isc_net_write_err) << Arg::Gds(isc_random) << "send_partial_packet/send_partial").raise(); } p->sent = true; } } } if (!port->send_partial(packet)) { (Arg::Gds(isc_net_write_err) << Arg::Gds(isc_random) << "send_partial_packet/send").raise(); } } static void server_death(rem_port* port) { /************************************** * * s e r v e r _ d e a t h * ************************************** * * Functional description * Received "EOF" from remote server * Cleanup events. * **************************************/ Rdb* rdb = port->port_context; if (rdb && !(port->port_flags & PORT_disconnect)) { for (Rvnt* event = rdb->rdb_events; event; event = event->rvnt_next) { if (event->rvnt_id) { event->rvnt_id = 0; event->rvnt_callback->eventCallbackFunction(0, NULL); } } } } static void svcstart(CheckStatusWrapper* status, Rdb* rdb, P_OP operation, USHORT object, USHORT incarnation, USHORT item_length, const UCHAR* items) { /************************************** * * s v c s t a r t * ************************************** * * Functional description * Instruct the server to start a service * **************************************/ ClumpletWriter send(ClumpletReader::SpbStart, MAX_DPB_SIZE, items, item_length); if (rdb->rdb_port->port_protocol < PROTOCOL_VERSION13) { // This is FB < 3.0. Lets convert the UTF8 strings to the OS codepage. IntlSpbStart().fromUtf8(send); } // Build the primary packet to get the operation started. PACKET* packet = &rdb->rdb_packet; packet->p_operation = operation; P_INFO* information = &packet->p_info; information->p_info_object = object; information->p_info_incarnation = incarnation; information->p_info_items.cstr_length = (ULONG) send.getBufferLength(); information->p_info_items.cstr_address = send.getBuffer(); information->p_info_buffer_length = (ULONG) send.getBufferLength(); send_packet(rdb->rdb_port, packet); // Set up for the response packet. P_RESP* response = &packet->p_resp; SaveString temp(response->p_resp_data, 0, NULL); response->p_resp_data.cstr_length = 0; receive_response(status, rdb, packet); } static void unsupported() { /************************************** * * u n s u p p o r t e d * ************************************** * * Functional description * No_entrypoint is called if there is not entrypoint for a given routine. * **************************************/ Arg::Gds(isc_wish_list).raise(); } static void zap_packet(PACKET* packet) { /************************************** * * z a p _ p a c k e t * ************************************** * * Functional description * Zero out a packet block. * **************************************/ memset(packet, 0, sizeof(struct packet)); } void Attachment::cancelOperation(CheckStatusWrapper* status, int kind) { /************************************* * * G D S _ C A N C E L _ O P E R A T I O N * ************************************** * * Functional description * Asynchronously cancel requests, running with db_handle on remote server. * **************************************/ try { reset(status); CHECK_HANDLE(rdb, isc_bad_db_handle); RemPortPtr port(rdb->rdb_port); if (kind == fb_cancel_abort) { port->force_close(); return; } if (port->port_protocol < PROTOCOL_VERSION12 || port->port_type != rem_port::INET) { unsupported(); } Cleanup unlockAsyncLock([this] { --(rdb->rdb_async_lock); }); if (++(rdb->rdb_async_lock) != 1) { // Something async already runs Arg::Gds(isc_async_active).raise(); } PACKET packet; packet.p_operation = op_cancel; P_CANCEL_OP* cancel = &packet.p_cancel_op; cancel->p_co_kind = kind; send_packet(rdb->rdb_port, &packet); } catch (const Exception& ex) { ex.stuffException(status); } } Rtr* Attachment::remoteTransaction(ITransaction* apiTra) { Transaction* rt = remoteTransactionInterface(apiTra); return rt ? rt->getTransaction() : NULL; } Transaction* Attachment::remoteTransactionInterface(ITransaction* apiTra) { if (!apiTra) return NULL; LocalStatus ls; CheckStatusWrapper dummy(&ls); ITransaction* valid = apiTra->validate(&dummy, this); if (!valid) return NULL; // If validation is successfull, this means that this attachment and valid transaction // use same provider. I.e. the following cast is safe. return static_cast(valid); } static void cleanDpb(Firebird::ClumpletWriter& dpb, const ParametersSet* tags) { dpb.deleteWithTag(tags->password); dpb.deleteWithTag(tags->password_enc); dpb.deleteWithTag(tags->trusted_auth); } } //namespace Remote void ClientPortsCleanup::closePort(rem_port* port) { RefMutexEnsureUnlock guard(*port->port_sync, FB_FUNCTION); if (port->port_flags & PORT_disconnect) return; if (guard.tryEnter()) Remote::finalize(port); else PortsCleanup::closePort(port); } RmtAuthBlock::RmtAuthBlock(const Firebird::AuthReader::AuthBlock& aBlock) : buffer(*getDefaultMemoryPool(), aBlock), rdr(*getDefaultMemoryPool(), buffer), info(*getDefaultMemoryPool()) { FbLocalStatus st; first(&st); check(&st); } const char* RmtAuthBlock::getType() { return info.type.nullStr(); } const char* RmtAuthBlock::getName() { return info.name.nullStr(); } const char* RmtAuthBlock::getPlugin() { return info.plugin.nullStr(); } const char* RmtAuthBlock::getSecurityDb() { return info.secDb.nullStr(); } const char* RmtAuthBlock::getOriginalPlugin() { return info.origPlug.nullStr(); } FB_BOOLEAN RmtAuthBlock::next(Firebird::CheckStatusWrapper* status) { try { rdr.moveNext(); return loadInfo(); } catch (const Exception& ex) { ex.stuffException(status); } return FB_FALSE; } FB_BOOLEAN RmtAuthBlock::first(Firebird::CheckStatusWrapper* status) { try { rdr.rewind(); return loadInfo(); } catch (const Exception& ex) { ex.stuffException(status); } return FB_FALSE; } FB_BOOLEAN RmtAuthBlock::loadInfo() { if (rdr.isEof()) return FB_FALSE; rdr.getInfo(info); return FB_TRUE; } ClntAuthBlock::ClntAuthBlock(const Firebird::PathName* fileName, Firebird::ClumpletReader* dpb, const ParametersSet* tags) : pluginList(getPool()), serverPluginList(getPool()), cliUserName(getPool()), cliPassword(getPool()), cliOrigUserName(getPool()), dataForPlugin(getPool()), dataFromPlugin(getPool()), cryptKeys(getPool()), dpbConfig(getPool()), dpbPlugins(getPool()), createdInterface(nullptr), plugins(IPluginManager::TYPE_AUTH_CLIENT), authComplete(false), firstTime(true) { if (dpb && tags) { if (dpb->find(tags->config_text)) dpb->getString(dpbConfig); if (dpb->find(tags->plugin_list)) dpb->getPath(dpbPlugins); if (dpb->find(tags->auth_block)) { AuthReader::AuthBlock plain; plain.add(dpb->getBytes(), dpb->getClumpLength()); remAuthBlock.reset(FB_NEW RmtAuthBlock(plain)); } } clntConfig = REMOTE_get_config(fileName, &dpbConfig); resetClnt(); } void ClntAuthBlock::resetDataFromPlugin() { dataFromPlugin.clear(); } void ClntAuthBlock::extractDataFromPluginTo(Firebird::ClumpletWriter& dpb, const ParametersSet* tags, int protocol) { if (!dataFromPlugin.hasData()) { return; } PathName pluginName = getPluginName(); if (protocol >= PROTOCOL_VERSION13) { if (firstTime) { fb_assert(tags->plugin_name && tags->plugin_list); if (pluginName.hasData()) dpb.insertString(tags->plugin_name, pluginName); dpb.deleteWithTag(tags->plugin_list); dpb.insertString(tags->plugin_list, pluginList); firstTime = false; HANDSHAKE_DEBUG(fprintf(stderr, "Cli: extractDataFromPluginTo: first time - added plugName & pluginList\n")); } fb_assert(tags->specific_data); dpb.insertBytes(tags->specific_data, dataFromPlugin.begin(), dataFromPlugin.getCount()); HANDSHAKE_DEBUG(fprintf(stderr, "Cli: extractDataFromPluginTo: Added %u bytes of spec data with tag %d\n", static_cast(dataFromPlugin.getCount()), tags->specific_data)); return; } if (REMOTE_legacy_auth(pluginName.c_str(), PROTOCOL_VERSION10)) // dataFromPlugin is encrypted password { fb_assert(tags->password_enc); dpb.insertBytes(tags->password_enc, dataFromPlugin.begin(), dataFromPlugin.getCount()); return; } fb_assert(REMOTE_legacy_auth(pluginName.c_str(), protocol)); // dataFromPlugin must be trustedAuth fb_assert(tags->trusted_auth); dpb.insertBytes(tags->trusted_auth, dataFromPlugin.begin(), dataFromPlugin.getCount()); } static inline void makeUtfString(bool uft8Convert, Firebird::string& s) { if (uft8Convert) { ISC_systemToUtf8(s); } ISC_unescape(s); } void ClntAuthBlock::loadClnt(Firebird::ClumpletWriter& dpb, const ParametersSet* tags) { bool uft8Convert = !dpb.find(tags->utf8_filename); for (dpb.rewind(); !dpb.isEof(); dpb.moveNext()) { const UCHAR t = dpb.getClumpTag(); if (t == tags->user_name) { dpb.getString(cliUserName); makeUtfString(uft8Convert, cliUserName); cliOrigUserName = cliUserName; fb_utils::dpbItemUpper(cliUserName); HANDSHAKE_DEBUG(fprintf(stderr, "Cli: loadClnt: Loaded from PB user = %s(was %s)\n", cliUserName.c_str(), cliOrigUserName.c_str())); } else if (t == tags->password) { dpb.getString(cliPassword); makeUtfString(uft8Convert, cliPassword); HANDSHAKE_DEBUG(fprintf(stderr, "Cli: loadClnt: Loaded from PB cliPassword = %s\n", cliPassword.c_str())); } else if (t == tags->encrypt_key) { HANDSHAKE_DEBUG(fprintf(stderr, "Cli: loadClnt: PB contains crypt key\n")); } } dpb.deleteWithTag(tags->password); } void ClntAuthBlock::extractDataFromPluginTo(CSTRING* to) { to->cstr_length = (ULONG) dataFromPlugin.getCount(); to->cstr_address = dataFromPlugin.begin(); to->cstr_allocated = 0; } void ClntAuthBlock::extractDataFromPluginTo(P_AUTH_CONT* to) { extractDataFromPluginTo(&to->p_data); PathName pluginName = getPluginName(); to->p_name.cstr_length = (ULONG) pluginName.length(); to->p_name.cstr_address = FB_NEW_POOL(*getDefaultMemoryPool()) UCHAR[to->p_name.cstr_length]; to->p_name.cstr_allocated = to->p_name.cstr_length; memcpy(to->p_name.cstr_address, pluginName.c_str(), to->p_name.cstr_length); HANDSHAKE_DEBUG(fprintf(stderr, "Cli: extractDataFromPluginTo: added plugin name (%d) and data (%d)\n", to->p_name.cstr_length, to->p_data.cstr_length)); if (firstTime) { to->p_list.cstr_length = (ULONG) pluginList.length(); to->p_list.cstr_address = (UCHAR*) pluginList.c_str(); to->p_list.cstr_allocated = 0; HANDSHAKE_DEBUG(fprintf(stderr, "Cli: extractDataFromPluginTo: added plugin list (%d len) to packet\n", to->p_list.cstr_length)); firstTime = false; } else { to->p_list.cstr_length = 0; } } const char* ClntAuthBlock::getLogin() { return cliUserName.nullStr(); } const char* ClntAuthBlock::getPassword() { return cliPassword.nullStr(); } IAuthBlock* ClntAuthBlock::getAuthBlock(CheckStatusWrapper* status) { return remAuthBlock; } const unsigned char* ClntAuthBlock::getData(unsigned int* length) { *length = (ULONG) dataForPlugin.getCount(); return *length ? dataForPlugin.begin() : NULL; } void ClntAuthBlock::putData(CheckStatusWrapper* status, unsigned int length, const void* data) { try { void* to = dataFromPlugin.getBuffer(length); memcpy(to, data, length); } catch (const Exception& ex) { ex.stuffException(status); } } bool ClntAuthBlock::checkPluginName(Firebird::PathName& nameToCheck) { Firebird::ParsedList parsed(pluginList); for (unsigned i = 0; i < parsed.getCount(); ++i) { if (parsed[i] == nameToCheck) { return true; } } return false; } Firebird::ICryptKey* ClntAuthBlock::newKey(CheckStatusWrapper* status) { status->init(); try { InternalCryptKey* k = FB_NEW InternalCryptKey; fb_assert(plugins.hasData()); k->keyName = plugins.name(); WIRECRYPT_DEBUG(fprintf(stderr, "Cli: newkey %s\n", k->keyName.c_str());) cryptKeys.add(k); return k; } catch (const Exception& ex) { ex.stuffException(status); } return NULL; } void ClntAuthBlock::tryNewKeys(rem_port* port) { for (unsigned k = cryptKeys.getCount(); k--; ) { if (port->tryNewKey(cryptKeys[k])) { releaseKeys(k); cryptKeys.clear(); return; } } cryptKeys.clear(); } void ClntAuthBlock::releaseKeys(unsigned from) { while (from < cryptKeys.getCount()) { delete cryptKeys[from++]; } } void ClntAuthBlock::createCryptCallback(Firebird::ICryptKeyCallback** callback) { if (*callback) return; *callback = clientCrypt.create(clntConfig); if (*callback) createdInterface = callback; } Firebird::ICryptKeyCallback* ClntAuthBlock::ClientCrypt::create(const Config* conf) { pluginItr.set(conf); return pluginItr.hasData() ? this : nullptr; } unsigned ClntAuthBlock::ClientCrypt::callback(unsigned dlen, const void* data, unsigned blen, void* buffer) { HANDSHAKE_DEBUG(fprintf(stderr, "dlen=%d blen=%d\n", dlen, blen)); int loop = 0; while (loop < 2) { for (; pluginItr.hasData(); pluginItr.next()) { if (!currentIface) { LocalStatus ls; CheckStatusWrapper st(&ls); HANDSHAKE_DEBUG(fprintf(stderr, "Try plugin %s\n", pluginItr.name())); currentIface = pluginItr.plugin()->chainHandle(&st); // if plugin does not support chaining - silently ignore it check(&st, isc_wish_list); HANDSHAKE_DEBUG(fprintf(stderr, "Use plugin %s, ptr=%p\n", pluginItr.name(), currentIface)); } // if we have an iface - try it if (currentIface) { unsigned retlen = currentIface->callback(dlen, data, blen, buffer); HANDSHAKE_DEBUG(fprintf(stderr, "Iface %p returned %d\n", currentIface, retlen)); if (retlen) return retlen; } // no success with iface - clear it // appropriate data structures to be released by plugin cleanup code currentIface = nullptr; } ++loop; // prepare iterator for next use pluginItr.rewind(); } // no luck with suggested data return 0; }