8
0
mirror of https://github.com/FirebirdSQL/firebird.git synced 2025-01-24 20:43:04 +01:00
firebird-mirror/src/jrd/svc.cpp

3208 lines
72 KiB
C++

/*
* PROGRAM: JRD Access Method
* MODULE: svc.cpp
* DESCRIPTION: Service manager functions
*
* 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.15 Sean Leyne - Code Cleanup, removed obsolete "EPSON" define
* 2002.02.15 Sean Leyne - Code Cleanup, removed obsolete "IMP" port
*
* 2002.10.27 Sean Leyne - Completed removal of obsolete "DELTA" port
* 2002.10.27 Sean Leyne - Completed removal of obsolete "IMP" port
*
* 2002.10.29 Sean Leyne - Removed obsolete "Netware" port
*
* 2008 Alex Peshkoff - refactored services code for MT safe engine
*/
#include "firebird.h"
#include <stdio.h>
#include <string.h>
#include "../common/file_params.h"
#include <stdarg.h>
#include "../jrd/jrd.h"
#include "../jrd/svc.h"
#include "../jrd/constants.h"
#include "gen/iberror.h"
#include "../jrd/license.h"
#include "../jrd/err_proto.h"
#include "../yvalve/gds_proto.h"
#include "../jrd/inf_proto.h"
#include "../common/isc_proto.h"
#include "../jrd/jrd_proto.h"
#include "../jrd/mov_proto.h"
#include "../yvalve/why_proto.h"
#include "../jrd/jrd_proto.h"
#include "../common/classes/alloc.h"
#include "../common/classes/init.h"
#include "../common/classes/ClumpletWriter.h"
#include "../common/utils_proto.h"
#include "../common/db_alias.h"
#include "../jrd/scl.h"
#include "../common/msg_encode.h"
#include "../jrd/trace/TraceManager.h"
#include "../jrd/trace/TraceObjects.h"
#include "../jrd/EngineInterface.h"
#include "../jrd/Mapping.h"
#include "../common/classes/RefMutex.h"
#include "../common/os/os_utils.h"
#include "../common/classes/DbImplementation.h"
// Services table.
#include "../jrd/svc_tab.h"
// The switches tables. Needed only for utilities that run as service, too.
#include "../common/classes/Switches.h"
#include "../alice/aliceswi.h"
#include "../burp/burpswi.h"
#include "../utilities/gsec/gsecswi.h"
#include "../utilities/gstat/dbaswi.h"
#include "../utilities/nbackup/nbkswi.h"
#include "../jrd/trace/traceswi.h"
#include "../jrd/val_proto.h"
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef HAVE_SYS_WAIT_H
# include <sys/wait.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_VFORK_H
#include <vfork.h>
#endif
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
#if !defined(WIN_NT)
# include <signal.h>
# include <sys/param.h>
# include <errno.h>
#else
# include <windows.h>
# include <io.h> // _open, _get_osfhandle
# include <stdlib.h>
#endif
#include <sys/stat.h>
#define statistics stat
using namespace Firebird;
const int SVC_user_dba = 2;
const int SVC_user_any = 1;
const int SVC_user_none = 0;
const int GET_LINE = 1;
const int GET_EOF = 2;
const int GET_BINARY = 4;
const int GET_ONCE = 8;
const char* const SPB_SEC_USERNAME = "isc_spb_sec_username";
namespace {
// Generic mutex to synchronize services
GlobalPtr<Mutex> globalServicesMutex;
// All that we need to shutdown service threads when shutdown in progress
typedef Array<Jrd::Service*> AllServices;
GlobalPtr<AllServices> allServices; // protected by globalServicesMutex
volatile bool svcShutdown = false;
class ThreadCollect
{
public:
ThreadCollect(MemoryPool& p)
: threads(p)
{ }
void join()
{
// join threads to be sure they are gone when shutdown is complete
// no need locking something cause this is expected to run when services are closing
waitFor(threads);
}
void add(Thread::Handle& h)
{
// put thread into completion wait queue when it finished running
MutexLockGuard g(threadsMutex, FB_FUNCTION);
threads.add(h);
}
void houseKeeping()
{
if (!threads.hasData())
return;
// join finished threads
AllThreads t;
{ // mutex scope
MutexLockGuard g(threadsMutex, FB_FUNCTION);
t.assign(threads);
threads.clear();
}
waitFor(t);
}
private:
typedef Array<Thread::Handle> AllThreads;
static void waitFor(AllThreads& thr)
{
while (thr.hasData())
{
Thread::Handle h(thr.pop());
Thread::waitForCompletion(h);
}
}
AllThreads threads;
Mutex threadsMutex;
};
GlobalPtr<ThreadCollect> threadCollect;
void spbVersionError()
{
ERR_post(Arg::Gds(isc_bad_spb_form) <<
Arg::Gds(isc_wrospbver));
}
} // anonymous namespace
using namespace Jrd;
Service::Validate::Validate(Service* svc)
: sharedGuard(globalServicesMutex, FB_FUNCTION)
{
sharedGuard.enter();
if (!svc->locateInAllServices())
{
// Service is so old that it's even missing in allServices array
Arg::Gds(isc_bad_svc_handle).raise();
}
// Appears we have correct service object, may use it later to lock mutex
}
Service::SafeMutexLock::SafeMutexLock(Service* svc, const char* f)
: Validate(svc),
existenceMutex(svc->svc_existence),
from(f)
{
sharedGuard.leave();
}
bool Service::SafeMutexLock::lock()
{
existenceMutex->enter(from);
return existenceMutex->link;
}
Service::ExistenceGuard::ExistenceGuard(Service* svc, const char* from)
: SafeMutexLock(svc, from)
{
if (!lock())
{
// could not lock service
existenceMutex->leave();
Arg::Gds(isc_bad_svc_handle).raise();
}
}
Service::ExistenceGuard::~ExistenceGuard()
{
try
{
existenceMutex->leave();
}
catch (const Exception&)
{
DtorException::devHalt();
}
}
Service::UnlockGuard::UnlockGuard(Service* svc, const char* from)
: SafeMutexLock(svc, from), locked(false), doLock(false)
{
existenceMutex->leave();
doLock = true;
}
bool Service::UnlockGuard::enter()
{
if (doLock)
{
locked = lock();
doLock = false;
}
return locked;
}
Service::UnlockGuard::~UnlockGuard()
{
if (!enter())
{
// could not lock service
DtorException::devHalt();
}
}
void Service::getOptions(ClumpletReader& spb)
{
svc_spb_version = spb.getBufferTag();
for (spb.rewind(); !(spb.isEof()); spb.moveNext())
{
switch (spb.getClumpTag())
{
case isc_spb_user_name:
spb.getString(svc_username);
fb_utils::dpbItemUpper(svc_username);
break;
case isc_spb_sql_role_name:
spb.getString(svc_sql_role);
break;
case isc_spb_auth_block:
svc_auth_block.clear();
svc_auth_block.add(spb.getBytes(), spb.getClumpLength());
break;
case isc_spb_command_line:
spb.getString(svc_command_line);
break;
case isc_spb_expected_db:
spb.getPath(svc_expected_db);
break;
case isc_spb_address_path:
spb.getData(svc_address_path);
{
ClumpletReader address_stack(ClumpletReader::UnTagged,
spb.getBytes(), spb.getClumpLength());
while (!address_stack.isEof())
{
if (address_stack.getClumpTag() != isc_dpb_address)
{
address_stack.moveNext();
continue;
}
ClumpletReader address(ClumpletReader::UnTagged,
address_stack.getBytes(), address_stack.getClumpLength());
while (!address.isEof())
{
switch (address.getClumpTag())
{
case isc_dpb_addr_protocol:
address.getString(svc_network_protocol);
break;
case isc_dpb_addr_endpoint:
address.getString(svc_remote_address);
break;
default:
break;
}
address.moveNext();
}
break;
}
}
break;
case isc_spb_process_name:
spb.getString(svc_remote_process);
break;
case isc_spb_process_id:
svc_remote_pid = spb.getInt();
break;
case isc_spb_utf8_filename:
svc_utf8 = true;
break;
}
}
}
void Service::parseSwitches()
{
svc_parsed_sw = svc_switches;
svc_parsed_sw.trim();
argv.clear();
argv.push("service"); // why not use it for argv[0]
if (svc_parsed_sw.isEmpty())
{
return;
}
bool inStr = false;
for (FB_SIZE_T i = 0; i < svc_parsed_sw.length(); ++i)
{
switch (svc_parsed_sw[i])
{
case SVC_TRMNTR:
svc_parsed_sw.erase(i, 1);
if (inStr)
{
if (i < svc_parsed_sw.length() && svc_parsed_sw[i] != SVC_TRMNTR)
{
inStr = false;
--i;
}
}
else
{
inStr = true;
--i;
}
break;
case ' ':
if (!inStr)
{
svc_parsed_sw[i] = 0;
}
break;
}
}
argv.push(svc_parsed_sw.c_str());
for (const char* p = svc_parsed_sw.begin(); p < svc_parsed_sw.end(); ++p)
{
if (!*p)
{
argv.push(p + 1);
}
}
}
void Service::outputVerbose(const char* text)
{
if (!usvcDataMode)
{
ULONG len = static_cast<ULONG>(strlen(text));
enqueue(reinterpret_cast<const UCHAR*>(text), len);
}
}
void Service::outputError(const char* /*text*/)
{
fb_assert(false);
}
void Service::outputData(const void* data, FB_SIZE_T len)
{
fb_assert(usvcDataMode);
enqueue(reinterpret_cast<const UCHAR*>(data), len);
}
void Service::printf(bool err, const SCHAR* format, ...)
{
// Errors are returned from services as vectors
fb_assert(!err);
if (err || usvcDataMode)
{
return;
}
// Ensure that service is not detached.
if (svc_flags & SVC_detached)
{
return;
}
string buf;
va_list arglist;
va_start(arglist, format);
buf.vprintf(format, arglist);
va_end(arglist);
enqueue(reinterpret_cast<const UCHAR*>(buf.begin()), buf.length());
}
bool Service::isService()
{
return true;
}
void Service::started()
{
// ExistenceGuard guard(this, FB_FUNCTION);
// Not needed here - lock is taken by thread waiting for us
if (!(svc_flags & SVC_evnt_fired))
{
svc_flags |= SVC_evnt_fired;
svcStart.release();
}
}
void Service::putLine(char tag, const char* val)
{
const ULONG len = strlen(val) & 0xFFFF;
UCHAR buf[3];
buf[0] = tag;
buf[1] = len;
buf[2] = len >> 8;
enqueue(buf, sizeof buf);
enqueue(reinterpret_cast<const UCHAR*>(val), len);
}
void Service::putSLong(char tag, SLONG val)
{
UCHAR buf[5];
buf[0] = tag;
buf[1] = val;
buf[2] = val >> 8;
buf[3] = val >> 16;
buf[4] = val >> 24;
enqueue(buf, sizeof buf);
}
void Service::putSInt64(char tag, SINT64 val)
{
UCHAR buf[9];
buf[0] = tag;
buf[1] = val;
buf[2] = val >> 8;
buf[3] = val >> 16;
buf[4] = val >> 24;
buf[5] = val >> 32;
buf[6] = val >> 40;
buf[7] = val >> 48;
buf[8] = val >> 56;
enqueue(buf, sizeof buf);
}
void Service::putChar(char tag, char val)
{
UCHAR buf[2];
buf[0] = tag;
buf[1] = val;
enqueue(buf, sizeof buf);
}
void Service::putBytes(const UCHAR* bytes, FB_SIZE_T len)
{
enqueue(bytes, len);
}
void Service::setServiceStatus(const ISC_STATUS* status_vector)
{
if (checkForShutdown())
{
return;
}
Arg::StatusVector passed(status_vector);
ERR_post_nothrow(passed, &svc_status);
}
void Service::setServiceStatus(const USHORT facility, const USHORT errcode,
const MsgFormat::SafeArg& args)
{
if (checkForShutdown())
{
return;
}
// Append error codes to the status vector
Arg::StatusVector status;
// stuff the error code
status << Arg::Gds(ENCODE_ISC_MSG(errcode, facility));
// stuff params
svc_arg_ptr = svc_arg_conv;
for (unsigned int loop = 0; loop < args.getCount(); ++loop)
{
put_status_arg(status, args.getCell(loop));
}
ERR_post_nothrow(status, &svc_status);
}
void Service::put_status_arg(Arg::StatusVector& status, const MsgFormat::safe_cell& value)
{
using MsgFormat::safe_cell;
switch (value.type)
{
case safe_cell::at_int64:
case safe_cell::at_uint64:
status << Arg::Num(static_cast<SLONG>(value.i_value)); // May truncate number!
break;
case safe_cell::at_str:
status << value.st_value.s_string;
break;
case safe_cell::at_char:
svc_arg_ptr[0] = value.c_value;
svc_arg_ptr[1] = 0;
status << svc_arg_ptr;
svc_arg_ptr += 2;
break;
default:
fb_assert(false);
break;
}
}
void Service::hidePasswd(ArgvType&, int)
{
// no action
}
const FbStatusVector* Service::getStatus()
{
return &svc_status;
}
void Service::initStatus()
{
svc_status->init();
}
void Service::checkService()
{
// no action
}
unsigned int Service::getAuthBlock(const unsigned char** bytes)
{
*bytes = svc_auth_block.hasData() ? svc_auth_block.begin() : NULL;
return svc_auth_block.getCount();
}
void Service::fillDpb(ClumpletWriter& dpb)
{
dpb.insertString(isc_dpb_config, EMBEDDED_PROVIDERS, fb_strlen(EMBEDDED_PROVIDERS));
if (svc_address_path.hasData())
{
dpb.insertData(isc_dpb_address_path, svc_address_path);
}
if (svc_utf8)
{
dpb.insertTag(isc_dpb_utf8_filename);
}
if (svc_crypt_callback)
{
// That's not DPB-related, but anyway should be done before attach/create DB
ISC_STATUS_ARRAY status;
if (fb_database_crypt_callback(status, svc_crypt_callback) != 0)
{
status_exception::raise(status);
}
}
}
bool Service::utf8FileNames()
{
return svc_utf8;
}
void Service::need_admin_privs(Arg::StatusVector& status, const char* message)
{
status << Arg::Gds(isc_insufficient_svc_privileges) << Arg::Str(message);
}
bool Service::ck_space_for_numeric(UCHAR*& info, const UCHAR* const end)
{
if ((info + 1 + sizeof(ULONG)) > end)
{
if (info < end)
*info++ = isc_info_truncated;
if (info < end)
*info++ = isc_info_end;
return false;
}
return true;
}
// The SERVER_CAPABILITIES_FLAG is used to mark architectural
// differences across servers. This allows applications like server
// manager to disable features as necessary.
namespace
{
inline ULONG getServerCapabilities()
{
ULONG val = REMOTE_HOP_SUPPORT;
#ifdef WIN_NT
val |= QUOTED_FILENAME_SUPPORT;
#endif // WIN_NT
Firebird::MasterInterfacePtr master;
switch (master->serverMode(-1))
{
case 1: // super
val |= MULTI_CLIENT_SUPPORT;
break;
case 0: // classic
val |= NO_SERVER_SHUTDOWN_SUPPORT;
break;
default: // none-server mode
break;
}
return val;
}
}
Service::Service(const TEXT* service_name, USHORT spb_length, const UCHAR* spb_data,
Firebird::ICryptKeyCallback* crypt_callback)
: svc_status(getPool()), svc_parsed_sw(getPool()),
svc_stdout_head(0), svc_stdout_tail(0), svc_service(NULL), svc_service_run(NULL),
svc_resp_alloc(getPool()), svc_resp_buf(0), svc_resp_ptr(0), svc_resp_buf_len(0),
svc_resp_len(0), svc_flags(SVC_finished), svc_user_flag(0), svc_spb_version(0),
svc_do_shutdown(false), svc_shutdown_in_progress(false), svc_timeout(false),
svc_username(getPool()), svc_sql_role(getPool()), svc_auth_block(getPool()),
svc_expected_db(getPool()), svc_trusted_role(false), svc_utf8(false),
svc_switches(getPool()), svc_perm_sw(getPool()), svc_address_path(getPool()),
svc_command_line(getPool()),
svc_network_protocol(getPool()), svc_remote_address(getPool()), svc_remote_process(getPool()),
svc_remote_pid(0), svc_trace_manager(NULL), svc_crypt_callback(crypt_callback),
svc_existence(FB_NEW_POOL(*getDefaultMemoryPool()) SvcMutex(this)),
svc_stdin_size_requested(0), svc_stdin_buffer(NULL), svc_stdin_size_preload(0),
svc_stdin_preload_requested(0), svc_stdin_user_size(0), svc_thread(0)
#ifdef DEV_BUILD
, svc_debug(false)
#endif
{
initStatus();
{ // scope
// Account service block in global array
MutexLockGuard guard(globalServicesMutex, FB_FUNCTION);
checkForShutdown();
allServices->add(this);
}
// Since this moment we should remove this service from allServices in case of error thrown
try
{
// If the service name begins with a slash, ignore it.
if (*service_name == '/' || *service_name == '\\') {
service_name++;
}
// Find the service by looking for an exact match.
string svcname(service_name);
#ifdef DEV_BUILD
if (svcname == "@@@")
{
svc_debug = true;
svcname = "service_mgr";
}
#endif
const serv_entry* serv;
for (serv = services; serv->serv_name; serv++)
{
if (svcname == serv->serv_name)
break;
}
if (!serv->serv_name)
{
status_exception::raise(Arg::Gds(isc_service_att_err) <<
Arg::Gds(isc_svcnotdef) << Arg::Str(svcname));
}
// Process the service parameter block.
ClumpletReader spb(ClumpletReader::spbList, spb_data, spb_length, spbVersionError);
dumpAuthBlock("Jrd::Service() ctor", &spb, isc_spb_auth_block);
getOptions(spb);
#ifdef DEV_BUILD
if (svc_debug)
{
svc_trace_manager = FB_NEW_POOL(*getDefaultMemoryPool()) TraceManager(this);
svc_user_flag = SVC_user_dba;
svc_service = serv;
return;
}
#endif
// Perhaps checkout the user in the security database.
USHORT user_flag;
if (!strcmp(serv->serv_name, "anonymous")) {
user_flag = SVC_user_none;
}
else
{
if (!svc_username.hasData())
{
if (svc_auth_block.hasData())
{
PathName dummy;
RefPtr<const Config> config;
expandDatabaseName(svc_expected_db, dummy, &config);
string trusted_role;
mapUser(svc_username, trusted_role, NULL, &svc_auth_block, svc_auth_block,
"services manager", NULL, config->getSecurityDatabase(), svc_crypt_callback, NULL);
trusted_role.upper();
svc_trusted_role = trusted_role == ADMIN_ROLE;
}
else
{
// we have embedded service connection, check OS auth
if (ISC_get_user(&svc_username, NULL, NULL))
{
svc_username = SYSDBA_USER_NAME;
}
}
}
if (!svc_username.hasData())
{
// user name and password are required while
// attaching to the services manager
status_exception::raise(Arg::Gds(isc_service_att_err) << Arg::Gds(isc_svcnouser));
}
if (svc_username.length() > USERNAME_LENGTH)
{
status_exception::raise(Arg::Gds(isc_long_login) <<
Arg::Num(svc_username.length()) << Arg::Num(USERNAME_LENGTH));
}
// Check that the validated user has the authority to access this service
if (svc_username != SYSDBA_USER_NAME && !svc_trusted_role) {
user_flag = SVC_user_any;
}
else {
user_flag = SVC_user_dba | SVC_user_any;
}
}
// move service switches in
string switches;
if (serv->serv_std_switches)
switches = serv->serv_std_switches;
if (svc_command_line.hasData() && serv->serv_std_switches)
switches += ' ';
switches += svc_command_line;
svc_flags |= switches.hasData() ? SVC_cmd_line : 0;
svc_perm_sw = switches;
svc_user_flag = user_flag;
svc_service = serv;
svc_trace_manager = FB_NEW_POOL(*getDefaultMemoryPool()) TraceManager(this);
// If an executable is defined for the service, try to fork a new thread.
// Only do this if we are working with a version 1 service
if (serv->serv_thd && svc_spb_version == isc_spb_version1)
{
start(serv);
}
} // try
catch (const Firebird::Exception& ex)
{
TraceManager* trace_manager = NULL;
FbLocalStatus status_vector;
try
{
// Use created trace manager if it's possible
const bool hasTrace = svc_trace_manager != NULL;
if (hasTrace)
trace_manager = svc_trace_manager;
else
trace_manager = FB_NEW_POOL(*getDefaultMemoryPool()) TraceManager(this);
if (trace_manager->needs(ITraceFactory::TRACE_EVENT_SERVICE_ATTACH))
{
ex.stuffException(&status_vector);
const ISC_STATUS exc = status_vector[1];
const bool no_priv = (exc == isc_login || exc == isc_no_priv);
TraceServiceImpl service(this);
trace_manager->event_service_attach(&service,
no_priv ? ITracePlugin::RESULT_UNAUTHORIZED : ITracePlugin::RESULT_FAILED);
}
if (!hasTrace)
delete trace_manager;
}
catch (const Firebird::Exception&)
{
}
removeFromAllServices();
throw;
}
if (svc_trace_manager->needs(ITraceFactory::TRACE_EVENT_SERVICE_ATTACH))
{
TraceServiceImpl service(this);
svc_trace_manager->event_service_attach(&service, ITracePlugin::RESULT_SUCCESS);
}
}
static THREAD_ENTRY_DECLARE svcShutdownThread(THREAD_ENTRY_PARAM)
{
if (fb_shutdown(10 * 1000 /* 10 seconds */, fb_shutrsn_services) == FB_SUCCESS)
{
InstanceControl::registerShutdown(0);
exit(0);
}
return 0;
}
void Service::detach()
{
ExistenceGuard guard(this, FB_FUNCTION);
if (svc_flags & SVC_detached)
{
// Service was already detached
Arg::Gds(isc_bad_svc_handle).raise();
}
// save it cause after call to finish() we can't access class members any more
const bool localDoShutdown = svc_do_shutdown;
TraceServiceImpl service(this);
svc_trace_manager->event_service_detach(&service, ITracePlugin::RESULT_SUCCESS);
// Mark service as detached.
finish(SVC_detached);
if (localDoShutdown)
{
// run in separate thread to avoid blocking in remote
Thread::start(svcShutdownThread, 0, THREAD_medium);
}
}
Service::~Service()
{
removeFromAllServices();
delete svc_trace_manager;
svc_trace_manager = NULL;
fb_assert(svc_existence->locked());
svc_existence->link = NULL;
}
void Service::removeFromAllServices()
{
MutexLockGuard guard(globalServicesMutex, FB_FUNCTION);
FB_SIZE_T pos;
if (locateInAllServices(&pos))
{
allServices->remove(pos);
return;
}
fb_assert(false);
}
bool Service::locateInAllServices(FB_SIZE_T* posPtr)
{
MutexLockGuard guard(globalServicesMutex, FB_FUNCTION);
AllServices& all(allServices);
for (FB_SIZE_T pos = 0; pos < all.getCount(); ++pos)
{
if (all[pos] == this)
{
if (posPtr)
{
*posPtr = pos;
}
return true;
}
}
return false;
}
ULONG Service::totalCount()
{
MutexLockGuard guard(globalServicesMutex, FB_FUNCTION);
AllServices& all(allServices);
ULONG cnt = 0;
// don't count already detached services
for (FB_SIZE_T i = 0; i < all.getCount(); i++)
{
if (!(all[i]->svc_flags & SVC_detached))
cnt++;
}
return cnt;
}
bool Service::checkForShutdown()
{
if (svcShutdown)
{
if (svc_shutdown_in_progress)
{
// Here we avoid multiple exceptions thrown
return true;
}
svc_shutdown_in_progress = true;
status_exception::raise(Arg::Gds(isc_att_shutdown));
}
return false;
}
void Service::shutdownServices()
{
svcShutdown = true;
MutexLockGuard guard(globalServicesMutex, FB_FUNCTION);
AllServices& all(allServices);
unsigned int pos;
// signal once for every still running service
for (pos = 0; pos < all.getCount(); pos++)
{
if (!(all[pos]->svc_flags & SVC_finished))
all[pos]->svc_detach_sem.release();
if (all[pos]->svc_stdin_size_requested)
all[pos]->svc_stdin_semaphore.release();
}
for (pos = 0; pos < all.getCount(); )
{
if (!(all[pos]->svc_flags & SVC_finished))
{
globalServicesMutex->leave();
Thread::sleep(1);
globalServicesMutex->enter(FB_FUNCTION);
pos = 0;
continue;
}
++pos;
}
threadCollect->join();
}
ISC_STATUS Service::query2(thread_db* /*tdbb*/,
USHORT send_item_length,
const UCHAR* send_items,
USHORT recv_item_length,
const UCHAR* recv_items,
USHORT buffer_length,
UCHAR* info)
{
ExistenceGuard guard(this, FB_FUNCTION);
if (svc_flags & SVC_detached)
{
// Service was already detached
Arg::Gds(isc_bad_svc_handle).raise();
}
UCHAR item;
UCHAR buffer[MAXPATHLEN];
USHORT l, length, get_flags;
UCHAR* stdin_request_notification = NULL;
// Setup the status vector
Arg::StatusVector status;
ULONG requestFromPut = 0;
try
{
// Process the send portion of the query first.
USHORT timeout = 0;
const UCHAR* items = send_items;
const UCHAR* const end_items = items + send_item_length;
while (items < end_items && *items != isc_info_end)
{
switch ((item = *items++))
{
case isc_info_end:
break;
default:
if (items + 2 <= end_items)
{
l = (USHORT) gds__vax_integer(items, 2);
items += 2;
if (items + l <= end_items)
{
switch (item)
{
case isc_info_svc_line:
requestFromPut = put(items, l);
break;
case isc_info_svc_message:
//put(items - 3, l + 3);
break;
case isc_info_svc_timeout:
timeout = (USHORT) gds__vax_integer(items, l);
break;
case isc_info_svc_version:
break;
}
}
items += l;
}
else
items += 2;
break;
}
}
// Process the receive portion of the query now.
const UCHAR* const end = info + buffer_length;
items = recv_items;
const UCHAR* const end_items2 = items + recv_item_length;
UCHAR* start_info;
if (*items == isc_info_length)
{
start_info = info;
items++;
}
else {
start_info = NULL;
}
while (items < end_items2 && *items != isc_info_end)
{
// if we attached to the "anonymous" service we allow only following queries:
// isc_info_svc_get_config - called from within remote/ibconfig.cpp
// isc_info_svc_dump_pool_info - called from within utilities/print_pool.cpp
if (svc_user_flag == SVC_user_none)
{
switch (*items)
{
case isc_info_svc_get_config:
case isc_info_svc_dump_pool_info:
break;
default:
status_exception::raise(Arg::Gds(isc_bad_spb_form) <<
Arg::Gds(isc_info_access));
break;
}
}
switch ((item = *items++))
{
case isc_info_end:
break;
case isc_info_svc_svr_db_info:
if (svc_user_flag & SVC_user_dba)
{
PathNameList databases(*getDefaultMemoryPool());
ULONG num_dbs, num_att, num_svc;
JRD_enum_attachments(&databases, num_att, num_dbs, num_svc);
fb_assert(num_dbs == databases.getCount());
*info++ = item;
// Move the number of attachments into the info buffer
if (!ck_space_for_numeric(info, end))
return 0;
*info++ = isc_spb_num_att;
ADD_SPB_NUMERIC(info, num_att);
// Move the number of databases in use into the info buffer
if (!ck_space_for_numeric(info, end))
return 0;
*info++ = isc_spb_num_db;
ADD_SPB_NUMERIC(info, num_dbs);
// Move db names into the info buffer
for (FB_SIZE_T i = 0; i < databases.getCount(); i++)
{
if (!(info = INF_put_item(isc_spb_dbname,
(USHORT) databases[i].length(),
databases[i].c_str(),
info, end)))
{
return 0;
}
}
if (info < end)
*info++ = isc_info_flag_end;
}
else
need_admin_privs(status, "isc_info_svc_svr_db_info");
break;
case isc_info_svc_svr_online:
*info++ = item;
if (svc_user_flag & SVC_user_dba)
{
svc_do_shutdown = false;
}
else
need_admin_privs(status, "isc_info_svc_svr_online");
break;
case isc_info_svc_svr_offline:
*info++ = item;
if (svc_user_flag & SVC_user_dba)
{
svc_do_shutdown = true;
}
else
need_admin_privs(status, "isc_info_svc_svr_offline");
break;
// The following 3 service commands (or items) stuff the response
// buffer 'info' with values of environment variable FIREBIRD,
// FIREBIRD_LOCK or FIREBIRD_MSG. If the environment variable
// is not set then default value is returned.
case isc_info_svc_get_env:
case isc_info_svc_get_env_lock:
case isc_info_svc_get_env_msg:
if (svc_user_flag & SVC_user_dba)
{
char* const auxBuf = reinterpret_cast<char*>(buffer);
switch (item)
{
case isc_info_svc_get_env:
gds__prefix(auxBuf, "");
break;
case isc_info_svc_get_env_lock:
iscPrefixLock(auxBuf, "", false);
break;
case isc_info_svc_get_env_msg:
gds__prefix_msg(auxBuf, "");
}
// Note: it is safe to use strlen to get a length of "buffer"
// because gds_prefix[_lock|_msg] returns a zero-terminated
// string.
info = INF_put_item(item, static_cast<USHORT>(strlen(auxBuf)), buffer, info, end);
if (!info)
{
return 0;
}
}
else
{
need_admin_privs(status, "isc_info_svc_get_env");
}
break;
case isc_info_svc_dump_pool_info:
{
char fname[MAXPATHLEN];
size_t length2 = gds__vax_integer(items, sizeof(USHORT));
if (length2 >= sizeof(fname))
length2 = sizeof(fname) - 1; // truncation
items += sizeof(USHORT);
strncpy(fname, (const char*) items, length2);
fname[length2] = 0;
break;
}
case isc_info_svc_get_config:
// TODO: iterate through all integer-based config values
// and return them to the client
break;
/*
case isc_info_svc_default_config:
*info++ = item;
if (svc_user_flag & SVC_user_dba) {
// TODO: reset the config values to defaults
}
else
need_admin_privs(status, "isc_info_svc_default_config");
break;
case isc_info_svc_set_config:
*info++ = item;
if (svc_user_flag & SVC_user_dba) {
// TODO: set the config values
}
else {
need_admin_privs(status, "isc_info_svc_set_config");
}
break;
*/
case isc_info_svc_version:
// The version of the service manager
if (!ck_space_for_numeric(info, end))
return 0;
*info++ = item;
ADD_SPB_NUMERIC(info, SERVICE_VERSION);
break;
case isc_info_svc_capabilities:
// bitmask defining any specific architectural differences
if (!ck_space_for_numeric(info, end))
return 0;
*info++ = item;
ADD_SPB_NUMERIC(info, getServerCapabilities());
break;
case isc_info_svc_running:
// Returns the (inversed) status of the flag SVC_finished
if (!ck_space_for_numeric(info, end))
return 0;
*info++ = item;
if (svc_flags & SVC_finished)
ADD_SPB_NUMERIC(info, FALSE)
else
ADD_SPB_NUMERIC(info, TRUE)
break;
case isc_info_svc_server_version:
// The version of the server engine
{ // scope
info = INF_put_item(item, static_cast<USHORT>(strlen(FB_VERSION)), FB_VERSION, info, end);
if (!info) {
return 0;
}
} // scope
break;
case isc_info_svc_implementation:
// The server implementation - e.g. Firebird/sun4
{ // scope
string buf2 = DbImplementation::current.implementation();
info = INF_put_item(item, buf2.length(), buf2.c_str(), info, end);
if (!info) {
return 0;
}
} // scope
break;
case isc_info_svc_stdin:
// Check - is stdin data required for server
if (!ck_space_for_numeric(info, end))
{
return 0;
}
*info++ = item;
if (!stdin_request_notification)
{
stdin_request_notification = info;
}
ADD_SPB_NUMERIC(info, 0);
break;
case isc_info_svc_user_dbpath:
if (svc_user_flag & SVC_user_dba)
{
// The path to the user security database (security2.fdb)
const RefPtr<const Config> defConf(Config::getDefaultConfig());
const char* secDb = defConf->getSecurityDatabase();
if (!(info = INF_put_item(item, static_cast<USHORT>(strlen(secDb)), secDb, info, end)))
{
return 0;
}
}
else
need_admin_privs(status, "isc_info_svc_user_dbpath");
break;
case isc_info_svc_response:
svc_resp_len = 0;
if (info + 4 >= end)
{
*info++ = isc_info_truncated;
break;
}
//put(&item, 1);
get(&item, 1, GET_BINARY, 0, &length);
get(buffer, 2, GET_BINARY, 0, &length);
l = (USHORT) gds__vax_integer(buffer, 2);
length = MIN(end - (info + 5), l);
get(info + 3, length, GET_BINARY, 0, &length);
info = INF_put_item(item, length, info + 3, info, end);
if (length != l)
{
*info++ = isc_info_truncated;
l -= length;
if (l > svc_resp_buf_len)
{
try {
svc_resp_buf = svc_resp_alloc.getBuffer(l);
}
catch (const BadAlloc&)
{
// NOMEM:
DEV_REPORT("SVC_query: out of memory");
// NOMEM: not really handled well
l = 0; // set the length to zero
}
svc_resp_buf_len = l;
}
get(svc_resp_buf, l, GET_BINARY, 0, &length);
svc_resp_ptr = svc_resp_buf;
svc_resp_len = l;
}
break;
case isc_info_svc_response_more:
if ( (l = length = svc_resp_len) )
length = MIN(end - (info + 5), l);
if (!(info = INF_put_item(item, length, svc_resp_ptr, info, end)))
{
return 0;
}
svc_resp_ptr += length;
svc_resp_len -= length;
if (length != l)
*info++ = isc_info_truncated;
break;
case isc_info_svc_total_length:
//put(&item, 1);
get(&item, 1, GET_BINARY, 0, &length);
get(buffer, 2, GET_BINARY, 0, &length);
l = (USHORT) gds__vax_integer(buffer, 2);
get(buffer, l, GET_BINARY, 0, &length);
if (!(info = INF_put_item(item, length, buffer, info, end))) {
return 0;
}
break;
case isc_info_svc_line:
case isc_info_svc_to_eof:
case isc_info_svc_limbo_trans:
case isc_info_svc_get_users:
if (info + 4 >= end)
{
*info++ = isc_info_truncated;
break;
}
switch (item)
{
case isc_info_svc_line:
get_flags = GET_LINE;
break;
case isc_info_svc_to_eof:
get_flags = GET_EOF;
break;
default:
get_flags = GET_BINARY;
break;
}
if (requestFromPut)
{
get_flags |= GET_ONCE;
}
get(info + 3, end - (info + 5), get_flags, timeout, &length);
// If the read timed out, return the data, if any, & a timeout
// item. If the input buffer was not large enough
// to store a read to eof, return the data that was read along
// with an indication that more is available.
if (!(info = INF_put_item(item, length, info + 3, info, end))) {
return 0;
}
if (svc_timeout)
{
*info++ = isc_info_svc_timeout;
}
else //if (!svc_stdin_size_requested)
{
if (!length && !(svc_flags & SVC_finished))
{
*info++ = isc_info_data_not_ready;
}
else if (item == isc_info_svc_to_eof && !(svc_flags & SVC_finished))
{
*info++ = isc_info_truncated;
}
}
break;
default:
status << Arg::Gds(isc_wish_list);
break;
}
if (svc_user_flag == SVC_user_none)
break;
}
if (info < end)
*info++ = isc_info_end;
if (start_info && (end - info >= 7))
{
const SLONG number = info - start_info;
fb_assert(number > 0);
memmove(start_info + 7, start_info, number);
if (stdin_request_notification)
stdin_request_notification += 7;
USHORT length2 = INF_convert(number, buffer);
fb_assert(length2 == 4); // We only accept SLONG
INF_put_item(isc_info_length, length2, buffer, start_info, end, true);
}
if (svc_trace_manager->needs(ITraceFactory::TRACE_EVENT_SERVICE_QUERY))
{
TraceServiceImpl service(this);
svc_trace_manager->event_service_query(&service, send_item_length, send_items,
recv_item_length, recv_items, ITracePlugin::RESULT_SUCCESS);
}
if (!requestFromPut)
{
requestFromPut = svc_stdin_size_requested;
}
if (requestFromPut)
{
if (stdin_request_notification)
{
ADD_SPB_NUMERIC(stdin_request_notification, requestFromPut);
}
else
{
(Arg::Gds(isc_svc_no_stdin)).raise();
}
}
if (status.hasData())
{
status.raise();
}
} // try
catch (const Firebird::Exception& ex)
{
if (svc_trace_manager->needs(ITraceFactory::TRACE_EVENT_SERVICE_QUERY))
{
FbLocalStatus status_vector;
ex.stuffException(&status_vector);
const ISC_STATUS exc = status_vector[1];
const bool no_priv = (exc == isc_login || exc == isc_no_priv ||
exc == isc_insufficient_svc_privileges);
TraceServiceImpl service(this);
svc_trace_manager->event_service_query(&service, send_item_length, send_items,
recv_item_length, recv_items,
(no_priv ? ITracePlugin::RESULT_UNAUTHORIZED : ITracePlugin::RESULT_FAILED));
}
throw;
}
return svc_status[1];
}
void Service::query(USHORT send_item_length,
const UCHAR* send_items,
USHORT recv_item_length,
const UCHAR* recv_items,
USHORT buffer_length,
UCHAR* info)
{
ExistenceGuard guard(this, FB_FUNCTION);
if (svc_flags & SVC_detached)
{
// Service was already detached
Arg::Gds(isc_bad_svc_handle).raise();
}
UCHAR item, *p;
UCHAR buffer[256];
USHORT l, length, get_flags;
try
{
// Process the send portion of the query first.
USHORT timeout = 0;
const UCHAR* items = send_items;
const UCHAR* const end_items = items + send_item_length;
while (items < end_items && *items != isc_info_end)
{
switch ((item = *items++))
{
case isc_info_end:
break;
default:
if (items + 2 <= end_items)
{
l = (USHORT) gds__vax_integer(items, 2);
items += 2;
if (items + l <= end_items)
{
switch (item)
{
case isc_info_svc_line:
//put(items, l);
break;
case isc_info_svc_message:
//put(items - 3, l + 3);
break;
case isc_info_svc_timeout:
timeout = (USHORT) gds__vax_integer(items, l);
break;
case isc_info_svc_version:
break;
}
}
items += l;
}
else
items += 2;
break;
}
}
// Process the receive portion of the query now.
const UCHAR* const end = info + buffer_length;
items = recv_items;
const UCHAR* const end_items2 = items + recv_item_length;
while (items < end_items2 && *items != isc_info_end)
{
switch ((item = *items++))
{
case isc_info_end:
break;
case isc_info_svc_svr_db_info:
if (svc_user_flag & SVC_user_dba)
{
PathNameList databases(*getDefaultMemoryPool());
ULONG num_dbs, num_att, num_svc;
JRD_enum_attachments(&databases, num_att, num_dbs, num_svc);
fb_assert(num_dbs == databases.getCount());
length = INF_convert(num_att, buffer);
info = INF_put_item(item, length, buffer, info, end);
if (!info) {
return;
}
length = INF_convert(num_dbs, buffer);
info = INF_put_item(item, length, buffer, info, end);
if (!info) {
return;
}
}
// Can not return error for service v.1 => simply ignore request
// else
// need_admin_privs(status, "isc_info_svc_svr_db_info");
break;
case isc_info_svc_svr_online:
*info++ = item;
if (svc_user_flag & SVC_user_dba)
{
svc_do_shutdown = false;
*info++ = 0; // Success
}
else
*info++ = 2; // No user authority
break;
case isc_info_svc_svr_offline:
*info++ = item;
if (svc_user_flag & SVC_user_dba)
{
svc_do_shutdown = true;
*info++ = 0; // Success
}
else
*info++ = 2; // No user authority
break;
// The following 3 service commands (or items) stuff the response
// buffer 'info' with values of environment variable FIREBIRD,
// FIREBIRD_LOCK or FIREBIRD_MSG. If the environment variable
// is not set then default value is returned.
case isc_info_svc_get_env:
case isc_info_svc_get_env_lock:
case isc_info_svc_get_env_msg:
if (svc_user_flag & SVC_user_dba)
{
TEXT PathBuffer[MAXPATHLEN];
switch (item)
{
case isc_info_svc_get_env:
gds__prefix(PathBuffer, "");
break;
case isc_info_svc_get_env_lock:
iscPrefixLock(PathBuffer, "", false);
break;
case isc_info_svc_get_env_msg:
gds__prefix_msg(PathBuffer, "");
}
// Note: it is safe to use strlen to get a length of "buffer"
// because gds_prefix[_lock|_msg] return a zero-terminated
// string.
if (!(info = INF_put_item(item, static_cast<USHORT>(strlen(PathBuffer)), PathBuffer, info, end)))
return;
}
// Can not return error for service v.1 => simply ignore request
// else
// need_admin_privs(status, "isc_info_svc_get_env");
break;
case isc_info_svc_dump_pool_info:
{
char fname[MAXPATHLEN];
size_t length2 = gds__vax_integer(items, sizeof(USHORT));
if (length2 >= sizeof(fname))
length2 = sizeof(fname) - 1; // truncation
items += sizeof(USHORT);
memcpy(fname, items, length2);
fname[length2] = 0;
break;
}
/*
case isc_info_svc_get_config:
// TODO: iterate through all integer-based config values
// and return them to the client
break;
case isc_info_svc_default_config:
*info++ = item;
if (svc_user_flag & SVC_user_dba) {
// TODO: reset the config values to defaults
}
*
* Can not return error for service v.1 => simply ignore request
else
need_admin_privs(status, "isc_info_svc_default_config:");
*
break;
case isc_info_svc_set_config:
*info++ = item;
if (svc_user_flag & SVC_user_dba) {
// TODO: set the config values
}
*
* Can not return error for service v.1 => simply ignore request
else
need_admin_privs(status, "isc_info_svc_set_config:");
*
break;
*/
case isc_info_svc_version:
// The version of the service manager
length = INF_convert(SERVICE_VERSION, buffer);
info = INF_put_item(item, length, buffer, info, end);
if (!info)
return;
break;
case isc_info_svc_capabilities:
// bitmask defining any specific architectural differences
length = INF_convert(getServerCapabilities(), buffer);
info = INF_put_item(item, length, buffer, info, end);
if (!info)
return;
break;
case isc_info_svc_server_version:
{
// The version of the server engine
p = buffer;
*p++ = 1; // Count
*p++ = sizeof(FB_VERSION) - 1;
for (const TEXT* gvp = FB_VERSION; *gvp; p++, gvp++)
*p = *gvp;
if (!(info = INF_put_item(item, p - buffer, buffer, info, end)))
{
return;
}
break;
}
case isc_info_svc_implementation:
// The server implementation - e.g. Firebird/sun4
p = buffer;
*p++ = 1; // Count
*p++ = DbImplementation::current.backwardCompatibleImplementation();
if (!(info = INF_put_item(item, p - buffer, buffer, info, end)))
{
return;
}
break;
case isc_info_svc_user_dbpath:
if (svc_user_flag & SVC_user_dba)
{
// The path to the user security database (security2.fdb)
const RefPtr<const Config> defConf(Config::getDefaultConfig());
const char* secDb = defConf->getSecurityDatabase();
if (!(info = INF_put_item(item, static_cast<USHORT>(strlen(secDb)), secDb, info, end)))
{
return;
}
}
// Can not return error for service v.1 => simply ignore request
// else
// need_admin_privs(status, "isc_info_svc_user_dbpath");
break;
case isc_info_svc_response:
svc_resp_len = 0;
if (info + 4 > end)
{
*info++ = isc_info_truncated;
break;
}
//put(&item, 1);
get(&item, 1, GET_BINARY, 0, &length);
get(buffer, 2, GET_BINARY, 0, &length);
l = (USHORT) gds__vax_integer(buffer, 2);
length = MIN(end - (info + 4), l);
get(info + 3, length, GET_BINARY, 0, &length);
info = INF_put_item(item, length, info + 3, info, end);
if (length != l)
{
*info++ = isc_info_truncated;
l -= length;
if (l > svc_resp_buf_len)
{
try {
svc_resp_buf = svc_resp_alloc.getBuffer(l);
}
catch (const BadAlloc&)
{
// NOMEM:
DEV_REPORT("SVC_query: out of memory");
// NOMEM: not really handled well
l = 0; // set the length to zero
}
svc_resp_buf_len = l;
}
get(svc_resp_buf, l, GET_BINARY, 0, &length);
svc_resp_ptr = svc_resp_buf;
svc_resp_len = l;
}
break;
case isc_info_svc_response_more:
if ( (l = length = svc_resp_len) )
length = MIN(end - (info + 4), l);
if (!(info = INF_put_item(item, length, svc_resp_ptr, info, end)))
{
return;
}
svc_resp_ptr += length;
svc_resp_len -= length;
if (length != l)
*info++ = isc_info_truncated;
break;
case isc_info_svc_total_length:
//put(&item, 1);
get(&item, 1, GET_BINARY, 0, &length);
get(buffer, 2, GET_BINARY, 0, &length);
l = (USHORT) gds__vax_integer(buffer, 2);
get(buffer, l, GET_BINARY, 0, &length);
if (!(info = INF_put_item(item, length, buffer, info, end)))
{
return;
}
break;
case isc_info_svc_line:
case isc_info_svc_to_eof:
if (info + 4 > end)
{
*info++ = isc_info_truncated;
break;
}
get_flags = (item == isc_info_svc_line) ? GET_LINE : GET_EOF;
get(info + 3, end - (info + 4), get_flags, timeout, &length);
// If the read timed out, return the data, if any, & a timeout
// item. If the input buffer was not large enough
// to store a read to eof, return the data that was read along
// with an indication that more is available.
info = INF_put_item(item, length, info + 3, info, end);
if (svc_timeout)
*info++ = isc_info_svc_timeout;
else
{
if (!length && !(svc_flags & SVC_finished))
*info++ = isc_info_data_not_ready;
else
{
if (item == isc_info_svc_to_eof && !(svc_flags & SVC_finished))
*info++ = isc_info_truncated;
}
}
break;
}
}
if (info < end)
{
*info = isc_info_end;
}
} // try
catch (const Firebird::Exception& ex)
{
if (svc_trace_manager->needs(ITraceFactory::TRACE_EVENT_SERVICE_QUERY))
{
FbLocalStatus status_vector;
ex.stuffException(&status_vector);
const ISC_STATUS exc = status_vector[1];
const bool no_priv = (exc == isc_login || exc == isc_no_priv);
// Report to Trace API that query failed
TraceServiceImpl service(this);
svc_trace_manager->event_service_query(&service, send_item_length, send_items,
recv_item_length, recv_items,
(no_priv ? ITracePlugin::RESULT_UNAUTHORIZED : ITracePlugin::RESULT_FAILED));
}
throw;
}
if (svc_flags & SVC_finished)
{
if ((svc_flags & SVC_detached) &&
svc_trace_manager->needs(ITraceFactory::TRACE_EVENT_SERVICE_QUERY))
{
TraceServiceImpl service(this);
svc_trace_manager->event_service_query(&service, send_item_length, send_items,
recv_item_length, recv_items, ITracePlugin::RESULT_SUCCESS);
}
}
}
THREAD_ENTRY_DECLARE Service::run(THREAD_ENTRY_PARAM arg)
{
int exit_code = -1;
try
{
Service* svc = (Service*)arg;
RefPtr<SvcMutex> ref(svc->svc_existence);
exit_code = svc->svc_service_run->serv_thd(svc);
if (svc->svc_thread)
{
threadCollect->add(svc->svc_thread);
svc->svc_thread = 0;
}
svc->started();
svc->svc_sem_full.release();
svc->finish(SVC_finished);
}
catch (const Exception& ex)
{
// Not much we can do here
exit_code = -1;
iscLogException("Exception in Service::run():", ex);
}
return (THREAD_ENTRY_RETURN)(IPTR) exit_code;
}
void Service::start(USHORT spb_length, const UCHAR* spb_data)
{
ExistenceGuard guard(this, FB_FUNCTION);
if (svc_flags & SVC_detached)
{
// Service was already detached
Arg::Gds(isc_bad_svc_handle).raise();
}
try
{
ClumpletReader spb(ClumpletReader::SpbStart, spb_data, spb_length);
// The name of the service is the first element of the buffer
if (spb.isEof())
{
status_exception::raise(Arg::Gds(isc_service_att_err) << Arg::Gds(isc_spb_no_id));
}
const UCHAR svc_id = spb.getClumpTag();
const serv_entry* serv;
for (serv = services; serv->serv_action; serv++)
{
if (serv->serv_action == svc_id)
break;
}
if (!serv->serv_name)
status_exception::raise(Arg::Gds(isc_service_att_err) << Arg::Gds(isc_service_not_supported));
svc_service_run = serv;
// currently we do not use "anonymous" service for any purposes but isc_service_query()
if (svc_user_flag == SVC_user_none)
{
status_exception::raise(Arg::Gds(isc_bad_spb_form) <<
Arg::Gds(isc_svc_start_failed));
}
if (!(svc_flags & SVC_finished))
status_exception::raise(Arg::Gds(isc_svc_in_use) << Arg::Str(serv->serv_name));
// Another service may have been started with this service block.
// If so, we must reset the service flags.
svc_switches.erase();
/***
if (!(svc_flags & SVC_detached))
{
svc_flags = 0;
}
***/
if (!svc_perm_sw.hasData())
{
// If svc_perm_sw is not used -- call a command-line parsing utility
conv_switches(spb, svc_switches);
}
else
{
// Command line options (isc_spb_options) is used.
// Currently the only case in which it might happen is -- gbak utility
// is called with a "-server" switch.
svc_switches = svc_perm_sw;
}
// Only need to add username and password information to those calls which need
// to make a database connection
const bool flNeedUser =
svc_id == isc_action_svc_backup ||
svc_id == isc_action_svc_restore ||
svc_id == isc_action_svc_nbak ||
svc_id == isc_action_svc_nrest ||
svc_id == isc_action_svc_repair ||
svc_id == isc_action_svc_db_stats ||
svc_id == isc_action_svc_properties ||
svc_id == isc_action_svc_trace_start ||
svc_id == isc_action_svc_trace_stop ||
svc_id == isc_action_svc_trace_suspend ||
svc_id == isc_action_svc_trace_resume ||
svc_id == isc_action_svc_trace_list ||
svc_id == isc_action_svc_add_user ||
svc_id == isc_action_svc_delete_user ||
svc_id == isc_action_svc_modify_user ||
svc_id == isc_action_svc_display_user ||
svc_id == isc_action_svc_display_user_adm ||
svc_id == isc_action_svc_set_mapping ||
svc_id == isc_action_svc_drop_mapping ||
svc_id == isc_action_svc_validate;
if (flNeedUser)
{
// add the username to the end of svc_switches if needed
if (svc_switches.hasData() && !svc_auth_block.hasData())
{
if (svc_username.hasData())
{
string auth = "-user ";
auth += svc_username;
auth += ' ';
svc_switches = auth + svc_switches;
}
}
if (svc_sql_role.hasData())
{
string auth = "-role ";
auth += svc_sql_role;
auth += ' ';
svc_switches = auth + svc_switches;
}
}
#ifdef DEV_BUILD
if (svc_debug)
{
::printf("%s %s\n", svc_service_run->serv_name, svc_switches.c_str());
return;
}
#endif
// All services except for get_ib_log require switches
spb.rewind();
if ((!svc_switches.hasData()) && actionNeedsArg(svc_id))
{
status_exception::raise(Arg::Gds(isc_bad_spb_form) <<
Arg::Gds(isc_svc_no_switches));
}
// Do not let everyone look at server log
if (svc_id == isc_action_svc_get_fb_log && !(svc_user_flag & SVC_user_dba))
{
status_exception::raise(Arg::Gds(isc_adm_task_denied));
}
// Break up the command line into individual arguments.
parseSwitches();
// The service block can be reused hence init a status vector.
initStatus();
if (serv->serv_thd)
{
svc_flags &= ~(SVC_evnt_fired | SVC_finished);
svc_stdout_head = svc_stdout_tail = 0;
Thread::start(run, this, THREAD_medium, &svc_thread);
// good time for housekeeping while new thread starts
threadCollect->houseKeeping();
// Check for the service being detached. This will prevent the thread
// from waiting infinitely if the client goes away.
while (!(svc_flags & SVC_detached))
{
// The semaphore will be released once the particular service
// has reached a point in which it can start to return
// information to the client. This will allow isc_service_start
// to include in its status vector information about the service's
// ability to start.
// This is needed since Thread::start() will almost always succeed.
//
// Do not unlock mutex here - one can call start not doing this.
if (svcStart.tryEnter(60))
{
// started() was called
break;
}
}
}
else
{
status_exception::raise(Arg::Gds(isc_svcnotdef) << Arg::Str(serv->serv_name));
}
} // try
catch (const Firebird::Exception& ex)
{
if (svc_trace_manager->needs(ITraceFactory::TRACE_EVENT_SERVICE_START))
{
FbLocalStatus status_vector;
ex.stuffException(&status_vector);
const ISC_STATUS exc = status_vector[1];
const bool no_priv = (exc == isc_login || exc == isc_no_priv);
TraceServiceImpl service(this);
svc_trace_manager->event_service_start(&service,
this->svc_switches.length(), this->svc_switches.c_str(),
no_priv ? ITracePlugin::RESULT_UNAUTHORIZED : ITracePlugin::RESULT_FAILED);
}
throw;
}
if (this->svc_trace_manager->needs(ITraceFactory::TRACE_EVENT_SERVICE_START))
{
TraceServiceImpl service(this);
this->svc_trace_manager->event_service_start(&service,
this->svc_switches.length(), this->svc_switches.c_str(),
(this->svc_status->getState() & IStatus::STATE_ERRORS ?
ITracePlugin::RESULT_FAILED : ITracePlugin::RESULT_SUCCESS));
}
}
int Service::readFbLog(UtilSvc* arg)
{
Service* service = (Service*) arg;
service->readFbLog();
return 0;
}
void Service::readFbLog()
{
bool svc_started = false;
Firebird::PathName name = fb_utils::getPrefix(IConfigManager::DIR_LOG, LOGFILE);
FILE* file = os_utils::fopen(name.c_str(), "r");
try
{
if (file != NULL)
{
initStatus();
started();
svc_started = true;
TEXT buffer[100];
setDataMode(true);
int n;
while ((n = fread(buffer, sizeof(buffer[0]), FB_NELEM(buffer), file)) > 0)
{
outputData(buffer, n);
if (checkForShutdown())
break;
}
setDataMode(false);
}
if (!file || (file && ferror(file)))
{
(Arg::Gds(isc_sys_request) << Arg::Str(file ? "fgets" : "fopen") <<
SYS_ERR(errno)).copyTo(&svc_status);
if (!svc_started)
{
started();
}
}
}
catch (const Firebird::Exception& e)
{
setDataMode(false);
e.stuffException(&svc_status);
}
if (file)
{
fclose(file);
}
}
void Service::start(const serv_entry* service_run)
{
// Break up the command line into individual arguments.
parseSwitches();
if (svc_service && svc_service->serv_name)
{
argv[0] = svc_service->serv_name;
}
svc_service_run = service_run;
Thread::start(run, this, THREAD_medium, &svc_thread);
}
ULONG Service::add_one(ULONG i)
{
return (i + 1) % SVC_STDOUT_BUFFER_SIZE;
}
ULONG Service::add_val(ULONG i, ULONG val)
{
return (i + val) % SVC_STDOUT_BUFFER_SIZE;
}
bool Service::empty(ULONG head) const
{
return svc_stdout_tail == head;
}
bool Service::full() const
{
return add_one(svc_stdout_tail) == svc_stdout_head;
}
#define ENQUEUE_DEQUEUE_DELAY 1
void Service::enqueue(const UCHAR* s, ULONG len)
{
if (checkForShutdown() || (svc_flags & SVC_detached))
{
svc_sem_full.release();
return;
}
while (len)
{
// Wait for space in buffer
bool flagFirst = true;
while (full())
{
if (flagFirst)
{
svc_sem_full.release();
flagFirst = false;
}
svc_sem_empty.tryEnter(1, 0);
if (checkForShutdown() || (svc_flags & SVC_detached))
{
svc_sem_full.release();
return;
}
}
const ULONG head = svc_stdout_head;
ULONG cnt = (head > svc_stdout_tail ? head : sizeof(svc_stdout)) - 1;
if (add_one(cnt) != head)
{
++cnt;
}
cnt -= svc_stdout_tail;
if (cnt > len)
{
cnt = len;
}
memcpy(&svc_stdout[svc_stdout_tail], s, cnt);
svc_stdout_tail = add_val(svc_stdout_tail, cnt);
s += cnt;
len -= cnt;
}
svc_sem_full.release();
}
void Service::get(UCHAR* buffer, USHORT length, USHORT flags, USHORT timeout, USHORT* return_length)
{
#ifdef HAVE_GETTIMEOFDAY
struct timeval start_time, end_time;
GETTIMEOFDAY(&start_time);
#else
time_t start_time, end_time;
time(&start_time);
#endif
*return_length = 0;
svc_timeout = false;
bool flagFirst = true;
ULONG head = svc_stdout_head;
while (length)
{
if ((empty(head) && (svc_flags & SVC_finished)) || checkForShutdown())
{
break;
}
if (empty(head))
{
if (svc_stdin_size_requested && (!(flags & GET_BINARY)))
{
// service needs data from user - notify him
break;
}
if (flagFirst)
{
svc_sem_empty.release();
flagFirst = false;
}
if (flags & GET_ONCE)
{
break;
}
if (full())
{
// buffer is full but LF is not present in it
break;
}
UnlockGuard guard(this, FB_FUNCTION);
svc_sem_full.tryEnter(1, 0);
if (!guard.enter())
Arg::Gds(isc_bad_svc_handle).raise();
}
#ifdef HAVE_GETTIMEOFDAY
GETTIMEOFDAY(&end_time);
const time_t elapsed_time = end_time.tv_sec - start_time.tv_sec;
#else
time(&end_time);
const time_t elapsed_time = end_time - start_time;
#endif
if (timeout && elapsed_time >= timeout)
{
ExistenceGuard guard(this, FB_FUNCTION);
svc_timeout = true;
break;
}
while ((!empty(head)) && length > 0)
{
flagFirst = true;
const UCHAR ch = svc_stdout[head];
head = add_one(head);
length--;
// If returning a line of information, replace all new line
// characters with a space. This will ensure that the output is
// consistent when returning a line or to eof.
if ((flags & GET_LINE) && ch == '\n')
{
buffer[(*return_length)++] = ' ';
length = 0;
break;
}
buffer[(*return_length)++] = ch;
}
if (!(flags & GET_LINE))
svc_stdout_head = head;
}
if (flags & GET_LINE)
{
if (length == 0 || full())
svc_stdout_head = head;
else
*return_length = 0;
}
svc_sem_empty.release();
}
ULONG Service::put(const UCHAR* buffer, ULONG length)
{
MutexLockGuard guard(svc_stdin_mutex, FB_FUNCTION);
// check length correctness
if (length > svc_stdin_size_requested && length > svc_stdin_preload_requested)
{
(Arg::Gds(isc_svc_bad_size)).raise();
}
if (svc_stdin_size_requested) // service waits for data from us
{
svc_stdin_user_size = MIN(length, svc_stdin_size_requested);
memcpy(svc_stdin_buffer, buffer, svc_stdin_user_size);
// reset satisfied request
ULONG blockSize = svc_stdin_size_requested;
svc_stdin_size_requested = 0;
// let data be used
svc_stdin_semaphore.release();
if (length == 0)
{
return 0;
}
// reset used block of data
length -= svc_stdin_user_size;
buffer += svc_stdin_user_size;
if (length == 0) // ask user to preload next block of data
{
if (!svc_stdin_preload)
{
svc_stdin_preload.reset(FB_NEW_POOL(getPool()) UCHAR[PRELOAD_BUFFER_SIZE]);
}
svc_stdin_preload_requested = MIN(blockSize, PRELOAD_BUFFER_SIZE);
return svc_stdin_preload_requested;
}
}
// Store data in preload buffer
fb_assert(length <= PRELOAD_BUFFER_SIZE);
fb_assert(length <= svc_stdin_preload_requested);
fb_assert(svc_stdin_size_preload == 0);
memcpy(svc_stdin_preload, buffer, length);
svc_stdin_size_preload = length;
return 0;
}
ULONG Service::getBytes(UCHAR* buffer, ULONG size)
{
{ // Guard scope
MutexLockGuard guard(svc_stdin_mutex, FB_FUNCTION);
if (svc_flags & SVC_detached) // no more data for this service please
{
return 0;
}
if (svc_stdin_size_preload != 0) // use data, preloaded by user
{
// Use data from preload buffer
size = MIN(size, svc_stdin_size_preload);
memcpy(buffer, svc_stdin_preload, size);
if (size < svc_stdin_size_preload)
{
// not good, client should not request so small block
svc_stdin_size_preload -= size;
memmove(svc_stdin_preload, svc_stdin_preload + size, svc_stdin_size_preload);
}
else
{
svc_stdin_size_preload = 0;
}
return size;
}
// Request new data portion
svc_stdin_size_requested = size;
svc_stdin_buffer = buffer;
// Wakeup Service::query() if it waits for data from service
svc_sem_full.release();
}
// Wait for data from client
svc_stdin_semaphore.enter();
return svc_stdin_user_size;
}
void Service::finish(USHORT flag)
{
if (flag == SVC_finished || flag == SVC_detached)
{
ExistenceGuard guard(this, FB_FUNCTION);
svc_flags |= flag;
if ((svc_flags & SVC_finished) && (svc_flags & SVC_detached))
{
delete this;
return;
}
if (svc_flags & SVC_detached)
{
svc_sem_empty.release();
// if service waits for data from us - return EOF
{ // guard scope
MutexLockGuard guard(svc_stdin_mutex, FB_FUNCTION);
if (svc_stdin_size_requested)
{
svc_stdin_user_size = 0;
svc_stdin_semaphore.release();
}
}
}
if (svc_flags & SVC_finished)
{
svc_sem_full.release();
}
else
{
svc_detach_sem.release();
}
}
}
void Service::conv_switches(ClumpletReader& spb, string& switches)
{
spb.rewind();
const UCHAR test = spb.getClumpTag();
if (test < isc_action_min || test >= isc_action_max) {
return; // error - action not defined
}
// convert to string
string sw;
if (! process_switches(spb, sw)) {
return;
}
switches = sw;
}
const TEXT* Service::find_switch(int in_spb_sw, const Switches::in_sw_tab_t* table, bool bitmask)
{
for (const Switches::in_sw_tab_t* in_sw_tab = table; in_sw_tab->in_sw_name; in_sw_tab++)
{
if (in_spb_sw == in_sw_tab->in_spb_sw && bitmask == in_sw_tab->in_sw_option)
return in_sw_tab->in_sw_name;
}
#ifdef DEV_BUILD
if (isatty(fileno(stderr)))
fprintf(stderr, "Miss %d %s\n", in_spb_sw, bitmask ? "option" : "switch");
#endif
return NULL;
}
bool Service::actionNeedsArg(UCHAR action)
{
switch (action)
{
case isc_action_svc_get_fb_log:
return false;
}
return true;
}
bool Service::process_switches(ClumpletReader& spb, string& switches)
{
if (spb.getBufferLength() == 0)
return false;
spb.rewind();
const UCHAR svc_action = spb.getClumpTag();
spb.moveNext();
string burp_database, burp_backup;
int burp_options = 0;
string nbk_database, nbk_file;
int nbk_level = -1;
bool val_database = false;
bool found = false;
string::size_type userPos = string::npos;
do
{
bool bigint = false;
switch (svc_action)
{
case isc_action_svc_nbak:
case isc_action_svc_nrest:
found = true;
switch (spb.getClumpTag())
{
case isc_spb_dbname:
if (nbk_database.hasData())
{
(Arg::Gds(isc_unexp_spb_form) << Arg::Str("only one isc_spb_dbname")).raise();
}
get_action_svc_string(spb, nbk_database);
break;
case isc_spb_nbk_level:
if (nbk_level >= 0)
{
(Arg::Gds(isc_unexp_spb_form) << Arg::Str("only one isc_spb_nbk_level")).raise();
}
nbk_level = spb.getInt();
break;
case isc_spb_nbk_file:
if (nbk_file.hasData() && svc_action != isc_action_svc_nrest)
{
(Arg::Gds(isc_unexp_spb_form) << Arg::Str("only one isc_spb_nbk_file")).raise();
}
get_action_svc_string(spb, nbk_file);
break;
case isc_spb_options:
if (!get_action_svc_bitmask(spb, nbackup_in_sw_table, switches))
{
return false;
}
break;
case isc_spb_nbk_direct:
if (!get_action_svc_parameter(spb.getClumpTag(), nbackup_in_sw_table, switches))
{
return false;
}
get_action_svc_string(spb, switches);
}
break;
case isc_action_svc_set_mapping:
case isc_action_svc_drop_mapping:
if (!found)
{
if (!get_action_svc_parameter(svc_action, gsec_action_in_sw_table, switches))
{
return false;
}
found = true;
if (spb.isEof())
{
break;
}
}
switch (spb.getClumpTag())
{
case isc_spb_sql_role_name:
case isc_spb_dbname:
if (!get_action_svc_parameter(spb.getClumpTag(), gsec_in_sw_table, switches))
{
return false;
}
get_action_svc_string(spb, switches);
break;
default:
return false;
}
break;
case isc_action_svc_delete_user:
case isc_action_svc_display_user:
case isc_action_svc_display_user_adm:
if (!found)
{
if (!get_action_svc_parameter(svc_action, gsec_action_in_sw_table, switches))
{
return false;
}
found = true;
if (spb.isEof() && (svc_action == isc_action_svc_display_user ||
svc_action == isc_action_svc_display_user_adm))
{
// in case of "display all users" the spb buffer contains
// nothing but isc_action_svc_display_user or isc_spb_dbname
break;
}
if (spb.getClumpTag() != isc_spb_sec_username)
{
userPos = switches.getCount();
}
}
switch (spb.getClumpTag())
{
case isc_spb_sql_role_name:
case isc_spb_dbname:
if (!get_action_svc_parameter(spb.getClumpTag(), gsec_in_sw_table, switches))
{
return false;
}
get_action_svc_string(spb, switches);
break;
case isc_spb_sec_username:
get_action_svc_string_pos(spb, switches, userPos);
userPos = string::npos;
break;
default:
fatal_exception::raise("Invalid item in service parameter block - invalid code for security database operation");
// no return
}
break;
case isc_action_svc_add_user:
case isc_action_svc_modify_user:
if (!found)
{
if (!get_action_svc_parameter(svc_action, gsec_action_in_sw_table, switches))
{
return false;
}
found = true;
if (spb.getClumpTag() != isc_spb_sec_username)
{
userPos = switches.getCount();
}
}
switch (spb.getClumpTag())
{
case isc_spb_sec_userid:
case isc_spb_sec_groupid:
if (!get_action_svc_parameter(spb.getClumpTag(), gsec_in_sw_table, switches))
{
return false;
}
get_action_svc_data(spb, switches, bigint);
break;
case isc_spb_sec_admin:
if (!get_action_svc_parameter(spb.getClumpTag(), gsec_in_sw_table, switches))
{
return false;
}
switches += (spb.getInt() ? "Yes " : "No ");
break;
case isc_spb_sql_role_name:
case isc_spb_sec_password:
case isc_spb_sec_groupname:
case isc_spb_sec_firstname:
case isc_spb_sec_middlename:
case isc_spb_sec_lastname:
case isc_spb_dbname:
if (!get_action_svc_parameter(spb.getClumpTag(), gsec_in_sw_table, switches))
{
return false;
}
get_action_svc_string(spb, switches);
break;
case isc_spb_sec_username:
get_action_svc_string_pos(spb, switches, userPos);
userPos = string::npos;
break;
default:
return false;
}
break;
case isc_action_svc_db_stats:
switch (spb.getClumpTag())
{
case isc_spb_sts_table:
if (!get_action_svc_parameter(spb.getClumpTag(), dba_in_sw_table, switches))
{
return false;
}
// fall through ....
case isc_spb_dbname:
get_action_svc_string(spb, switches);
break;
case isc_spb_options:
if (!get_action_svc_bitmask(spb, dba_in_sw_table, switches))
{
return false;
}
break;
case isc_spb_command_line:
{
string s;
spb.getString(s);
switches += s;
switches += ' ';
}
break;
default:
return false;
}
break;
case isc_action_svc_backup:
case isc_action_svc_restore:
switch (spb.getClumpTag())
{
case isc_spb_bkp_file:
get_action_svc_string(spb, burp_backup);
break;
case isc_spb_dbname:
get_action_svc_string(spb, burp_database);
break;
case isc_spb_options:
burp_options |= spb.getInt();
if (!get_action_svc_bitmask(spb, reference_burp_in_sw_table, switches))
{
return false;
}
break;
case isc_spb_bkp_length:
get_action_svc_data(spb, burp_backup, bigint);
break;
case isc_spb_res_length:
get_action_svc_data(spb, burp_database, bigint);
break;
case isc_spb_bkp_factor:
case isc_spb_res_buffers:
case isc_spb_res_page_size:
case isc_spb_verbint:
if (!get_action_svc_parameter(spb.getClumpTag(), reference_burp_in_sw_table, switches))
{
return false;
}
get_action_svc_data(spb, switches, bigint);
break;
case isc_spb_res_access_mode:
if (!get_action_svc_parameter(*(spb.getBytes()), reference_burp_in_sw_table, switches))
{
return false;
}
break;
case isc_spb_verbose:
if (!get_action_svc_parameter(spb.getClumpTag(), reference_burp_in_sw_table, switches))
{
return false;
}
break;
case isc_spb_res_fix_fss_data:
case isc_spb_res_fix_fss_metadata:
case isc_spb_bkp_stat:
case isc_spb_bkp_skip_data:
if (!get_action_svc_parameter(spb.getClumpTag(), reference_burp_in_sw_table, switches))
{
return false;
}
get_action_svc_string(spb, switches);
break;
default:
return false;
}
break;
case isc_action_svc_repair:
case isc_action_svc_properties:
switch (spb.getClumpTag())
{
case isc_spb_dbname:
get_action_svc_string(spb, switches);
break;
case isc_spb_options:
if (!get_action_svc_bitmask(spb, alice_in_sw_table, switches))
{
return false;
}
break;
case isc_spb_rpr_commit_trans_64:
case isc_spb_rpr_rollback_trans_64:
case isc_spb_rpr_recover_two_phase_64:
bigint = true;
// fall into
case isc_spb_prp_page_buffers:
case isc_spb_prp_sweep_interval:
case isc_spb_prp_shutdown_db:
case isc_spb_prp_deny_new_attachments:
case isc_spb_prp_deny_new_transactions:
case isc_spb_prp_force_shutdown:
case isc_spb_prp_attachments_shutdown:
case isc_spb_prp_transactions_shutdown:
case isc_spb_prp_set_sql_dialect:
case isc_spb_rpr_commit_trans:
case isc_spb_rpr_rollback_trans:
case isc_spb_rpr_recover_two_phase:
if (!get_action_svc_parameter(spb.getClumpTag(), alice_in_sw_table, switches))
{
return false;
}
get_action_svc_data(spb, switches, bigint);
break;
case isc_spb_prp_write_mode:
case isc_spb_prp_access_mode:
case isc_spb_prp_reserve_space:
if (!get_action_svc_parameter(*(spb.getBytes()), alice_in_sw_table, switches))
{
return false;
}
break;
case isc_spb_prp_shutdown_mode:
case isc_spb_prp_online_mode:
if (get_action_svc_parameter(spb.getClumpTag(), alice_in_sw_table, switches))
{
unsigned int val = spb.getInt();
if (val >= FB_NELEM(alice_mode_sw_table))
{
return false;
}
switches += alice_mode_sw_table[val];
switches += " ";
break;
}
return false;
default:
return false;
}
break;
case isc_action_svc_trace_start:
case isc_action_svc_trace_stop:
case isc_action_svc_trace_suspend:
case isc_action_svc_trace_resume:
case isc_action_svc_trace_list:
if (!found)
{
if (!get_action_svc_parameter(svc_action, trace_action_in_sw_table, switches)) {
return false;
}
found = true;
}
if (svc_action == isc_action_svc_trace_list)
break;
if (!get_action_svc_parameter(spb.getClumpTag(), trace_option_in_sw_table, switches)) {
return false;
}
switch (spb.getClumpTag())
{
case isc_spb_trc_cfg:
case isc_spb_trc_name:
get_action_svc_string(spb, switches);
break;
case isc_spb_trc_id:
get_action_svc_data(spb, switches, bigint);
break;
default:
return false;
}
break;
case isc_action_svc_validate:
if (!get_action_svc_parameter(spb.getClumpTag(), val_option_in_sw_table, switches)) {
return false;
}
switch (spb.getClumpTag())
{
case isc_spb_dbname:
if (val_database) {
(Arg::Gds(isc_unexp_spb_form) << Arg::Str("only one isc_spb_dbname")).raise();
}
val_database = true;
// fall thru
case isc_spb_val_tab_incl:
case isc_spb_val_tab_excl:
case isc_spb_val_idx_incl:
case isc_spb_val_idx_excl:
get_action_svc_string(spb, switches);
break;
case isc_spb_val_lock_timeout:
get_action_svc_data(spb, switches, bigint);
break;
}
break;
default:
return false;
}
spb.moveNext();
} while (! spb.isEof());
if (userPos != string::npos && svc_action != isc_action_svc_display_user &&
svc_action != isc_action_svc_display_user_adm)
{
// unexpected item in service parameter block, expected @1
status_exception::raise(Arg::Gds(isc_unexp_spb_form) << Arg::Str(SPB_SEC_USERNAME));
}
// postfixes for burp & nbackup
switch (svc_action)
{
case isc_action_svc_backup:
switches += (burp_database + burp_backup);
break;
case isc_action_svc_restore:
if (! (burp_options & (isc_spb_res_create | isc_spb_res_replace)))
{
// user not specified create or replace database
// default to create for restore
switches += "-CREATE_DATABASE ";
}
switches += (burp_backup + burp_database);
break;
case isc_action_svc_nbak:
case isc_action_svc_nrest:
if (nbk_database.isEmpty())
{
(Arg::Gds(isc_missing_required_spb) << Arg::Str("isc_spb_dbname")).raise();
}
if (nbk_file.isEmpty())
{
(Arg::Gds(isc_missing_required_spb) << Arg::Str("isc_spb_nbk_file")).raise();
}
if (!get_action_svc_parameter(svc_action, nbackup_action_in_sw_table, switches))
{
return false;
}
if (svc_action == isc_action_svc_nbak)
{
if (nbk_level < 0)
{
(Arg::Gds(isc_missing_required_spb) << Arg::Str("isc_spb_nbk_level")).raise();
}
string temp;
temp.printf("%d ", nbk_level);
switches += temp;
}
switches += nbk_database;
switches += nbk_file;
break;
case isc_action_svc_validate:
if (!val_database)
{
(Arg::Gds(isc_missing_required_spb) << Arg::Str("isc_spb_dbname")).raise();
}
break;
}
switches.rtrim();
return switches.length() > 0;
}
bool Service::get_action_svc_bitmask(const ClumpletReader& spb,
const Switches::in_sw_tab_t* table,
string& switches)
{
const int opt = spb.getInt();
ISC_ULONG mask = 1;
for (int count = (sizeof(ISC_ULONG) * 8) - 1; count--; mask <<= 1)
{
if (opt & mask)
{
const TEXT* s_ptr = find_switch((opt & mask), table, true);
if (!s_ptr)
{
return false;
}
switches += '-';
switches += s_ptr;
switches += ' ';
}
}
return true;
}
void Service::get_action_svc_string(const ClumpletReader& spb, string& switches)
{
string s;
spb.getString(s);
addStringWithSvcTrmntr(s, switches);
}
void Service::get_action_svc_string_pos(const ClumpletReader& spb, string& switches, string::size_type p)
{
if (p == string::npos)
get_action_svc_string(spb, switches);
else
{
string s;
get_action_svc_string(spb, s);
switches.insert(p, s);
}
}
void Service::get_action_svc_data(const ClumpletReader& spb, string& switches, bool bigint)
{
string s;
s.printf("%" SQUADFORMAT" ", bigint ? spb.getBigInt() : (SINT64) spb.getInt());
switches += s;
}
bool Service::get_action_svc_parameter(UCHAR action,
const Switches::in_sw_tab_t* table,
string& switches)
{
const TEXT* s_ptr = find_switch(action, table, false);
if (!s_ptr)
{
return false;
}
switches += '-';
switches += s_ptr;
switches += ' ';
return true;
}
const char* Service::getServiceMgr() const
{
return svc_service ? svc_service->serv_name : NULL;
}
const char* Service::getServiceName() const
{
return svc_service_run ? svc_service_run->serv_name : NULL;
}
bool Service::getUserAdminFlag() const
{
return (svc_user_flag & SVC_user_dba);
}