/* * PROGRAM: JRD Access Method * MODULE: dba.epp * DESCRIPTION: Database analysis tool * * 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): ______________________________________. * * 2001.08.07 Sean Leyne - Code Cleanup, removed "#ifdef READONLY_DATABASE" * conditionals, as the engine now fully supports * readonly databases. * * 2002.10.29 Sean Leyne - Removed obsolete "Netware" port * */ #include "firebird.h" #include "fb_string.h" #include "../jrd/common.h" #include #include "../common/classes/alloc.h" #include #include #include #include "../jrd/ibsetjmp.h" #include "../jrd/jrd_time.h" #include "../jrd/ibase.h" #include "../jrd/ods.h" #include "../jrd/btn.h" #include "../jrd/svc.h" #include "../jrd/license.h" #include "../jrd/msg_encode.h" #include "../jrd/gdsassert.h" #ifndef SUPERSERVER #include "../utilities/gstat/ppg_proto.h" #endif #include "../utilities/gstat/dbaswi.h" #include "../jrd/gds_proto.h" #include "../jrd/isc_f_proto.h" #include "../jrd/thd.h" #include "../jrd/enc_proto.h" #ifdef SUPERSERVER #include "../utilities/common/cmd_util_proto.h" #include "../jrd/thd_proto.h" #endif #include "../common/utils_proto.h" #ifdef HAVE_UNISTD_H #include #endif #ifdef WIN_NT #include #include "../jrd/jrd_pwd.h" #endif using namespace Ods; /* For Netware the follow DB handle and isc_status is #defined to be a */ /* local variable on the stack in main. This is to avoid multiple */ /* threading problems with module level statics. */ DATABASE DB = STATIC "yachts.lnk"; #define DB db_handle #define isc_status status_vector const SSHORT BUCKETS = 5; //#define WINDOW_SIZE (1 << 17) struct dba_idx { dba_idx* idx_next; SSHORT idx_id; SSHORT idx_depth; SLONG idx_leaf_buckets; SLONG idx_total_duplicates; SLONG idx_max_duplicates; SLONG idx_nodes; SLONG idx_data_length; SLONG idx_fill_distribution[BUCKETS]; SCHAR idx_name[32]; }; struct dba_rel { dba_rel* rel_next; dba_idx* rel_indexes; SLONG rel_index_root; SLONG rel_pointer_page; SLONG rel_slots; SLONG rel_data_pages; ULONG rel_records; ULONG rel_record_space; ULONG rel_versions; ULONG rel_version_space; ULONG rel_max_versions; SLONG rel_fill_distribution[BUCKETS]; ULONG rel_total_space; SSHORT rel_id; SCHAR rel_name[32]; }; /* kidnapped from jrd/pio.h and abused */ struct dba_fil { dba_fil* fil_next; /* Next file in database */ ULONG fil_min_page; /* Minimum page number in file */ ULONG fil_max_page; /* Maximum page number in file */ USHORT fil_fudge; /* Fudge factor for page relocation */ #ifdef WIN_NT void *fil_desc; #else int fil_desc; #endif USHORT fil_length; /* Length of expanded file name */ SCHAR fil_string[1]; /* Expanded file name */ }; static SCHAR* alloc(size_t); static void analyze_data(dba_rel*, bool); static bool analyze_data_page(dba_rel*, const data_page*, bool); static ULONG analyze_fragments(const dba_rel*, const rhdf*); static ULONG analyze_versions(dba_rel*, const rhdf*); static void analyze_index(dba_rel*, dba_idx*); #if (defined WIN_NT) static void db_error(SLONG); #else static void db_error(int); #endif static dba_fil* db_open(const char*, USHORT); static const pag* db_read(SLONG); #ifdef SUPERSERVER static void db_close(int); #endif static void move(const SCHAR*, SCHAR*, SSHORT); static void print_distribution(const SCHAR*, const SLONG*); static void dba_error(USHORT, const TEXT*, const TEXT*, const TEXT*, const TEXT*, const TEXT*); static void dba_print(USHORT, const TEXT*, const TEXT*, const TEXT*, const TEXT*, const TEXT*); #ifndef INCLUDE_FB_BLK #include "../include/fb_blk.h" #endif #include "../jrd/db_alias.h" #include "../jrd/svc.h" #include "../jrd/svc_proto.h" #ifdef SUPERSERVER #include #if (defined WIN_NT) #include #endif #include "../jrd/jrd_pwd.h" #include "../utilities/gstat/ppg_proto.h" #define FPRINTF SVC_fprintf struct open_files { int desc; struct open_files *open_files_next; }; struct dba_mem { char *memory; struct dba_mem *mem_next; }; #endif #ifndef FPRINTF #define FPRINTF fprintf #endif /* threading declarations for thread data */ struct tdba { struct thdd tdba_thd_data; UCHAR *dba_env; dba_fil* files; dba_rel* relations; SSHORT page_size; SLONG page_number; pag* buffer1; pag* buffer2; pag* global_buffer; int exit_code; #ifdef SUPERSERVER Jrd::Service* sw_outfile; dba_mem *head_of_mem_list; open_files *head_of_files_list; Jrd::Service* dba_service_blk; #else FILE* sw_outfile; #endif ISC_STATUS *dba_status; ISC_STATUS_ARRAY dba_status_vector; }; #ifdef GET_THREAD_DATA #undef GET_THREAD_DATA #endif #ifdef SUPERSERVER #define GET_THREAD_DATA ((tdba*) THD_get_specific()) #define SET_THREAD_DATA { tddba = &thd_context; \ THD_put_specific ((THDD) tddba);\ tddba->tdba_thd_data.thdd_type = THDD_TYPE_TDBA; } #define RESTORE_THREAD_DATA THD_restore_specific() #else static struct tdba* gddba; #define GET_THREAD_DATA (gddba) #define SET_THREAD_DATA gddba = tddba = &thd_context; \ tddba->tdba_thd_data.thdd_type = THDD_TYPE_TDBA #define RESTORE_THREAD_DATA #endif void inline dba_exit(int code, tdba* tddba) { tddba->exit_code = code; // Throw this kind of exception, because gstat uses status vector (and stuff_exception) to // handle errors Firebird::status_exception::raise(); } const USHORT GSTAT_MSG_FAC = 21; #if defined (WIN95) static bool fAnsiCP = false; #define TRANSLATE_CP(a) if (!fAnsiCP) CharToOem(a, a) #else #define TRANSLATE_CP(a) #endif #ifdef SUPERSERVER int main_gstat(Jrd::Service* service) #else int CLIB_ROUTINE main(int argc, char** argv) #endif { /************************************** * * m a i n * ************************************** * * Functional description * Gather information from system relations to do analysis * of a database. * **************************************/ SCHAR temp[1024], file_name[1024]; bool sw_system = false; bool sw_data = false; bool sw_index = false; bool sw_version = false; bool sw_header = false; bool sw_log = false; bool sw_record = false; bool sw_relation = false; isc_db_handle db_handle = 0; UCHAR buf[256]; UCHAR pass_buff[128], user_buff[128], *password = pass_buff, *username = user_buff; struct tdba thd_context, *tddba; JMP_BUF env; #if defined (WIN95) && !defined (SUPERSERVER) BOOL fAnsiCP; #endif SET_THREAD_DATA; SVC_PUTSPECIFIC_DATA; memset(tddba, 0, sizeof(*tddba)); tddba->dba_env = (UCHAR *) env; #ifdef SUPERSERVER /* Reinitialize static variables for multi-threading */ int argc = service->svc_argc; char** argv = service->svc_argv; #endif ISC_STATUS* status_vector = NULL; try { #ifdef SUPERSERVER Jrd::Service* sw_outfile = tddba->sw_outfile = service; #else FILE* sw_outfile = tddba->sw_outfile = stdout; #endif #if defined (WIN95) && !defined (SUPERSERVER) fAnsiCP = false; #endif /* Perform some special handling when run as an Interbase service. The first switch can be "-svc" (lower case!) or it can be "-svc_re" followed by 3 file descriptors to use in re-directing stdin, stdout, and stderr. */ tddba->dba_status = tddba->dba_status_vector; status_vector = tddba->dba_status; bool called_as_service = false; if (argc > 1 && !strcmp(argv[1], "-svc")) { called_as_service = true; argv++; argc--; } #ifdef SUPERSERVER else if (!strcmp(argv[1], "-svc_thd")) { called_as_service = true; tddba->dba_service_blk = service; tddba->dba_status = service->svc_status; argv++; argc--; } #endif else if (argc > 4 && !strcmp(argv[1], "-svc_re")) { called_as_service = true; long redir_in = atol(argv[2]); long redir_out = atol(argv[3]); long redir_err = atol(argv[4]); #ifdef WIN_NT #if defined (WIN95) && !defined (SUPERSERVER) fAnsiCP = true; #endif redir_in = _open_osfhandle(redir_in, 0); redir_out = _open_osfhandle(redir_out, 0); redir_err = _open_osfhandle(redir_err, 0); #endif if (redir_in != 0) if (dup2((int) redir_in, 0)) close((int) redir_in); if (redir_out != 1) if (dup2((int) redir_out, 1)) close((int) redir_out); if (redir_err != 2) if (dup2((int) redir_err, 2)) close((int) redir_err); argv += 4; argc -= 4; } const char* name = NULL; MOVE_CLEAR(user_buff, sizeof(user_buff)); MOVE_CLEAR(pass_buff, sizeof(pass_buff)); const TEXT* const* const end = argv + argc; ++argv; while (argv < end) { const TEXT* str = *argv++; if (*str == '-') { if (!str[1]) str = "-*NONE*"; in_sw_tab_t* in_sw_tab; const TEXT* q; for (in_sw_tab = dba_in_sw_table; q = in_sw_tab->in_sw_name; in_sw_tab++) { TEXT c; for (const TEXT* p = str + 1; c = *p++;) { if (UPPER(c) != *q++) break; } if (!c) break; } in_sw_tab->in_sw_state = TRUE; switch (in_sw_tab->in_sw) { case 0: dba_print(20, str + 1, 0, 0, 0, 0); /* msg 20: unknown switch "%s" */ dba_print(21, 0, 0, 0, 0, 0); /* msg 21: Available switches: */ for (in_sw_tab = dba_in_sw_table; in_sw_tab->in_sw; in_sw_tab++) { if (in_sw_tab->in_sw_msg) dba_print(in_sw_tab->in_sw_msg, 0, 0, 0, 0, 0); } #ifdef SUPERSERVER CMD_UTIL_put_svc_status(tddba->dba_service_blk->svc_status, GSTAT_MSG_FAC, 1, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL); #endif dba_error(1, 0, 0, 0, 0, 0); /* msg 1: found unknown switch */ break; case IN_SW_DBA_USERNAME: if (argv < end) strcpy((char*) username, *argv++); break; case IN_SW_DBA_PASSWORD: if (argv < end) strcpy((char*) password, *argv++); break; case IN_SW_DBA_SYSTEM: sw_system = true; break; case IN_SW_DBA_DATA: sw_data = true; break; case IN_SW_DBA_INDEX: sw_index = true; break; case IN_SW_DBA_VERSION: sw_version = true; break; case IN_SW_DBA_HEADER: sw_header = true; break; case IN_SW_DBA_LOG: sw_log = true; break; case IN_SW_DBA_DATAIDX: sw_index = sw_data = true; break; case IN_SW_DBA_RECORD: sw_record = true; break; case IN_SW_DBA_RELATION: sw_relation = true; while (argv < end && **argv != '-') { if (strlen(*argv) > 31) { argv++; continue; } dba_rel* relation = (dba_rel*) alloc(sizeof(struct dba_rel)); strcpy(relation->rel_name, *argv++); fb_utils::fb_exact_name(relation->rel_name); relation->rel_id = -1; dba_rel** next = &tddba->relations; while (*next) { next = &(*next)->rel_next; } *next = relation; } break; } } // if (*str == '-') else { name = str; } } if (sw_version) dba_print(5, GDS_VERSION, 0, 0, 0, 0); /* msg 5: gstat version %s */ if (!name) { #ifdef SUPERSERVER CMD_UTIL_put_svc_status(tddba->dba_service_blk->svc_status, GSTAT_MSG_FAC, 2, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL); #endif dba_error(2, 0, 0, 0, 0, 0); /* msg 2: please retry, giving a database name */ } if (!sw_data && !sw_index) sw_data = sw_index = true; if (sw_record && !sw_data) sw_data = true; #if defined (WIN95) && !defined (SUPERSERVER) if (!fAnsiCP) { const ULONG ulConsoleCP = GetConsoleCP(); if (ulConsoleCP == GetACP()) fAnsiCP = true; else if (ulConsoleCP != GetOEMCP()) { FPRINTF(sw_outfile, "WARNING: The current codepage is not supported. Any use of any\n" " extended characters may result in incorrect file names.\n"); } } #endif /* Open database and go to work */ Firebird::PathName temp_buf = name; if (ResolveDatabaseAlias(temp_buf, temp_buf)) { name = temp_buf.c_str(); } dba_fil* current = db_open(name, strlen(name)); tddba->page_size = sizeof(temp); tddba->global_buffer = (pag*) temp; tddba->page_number = -1; const header_page* header = (const header_page*) db_read((SLONG) 0); #ifdef SUPERSERVER service->svc_started(); #endif /* ODS7 was not released as a shipping ODS and was replaced * by ODS 8 in version 4.0 of InterBase. There will not be any * customer databases which have an ODS of 7 */ if (header->hdr_ods_version > ODS_VERSION || (header->hdr_ods_version < ODS_VERSION8 && header->hdr_ods_version != ODS_VERSION6)) { #ifdef SUPERSERVER CMD_UTIL_put_svc_status(tddba->dba_service_blk->svc_status, GSTAT_MSG_FAC, 3, isc_arg_number, (void*)(IPTR)ODS_VERSION, isc_arg_number, (void*)(IPTR)header->hdr_ods_version, 0, NULL, 0, NULL, 0, NULL); #endif dba_error(3, (TEXT *)(IPTR) ODS_VERSION, (TEXT *)(IPTR) header->hdr_ods_version, 0, 0, 0); /* msg 3: Wrong ODS version, expected %d, encountered %d? */ } #if defined (WIN95) && !defined (SUPERSERVER) if (!fAnsiCP) AnsiToOem(name, file_name); else #endif strcpy(file_name, name); dba_print(6, file_name, 0, 0, 0, 0); /* msg 6: \nDatabase \"%s\"\n */ tddba->page_size = header->hdr_page_size; tddba->buffer1 = (pag*) alloc(tddba->page_size); tddba->buffer2 = (pag*) alloc(tddba->page_size); tddba->global_buffer = (pag*) alloc(tddba->page_size); tddba->page_number = -1; /* gather continuation files */ SLONG page = HEADER_PAGE; do { if (page != HEADER_PAGE) current = db_open(file_name, strlen(file_name)); do { header = (const header_page*) db_read((SLONG) page); if (current != tddba->files) current->fil_fudge = 1; /* ignore header page once read it */ *file_name = '\0'; const UCHAR* vp = header->hdr_data; for (const UCHAR* const vend = vp + header->hdr_page_size; vp < vend && *vp != HDR_end; vp += 2 + vp[1]) { if (*vp == HDR_file) { memcpy(file_name, vp + 2, vp[1]); *(file_name + vp[1]) = '\0'; #if defined (WIN95) && !defined (SUPERSERVER) if (!fAnsiCP) AnsiToOem(file_name, file_name); #endif } if (*vp == HDR_last_page) { memcpy(¤t->fil_max_page, vp + 2, sizeof(current->fil_max_page)); } } } while (page = header->hdr_next_page); page = current->fil_max_page + 1; /* first page of next file */ } while (*file_name); /* Print header page */ page = HEADER_PAGE; do { header = (const header_page*) db_read((SLONG) page); PPG_print_header(header, page, sw_outfile); page = header->hdr_next_page; } while (page); if (sw_header) dba_exit(FINI_OK, tddba); /* print continuation file sequence */ dba_print(7, 0, 0, 0, 0, 0); // msg 7: \n\nDatabase file sequence: for (current = tddba->files; current->fil_next; current = current->fil_next) { dba_print(8, current->fil_string, current->fil_next->fil_string, 0, 0, 0); //* msg 8: File %s continues as file %s } dba_print(9, current->fil_string, (TEXT*)((current == tddba->files) ? "only" : "last"), 0, 0, 0); // msg 9: File %s is the %s file\n /* print log page */ page = LOG_PAGE; do { const log_info_page* logp = (const log_info_page*) db_read((SLONG) page); PPG_print_log(logp, page, sw_outfile); page = logp->log_next_page; } while (page); if (sw_log) dba_exit(FINI_OK, tddba); /* Check to make sure that the user accessing the database is either * SYSDBA or owner of the database */ UCHAR dpb_string[256]; UCHAR* dpb = dpb_string; *dpb++ = isc_dpb_version1; *dpb++ = isc_dpb_gstat_attach; *dpb++ = 0; if (*username) { *dpb++ = isc_dpb_user_name; *dpb++ = strlen((char*) username); strcpy((char*) dpb, (char*) username); dpb += strlen((char*) username); } if (*password) { if (called_as_service) *dpb++ = isc_dpb_password_enc; else *dpb++ = isc_dpb_password; *dpb++ = strlen((char*) password); strcpy((char*) dpb, (char*) password); dpb += strlen((char*) password); } const SSHORT dpb_length = dpb - dpb_string; isc_attach_database(status_vector, 0, name, &DB, dpb_length, (char*) dpb_string); if (status_vector[1]) dba_exit(FINI_ERROR, tddba); if (sw_version) isc_version(&DB, NULL, NULL); isc_tr_handle transact1 = 0; START_TRANSACTION transact1 READ_ONLY; ON_ERROR dba_exit(FINI_ERROR, tddba); END_ERROR isc_req_handle request1 = 0; isc_req_handle request2 = 0; isc_req_handle request3 = 0; FOR(TRANSACTION_HANDLE transact1 REQUEST_HANDLE request1) X IN RDB$RELATIONS SORTED BY DESC X.RDB$RELATION_NAME if (!sw_system && X.RDB$SYSTEM_FLAG) { continue; } if (!X.RDB$VIEW_BLR.NULL || !X.RDB$EXTERNAL_FILE.NULL) { continue; } dba_rel* relation; if (sw_relation) { fb_utils::fb_exact_name(X.RDB$RELATION_NAME); for (relation = tddba->relations; relation; relation = relation->rel_next) { if (!(strcmp(relation->rel_name, X.RDB$RELATION_NAME))) { relation->rel_id = X.RDB$RELATION_ID; break; } } if (!relation) continue; } else { relation = (dba_rel*) alloc(sizeof(struct dba_rel)); relation->rel_next = tddba->relations; tddba->relations = relation; relation->rel_id = X.RDB$RELATION_ID; strcpy(relation->rel_name, X.RDB$RELATION_NAME); fb_utils::fb_exact_name(relation->rel_name); } FOR(TRANSACTION_HANDLE transact1 REQUEST_HANDLE request2) Y IN RDB$PAGES WITH Y.RDB$RELATION_ID EQ relation->rel_id AND Y.RDB$PAGE_SEQUENCE EQ 0 if (Y.RDB$PAGE_TYPE == pag_pointer) { relation->rel_pointer_page = Y.RDB$PAGE_NUMBER; } if (Y.RDB$PAGE_TYPE == pag_root) { relation->rel_index_root = Y.RDB$PAGE_NUMBER; } END_FOR; ON_ERROR dba_exit(FINI_ERROR, tddba); END_ERROR if (sw_index) FOR(TRANSACTION_HANDLE transact1 REQUEST_HANDLE request3) Y IN RDB$INDICES WITH Y.RDB$RELATION_NAME EQ relation->rel_name SORTED BY DESC Y.RDB$INDEX_NAME if (Y.RDB$INDEX_INACTIVE) continue; dba_idx* index = (dba_idx*) alloc(sizeof(struct dba_idx)); index->idx_next = relation->rel_indexes; relation->rel_indexes = index; strcpy(index->idx_name, Y.RDB$INDEX_NAME); fb_utils::fb_exact_name(index->idx_name); index->idx_id = Y.RDB$INDEX_ID - 1; END_FOR; ON_ERROR dba_exit(FINI_ERROR, tddba); END_ERROR END_FOR; ON_ERROR dba_exit(FINI_ERROR, tddba); END_ERROR if (request1) { isc_release_request(status_vector, &request1); } if (request2) { isc_release_request(status_vector, &request2); } if (request3) { isc_release_request(status_vector, &request3); } COMMIT transact1; ON_ERROR dba_exit(FINI_ERROR, tddba); END_ERROR // FINISH; error! FINISH ON_ERROR dba_exit(FINI_ERROR, tddba); END_ERROR dba_print(10, 0, 0, 0, 0, 0); // msg 10: \nAnalyzing database pages ...\n { // scope for MSVC6 for (dba_rel* relation = tddba->relations; relation; relation = relation->rel_next) { if (relation->rel_id == -1) { continue; } if (sw_data) { analyze_data(relation, sw_record); } for (dba_idx* index = relation->rel_indexes; index; index = index->idx_next) { analyze_index(relation, index); } } } // scope for MSVC6 /* Print results */ for (const dba_rel* relation = tddba->relations; relation; relation = relation->rel_next) { if (relation->rel_id == -1) { continue; } FPRINTF(sw_outfile, "%s (%d)\n", relation->rel_name, relation->rel_id); if (sw_data) { dba_print(11, (TEXT *)(IPTR) relation->rel_pointer_page, (TEXT *)(IPTR) relation->rel_index_root, 0, 0, 0); // msg 11: " Primary pointer page: %ld, Index root page: %ld" if (sw_record) { double average = (relation->rel_records) ? (double) relation->rel_record_space / relation->rel_records : 0.0; sprintf((char*) buf, "%.2f", average); FPRINTF(sw_outfile, " Average record length: %s, total records: %ld\n", buf, relation->rel_records); // dba_print(18, buf, relation->rel_records, 0, 0, 0); // msg 18: " Average record length: %s, total records: %ld average = (relation->rel_versions) ? (double) relation->rel_version_space / relation->rel_versions : 0.0; sprintf((char*) buf, "%.2f", average); FPRINTF(sw_outfile, " Average version length: %s, total versions: %ld, max versions: %ld\n", buf, relation->rel_versions, relation->rel_max_versions); // dba_print(19, buf, relation->rel_versions, // relation->rel_max_versions, 0, 0); // msg 19: " Average version length: %s, total versions: %ld, max versions: %ld } const double average = (relation->rel_data_pages) ? (double) relation->rel_total_space * 100 / ((double) relation->rel_data_pages * (tddba->page_size - DPG_SIZE)) : 0.0; sprintf((char*) buf, "%.0f%%", average); dba_print(12, (TEXT*)(IPTR) relation->rel_data_pages, (TEXT*)(IPTR) relation->rel_slots, (TEXT*) buf, 0, 0); /* msg 12: " Data pages: %ld, data page slots: %ld, average fill: %s */ dba_print(13, 0, 0, 0, 0, 0); /* msg 13: " Fill distribution:" */ print_distribution("\t", relation->rel_fill_distribution); } FPRINTF(sw_outfile, "\n"); for (const dba_idx* index = relation->rel_indexes; index; index = index->idx_next) { dba_print(14, index->idx_name, (TEXT *)(IPTR) index->idx_id, 0, 0, 0); // msg 14: " Index %s (%d)" dba_print(15, (TEXT *)(IPTR) index->idx_depth, (TEXT *)(IPTR) index->idx_leaf_buckets, (TEXT *)(IPTR) index->idx_nodes, 0, 0); // msg 15: \tDepth: %d, leaf buckets: %ld, nodes: %ld const double average = (index->idx_nodes) ? index->idx_data_length / index->idx_nodes : 0; sprintf((char*) buf, "%.2f", average); dba_print(16, (TEXT*) buf, (TEXT*)(IPTR) index->idx_total_duplicates, (TEXT*)(IPTR) index->idx_max_duplicates, 0, 0); // msg 16: \tAverage data length: %s, total dup: %ld, max dup: %ld" dba_print(17, 0, 0, 0, 0, 0); // msg 17: \tFill distribution: print_distribution("\t ", index->idx_fill_distribution); FPRINTF(sw_outfile, "\n"); } } dba_exit(FINI_OK, tddba); } // try catch (const std::exception& ex) { Firebird::stuff_exception(status_vector, ex); /* free mem */ if (status_vector[1]) { #ifdef SUPERSERVER ISC_STATUS* status = tddba->dba_service_blk->svc_status; if (status != status_vector) { int i = 0; while (*status && (++i < ISC_STATUS_LENGTH)) { status++; } for (int j = 0; status_vector[j] && (i < ISC_STATUS_LENGTH); j++, i++) { *status++ = status_vector[j]; } } #endif const ISC_STATUS* vector = status_vector; SCHAR s[1024]; if (isc_interprete_cpp(s, &vector)) { FPRINTF(tddba->sw_outfile, "%s\n", s); s[0] = '-'; while (isc_interprete_cpp(s + 1, &vector)) { FPRINTF(tddba->sw_outfile, "%s\n", s); } } } /* if there still exists a database handle, disconnect from the * server */ FINISH; #ifdef SUPERSERVER service->svc_started(); dba_mem* alloced = tddba->head_of_mem_list; while (alloced != 0) { delete[] alloced->memory; alloced = alloced->mem_next; } /* close files */ open_files* open_file = tddba->head_of_files_list; while (open_file) { db_close(open_file->desc); open_file = open_file->open_files_next; } /* free linked lists */ while (tddba->head_of_files_list != 0) { open_files* tmp1 = tddba->head_of_files_list; tddba->head_of_files_list = tddba->head_of_files_list->open_files_next; delete tmp1; } while (tddba->head_of_mem_list != 0) { dba_mem* tmp2 = tddba->head_of_mem_list; tddba->head_of_mem_list = tddba->head_of_mem_list->mem_next; delete tmp2; } /* Mark service thread as finished and cleanup memory being * used by service in case if client detached from the service */ SVC_finish(service, Jrd::SVC_finished); #endif int exit_code = tddba->exit_code; RESTORE_THREAD_DATA; #ifdef SUPERSERVER return exit_code; #else exit(exit_code); #endif } return 0; } static char* alloc(size_t size) { /************************************** * * a l l o c * ************************************** * * Functional description * Allocate and zero a piece of memory. * **************************************/ #ifdef SUPERSERVER tdba* tddba = GET_THREAD_DATA; char* const block = FB_NEW(*getDefaultMemoryPool()) SCHAR[size]; #else char* const block = (char*) gds__alloc(size); #endif if (!block) { /* NOMEM: return error */ dba_error(31, 0, 0, 0, 0, 0); } /* note: shouldn't we check for the NULL?? */ char* p = block; do { *p++ = 0; } while (--size); #ifdef SUPERSERVER dba_mem* mem_list = FB_NEW(*getDefaultMemoryPool()) dba_mem; if (!mem_list) { /* NOMEM: return error */ dba_error(31, 0, 0, 0, 0, 0); } mem_list->memory = block; mem_list->mem_next = 0; if (tddba->head_of_mem_list == 0) tddba->head_of_mem_list = mem_list; else { mem_list->mem_next = tddba->head_of_mem_list; tddba->head_of_mem_list = mem_list; } #endif return block; } static void analyze_data( dba_rel* relation, bool sw_record) { /************************************** * * a n a l y z e _ d a t a * ************************************** * * Functional description * Analyze data pages associated with relation. * **************************************/ tdba* tddba = GET_THREAD_DATA; pointer_page* ptr_page = (pointer_page*) tddba->buffer1; for (SLONG next_pp = relation->rel_pointer_page; next_pp; next_pp = ptr_page->ppg_next) { move((const SCHAR*) db_read(next_pp), (SCHAR*) ptr_page, tddba->page_size); const SLONG* ptr = ptr_page->ppg_page; for (const SLONG* const end = ptr + ptr_page->ppg_count; ptr < end; ptr++) { ++relation->rel_slots; if (*ptr) { ++relation->rel_data_pages; if (!analyze_data_page(relation, (const data_page*) db_read(*ptr), sw_record)) { dba_print(18, (TEXT*)(IPTR) *ptr, 0, 0, 0, 0); // msg 18: " Expected data on page %ld" */ } } } } } static bool analyze_data_page( dba_rel* relation, const data_page* page, bool sw_record) { /************************************** * * a n a l y z e _ d a t a _ p a g e * ************************************** * * Functional description * Analyze space utilization for data page. * **************************************/ tdba* tddba = GET_THREAD_DATA; if (page->pag_type != pag_data) return false; if (sw_record) { move((const SCHAR*) page, (SCHAR*) tddba->buffer2, tddba->page_size); page = (const data_page*) tddba->buffer2; } SSHORT space = page->dpg_count * sizeof(data_page::dpg_repeat); const data_page::dpg_repeat* tail = page->dpg_rpt; for (const data_page::dpg_repeat* const end = tail + page->dpg_count; tail < end; tail++) { if (tail->dpg_offset && tail->dpg_length) { space += tail->dpg_length; if (sw_record) { const rhdf* header = (const rhdf*) ((SCHAR *) page + tail->dpg_offset); if (!(header->rhdf_flags & (rhd_blob | rhd_chain | rhd_fragment))) { ++relation->rel_records; relation->rel_record_space += tail->dpg_length; if (header->rhdf_flags & rhd_incomplete) { relation->rel_record_space -= RHDF_SIZE; relation->rel_record_space += analyze_fragments(relation, header); } else relation->rel_record_space -= RHD_SIZE; if (header->rhdf_b_page) relation->rel_version_space += analyze_versions(relation, header); } } } } relation->rel_total_space += space; SSHORT bucket = (space * BUCKETS) / (tddba->page_size - DPG_SIZE); if (bucket == BUCKETS) --bucket; ++relation->rel_fill_distribution[bucket]; return true; } static ULONG analyze_fragments(const dba_rel* relation, const rhdf* header) { /************************************** * * a n a l y z e _ f r a g m e n t s * ************************************** * * Functional description * Analyze space used by a record's fragments. * **************************************/ tdba* tddba = GET_THREAD_DATA; ULONG space = 0; while (header->rhdf_flags & rhd_incomplete) { const SLONG f_page = header->rhdf_f_page; const USHORT f_line = header->rhdf_f_line; const data_page* page = (const data_page*) db_read(f_page); if (page->pag_type != pag_data || page->dpg_relation != relation->rel_id || page->dpg_count <= f_line) { break; } const data_page::dpg_repeat* index = &page->dpg_rpt[f_line]; if (!index->dpg_offset) break; space += index->dpg_length; space -= RHDF_SIZE; header = (const rhdf*) ((SCHAR *) page + index->dpg_offset); } return space; } static void analyze_index( dba_rel* relation, dba_idx* index) { /************************************** * * a n a l y z e _ i n d e x * ************************************** * * Functional description * **************************************/ tdba* tddba = GET_THREAD_DATA; const index_root_page* index_root = (const index_root_page*) db_read(relation->rel_index_root); SLONG page; if (index_root->irt_count <= index->idx_id || !(page = index_root->irt_rpt[index->idx_id].irt_root)) { return; } // CVC: The two const_cast's for bucket can go away if BTreeNode's functions // are overloaded for constness. They don't modify bucket and pointer's contents. const btree_page* bucket = (const btree_page*) db_read(page); index->idx_depth = bucket->btr_level + 1; UCHAR* pointer; IndexNode node; while (bucket->btr_level) { pointer = BTreeNode::getPointerFirstNode(const_cast(bucket)); BTreeNode::readNode(&node, pointer, bucket->pag_flags, false); bucket = (const btree_page*) db_read(node.pageNumber); } bool firstLeafNode = true; SLONG number; SLONG duplicates = 0; // AB: In fact length for KEY should be MAX_KEY (1/4 of used page-size) // This value should be kept equal with size declared in btr.h //UCHAR key[4096]; UCHAR* key = (UCHAR*) alloc(tddba->page_size / 4); USHORT key_length = 0; while (true) { ++index->idx_leaf_buckets; pointer = BTreeNode::getPointerFirstNode(const_cast(bucket)); const UCHAR* const firstNode = pointer; while (true) { pointer = BTreeNode::readNode(&node, pointer, bucket->pag_flags, true); if (node.isEndBucket || node.isEndLevel) { break; } ++index->idx_nodes; index->idx_data_length += node.length; USHORT l = node.length + node.prefix; bool dup; if (node.nodePointer == firstNode) { dup = BTreeNode::keyEquality(key_length, key, &node); } else { dup = (!node.length) && (l == key_length); } if (firstLeafNode) { dup = false; firstLeafNode = false; } if (dup) { ++index->idx_total_duplicates; ++duplicates; } else { if (duplicates > index->idx_max_duplicates) { index->idx_max_duplicates = duplicates; } duplicates = 0; } key_length = l; l = node.length; if (l) { UCHAR* p = key + node.prefix; const UCHAR* q = node.data; do { *p++ = *q++; } while (--l); } } if (duplicates > index->idx_max_duplicates) { index->idx_max_duplicates = duplicates; } const USHORT header = (USHORT)(firstNode - (UCHAR*) bucket); const USHORT space = bucket->btr_length - header; USHORT n = (space * BUCKETS) / (tddba->page_size - header); if (n == BUCKETS) { --n; } ++index->idx_fill_distribution[n]; if (node.isEndLevel) { break; } number = page; page = bucket->btr_sibling; bucket = (const btree_page*) db_read(page); if (bucket->pag_type != pag_index) { dba_print(19, (const TEXT*)(IPTR) page, (const TEXT*)(IPTR) number, 0, 0, 0); // mag 19: " Expected b-tree bucket on page %ld from %ld" break; } } } static ULONG analyze_versions( dba_rel* relation, const rhdf* header) { /************************************** * * a n a l y z e _ v e r s i o n s * ************************************** * * Functional description * Analyze space used by a record's back versions. * **************************************/ tdba* tddba = GET_THREAD_DATA; ULONG space = 0, versions = 0; SLONG b_page = header->rhdf_b_page; USHORT b_line = header->rhdf_b_line; while (b_page) { const data_page* page = (const data_page*) db_read(b_page); if (page->pag_type != pag_data || page->dpg_relation != relation->rel_id || page->dpg_count <= b_line) { break; } const data_page::dpg_repeat* index = &page->dpg_rpt[b_line]; if (!index->dpg_offset) break; space += index->dpg_length; ++relation->rel_versions; ++versions; header = (const rhdf*) ((SCHAR *) page + index->dpg_offset); b_page = header->rhdf_b_page; b_line = header->rhdf_b_line; if (header->rhdf_flags & rhd_incomplete) { space -= RHDF_SIZE; space += analyze_fragments(relation, header); } else space -= RHD_SIZE; } if (versions > relation->rel_max_versions) relation->rel_max_versions = versions; return space; } #ifdef WIN_NT #ifdef SUPERSERVER static void db_close( int file_desc) { /************************************** * * d b _ c l o s e ( W I N _ N T ) * ************************************** * * Functional description * Close an open file * **************************************/ CloseHandle((HANDLE) file_desc); } #endif static void db_error( SLONG status) { /************************************** * * d b _ e r r o r ( W I N _ N T ) * ************************************** * * Functional description * **************************************/ TEXT s[128]; tdba* tddba = GET_THREAD_DATA; tddba->page_number = -1; if (!FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, status, GetUserDefaultLangID(), s, sizeof(s), NULL)) sprintf(s, "unknown Windows NT error %ld", status); FPRINTF(tddba->sw_outfile, "%s\n", s); dba_exit(FINI_ERROR, tddba); } // CVC: This function was using cast to char* for the first param always // and the callers had to cast their char*'s to UCHAR*, too. Since the // real parameter is char* and always the usage is char*, I changed signature. static dba_fil* db_open(const char* file_name, USHORT file_length) { /************************************** * * d b _ o p e n ( W I N _ N T ) * ************************************** * * Functional description * Open a database file. * **************************************/ tdba* tddba = GET_THREAD_DATA; dba_fil* fil; if (tddba->files) { for (fil = tddba->files; fil->fil_next; fil = fil->fil_next); fil->fil_next = (dba_fil*) alloc(sizeof(dba_fil) + strlen(file_name) + 1); fil->fil_next->fil_min_page = fil->fil_max_page + 1; fil = fil->fil_next; } else { /* empty list */ fil = tddba->files = (dba_fil*) alloc(sizeof(dba_fil) + strlen(file_name) + 1); fil->fil_min_page = 0L; } fil->fil_next = NULL; strcpy(fil->fil_string, file_name); fil->fil_length = strlen(file_name); fil->fil_fudge = 0; fil->fil_max_page = 0L; fil->fil_desc = CreateFile( reinterpret_cast(file_name), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS, 0); if (fil->fil_desc == INVALID_HANDLE_VALUE) { #ifdef SUPERSERVER CMD_UTIL_put_svc_status(tddba->dba_service_blk->svc_status, GSTAT_MSG_FAC, 29, isc_arg_string, file_name, 0, NULL, 0, NULL, 0, NULL, 0, NULL); // msg 29: Can't open database file %s #endif db_error(GetLastError()); } #ifdef SUPERSERVER open_files* file_list = FB_NEW(*getDefaultMemoryPool()) open_files; if (!file_list) { /* NOMEM: return error */ dba_error(31, 0, 0, 0, 0, 0); } file_list->desc = reinterpret_cast(fil->fil_desc); file_list->open_files_next = 0; if (tddba->head_of_files_list == 0) tddba->head_of_files_list = file_list; else { file_list->open_files_next = tddba->head_of_files_list; tddba->head_of_files_list = file_list; } #endif return fil; } static const pag* db_read( SLONG page_number) { /************************************** * * d b _ r e a d ( W I N _ N T ) * ************************************** * * Functional description * Read a database page. * **************************************/ tdba* tddba = GET_THREAD_DATA; if (tddba->page_number == page_number) return tddba->global_buffer; tddba->page_number = page_number; dba_fil* fil; for (fil = tddba->files; page_number > (SLONG) fil->fil_max_page && fil->fil_next;) { fil = fil->fil_next; } page_number -= fil->fil_min_page - fil->fil_fudge; LARGE_INTEGER liOffset; liOffset.QuadPart = UInt32x32To64((DWORD) page_number, (DWORD) tddba->page_size); if (SetFilePointer (fil->fil_desc, (LONG) liOffset.LowPart, &liOffset.HighPart, FILE_BEGIN) == (DWORD) -1) { #ifdef SUPERSERVER CMD_UTIL_put_svc_status(tddba->dba_service_blk->svc_status, GSTAT_MSG_FAC, 30, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL); // msg 30: Can't read a database page #endif db_error(GetLastError()); } SLONG actual_length; if (!ReadFile( fil->fil_desc, tddba->global_buffer, tddba->page_size, reinterpret_cast(&actual_length), NULL)) { #ifdef SUPERSERVER CMD_UTIL_put_svc_status(tddba->dba_service_blk->svc_status, GSTAT_MSG_FAC, 30, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL); // msg 30: Can't read a database page #endif db_error(GetLastError()); } if (actual_length != tddba->page_size) { #ifdef SUPERSERVER CMD_UTIL_put_svc_status(tddba->dba_service_blk->svc_status, GSTAT_MSG_FAC, 4, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL); #endif dba_error(4, 0, 0, 0, 0, 0); // msg 4: Unexpected end of database file. } return tddba->global_buffer; } #endif // ifdef WIN_NT #ifndef WIN_NT #ifdef SUPERSERVER static void db_close( int file_desc) { /************************************** * * d b _ c l o s e * ************************************** * * Functional description * Close an open file * **************************************/ close(file_desc); } #endif static void db_error( int status) { /************************************** * * d b _ e r r o r * ************************************** * * Functional description * **************************************/ tdba* tddba = GET_THREAD_DATA; tddba->page_number = -1; /* FIXME: The strerror() function returns the appropriate description string, or an unknown error message if the error code is unknown. EKU: p cannot be NULL! */ #if 1 FPRINTF(tddba->sw_outfile, "%s\n", strerror(status)); #else /* EKU: Old code */ #ifndef VMS FPRINTF(tddba->sw_outfile, "%s\n", strerror(status)); #else const char* p; if ((p = strerror(status)) || (p = strerror(EVMSERR, status))) FPRINTF(tddba->sw_outfile, "%s\n", p); else FPRINTF(tddba->sw_outfile, "uninterpreted code %x\n", status); #endif #endif dba_exit(FINI_ERROR, tddba); } // CVC: This function was using cast to char* for the first param always // and the callers had to cast their char*'s to UCHAR*, too. Since the // real parameter is char* and always the usage is char*, I changed signature. static dba_fil* db_open(const char* file_name, USHORT file_length) { /************************************** * * d b _ o p e n * ************************************** * * Functional description * Open a database file. * Put the file on an ordered list. * **************************************/ tdba* tddba = GET_THREAD_DATA; dba_fil* fil; if (tddba->files) { for (fil = tddba->files; fil->fil_next; fil = fil->fil_next); fil->fil_next = (dba_fil*) alloc(sizeof(dba_fil) + strlen(file_name) + 1); fil->fil_next->fil_min_page = fil->fil_max_page + 1; fil = fil->fil_next; } else { /* empty list */ fil = tddba->files = (dba_fil*) alloc(sizeof(dba_fil) + strlen(file_name) + 1); fil->fil_min_page = 0L; } fil->fil_next = NULL; strcpy(fil->fil_string, file_name); fil->fil_length = strlen(file_name); fil->fil_fudge = 0; fil->fil_max_page = 0L; if ((fil->fil_desc = open(file_name, O_RDONLY)) == -1) { #ifdef SUPERSERVER CMD_UTIL_put_svc_status(tddba->dba_service_blk->svc_status, GSTAT_MSG_FAC, 29, isc_arg_string, file_name, 0, NULL, 0, NULL, 0, NULL, 0, NULL); // msg 29: Can't open database file %s #endif db_error(errno); } #ifdef SUPERSERVER open_files* file_list = FB_NEW(*getDefaultMemoryPool()) open_files; if (!file_list) { /* NOMEM: return error */ dba_error(31, 0, 0, 0, 0, 0); } file_list->desc = fil->fil_desc; file_list->open_files_next = 0; if (tddba->head_of_files_list == 0) tddba->head_of_files_list = file_list; else { file_list->open_files_next = tddba->head_of_files_list; tddba->head_of_files_list = file_list; } #endif return fil; } static const pag* db_read( SLONG page_number) { /************************************** * * d b _ r e a d * ************************************** * * Functional description * Read a database page. * **************************************/ tdba* tddba = GET_THREAD_DATA; if (tddba->page_number == page_number) return tddba->global_buffer; tddba->page_number = page_number; dba_fil* fil; for (fil = tddba->files; page_number > (SLONG) fil->fil_max_page && fil->fil_next;) { fil = fil->fil_next; } page_number -= fil->fil_min_page - fil->fil_fudge; const UINT64 offset = ((UINT64)page_number) * ((UINT64)tddba->page_size); if (lseek (fil->fil_desc, offset, 0) == -1) { #ifdef SUPERSERVER CMD_UTIL_put_svc_status(tddba->dba_service_blk->svc_status, GSTAT_MSG_FAC, 30, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL); // msg 30: Can't read a database page #endif db_error(errno); } SSHORT length = tddba->page_size; for (SCHAR* p = (SCHAR *) tddba->global_buffer; length > 0;) { const SSHORT l = read(fil->fil_desc, p, length); if (l < 0) { #ifdef SUPERSERVER CMD_UTIL_put_svc_status(tddba->dba_service_blk->svc_status, GSTAT_MSG_FAC, 30, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL); // msg 30: Can't read a database page #endif db_error(errno); } if (!l) { #ifdef SUPERSERVER CMD_UTIL_put_svc_status(tddba->dba_service_blk->svc_status, GSTAT_MSG_FAC, 4, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL); #endif dba_error(4, 0, 0, 0, 0, 0); // msg 4: Unexpected end of database file. } p += l; length -= l; } return tddba->global_buffer; } #endif static void dba_error( USHORT errcode, const TEXT* arg1, const TEXT* arg2, const TEXT* arg3, const TEXT* arg4, const TEXT* arg5) { /************************************** * * d b a _ e r r o r * ************************************** * * Functional description * Format and print an error message, then punt. * **************************************/ tdba* tddba = GET_THREAD_DATA; tddba->page_number = -1; dba_print(errcode, arg1, arg2, arg3, arg4, arg5); dba_exit(FINI_ERROR, tddba); } static void dba_print( USHORT number, const TEXT* arg1, const TEXT* arg2, const TEXT* arg3, const TEXT* arg4, const TEXT* arg5) { /************************************** * * d b a _ p r i n t * ************************************** * * Functional description * Retrieve a message from the error file, format it, and print it. * **************************************/ TEXT buffer[256]; tdba* tddba = GET_THREAD_DATA; gds__msg_format(NULL, GSTAT_MSG_FAC, number, sizeof(buffer), buffer, arg1, arg2, arg3, arg4, arg5); TRANSLATE_CP(buffer); FPRINTF(tddba->sw_outfile, "%s\n", buffer); } static void move(const SCHAR* from, SCHAR* to, SSHORT length) { /************************************** * * m o v e * ************************************** * * Functional description * Move some stuff. * **************************************/ memcpy(to, from, (int) length); } static void print_distribution(const SCHAR* prefix, const SLONG* vector) { /************************************** * * p r i n t _ d i s t r i b u t i o n * ************************************** * * Functional description * Print distribution as percentages. * **************************************/ tdba* tddba = GET_THREAD_DATA; for (SSHORT n = 0; n < BUCKETS; n++) { FPRINTF(tddba->sw_outfile, "%s%2d - %2d%% = %ld\n", prefix, n * 100 / BUCKETS, (n + 1) * 100 / BUCKETS - 1, vector[n]); } }