/* * 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 * */ #include "firebird.h" #include #include #include "../jrd/common.h" #include "../jrd/file_params.h" #include #include "../jrd/jrd.h" #include "../jrd/svc.h" #include "../jrd/constants.h" #include "../jrd/jrd_pwd.h" #include "../alice/aliceswi.h" #include "../burp/burpswi.h" #include "../jrd/ibase.h" #include "gen/iberror.h" #include "../jrd/license.h" #include "../jrd/err_proto.h" #include "../jrd/gds_proto.h" #include "../jrd/inf_proto.h" #include "../jrd/isc_proto.h" #include "../jrd/jrd_proto.h" #include "../jrd/mov_proto.h" #include "../jrd/thread_proto.h" #include "../jrd/why_proto.h" #include "../jrd/utl_proto.h" #include "../jrd/jrd_proto.h" #include "../jrd/enc_proto.h" #include "../utilities/gsec/gsecswi.h" #include "../utilities/gstat/dbaswi.h" #include "../common/classes/alloc.h" #include "../common/classes/init.h" #include "../common/classes/ClumpletWriter.h" #include "../jrd/ibase.h" #include "../common/utils_proto.h" #include "../utilities/common/cmd_util_proto.h" #include "../jrd/scl.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 SOLARIS #include #endif #ifdef SCO_UNIX #include #endif #if !defined(WIN_NT) # include # include # include # include #else # include # include // _open, _get_osfhandle # include # include # include #endif #define statistics stat 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 TEXT SVC_TRMNTR = '\377'; const char* const SPB_SEC_USERNAME = "isc_spb_sec_username"; namespace { // Option block for service parameter block struct Options { Firebird::string spb_sys_user_name; Firebird::string spb_user_name; Firebird::string spb_password; Firebird::string spb_password_enc; Firebird::string spb_command_line; Firebird::string spb_network_protocol; Firebird::string spb_remote_address; Firebird::string spb_trusted_login; bool spb_trusted_role; USHORT spb_version; // Parse service parameter block picking up options and things. Options(Firebird::ClumpletReader& spb) : spb_trusted_role(false), spb_version(0) { const UCHAR p = spb.getBufferTag(); if (p != isc_spb_version1 && p != isc_spb_current_version) { ERR_post(isc_bad_spb_form, isc_arg_gds, isc_wrospbver, 0); } spb_version = p; for (spb.rewind(); !(spb.isEof()); spb.moveNext()) { switch (spb.getClumpTag()) { case isc_spb_sys_user_name: spb.getString(spb_sys_user_name); break; case isc_spb_user_name: spb.getString(spb_user_name); break; case isc_spb_password: spb.getString(spb_password); break; case isc_spb_password_enc: spb.getString(spb_password_enc); break; case isc_spb_trusted_auth: spb.getString(spb_trusted_login); break; case isc_spb_trusted_role: spb_trusted_role = true; break; case isc_spb_command_line: spb.getString(spb_command_line); break; case isc_spb_address_path: { Firebird::ClumpletReader address_stack(Firebird::ClumpletReader::UnTagged, spb.getBytes(), spb.getClumpLength()); while (!address_stack.isEof()) { if (address_stack.getClumpTag() != isc_dpb_address) { address_stack.moveNext(); continue; } Firebird::ClumpletReader address(Firebird::ClumpletReader::UnTagged, address_stack.getBytes(), address_stack.getClumpLength()); while (!address.isEof()) { switch (address.getClumpTag()) { case isc_dpb_addr_protocol: address.getString(spb_network_protocol); break; case isc_dpb_addr_endpoint: address.getString(spb_remote_address); break; default: break; } address.moveNext(); } break; } } break; } } } }; // Generic mutex to synchronize services Firebird::GlobalPtr svc_mutex; // All that we need to shutdown service threads when shutdown in progress typedef Firebird::Array AllServices; Firebird::GlobalPtr allServices; // protected by svc_mutex volatile bool svcShutdown = false; } // anonymous namespace using namespace Jrd; 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 (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::printf(const SCHAR* format, ...) { // Ensure that service is not detached. if (svc_flags & SVC_detached) { return; } Firebird::string buf; va_list arglist; va_start(arglist, format); buf.vprintf(format, arglist); va_end(arglist); for (size_t n = 0; n < buf.length() && !(svc_flags & SVC_detached); ++n) { enqueueByte(buf[n]); } } bool Service::isService() { return true; } void Service::started() { if (!(svc_flags & SVC_evnt_fired)) { Firebird::MutexLockGuard guard(svc_mutex); svc_flags |= SVC_evnt_fired; svcStart.release(); } } void Service::finish() { finish(SVC_finished); } void Service::putLine(char tag, const char* val) { size_t len = strlen(val); len &= 0xFFFF; enqueueByte(tag); enqueueByte(len); enqueueByte(len >> 8); for (size_t i = 0; i < len; i++) { enqueueByte(val[i]); } } void Service::putSLong(char tag, SLONG val) { enqueueByte(tag); enqueueByte(val); enqueueByte(val >> 8); enqueueByte(val >> 16); enqueueByte(val >> 24); } void Service::putChar(char tag, char val) { enqueueByte(tag); enqueueByte(val); } void Service::stuffStatus(const ISC_STATUS* status_vector) { if (checkForShutdown()) { return; } if (status_vector != svc_status) { ISC_STATUS* status = svc_status; int i = 0; while (*status && (++i < ISC_STATUS_LENGTH)) { status++; } if (i == 1) // We have a vector: isc_arg_gds, isc_arg_end. { i = 0; status = svc_status; } for (int j = 0; status_vector[j] && (i < ISC_STATUS_LENGTH); j++, i++) { *status++ = status_vector[j]; } } } void Service::stuffStatus(const USHORT facility, const USHORT errcode, const MsgFormat::SafeArg& args) { if (checkForShutdown()) { return; } CMD_UTIL_put_svc_status(svc_status, facility, errcode, args); } void Service::hidePasswd(ArgvType&, int) { // no action } ISC_STATUS* Service::getStatus() { return svc_status; } void Service::checkService() { // no action } void Service::need_admin_privs(ISC_STATUS** status, const char* message) { ISC_STATUS* stat = *status; *stat++ = isc_insufficient_svc_privileges; *stat++ = isc_arg_string; *stat++ = (ISC_STATUS)(U_IPTR) ERR_string(message, strlen(message)); *stat++ = isc_arg_end; *status = stat; } bool Service::ck_space_for_numeric(char*& info, const char* const end) { if ((info + 1 + sizeof(ULONG)) > end) { if (info < end) *info++ = isc_info_truncated; return false; } return true; } #ifdef DEBUG THREAD_ENTRY_DECLARE test_thread(THREAD_ENTRY_PARAM); void test_cmd(USHORT, SCHAR *, TEXT **); #define TEST_THREAD test_thread #define TEST_CMD test_cmd #else #define TEST_THREAD NULL #define TEST_CMD NULL #endif /* Service Functions */ #if !defined(BOOT_BUILD) #include "../burp/burp_proto.h" #include "../alice/alice_proto.h" THREAD_ENTRY_DECLARE main_gstat(THREAD_ENTRY_PARAM arg); #define MAIN_GBAK BURP_main #define MAIN_GFIX ALICE_main #define MAIN_GSTAT main_gstat #else #define MAIN_GBAK NULL #define MAIN_GFIX NULL #define MAIN_GSTAT NULL #endif #if !defined(EMBEDDED) && !defined(BOOT_BUILD) #include "../utilities/gsec/gsec_proto.h" #define MAIN_GSEC GSEC_main #else #define MAIN_GSEC NULL #endif struct serv_entry { USHORT serv_action; // isc_action_svc_.... const TEXT* serv_name; // old service name const TEXT* serv_std_switches; // old cmd-line switches ThreadEntryPoint* serv_thd; // thread to execute }; static const serv_entry services[] = { { isc_action_max, "print_cache", "", NULL }, { isc_action_max, "print_locks", "", NULL }, { isc_action_max, "start_cache", "", NULL }, { isc_action_max, "analyze_database", "", MAIN_GFIX }, { isc_action_max, "backup", "-b", MAIN_GBAK }, { isc_action_max, "create", "-c", MAIN_GBAK }, { isc_action_max, "restore", "-r", MAIN_GBAK }, { isc_action_max, "gdef", "-svc", NULL }, { isc_action_max, "gsec", "-svc", MAIN_GSEC }, { isc_action_max, "disable_journal", "-disable", NULL }, { isc_action_max, "dump_journal", "-online_dump", NULL }, { isc_action_max, "enable_journal", "-enable", NULL }, { isc_action_max, "monitor_journal", "-console", NULL }, { isc_action_max, "query_server", NULL, NULL }, { isc_action_max, "start_journal", "-server", NULL }, { isc_action_max, "stop_cache", "-shut -cache", MAIN_GFIX }, { isc_action_max, "stop_journal", "-console", NULL }, { isc_action_max, "anonymous", NULL, NULL }, // NEW VERSION 2 calls, the name field MUST be different from those names above { isc_action_max, "service_mgr", NULL, NULL }, { isc_action_svc_backup, "Backup Database", NULL, MAIN_GBAK }, { isc_action_svc_restore, "Restore Database", NULL, MAIN_GBAK }, { isc_action_svc_repair, "Repair Database", NULL, MAIN_GFIX }, { isc_action_svc_add_user, "Add User", NULL, MAIN_GSEC }, { isc_action_svc_delete_user, "Delete User", NULL, MAIN_GSEC }, { isc_action_svc_modify_user, "Modify User", NULL, MAIN_GSEC }, { isc_action_svc_display_user, "Display User", NULL, MAIN_GSEC }, { isc_action_svc_properties, "Database Properties", NULL, MAIN_GFIX }, { isc_action_svc_lock_stats, "Lock Stats", NULL, TEST_THREAD }, { isc_action_svc_db_stats, "Database Stats", NULL, MAIN_GSTAT }, { isc_action_svc_get_fb_log, "Get Log File", NULL, Service::readFbLog }, /* actions with no names are undocumented */ { isc_action_svc_set_config, NULL, NULL, TEST_THREAD }, { isc_action_svc_default_config, NULL, NULL, TEST_THREAD }, { isc_action_svc_set_env, NULL, NULL, TEST_THREAD }, { isc_action_svc_set_env_lock, NULL, NULL, TEST_THREAD }, { isc_action_svc_set_env_msg, NULL, NULL, TEST_THREAD }, { 0, NULL, NULL, NULL } }; /* The SERVER_CAPABILITIES_FLAG is used to mark architectural ** differences across servers. This allows applications like server ** manager to disable features as necessary. */ #ifdef SUPERSERVER const ULONG SERVER_CAPABILITIES = REMOTE_HOP_SUPPORT | MULTI_CLIENT_SUPPORT | SERVER_CONFIG_SUPPORT; # ifdef WIN_NT const ULONG SERVER_CAPABILITIES_FLAG = SERVER_CAPABILITIES | QUOTED_FILENAME_SUPPORT; # else const ULONG SERVER_CAPABILITIES_FLAG = SERVER_CAPABILITIES | NO_SERVER_SHUTDOWN_SUPPORT; # endif // WIN_NT #else // SUPERSERVER const ULONG SERVER_CAPABILITIES_FLAG = REMOTE_HOP_SUPPORT | NO_SERVER_SHUTDOWN_SUPPORT; #endif // SERVER_CAPABILITIES Service::Service(USHORT service_length, const TEXT* service_name, USHORT spb_length, const UCHAR* spb_data) : svc_parsed_sw(getPool()), svc_handle(0), svc_status(svc_status_array), svc_stdout_head(1), svc_stdout_tail(SVC_STDOUT_BUFFER_SIZE), svc_resp_alloc(getPool()), svc_resp_buf(0), svc_resp_ptr(0), svc_resp_buf_len(0), svc_resp_len(0), svc_flags(0), svc_user_flag(0), svc_spb_version(0), svc_do_shutdown(false), svc_username(getPool()), svc_enc_password(getPool()), svc_trusted_login(getPool()), svc_trusted_role(false), svc_uses_security_database(false), svc_switches(getPool()), svc_perm_sw(getPool()) { memset(svc_status_array, 0, sizeof svc_status_array); { // scope // Account service block in global array Firebird::MutexLockGuard guard(svc_mutex); checkForShutdown(); allServices->add(this); } // If the service name begins with a slash, ignore it. if (*service_name == '/' || *service_name == '\\') { service_name++; if (service_length) service_length--; } // Find the service by looking for an exact match. const Firebird::string misc_buf(service_name, service_length ? service_length : strlen(service_name)); const serv_entry* serv; for (serv = services; serv->serv_name; serv++) { if (misc_buf == serv->serv_name) break; } if (!serv->serv_name) { Firebird::status_exception::raise(isc_service_att_err, isc_arg_gds, isc_svcnotdef, isc_arg_string, ERR_string(misc_buf), 0); } // Process the service parameter block. Firebird::ClumpletReader spb(Firebird::ClumpletReader::SpbAttach, spb_data, spb_length); Options options(spb); // Perhaps checkout the user in the security database. SecurityDatabase::InitHolder siHolder; USHORT user_flag; if (!strcmp(serv->serv_name, "anonymous")) { user_flag = SVC_user_none; } else { if (options.spb_trusted_login.hasData()) { options.spb_user_name = options.spb_trusted_login; } else { if (!options.spb_user_name.hasData()) { // user name and password are required while // attaching to the services manager Firebird::status_exception::raise(isc_service_att_err, isc_arg_gds, isc_svcnouser, 0); } else { Firebird::string name; // unused after retrieved int id, group, node_id; const Firebird::string remote = options.spb_network_protocol + (options.spb_network_protocol.isEmpty() || options.spb_remote_address.isEmpty() ? "" : "/") + options.spb_remote_address; SecurityDatabase::verifyUser(name, options.spb_user_name.nullStr(), options.spb_password.nullStr(), options.spb_password_enc.nullStr(), &id, &group, &node_id, remote); svc_uses_security_database = true; } } if (options.spb_user_name.length() > USERNAME_LENGTH) { Firebird::status_exception::raise(isc_long_login, isc_arg_number, options.spb_user_name.length(), isc_arg_number, USERNAME_LENGTH, 0); } // Check that the validated user has the authority to access this service Firebird::string uName(options.spb_user_name); uName.upper(); if ((uName != SYSDBA_USER_NAME) && !options.spb_trusted_role) { user_flag = SVC_user_any; } else { user_flag = SVC_user_dba | SVC_user_any; } } // move service switches in Firebird::string switches; if (serv->serv_std_switches) switches = serv->serv_std_switches; if (options.spb_command_line.hasData() && serv->serv_std_switches) switches += " "; switches += options.spb_command_line; svc_flags = switches.hasData() ? SVC_cmd_line : 0; svc_perm_sw = switches; svc_user_flag = user_flag; svc_service = serv; svc_spb_version = options.spb_version; svc_username = options.spb_user_name; svc_trusted_login = options.spb_trusted_login; svc_trusted_role = options.spb_trusted_role; // The password will be issued to the service threads on NT since // there is no OS authentication. If the password is not yet // encrypted, then encrypt it before saving it (since there is no // decrypt function). if (options.spb_password_enc.hasData()) { svc_enc_password = options.spb_password_enc; } else if (options.spb_password.hasData()) { svc_enc_password.resize(MAX_PASSWORD_LENGTH + 2); ENC_crypt(svc_enc_password.begin(), svc_enc_password.length(), options.spb_password.c_str(), PASSWORD_SALT); svc_enc_password.recalculate_length(); svc_enc_password.erase(0, 2); } // 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 && options.spb_version == isc_spb_version1) { start(serv->serv_thd); } if (svc_uses_security_database) { siHolder.clear(); } } void Service::detach() { if (svc_do_shutdown) { if (fb_shutdown(10 * 1000 /* 10 seconds */) == FB_SUCCESS) { Firebird::InstanceControl::registerShutdown(0); exit(0); } } if (svc_uses_security_database) { SecurityDatabase::shutdown(); } // Mark service as detached. finish(SVC_detached); } Service::~Service() { #ifdef WIN_NT CloseHandle((HANDLE) svc_handle); #endif Firebird::MutexLockGuard guard(svc_mutex); AllServices& all(allServices); for (unsigned int pos = 0; pos < all.getCount(); ++pos) { if (all[pos] == this) { all.remove(pos); break; } } } bool Service::checkForShutdown() { if (svcShutdown) { Firebird::MutexLockGuard guard(svc_mutex); if (svc_flags & SVC_shutdown) { // Here we avoid multiple exceptions thrown return true; } svc_flags |= SVC_shutdown; Firebird::status_exception::raise(isc_att_shutdown, isc_arg_end); } return false; } void Service::shutdownServices() { svcShutdown = true; Firebird::MutexLockGuard guard(svc_mutex); AllServices& all(allServices); for (unsigned int pos = 0; pos < all.getCount(); ) { if (all[pos]->svc_flags & SVC_thd_running) { svc_mutex->leave(); THD_sleep(1); svc_mutex->enter(); pos = 0; continue; } ++pos; } } ISC_STATUS Service::query2(thread_db* tdbb, USHORT send_item_length, const SCHAR* send_items, USHORT recv_item_length, const SCHAR* recv_items, USHORT buffer_length, SCHAR* info) { SCHAR item; char buffer[MAXPATHLEN]; USHORT l, length, version, get_flags; // Setup the status vector ISC_STATUS* status = tdbb->tdbb_status_vector; *status++ = isc_arg_gds; // Process the send portion of the query first. USHORT timeout = 0; const SCHAR* items = send_items; const SCHAR* 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(reinterpret_cast< const UCHAR*>(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(reinterpret_cast< const UCHAR*>(items), l); break; case isc_info_svc_version: version = (USHORT) gds__vax_integer(reinterpret_cast< const UCHAR*>(items), l); break; } } items += l; } else items += 2; break; } } // Process the receive portion of the query now. const SCHAR* const end = info + buffer_length; items = recv_items; const SCHAR* const end_items2 = items + recv_item_length; SCHAR* start_info; if (*items == isc_info_length) { start_info = info; items++; } else { start_info = 0; } 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: Firebird::status_exception::raise(isc_bad_spb_form, 0); break; } } switch ((item = *items++)) { case isc_info_end: break; case isc_info_svc_svr_db_info: if (svc_user_flag & SVC_user_dba) { UCHAR dbbuf[1024]; ULONG num_dbs = 0; ULONG num_att = 0; *info++ = item; UCHAR* const ptr = JRD_num_attachments(dbbuf, sizeof(dbbuf), JRD_info_dbnames, &num_att, &num_dbs); /* 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 */ const TEXT* ptr2 = reinterpret_cast(ptr); if (ptr2) { USHORT num = (USHORT) isc_vax_integer(ptr2, sizeof(USHORT)); fb_assert(num == num_dbs); ptr2 += sizeof(USHORT); for (; num; num--) { length = (USHORT) isc_vax_integer(ptr2, sizeof(USHORT)); ptr2 += sizeof(USHORT); if (! (info = INF_put_item(isc_spb_dbname, length, ptr2, info, end))) { if (ptr != dbbuf) gds__free(ptr); // memory has been allocated by JRD_num_attachments() return 0; } ptr2 += length; } if (ptr != dbbuf) gds__free(ptr); // memory has been allocated by JRD_num_attachments() } 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; WHY_set_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; WHY_set_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) { switch (item) { case isc_info_svc_get_env: gds__prefix(buffer, ""); break; case isc_info_svc_get_env_lock: gds__prefix_lock(buffer, ""); break; case isc_info_svc_get_env_msg: gds__prefix_msg(buffer, ""); } /* Note: it is safe to use strlen to get a length of "buffer" because gds_prefix[_lock|_msg] return a zero-terminated string */ info = INF_put_item(item, strlen(buffer), buffer, info, end); if (!info) { return 0; } } else { need_admin_privs(&status, "isc_info_svc_get_env"); } break; #ifdef SUPERSERVER case isc_info_svc_dump_pool_info: { char fname[MAXPATHLEN]; size_t length2 = isc_vax_integer(items, sizeof(USHORT)); if (length2 >= sizeof(fname)) length2 = sizeof(fname) - 1; // truncation items += sizeof(USHORT); strncpy(fname, items, length2); fname[length2] = 0; break; } #endif 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, SERVER_CAPABILITIES_FLAG); break; case isc_info_svc_running: /* Returns the status of the flag SVC_thd_running */ if (!ck_space_for_numeric(info, end)) return 0; *info++ = item; if (svc_flags & SVC_thd_running) ADD_SPB_NUMERIC(info, TRUE) else ADD_SPB_NUMERIC(info, FALSE) break; case isc_info_svc_server_version: /* The version of the server engine */ info = INF_put_item(item, strlen(GDS_VERSION), GDS_VERSION, info, end); if (!info) { return 0; } break; case isc_info_svc_implementation: /* The server implementation - e.g. Firebird/sun4 */ isc_format_implementation(IMPLEMENTATION, sizeof(buffer), buffer, 0, 0, NULL); info = INF_put_item(item, strlen(buffer), buffer, info, end); if (!info) { return 0; } break; case isc_info_svc_user_dbpath: if (svc_user_flag & SVC_user_dba) { /* The path to the user security database (security2.fdb) */ SecurityDatabase::getPath(buffer); if (!(info = INF_put_item(item, strlen(buffer), buffer, 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(reinterpret_cast< UCHAR*>(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 Firebird::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(reinterpret_cast(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, reinterpret_cast (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(reinterpret_cast(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; } 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_flags & 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; default: *status++ = isc_wish_list; *status++ = isc_arg_end; break; } if (svc_user_flag == SVC_user_none) break; } if (info < end) *info = isc_info_end; if (start_info && (end - info >= 7)) { SLONG number = 1 + (info - start_info); memmove(start_info + 7, start_info, number); USHORT length2 = INF_convert(number, buffer); INF_put_item(isc_info_length, length2, buffer, start_info, end); } if (!(svc_flags & SVC_thd_running)) { finish(SVC_finished); } return tdbb->tdbb_status_vector[1]; } void Service::query(USHORT send_item_length, const SCHAR* send_items, USHORT recv_item_length, const SCHAR* recv_items, USHORT buffer_length, SCHAR* info) { SCHAR item, *p; char buffer[256]; TEXT PathBuffer[MAXPATHLEN]; USHORT l, length, version, get_flags; // Process the send portion of the query first. USHORT timeout = 0; const SCHAR* items = send_items; const SCHAR* 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(reinterpret_cast(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(reinterpret_cast(items), l); break; case isc_info_svc_version: version = (USHORT) gds__vax_integer(reinterpret_cast(items), l); break; } } items += l; } else items += 2; break; } } // Process the receive portion of the query now. const SCHAR* const end = info + buffer_length; items = recv_items; const SCHAR* 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) { ULONG num_att = 0; ULONG num_dbs = 0; JRD_num_attachments(NULL, 0, JRD_info_none, &num_att, &num_dbs); 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; WHY_set_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; WHY_set_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) { switch (item) { case isc_info_svc_get_env: gds__prefix(PathBuffer, ""); break; case isc_info_svc_get_env_lock: gds__prefix_lock(PathBuffer, ""); 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, 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; #ifdef SUPERSERVER case isc_info_svc_dump_pool_info: { char fname[MAXPATHLEN]; size_t length2 = isc_vax_integer(items, sizeof(USHORT)); if (length2 >= sizeof(fname)) length2 = sizeof(fname) - 1; // truncation items += sizeof(USHORT); strncpy(fname, items, length2); fname[length2] = 0; break; } #endif /* 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(SERVER_CAPABILITIES_FLAG, 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(GDS_VERSION) - 1; for (const TEXT* gvp = GDS_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++ = IMPLEMENTATION; 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) */ SecurityDatabase::getPath(buffer); if (!(info = INF_put_item(item, strlen(buffer), buffer, 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(reinterpret_cast(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 Firebird::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(reinterpret_cast(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, reinterpret_cast(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(reinterpret_cast(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_flags & 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; } if (!(svc_flags & SVC_thd_running)) { finish(SVC_finished); } } void Service::start(USHORT spb_length, const UCHAR* spb_data) { Firebird::ClumpletReader spb(Firebird::ClumpletReader::SpbStart, spb_data, spb_length); /* The name of the service is the first element of the buffer */ 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) Firebird::status_exception::raise(isc_service_att_err, isc_arg_gds, isc_service_not_supported, 0); /* currently we do not use "anonymous" service for any purposes but isc_service_query() */ if (svc_user_flag == SVC_user_none) { Firebird::status_exception::raise(isc_bad_spb_form, 0); } { // scope for locked svc_mutex Firebird::MutexLockGuard guard(svc_mutex); if (svc_flags & SVC_thd_running) { Firebird::status_exception::raise(isc_svc_in_use, isc_arg_string, ERR_string(serv->serv_name, strlen(serv->serv_name)), 0); } /* 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 */ if (svc_id == isc_action_svc_backup || svc_id == isc_action_svc_restore || svc_id == isc_action_svc_repair || 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_db_stats || svc_id == isc_action_svc_properties) { /* add the username and password to the end of svc_switches if needed */ if (svc_switches.hasData()) { if (svc_trusted_login.hasData()) { svc_switches += " -"; svc_switches += TRUSTED_USER_SWITCH; svc_switches += ' '; svc_switches += svc_username; if (svc_trusted_role) { svc_switches += " -"; svc_switches += TRUSTED_ROLE_SWITCH; } } else { if (svc_username.hasData()) { svc_switches += " -"; svc_switches += USERNAME_SWITCH; svc_switches += ' '; svc_switches += svc_username; } if (svc_enc_password.hasData()) { svc_switches += " -"; svc_switches += PASSWORD_SWITCH; svc_switches += ' '; svc_switches += svc_enc_password; } } } } // All services except for get_ib_log require switches spb.rewind(); if ((!svc_switches.hasData()) && svc_id != isc_action_svc_get_fb_log) { Firebird::status_exception::raise(isc_bad_spb_form, 0); } // Do not let everyone look at server log if (svc_id == isc_action_svc_get_fb_log && !(svc_user_flag & SVC_user_dba)) { Firebird::status_exception::raise(isc_adm_task_denied, 0); } // Break up the command line into individual arguments. parseSwitches(); // The service block can be reused hence init a status vector. memset((void *) svc_status, 0, sizeof(ISC_STATUS_ARRAY)); if (serv->serv_thd) { { // scope Firebird::MutexLockGuard guard(svc_mutex); svc_flags &= ~SVC_evnt_fired; svc_flags |= SVC_thd_running; } gds__thread_start(serv->serv_thd, this, THREAD_medium, 0, (void *) &svc_handle); // 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 gds__thread_start will almost always succeed. if (svcStart.tryEnter(60)) { // started() was called break; } } } else { Firebird::status_exception::raise(isc_svcnotdef, isc_arg_string, ERR_string(serv->serv_name, strlen(serv->serv_name)), 0); } } THREAD_ENTRY_DECLARE Service::readFbLog(THREAD_ENTRY_PARAM arg) { Service* service = (Service*) arg; service->readFbLog(); return 0; } void Service::readFbLog() { bool svc_started = false; ISC_STATUS *status = svc_status; *status++ = isc_arg_gds; TEXT name[MAXPATHLEN]; gds__prefix(name, LOGFILE); FILE* file = fopen(name, "r"); if (file != NULL) { *status++ = FB_SUCCESS; *status++ = isc_arg_end; started(); svc_started = true; TEXT buffer[100]; while (!feof(file) && !ferror(file)) { fgets(buffer, sizeof(buffer), file); output(buffer); } } if (!file || file && ferror(file)) { *status++ = isc_sys_request; CMD_UTIL_put_status_arg(status, file ? "fgets" : "fopen"); *status++ = SYS_ARG; *status++ = errno; *status++ = isc_arg_end; if (!svc_started) { started(); } } if (file) fclose(file); finish(SVC_finished); } USHORT Service::add_one(USHORT i) { return ((i % SVC_STDOUT_BUFFER_SIZE) + 1); } bool Service::empty() const { return add_one(svc_stdout_tail) == svc_stdout_head; } bool Service::full() const { return add_one(add_one(svc_stdout_tail)) == svc_stdout_head; } UCHAR Service::dequeueByte() { const UCHAR ch = svc_stdout[svc_stdout_head]; svc_stdout_head = add_one(svc_stdout_head); return ch; } void Service::enqueueByte(UCHAR ch) { if (checkForShutdown()) { return; } // Wait for space in buffer while service is not detached. while (full() && !(svc_flags & SVC_detached)) { THREAD_SLEEP(1); if (checkForShutdown()) { return; } } // Ensure that service is not detached. if (!(svc_flags & SVC_detached)) { svc_stdout[add_one(svc_stdout_tail)] = ch; svc_stdout_tail = add_one(svc_stdout_tail); } } void Service::start(ThreadEntryPoint* service_thread) { // Break up the command line into individual arguments. parseSwitches(); if (svc_service && svc_service->serv_name) { argv[0] = svc_service->serv_name; } gds__thread_start(service_thread, this, THREAD_medium, 0, (void*) &svc_handle); } void Service::get(SCHAR* 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; { // scope Firebird::MutexLockGuard guard(svc_mutex); svc_flags &= ~SVC_timeout; } while (length) { if (empty()) THREAD_SLEEP(1); #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)) { Firebird::MutexLockGuard guard(svc_mutex); svc_flags &= SVC_timeout; return; } while (!empty() && length > 0) { const int ch = dequeueByte(); 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)++] = ' '; return; } buffer[(*return_length)++] = ch; } if (empty() && (svc_flags & SVC_finished)) return; } } void Service::put(const SCHAR* buffer, USHORT length) { // Nothing } void Service::finish(USHORT flag) { if (flag == SVC_finished || flag == SVC_detached) { Firebird::MutexLockGuard guard(svc_mutex); svc_flags |= flag; if (! (svc_flags & SVC_thd_running)) { svc_flags |= SVC_finished; } if (svc_flags & SVC_finished && svc_flags & SVC_detached) { delete this; return; } if (svc_flags & SVC_finished) { svc_flags &= ~SVC_thd_running; #ifdef WIN_NT CloseHandle((HANDLE) svc_handle); #endif svc_handle = 0; } } } void Service::conv_switches(Firebird::ClumpletReader& spb, Firebird::string& switches) { spb.rewind(); if (spb.getClumpTag() < isc_action_min || spb.getClumpTag() > isc_action_max) { return; /* error - action not defined */ } // convert to string Firebird::string sw; if (! process_switches(spb, sw)) { return; } switches = sw; } const TEXT* Service::find_switch(int in_spb_sw, const in_sw_tab_t* table) { for (const 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) return in_sw_tab->in_sw_name; } return NULL; } bool Service::process_switches(Firebird::ClumpletReader& spb, Firebird::string& switches) { if (spb.getBufferLength() == 0) return false; spb.rewind(); const UCHAR svc_action = spb.getClumpTag(); spb.moveNext(); Firebird::string burp_database, burp_backup; int burp_options = 0; bool found = false; do { switch (svc_action) { case isc_action_svc_delete_user: case isc_action_svc_display_user: if (!found) { if (!get_action_svc_parameter(svc_action, gsec_action_in_sw_table, switches)) { return false; } if (spb.isEof() && svc_action == isc_action_svc_display_user) { // 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 && spb.getClumpTag() != isc_spb_dbname) { // unexpected item in service parameter block, expected @1 Firebird::status_exception::raise(isc_unexp_spb_form, isc_arg_string, ERR_string(SPB_SEC_USERNAME, strlen(SPB_SEC_USERNAME)), 0); } found = true; } 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; } // fall through .... case isc_spb_sec_username: get_action_svc_string(spb, switches); break; default: return false; } 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; } if (spb.getClumpTag() != isc_spb_sec_username) { // unexpected item in service parameter block, expected @1 Firebird::status_exception::raise(isc_unexp_spb_form, isc_arg_string, ERR_string(SPB_SEC_USERNAME, strlen(SPB_SEC_USERNAME)), 0); } found = true; } 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); 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; } // fall through .... case isc_spb_sec_username: get_action_svc_string(spb, switches); break; default: return false; } break; case isc_action_svc_db_stats: switch (spb.getClumpTag()) { 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: get_action_svc_string(spb, 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, burp_in_sw_table, switches)) { return false; } break; case isc_spb_bkp_length: get_action_svc_data(spb, burp_backup); break; case isc_spb_res_length: get_action_svc_data(spb, burp_database); break; case isc_spb_bkp_factor: case isc_spb_res_buffers: case isc_spb_res_page_size: if (!get_action_svc_parameter(spb.getClumpTag(), burp_in_sw_table, switches)) { return false; } get_action_svc_data(spb, switches); break; case isc_spb_res_access_mode: if (!get_action_svc_parameter(*(spb.getBytes()), burp_in_sw_table, switches)) { return false; } break; case isc_spb_verbose: if (!get_action_svc_parameter(spb.getClumpTag(), burp_in_sw_table, switches)) { return false; } 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_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_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); 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; default: return false; } break; default: return false; } spb.moveNext(); } while (! spb.isEof()); // postfixes for burp 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; } switches.rtrim(); return switches.length() > 0; } bool Service::get_action_svc_bitmask(const Firebird::ClumpletReader& spb, const in_sw_tab_t* table, Firebird::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); if (!s_ptr) { return false; } switches += '-'; switches += s_ptr; switches += ' '; } } return true; } void Service::get_action_svc_string(const Firebird::ClumpletReader& spb, Firebird::string& switches) { // All string parameters are delimited by SVC_TRMNTR. // This is done to ensure that paths with spaces are handled correctly // when creating the argc / argv paramters for the service. // SVC_TRMNTRs inside the string are duplicated. Firebird::string s; spb.getString(s); for (size_t i = 0; i < s.length(); ++i) { if (s[i] == SVC_TRMNTR) { s.insert(i, 1, SVC_TRMNTR); ++i; } } switches += SVC_TRMNTR; switches += s; switches += SVC_TRMNTR; switches += ' '; } void Service::get_action_svc_data(const Firebird::ClumpletReader& spb, Firebird::string& switches) { Firebird::string s; s.printf("%"ULONGFORMAT" ", spb.getInt()); switches += s; } bool Service::get_action_svc_parameter(UCHAR action, const in_sw_tab_t* table, Firebird::string& switches) { const TEXT* s_ptr = find_switch(action, table); if (!s_ptr) { return false; } switches += '-'; switches += s_ptr; switches += ' '; return true; } #ifdef DEBUG /* The following two functions are temporary stubs and will be * removed as the services API takes shape. They are used to * test that the paths for starting services and parsing command-lines * are followed correctly. */ THREAD_ENTRY_DECLARE test_thread(THREAD_ENTRY_PARAM) { gds__log("Starting service"); return FINI_OK; } void test_cmd(USHORT spb_length, SCHAR* spb, TEXT** switches) { gds__log("test_cmd called"); } #endif