8
0
mirror of https://github.com/FirebirdSQL/firebird.git synced 2025-01-26 08:03:03 +01:00
firebird-mirror/src/remote/server.cpp
2011-04-17 05:00:56 +00:00

5689 lines
141 KiB
C++

/*
* PROGRAM: JRD Remote Server
* MODULE: server.cpp
* DESCRIPTION: Remote server
*
* 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.02.27 Claudio Valderrama: Fix SF Bug #509995.
*
* 2002.10.29 Sean Leyne - Removed obsolete "Netware" port
*
* 2002.10.30 Sean Leyne - Removed support for obsolete "PC_PLATFORM" define
*
*/
#include "firebird.h"
#include "../jrd/common.h"
#include <stdio.h>
#include <string.h>
#include "../jrd/ibase.h"
#include "../jrd/gdsassert.h"
#include "../remote/remote.h"
#include "../jrd/ThreadStart.h"
#include "../jrd/license.h"
#include "../jrd/file_params.h"
#include "../common/classes/timestamp.h"
#include "../remote/merge_proto.h"
#include "../remote/parse_proto.h"
#include "../remote/remot_proto.h"
#include "../remote/serve_proto.h"
#include "../remote/xdr_proto.h"
#ifdef WIN_NT
#include "../remote/os/win32/cntl_proto.h"
#include <stdlib.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include "../jrd/gds_proto.h"
#include "../jrd/isc_proto.h"
#include "../jrd/isc_s_proto.h"
#include "../jrd/thread_proto.h"
#include "../jrd/why_proto.h"
#include "../jrd/constants.h"
#include "../common/classes/init.h"
#include "../common/classes/semaphore.h"
#include "../common/classes/ClumpletWriter.h"
#include "../common/config/config.h"
#include "../common/utils_proto.h"
#ifdef DEBUG
#include "gen/iberror.h"
#endif
#include "../common/classes/FpeControl.h"
#include "../remote/proto_proto.h" // xdr_protocol_overhead()
#include "../jrd/scroll_cursors.h"
#include "../jrd/os/fbsyslog.h"
struct server_req_t : public Firebird::GlobalStorage
{
server_req_t* req_next;
server_req_t* req_chain;
RemPortPtr req_port;
PACKET req_send;
PACKET req_receive;
public:
server_req_t() : req_next(0), req_chain(0) { }
};
struct srvr : public Firebird::GlobalStorage
{
srvr* const srvr_next;
const rem_port* const srvr_parent_port;
const rem_port::rem_port_t srvr_port_type;
const USHORT srvr_flags;
public:
srvr(srvr* servers, rem_port* port, USHORT flags) :
srvr_next(servers), srvr_parent_port(port),
srvr_port_type(port->port_type), srvr_flags(flags)
{ }
};
namespace {
// this sets of parameters help use same functions
// for both services and databases attachments
struct ParametersSet
{
UCHAR address_path;
};
const ParametersSet dpbParam = {isc_dpb_address_path};
const ParametersSet spbParam = {isc_spb_address_path};
#ifdef WIN_NT
class GlobalPortLock
{
public:
explicit GlobalPortLock(int id)
: handle(INVALID_HANDLE_VALUE)
{
if (id)
{
TEXT mutex_name[MAXPATHLEN];
fb_utils::snprintf(mutex_name, sizeof(mutex_name), "FirebirdPortMutex%d", id);
fb_utils::prefix_kernel_object_name(mutex_name, sizeof(mutex_name));
if (!(handle = CreateMutex(ISC_get_security_desc(), FALSE, mutex_name)))
{
// MSDN: if the caller has limited access rights, the function will fail with
// ERROR_ACCESS_DENIED and the caller should use the OpenMutex function.
if (GetLastError() == ERROR_ACCESS_DENIED)
Firebird::system_call_failed::raise("CreateMutex - cannot open existing mutex");
else
Firebird::system_call_failed::raise("CreateMutex");
}
if (WaitForSingleObject(handle, INFINITE) == WAIT_FAILED)
{
Firebird::system_call_failed::raise("WaitForSingleObject");
}
}
}
~GlobalPortLock()
{
if (handle != INVALID_HANDLE_VALUE)
{
if (!ReleaseMutex(handle))
{
Firebird::system_call_failed::raise("ReleaseMutex");
}
CloseHandle(handle);
}
}
private:
HANDLE handle;
};
#else
class GlobalPortLock
{
public:
explicit GlobalPortLock(int id)
: fd(-1)
{
if (id)
{
Firebird::string firebirdPortMutex;
firebirdPortMutex.printf(PORT_FILE, id);
TEXT filename[MAXPATHLEN];
gds__prefix_lock(filename, firebirdPortMutex.c_str());
if ((fd = open(filename, O_WRONLY | O_CREAT, 0666)) < 0)
{
Firebird::system_call_failed::raise("open");
}
struct flock lock;
lock.l_type = F_WRLCK;
lock.l_whence = 0;
lock.l_start = 0;
lock.l_len = 0;
if (fcntl(fd, F_SETLK, &lock) == -1)
{
Firebird::system_call_failed::raise("fcntl");
}
}
}
~GlobalPortLock()
{
if (fd != -1)
{
struct flock lock;
lock.l_type = F_UNLCK;
lock.l_whence = 0;
lock.l_start = 0;
lock.l_len = 0;
if (fcntl(fd, F_SETLK, &lock) == -1)
{
Firebird::system_call_failed::raise("fcntl");
}
close(fd);
}
}
private:
int fd;
};
#endif
} // anonymous
static void free_request(server_req_t*);
static server_req_t* alloc_request();
static bool link_request(rem_port*, server_req_t*);
static bool accept_connection(rem_port*, P_CNCT*, PACKET*);
static ISC_STATUS allocate_statement(rem_port*, /*P_RLSE*,*/ PACKET*);
static void append_request_chain(server_req_t*, server_req_t**);
static void append_request_next(server_req_t*, server_req_t**);
static void attach_database(rem_port*, P_OP, P_ATCH*, PACKET*);
static void attach_service(rem_port*, P_ATCH*, PACKET*);
static void attach_database2(rem_port*, P_OP, const char*, int, const UCHAR*, int, PACKET*);
static void attach_service2(rem_port*, P_OP, const char*, int, const UCHAR*, int, PACKET*);
#ifdef TRUSTED_AUTH
static void trusted_auth(rem_port*, const P_TRAU*, PACKET*);
static bool canUseTrusted();
#endif
#ifdef NOT_USED_OR_REPLACED
static void aux_connect(rem_port*, P_REQ*, PACKET*);
#endif
static void aux_request(rem_port*, /*P_REQ*,*/ PACKET*);
static bool bad_port_context(ISC_STATUS*, Rdb*, const ISC_LONG);
static ISC_STATUS cancel_events(rem_port*, P_EVENT*, PACKET*);
static void addClumplets(Firebird::ClumpletWriter&, const ParametersSet&, const rem_port*);
static void cancel_operation(rem_port*, USHORT);
static bool check_request(Rrq*, USHORT, USHORT);
static USHORT check_statement_type(Rsr*);
#ifdef SCROLLABLE_CURSORS
static RMessage* dump_cache(Rrq::rrq_repeat*);
#endif
static bool get_next_msg_no(Rrq*, USHORT, USHORT*);
static Rtr* make_transaction(Rdb*, FB_API_HANDLE);
static bool process_packet(rem_port* port, PACKET* sendL, PACKET* receive, rem_port** result);
static void release_blob(Rbl*);
static void release_event(Rvnt*);
static void release_request(Rrq*);
static void release_statement(Rsr**);
static void release_sql_request(Rsr*);
static void release_transaction(Rtr*);
#ifdef SCROLLABLE_CURSORS
static RMessage* scroll_cache(Rrq::rrq_repeat*, USHORT *, ULONG *);
#endif
static void send_error(rem_port* port, PACKET* apacket, ISC_STATUS errcode);
static void server_ast(void*, USHORT, const UCHAR*);
static void set_server(rem_port*, USHORT);
static void success(ISC_STATUS*);
static int shut_server(const int, const int, void*);
static THREAD_ENTRY_DECLARE loopThread(THREAD_ENTRY_PARAM);
static void zap_packet(PACKET*, bool);
inline bool bad_db(ISC_STATUS* status_vector, Rdb* rdb)
{
return bad_port_context(status_vector, rdb, isc_bad_db_handle);
}
inline bool bad_service(ISC_STATUS* status_vector, Rdb* rdb)
{
return bad_port_context(status_vector, rdb, isc_bad_svc_handle);
}
class Worker
{
public:
static const int MAX_THREADS = MAX_SLONG;
static const int IDLE_TIMEOUT = 60;
Worker();
~Worker();
bool wait(int timeout = IDLE_TIMEOUT); // true is success, false if timeout
static bool wakeUp();
void setState(const bool active);
static void start(USHORT flags);
static int getCount() { return m_cntAll; }
static bool isShuttingDown() { return shutting_down; }
static void shutdown();
private:
Worker* m_next;
Worker* m_prev;
Firebird::Semaphore m_sem;
bool m_active;
#ifdef DEV_BUILD
FB_THREAD_ID m_tid;
#endif
void remove();
void insert(const bool active);
bool inList() const;
static void wakeUpAll();
static Worker* m_activeWorkers;
static Worker* m_idleWorkers;
static Firebird::GlobalPtr<Firebird::Mutex> m_mutex;
static int m_cntAll;
static int m_cntIdle;
static bool shutting_down;
};
Worker* Worker::m_activeWorkers = NULL;
Worker* Worker::m_idleWorkers = NULL;
Firebird::GlobalPtr<Firebird::Mutex> Worker::m_mutex;
int Worker::m_cntAll = 0;
int Worker::m_cntIdle = 0;
bool Worker::shutting_down = false;
static Firebird::GlobalPtr<Firebird::Mutex> request_que_mutex;
static server_req_t* request_que = NULL;
static server_req_t* free_requests = NULL;
static server_req_t* active_requests = NULL;
static int ports_active = 0; // length of active_requests
static int ports_pending = 0; // length of request_que
static Firebird::GlobalPtr<Firebird::Mutex> servers_mutex;
static srvr* servers = NULL;
static Firebird::AtomicCounter cntServers;
static bool server_shutdown = false;
static const UCHAR request_info[] =
{
isc_info_state,
isc_info_message_number,
isc_info_end
};
static const UCHAR sql_info[] =
{
isc_info_sql_stmt_type,
isc_info_sql_batch_fetch
};
#define GDS_DSQL_ALLOCATE isc_dsql_allocate_statement
#define GDS_DSQL_EXECUTE isc_dsql_execute2_m
#define GDS_DSQL_EXECUTE_IMMED isc_dsql_exec_immed3_m
#define GDS_DSQL_FETCH isc_dsql_fetch_m
#define GDS_DSQL_FREE isc_dsql_free_statement
#define GDS_DSQL_INSERT isc_dsql_insert_m
#define GDS_DSQL_PREPARE isc_dsql_prepare_m
#define GDS_DSQL_SET_CURSOR isc_dsql_set_cursor_name
#define GDS_DSQL_SQL_INFO isc_dsql_sql_info
void SRVR_main(rem_port* main_port, USHORT flags)
{
/**************************************
*
* S R V R _ m a i n
*
**************************************
*
* Functional description
* Main entrypoint of server.
*
**************************************/
Firebird::FpeControl::maskAll();
// Setup context pool for main thread
Firebird::ContextPoolHolder mainThreadContext(getDefaultMemoryPool());
PACKET send, receive;
zap_packet(&receive, true);
zap_packet(&send, true);
set_server(main_port, flags);
while (true)
{
// Note: The following is cloned in server other SRVR_main instances.
ISC_STATUS_ARRAY status = {0};
main_port->port_status_vector = status;
rem_port* port = main_port->receive(&receive);
if (!port) {
break;
}
if (!process_packet(port, &send, &receive, &port)) {
break;
}
}
}
static void free_request(server_req_t* request)
{
/**************************************
*
* f r e e _ r e q u e s t
*
**************************************
*
* Functional description
*
**************************************/
Firebird::MutexLockGuard queGuard(request_que_mutex);
request->req_port = 0;
request->req_next = free_requests;
free_requests = request;
}
static server_req_t* alloc_request()
{
/**************************************
*
* a l l o c _ r e q u e s t
*
**************************************
*
* Functional description
* Get request block from the free blocks list,
* if empty - allocate the new one.
*
**************************************/
Firebird::MutexEnsureUnlock queGuard(request_que_mutex);
queGuard.enter();
server_req_t* request = free_requests;
#if defined(DEV_BUILD) && defined(DEBUG)
int request_count = 0;
#endif
// Allocate a memory block to store the request in
if (request)
{
free_requests = request->req_next;
}
else
{
// No block on the free list - allocate some new memory
for (;;)
{
try
{
request = new server_req_t;
break;
}
catch (const Firebird::BadAlloc&)
{ }
#if defined(DEV_BUILD) && defined(DEBUG)
if (request_count++ > 4)
Firebird::BadAlloc::raise();
#endif
// System is out of memory, let's delay processing this
// request and hope another thread will free memory or
// request blocks that we can then use.
queGuard.leave();
THREAD_SLEEP(1 * 1000);
queGuard.enter();
}
zap_packet(&request->req_send, true);
zap_packet(&request->req_receive, true);
#ifdef DEBUG_REMOTE_MEMORY
printf("alloc_request allocate request %x\n", request);
#endif
}
request->req_next = NULL;
request->req_chain = NULL;
return request;
}
static bool link_request(rem_port* port, server_req_t* request)
{
/**************************************
*
* l i n k _ r e q u e s t
*
**************************************
*
* Functional description
* Search for a port in a queue,
* if found - append new request to it.
*
**************************************/
const P_OP operation = request->req_receive.p_operation;
server_req_t* queue;
{ // request_que_mutex scope
Firebird::MutexLockGuard queGuard(request_que_mutex);
bool active = true;
queue = active_requests;
while (true)
{
for (; queue; queue = queue->req_next)
{
if (queue->req_port == port)
{
// Don't queue a dummy keepalive packet if there is a request on this port
if (operation == op_dummy)
{
free_request(request);
return true;
}
append_request_chain(request, &queue->req_chain);
#ifdef DEBUG_REMOTE_MEMORY
printf("link_request %s request_queued %d\n",
active ? "ACTIVE" : "PENDING", port->port_requests_queued.value());
fflush(stdout);
#endif
break;
}
}
if (queue || !active)
break;
queue = request_que;
active = false;
}
if (!queue) {
append_request_next(request, &request_que);
}
} // request_que_mutex scope
++port->port_requests_queued;
if (queue)
{
if (operation == op_exit || operation == op_disconnect)
cancel_operation(port, fb_cancel_raise);
return true;
}
return false;
}
void SRVR_multi_thread( rem_port* main_port, USHORT flags)
{
/**************************************
*
* S R V R _ m u l t i _ t h r e a d
*
**************************************
*
* Functional description
* Multi-threaded flavor of server.
*
**************************************/
// Check for errors/missing firebird.conf
const char* anyError = Config::getMessage();
if (anyError)
{
Firebird::Syslog::Record(Firebird::Syslog::Error, anyError);
return;
}
server_req_t* request = NULL;
RemPortPtr port; // Was volatile PORT port = NULL;
// hold reference on main_port to free asyncPacket before deletion of port
RemPortPtr mainPortRef(main_port);
PACKET asyncPacket;
zap_packet(&asyncPacket, true);
++cntServers;
try
{
set_server(main_port, flags);
// We need to have this error handler as there is so much underlaying code
// that is depending on it being there. The expected failure
// that can occur in the call paths from here is out-of-memory
// and it is unknown if other failures can get us here
// Failures can occur from set_server as well as RECEIVE
//
// Note that if a failure DOES occur, we reenter the loop and try to continue
// operations. This is important as this is the main receive loop for
// new incoming requests, if we exit here no new requests can come to the
// server.
try
{
const size_t MAX_PACKET_SIZE = MAX_SSHORT;
const SSHORT bufSize = MIN(main_port->port_buff_size, MAX_PACKET_SIZE);
Firebird::UCharBuffer packet_buffer;
UCHAR* const buffer = packet_buffer.getBuffer(bufSize);
// When this loop exits, the server will no longer receive requests
while (true)
{
SSHORT dataSize;
// We have a request block - now get some information to stick into it
const bool ok = main_port->select_multi(buffer, bufSize, &dataSize, port);
if (!port)
{
if ((main_port->port_server_flags & SRVR_multi_client) && !server_shutdown)
{
gds__log("SRVR_multi_thread/RECEIVE: error on main_port, shutting down");
}
break;
}
if (dataSize)
{
const SSHORT asyncSize = port->asyncReceive(&asyncPacket, buffer, dataSize);
if (asyncSize == dataSize)
{
port = NULL;
continue;
}
dataSize -= asyncSize;
Firebird::RefMutexGuard queGuard(*port->port_que_sync);
memcpy(port->port_queue.add().getBuffer(dataSize), buffer + asyncSize, dataSize);
}
Firebird::RefMutexEnsureUnlock portGuard(*port->port_sync);
const bool portLocked = portGuard.tryEnter();
// Handle bytes received only if port is currently idle and has no requests
// queued or if it is disconnect (dataSize == 0). Else let loopThread
// handle these bytes
if ((portLocked && !port->port_requests_queued.value()) || !dataSize)
{
// Allocate a memory block to store the request in
request = alloc_request();
if (dataSize)
{
fb_assert(portLocked);
fb_assert(port->port_requests_queued.value() == 0);
Firebird::RefMutexGuard queGuard(*port->port_que_sync);
const rem_port::RecvQueState recvState = port->getRecvState();
port->receive(&request->req_receive);
if (request->req_receive.p_operation == op_partial)
{
port->setRecvState(recvState);
portGuard.leave();
free_request(request);
request = 0;
port = 0;
continue;
}
if (!port->haveRecvData())
{
port->clearRecvQue();
}
}
else
{
request->req_receive.p_operation = ok ? op_dummy : op_exit;
}
request->req_port = port;
if (portLocked)
{
portGuard.leave();
}
// link_request will increment port_requests_queued. Port is not locked
// at this point but it is safe because :
// - port_requests_queued is atomic counter
// - the only place where we check its value is at the same thread (see above)
// - other thread where port_requests_queued is changed is loopThread and
// there port is locked
// - same port can be accessed no more than by two threads simultaneously -
// this one and some instance of loopThread
if (!link_request(port, request))
{
// Request was assigned to the waiting queue so we need to wake up a
// thread to handle it
REMOTE_TRACE(("Enqueue request %p", request));
request = 0;
#ifdef DEBUG_REMOTE_MEMORY
printf("SRVR_multi_thread APPEND_PENDING request_queued %d\n",
port->port_requests_queued.value());
fflush(stdout);
#endif
Worker::start(flags);
}
request = 0;
}
port = 0;
}
Worker::shutdown();
// All worker threads are stopped and will never run any more
// Disconnect remaining ports gracefully
rem_port* run_port = main_port;
while (run_port)
{
rem_port* current_port = run_port; // important order of operations
run_port = run_port->port_next; // disconnect() can modify linked list of ports
if (!(current_port->port_flags & PORT_disconnect))
current_port->disconnect(NULL, NULL);
}
}
catch (const Firebird::Exception& e)
{
gds__log("SRVR_multi_thread: shutting down due to unhandled exception");
ISC_STATUS_ARRAY status_vector;
Firebird::stuff_exception(status_vector, e);
gds__log_status(0, status_vector);
// If we got as far as having a port allocated before the error, disconnect it
// gracefully.
if (port)
{
/*
#if defined(DEV_BUILD) && defined(DEBUG)
ConsolePrintf("%%ISERVER-F-NOPORT, no port in a storm\r\n");
#endif
*/
gds__log("SRVR_multi_thread: forcefully disconnecting a port");
// To handle recursion within the error handler
try
{
// If we have a port, request really should be non-null, but just in case ...
if (request != NULL)
{
// Send client a real status indication of why we disconnected them
// Note that send_response() can post errors that wind up in this same handler
/*
#if defined(DEV_BUILD) && defined(DEBUG)
ConsolePrintf("%%ISERVER-F-NOMEM, virtual memory exhausted\r\n");
#endif
*/
port->send_response(&request->req_send, 0, 0, status_vector, false);
port->disconnect(&request->req_send, &request->req_receive);
}
else
{
// Can't tell the client much, just make 'em go away. Their side should detect
// a network error
port->disconnect(NULL, NULL);
}
port = NULL;
} // try
catch (const Firebird::Exception&)
{
port->disconnect(NULL, NULL);
port = NULL;
}
}
// There was an error in the processing of the request, if we have allocated
// a request, free it up and continue.
if (request != NULL)
{
free_request(request);
request = NULL;
}
}
if (main_port->port_async_receive)
{
REMOTE_free_packet(main_port->port_async_receive, &asyncPacket);
}
}
catch (const Firebird::Exception&)
{
// Some kind of unhandled error occurred during server setup. In lieu
// of anything we CAN do, log something (and we might be so hosed
// we can't log anything) and give up.
// The likely error here is out-of-memory.
gds__log("SRVR_multi_thread: error during startup, shutting down");
}
--cntServers;
}
static bool accept_connection(rem_port* port, P_CNCT* connect, PACKET* send)
{
/**************************************
*
* a c c e p t _ c o n n e c t i o n
*
**************************************
*
* Functional description
* Process a connect packet.
*
**************************************/
// Accept the physical connection
send->p_operation = op_reject;
P_ACPT* accept = &send->p_acpt;
if (!port->accept(connect))
{
port->send(send);
return false;
}
// Select the most appropriate protocol (this will get smarter)
P_ARCH architecture = arch_generic;
USHORT version = 0;
USHORT type = 0;
bool accepted = false;
USHORT weight = 0;
const p_cnct::p_cnct_repeat* protocol = connect->p_cnct_versions;
for (const p_cnct::p_cnct_repeat* const end = protocol + connect->p_cnct_count;
protocol < end; protocol++)
{
if ((protocol->p_cnct_version == PROTOCOL_VERSION3 ||
protocol->p_cnct_version == PROTOCOL_VERSION4 ||
protocol->p_cnct_version == PROTOCOL_VERSION5 ||
protocol->p_cnct_version == PROTOCOL_VERSION6 ||
protocol->p_cnct_version == PROTOCOL_VERSION7 ||
protocol->p_cnct_version == PROTOCOL_VERSION8 ||
protocol->p_cnct_version == PROTOCOL_VERSION9 ||
protocol->p_cnct_version == PROTOCOL_VERSION10 ||
protocol->p_cnct_version == PROTOCOL_VERSION11 ||
protocol->p_cnct_version == PROTOCOL_VERSION12
#ifdef SCROLLABLE_CURSORS
|| protocol->p_cnct_version == PROTOCOL_SCROLLABLE_CURSORS
#endif
) &&
(protocol->p_cnct_architecture == arch_generic ||
protocol->p_cnct_architecture == ARCHITECTURE) &&
protocol->p_cnct_weight >= weight)
{
accepted = true;
weight = protocol->p_cnct_weight;
version = protocol->p_cnct_version;
architecture = protocol->p_cnct_architecture;
type = MIN(protocol->p_cnct_max_type, ptype_lazy_send);
send->p_operation = op_accept;
}
}
// Send off out gracious acceptance or flag rejection
if (!accepted)
{
port->send(send);
return false;
}
accept->p_acpt_version = port->port_protocol = version;
accept->p_acpt_architecture = architecture;
accept->p_acpt_type = type;
// and modify the version string to reflect the chosen protocol
Firebird::string buffer;
buffer.printf("%s/P%d", port->port_version->str_data, port->port_protocol & FB_PROTOCOL_MASK);
delete port->port_version;
port->port_version = REMOTE_make_string(buffer.c_str());
if (architecture == ARCHITECTURE)
port->port_flags |= PORT_symmetric;
if (type == ptype_rpc)
port->port_flags |= PORT_rpc;
if (type != ptype_out_of_band)
port->port_flags |= PORT_no_oob;
if (type == ptype_lazy_send)
port->port_flags |= PORT_lazy;
port->send(send);
return true;
}
static ISC_STATUS allocate_statement( rem_port* port, /*P_RLSE* allocate,*/ PACKET* send)
{
/**************************************
*
* a l l o c a t e _ s t a t e m e n t
*
**************************************
*
* Functional description
* Allocate a statement handle.
*
**************************************/
ISC_STATUS_ARRAY status_vector;
Rdb* rdb = port->port_context;
if (bad_db(status_vector, rdb))
{
return port->send_response(send, 0, 0, status_vector, true);
}
FB_API_HANDLE handle = 0;
GDS_DSQL_ALLOCATE(status_vector, &rdb->rdb_handle, &handle);
OBJCT object;
if (status_vector[1])
object = 0;
else
{
// Allocate SQL request block
Rsr* statement = new Rsr;
statement->rsr_rdb = rdb;
statement->rsr_handle = handle;
if (statement->rsr_id = port->get_id(statement))
{
object = statement->rsr_id;
statement->rsr_next = rdb->rdb_sql_requests;
rdb->rdb_sql_requests = statement;
}
else
{
object = 0;
GDS_DSQL_FREE(status_vector, &statement->rsr_handle, DSQL_drop);
delete statement;
status_vector[0] = isc_arg_gds;
status_vector[1] = isc_too_many_handles;
status_vector[2] = isc_arg_end;
}
}
return port->send_response(send, object, 0, status_vector, true);
}
static void append_request_chain(server_req_t* request, server_req_t** que_inst)
{
/**************************************
*
* a p p e n d _ r e q u e s t _ c h a i n
*
**************************************
*
* Functional description
* Traverse using req_chain ptr and append
* a request at the end of a que.
* Return count of pending requests.
*
**************************************/
Firebird::MutexLockGuard queGuard(request_que_mutex);
while (*que_inst)
que_inst = &(*que_inst)->req_chain;
*que_inst = request;
}
static void append_request_next(server_req_t* request, server_req_t** que_inst)
{
/**************************************
*
* a p p e n d _ r e q u e s t _ n e x t
*
**************************************
*
* Functional description
* Traverse using req_next ptr and append
* a request at the end of a que.
* Return count of pending requests.
*
**************************************/
Firebird::MutexLockGuard queGuard(request_que_mutex);
while (*que_inst)
que_inst = &(*que_inst)->req_next;
*que_inst = request;
ports_pending++;
}
static void addClumplets(Firebird::ClumpletWriter& dpb_buffer,
const ParametersSet& par,
const rem_port* port)
{
/**************************************
*
* a d d C l u m p l e t s
*
**************************************
*
* Functional description
* Insert remote endpoint data into DPB address stack
*
**************************************/
Firebird::ClumpletWriter address_stack_buffer(Firebird::ClumpletReader::UnTagged, MAX_UCHAR - 2);
if (dpb_buffer.find(par.address_path))
{
address_stack_buffer.reset(dpb_buffer.getBytes(), dpb_buffer.getClumpLength());
dpb_buffer.deleteClumplet();
}
Firebird::ClumpletWriter address_record(Firebird::ClumpletReader::UnTagged, MAX_UCHAR - 2);
if (port->port_protocol_str)
{
address_record.insertString(isc_dpb_addr_protocol,
port->port_protocol_str->str_data, port->port_protocol_str->str_length);
}
if (port->port_address_str)
{
address_record.insertString(isc_dpb_addr_endpoint,
port->port_address_str->str_data, port->port_address_str->str_length);
}
// We always insert remote address descriptor as a first element
// of appropriate clumplet so user cannot fake it and engine may somewhat trust it.
fb_assert(address_stack_buffer.getCurOffset() == 0);
address_stack_buffer.insertBytes(isc_dpb_address,
address_record.getBuffer(), address_record.getBufferLength());
dpb_buffer.insertBytes(par.address_path, address_stack_buffer.getBuffer(),
address_stack_buffer.getBufferLength());
// Remove all remaining isc_*pb_address_path clumplets.
// This is the security feature to prevent user from faking remote address
// by passing multiple address_path clumplets. Engine assumes that
// dpb contains no more than one address_path clumplet and for
// clients coming via remote interface it can trust the first address from
// address stack. Clients acessing database directly can do whatever they
// want with the engine including faking the source address, not much we
// can do about it.
while (!dpb_buffer.isEof())
{
if (dpb_buffer.getClumpTag() == par.address_path)
dpb_buffer.deleteClumplet();
else
dpb_buffer.moveNext();
}
}
static void attach_database(rem_port* port, P_OP operation, P_ATCH* attach, PACKET* send)
{
/**************************************
*
* a t t a c h _ d a t a b a s e
*
**************************************
*
* Functional description
* Process an attach or create packet.
*
**************************************/
const char* file = reinterpret_cast<const char*>(attach->p_atch_file.cstr_address);
const USHORT l = attach->p_atch_file.cstr_length;
const UCHAR* dpb = attach->p_atch_dpb.cstr_address;
const USHORT dl = attach->p_atch_dpb.cstr_length;
Firebird::ClumpletWriter dpb_buffer(Firebird::ClumpletReader::Tagged, MAX_SSHORT);
if (dl)
dpb_buffer.reset(dpb, dl);
else
dpb_buffer.reset(isc_dpb_version1);
// remove trusted role if present (security measure)
dpb_buffer.deleteWithTag(isc_dpb_trusted_role);
#ifdef TRUSTED_AUTH
// Do we need trusted authentication?
if (dpb_buffer.find(isc_dpb_trusted_auth))
{
try
{
// extract trusted authentication data from dpb
AuthSspi::DataHolder data;
memcpy(data.getBuffer(dpb_buffer.getClumpLength()),
dpb_buffer.getBytes(), dpb_buffer.getClumpLength());
dpb_buffer.deleteClumplet();
// remove extra trusted auth if present (security measure)
dpb_buffer.deleteWithTag(isc_dpb_trusted_auth);
if (canUseTrusted() && port->port_protocol >= PROTOCOL_VERSION11)
{
port->port_trusted_auth =
new ServerAuth(file, l, dpb_buffer, attach_database2, operation);
AuthSspi* authSspi = port->port_trusted_auth->authSspi;
if (authSspi->accept(data) && authSspi->isActive())
{
send->p_operation = op_trusted_auth;
cstring& s = send->p_trau.p_trau_data;
s.cstr_allocated = 0;
s.cstr_length = data.getCount();
s.cstr_address = data.begin();
port->send(send);
return;
}
}
}
catch (const Firebird::status_exception& e)
{
ISC_STATUS_ARRAY status_vector;
Firebird::stuff_exception(status_vector, e);
delete port->port_trusted_auth;
port->port_trusted_auth = 0;
port->send_response(send, 0, 0, status_vector, false);
return;
}
}
#else
// remove trusted auth if present (security measure)
dpb_buffer.deleteWithTag(isc_dpb_trusted_auth);
#endif // TRUSTED_AUTH
attach_database2(port, operation, file, l, dpb_buffer.getBuffer(),
dpb_buffer.getBufferLength(), send);
}
static void attach_database2(rem_port* port,
P_OP operation,
const char* file,
int l,
const UCHAR* dpb,
int dl,
PACKET* send)
{
send->p_operation = op_accept;
FB_API_HANDLE handle = 0;
Firebird::ClumpletWriter dpb_buffer(Firebird::ClumpletReader::Tagged, MAX_SSHORT);
if (dl)
dpb_buffer.reset(dpb, dl);
else
dpb_buffer.reset(isc_dpb_version1);
#ifdef TRUSTED_AUTH
// If we have trusted authentication, append it to database parameter block
if (port->port_trusted_auth)
{
AuthSspi* authSspi = port->port_trusted_auth->authSspi;
Firebird::string trustedUserName;
bool wheel = false;
if (authSspi->getLogin(trustedUserName, wheel))
{
dpb_buffer.insertString(isc_dpb_trusted_auth, trustedUserName);
if (wheel && !dpb_buffer.find(isc_dpb_sql_role_name))
{
dpb_buffer.insertString(isc_dpb_trusted_role, ADMIN_ROLE, strlen(ADMIN_ROLE));
}
}
}
#endif // TRUSTED_AUTH
// If we have user identification, append it to database parameter block
rem_str* string = port->port_user_name;
if (string)
{
dpb_buffer.setCurOffset(dpb_buffer.getBufferLength());
dpb_buffer.insertString(isc_dpb_sys_user_name, string->str_data, string->str_length);
}
// Now insert additional clumplets into dpb
addClumplets(dpb_buffer, dpbParam, port);
// Disable remote gsec attachments */
dpb_buffer.deleteWithTag(isc_dpb_gsec_attach);
dpb_buffer.deleteWithTag(isc_dpb_sec_attach);
// See if user has specified parameters relevant to the connection,
// they will be stuffed in the DPB if so.
REMOTE_get_timeout_params(port, &dpb_buffer);
dpb = dpb_buffer.getBuffer();
dl = dpb_buffer.getBufferLength();
ISC_STATUS_ARRAY status_vector;
if (operation == op_attach)
{
isc_attach_database(status_vector, l, file,
&handle, dl, reinterpret_cast<const char*>(dpb));
}
else
{
isc_create_database(status_vector, l, file,
&handle, dl, reinterpret_cast<const char*>(dpb), 0);
}
if (!status_vector[1])
{
Rdb* rdb = new Rdb;
if (rdb)
{
port->port_context = rdb;
#ifdef DEBUG_REMOTE_MEMORY
printf("attach_databases(server) allocate rdb %x\n", rdb);
#endif
rdb->rdb_port = port;
rdb->rdb_handle = handle;
}
else
{
status_vector[0] = isc_arg_gds;
status_vector[1] = isc_virmemexh;
status_vector[2] = isc_arg_end;
}
}
port->send_response(send, 0, 0, status_vector, false);
#ifdef TRUSTED_AUTH
delete port->port_trusted_auth;
port->port_trusted_auth = 0;
#endif
}
#ifdef NOT_USED_OR_REPLACED
static void aux_connect( rem_port* port, P_REQ * request, PACKET* send)
{
/**************************************
*
* a u x _ c o n n e c t
*
**************************************
*
* Functional description
* We're receive a auxiliary connection on the main communications
* channel. Accept connection and reply politely.
*
* 13-Mar-2004, Nickolay Samofatov
* This code is 64-bit unsafe, unused and also has a security hole, thus I disable it for now
*
**************************************/
port->connect(0);
rem_port* partner = (rem_port*) request->p_req_partner;
partner->port_async = port;
}
#endif
static void aux_request( rem_port* port, /*P_REQ* request,*/ PACKET* send)
{
/**************************************
*
* a u x _ r e q u e s t
*
**************************************
*
* Functional description
* Other guy wants to establish a secondary connection.
* Humor him.
*
**************************************/
ISC_STATUS_ARRAY status_vector;
// save the port status vector
ISC_STATUS* const save_status = port->port_status_vector;
port->port_status_vector = status_vector;
success(status_vector);
Rdb* const rdb = port->port_context;
if (bad_db(status_vector, rdb))
{
port->send_response(send, 0, 0, status_vector, false);
return;
}
// This buffer is used by INET and WNET transports
// to return the server identification string
UCHAR buffer[BUFFER_TINY];
send->p_resp.p_resp_data.cstr_address = buffer;
// To be retrieved via an overloaded class member once our ports become real classes
const int aux_port_id = (port->port_type == rem_port::INET) ? Config::getRemoteAuxPort() : 0;
GlobalPortLock auxPortLock(aux_port_id);
rem_port* const aux_port = port->request(send);
port->send_response(send, rdb->rdb_id, send->p_resp.p_resp_data.cstr_length, status_vector, false);
if (status_vector[1])
{
// restore the port status vector
port->port_status_vector = save_status;
return;
}
if (aux_port)
{
ISC_STATUS* const save_status2 = aux_port->port_status_vector;
aux_port->port_status_vector = status_vector;
if (aux_port->connect(send))
{
aux_port->port_context = rdb;
aux_port->port_status_vector = save_status2;
}
else
{
iscLogStatus(NULL, aux_port->port_status_vector);
fb_assert(port->port_async == aux_port);
port->port_async = NULL;
aux_port->disconnect();
}
}
// restore the port status vector
port->port_status_vector = save_status;
}
static bool bad_port_context(ISC_STATUS* status_vector, Rdb* rdb, const ISC_LONG error)
{
/**************************************
*
* b a d _ p o r t _ c o n t e x t
*
**************************************
*
* Functional description
* Check rdb pointer, in case of error create status vector
*
**************************************/
if (rdb)
{
return false;
}
status_vector[0] = isc_arg_gds;
status_vector[1] = error;
status_vector[2] = isc_arg_end;
return true;
}
static ISC_STATUS cancel_events( rem_port* port, P_EVENT * stuff, PACKET* send)
{
/**************************************
*
* c a n c e l _ e v e n t s
*
**************************************
*
* Functional description
* Cancel events.
*
**************************************/
ISC_STATUS_ARRAY status_vector;
success(status_vector);
// Which database ?
Rdb* rdb = port->port_context;
if (bad_db(status_vector, rdb))
{
return port->send_response(send, 0, 0, status_vector, false);
}
// Find the event
Rvnt* event;
for (event = rdb->rdb_events; event; event = event->rvnt_next)
{
if (event->rvnt_rid == stuff->p_event_rid)
break;
}
// If no event found, pretend it was cancelled
if (!event) {
return port->send_response(send, 0, 0, status_vector, false);
}
// cancel the event
if (event->rvnt_id) {
isc_cancel_events(status_vector, &rdb->rdb_handle, &event->rvnt_id);
}
// zero event info
event->rvnt_id = 0L;
event->rvnt_rid = 0L;
event->rvnt_ast = 0;
// return response
return port->send_response(send, 0, 0, status_vector, false);
}
static void cancel_operation( rem_port* port, USHORT kind)
{
/**************************************
*
* c a n c e l _ o p e r a t i o n
*
**************************************
*
* Functional description
* Flag a running operation for cancel.
* Service operations are not currently
* able to be canceled.
*
**************************************/
Rdb* rdb;
if ((port->port_flags & (PORT_async | PORT_disconnect)) || !(rdb = port->port_context))
{
return;
}
if (rdb->rdb_handle)
{
if (!(rdb->rdb_flags & Rdb::SERVICE))
{
ISC_STATUS_ARRAY status_vector;
fb_cancel_operation(status_vector, &rdb->rdb_handle, kind);
}
}
}
static bool check_request(Rrq* request, USHORT incarnation, USHORT msg_number)
{
/**************************************
*
* c h e c k _ r e q u e s t
*
**************************************
*
* Functional description
* Check to see if a request is ready to send us a particular
* message. If so, return true, otherwise false.
*
**************************************/
USHORT n;
if (!get_next_msg_no(request, incarnation, &n))
return false;
return (msg_number == n);
}
static USHORT check_statement_type( Rsr* statement)
{
/**************************************
*
* c h e c k _ s t a t e m e n t _ t y p e
*
**************************************
*
* Functional description
* Return the type of SQL statement.
*
**************************************/
UCHAR buffer[16];
ISC_STATUS_ARRAY local_status;
USHORT ret = 0;
bool done = false;
if (!GDS_DSQL_SQL_INFO(local_status, &statement->rsr_handle,
sizeof(sql_info), (const SCHAR*) sql_info,
sizeof(buffer), reinterpret_cast<char*>(buffer)))
{
for (const UCHAR* info = buffer; (*info != isc_info_end) && !done;)
{
const USHORT l = (USHORT) gds__vax_integer(info + 1, 2);
const USHORT type = (USHORT) gds__vax_integer(info + 3, l);
switch (*info)
{
case isc_info_sql_stmt_type:
switch (type)
{
case isc_info_sql_stmt_get_segment:
case isc_info_sql_stmt_put_segment:
ret |= STMT_BLOB;
break;
case isc_info_sql_stmt_select:
case isc_info_sql_stmt_select_for_upd:
ret |= STMT_DEFER_EXECUTE;
break;
}
break;
case isc_info_sql_batch_fetch:
if (type == 0)
ret |= STMT_NO_BATCH;
break;
case isc_info_error:
case isc_info_truncated:
done = true;
break;
}
info += 3 + l;
}
}
return ret;
}
ISC_STATUS rem_port::compile(P_CMPL* compileL, PACKET* sendL)
{
/**************************************
*
* c o m p i l e
*
**************************************
*
* Functional description
* Compile and request.
*
**************************************/
ISC_STATUS_ARRAY status_vector;
Rdb* rdb = this->port_context;
if (bad_db(status_vector, rdb))
{
return this->send_response(sendL, 0, 0, status_vector, false);
}
FB_API_HANDLE handle = 0;
const UCHAR* blr = compileL->p_cmpl_blr.cstr_address;
const USHORT blr_length = compileL->p_cmpl_blr.cstr_length;
isc_compile_request(status_vector, &rdb->rdb_handle, &handle, blr_length,
reinterpret_cast<const char*>(blr));
if (status_vector[1])
return this->send_response(sendL, 0, 0, status_vector, false);
// Parse the request to find the messages
RMessage* message = PARSE_messages(blr, blr_length);
USHORT max_msg = 0;
RMessage* next;
for (next = message; next; next = next->msg_next)
max_msg = MAX(max_msg, next->msg_number);
// Allocate block and merge into data structures
Rrq* requestL = new Rrq(max_msg + 1);
#ifdef DEBUG_REMOTE_MEMORY
printf("compile(server) allocate request %x\n", request);
#endif
requestL->rrq_handle = handle;
requestL->rrq_rdb = rdb;
requestL->rrq_max_msg = max_msg;
OBJCT object = 0;
if (requestL->rrq_id = this->get_id(requestL))
{
object = requestL->rrq_id;
requestL->rrq_next = rdb->rdb_requests;
rdb->rdb_requests = requestL;
}
else
{
isc_release_request(status_vector, &requestL->rrq_handle);
delete requestL;
status_vector[0] = isc_arg_gds;
status_vector[1] = isc_too_many_handles;
status_vector[2] = isc_arg_end;
return this->send_response(sendL, 0, 0, status_vector, false);
}
while (message)
{
next = message->msg_next;
message->msg_next = message;
#ifdef SCROLLABLE_CURSORS
message->msg_prior = message;
#endif
Rrq::rrq_repeat* tail = &requestL->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;
message = next;
}
return this->send_response(sendL, object, 0, status_vector, false);
}
ISC_STATUS rem_port::ddl(P_DDL* ddlL, PACKET* sendL)
{
/**************************************
*
* d d l
*
**************************************
*
* Functional description
* Execute isc_ddl call.
*
**************************************/
ISC_STATUS_ARRAY status_vector;
Rtr* transaction;
getHandle(transaction, ddlL->p_ddl_transaction);
Rdb* rdb = this->port_context;
if (bad_db(status_vector, rdb))
{
return this->send_response(sendL, 0, 0, status_vector, false);
}
const UCHAR* blr = ddlL->p_ddl_blr.cstr_address;
const USHORT blr_length = ddlL->p_ddl_blr.cstr_length;
isc_ddl(status_vector, &rdb->rdb_handle, &transaction->rtr_handle,
blr_length, reinterpret_cast<const char*>(blr));
return this->send_response(sendL, 0, 0, status_vector, false);
}
void rem_port::disconnect(PACKET* sendL, PACKET* receiveL)
{
/**************************************
*
* d i s c o n n e c t
*
**************************************
*
* Functional description
* We've lost the connection to the parent. Stop everything.
*
**************************************/
Rdb* rdb = this->port_context;
if (this->port_flags & PORT_async)
{
if (rdb && rdb->rdb_port && !(rdb->rdb_port->port_flags & PORT_disconnect))
{
PACKET *packet = &rdb->rdb_packet;
packet->p_operation = op_dummy;
rdb->rdb_port->send(packet);
}
return;
}
this->port_flags |= PORT_disconnect;
if (!rdb)
{
REMOTE_free_packet(this, sendL);
REMOTE_free_packet(this, receiveL);
this->disconnect();
return;
}
// For WNET and XNET we should send dummy op_disconnect packet
// to wakeup async port handling events on client side.
// For INET it's not necessary because INET client's async port
// wakes up while server performs shutdown(socket) call on its async port.
// See interface.cpp - event_thread().
PACKET *packet = &rdb->rdb_packet;
if (this->port_async)
{
if ((this->port_type == rem_port::XNET) || (this->port_type == rem_port::PIPE))
{
packet->p_operation = op_disconnect;
this->port_async->send(packet);
}
this->port_async->port_flags |= PORT_disconnect;
}
if (rdb->rdb_handle)
{
ISC_STATUS_ARRAY status_vector;
if (!(rdb->rdb_flags & Rdb::SERVICE))
{
// Prevent a pending or spurious cancel from aborting
// a good, clean detach from the database.
fb_cancel_operation(status_vector, &rdb->rdb_handle, fb_cancel_disable);
while (rdb->rdb_requests)
release_request(rdb->rdb_requests);
while (rdb->rdb_sql_requests)
release_sql_request(rdb->rdb_sql_requests);
Rtr* transaction;
while (transaction = rdb->rdb_transactions)
{
if (!transaction->rtr_limbo)
isc_rollback_transaction(status_vector, &transaction->rtr_handle);
#ifdef SUPERSERVER
// The underlying JRD subsystem will release all
// memory resources related to a limbo transaction
// as a side-effect of the database detach call below.
// However, the y-valve handle must be released.
else
fb_disconnect_transaction(status_vector, &transaction->rtr_handle);
#endif
release_transaction(rdb->rdb_transactions);
}
isc_detach_database(status_vector, &rdb->rdb_handle);
while (rdb->rdb_events) {
release_event(rdb->rdb_events);
}
if (this->port_statement) {
release_statement(&this->port_statement);
}
}
else
{
isc_service_detach(status_vector, &rdb->rdb_handle);
}
}
REMOTE_free_packet(this, sendL);
REMOTE_free_packet(this, receiveL);
#ifdef DEBUG_REMOTE_MEMORY
printf("disconnect(server) free rdb %x\n", rdb);
#endif
this->port_context = NULL;
if (this->port_async)
this->port_async->port_context = NULL;
delete rdb;
if (this->port_connection)
{
#ifdef DEBUG_REMOTE_MEMORY
printf("disconnect(server) free string %x\n", this->port_connection);
#endif
delete this->port_connection;
this->port_connection = NULL;
}
if (this->port_version)
{
#ifdef DEBUG_REMOTE_MEMORY
printf("disconnect(server) free string %x\n", this->port_version);
#endif
delete this->port_version;
this->port_version = NULL;
}
if (this->port_passwd)
{
#ifdef DEBUG_REMOTE_MEMORY
printf("disconnect(server) free string %x\n", this->port_passwd);
#endif
delete this->port_passwd;
this->port_passwd = NULL;
}
if (this->port_user_name)
{
#ifdef DEBUG_REMOTE_MEMORY
printf("disconnect(server) free string %x\n", this->port_user_name);
#endif
delete this->port_user_name;
this->port_user_name = NULL;
}
if (this->port_host)
{
#ifdef DEBUG_REMOTE_MEMORY
printf("disconnect(server) free string %x\n", this->port_host);
#endif
delete this->port_host;
this->port_host = NULL;
}
this->disconnect();
}
void rem_port::drop_database(P_RLSE* /*release*/, PACKET* sendL)
{
/**************************************
*
* d r o p _ d a t a b a s e
*
**************************************
*
* Functional description
* End a request.
*
**************************************/
ISC_STATUS_ARRAY status_vector;
Rdb* rdb = this->port_context;
if (bad_db(status_vector, rdb))
{
this->send_response(sendL, 0, 0, status_vector, false);
return;
}
isc_drop_database(status_vector, &rdb->rdb_handle);
if (status_vector[1] && (status_vector[1] != isc_drdb_completed_with_errs))
{
this->send_response(sendL, 0, 0, status_vector, false);
return;
}
port_flags |= PORT_detached;
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 (this->port_statement)
release_statement(&this->port_statement);
this->send_response(sendL, 0, 0, status_vector, false);
}
#ifdef SCROLLABLE_CURSORS
static RMessage* dump_cache( Rrq::rrq_repeat* tail)
{
/**************************************
*
* d u m p _ c a c h e
*
**************************************
*
* Functional description
* We have encountered a situation where what's in
* cache is not useful, so empty the cache in
* preparation for refilling it.
*
**************************************/
RMessage* message = tail->rrq_xdr;
while (true)
{
message->msg_address = NULL;
message = message->msg_next;
if (message == tail->rrq_xdr)
break;
}
tail->rrq_message = message;
return message;
}
#endif
ISC_STATUS rem_port::end_blob(P_OP operation, P_RLSE * release, PACKET* sendL)
{
/**************************************
*
* e n d _ b l o b
*
**************************************
*
* Functional description
* End a blob.
*
**************************************/
Rbl* blob;
ISC_STATUS_ARRAY status_vector;
getHandle(blob, release->p_rlse_object);
if (operation == op_close_blob)
isc_close_blob(status_vector, &blob->rbl_handle);
else
isc_cancel_blob(status_vector, &blob->rbl_handle);
if (!status_vector[1]) {
release_blob(blob);
}
return this->send_response(sendL, 0, 0, status_vector, false);
}
ISC_STATUS rem_port::end_database(P_RLSE* /*release*/, PACKET* sendL)
{
/**************************************
*
* e n d _ d a t a b a s e
*
**************************************
*
* Functional description
* End a request.
*
**************************************/
ISC_STATUS_ARRAY status_vector;
Rdb* rdb = this->port_context;
if (bad_db(status_vector, rdb))
{
return this->send_response(sendL, 0, 0, status_vector, false);
}
isc_detach_database(status_vector, &rdb->rdb_handle);
if (status_vector[1])
return this->send_response(sendL, 0, 0, status_vector, false);
port_flags |= PORT_detached;
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 (this->port_statement)
release_statement(&this->port_statement);
return this->send_response(sendL, 0, 0, status_vector, false);
}
ISC_STATUS rem_port::end_request(P_RLSE * release, PACKET* sendL)
{
/**************************************
*
* e n d _ r e q u e s t
*
**************************************
*
* Functional description
* End a request.
*
**************************************/
Rrq* requestL;
ISC_STATUS_ARRAY status_vector;
getHandle(requestL, release->p_rlse_object);
isc_release_request(status_vector, &requestL->rrq_handle);
if (!status_vector[1])
release_request(requestL);
return this->send_response(sendL, 0, 0, status_vector, true);
}
ISC_STATUS rem_port::end_statement(P_SQLFREE* free_stmt, PACKET* sendL)
{
/*****************************************
*
* e n d _ s t a t e m e n t
*
*****************************************
*
* Functional description
* Free a statement.
*
*****************************************/
Rsr* statement;
ISC_STATUS_ARRAY status_vector;
getHandle(statement, free_stmt->p_sqlfree_statement);
GDS_DSQL_FREE(status_vector, &statement->rsr_handle, free_stmt->p_sqlfree_option);
if (status_vector[1])
return this->send_response(sendL, 0, 0, status_vector, true);
if (!statement->rsr_handle)
{
release_sql_request(statement);
statement = NULL;
}
else
{
statement->rsr_flags.clear(Rsr::FETCHED);
statement->rsr_rtr = NULL;
REMOTE_reset_statement(statement);
statement->rsr_message = statement->rsr_buffer;
}
const USHORT object = statement ? statement->rsr_id : INVALID_OBJECT;
return this->send_response(sendL, object, 0, status_vector, true);
}
ISC_STATUS rem_port::end_transaction(P_OP operation, P_RLSE * release, PACKET* sendL)
{
/**************************************
*
* e n d _ t r a n s a c t i o n
*
**************************************
*
* Functional description
* End a transaction.
*
**************************************/
Rtr* transaction;
ISC_STATUS_ARRAY status_vector;
getHandle(transaction, release->p_rlse_object);
switch (operation)
{
case op_commit:
isc_commit_transaction(status_vector, &transaction->rtr_handle);
break;
case op_rollback:
isc_rollback_transaction(status_vector, &transaction->rtr_handle);
break;
case op_rollback_retaining:
isc_rollback_retaining(status_vector, &transaction->rtr_handle);
break;
case op_commit_retaining:
isc_commit_retaining(status_vector, &transaction->rtr_handle);
break;
case op_prepare:
if (!isc_prepare_transaction(status_vector, &transaction->rtr_handle))
transaction->rtr_limbo = true;
break;
}
if (!status_vector[1])
if (operation == op_commit || operation == op_rollback)
{
REMOTE_cleanup_transaction(transaction);
release_transaction(transaction);
}
return this->send_response(sendL, 0, 0, status_vector, false);
}
ISC_STATUS rem_port::execute_immediate(P_OP op, P_SQLST * exnow, PACKET* sendL)
{
/*****************************************
*
* e x e c u t e _ i m m e d i a t e
*
*****************************************
*
* Functional description
* process an execute immediate DSQL packet
*
*****************************************/
Rtr* transaction = NULL;
USHORT in_blr_length, in_msg_type, parser_version, out_blr_length, out_msg_type;
ISC_STATUS_ARRAY status_vector;
Rdb* rdb = this->port_context;
if (bad_db(status_vector, rdb))
{
return this->send_response(sendL, 0, 0, status_vector, false);
}
// Do not call CHECK_HANDLE if this is the start of a transaction
if (exnow->p_sqlst_transaction) {
getHandle(transaction, exnow->p_sqlst_transaction);
}
USHORT in_msg_length = 0, out_msg_length = 0;
const UCHAR* in_msg = NULL;
UCHAR* out_msg = NULL;
UCHAR* in_blr;
UCHAR* out_blr;
if (op == op_exec_immediate2)
{
in_blr_length = exnow->p_sqlst_blr.cstr_length;
in_blr = exnow->p_sqlst_blr.cstr_address;
in_msg_type = exnow->p_sqlst_message_number;
if (this->port_statement->rsr_bind_format)
{
in_msg_length = this->port_statement->rsr_bind_format->fmt_length;
RMessage* message = this->port_statement->rsr_message;
if (!message->msg_address)
{
message->msg_address = message->msg_buffer;
}
in_msg = message->msg_address;
}
out_blr_length = exnow->p_sqlst_out_blr.cstr_length;
out_blr = exnow->p_sqlst_out_blr.cstr_address;
out_msg_type = exnow->p_sqlst_out_message_number;
if (this->port_statement->rsr_select_format)
{
out_msg_length = this->port_statement->rsr_select_format->fmt_length;
RMessage* message = this->port_statement->rsr_message;
if (!message->msg_address)
{
message->msg_address = message->msg_buffer;
}
out_msg = message->msg_address;
}
}
else
{
in_blr_length = out_blr_length = 0;
in_blr = out_blr = NULL;
in_msg_type = out_msg_type = 0;
}
FB_API_HANDLE handle = transaction ? transaction->rtr_handle : 0;
// Since the API to GDS_DSQL_EXECUTE_IMMED is public and can not be changed, there needs to
// be a way to send the parser version to DSQL so that the parser can compare the keyword
// version to the parser version. To accomplish this, the parser version is combined with
// the client dialect and sent across that way. In dsql8_execute_immediate, the parser version
// and client dialect are separated and passed on to their final desintations. The information
// is combined as follows:
// Dialect * 10 + parser_version
//
// and is extracted in dsql8_execute_immediate as follows:
// parser_version = ((dialect * 10) + parser_version) % 10
// client_dialect = ((dialect * 10) + parser_version) / 10
//
// For example, parser_version = 1 and client dialect = 1
//
// combined = (1 * 10) + 1 == 11
//
// parser = (combined) % 10 == 1
// dialect = (combined) / 10 == 1
parser_version = (this->port_protocol < PROTOCOL_VERSION10) ? 1 : 2;
GDS_DSQL_EXECUTE_IMMED(status_vector,
&rdb->rdb_handle,
&handle,
exnow->p_sqlst_SQL_str.cstr_length,
reinterpret_cast<const char*>(exnow->p_sqlst_SQL_str.cstr_address),
(USHORT) ((exnow->p_sqlst_SQL_dialect * 10) + parser_version),
in_blr_length,
reinterpret_cast<char*>(in_blr),
in_msg_type,
in_msg_length,
reinterpret_cast<const char*>(in_msg),
out_blr_length,
reinterpret_cast<char*>(out_blr),
out_msg_type,
out_msg_length,
reinterpret_cast<char*>(out_msg));
if (op == op_exec_immediate2)
{
this->port_statement->rsr_format = this->port_statement->rsr_select_format;
sendL->p_operation = op_sql_response;
sendL->p_sqldata.p_sqldata_messages = (status_vector[1] || !out_msg) ? 0 : 1;
this->send_partial(sendL);
}
if (!status_vector[1])
{
if (transaction && !handle)
{
REMOTE_cleanup_transaction(transaction);
release_transaction(transaction);
transaction = NULL;
}
else if (!transaction && handle)
{
if (!(transaction = make_transaction(rdb, handle)))
{
status_vector[0] = isc_arg_gds;
status_vector[1] = isc_too_many_handles;
status_vector[2] = isc_arg_end;
}
}
}
return this->send_response( sendL, (OBJCT) (transaction ? transaction->rtr_id : 0),
0, status_vector, false);
}
ISC_STATUS rem_port::execute_statement(P_OP op, P_SQLDATA* sqldata, PACKET* sendL)
{
/*****************************************
*
* e x e c u t e _ s t a t e m e n t
*
*****************************************
*
* Functional description
* Execute a non-SELECT dynamic SQL statement.
*
*****************************************/
Rtr* transaction = NULL;
// Do not call CHECK_HANDLE if this is the start of a transaction
if (sqldata->p_sqldata_transaction)
{
getHandle(transaction, sqldata->p_sqldata_transaction);
}
Rsr* statement;
getHandle(statement, sqldata->p_sqldata_statement);
USHORT in_msg_length = 0, out_msg_length = 0;
UCHAR* in_msg = NULL;
UCHAR* out_msg = NULL;
USHORT out_msg_type, out_blr_length;
UCHAR* out_blr;
if (statement->rsr_format)
{
fb_assert(statement->rsr_format == statement->rsr_bind_format);
in_msg_length = statement->rsr_format->fmt_length;
in_msg = statement->rsr_message->msg_address;
}
if (op == op_execute2)
{
out_blr_length = sqldata->p_sqldata_out_blr.cstr_length;
out_blr = sqldata->p_sqldata_out_blr.cstr_address;
out_msg_type = sqldata->p_sqldata_out_message_number;
if (this->port_statement->rsr_select_format)
{
out_msg_length = this->port_statement->rsr_select_format->fmt_length;
out_msg = this->port_statement->rsr_message->msg_buffer;
}
}
else
{
out_blr_length = 0;
out_msg_type = 0;
out_blr = NULL;
}
statement->rsr_flags.clear(Rsr::FETCHED);
FB_API_HANDLE handle = transaction ? transaction->rtr_handle : 0;
ISC_STATUS_ARRAY status_vector;
GDS_DSQL_EXECUTE(status_vector,
&handle,
&statement->rsr_handle,
sqldata->p_sqldata_blr.cstr_length,
reinterpret_cast<const char*>(sqldata->p_sqldata_blr.cstr_address),
sqldata->p_sqldata_message_number,
in_msg_length,
reinterpret_cast<char*>(in_msg),
out_blr_length,
reinterpret_cast<char*>(out_blr),
out_msg_type,
out_msg_length,
reinterpret_cast<char*>(out_msg));
if (op == op_execute2)
{
this->port_statement->rsr_format = this->port_statement->rsr_select_format;
sendL->p_operation = op_sql_response;
sendL->p_sqldata.p_sqldata_messages = (status_vector[1] || !out_msg) ? 0 : 1;
this->send_partial(sendL);
}
if (!status_vector[1])
{
if (transaction && !handle)
{
REMOTE_cleanup_transaction(transaction);
release_transaction(transaction);
transaction = NULL;
}
else if (!transaction && handle)
{
if (!(transaction = make_transaction(statement->rsr_rdb, handle)))
{
status_vector[0] = isc_arg_gds;
status_vector[1] = isc_too_many_handles;
status_vector[2] = isc_arg_end;
}
}
statement->rsr_rtr = transaction;
}
const bool defer = this->haveRecvData();
return this->send_response( sendL, (OBJCT) (transaction ? transaction->rtr_id : 0),
0, status_vector, defer);
}
ISC_STATUS rem_port::fetch(P_SQLDATA * sqldata, PACKET* sendL)
{
/*****************************************
*
* f e t c h
*
*****************************************
*
* Functional description
* Fetch next record from a dynamic SQL cursor.
*
*****************************************/
Rsr* statement;
getHandle(statement, sqldata->p_sqldata_statement);
if (statement->rsr_flags.test(Rsr::BLOB)) {
return this->fetch_blob(sqldata, sendL);
}
USHORT msg_length;
if (statement->rsr_format) {
msg_length = statement->rsr_format->fmt_length;
}
else {
msg_length = 0;
}
USHORT count = ((this->port_flags & PORT_rpc) || statement->rsr_flags.test(Rsr::NO_BATCH)) ?
1 : sqldata->p_sqldata_messages;
USHORT count2 = statement->rsr_flags.test(Rsr::NO_BATCH) ? 0 : count;
// On first fetch, clear the end-of-stream flag & reset the message buffers
if (!statement->rsr_flags.test(Rsr::FETCHED))
{
statement->rsr_flags.clear(Rsr::EOF_SET | Rsr::STREAM_ERR);
statement->clearException();
RMessage* message = statement->rsr_message;
if (message != NULL)
{
statement->rsr_buffer = message;
while (true)
{
message->msg_address = NULL;
message = message->msg_next;
if (message == statement->rsr_message)
break;
}
}
}
// Get ready to ship the data out
P_SQLDATA* response = &sendL->p_sqldata;
sendL->p_operation = op_fetch_response;
response->p_sqldata_statement = sqldata->p_sqldata_statement;
response->p_sqldata_status = 0;
response->p_sqldata_messages = 1;
ISC_STATUS s = 0;
RMessage* message = NULL;
// Check to see if any messages are already sitting around
ISC_STATUS_ARRAY status_vector;
while (true)
{
// Have we exhausted the cache & reached cursor EOF?
if (statement->rsr_flags.test(Rsr::EOF_SET) && !statement->rsr_msgs_waiting)
{
statement->rsr_flags.clear(Rsr::EOF_SET);
s = 100;
count2 = 0;
break;
}
// Have we exhausted the cache & have a pending error?
if (statement->rsr_flags.test(Rsr::STREAM_ERR) && !statement->rsr_msgs_waiting)
{
fb_assert(statement->rsr_status);
statement->rsr_flags.clear(Rsr::STREAM_ERR);
return this->send_response(sendL, 0, 0, statement->rsr_status->value(), false);
}
message = statement->rsr_buffer;
// Make sure message can be de referenced, if not then return false
if (message == NULL)
return FALSE;
// If we don't have a message cached, get one from the access method.
if (!message->msg_address)
{
fb_assert(statement->rsr_msgs_waiting == 0);
s = GDS_DSQL_FETCH(status_vector,
&statement->rsr_handle,
sqldata->p_sqldata_blr.cstr_length,
reinterpret_cast<char*>(sqldata->p_sqldata_blr.cstr_address),
sqldata->p_sqldata_message_number,
msg_length,
reinterpret_cast<char*>(message->msg_buffer.operator UCHAR*()));
statement->rsr_flags.set(Rsr::FETCHED);
if (s)
{
if (s == 100 || s == 101)
{
count2 = 0;
break;
}
return this->send_response(sendL, 0, 0, status_vector, false);
}
message->msg_address = message->msg_buffer;
}
else
{
// Take a message from the outqoing queue
fb_assert(statement->rsr_msgs_waiting >= 1);
statement->rsr_msgs_waiting--;
}
// For compatibility with Protocol 7, we must break out of the
// loop before sending the last record.
count--;
if (this->port_protocol <= PROTOCOL_VERSION7 && count <= 0) {
break;
}
// There's a buffer waiting -- send it
if (!this->send_partial(sendL)) {
return FALSE;
}
message->msg_address = NULL;
// If we've sent the requested amount, break out of loop
if (count <= 0)
break;
}
response->p_sqldata_status = s;
response->p_sqldata_messages = 0;
// hvlad: message->msg_address not used in xdr_protocol because of
// response->p_sqldata_messages set to zero above.
// It is important to not zero message->msg_address after send because
// during thread context switch in send we can receive packet with
// op_free and op_execute (lazy port feature allow this) which itself
// set message->msg_address to some useful information
// This fix must be re-thought when real multithreading will be implemented
if (message) {
message->msg_address = NULL;
}
this->send(sendL);
// Since we have a little time on our hands while this packet is sent
// and processed, get the next batch of records. Start by finding the
// next free buffer.
message = statement->rsr_buffer;
RMessage* next = NULL;
while (message->msg_address && message->msg_next != statement->rsr_buffer)
message = message->msg_next;
for (; count2; --count2)
{
if (message->msg_address)
{
if (!next)
{
next = statement->rsr_buffer;
while (next->msg_next != message)
next = next->msg_next;
}
message = new RMessage(statement->rsr_fmt_length);
message->msg_number = next->msg_number;
message->msg_next = next->msg_next;
next->msg_next = message;
next = message;
}
s = GDS_DSQL_FETCH(status_vector,
&statement->rsr_handle,
sqldata->p_sqldata_blr.cstr_length,
reinterpret_cast<char*>(sqldata->p_sqldata_blr.cstr_address),
sqldata->p_sqldata_message_number,
msg_length,
reinterpret_cast<char*>(message->msg_buffer.operator UCHAR*()));
if (s)
{
if (status_vector[1])
{
// If already have an error queued, don't overwrite it
if (!statement->rsr_flags.test(Rsr::STREAM_ERR))
{
statement->rsr_flags.set(Rsr::STREAM_ERR);
statement->saveException(status_vector, true);
}
}
if (s == 100)
statement->rsr_flags.set(Rsr::EOF_SET);
break;
}
message->msg_address = message->msg_buffer;
message = message->msg_next;
statement->rsr_msgs_waiting++;
}
return TRUE;
}
ISC_STATUS rem_port::fetch_blob(P_SQLDATA * sqldata, PACKET* sendL)
{
/*****************************************
*
* f e t c h _ b l o b
*
*****************************************
*
* Functional description
* Fetch next record from a dynamic SQL cursor.
*
*****************************************/
Rsr* statement;
getHandle(statement, sqldata->p_sqldata_statement);
USHORT msg_length;
if (statement->rsr_format)
msg_length = statement->rsr_format->fmt_length;
else
msg_length = 0;
RMessage* message = statement->rsr_message;
if (message != NULL)
statement->rsr_buffer = message;
// Get ready to ship the data out
P_SQLDATA* response = &sendL->p_sqldata;
sendL->p_operation = op_fetch_response;
response->p_sqldata_statement = sqldata->p_sqldata_statement;
response->p_sqldata_status = 0;
response->p_sqldata_messages = 1;
ISC_STATUS s = 0;
message = statement->rsr_buffer;
ISC_STATUS_ARRAY status_vector;
s = GDS_DSQL_FETCH(status_vector,
&statement->rsr_handle,
sqldata->p_sqldata_blr.cstr_length,
reinterpret_cast<char*>(sqldata->p_sqldata_blr.cstr_address),
sqldata->p_sqldata_message_number,
msg_length,
reinterpret_cast<char*>(message->msg_buffer.operator UCHAR*()));
if (!status_vector[1] || status_vector[1] != isc_segment || status_vector[1] != isc_segstr_eof)
{
message->msg_address = message->msg_buffer;
response->p_sqldata_status = s;
response->p_sqldata_messages = (status_vector[1] == isc_segstr_eof) ? 0 : 1;
this->send_partial(sendL);
message->msg_address = NULL;
}
return this->send_response(sendL, 0, 0, status_vector, false);
}
static bool get_next_msg_no(Rrq* request, USHORT incarnation, USHORT * msg_number)
{
/**************************************
*
* g e t _ n e x t _ m s g _ n o
*
**************************************
*
* Functional description
* Return the number of the next message
* in the request.
*
**************************************/
ISC_STATUS_ARRAY status_vector;
UCHAR info_buffer[128];
isc_request_info(status_vector, &request->rrq_handle, incarnation,
sizeof(request_info), reinterpret_cast<const SCHAR*>(request_info),
sizeof(info_buffer), reinterpret_cast<char*>(info_buffer));
if (status_vector[1])
return false;
bool result = false;
for (const UCHAR* info = info_buffer; *info != isc_info_end;)
{
const USHORT l = (USHORT) gds__vax_integer(info + 1, 2);
const USHORT n = (USHORT) gds__vax_integer(info + 3, l);
switch (*info)
{
case isc_info_state:
if (n != isc_info_req_send)
return false;
break;
case isc_info_message_number:
*msg_number = n;
result = true;
break;
default:
return false;
}
info += 3 + l;
}
return result;
}
ISC_STATUS rem_port::get_segment(P_SGMT* segment, PACKET* sendL)
{
/**************************************
*
* g e t _ s e g m e n t
*
**************************************
*
* Functional description
* Get a single blob segment.
*
**************************************/
Rbl* blob;
getHandle(blob, segment->p_sgmt_blob);
UCHAR temp_buffer[BLOB_LENGTH];
USHORT buffer_length = segment->p_sgmt_length;
UCHAR* buffer;
if (buffer_length <= sizeof(temp_buffer))
buffer = temp_buffer;
else
{
if (buffer_length > blob->rbl_buffer_length)
{
blob->rbl_buffer = blob->rbl_data.getBuffer(buffer_length);
blob->rbl_buffer_length = buffer_length;
}
buffer = blob->rbl_buffer;
}
#ifdef DEBUG_REMOTE_MEMORY
printf("get_segment(server) allocate buffer %x\n", buffer);
#endif
sendL->p_resp.p_resp_data.cstr_address = buffer;
// Be backwards compatible
ISC_STATUS_ARRAY status_vector;
USHORT length;
if (this->port_flags & PORT_rpc)
{
length = 0;
isc_get_segment(status_vector, &blob->rbl_handle, &length,
segment->p_sgmt_length, reinterpret_cast<char*>(buffer));
const ISC_STATUS status =
this->send_response(sendL, blob->rbl_id, length, status_vector, false);
#ifdef DEBUG_REMOTE_MEMORY
printf("get_segment(server) free buffer %x\n", buffer);
#endif
return status;
}
// Gobble up a buffer's worth of segments
UCHAR* p = buffer;
ISC_STATUS state = 0;
while (buffer_length > 2)
{
buffer_length -= 2;
p += 2;
isc_get_segment(status_vector, &blob->rbl_handle, &length,
buffer_length, reinterpret_cast<char*>(p));
if (status_vector[1] == isc_segstr_eof)
{
state = 2;
success(status_vector);
p -= 2;
break;
}
if (status_vector[1] && (status_vector[1] != isc_segment))
{
p -= 2;
break;
}
p[-2] = (UCHAR) length;
p[-1] = (UCHAR) (length >> 8);
p += length;
buffer_length -= length;
if (status_vector[1] == isc_segment)
{
state = 1;
success(status_vector);
break;
}
}
const ISC_STATUS status = this->send_response(sendL, (OBJCT) state, (USHORT) (p - buffer),
status_vector, false);
#ifdef DEBUG_REMOTE_MEMORY
printf("get_segment(server) free buffer %x\n", buffer);
#endif
return status;
}
ISC_STATUS rem_port::get_slice(P_SLC * stuff, PACKET* sendL)
{
/**************************************
*
* g e t _ s l i c e
*
**************************************
*
* Functional description
* Get an array slice.
*
**************************************/
Rtr* transaction;
ISC_STATUS_ARRAY status_vector;
Rdb* rdb = this->port_context;
if (bad_db(status_vector, rdb))
{
return this->send_response(sendL, 0, 0, status_vector, false);
}
getHandle(transaction, stuff->p_slc_transaction);
Firebird::HalfStaticArray<UCHAR, 4096> temp_buffer;
UCHAR* slice = 0;
if (stuff->p_slc_length)
{
slice = temp_buffer.getBuffer(stuff->p_slc_length);
memset(slice, 0, stuff->p_slc_length);
#ifdef DEBUG_REMOTE_MEMORY
printf("get_slice(server) allocate buffer %x\n", slice);
#endif
}
P_SLR* response = &sendL->p_slr;
isc_get_slice(status_vector, &rdb->rdb_handle, &transaction->rtr_handle,
(ISC_QUAD*) &stuff->p_slc_id, stuff->p_slc_sdl.cstr_length,
reinterpret_cast<const char*>(stuff->p_slc_sdl.cstr_address),
stuff->p_slc_parameters.cstr_length,
(const ISC_LONG*) stuff->p_slc_parameters.cstr_address,
stuff->p_slc_length, slice,
reinterpret_cast<SLONG*>(&response->p_slr_length));
ISC_STATUS status;
if (status_vector[1])
status = this->send_response(sendL, 0, 0, status_vector, false);
else
{
sendL->p_operation = op_slice;
response->p_slr_slice.lstr_address = slice;
response->p_slr_slice.lstr_length = response->p_slr_length;
response->p_slr_sdl = stuff->p_slc_sdl.cstr_address;
response->p_slr_sdl_length = stuff->p_slc_sdl.cstr_length;
this->send(sendL);
response->p_slr_sdl = 0;
status = FB_SUCCESS;
}
return status;
}
ISC_STATUS rem_port::info(P_OP op, P_INFO* stuff, PACKET* sendL)
{
/**************************************
*
* i n f o
*
**************************************
*
* Functional description
* Get info for a blob, database, request, service,
* statement, or transaction.
*
**************************************/
ISC_STATUS_ARRAY status_vector;
Rdb* rdb = this->port_context;
if (bad_db(status_vector, rdb))
{
return this->send_response(sendL, 0, 0, status_vector, false);
}
// Make sure there is a suitable temporary blob buffer
Firebird::Array<UCHAR> buf;
UCHAR* const buffer = buf.getBuffer(stuff->p_info_buffer_length);
memset(buffer, 0, stuff->p_info_buffer_length);
Firebird::HalfStaticArray<SCHAR, 1024> info;
SCHAR* info_buffer = 0;
USHORT info_len;
Firebird::HalfStaticArray<UCHAR, 1024> temp;
UCHAR* temp_buffer = 0;
if (op == op_info_database)
{
info_len = 0;
info_buffer = 0;
temp_buffer = temp.getBuffer(stuff->p_info_buffer_length);
}
else
{
// stuff isc_info_length in front of info items buffer
CSTRING_CONST* info_string = (op == op_service_info) ?
&stuff->p_info_recv_items : &stuff->p_info_items;
info_len = 1 + info_string->cstr_length;
info_buffer = info.getBuffer(info_len);
*info_buffer = isc_info_length;
memmove(info_buffer + 1, info_string->cstr_address, info_len - 1);
}
Rbl* blob;
Rtr* transaction;
Rsr* statement;
USHORT info_db_len = 0;
switch (op)
{
case op_info_blob:
getHandle(blob, stuff->p_info_object);
isc_blob_info(status_vector, &blob->rbl_handle,
info_len, info_buffer,
stuff->p_info_buffer_length, reinterpret_cast<char*>(buffer));
break;
case op_info_database:
isc_database_info(status_vector, &rdb->rdb_handle,
stuff->p_info_items.cstr_length,
reinterpret_cast<const char*>(stuff->p_info_items.cstr_address),
stuff->p_info_buffer_length, //sizeof(temp)
reinterpret_cast<char*>(temp_buffer)); //temp
if (!status_vector[1])
{
Firebird::string version;
version.printf("%s/%s", GDS_VERSION, this->port_version->str_data);
info_db_len = MERGE_database_info(temp_buffer, //temp
buffer, stuff->p_info_buffer_length,
IMPLEMENTATION, 4, 1,
reinterpret_cast<const UCHAR*>(version.c_str()),
reinterpret_cast<const UCHAR*>(this->port_host->str_data));
}
break;
case op_info_request:
{
Rrq* requestL;
getHandle(requestL, stuff->p_info_object);
isc_request_info(status_vector, &requestL->rrq_handle,
stuff->p_info_incarnation,
info_len, info_buffer,
stuff->p_info_buffer_length, reinterpret_cast<char*>(buffer));
}
break;
case op_info_transaction:
getHandle(transaction, stuff->p_info_object);
isc_transaction_info(status_vector, &transaction->rtr_handle,
info_len, info_buffer,
stuff->p_info_buffer_length, reinterpret_cast<char*>(buffer));
break;
case op_service_info:
isc_service_query(status_vector,
&rdb->rdb_handle,
NULL,
stuff->p_info_items.cstr_length,
reinterpret_cast<const char*>(stuff->p_info_items.cstr_address),
info_len, info_buffer,
stuff->p_info_buffer_length, reinterpret_cast<char*>(buffer));
break;
case op_info_sql:
getHandle(statement, stuff->p_info_object);
GDS_DSQL_SQL_INFO(status_vector,
&statement->rsr_handle,
info_len, info_buffer,
stuff->p_info_buffer_length, reinterpret_cast<char*>(buffer));
break;
}
// Send a response that includes the segment.
USHORT response_len = info_db_len ? info_db_len : stuff->p_info_buffer_length;
SSHORT skip_len = 0;
if (*buffer == isc_info_length)
{
skip_len = gds__vax_integer(buffer + 1, 2);
const SLONG val = gds__vax_integer(buffer + 3, skip_len);
fb_assert(val >= 0);
skip_len += 3;
if (val && ULONG(val) < ULONG(response_len)) {
response_len = val;
}
}
sendL->p_resp.p_resp_data.cstr_address = buffer + skip_len;
const ISC_STATUS status = this->send_response(sendL, stuff->p_info_object,
response_len, status_vector, false);
return status;
}
ISC_STATUS rem_port::insert(P_SQLDATA * sqldata, PACKET* sendL)
{
/*****************************************
*
* i n s e r t
*
*****************************************
*
* Functional description
* Insert next record into a dynamic SQL cursor.
*
*****************************************/
Rsr* statement;
getHandle(statement, sqldata->p_sqldata_statement);
USHORT msg_length;
const UCHAR* msg;
if (statement->rsr_format)
{
msg_length = statement->rsr_format->fmt_length;
msg = statement->rsr_message->msg_address;
}
else
{
msg_length = 0;
msg = NULL;
}
ISC_STATUS_ARRAY status_vector;
GDS_DSQL_INSERT(status_vector,
&statement->rsr_handle,
sqldata->p_sqldata_blr.cstr_length,
reinterpret_cast<const char*>(sqldata->p_sqldata_blr.cstr_address),
sqldata->p_sqldata_message_number, msg_length,
reinterpret_cast<const char*>(msg));
return this->send_response(sendL, 0, 0, status_vector, false);
}
static Rtr* make_transaction (Rdb* rdb, FB_API_HANDLE handle)
{
/**************************************
*
* m a k e _ t r a n s a c t i o n
*
**************************************
*
* Functional description
* Create a local transaction handle.
*
**************************************/
Rtr* transaction = new Rtr;
transaction->rtr_rdb = rdb;
transaction->rtr_handle = handle;
if (transaction->rtr_id = rdb->rdb_port->get_id(transaction))
{
transaction->rtr_next = rdb->rdb_transactions;
rdb->rdb_transactions = transaction;
}
else
{
delete transaction;
transaction = NULL;
}
return transaction;
}
ISC_STATUS rem_port::open_blob(P_OP op, P_BLOB* stuff, PACKET* sendL)
{
/**************************************
*
* o p e n _ b l o b
*
**************************************
*
* Functional description
* Open or create a new blob.
*
**************************************/
Rtr* transaction;
ISC_STATUS_ARRAY status_vector;
getHandle(transaction, stuff->p_blob_transaction);
Rdb* rdb = this->port_context;
if (bad_db(status_vector, rdb))
{
return this->send_response(sendL, 0, 0, status_vector, false);
}
FB_API_HANDLE handle = 0;
USHORT bpb_length = 0;
const UCHAR* bpb = NULL;
if (op == op_open_blob2 || op == op_create_blob2)
{
bpb_length = stuff->p_blob_bpb.cstr_length;
bpb = stuff->p_blob_bpb.cstr_address;
}
if (op == op_open_blob || op == op_open_blob2)
isc_open_blob2(status_vector, &rdb->rdb_handle,
&transaction->rtr_handle, &handle,
(ISC_QUAD*) &stuff->p_blob_id, bpb_length, bpb);
else
isc_create_blob2(status_vector, &rdb->rdb_handle, &transaction->rtr_handle,
&handle, (ISC_QUAD*) &sendL->p_resp.p_resp_blob_id,
bpb_length, reinterpret_cast<const char*>(bpb));
USHORT object;
if (status_vector[1])
object = 0;
else
{
Rbl* blob = new Rbl;
#ifdef DEBUG_REMOTE_MEMORY
printf("open_blob(server) allocate blob %x\n", blob);
#endif
blob->rbl_handle = handle;
blob->rbl_rdb = rdb;
if (blob->rbl_id = this->get_id(blob))
{
object = blob->rbl_id;
blob->rbl_rtr = transaction;
blob->rbl_next = transaction->rtr_blobs;
transaction->rtr_blobs = blob;
}
else
{
object = 0;
isc_cancel_blob(status_vector, &blob->rbl_handle);
delete blob;
status_vector[0] = isc_arg_gds;
status_vector[1] = isc_too_many_handles;
status_vector[2] = isc_arg_end;
}
}
return this->send_response(sendL, object, 0, status_vector, false);
}
ISC_STATUS rem_port::prepare(P_PREP * stuff, PACKET* sendL)
{
/**************************************
*
* p r e p a r e
*
**************************************
*
* Functional description
* End a transaction.
*
**************************************/
Rtr* transaction;
ISC_STATUS_ARRAY status_vector;
getHandle(transaction, stuff->p_prep_transaction);
if (!isc_prepare_transaction2(status_vector, &transaction->rtr_handle,
stuff->p_prep_data.cstr_length,
stuff->p_prep_data.cstr_address))
{
transaction->rtr_limbo = true;
}
return this->send_response(sendL, 0, 0, status_vector, false);
}
ISC_STATUS rem_port::prepare_statement(P_SQLST * prepareL, PACKET* sendL)
{
/*****************************************
*
* p r e p a r e _ s t a t e m e n t
*
*****************************************
*
* Functional description
* Prepare a dynamic SQL statement for execution.
*
*****************************************/
Rtr* transaction = NULL;
Rsr* statement;
// Do not call CHECK_HANDLE if this is the start of a transaction
if (prepareL->p_sqlst_transaction)
{
getHandle(transaction, prepareL->p_sqlst_transaction);
}
getHandle(statement, prepareL->p_sqlst_statement);
Firebird::HalfStaticArray<UCHAR, 1024> local_buffer, info_buffer;
UCHAR* const info = info_buffer.getBuffer(prepareL->p_sqlst_items.cstr_length + 1);
UCHAR* const buffer = local_buffer.getBuffer(prepareL->p_sqlst_buffer_length);
// stuff isc_info_length in front of info items buffer
*info = isc_info_length;
memmove(info + 1, prepareL->p_sqlst_items.cstr_address, prepareL->p_sqlst_items.cstr_length);
FB_API_HANDLE handle = transaction ? transaction->rtr_handle : 0;
// Since the API to GDS_DSQL_PREPARE is public and can not be changed, there needs to
// be a way to send the parser version to DSQL so that the parser can compare the keyword
// version to the parser version. To accomplish this, the parser version is combined with
// the client dialect and sent across that way. In dsql8_prepare_statement, the parser version
// and client dialect are separated and passed on to their final desintations. The information
// is combined as follows:
// Dialect * 10 + parser_version
//
// and is extracted in dsql8_prepare_statement as follows:
// parser_version = ((dialect * 10) + parser_version) % 10
// client_dialect = ((dialect * 10) + parser_version) / 10
//
// For example, parser_version = 1 and client dialect = 1
//
// combined = (1 * 10) + 1 == 11
//
// parser = (combined) % 10 == 1
// dialect = (combined) / 10 == 1
const USHORT parser_version = (this->port_protocol < PROTOCOL_VERSION10) ? 1 : 2;
ISC_STATUS_ARRAY status_vector;
GDS_DSQL_PREPARE(status_vector,
&handle,
&statement->rsr_handle,
prepareL->p_sqlst_SQL_str.cstr_length,
reinterpret_cast<const char*>(prepareL->p_sqlst_SQL_str.cstr_address),
(USHORT) ((prepareL->p_sqlst_SQL_dialect * 10) + parser_version),
prepareL->p_sqlst_items.cstr_length + 1,
reinterpret_cast<const char*> (info),
prepareL->p_sqlst_buffer_length,
reinterpret_cast<char*>(buffer));
if (status_vector[1]) {
return this->send_response(sendL, 0, 0, status_vector, false);
}
REMOTE_reset_statement(statement);
statement->rsr_flags.clear(Rsr::BLOB | Rsr::NO_BATCH | Rsr::DEFER_EXECUTE);
USHORT state = check_statement_type(statement);
if (state & STMT_BLOB) {
statement->rsr_flags.set(Rsr::BLOB);
}
if (state & STMT_NO_BATCH) {
statement->rsr_flags.set(Rsr::NO_BATCH);
}
if ((state & STMT_DEFER_EXECUTE) && (port_flags & PORT_lazy)) {
statement->rsr_flags.set(Rsr::DEFER_EXECUTE);
}
if (!(port_flags & PORT_lazy)) {
state = (state & STMT_BLOB) ? 1 : 0;
}
// Send a response that includes the info requested.
USHORT response_len = prepareL->p_sqlst_buffer_length;
SSHORT skip_len = 0;
if (*buffer == isc_info_length)
{
skip_len = gds__vax_integer(buffer + 1, 2);
const SLONG val = gds__vax_integer(buffer + 3, skip_len);
skip_len += 3;
if (val && val <= response_len) {
response_len = val;
}
}
sendL->p_resp.p_resp_data.cstr_address = buffer + skip_len;
const ISC_STATUS status = this->send_response(sendL, state, response_len, status_vector, false);
return status;
}
class DecrementRequestsQueued
{
public:
explicit DecrementRequestsQueued(rem_port* port) :
m_port(port)
{}
~DecrementRequestsQueued()
{
--m_port->port_requests_queued;
}
private:
RemPortPtr m_port;
};
// Declared in serve_proto.h
static bool process_packet(rem_port* port, PACKET* sendL, PACKET* receive, rem_port** result)
{
/**************************************
*
* p r o c e s s _ p a c k e t
*
**************************************
*
* Functional description
* Given an packet received from client, process it a packet ready to be
* sent.
*
**************************************/
Firebird::RefMutexGuard portGuard(*port->port_sync);
DecrementRequestsQueued dec(port);
try
{
const P_OP op = receive->p_operation;
switch (op)
{
case op_connect:
if (!accept_connection(port, &receive->p_cnct, sendL))
{
rem_str* string = port->port_user_name;
if (string)
{
gds__log("SERVER/process_packet: connection rejected for %*.*s",
string->str_length, string->str_length, string->str_data);
}
if (port->port_server->srvr_flags & SRVR_multi_client) {
port->port_state = rem_port::BROKEN;
}
else
{
gds__log("SERVER/process_packet: connect reject, server exiting");
port->disconnect(sendL, receive);
return false;
}
}
break;
case op_compile:
port->compile(&receive->p_cmpl, sendL);
break;
case op_attach:
case op_create:
attach_database(port, op, &receive->p_atch, sendL);
break;
case op_service_attach:
attach_service(port, &receive->p_atch, sendL);
break;
case op_trusted_auth:
#ifdef TRUSTED_AUTH
trusted_auth(port, &receive->p_trau, sendL);
#else
send_error(port, sendL, isc_wish_list);
#endif
break;
case op_update_account_info:
case op_authenticate_user:
send_error(port, sendL, isc_wish_list);
break;
case op_service_start:
port->service_start(&receive->p_info, sendL);
break;
case op_disconnect:
case op_exit:
{
const srvr* server = port->port_server;
if (!server)
break;
if ((server->srvr_flags & SRVR_multi_client) && port != server->srvr_parent_port)
{
port->disconnect(sendL, receive);
port = NULL;
break;
}
if ((server->srvr_flags & SRVR_multi_client) && port == server->srvr_parent_port)
{
gds__log("SERVER/process_packet: Multi-client server shutdown");
}
port->disconnect(sendL, receive);
return false;
}
case op_receive:
port->receive_msg(&receive->p_data, sendL);
break;
case op_send:
port->send_msg(&receive->p_data, sendL);
break;
case op_start:
case op_start_and_receive:
port->start(op, &receive->p_data, sendL);
break;
case op_start_and_send:
case op_start_send_and_receive:
port->start_and_send(op, &receive->p_data, sendL);
break;
case op_transact:
port->transact_request(&receive->p_trrq, sendL);
break;
case op_reconnect:
case op_transaction:
port->start_transaction(op, &receive->p_sttr, sendL);
break;
case op_prepare:
case op_rollback:
case op_rollback_retaining:
case op_commit:
case op_commit_retaining:
port->end_transaction(op, &receive->p_rlse, sendL);
break;
case op_detach:
port->end_database(&receive->p_rlse, sendL);
break;
case op_service_detach:
port->service_end(&receive->p_rlse, sendL);
break;
case op_drop_database:
port->drop_database(&receive->p_rlse, sendL);
break;
case op_create_blob:
case op_open_blob:
case op_create_blob2:
case op_open_blob2:
port->open_blob(op, &receive->p_blob, sendL);
break;
case op_batch_segments:
case op_put_segment:
port->put_segment(op, &receive->p_sgmt, sendL);
break;
case op_get_segment:
port->get_segment(&receive->p_sgmt, sendL);
break;
case op_seek_blob:
port->seek_blob(&receive->p_seek, sendL);
break;
case op_cancel_blob:
case op_close_blob:
port->end_blob(op, &receive->p_rlse, sendL);
break;
case op_prepare2:
port->prepare(&receive->p_prep, sendL);
break;
case op_release:
port->end_request(&receive->p_rlse, sendL);
break;
case op_info_blob:
case op_info_database:
case op_info_request:
case op_info_transaction:
case op_service_info:
case op_info_sql:
port->info(op, &receive->p_info, sendL);
break;
case op_que_events:
port->que_events(&receive->p_event, sendL);
break;
case op_cancel_events:
cancel_events(port, &receive->p_event, sendL);
break;
case op_connect_request:
aux_request(port, /*&receive->p_req,*/ sendL);
break;
#ifdef NOT_USED_OR_REPLACED
case op_aux_connect:
aux_connect(port, &receive->p_req, sendL);
break;
#endif
case op_ddl:
port->ddl(&receive->p_ddl, sendL);
break;
case op_get_slice:
port->get_slice(&receive->p_slc, sendL);
break;
case op_put_slice:
port->put_slice(&receive->p_slc, sendL);
break;
case op_allocate_statement:
allocate_statement(port, /*&receive->p_rlse,*/ sendL);
break;
case op_execute:
case op_execute2:
port->execute_statement(op, &receive->p_sqldata, sendL);
break;
case op_exec_immediate:
case op_exec_immediate2:
port->execute_immediate(op, &receive->p_sqlst, sendL);
break;
case op_fetch:
port->fetch(&receive->p_sqldata, sendL);
break;
case op_free_statement:
port->end_statement(&receive->p_sqlfree, sendL);
break;
case op_insert:
port->insert(&receive->p_sqldata, sendL);
break;
case op_prepare_statement:
port->prepare_statement(&receive->p_sqlst, sendL);
break;
case op_set_cursor:
port->set_cursor(&receive->p_sqlcur, sendL);
break;
case op_dummy:
sendL->p_operation = op_dummy;
port->send(sendL);
break;
case op_cancel:
cancel_operation(port, receive->p_cancel_op.p_co_kind);
break;
default:
gds__log("SERVER/process_packet: don't understand packet type %d", receive->p_operation);
port->port_state = rem_port::BROKEN;
break;
}
if (port && port->port_state == rem_port::BROKEN)
{
if (!port->port_parent)
{
if (!Worker::isShuttingDown() && !(port->port_flags & PORT_rdb_shutdown))
gds__log("SERVER/process_packet: broken port, server exiting");
port->disconnect(sendL, receive);
return false;
}
port->disconnect(sendL, receive);
port = NULL;
}
} // try
catch (const Firebird::status_exception& ex)
{
// typical case like bad handle passed
ISC_STATUS_ARRAY local_status;
memset(local_status, 0, sizeof(local_status));
Firebird::stuff_exception(local_status, ex);
// Send it to the user
port->send_response(sendL, 0, 0, local_status, false);
}
catch (const Firebird::Exception& ex)
{
// something more serious happened
ISC_STATUS_ARRAY local_status;
memset(local_status, 0, sizeof(local_status));
// Log the error to the user.
Firebird::stuff_exception(local_status, ex);
gds__log_status(0, local_status);
port->send_response(sendL, 0, 0, local_status, false);
port->disconnect(sendL, receive); // Well, how about this...
return false;
}
if (result)
{
*result = port;
}
return true;
}
#ifdef TRUSTED_AUTH
static void trusted_auth(rem_port* port, const P_TRAU* p_trau, PACKET* send)
{
/**************************************
*
* t r u s t e d _ a u t h
*
**************************************
*
* Functional description
* Server side of trusted auth handshake.
*
**************************************/
ServerAuth* sa = port->port_trusted_auth;
if (! sa)
send_error(port, send, isc_unavailable);
try
{
AuthSspi::DataHolder data;
memcpy(data.getBuffer(p_trau->p_trau_data.cstr_length),
p_trau->p_trau_data.cstr_address, p_trau->p_trau_data.cstr_length);
AuthSspi* authSspi = sa->authSspi;
if (authSspi->accept(data) && authSspi->isActive())
{
send->p_operation = op_trusted_auth;
cstring& s = send->p_trau.p_trau_data;
s.cstr_allocated = 0;
s.cstr_length = data.getCount();
s.cstr_address = data.begin();
port->send(send);
return;
}
}
catch (const Firebird::status_exception& e)
{
ISC_STATUS_ARRAY status_vector;
Firebird::stuff_exception(status_vector, e);
port->send_response(send, 0, 0, status_vector, false);
return;
}
sa->part2(port, sa->operation, sa->fileName.c_str(), sa->fileName.length(),
sa->clumplet.begin(), sa->clumplet.getCount(), send);
}
static bool canUseTrusted()
{
/**************************************
*
* c a n U s e T r u s t e d
*
**************************************
*
* Functional description
* Cache config setting for trusted auth.
*
**************************************/
static AmCache useTrusted = AM_UNKNOWN;
if (useTrusted == AM_UNKNOWN)
{
Firebird::PathName authMethod(Config::getAuthMethod());
useTrusted = (authMethod == AmTrusted || authMethod == AmMixed) ? AM_ENABLED : AM_DISABLED;
}
return useTrusted == AM_ENABLED;
}
#endif // TRUSTED_AUTH
ISC_STATUS rem_port::put_segment(P_OP op, P_SGMT * segment, PACKET* sendL)
{
/**************************************
*
* p u t _ s e g m e n t
*
**************************************
*
* Functional description
* Write a single blob segment.
*
**************************************/
Rbl* blob;
getHandle(blob, segment->p_sgmt_blob);
const UCHAR* p = segment->p_sgmt_segment.cstr_address;
USHORT length = segment->p_sgmt_segment.cstr_length;
// Do the signal segment version. If it failed, just pass on the bad news.
ISC_STATUS_ARRAY status_vector;
if (op == op_put_segment)
{
isc_put_segment(status_vector, &blob->rbl_handle, length, reinterpret_cast<const char*>(p));
return this->send_response(sendL, 0, 0, status_vector, false);
}
// We've got a batch of segments. This is only slightly more complicated
const UCHAR* const end = p + length;
while (p < end)
{
length = *p++;
length += *p++ << 8;
isc_put_segment(status_vector, &blob->rbl_handle, length, reinterpret_cast<const char*>(p));
if (status_vector[1])
return this->send_response(sendL, 0, 0, status_vector, false);
p += length;
}
return this->send_response(sendL, 0, 0, status_vector, false);
}
ISC_STATUS rem_port::put_slice(P_SLC * stuff, PACKET* sendL)
{
/**************************************
*
* p u t _ s l i c e
*
**************************************
*
* Functional description
* Put an array slice.
*
**************************************/
Rtr* transaction;
ISC_STATUS_ARRAY status_vector;
getHandle(transaction, stuff->p_slc_transaction);
Rdb* rdb = this->port_context;
if (bad_db(status_vector, rdb))
{
return this->send_response(sendL, 0, 0, status_vector, false);
}
sendL->p_resp.p_resp_blob_id = stuff->p_slc_id;
isc_put_slice(status_vector, &rdb->rdb_handle, &transaction->rtr_handle,
(ISC_QUAD*) &sendL->p_resp.p_resp_blob_id,
stuff->p_slc_sdl.cstr_length,
reinterpret_cast<const char*>(stuff->p_slc_sdl.cstr_address),
stuff->p_slc_parameters.cstr_length,
(const ISC_LONG*) stuff->p_slc_parameters.cstr_address,
stuff->p_slc_slice.lstr_length,
stuff->p_slc_slice.lstr_address);
return this->send_response(sendL, 0, 0, status_vector, false);
}
ISC_STATUS rem_port::que_events(P_EVENT * stuff, PACKET* sendL)
{
/**************************************
*
* q u e _ e v e n t s
*
**************************************
*
* Functional description
*
**************************************/
ISC_STATUS_ARRAY status_vector;
Rdb* rdb = this->port_context;
if (bad_db(status_vector, rdb))
{
return this->send_response(sendL, 0, 0, status_vector, false);
}
// 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 = new Rvnt;
#ifdef DEBUG_REMOTE_MEMORY
printf("que_events(server) allocate event %x\n", event);
#endif
event->rvnt_next = rdb->rdb_events;
rdb->rdb_events = event;
}
event->rvnt_ast = stuff->p_event_ast;
// CVC: Going from SLONG to void*, problems when sizeof(void*) > 4
// Nickolay Samofatov. No problem actually. Event argument returned by server
// on event firing is not used by the client. Client looks up argument via
// seaching client-side event table and remote event ID passed by server on
// event registration. As a result we may use this argument value for
// server-side debugging only.
event->rvnt_arg = (void*)(IPTR) stuff->p_event_arg;
event->rvnt_rid = stuff->p_event_rid;
event->rvnt_rdb = rdb;
isc_que_events(status_vector, &rdb->rdb_handle, &event->rvnt_id,
stuff->p_event_items.cstr_length,
stuff->p_event_items.cstr_address,
server_ast,
event);
const SLONG id = event->rvnt_id;
if (status_vector[1]) {
event->rvnt_id = 0;
}
return this->send_response(sendL, (OBJCT) id, 0, status_vector, false);
}
ISC_STATUS rem_port::receive_after_start(P_DATA* data, PACKET* sendL, ISC_STATUS* status_vector)
{
/**************************************
*
* r e c e i v e _ a f t e r _ s t a r t
*
**************************************
*
* Functional description
* Receive a message.
*
**************************************/
Rrq* requestL;
getHandle(requestL, data->p_data_request);
const USHORT level = data->p_data_incarnation;
requestL = REMOTE_find_request(requestL, level);
// Figure out the number of the message that we're stalled on.
USHORT msg_number;
if (!get_next_msg_no(requestL, level, &msg_number)) {
return this->send_response(sendL, 0, 0, status_vector, false);
}
sendL->p_operation = op_response_piggyback;
P_RESP* response = &sendL->p_resp;
response->p_resp_object = msg_number;
response->p_resp_status_vector = status_vector;
response->p_resp_data.cstr_length = 0;
this->send_partial(sendL);
// Fill in packet to fool receive into thinking that it has been
// called directly by the client.
const Rrq::rrq_repeat* tail = &requestL->rrq_rpt[msg_number];
const rem_fmt* format = tail->rrq_format;
data->p_data_message_number = msg_number;
if (this->port_flags & PORT_rpc)
{
data->p_data_messages = 1;
}
else
{
data->p_data_messages =
(USHORT) REMOTE_compute_batch_size(this,
(USHORT) xdr_protocol_overhead(op_response_piggyback),
op_send,
format);
}
return this->receive_msg(data, sendL);
}
ISC_STATUS rem_port::receive_msg(P_DATA * data, PACKET* sendL)
{
/**************************************
*
* r e c e i v e _ m s g
*
**************************************
*
* Functional description
* Receive a message.
*
**************************************/
// Find the database, request, message number, and the number of
// messages the client is willing to cope with. Then locate the
// message control block for the request and message type.
Rrq* requestL;
getHandle(requestL, data->p_data_request);
const USHORT level = data->p_data_incarnation;
requestL = REMOTE_find_request(requestL, level);
const USHORT msg_number = data->p_data_message_number;
USHORT count, count2;
count2 = count = (this->port_flags & PORT_rpc) ? 1 : data->p_data_messages;
ISC_STATUS_ARRAY status_vector;
if (msg_number > requestL->rrq_max_msg)
{
status_vector[0] = isc_arg_gds;
status_vector[1] = isc_badmsgnum;
status_vector[2] = isc_arg_end;
return this->send_response(sendL, 0, 0, status_vector, false);
}
Rrq::rrq_repeat* tail = &requestL->rrq_rpt[msg_number];
const rem_fmt* format = tail->rrq_format;
// Get ready to ship the data out
sendL->p_operation = op_send;
sendL->p_data.p_data_request = data->p_data_request;
sendL->p_data.p_data_message_number = msg_number;
sendL->p_data.p_data_incarnation = level;
sendL->p_data.p_data_messages = 1;
#ifdef SCROLLABLE_CURSORS
// check the direction and offset for possible redirection; if the user
// scrolls in a direction other than we were going or an offset other
// than 1, then we need to resynchronize:
// if the direction is the same as the lookahead cache, scroll forward
// through the cache to see if we find the record; otherwise scroll the
// next layer down backward by an amount equal to the number of records
// in the cache, plus the amount asked for by the next level up
USHORT direction;
ULONG offset;
if (this->port_protocol < PROTOCOL_SCROLLABLE_CURSORS)
{
direction = blr_forward;
offset = 1;
}
else
{
direction = data->p_data_direction;
offset = data->p_data_offset;
tail->rrq_xdr = scroll_cache(tail, &direction, &offset);
}
#endif
// Check to see if any messages are already sitting around
RMessage* message = 0;
while (true)
{
message = tail->rrq_xdr;
// If we don't have a message cached, get one from the next layer down.
if (!message->msg_address)
{
// If we have an error queued for delivery, send it now
if (requestL->rrq_status_vector[1])
{
const ISC_STATUS res =
this->send_response(sendL, 0, 0, requestL->rrq_status_vector, false);
memset(requestL->rrq_status_vector, 0, sizeof(requestL->rrq_status_vector));
return res;
}
#ifdef SCROLLABLE_CURSORS
isc_receive2(status_vector, &requestL->rrq_handle, msg_number,
format->fmt_length, message->msg_buffer, level, direction, offset);
#else
isc_receive(status_vector, &requestL->rrq_handle, msg_number,
format->fmt_length, message->msg_buffer, level);
#endif
if (status_vector[1])
return this->send_response(sendL, 0, 0, status_vector, false);
#ifdef SCROLLABLE_CURSORS
// set the appropriate flags according to the way we just scrolled
// the next layer down, and calculate the offset from the beginning
// of the result set
switch (direction)
{
case blr_forward:
tail->rrq_flags &= ~Rrq::BACKWARD;
tail->rrq_absolute += (tail->rrq_flags & Rrq::ABSOLUTE_BACKWARD) ? -offset : offset;
break;
case blr_backward:
tail->rrq_flags |= Rrq::BACKWARD;
tail->rrq_absolute += (tail->rrq_flags & Rrq::ABSOLUTE_BACKWARD) ? offset : -offset;
break;
case blr_bof_forward:
tail->rrq_flags &= ~Rrq::BACKWARD;
tail->rrq_flags &= ~Rrq::ABSOLUTE_BACKWARD;
tail->rrq_absolute = offset;
direction = blr_forward;
break;
case blr_eof_backward:
tail->rrq_flags |= Rrq::BACKWARD;
tail->rrq_flags |= Rrq::ABSOLUTE_BACKWARD;
tail->rrq_absolute = offset;
direction = blr_backward;
break;
}
message->msg_absolute = tail->rrq_absolute;
// if we have already scrolled to the location indicated,
// then we just want to continue one by one in that direction
offset = 1;
#endif
message->msg_address = message->msg_buffer;
}
// If there aren't any buffers remaining, break out of loop
if (--count <= 0)
break;
// There's a buffer waiting -- see if the request is ready to send
RMessage* next = message->msg_next;
if ((next == message || !next->msg_address) &&
!check_request(requestL, data->p_data_incarnation, msg_number))
{
// We've reached the end of the RSE - don't prefetch and flush
// everything we've buffered so far
count2 = 0;
break;
}
if (!this->send_partial(sendL))
return FALSE;
message->msg_address = NULL;
}
sendL->p_data.p_data_messages = 0;
this->send(sendL);
message->msg_address = NULL;
// Bump up the message pointer to resync with rrq_xdr (rrq_xdr
// was incremented by xdr_request in the SEND call).
tail->rrq_message = message->msg_next;
// Since we have a little time on our hands while this packet is sent
// and processed, get the next batch of records. Start by finding the
// next free buffer.
message = tail->rrq_xdr;
RMessage* prior = NULL;
while (message->msg_address && message->msg_next != tail->rrq_xdr)
message = message->msg_next;
for (; count2 && check_request(requestL, data->p_data_incarnation, msg_number); --count2)
{
if (message->msg_address)
{
if (!prior)
#ifdef SCROLLABLE_CURSORS
prior = message->msg_prior;
#else
prior = tail->rrq_xdr;
while (prior->msg_next != message)
prior = prior->msg_next;
#endif
// allocate a new message block and put it in the cache
message = new RMessage(format->fmt_length);
#ifdef DEBUG_REMOTE_MEMORY
printf("receive_msg(server) allocate message %x\n", message);
#endif
message->msg_number = prior->msg_number;
message->msg_next = prior->msg_next;
#ifdef SCROLLABLE_CURSORS
message->msg_prior = prior;
#endif
prior->msg_next = message;
prior = message;
}
// fetch the next record into cache; even for scrollable cursors, we are
// just doing a simple lookahead continuing on in the last direction specified,
// so there is no reason to do an isc_receive2()
isc_receive(status_vector, &requestL->rrq_handle, msg_number,
format->fmt_length, message->msg_buffer, data->p_data_incarnation);
// Did we have an error? If so, save it for later delivery
if (status_vector[1])
{
// If already have an error queued, don't overwrite it
if (!requestL->rrq_status_vector[1]) {
memcpy(requestL->rrq_status_vector, status_vector, sizeof(requestL->rrq_status_vector));
}
break;
}
#ifdef SCROLLABLE_CURSORS
// if we have already scrolled to the location indicated,
// then we just want to continue on in that direction
switch (direction)
{
case blr_forward:
tail->rrq_absolute += (tail->rrq_flags & Rrq::ABSOLUTE_BACKWARD) ? -offset : offset;
break;
case blr_backward:
tail->rrq_absolute += (tail->rrq_flags & Rrq::ABSOLUTE_BACKWARD) ? offset : -offset;
break;
}
message->msg_absolute = tail->rrq_absolute;
#endif
message->msg_address = message->msg_buffer;
message = message->msg_next;
}
return TRUE;
}
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.
*
**************************************/
Rdb* rdb = blob->rbl_rdb;
Rtr* transaction = blob->rbl_rtr;
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;
}
}
#ifdef DEBUG_REMOTE_MEMORY
printf("release_blob(server) free blob %x\n", blob);
#endif
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_request( Rrq* request)
{
/**************************************
*
* r e l e a s e _ r e q u e s t
*
**************************************
*
* Functional description
* Release a request block.
*
**************************************/
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?
*
**************************************/
delete (*statement)->rsr_select_format;
delete (*statement)->rsr_bind_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;
}
}
#ifdef DEBUG_REMOTE_MEMORY
printf("release_transact(server) free transaction %x\n", transaction);
#endif
delete transaction;
}
#ifdef SCROLLABLE_CURSORS
static RMessage* scroll_cache(Rrq::rrq_repeat* tail, USHORT* direction, ULONG* offset)
{
/**************************************
*
* s c r o l l _ c a c h e
*
**************************************
*
* Functional description
*
* Try to fetch the requested record from cache, if possible. This algorithm depends
* on all scrollable cursors being INSENSITIVE to database changes, so that absolute
* record numbers within the result set will remain constant.
*
* 1. BOF Forward or EOF Backward: Retain the record number of the offset from the
* beginning or end of the result set. If we can figure out the relative offset
* from the absolute, then scroll to it. If it's in cache, great, otherwise dump
* the cache and have the server scroll the correct number of records.
*
* 2. Forward or Backward: Try to scroll the desired offset in cache. If we
* scroll off the end of the cache, dump the cache and ask the server for a
* packetful of records.
*
* This routine differs from the corresponding routine on the client in that
* we are using only a lookahead cache. There is no point in caching records backward,
* in that the client already has them and would not request them from us.
*
**************************************/
// if we are to continue in the current direction, set direction to
// the last direction scrolled; then depending on the direction asked
// for, save the last direction asked for by the next layer above
if (*direction == blr_continue)
{
if (tail->rrq_flags & Rrq::LAST_BACKWARD)
*direction = blr_backward;
else
*direction = blr_forward;
}
if (*direction == blr_forward || *direction == blr_bof_forward)
tail->rrq_flags &= ~Rrq::LAST_BACKWARD;
else
tail->rrq_flags |= Rrq::LAST_BACKWARD;
// set to the first message; if it has no record, this means the cache is
// empty and there is no point in trying to find the record here
RMessage* message = tail->rrq_xdr;
if (!message->msg_address)
return message;
// if we are scrolling from BOF and the cache was started from EOF (or vice-versa),
// the cache is unusable.
if ((*direction == blr_bof_forward && (tail->rrq_flags & Rrq::ABSOLUTE_BACKWARD)) ||
(*direction == blr_eof_backward && !(tail->rrq_flags & Rrq::ABSOLUTE_BACKWARD)))
{
return dump_cache(tail);
}
// if we are going to an absolute position, see if we can find that position
// in cache, otherwise change to a relative seek from our former position
if (*direction == blr_bof_forward || *direction == blr_eof_backward)
{
// If offset is before our current position, just dump the cache because
// the server cache is purely a lookahead cache--we don't bother to cache
// back records because the client will cache those records, making it
// unlikely the client would be asking us for a record which is in our cache.
if (*offset < message->msg_absolute)
return dump_cache(tail);
// convert the absolute to relative, and prepare to scroll forward to look for the record
*offset -= message->msg_absolute;
if (*direction == blr_bof_forward)
*direction = blr_forward;
else
*direction = blr_backward;
}
if ((*direction == blr_forward && (tail->rrq_flags & Rrq::BACKWARD)) ||
(*direction == blr_backward && !(tail->rrq_flags & Rrq::BACKWARD)))
{
// lookahead cache was in opposite direction from the scroll direction,
// so increase the scroll amount by the amount we looked ahead, then
// dump the cache
for (message = tail->rrq_xdr; message->msg_address;)
{
(*offset)++;
message = message->msg_next;
if (message == tail->rrq_message)
break;
}
return dump_cache(tail);
}
// lookahead cache is in same direction we want to scroll, so scroll
// forward through the cache, decrementing the offset
for (message = tail->rrq_xdr; message->msg_address;)
{
if (*offset == 1)
break;
(*offset)--;
message = message->msg_next;
if (message == tail->rrq_message)
break;
}
return message;
}
#endif
ISC_STATUS rem_port::seek_blob(P_SEEK* seek, PACKET* sendL)
{
/**************************************
*
* s e e k _ b l o b
*
**************************************
*
* Functional description
* Execute a blob seek operation.
*
**************************************/
Rbl* blob;
getHandle(blob, seek->p_seek_blob);
const SSHORT mode = seek->p_seek_mode;
const SLONG offset = seek->p_seek_offset;
ISC_STATUS_ARRAY status_vector;
SLONG result;
isc_seek_blob(status_vector, &blob->rbl_handle, mode, offset, &result);
sendL->p_resp.p_resp_blob_id.bid_quad_low = result;
return this->send_response(sendL, 0, 0, status_vector, false);
}
ISC_STATUS rem_port::send_msg(P_DATA * data, PACKET* sendL)
{
/**************************************
*
* s e n d _ m s g
*
**************************************
*
* Functional description
* Handle a isc_send operation.
*
**************************************/
ISC_STATUS_ARRAY status_vector;
Rrq* requestL;
getHandle(requestL, data->p_data_request);
const USHORT number = data->p_data_message_number;
requestL = REMOTE_find_request(requestL, data->p_data_incarnation);
if (number > requestL->rrq_max_msg)
{
status_vector[0] = isc_arg_gds;
status_vector[1] = isc_badmsgnum;
status_vector[2] = isc_arg_end;
return this->send_response(sendL, 0, 0, status_vector, false);
}
RMessage* message = requestL->rrq_rpt[number].rrq_message;
const rem_fmt* format = requestL->rrq_rpt[number].rrq_format;
isc_send(status_vector, &requestL->rrq_handle, number, format->fmt_length,
message->msg_address, data->p_data_incarnation);
message->msg_address = NULL;
return this->send_response(sendL, 0, 0, status_vector, false);
}
ISC_STATUS rem_port::send_response( PACKET* sendL,
OBJCT object,
USHORT length,
const ISC_STATUS* status_vector,
bool defer_flag)
{
/**************************************
*
* s e n d _ r e s p o n s e
*
**************************************
*
* Functional description
* Send a response packet.
*
**************************************/
// Start by translating the status vector into "generic" form
ISC_STATUS_ARRAY new_vector;
ISC_STATUS* v = new_vector;
TEXT buffer[1024];
const char* const bufferEnd = buffer + sizeof(buffer);
TEXT* p = buffer;
const ISC_STATUS exit_code = status_vector[1];
for (bool sw = true; *status_vector && sw;)
{
switch (*status_vector)
{
case isc_arg_warning:
case isc_arg_gds:
// When talking with older (pre 6.0) clients, do not send warnings
if (*status_vector == isc_arg_warning && this->port_protocol < PROTOCOL_VERSION10)
{
sw = false;
continue;
}
*v++ = *status_vector++;
// The status codes are converted to their offsets so that they
// were compatible with the RDB implementation. This was fine
// when status codes were restricted to a single facility. Now
// that the facility is part of the status code we need to know
// this on the client side, thus when talking with 6.0 and newer
// clients, do not decode the status code, just send it to the
// client. The same check is made in interface.cpp::check_response
if (this->port_protocol < PROTOCOL_VERSION10)
{
USHORT fac = 0, code_class = 0;
*v++ = gds__decode(*status_vector++, &fac, &code_class);
}
else {
*v++ = *status_vector++;
}
for (;;)
{
switch (*status_vector)
{
case isc_arg_string:
case isc_arg_number:
*v++ = *status_vector++;
*v++ = *status_vector++;
continue;
case isc_arg_cstring:
++status_vector;
*v++ = isc_arg_string;
if (p < bufferEnd) // CVC: Avoid B.O.
{
TEXT** sp = (TEXT**) v;
*sp++ = p;
v = (ISC_STATUS*) sp;
SLONG l = *status_vector++;
const TEXT* q = (TEXT*) *status_vector++;
while (l-- > 0 && (p < bufferEnd - 1))
*p++ = *q++;
*p++ = 0;
}
else
{
// See if we can honor zero length strings, else use an error msg
TEXT** sp = (TEXT**) v;
if (!*status_vector) // this is the length
*sp++ = const_cast<char*>("");
else
*sp++ = const_cast<char*>("Not enough buffer for message");
v = (ISC_STATUS*) sp;
status_vector += 2;
}
continue;
} // switch
break;
} // for (;;)
continue;
case isc_arg_interpreted:
case isc_arg_sql_state:
*v++ = *status_vector++;
*v++ = *status_vector++;
continue;
}
const int l = (p < bufferEnd) ? fb_interpret(p, bufferEnd - p, &status_vector) : 0;
if (l == 0)
break;
*v++ = isc_arg_interpreted;
TEXT** sp = (TEXT**) v;
*sp++ = p;
v = (ISC_STATUS *) sp;
p += l;
sw = false;
}
*v = isc_arg_end;
// Format and send response. Note: the blob_id and data address fields
// of the response packet may contain valid data. Don't trash them.
sendL->p_operation = op_response;
P_RESP* response = &sendL->p_resp;
response->p_resp_object = object;
response->p_resp_status_vector = new_vector;
response->p_resp_data.cstr_length = length;
const bool defer = (this->port_flags & PORT_lazy) && defer_flag;
if (defer) {
this->send_partial(sendL);
}
else {
this->send(sendL);
}
// If database or attachment has been shut down,
// there's no point in keeping the connection open
if (!defer && (exit_code == isc_shutdown || exit_code == isc_att_shutdown))
{
this->port_state = rem_port::BROKEN;
this->port_flags |= PORT_rdb_shutdown;
}
return exit_code;
}
// Maybe this can be a member of rem_port?
static void send_error(rem_port* port, PACKET* apacket, ISC_STATUS errcode)
{
ISC_STATUS_ARRAY status_vector;
status_vector[0] = isc_arg_gds;
status_vector[1] = errcode;
status_vector[2] = isc_arg_end;
port->send_response(apacket, 0, 0, status_vector, false);
}
static void server_ast(void* event_void, USHORT length, const UCHAR* items)
{
/**************************************
*
* s e r v e r _ a s t
*
**************************************
*
* Functional description
* Send an asynchrous event packet back to client.
*
**************************************/
Rvnt* event = static_cast<Rvnt*>(event_void);
event->rvnt_id = 0;
Rdb* rdb = event->rvnt_rdb;
rem_port* port = rdb->rdb_port->port_async;
if (!port) {
return;
}
Firebird::RefMutexGuard portGuard(*port->port_sync);
PACKET packet;
packet.p_operation = op_event;
P_EVENT* p_event = &packet.p_event;
p_event->p_event_database = rdb->rdb_id;
p_event->p_event_items.cstr_length = length;
p_event->p_event_items.cstr_address = items;
// Nickolay Samofatov: We keep these values and even pass them to the client
// (as 32-bit values) when event is fired, but client ignores them.
p_event->p_event_ast = event->rvnt_ast;
p_event->p_event_arg = (SLONG)(IPTR) event->rvnt_arg;
p_event->p_event_rid = event->rvnt_rid;
port->send(&packet);
}
static void attach_service(rem_port* port, P_ATCH* attach, PACKET* sendL)
{
const char* service_name = reinterpret_cast<const char*>(attach->p_atch_file.cstr_address);
const USHORT service_length = attach->p_atch_file.cstr_length;
Firebird::ClumpletWriter spb(Firebird::ClumpletReader::SpbAttach, MAX_DPB_SIZE,
attach->p_atch_dpb.cstr_address, attach->p_atch_dpb.cstr_length, isc_spb_current_version);
// remove trusted role if present (security measure)
spb.deleteWithTag(isc_spb_trusted_role);
#ifdef TRUSTED_AUTH
// Do we can & need trusted authentication?
if (spb.find(isc_spb_trusted_auth))
{
try
{
// extract trusted authentication data from spb
AuthSspi::DataHolder data;
memcpy(data.getBuffer(spb.getClumpLength()), spb.getBytes(), spb.getClumpLength());
spb.deleteClumplet();
// remove extra trusted auth if present (security measure)
spb.deleteWithTag(isc_spb_trusted_auth);
if (canUseTrusted() && port->port_protocol >= PROTOCOL_VERSION11)
{
port->port_trusted_auth =
new ServerAuth(service_name, service_length, spb, attach_service2, op_trusted_auth);
AuthSspi* authSspi = port->port_trusted_auth->authSspi;
if (authSspi->accept(data) && authSspi->isActive())
{
sendL->p_operation = op_trusted_auth;
cstring& s = sendL->p_trau.p_trau_data;
s.cstr_allocated = 0;
s.cstr_length = data.getCount();
s.cstr_address = data.begin();
port->send(sendL);
return;
}
}
}
catch (const Firebird::status_exception& e)
{
ISC_STATUS_ARRAY status_vector;
Firebird::stuff_exception(status_vector, e);
delete port->port_trusted_auth;
port->port_trusted_auth = 0;
port->send_response(sendL, 0, 0, status_vector, false);
return;
}
}
#else // TRUSTED_AUTH
// remove trusted auth if present (security measure)
spb.deleteWithTag(isc_spb_trusted_auth);
#endif // TRUSTED_AUTH
attach_service2(port, op_trusted_auth, service_name, service_length,
spb.getBuffer(), spb.getBufferLength(), sendL);
}
static void attach_service2(rem_port* port,
P_OP,
const char* service_name,
int service_length,
const UCHAR* spb,
int sl,
PACKET* sendL)
{
Firebird::ClumpletWriter tmp(Firebird::ClumpletReader::SpbAttach, MAX_DPB_SIZE,
spb, sl, isc_spb_current_version);
port->service_attach(service_name, service_length, tmp, sendL);
#ifdef TRUSTED_AUTH
delete port->port_trusted_auth;
port->port_trusted_auth = 0;
#endif
}
ISC_STATUS rem_port::service_attach(const char* service_name,
const USHORT service_length,
Firebird::ClumpletWriter& spb,
PACKET* sendL)
{
/**************************************
*
* s e r v i c e _ a t t a c h
*
**************************************
*
* Functional description
* Connect to a Firebird service.
*
**************************************/
sendL->p_operation = op_accept;
FB_API_HANDLE handle = 0;
#ifdef TRUSTED_AUTH
// If we have trusted authentication, append it to database parameter block
if (port_trusted_auth)
{
AuthSspi* authSspi = port_trusted_auth->authSspi;
Firebird::string trustedUserName;
bool wheel = false;
if (authSspi->getLogin(trustedUserName, wheel))
{
spb.insertString(isc_spb_trusted_auth, trustedUserName);
if (wheel && !spb.find(isc_spb_sql_role_name))
{
spb.insertString(isc_spb_trusted_role, ADMIN_ROLE, strlen(ADMIN_ROLE));
}
}
}
#endif // TRUSTED_AUTH
// If we have user identification, append it to database parameter block
const rem_str* string = port_user_name;
if (string)
{
spb.setCurOffset(spb.getBufferLength());
spb.insertString(isc_spb_sys_user_name, string->str_data, string->str_length);
}
// Now insert additional clumplets into spb
addClumplets(spb, spbParam, this);
// See if user has specified parameters relevent to the connection,
// they will be stuffed in the SPB if so.
REMOTE_get_timeout_params(this, &spb);
ISC_STATUS_ARRAY status_vector;
isc_service_attach(status_vector, service_length, service_name, &handle,
spb.getBufferLength(), reinterpret_cast<const char*>(spb.getBuffer()));
if (!status_vector[1])
{
Rdb* rdb = new Rdb;
if (rdb)
{
this->port_context = rdb;
#ifdef DEBUG_REMOTE_MEMORY
printf("attach_service(server) allocate rdb %x\n", rdb);
#endif
rdb->rdb_port = this;
rdb->rdb_handle = handle;
rdb->rdb_flags |= Rdb::SERVICE;
}
else
{
status_vector[0] = isc_arg_gds;
status_vector[1] = isc_virmemexh;
status_vector[2] = isc_arg_end;
}
}
return this->send_response(sendL, 0, 0, status_vector, false);
}
ISC_STATUS rem_port::service_end(P_RLSE* /*release*/, PACKET* sendL)
{
/**************************************
*
* s e r v i c e _ e n d
*
**************************************
*
* Functional description
* Close down a service.
*
**************************************/
ISC_STATUS_ARRAY status_vector;
Rdb* rdb = this->port_context;
if (bad_service(status_vector, rdb))
{
return this->send_response(sendL, 0, 0, status_vector, false);
}
isc_service_detach(status_vector, &rdb->rdb_handle);
if (status_vector[1] == 0) {
port_flags |= PORT_detached;
}
return this->send_response(sendL, 0, 0, status_vector, false);
}
ISC_STATUS rem_port::service_start(P_INFO * stuff, PACKET* sendL)
{
/**************************************
*
* s e r v i c e _ s t a r t
*
**************************************
*
* Functional description
* Start a service on the server
*
**************************************/
ISC_STATUS_ARRAY status_vector;
Rdb* rdb = this->port_context;
if (bad_service(status_vector, rdb))
{
return this->send_response(sendL, 0, 0, status_vector, false);
}
SLONG* reserved = 0; // reserved for future use
isc_service_start(status_vector,
&rdb->rdb_handle,
reserved,
stuff->p_info_items.cstr_length,
reinterpret_cast<const char*>(stuff->p_info_items.cstr_address));
return this->send_response(sendL, 0, 0, status_vector, false);
}
ISC_STATUS rem_port::set_cursor(P_SQLCUR * sqlcur, PACKET* sendL)
{
/*****************************************
*
* s e t _ c u r s o r
*
*****************************************
*
* Functional description
* Set a cursor name for a dynamic request.
*
*****************************************/
Rsr* statement;
ISC_STATUS_ARRAY status_vector;
getHandle(statement, sqlcur->p_sqlcur_statement);
GDS_DSQL_SET_CURSOR(status_vector,
&statement->rsr_handle,
reinterpret_cast<const char*>(sqlcur->p_sqlcur_cursor_name.cstr_address),
sqlcur->p_sqlcur_type);
return this->send_response(sendL, 0, 0, status_vector, false);
}
void set_server( rem_port* port, USHORT flags)
{
/**************************************
*
* s e t _ s e r v e r
*
**************************************
*
* Functional description
* Look up the server for this type
* of port. If one doesn't exist,
* create it.
*
**************************************/
Firebird::MutexLockGuard srvrGuard(servers_mutex);
srvr* server;
for (server = servers; server; server = server->srvr_next)
{
if (port->port_type == server->srvr_port_type) {
break;
}
}
if (!server)
{
servers = server = new srvr(servers, port, flags);
fb_shutdown_callback(0, shut_server, fb_shut_postproviders, 0);
}
port->port_server = server;
}
ISC_STATUS rem_port::start(P_OP operation, P_DATA * data, PACKET* sendL)
{
/**************************************
*
* s t a r t
*
**************************************
*
* Functional description
*
**************************************/
Rtr* transaction;
getHandle(transaction, data->p_data_transaction);
Rrq* requestL;
getHandle(requestL, data->p_data_request);
requestL = REMOTE_find_request(requestL, data->p_data_incarnation);
REMOTE_reset_request(requestL, 0);
ISC_STATUS_ARRAY status_vector;
isc_start_request(status_vector, &requestL->rrq_handle,
&transaction->rtr_handle, data->p_data_incarnation);
if (!status_vector[1])
{
requestL->rrq_rtr = transaction;
if (operation == op_start_and_receive)
return this->receive_after_start(data, sendL, status_vector);
}
return this->send_response(sendL, 0, 0, status_vector, false);
}
ISC_STATUS rem_port::start_and_send(P_OP operation, P_DATA* data, PACKET* sendL)
{
/**************************************
*
* s t a r t _ a n d _ s e n d
*
**************************************
*
* Functional description
*
**************************************/
ISC_STATUS_ARRAY status_vector;
Rtr* transaction;
getHandle(transaction, data->p_data_transaction);
Rrq* requestL;
getHandle(requestL, data->p_data_request);
requestL = REMOTE_find_request(requestL, data->p_data_incarnation);
const USHORT number = data->p_data_message_number;
if (number > requestL->rrq_max_msg)
{
status_vector[0] = isc_arg_gds;
status_vector[1] = isc_badmsgnum;
status_vector[2] = isc_arg_end;
return this->send_response(sendL, 0, 0, status_vector, false);
}
RMessage* message = requestL->rrq_rpt[number].rrq_message;
const rem_fmt* format = requestL->rrq_rpt[number].rrq_format;
REMOTE_reset_request(requestL, message);
isc_start_and_send(status_vector, &requestL->rrq_handle,
&transaction->rtr_handle, number,
format->fmt_length, message->msg_address,
data->p_data_incarnation);
if (!status_vector[1])
{
requestL->rrq_rtr = transaction;
if (operation == op_start_send_and_receive) {
return this->receive_after_start(data, sendL, status_vector);
}
}
return this->send_response(sendL, 0, 0, status_vector, false);
}
ISC_STATUS rem_port::start_transaction(P_OP operation, P_STTR * stuff, PACKET* sendL)
{
/**************************************
*
* s t a r t _ t r a n s a c t i o n
*
**************************************
*
* Functional description
* Start a transaction.
*
**************************************/
ISC_STATUS_ARRAY status_vector;
Rdb* rdb = this->port_context;
if (bad_db(status_vector, rdb))
{
return this->send_response(sendL, 0, 0, status_vector, false);
}
FB_API_HANDLE handle = 0;
if (operation == op_reconnect)
isc_reconnect_transaction(status_vector, &rdb->rdb_handle, &handle,
stuff->p_sttr_tpb.cstr_length,
reinterpret_cast<const char*>(stuff->p_sttr_tpb.cstr_address));
else
isc_start_transaction(status_vector, &handle, (SSHORT) 1, &rdb->rdb_handle,
stuff->p_sttr_tpb.cstr_length,
stuff->p_sttr_tpb.cstr_address);
OBJCT object;
if (status_vector[1])
{
object = 0;
}
else
{
Rtr* transaction = make_transaction(rdb, handle);
if (transaction)
{
object = transaction->rtr_id;
if (operation == op_reconnect)
transaction->rtr_limbo = true;
#ifdef DEBUG_REMOTE_MEMORY
printf("start_transaction(server) allocate trans %x\n", transaction);
#endif
}
else
{
object = 0;
if (operation != op_reconnect)
isc_rollback_transaction(status_vector, &handle);
#ifdef SUPERSERVER
// Note that there is an underlying transaction pool
// that won't be released until this connection is
// detached. At least release the y-valve handle.
else
fb_disconnect_transaction(status_vector, &handle);
#endif
status_vector[0] = isc_arg_gds;
status_vector[1] = isc_too_many_handles;
status_vector[2] = isc_arg_end;
}
}
return this->send_response(sendL, object, 0, status_vector, false);
}
static void success( ISC_STATUS* status_vector)
{
/**************************************
*
* s u c c e s s
*
**************************************
*
* Functional description
* Set status vector to indicate success.
*
**************************************/
status_vector[0] = isc_arg_gds;
status_vector[1] = FB_SUCCESS;
status_vector[2] = isc_arg_end;
}
static THREAD_ENTRY_DECLARE loopThread(THREAD_ENTRY_PARAM)
{
/**************************************
*
* l o o p T h r e a d
*
**************************************
*
* Functional description
* Execute requests in a happy loop.
*
**************************************/
try {
Firebird::FpeControl::maskAll();
Worker worker;
while (!Worker::isShuttingDown())
{
Firebird::MutexEnsureUnlock reqQueGuard(request_que_mutex);
reqQueGuard.enter();
server_req_t* request = request_que;
if (request)
{
worker.setState(true);
REMOTE_TRACE(("Dequeue request %p", request_que));
request_que = request->req_next;
ports_pending--;
reqQueGuard.leave();
while (request)
{
rem_port* port = NULL;
// Bind a thread to a port.
if (request->req_port->port_server_flags & SRVR_thread_per_port)
{
port = request->req_port;
free_request(request);
SRVR_main(port, port->port_server_flags);
request = 0;
continue;
}
// Splice request into list of active requests, execute request,
// and unsplice
{ // scope
Firebird::MutexLockGuard queGuard(request_que_mutex);
request->req_next = active_requests;
active_requests = request;
ports_active++;
}
// Validate port. If it looks ok, process request
Firebird::RefMutexEnsureUnlock portQueGuard(*request->req_port->port_que_sync);
{ // port_sync scope
Firebird::RefMutexGuard portGuard(*request->req_port->port_sync);
if (request->req_port->port_state == rem_port::DISCONNECTED ||
!process_packet(request->req_port, &request->req_send, &request->req_receive, &port))
{
port = NULL;
}
// With lazy port feature enabled we can have more received and
// not handled data in receive queue. Handle it now if it contains
// whole request packet. If it contain partial packet don't clear
// request queue, restore receive buffer state to state before
// reading packet and wait until rest of data arrives
if (port)
{
fb_assert(port == request->req_port);
// It is very important to not release port_que_sync before
// port_sync, else we can miss data arrived at time between
// releasing locks and will never handle it. Therefore we
// can't ise MutexLockGuard here
portQueGuard.enter();
if (port->haveRecvData())
{
server_req_t* new_request = alloc_request();
const rem_port::RecvQueState recvState = port->getRecvState();
port->receive(&new_request->req_receive);
if (new_request->req_receive.p_operation == op_partial)
{
// gds__log("Partial");
free_request(new_request);
port->setRecvState(recvState);
}
else
{
if (!port->haveRecvData())
port->clearRecvQue();
new_request->req_port = port;
#ifdef DEV_BUILD
const bool ok =
#endif
link_request(port, new_request);
fb_assert(ok);
}
}
}
} // port_sync scope
if (port) {
portQueGuard.leave();
}
{ // request_que_mutex scope
Firebird::MutexLockGuard queGuard(request_que_mutex);
// Take request out of list of active requests
for (server_req_t** req_ptr = &active_requests; *req_ptr;
req_ptr = &(*req_ptr)->req_next)
{
if (*req_ptr == request)
{
*req_ptr = request->req_next;
ports_active--;
break;
}
}
// If this is a explicit or implicit disconnect, get rid of
// any chained requests
if (!port)
{
server_req_t* next;
while (next = request->req_chain)
{
request->req_chain = next->req_chain;
free_request(next);
}
if (request->req_send.p_operation == op_void &&
request->req_receive.p_operation == op_void)
{
delete request;
request = 0;
}
}
else
{
#ifdef DEBUG_REMOTE_MEMORY
printf("thread ACTIVE request_queued %d\n",
port->port_requests_queued.value());
fflush(stdout);
#endif
}
// Pick up any remaining chained request, and free current request
if (request)
{
server_req_t* next = request->req_chain;
free_request(request);
//request = next;
// Try to be fair - put new request at the end of waiting
// requests queue and take request to work on from the
// head of the queue
if (next)
{
append_request_next(next, &request_que);
request = request_que;
request_que = request->req_next;
ports_pending--;
}
else {
request = NULL;
}
}
} // request_que_mutex scope
} // while (request)
}
else
{
worker.setState(false);
reqQueGuard.leave();
if (Worker::isShuttingDown())
break;
if (!worker.wait())
break;
}
}
} // try
catch (const Firebird::Exception& ex)
{
iscLogException("Error while processing the incoming packet", ex);
}
return 0;
}
SSHORT rem_port::asyncReceive(PACKET* asyncPacket, const UCHAR* buffer, SSHORT dataSize)
{
/**************************************
*
* a s y n c R e c e i v e
*
**************************************
*
* Functional description
* If possible, accept incoming data asynchronously
*
**************************************/
if (! port_async_receive)
{
return 0;
}
if (haveRecvData())
{
// We have no reliable way to distinguish network packet that start
// from the beginning of XDR packet or in the middle of it.
// Therefore try to process asynchronously only if there is no data
// waiting in port queue. This can lead to fallback to synchronous
// processing of async command (i.e. with some delay), but reliably
// protects from spurious protocol breakage.
return 0;
}
switch (getOperation(buffer, dataSize))
{
case op_cancel:
break;
default:
return 0;
}
{ // scope for guard
static Firebird::GlobalPtr<Firebird::Mutex> mutex;
Firebird::MutexLockGuard guard(mutex);
port_async_receive->clearRecvQue();
port_async_receive->port_receive.x_handy = 0;
memcpy(port_async_receive->port_queue.add().getBuffer(dataSize), buffer, dataSize);
// It's required, that async packets follow simple rule:
// xdr packet fits into network packet.
port_async_receive->receive(asyncPacket);
}
const SSHORT asyncSize = dataSize - port_async_receive->port_receive.x_handy;
fb_assert(asyncSize >= 0);
switch (asyncPacket->p_operation)
{
case op_cancel:
cancel_operation(this, asyncPacket->p_cancel_op.p_co_kind);
break;
default:
return 0;
}
return asyncSize;
}
ISC_STATUS rem_port::transact_request(P_TRRQ* trrq, PACKET* sendL)
{
/**************************************
*
* t r a n s a c t _ r e q u e s t
*
**************************************
*
* Functional description
*
**************************************/
ISC_STATUS_ARRAY status_vector;
Rtr* transaction;
getHandle(transaction, trrq->p_trrq_transaction);
Rdb* rdb = this->port_context;
if (bad_db(status_vector, rdb))
{
return this->send_response(sendL, 0, 0, status_vector, false);
}
UCHAR* blr = trrq->p_trrq_blr.cstr_address;
const USHORT blr_length = trrq->p_trrq_blr.cstr_length;
Rpr* procedure = this->port_rpr;
UCHAR* in_msg = (procedure->rpr_in_msg) ? procedure->rpr_in_msg->msg_address : NULL;
const USHORT in_msg_length =
(procedure->rpr_in_format) ? procedure->rpr_in_format->fmt_length : 0;
UCHAR* out_msg = (procedure->rpr_out_msg) ? procedure->rpr_out_msg->msg_address : NULL;
const USHORT out_msg_length =
(procedure->rpr_out_format) ? procedure->rpr_out_format->fmt_length : 0;
isc_transact_request(status_vector,
&rdb->rdb_handle,
&transaction->rtr_handle,
blr_length,
reinterpret_cast<char*>(blr),
in_msg_length,
reinterpret_cast<char*>(in_msg),
out_msg_length,
reinterpret_cast<char*>(out_msg));
if (status_vector[1])
return this->send_response(sendL, 0, 0, status_vector, false);
P_DATA* data = &sendL->p_data;
sendL->p_operation = op_transact_response;
data->p_data_messages = 1;
this->send(sendL);
return FB_SUCCESS;
}
static void zap_packet(PACKET* packet, bool new_packet)
{
/**************************************
*
* z a p _ p a c k e t
*
**************************************
*
* Functional description
* Zero out a packet block.
*
**************************************/
if (new_packet)
memset(packet, 0, sizeof(PACKET));
else
{
#ifdef DEBUG_XDR_MEMORY
// Don't trash debug xdr memory allocation table of recycled packets.
memset(&packet->p_operation, 0, sizeof(PACKET) - OFFSET(PACKET*, p_operation));
#else
memset(packet, 0, sizeof(PACKET));
#endif
}
}
Worker::Worker()
{
m_active = false;
m_next = m_prev = NULL;
#ifdef DEV_BUILD
m_tid = getThreadId();
#endif
Firebird::MutexLockGuard guard(m_mutex);
insert(m_active);
}
Worker::~Worker()
{
Firebird::MutexLockGuard guard(m_mutex);
if (inList())
{
remove();
--m_cntAll;
}
}
bool Worker::wait(int timeout)
{
if (m_sem.tryEnter(timeout))
return true;
Firebird::MutexLockGuard guard(m_mutex);
if (m_sem.tryEnter(0))
return true;
fb_assert( inList() );
remove();
--m_cntAll;
return false;
}
void Worker::setState(const bool active)
{
if (m_active == active)
return;
Firebird::MutexLockGuard guard(m_mutex);
remove();
insert(active);
}
bool Worker::wakeUp()
{
Firebird::MutexLockGuard reqQueGuard(request_que_mutex);
#ifdef _DEBUG
int cnt = 0;
for (server_req_t* req = request_que; req; req = req->req_next)
cnt++;
fb_assert(cnt == ports_pending);
cnt = 0;
for (server_req_t* req = active_requests; req; req = req->req_next)
cnt++;
fb_assert(cnt == ports_active);
#endif
if (!ports_pending)
return true;
Firebird::MutexLockGuard guard(m_mutex);
if (m_idleWorkers)
{
Worker* idle = m_idleWorkers;
idle->setState(true);
idle->m_sem.release();
return true;
}
if (m_cntAll >= ports_active + ports_pending)
return true;
return (m_cntAll >= MAX_THREADS);
}
void Worker::wakeUpAll()
{
Firebird::MutexLockGuard guard(m_mutex);
for (Worker* thd = m_idleWorkers; thd; thd = thd->m_next)
thd->m_sem.release();
}
void Worker::remove()
{
if (!m_active && (m_next || m_prev || m_idleWorkers == this)) {
m_cntIdle--;
}
if (m_idleWorkers == this) {
m_idleWorkers = this->m_next;
}
if (m_activeWorkers == this) {
m_activeWorkers = this->m_next;
}
if (m_next) {
m_next->m_prev = this->m_prev;
}
if (m_prev) {
m_prev->m_next = this->m_next;
}
m_prev = m_next = NULL;
}
void Worker::insert(const bool active)
{
fb_assert(!m_next);
fb_assert(!m_prev);
fb_assert(m_idleWorkers != this);
fb_assert(m_activeWorkers != this);
Worker** list = active ? &m_activeWorkers : &m_idleWorkers;
m_next = *list;
if (*list) {
(*list)->m_prev = this;
}
*list = this;
m_active = active;
if (!m_active)
{
m_cntIdle++;
fb_assert(m_idleWorkers == this);
}
else
fb_assert(m_activeWorkers == this);
}
bool Worker::inList() const
{
return m_next || m_prev || (m_idleWorkers == this) || (m_activeWorkers == this);
}
void Worker::start(USHORT flags)
{
if (!isShuttingDown() && !wakeUp())
{
if (isShuttingDown())
return;
Firebird::MutexLockGuard guard(m_mutex);
if (gds__thread_start(loopThread, (void*)(IPTR) flags, THREAD_medium, 0, 0) == 0)
{
++m_cntAll;
}
else if (!m_cntAll)
{
Firebird::Arg::Gds(isc_no_threads).raise();
}
}
}
void Worker::shutdown()
{
Firebird::MutexLockGuard guard(m_mutex);
if (shutting_down)
{
return;
}
shutting_down = true;
while (getCount())
{
wakeUpAll();
m_mutex->leave(); // we need CheckoutGuard here
try
{
THREAD_SLEEP(100);
}
catch (const Firebird::Exception&)
{
m_mutex->enter();
throw;
}
m_mutex->enter();
}
}
static int shut_server(const int, const int, void*)
{
server_shutdown = true;
return 0;
}