/* * PROGRAM: JRD Access Method * MODULE: dba_full.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): ______________________________________. * * 2002.10.29 Sean Leyne - Removed obsolete "Netware" port * */ #include "../jrd/common.h" #include "firebird.h" #include "../jrd/ib_stdio.h" #include #include #include "../jrd/ibsetjmp.h" #include "../jrd/jrd_time.h" #include "../jrd/y_ref.h" #include "../jrd/ibase.h" #include "../jrd/ods.h" #include "../jrd/license.h" #include "../utilities/gstat/ppg_proto.h" #include "../jrd/gds_proto.h" #include "../jrd/isc_f_proto.h" #include "../jrd/thd.h" #include "../jrd/enc_proto.h" #include "../include/fb_exception.h" #ifdef WIN_NT #include #include "../jrd/jrd_pwd.h" #endif #define LOCKSMITH_USER "SYSDBA" #define LOCKSMITH_PASSWORD "masterkey" /* 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 #define ALLOC(size) alloc ((SLONG) size); #define BUCKETS 5 #define WINDOW_SIZE (1 << 17) struct dba_rel { dba_rel* rel_next; struct idx* rel_indexes; // forward decl SLONG rel_index_root; SLONG rel_pointer_page; SLONG rel_slots; SLONG rel_data_pages; SLONG rel_fill_distribution[BUCKETS]; ULONG rel_total_space; SSHORT rel_id; SCHAR rel_name[32]; }; struct idx { 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]; }; /* 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 char* alloc(SLONG); static void analyze_data(dba_rel*); static bool analyze_data_page(dba_rel*, const data_page*); static void analyze_index(dba_rel*, 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); static bool key_equality(SSHORT, const SCHAR*, const btn*); static void move(const SCHAR*, SCHAR*, SSHORT); static void print_distribution(const SCHAR*, const SLONG*); static void print_header(const header_page*); static void truncate_name(SCHAR*); static const TEXT help_text[] = "Available switches:\n\ -a analyze data and index pages\n\ -d analyze data pages\n\ -i analyze index leaf pages\n\ -s analyze system relations\n\ -z display version number\n"; #ifndef FPRINTF #define FPRINTF ib_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; PAG buffer1; PAG global_buffer; int exit_code; IB_FILE* sw_outfile; }; #ifdef GET_THREAD_DATA #undef GET_THREAD_DATA #endif static 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 void inline dba_full_exit(int code, tdba* tddba) { tddba->exit_code = code; throw std::exception(); } int CLIB_ROUTINE main( int argc, char** argv) { /************************************** * * m a i n * ************************************** * * Functional description * Gather information from system relations to do analysis * of a database. * **************************************/ isc_db_handle db_handle = NULL; struct tdba thd_context, *tddba; JMP_BUF env; ISC_STATUS_ARRAY status_vector; SET_THREAD_DATA; memset(tddba, 0, sizeof(*tddba)); memset(&status_vector, 0, sizeof(status_vector)); tddba->dba_env = (UCHAR*) env; if (SETJMP(env)) { if (status_vector[1]) { SCHAR s[1024]; const ISC_STATUS* vector = status_vector; 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); } } const int exit_code = tddba->exit_code; RESTORE_THREAD_DATA; exit(exit_code); } IB_FILE* sw_outfile = tddba->sw_outfile = ib_stderr; #if defined (WIN95) bool 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 ib_stdin, ib_stdout, and ib_stderr. */ if (argc > 1 && !strcmp(argv[1], "-svc")) { argv++; argc--; } else if (argc > 4 && !strcmp(argv[1], "-svc_re")) { long redir_in = atol(argv[2]); long redir_out = atol(argv[3]); long redir_err = atol(argv[4]); #ifdef WIN_NT #if defined (WIN95) 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; bool sw_system; bool sw_data; bool sw_index; bool sw_version; bool sw_header; bool sw_log; sw_log = sw_system = sw_data = sw_index = sw_version = sw_header = false; const char* const* const end = argv + argc; for (++argv; argv < end;) { const char* p = *argv++; if (*p == '-') switch (UPPER(p[1])) { case 'S': sw_system = true; break; case 'D': sw_data = true; break; case 'I': sw_index = true; break; case 'A': sw_index = sw_data = true; break; case 'Z': sw_version = true; break; case 'H': sw_header = true; break; case 'L': sw_log = true; break; default: FPRINTF(sw_outfile, "%s\n", help_text); dba_full_exit(FINI_ERROR, tddba); } else name = p; } if (!name) { FPRINTF(sw_outfile, "please retry, giving a database name\n"); dba_full_exit(FINI_ERROR, tddba); } if (!sw_data && !sw_index) sw_data = sw_index = true; #if defined (WIN95) if (!fAnsiCP) { ULONG ulConsoleCP; 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 if (sw_version) FPRINTF(sw_outfile, "gstat version %s\n", GDS_VERSION); /* Open database and go to work */ dba_fil* current = db_open(name, strlen(name)); char temp[1024]; tddba->page_size = sizeof(temp); tddba->global_buffer = (PAG) temp; const header_page* header = (const header_page*) db_read((SLONG) 0); if (header->hdr_ods_version != ODS_VERSION && header->hdr_ods_version != ODS_VERSION6) { FPRINTF(sw_outfile, "Wrong ODS version, expected %d, encountered %d?\n", ODS_VERSION, header->hdr_ods_version); dba_full_exit(FINI_ERROR, tddba); } char file_name[1024]; #if defined (WIN95) if (!fAnsiCP) AnsiToOem(name, file_name); else #endif strcpy(file_name, name); FPRINTF(sw_outfile, "\nDatabase \"%s\"\n\n", file_name); tddba->page_size = header->hdr_page_size; tddba->buffer1 = (PAG) ALLOC(tddba->page_size); tddba->global_buffer = (PAG) ALLOC(tddba->page_size); /* gather continuation files */ SLONG page = HEADER_PAGE; do { if (page != HEADER_PAGE) current = db_open(file_name, strlen(file_name)); do { header = (const hrd*) 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) 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); } while (page = header->hdr_next_page); if (sw_header) dba_full_exit(FINI_OK, tddba); /* print continuation file sequence */ FPRINTF(sw_outfile, "\n\nDatabase file sequence:\n"); for (current = tddba->files; current->fil_next; current = current->fil_next) { FPRINTF(sw_outfile, "File %s continues as file %s\n", current->fil_string, current->fil_next->fil_string); } FPRINTF(sw_outfile, "File %s is the %s file\n\n", current->fil_string, (current == tddba->files) ? "only" : "last"); /* 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_full_exit(FINI_OK, tddba); #if (defined WIN_NT) UCHAR dpb_string[100]; UCHAR* dpb = dpb_string; *dpb++ = isc_dpb_version1; *dpb++ = isc_dpb_user_name; *dpb++ = strlen(LOCKSMITH_USER); const TEXT* q = LOCKSMITH_USER; while (*q) *dpb++ = *q++; *dpb++ = isc_dpb_password_enc; char password_enc[33]; strcpy(password_enc, (char *) ENC_crypt(LOCKSMITH_PASSWORD, PASSWORD_SALT)); q = password_enc + 2; *dpb++ = strlen(q); while (*q) *dpb++ = *q++; 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_full_exit(FINI_ERROR, tddba); #else READY name AS DB; ON_ERROR dba_full_exit(FINI_ERROR, tddba); END_ERROR #endif if (sw_version) isc_version(&DB, NULL, NULL); isc_tr_handle transact1 = 0; START_TRANSACTION transact1 READ_ONLY; ON_ERROR dba_full_exit(FINI_ERROR, tddba); END_ERROR isc_req_handle request1 = NULL; isc_req_handle request2 = NULL; isc_req_handle request3 = NULL; 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* arelation = (dba_rel*) ALLOC(sizeof(struct dba_rel)); arelation->rel_next = tddba->relations; tddba->relations = arelation; arelation->rel_id = X.RDB$RELATION_ID; strcpy(arelation->rel_name, X.RDB$RELATION_NAME); truncate_name(arelation->rel_name); FOR(TRANSACTION_HANDLE transact1 REQUEST_HANDLE request2) Y IN RDB$PAGES WITH Y.RDB$RELATION_ID EQ arelation->rel_id AND Y.RDB$PAGE_SEQUENCE EQ 0 if (Y.RDB$PAGE_TYPE == pag_pointer) arelation->rel_pointer_page = Y.RDB$PAGE_NUMBER; if (Y.RDB$PAGE_TYPE == pag_root) arelation->rel_index_root = Y.RDB$PAGE_NUMBER; END_FOR; ON_ERROR dba_full_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 arelation->rel_name SORTED BY DESC Y.RDB$INDEX_NAME if (Y.RDB$INDEX_INACTIVE) continue; idx* aindex = (idx*) ALLOC(sizeof(idx)); aindex->idx_next = arelation->rel_indexes; arelation->rel_indexes = aindex; strcpy(aindex->idx_name, Y.RDB$INDEX_NAME); truncate_name(aindex->idx_name); aindex->idx_id = Y.RDB$INDEX_ID - 1; END_FOR; ON_ERROR dba_full_exit(FINI_ERROR, tddba); END_ERROR END_FOR; ON_ERROR dba_full_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_full_exit(FINI_ERROR, tddba); END_ERROR FINISH ON_ERROR dba_full_exit(FINI_ERROR, tddba); END_ERROR FPRINTF(sw_outfile, "\nAnalyzing database pages ...\n\n"); dba_rel* relation; for (relation = tddba->relations; relation; relation = relation->rel_next) { if (sw_data) analyze_data(relation); for (idx* index = relation->rel_indexes; index; index = index->idx_next) analyze_index(relation, index); } /* Print results */ for (relation = tddba->relations; relation; relation = relation->rel_next) { FPRINTF(sw_outfile, "%s (%d)\n", relation->rel_name, relation->rel_id); if (sw_data) { FPRINTF(sw_outfile, " Primary pointer page: %ld, Index root page: %ld\n", relation->rel_pointer_page, relation->rel_index_root); const double average = (relation->rel_data_pages) ? (double) relation-> rel_total_space * 100 / ((double) relation->rel_data_pages * (tddba->page_size - DPG_SIZE)) : 0; FPRINTF(sw_outfile, " Data pages: %ld, data page slots: %ld, average fill: %.0f%%\n", relation->rel_data_pages, relation->rel_slots, average); FPRINTF(sw_outfile, " Fill distribution:\n"); print_distribution("\t", relation->rel_fill_distribution); } FPRINTF(sw_outfile, "\n"); for (idx* index = relation->rel_indexes; index; index = index->idx_next) { FPRINTF(sw_outfile, " Index %s (%d)\n", index->idx_name, index->idx_id); FPRINTF(sw_outfile, "\tDepth: %d, leaf buckets: %ld, nodes: %ld\n", index->idx_depth, index->idx_leaf_buckets, index->idx_nodes); const double average = (index->idx_nodes) ? index->idx_data_length / index->idx_nodes : 0; FPRINTF(sw_outfile, "\tAverage data length: %.2f, total dup: %ld, max dup: %ld\n", average, index->idx_total_duplicates, index->idx_max_duplicates); FPRINTF(sw_outfile, "\tFill distribution:\n"); print_distribution("\t ", index->idx_fill_distribution); FPRINTF(sw_outfile, "\n"); } } dba_full_exit(FINI_OK, tddba); return 0; } static char* alloc( SLONG size) { /************************************** * * a l l o c * ************************************** * * Functional description * Allocate and zero a piece of memory. * **************************************/ char* const block = (char*) gds__alloc(size); if (!block) { /* NOMEM: return error */ dba_error(31, 0, 0, 0, 0, 0); } char* p = block; do { *p++ = 0; } while (--size); return block; } static void analyze_data( dba_rel* relation) { /************************************** * * 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))) FPRINTF(tddba->sw_outfile, " Expected data on page %ld\n", *ptr); } } } } static bool analyze_data_page( dba_rel* relation, const data_page* page) { /************************************** * * 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; 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; } 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 void analyze_index( dba_rel* relation, idx* index) { /************************************** * * a n a l y z e _ i n d e x * ************************************** * * Functional description * **************************************/ SSHORT n, space, duplicates, key_length, l, page_count; UCHAR key[256], *p, *q; SLONG number, page, prior_page, node_count; tdba* tddba = GET_THREAD_DATA; index_root_page* index_root = (const index_root_page*) db_read(relation->rel_index_root); if (index_root->irt_count <= index->idx_id || !(page = index_root->irt_rpt[index->idx_id].irt_root)) { return; } const btree_page* bucket = (const btree_page*) db_read(page); if (bucket->btr_length > tddba->page_size) { FPRINTF(tddba->sw_outfile, "\n***ERROR: page size is too large: %ld\n", bucket->btr_length); return; } index->idx_depth = bucket->btr_level + 1; /*** go down through the levels, printing all pages ***/ while (bucket->btr_level) { FPRINTF(tddba->sw_outfile, "\nlevel %ld:\n", bucket->btr_level); SLONG right_sibling = page; move((SCHAR*) bucket->btr_nodes[0].btn_number, (SCHAR*) &page, sizeof(page)); node_count = 0; prior_page = 0; for (page_count = 0; right_sibling; right_sibling = bucket->btr_sibling, page_count++) { FPRINTF(tddba->sw_outfile, "page: %ld\n", right_sibling); bucket = (const btree_page*) db_read(right_sibling); if (bucket->btr_left_sibling != prior_page) { FPRINTF(tddba->sw_outfile, "\n***ERROR: left sibling is %ld\n", bucket->btr_left_sibling); return; } if (bucket->btr_length > tddba->page_size) { FPRINTF(tddba->sw_outfile, "\n***ERROR: page size is too large: %ld\n", bucket->btr_length); return; } prior_page = right_sibling; if (bucket->pag_flags & btr_not_propogated) { FPRINTF(tddba->sw_outfile, "\n***ERROR: non-leaf bucket marked not propogated\n"); return; } /* count the nodes on page as well */ SLONG node_page_count = 0; FPRINTF(tddba->sw_outfile, "\nnodes:"); for (const btn* node = bucket->btr_nodes; true; node = (btn*) (node->btn_data + node->btn_length)) { move((SCHAR*) node->btn_number, (SCHAR*) &number, sizeof(number)); if (!(node_page_count % 4)) FPRINTF(tddba->sw_outfile, "\n"); FPRINTF(tddba->sw_outfile, "[%ld:%d,%d,", number, node->btn_prefix, node->btn_length); /* print out the prefix */ if (l = node->btn_prefix) { p = key; FPRINTF(tddba->sw_outfile, "("); do { FPRINTF(tddba->sw_outfile, "\'%d\'", *p); p++; } while (--l); FPRINTF(tddba->sw_outfile, ")"); } if (l = node->btn_length) { p = key + node->btn_prefix; q = node->btn_data; do { FPRINTF(tddba->sw_outfile, "\'%d\'", *q); *p++ = *q++; } while (--l); } FPRINTF(tddba->sw_outfile, "] "); if (number < 0) break; if (!node->btn_length && !node->btn_prefix && (bucket->btr_left_sibling || node > bucket->btr_nodes)) { FPRINTF(tddba->sw_outfile, "\n***ERROR: node prefix and length 0\n"); return; } node_page_count++; } if (!node_page_count) { FPRINTF(tddba->sw_outfile, "\n***ERROR: page empty\n"); return; } FPRINTF(tddba->sw_outfile, "\nnodes on page: %d\n", node_page_count); node_count += node_page_count; } FPRINTF(tddba->sw_outfile, "\npage count: %d\n", page_count); FPRINTF(tddba->sw_outfile, "node count: %d\n", node_count); bucket = (const btree_page*) db_read(page); if (bucket->btr_length > tddba->page_size) { FPRINTF(tddba->sw_outfile, "\n***ERROR: page size is too large: %ld\n", bucket->btr_length); return; } } duplicates = 0; key_length = 0; FPRINTF(tddba->sw_outfile, "\nlevel 0:"); for (;;) { /*** print the pages involved at the leaf ***/ if (bucket->pag_flags & btr_not_propogated) FPRINTF(tddba->sw_outfile, "\n\n(page %ld):", page); else FPRINTF(tddba->sw_outfile, "\n\npage %ld:", page); ++index->idx_leaf_buckets; SLONG node_page_count = 0; for (node = bucket->btr_nodes;; node = (BTN) (node->btn_data + node->btn_length)) { move((SCHAR*) node->btn_number, (SCHAR*) &number, sizeof(number)); if (!(node_page_count % 4)) FPRINTF(tddba->sw_outfile, "\n"); FPRINTF(tddba->sw_outfile, "[%ld:%d,%d,", number, node->btn_prefix, node->btn_length); /* print out the prefix */ if (l = node->btn_prefix) { p = key; FPRINTF(tddba->sw_outfile, "("); do { FPRINTF(tddba->sw_outfile, "\'%d\'", *p++); } while (--l); FPRINTF(tddba->sw_outfile, ")"); } /* print out the node data */ if (l = node->btn_length) { q = node->btn_data; do { FPRINTF(tddba->sw_outfile, "\'%d\'", *q++); } while (--l); } FPRINTF(tddba->sw_outfile, "] "); if (number < 0) break; ++index->idx_nodes; index->idx_data_length += node->btn_length; l = node->btn_length + node->btn_prefix; bool dup; if (node == bucket->btr_nodes) dup = key_equality(key_length, (SCHAR*) key, node); else dup = !node->btn_length && l == key_length; if (dup) { ++index->idx_total_duplicates; ++duplicates; } else { if (duplicates > index->idx_max_duplicates) index->idx_max_duplicates = duplicates; duplicates = 0; } /* save the key */ key_length = l; if (l = node->btn_length) { p = key + node->btn_prefix; q = node->btn_data; do { *p++ = *q++; } while (--l); } node_page_count++; } FPRINTF(tddba->sw_outfile, "\nnode count: %ld", node_page_count); if (duplicates > index->idx_max_duplicates) index->idx_max_duplicates = duplicates; space = bucket->btr_length - OFFSETA(btree_page*, btr_nodes); n = (space * BUCKETS) / (tddba->page_size - OFFSETA(btree_page*, btr_nodes)); if (n == BUCKETS) --n; ++index->idx_fill_distribution[n]; if (number == END_LEVEL) break; number = page; page = bucket->btr_sibling; bucket = (const btree_page*) db_read(page); if (bucket->pag_type != pag_index) { FPRINTF(tddba->sw_outfile, "\n***ERROR: Expected b-tree bucket on page %ld from %ld\n", page, number); break; } if (bucket->btr_length > tddba->page_size) { FPRINTF(tddba->sw_outfile, "\n***ERROR: page size is too large: %ld\n", bucket->btr_length); return; } if (bucket->btr_left_sibling != number) { FPRINTF(tddba->sw_outfile, "\n***ERROR: left sibling is %ld\n", bucket->btr_left_sibling); return; } } FPRINTF(tddba->sw_outfile, "\n"); } #ifdef WIN_NT 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; 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_full_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. * **************************************/ dba_fil* fil; tdba* tddba = GET_THREAD_DATA; 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 = CreateFile(file_name, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS, 0)) == INVALID_HANDLE_VALUE) db_error(GetLastError()); 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; dba_fil* fil; for (fil = tddba->files; page_number > 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) == -1) { db_error(GetLastError()); } SLONG actual_length; if (!ReadFile(fil->fil_desc, tddba->global_buffer, tddba->page_size, reinterpret_cast(&actual_length), NULL)) { db_error(GetLastError()); } if (actual_length != tddba->page_size) { FPRINTF(tddba->sw_outfile, "Unexpected end of database file.\n"); dba_full_exit(FINI_ERROR, tddba); } return tddba->global_buffer; } #else // now, on non WinNT static void db_error( int status) { /************************************** * * d b _ e r r o r * ************************************** * * Functional description * **************************************/ tdba* tddba = GET_THREAD_DATA; /* 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 #ifndef VMS FPRINTF(tddba->sw_outfile, "%s\n", strerror(status)); #else const SCHAR* 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_full_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. * **************************************/ dba_fil* fil; tdba* tddba = GET_THREAD_DATA; 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, 2)) == -1) db_error(errno); 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; dba_fil* fil; for (fil = tddba->files; page_number > 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) 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) db_error(errno); if (!l) { FPRINTF(tddba->sw_outfile, "Unexpected end of database file.\n"); dba_full_exit(FINI_ERROR, tddba); } p += l; length -= l; } return tddba->global_buffer; } #endif static bool key_equality( SSHORT length, const SCHAR* key, const btn* node) { /************************************** * * k e y _ e q u a l i t y * ************************************** * * Functional description * Check a B-tree node against a key for equality. * **************************************/ if (length != node->btn_length + node->btn_prefix) return false; SSHORT l = node->btn_length; if (!l) return true; const SCHAR* p = (SCHAR *) node->btn_data; key += node->btn_prefix; do { if (*p++ != *key++) return false; } while (--l); return true; } static void move( const SCHAR* from, SCHAR* to, SSHORT length) { /************************************** * * m o v e * ************************************** * * Functional description * Move some stuff. * **************************************/ do { *to++ = *from++; } while (--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]); } } static void print_header( const header_page* header) { /************************************** * * p r i n t _ h e a d e r * ************************************** * * Functional description * Print database header page. * **************************************/ tdba* tddba = GET_THREAD_DATA; IB_FILE* sw_outfile = tddba->sw_outfile; FPRINTF(sw_outfile, "Database header page information:\n"); FPRINTF(sw_outfile, " Page size\t\t%d\n", header->hdr_page_size); FPRINTF(sw_outfile, " ODS version\t\t%d.%d\n", header->hdr_ods_version, header->hdr_ods_minor); FPRINTF(sw_outfile, " Oldest transaction\t%ld\n", header->hdr_oldest_transaction); FPRINTF(sw_outfile, " Oldest active\t\t%ld\n", header->hdr_oldest_active); FPRINTF(sw_outfile, " Oldest snapshot\t\t%ld\n", header->hdr_oldest_snapshot); FPRINTF(sw_outfile, " Next transaction\t%ld\n", header->hdr_next_transaction); /* FPRINTF (sw_outfile, " Sequence number %d\n", header->hdr_sequence); FPRINTF (sw_outfile, " Creation date \n", header->hdr_creation_date); */ FPRINTF(sw_outfile, " Next attachment ID\t%ld\n", header->hdr_attachment_id); FPRINTF(sw_outfile, " Implementation ID\t%ld\n", header->hdr_implementation); FPRINTF(sw_outfile, " Shadow count\t%ld\n", header->hdr_shadow_count); tm time; isc_decode_date((ISC_QUAD*)header->hdr_creation_date, &time); FPRINTF(sw_outfile, " Creation date\t%s %d, %d %d:%02d:%02d\n", FB_SHORT_MONTHS[time.tm_mon], time.tm_mday, time.tm_year + 1900, time.tm_hour, time.tm_min, time.tm_sec); const SSHORT flags = header->hdr_flags; if (flags) { FPRINTF(sw_outfile, " Attributes\t\t"); if (flags & hdr_force_write) { FPRINTF(sw_outfile, "force write"); if (flags & hdr_no_reserve) FPRINTF(sw_outfile, ", "); } if (flags & hdr_no_reserve) FPRINTF(sw_outfile, "no reserve"); FPRINTF(sw_outfile, "\n"); } FPRINTF(sw_outfile, "\n Variable header data:\n"); const UCHAR* p = header->hdr_data; for (const UCHAR* const end = p + header->hdr_page_size; p < end && *p != HDR_end; p += 2 + p[1]) { SLONG number; switch (*p) { case HDR_root_file_name: FPRINTF(sw_outfile, "\tRoot file name: %*s\n", p[1], p + 2); break; case HDR_journal_server: FPRINTF(sw_outfile, "\tJournal server: %*s\n", p[1], p + 2); break; case HDR_file: FPRINTF(sw_outfile, "\tContinuation file: %*s\n", p[1], p + 2); break; case HDR_last_page: move((SCHAR*) (p + 2), (SCHAR*) &number, sizeof(number)); FPRINTF(sw_outfile, "\tLast logical page: %ld\n", number); break; case HDR_unlicensed: move((SCHAR*) (p + 2), (SCHAR*) &number, sizeof(number)); FPRINTF(sw_outfile, "\tUnlicensed accesses: %ld\n", number); break; case HDR_sweep_interval: move((SCHAR*) (p + 2), (SCHAR*) &number, sizeof(number)); FPRINTF(sw_outfile, "\tSweep interval: %ld\n", number); break; case HDR_log_name: FPRINTF(sw_outfile, "\tReplay logging file: %*s\n", p[1], p + 2); break; default: FPRINTF(sw_outfile, "\tUnrecognized option %d, length %d\n", p[0], p[1]); } } FPRINTF(sw_outfile, "\t*END*\n"); } // Warning: this function stops at the first blank. static void truncate_name( SCHAR* string) { /************************************** * * t r u n c a t e _ n a m e * ************************************** * * Functional description * Zap trailing blanks. * **************************************/ for (; *string; ++string) if (*string == ' ') { *string = 0; return; } }