/* * 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 #include #include "../common/file_params.h" #include #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 #endif #ifdef HAVE_SYS_WAIT_H # include #endif #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_VFORK_H #include #endif #ifdef HAVE_FCNTL_H #include #endif #if !defined(WIN_NT) # include # include # include #else # include # include // _open, _get_osfhandle # include #endif #include #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 globalServicesMutex; // All that we need to shutdown service threads when shutdown in progress typedef Array AllServices; GlobalPtr 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 AllThreads; static void waitFor(AllThreads& thr) { while (thr.hasData()) { Thread::Handle h(thr.pop()); Thread::waitForCompletion(h); } } AllThreads threads; Mutex threadsMutex; }; GlobalPtr 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(strlen(text)); enqueue(reinterpret_cast(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(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(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(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(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 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(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(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(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 defConf(Config::getDefaultConfig()); const char* secDb = defConf->getSecurityDatabase(); if (!(info = INF_put_item(item, static_cast(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(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 defConf(Config::getDefaultConfig()); const char* secDb = defConf->getSecurityDatabase(); if (!(info = INF_put_item(item, static_cast(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 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); }