mirror of
https://github.com/FirebirdSQL/firebird.git
synced 2025-01-31 18:43:03 +01:00
1182 lines
29 KiB
C++
1182 lines
29 KiB
C++
/*
|
|
* PROGRAM: JRD Rebuild scrambled database
|
|
* MODULE: rebuild.cpp
|
|
* DESCRIPTION: Main routine for analyzing and rebuilding database
|
|
*
|
|
* 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): ______________________________________.
|
|
*/
|
|
|
|
#include "../jrd/common.h"
|
|
|
|
#include "firebird.h"
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
|
|
#include "../jrd/ibase.h"
|
|
#include "../jrd/jrd.h"
|
|
#include "../jrd/jrd_time.h"
|
|
#include "../jrd/pag.h"
|
|
#include "../jrd/tra.h"
|
|
#include "../utilities/rebuild/rebuild.h"
|
|
#include "../utilities/rebuild/rebui_proto.h"
|
|
#include "../utilities/rebuild/rmet_proto.h"
|
|
#include "../utilities/rebuild/rstor_proto.h"
|
|
#include "../jrd/dmp_proto.h"
|
|
#include "../jrd/gds_proto.h"
|
|
#include "../common/utils_proto.h
|
|
|
|
#ifndef O_RDWR
|
|
#include <fcntl.h>
|
|
#endif
|
|
|
|
Database dbb_struct;
|
|
thread_db tdbb_struct, *gdbb;
|
|
PageControl dim;
|
|
jrd_tra dull;
|
|
|
|
const ULONG* tips;
|
|
|
|
FILE* dbg_file;
|
|
|
|
static void checksum(rbdb*, ULONG, ULONG, bool);
|
|
static USHORT compute_checksum(const rbdb*, PAG);
|
|
static void db_error(int);
|
|
static void dump(FILE*, rbdb*, ULONG, ULONG, UCHAR);
|
|
static void dump_tips(FILE*, rbdb*);
|
|
static void format_header(const rbdb*, header_page*, int, ULONG, ULONG, ULONG, ULONG);
|
|
static void format_index_root(index_root_page*, int, SSHORT, SSHORT);
|
|
static void format_pointer(pointer_page*, int, SSHORT, SSHORT, bool, SSHORT, SLONG *);
|
|
static void format_pip(page_inv_page*, int, int);
|
|
static void format_tip(tx_inv_page*, int, SLONG);
|
|
static void get_next_file(rbdb*, header_page*);
|
|
static void get_range(TEXT***, const TEXT* const* const, ULONG*, ULONG*);
|
|
static void get_switch(TEXT**, swc*);
|
|
static header_page* open_database(rbdb*, ULONG);
|
|
static void print_db_header(FILE*, const header_page*);
|
|
static void rebuild(rbdb*);
|
|
static void write_headers(FILE*, rbdb*, ULONG, ULONG);
|
|
|
|
static bool sw_rebuild;
|
|
static bool sw_print;
|
|
static bool sw_store;
|
|
static bool sw_dump_pages;
|
|
static bool sw_checksum;
|
|
//static bool sw_fudge;
|
|
static bool sw_fix;
|
|
static bool sw_dump_tips;
|
|
|
|
static const ULONG PPG_NUMBERS[] =
|
|
{
|
|
5897, 6058, 5409, 6199, 6200, 6220, 6221,
|
|
4739, 4868, 6332, 6333, 6329, 6359, 6751,
|
|
6331, 6392, 6806, 6819, 6820, 6866, 6875,
|
|
6876, 7019, 7284, 7430, 7431, 7757, 6893,
|
|
6894, 7408, 8308, 8309, 1036, 9120, 4528,
|
|
4563, 4572, 0, 0
|
|
};
|
|
|
|
|
|
int main( int argc, char *argv[])
|
|
{
|
|
/**************************************
|
|
*
|
|
* m a i n
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Parse and interpret command line, then do a variety
|
|
* of things.
|
|
*
|
|
**************************************/
|
|
TEXT out_file[128];
|
|
|
|
dbg_file = stdout;
|
|
sw_rebuild = sw_print = sw_store = sw_dump_pages = sw_checksum = false;
|
|
sw_dump_tips = /*sw_fudge = */ sw_fix = false;
|
|
|
|
ULONG c_lower_bound, c_upper_bound, d_lower_bound, d_upper_bound,
|
|
p_lower_bound, p_upper_bound, pg_size;
|
|
pg_size = p_lower_bound = c_lower_bound = d_lower_bound = 0;
|
|
p_upper_bound = c_upper_bound = d_upper_bound = BIG_NUMBER;
|
|
USHORT pg_type = 0;
|
|
|
|
const TEXT* const* const end = argv + argc;
|
|
++argv;
|
|
swc switch_space;
|
|
swc* token = &switch_space;
|
|
|
|
rbdb* rbdb = NULL;
|
|
header_page* header = NULL;
|
|
TEXT* ascii_out = NULL;
|
|
TEXT* db_in = NULL;
|
|
|
|
while (argv < end)
|
|
{
|
|
get_switch(argv, token);
|
|
argv++;
|
|
if (!token->swc_switch)
|
|
db_in = token->swc_string;
|
|
else
|
|
{
|
|
switch (*token->swc_string)
|
|
{
|
|
case 'b':
|
|
pg_size = atoi(*argv++);
|
|
break;
|
|
|
|
case 'c':
|
|
sw_checksum = true;
|
|
get_range(&argv, end, &c_lower_bound, &c_upper_bound);
|
|
break;
|
|
|
|
case 'd':
|
|
if ((argv < end) && (!strcmp(*argv, "tips"))) {
|
|
argv++;
|
|
sw_dump_tips = true;
|
|
}
|
|
else
|
|
sw_dump_pages = true;
|
|
|
|
get_range(&argv, end, &d_lower_bound, &d_upper_bound);
|
|
break;
|
|
|
|
case 'f':
|
|
sw_fix = true;
|
|
break;
|
|
|
|
case 'o':
|
|
if (argv < end)
|
|
{
|
|
get_switch(argv, token);
|
|
if (token->swc_switch)
|
|
break;
|
|
fb_utils::copy_terminate(out_file, token->swc_string, sizeof(out_file));
|
|
ascii_out = out_file;
|
|
argv++;
|
|
}
|
|
case 'p':
|
|
sw_print = true;
|
|
get_range(&argv, end, &p_lower_bound, &p_upper_bound);
|
|
break;
|
|
|
|
case 'r':
|
|
sw_rebuild = true;
|
|
break;
|
|
|
|
case 's':
|
|
sw_store = true;
|
|
break;
|
|
|
|
case 't':
|
|
pg_type = atoi(*argv++);
|
|
break;
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
if (db_in)
|
|
{
|
|
rbdb = (rbdb*) RBDB_alloc((SLONG) (sizeof(struct rbdb) + strlen(db_in)));
|
|
strcpy(rbdb->rbdb_file.fil_name, db_in);
|
|
rbdb->rbdb_file.fil_length = strlen(db_in);
|
|
if (header = open_database(rbdb, pg_size))
|
|
get_next_file(rbdb, header);
|
|
|
|
// some systems don't care for this write sharing stuff...
|
|
if (rbdb && (sw_dump_tips || sw_dump_pages))
|
|
{
|
|
RBDB_close(rbdb);
|
|
if (rbdb->rbdb_valid)
|
|
tips = RMET_tips(db_in);
|
|
RBDB_open(rbdb);
|
|
}
|
|
}
|
|
|
|
|
|
gdbb = &tdbb_struct;
|
|
gdbb->tdbb_database = &dbb_struct;
|
|
gdbb->tdbb_transaction = &dull;
|
|
dull.tra_number = header->hdr_next_transaction;
|
|
gdbb->tdbb_database->dbb_max_records = (rbdb->rbdb_page_size - sizeof(struct data_page)) /
|
|
(sizeof(data_page::dpg_repeat) + OFFSETA(RHD, rhd_data));
|
|
gdbb->tdbb_database->dbb_pcontrol = &dim;
|
|
gdbb->tdbb_database->dbb_dp_per_pp =
|
|
(rbdb->rbdb_page_size - OFFSETA(pointer_page*, ppg_page)) * 8 / 34;
|
|
gdbb->tdbb_database->dbb_pcontrol->pgc_bytes =
|
|
rbdb->rbdb_page_size - OFFSETA(page_inv_page*, pip_bits);
|
|
gdbb->tdbb_database->dbb_pcontrol->pgc_ppp = gdbb->tdbb_database->dbb_pcontrol->pgc_bytes * 8;
|
|
gdbb->tdbb_database->dbb_pcontrol->pgc_tpt =
|
|
(rbdb->rbdb_page_size - OFFSETA(tx_inv_page*, tip_transactions)) * 4;
|
|
gdbb->tdbb_database->dbb_pcontrol->pgc_pip = 1;
|
|
|
|
if (ascii_out)
|
|
dbg_file = fopen(ascii_out, "w");
|
|
|
|
if (sw_print && rbdb && header)
|
|
write_headers(dbg_file, rbdb, p_lower_bound, p_upper_bound);
|
|
|
|
if (sw_store && rbdb && header)
|
|
RSTORE(rbdb);
|
|
|
|
if (sw_checksum && rbdb && header)
|
|
checksum(rbdb, c_lower_bound, c_upper_bound, sw_fix);
|
|
|
|
if (sw_dump_tips && rbdb && header)
|
|
dump_tips(dbg_file, rbdb);
|
|
|
|
if (sw_dump_pages && rbdb && header)
|
|
dump(dbg_file, rbdb, d_lower_bound, d_upper_bound, pg_type);
|
|
|
|
if (sw_rebuild && rbdb && header)
|
|
rebuild(rbdb);
|
|
|
|
if (ascii_out)
|
|
fclose(dbg_file);
|
|
|
|
if (rbdb)
|
|
RBDB_close(rbdb);
|
|
|
|
while (rbdb)
|
|
{
|
|
rbdb* const next_db = rbdb->rbdb_next;
|
|
if (rbdb->rbdb_buffer1)
|
|
gds__free(rbdb->rbdb_buffer1);
|
|
if (rbdb->rbdb_buffer2)
|
|
gds__free(rbdb->rbdb_buffer2);
|
|
gds__free(rbdb);
|
|
rbdb = next_db;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
#ifdef HPUX
|
|
PAG CCH_fetch(WIN* x, USHORT y, int z)
|
|
{
|
|
/**************************************
|
|
*
|
|
* C C H _ f e t c h
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* This routine lets me link in
|
|
* DMP.C from JRD without having the
|
|
* linker piss all over it.
|
|
*
|
|
**************************************/
|
|
}
|
|
|
|
|
|
PAG CCH_release(WIN* x)
|
|
{
|
|
/**************************************
|
|
*
|
|
* C C H _ f e t c h
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* This routine lets me link in
|
|
* DMP.C from JRD without having the
|
|
* linker piss all over it.
|
|
*
|
|
**************************************/
|
|
}
|
|
#endif
|
|
|
|
|
|
void* RBDB_alloc(SLONG size)
|
|
{
|
|
/**************************************
|
|
*
|
|
* R B D B _ a l l o c
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Allocate and zero a piece of memory.
|
|
*
|
|
**************************************/
|
|
return memset(gds__alloc(size), 0, size);
|
|
}
|
|
|
|
|
|
void RBDB_close( rbdb* rbdb)
|
|
{
|
|
/**************************************
|
|
*
|
|
* R B D B _ c l o s e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
*
|
|
**************************************/
|
|
for (; rbdb; rbdb = rbdb->rbdb_next)
|
|
close(rbdb->rbdb_file.fil_file);
|
|
}
|
|
|
|
|
|
void RBDB_open( rbdb* rbdb)
|
|
{
|
|
/**************************************
|
|
*
|
|
* R B D B _ o p e n
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Open a database file.
|
|
*
|
|
**************************************/
|
|
if ((rbdb->rbdb_file.fil_file = open(rbdb->rbdb_file.fil_name, O_RDWR, 0)) == -1)
|
|
{
|
|
db_error(errno);
|
|
}
|
|
}
|
|
|
|
|
|
PAG RBDB_read(rbdb* rbdb, SLONG page_number)
|
|
{
|
|
/**************************************
|
|
*
|
|
* R B D B _ r e a d
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Read a database page.
|
|
*
|
|
**************************************/
|
|
int file = rbdb->rbdb_file.fil_file;
|
|
|
|
const FB_UINT64 offset = ((FB_UINT64) page_number) * ((FB_UINT64) rbdb->rbdb_page_size);
|
|
if (lseek (file, offset, 0) == -1)
|
|
db_error(errno);
|
|
|
|
SSHORT length = rbdb->rbdb_page_size;
|
|
for (char* p = (SCHAR *) rbdb->rbdb_buffer1; length > 0;)
|
|
{
|
|
const SSHORT l = read(file, p, length);
|
|
if (l < 0)
|
|
db_error(errno);
|
|
else if (l == 0)
|
|
return NULL;
|
|
p += l;
|
|
length -= l;
|
|
}
|
|
rbdb->rbdb_file.fil_file = file;
|
|
return rbdb->rbdb_buffer1;
|
|
}
|
|
|
|
|
|
void RBDB_write( rbdb* rbdb, PAG page, SLONG page_number)
|
|
{
|
|
/**************************************
|
|
*
|
|
* R B D B _ w r i t e ( u n i x )
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Write a database page.
|
|
*
|
|
**************************************/
|
|
page->pag_checksum = compute_checksum(rbdb, page);
|
|
const ULONG page_size = rbdb->rbdb_page_size;
|
|
int fd = rbdb->rbdb_file.fil_file;
|
|
|
|
const FB_UINT64 offset = ((FB_UINT64) page_number) * ((FB_UINT64) page_size);
|
|
if (lseek (fd, offset, 0) == -1)
|
|
db_error(errno);
|
|
if (write(fd, page, page_size) == -1)
|
|
db_error(errno);
|
|
}
|
|
|
|
|
|
static void checksum( rbdb* rbdb, ULONG lower, ULONG upper, bool sw_fix)
|
|
{
|
|
/**************************************
|
|
*
|
|
* c h e c k s u m
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* read, compute, check, and correct
|
|
* checksums in this database.
|
|
*
|
|
**************************************/
|
|
TEXT s[128];
|
|
|
|
for (ULONG page_number = lower; page_number <= upper; page_number++)
|
|
{
|
|
pag* page = RBDB_read(rbdb, page_number);
|
|
if (!page)
|
|
return;
|
|
const USHORT old_checksum = page->pag_checksum;
|
|
const USHORT new_checksum = compute_checksum(rbdb, page);
|
|
if (sw_fix)
|
|
page->pag_checksum = new_checksum;
|
|
if (new_checksum == old_checksum)
|
|
sprintf(s, "checksum %5d is OK", old_checksum);
|
|
else
|
|
sprintf(s, "stored checksum %5d\tcomputed checksum %5d\t%s",
|
|
old_checksum, new_checksum, sw_fix ? "fixed" : "");
|
|
printf("page %9d\t%s\n", page_number, s);
|
|
}
|
|
}
|
|
|
|
|
|
static USHORT compute_checksum( const rbdb* rbdb, PAG page)
|
|
{
|
|
/**************************************
|
|
*
|
|
* c o m p u t e _ c h e c k s u m
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* compute checksum for a V3 page.
|
|
*
|
|
**************************************/
|
|
const ULONG* const end = (ULONG *) ((SCHAR *) page + rbdb->rbdb_page_size);
|
|
const USHORT old_checksum = page->pag_checksum;
|
|
page->pag_checksum = 0;
|
|
const ULONG* p = (ULONG *) page;
|
|
ULONG checksum = 0;
|
|
|
|
do {
|
|
checksum += *p++;
|
|
checksum += *p++;
|
|
checksum += *p++;
|
|
checksum += *p++;
|
|
checksum += *p++;
|
|
checksum += *p++;
|
|
checksum += *p++;
|
|
checksum += *p++;
|
|
} while (p < end);
|
|
|
|
page->pag_checksum = old_checksum;
|
|
|
|
if (checksum)
|
|
return checksum;
|
|
|
|
// If the page is all zeros, return an artificial checksum
|
|
|
|
for (p = (ULONG *) page; p < end;)
|
|
{
|
|
if (*p++)
|
|
return checksum;
|
|
}
|
|
|
|
// Page is all zeros -- invent a checksum
|
|
|
|
return 12345;
|
|
}
|
|
|
|
static void db_error( int status)
|
|
{
|
|
/**************************************
|
|
*
|
|
* d b _ e r r o r
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
*
|
|
**************************************/
|
|
printf(strerror(status));
|
|
exit(FINI_ERROR);
|
|
}
|
|
|
|
|
|
static void dump(FILE* file, rbdb* rbdb, ULONG lower, ULONG upper, UCHAR pg_type)
|
|
{
|
|
/**************************************
|
|
*
|
|
* d u m p
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* dump the contents of some page
|
|
* or pages in the database.
|
|
*
|
|
**************************************/
|
|
ULONG sequence = 0;
|
|
|
|
if (rbdb->rbdb_last_page && upper == BIG_NUMBER)
|
|
upper = rbdb->rbdb_last_page;
|
|
|
|
PAG page;
|
|
while (page = RBDB_read(rbdb, lower))
|
|
{
|
|
if (page->pag_type == pag_transactions && tips)
|
|
{
|
|
for (const ULONG* tip = tips; tip[sequence]; sequence++)
|
|
{
|
|
if (tip[sequence] == lower)
|
|
break;
|
|
else if (!tip[sequence]) {
|
|
sequence = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
sequence = 0;
|
|
if (pg_type && (page->pag_type != pg_type))
|
|
{
|
|
fprintf(file, "\nChanging page %d type from %d to %d\n", lower, page->pag_type, pg_type);
|
|
page->pag_type = pg_type;
|
|
}
|
|
DMP_fetched_page(page, lower, sequence, rbdb->rbdb_page_size);
|
|
const ULONG* p = (ULONG *) page;
|
|
const ULONG* const end = p + (rbdb->rbdb_page_size / sizeof(ULONG)) - 1;
|
|
while (!*p && p < end)
|
|
++p;
|
|
if (!*p)
|
|
fprintf(file, " Page is all zeroes.\n");
|
|
// This cannot be true because sw_fudge is never activated
|
|
//if (sw_fudge)
|
|
// RBDB_write(rbdb, page, lower);
|
|
if (++lower > upper)
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
static void dump_tips( FILE* file, rbdb* rbdb)
|
|
{
|
|
/**************************************
|
|
*
|
|
* d u m p _ t i p s
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* dump the contents of tip pages
|
|
* in the database.
|
|
*
|
|
**************************************/
|
|
if (!tips)
|
|
fprintf(file, "not enough database. Store headers and look there\n");
|
|
|
|
PAG page;
|
|
ULONG sequence = 1;
|
|
for (const ULONG* tip = tips; *tip && (page = RBDB_read(rbdb, *tip)); sequence++)
|
|
{
|
|
DMP_fetched_page(page, *tip++, sequence, rbdb->rbdb_page_size);
|
|
}
|
|
}
|
|
|
|
|
|
static void format_header(const rbdb* rbdb,
|
|
header_page* page,
|
|
int page_size,
|
|
ULONG oldest, ULONG active, ULONG next, ULONG imp)
|
|
{
|
|
/**************************************
|
|
*
|
|
* f o r m a t _ h e a d e r
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Format a header page from inputs
|
|
*
|
|
**************************************/
|
|
memset(page, 0, page_size);
|
|
page->hdr_page_size = page_size;
|
|
page->pag_type = pag_header;
|
|
page->hdr_ods_version = ODS_VERSION | ODS_TYPE_CURRENT;
|
|
page->hdr_PAGES = 2;
|
|
page->hdr_oldest_transaction = oldest;
|
|
page->hdr_oldest_active = active;
|
|
page->hdr_next_transaction = next;
|
|
page->hdr_implementation = imp;
|
|
page->pag_checksum = compute_checksum(rbdb, page);
|
|
}
|
|
|
|
|
|
static void format_index_root(index_root_page* page,
|
|
int page_size, SSHORT relation_id, SSHORT count)
|
|
{
|
|
/**************************************
|
|
*
|
|
* f o r m a t _ i n d e x _ r o o t
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Format an index root page, without any indexes for a start
|
|
*
|
|
**************************************/
|
|
memset(page, 0, page_size);
|
|
page->pag_type = pag_root;
|
|
page->irt_relation = relation_id;
|
|
page->irt_count = count;
|
|
}
|
|
|
|
|
|
static void format_pointer(pointer_page* page,
|
|
int page_size,
|
|
SSHORT relation_id,
|
|
SSHORT sequence,
|
|
bool eof, SSHORT count, SLONG * page_vector)
|
|
{
|
|
/**************************************
|
|
*
|
|
* f o r m a t _ p o i n t e r
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Format a pointer page. In addition of the buffer and page size,
|
|
* we need a relation id, a pointer page sequence (within relation),
|
|
* a flag indicated whether this is the last pointer page for the
|
|
* relation, and a count and vector of pages to point to.
|
|
*
|
|
**************************************/
|
|
memset(page, 0, page_size);
|
|
page->pag_type = pag_pointer;
|
|
page->ppg_sequence = sequence;
|
|
page->ppg_relation = relation_id;
|
|
page->ppg_count = count;
|
|
page->ppg_min_space = 0;
|
|
page->ppg_max_space = count;
|
|
if (eof)
|
|
page->pag_flags |= ppg_eof;
|
|
memcpy(page->ppg_page, page_vector, count * sizeof(SLONG));
|
|
}
|
|
|
|
|
|
static void format_pip( page_inv_page* page, int page_size, int last_flag)
|
|
{
|
|
/**************************************
|
|
*
|
|
* f o r m a t _ p i p
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Fake a fully RBDB_allocated (all pages RBDB_allocated) page inventory
|
|
* page.
|
|
*
|
|
**************************************/
|
|
page->pag_type = pag_pages;
|
|
page->pag_flags = 0;
|
|
|
|
// Set all page bits to zero, indicating RBDB_allocated
|
|
|
|
const SSHORT bytes = page_size - OFFSETA(page_inv_page*, pip_bits);
|
|
memset(page->pip_bits, 0, bytes);
|
|
|
|
// If this is the last pip, make sure the last page (which
|
|
// will become the next pip) is marked free. When the
|
|
// time comes to RBDB_allocate the next page, that page will
|
|
// be formatted as the next pip.
|
|
|
|
if (last_flag)
|
|
page->pip_bits[bytes - 1] |= 1 << 7;
|
|
}
|
|
|
|
|
|
static void format_tip( tx_inv_page* page, int page_size, SLONG next_page)
|
|
{
|
|
/**************************************
|
|
*
|
|
* f o r m a t _ t i p
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Fake a fully commit transaction inventory page.
|
|
*
|
|
**************************************/
|
|
page->pag_type = pag_transactions;
|
|
page->pag_flags = 0;
|
|
|
|
// The "next" tip page number is included for redundancy, but is not actually
|
|
// read by the engine, so can be safely left zero. If known, it would nice
|
|
// to supply it.
|
|
|
|
page->tip_next = next_page;
|
|
|
|
// Code for committed transaction is 3, so just fill all bytes with -1
|
|
|
|
const SSHORT bytes = page_size - OFFSETA(tx_inv_page*, tip_transactions);
|
|
memset(page->tip_transactions, -1, bytes);
|
|
}
|
|
|
|
|
|
static void get_next_file( rbdb* rbdb, header_page* header)
|
|
{
|
|
/**************************************
|
|
*
|
|
* g e t _ n e x t _ f i l e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* If there's another file as part of
|
|
* this database, get it now.
|
|
*
|
|
**************************************/
|
|
rbdb** next = &rbdb->rbdb_next;
|
|
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])
|
|
{
|
|
if (*p == HDR_file)
|
|
{
|
|
rbdb* next_rbdb = (rbdb*) RBDB_alloc(sizeof(struct rbdb) + (SSHORT) p[1]);
|
|
next_rbdb->rbdb_file.fil_length = (SSHORT) p[1];
|
|
strncpy(next_rbdb->rbdb_file.fil_name, p + 2, (SSHORT) p[1]);
|
|
*next = next_rbdb;
|
|
next = &next_rbdb->rbdb_next;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void get_range(TEXT*** argv, const TEXT* const* const end, ULONG* lower, ULONG* upper)
|
|
{
|
|
/**************************************
|
|
*
|
|
* g e t _ r a n g e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* get a range out of the argument
|
|
* vector;
|
|
*
|
|
**************************************/
|
|
struct swc token_space;
|
|
swc* token = &token_space;
|
|
|
|
if (*argv < end)
|
|
{
|
|
get_switch(*argv, token);
|
|
if (token->swc_switch)
|
|
return;
|
|
++*argv;
|
|
TEXT c = 0;
|
|
const TEXT* p;
|
|
for (p = token->swc_string; *p; p++)
|
|
if (*p < '0' || *p > '9') {
|
|
c = *p;
|
|
*p++ = 0;
|
|
break;
|
|
}
|
|
*upper = *lower = (ULONG) atoi(token->swc_string);
|
|
if (*p && (c == ':' || c == ','))
|
|
{
|
|
if (*p == '*')
|
|
*upper = BIG_NUMBER;
|
|
else
|
|
*upper = (ULONG) atoi(p);
|
|
return;
|
|
}
|
|
}
|
|
if (*argv < end)
|
|
{
|
|
get_switch(*argv, token);
|
|
if (token->swc_switch)
|
|
return;
|
|
if ((*token->swc_string == ':') || (*token->swc_string == ','))
|
|
{
|
|
const TEXT* p = token->swc_string;
|
|
if (*++p)
|
|
{
|
|
if (*p == '*')
|
|
*upper = BIG_NUMBER;
|
|
else
|
|
*upper = (ULONG) atoi(p);
|
|
}
|
|
else if (*argv++ < end)
|
|
{
|
|
get_switch(*argv, token);
|
|
if (token->swc_switch)
|
|
return;
|
|
}
|
|
}
|
|
if (*token->swc_string == '*')
|
|
*upper = BIG_NUMBER;
|
|
else
|
|
*upper = (ULONG) atoi(token->swc_string);
|
|
*argv++;
|
|
}
|
|
}
|
|
|
|
|
|
static void get_switch( TEXT** argv, swc* token)
|
|
{
|
|
/**************************************
|
|
*
|
|
* g e t _ s w i t c h
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* get the next argument in the argument
|
|
* vector;
|
|
*
|
|
**************************************/
|
|
token->swc_string = *argv;
|
|
|
|
if (*token->swc_string == '-') {
|
|
token->swc_switch = true;
|
|
token->swc_string++;
|
|
}
|
|
else
|
|
token->swc_switch = false;
|
|
|
|
const int temp = strlen(token->swc_string) - 1;
|
|
|
|
if (token->swc_string[temp] == ',') {
|
|
token->swc_string[temp] = '\0';
|
|
//token->swc_comma = true;
|
|
}
|
|
//else
|
|
// token->swc_comma = false;
|
|
}
|
|
|
|
|
|
static header_page* open_database( rbdb* rbdb, ULONG pg_size)
|
|
{
|
|
/**************************************
|
|
*
|
|
* o p e n _ d a t a b a s e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Open a database and setup the
|
|
* rbdb. Return a pointer to the
|
|
* header page page header;
|
|
*
|
|
**************************************/
|
|
UCHAR temp[1024];
|
|
|
|
RBDB_open(rbdb);
|
|
|
|
rbdb->rbdb_page_size = sizeof(temp);
|
|
rbdb->rbdb_buffer1 = (PAG) temp;
|
|
rbdb->rbdb_valid = true;
|
|
|
|
header_page* header = (header_page*) RBDB_read(rbdb, (SLONG) 0);
|
|
|
|
if (header->pag_type != pag_header) {
|
|
printf("header page has wrong type, expected %d found %d!\n", pag_header, header->pag_type);
|
|
rbdb->rbdb_valid = false;
|
|
}
|
|
|
|
if (header->hdr_ods_version != ODS_VERSION | ODS_TYPE_CURRENT)
|
|
{
|
|
printf("Wrong ODS version, expected %d type %04x, encountered %d type %04x.\n",
|
|
ODS_VERSION, ODS_TYPE_CURRENT,
|
|
header->hdr_ods_version & ~ODS_TYPE_MASK,
|
|
header->hdr_ods_version & ODS_TYPE_MASK
|
|
);
|
|
rbdb->rbdb_valid = false;
|
|
}
|
|
|
|
if (pg_size && (pg_size != header->hdr_page_size)) {
|
|
printf("Using page size %d\n", pg_size);
|
|
header->hdr_page_size = pg_size;
|
|
rbdb->rbdb_valid = false;
|
|
}
|
|
else if (!header->hdr_page_size) {
|
|
printf("Using page size 1024\n");
|
|
header->hdr_page_size = 1024;
|
|
rbdb->rbdb_valid = false;
|
|
}
|
|
|
|
printf("\nDatabase \"%s\"\n\n", rbdb->rbdb_file.fil_name);
|
|
|
|
|
|
rbdb->rbdb_page_size = header->hdr_page_size;
|
|
rbdb->rbdb_map_count = rbdb->rbdb_map_length / rbdb->rbdb_page_size;
|
|
rbdb->rbdb_buffer1 = (PAG) RBDB_alloc(rbdb->rbdb_page_size);
|
|
rbdb->rbdb_buffer2 = (PAG) RBDB_alloc(rbdb->rbdb_page_size);
|
|
|
|
return header;
|
|
}
|
|
|
|
|
|
static void print_db_header( FILE* file, const header_page* header)
|
|
{
|
|
/**************************************
|
|
*
|
|
* p r i n t _ d b _ h e a d e r
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Print database header page.
|
|
*
|
|
**************************************/
|
|
fprintf(file, "Database header page information:\n");
|
|
fprintf(file, " Page size\t\t\t%d\n", header->hdr_page_size);
|
|
fprintf(file, " ODS version\t\t\t%d type %04x\n",
|
|
header->hdr_ods_version & ~ODS_TYPE_MASK,
|
|
header->hdr_ods_version & ODS_TYPE_MASK);
|
|
fprintf(file, " PAGES\t\t\t%d\n", header->hdr_PAGES);
|
|
fprintf(file, " next page\t\t\t%d\n", header->hdr_next_page);
|
|
fprintf(file, " Oldest transaction\t\t%ld\n", header->hdr_oldest_transaction);
|
|
fprintf(file, " Oldest active\t\t%ld\n", header->hdr_oldest_active);
|
|
fprintf(file, " Oldest snapshot\t\t%ld\n", header->hdr_oldest_snapshot);
|
|
fprintf(file, " Next transaction\t\t%ld\n", header->hdr_next_transaction);
|
|
|
|
fprintf(file, " Data pages per pointer page\t%ld\n", gdbb->tdbb_database->dbb_dp_per_pp);
|
|
fprintf(file, " Max records per page\t%ld\n", gdbb->tdbb_database->dbb_max_records);
|
|
|
|
//fprintf (" Sequence number %d\n", header->hdr_sequence);
|
|
//fprintf (" Creation date \n", header->hdr_creation_date);
|
|
|
|
fprintf(file, " Next attachment ID\t\t%ld\n", header->hdr_attachment_id);
|
|
fprintf(file, " Implementation ID\t\t%ld\n", header->hdr_implementation);
|
|
fprintf(file, " Shadow count\t\t%ld\n", header->hdr_shadow_count);
|
|
|
|
tm time;
|
|
isc_decode_timestamp(header->hdr_creation_date, &time);
|
|
|
|
fprintf(file, " Creation date:\t\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);
|
|
fprintf(file, " Cache buffers\t\t%ld\n", header->hdr_cache_buffers);
|
|
fprintf(file, " Bumped transaction\t\t%ld\n", header->hdr_bumped_transaction);
|
|
|
|
fprintf(file, "\n Variable header data:\n");
|
|
|
|
SLONG number;
|
|
|
|
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])
|
|
{
|
|
switch (*p)
|
|
{
|
|
case HDR_root_file_name:
|
|
fprintf(file, "\tRoot file name: %*s\n", p[1], p + 2);
|
|
break;
|
|
/*
|
|
case HDR_journal_server:
|
|
fprintf(file, "\tJournal server: %*s\n", p[1], p + 2);
|
|
break;
|
|
*/
|
|
case HDR_file:
|
|
fprintf(file, "\tContinuation file: %*s\n", p[1], p + 2);
|
|
break;
|
|
|
|
case HDR_last_page:
|
|
memcpy(&number, p + 2, sizeof(number));
|
|
fprintf(file, "\tLast logical page: %ld\n", number);
|
|
break;
|
|
/*
|
|
case HDR_unlicensed:
|
|
memcpy(&number, p + 2, sizeof(number));
|
|
fprintf(file, "\tUnlicensed accesses: %ld\n", number);
|
|
break;
|
|
*/
|
|
case HDR_sweep_interval:
|
|
memcpy(&number, p + 2, sizeof(number));
|
|
fprintf(file, "\tSweep interval: %ld\n", number);
|
|
break;
|
|
|
|
case HDR_log_name:
|
|
fprintf(file, "\tReplay logging file: %*s\n", p[1], p + 2);
|
|
break;
|
|
/*
|
|
case HDR_journal_file:
|
|
fprintf(file, "\tJournal file: %*s\n", p[1], p + 2);
|
|
break;
|
|
*/
|
|
case HDR_password_file_key:
|
|
fprintf(file, "\tPassword file key: (can't print)\n");
|
|
break;
|
|
/*
|
|
case HDR_backup_info:
|
|
fprintf(file, "\tBackup info: (can't print)\n");
|
|
break;
|
|
|
|
case HDR_cache_file:
|
|
fprintf(file, "\tShared cache file: %*s\n", p[1], p + 2);
|
|
break;
|
|
*/
|
|
default:
|
|
fprintf(file, "\tUnrecognized option %d, length %d\n", p[0], p[1]);
|
|
}
|
|
}
|
|
|
|
fprintf(file, "\n\n");
|
|
}
|
|
|
|
|
|
static void rebuild( rbdb* rbdb)
|
|
{
|
|
/**************************************
|
|
*
|
|
* r e b u i l d
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Write out an improved database.
|
|
*
|
|
**************************************/
|
|
const ULONG page_size = rbdb->rbdb_page_size;
|
|
pag* page = rbdb->rbdb_buffer1;
|
|
const ULONG* page_numbers = PPG_NUMBERS;
|
|
for (ULONG number = 5898; (number < 5899) && (page = RBDB_read(rbdb, number)); number++)
|
|
{
|
|
pointer_page* pointer = (pointer_page*) page;
|
|
// format_pointer (page, page_size, 25, 3, true, 37, page_numbers);
|
|
|
|
RBDB_write(rbdb, page, number);
|
|
}
|
|
}
|
|
|
|
|
|
static void write_headers(FILE* file, rbdb* rbdb, ULONG lower, ULONG upper)
|
|
{
|
|
/**************************************
|
|
*
|
|
* w r i t e _ h e a d e r s
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Print out the page headers.
|
|
*
|
|
**************************************/
|
|
const pag* page;
|
|
for (ULONG page_number = lower;
|
|
(page_number <= upper) && (page = RBDB_read(rbdb, page_number));
|
|
page_number++)
|
|
{
|
|
fprintf(file, "page %d, ", page_number);
|
|
|
|
switch (page->pag_type)
|
|
{
|
|
case pag_header:
|
|
fprintf(file, "header page, checksum %d\n", page->pag_checksum);
|
|
print_db_header(file, (header_page*) page);
|
|
break;
|
|
|
|
case pag_pages:
|
|
{
|
|
fprintf(file, "page inventory page, checksum %d\n", page->pag_checksum);
|
|
const page_inv_page* pip = (page_inv_page*) page;
|
|
fprintf(file, "\tlowest free page %d\n\n", pip->pip_min);
|
|
}
|
|
break;
|
|
|
|
case pag_transactions:
|
|
fprintf(file, "TIP page, checksum %d\n", page->pag_checksum);
|
|
fprintf(file, "\tnext tip for database %ld\n\n", ((tx_inv_page*) page)->tip_next);
|
|
break;
|
|
|
|
case pag_pointer:
|
|
{
|
|
fprintf(file, "pointer page, checksum %d\n", page->pag_checksum);
|
|
const pointer_page* pointer = (pointer_page*) page;
|
|
fprintf(file, "\trelation %d, sequence %ld, next pip %ld, active slots %d\n",
|
|
pointer->ppg_relation, pointer->ppg_sequence,
|
|
pointer->ppg_next, pointer->ppg_count);
|
|
fprintf(file, "\tfirst slot with space %d, last slot with space %d\n",
|
|
pointer->ppg_min_space, pointer->ppg_max_space);
|
|
fprintf(file, "\t%s\n",
|
|
(pointer->pag_flags & ppg_eof) ? "last pointer for relation\n" : "");
|
|
}
|
|
break;
|
|
|
|
case pag_data:
|
|
{
|
|
fprintf(file, "data page, checksum %d\n", page->pag_checksum);
|
|
const data_page* data = (data_page*) page;
|
|
fprintf(file, "\trelation %d, sequence %ld, records on page %d\n",
|
|
data->dpg_relation, data->dpg_sequence,
|
|
data->dpg_count);
|
|
fprintf(file, "\t%s%s%s%s\n",
|
|
(data->pag_flags & dpg_orphan) ? "orphan " : "",
|
|
(data->pag_flags & dpg_full) ? "full " : "",
|
|
(data->pag_flags & dpg_large) ? "contains a large object" : "",
|
|
(data->pag_flags) ? "\n" : "");
|
|
}
|
|
break;
|
|
|
|
case pag_root:
|
|
{
|
|
fprintf(file, "index root page, checksum %d\n", page->pag_checksum);
|
|
const index_root_page* index_root = (index_root_page*) page;
|
|
fprintf(file, "\trelation %d, number of indexes %d\n\n",
|
|
index_root->irt_relation, index_root->irt_count);
|
|
}
|
|
break;
|
|
|
|
case pag_index:
|
|
{
|
|
fprintf(file, "btree page (bucket), checksum %d\n", page->pag_checksum);
|
|
const btree_page* bucket = (btree_page*) page;
|
|
fprintf(file, "\trelation %d, right sibling bucket: %ld,\n",
|
|
bucket->btr_relation, bucket->btr_sibling);
|
|
fprintf(file, "\tdata length %d, index id %d, level %d\n",
|
|
bucket->btr_length, bucket->btr_id, bucket->btr_level);
|
|
fprintf(file, "\t%s%s%s\n",
|
|
(bucket->pag_flags & btr_leftmost) ? "leftmost " : "",
|
|
(bucket->pag_flags & btr_not_prop) ? "all duplicates " : "",
|
|
(bucket->Pag_flags & btr_marked) ? "marked for delete" : "");
|
|
}
|
|
break;
|
|
|
|
case pag_blob:
|
|
{
|
|
fprintf(file, "blob page, checksum %d\n", page->pag_checksum);
|
|
const blob_page* blob = (blob_page*) page;
|
|
fprintf(file, "\tlead page: %ld, sequence: %ld, length: %d\n",
|
|
blob->blp_lead_page, blob->blp_sequence,
|
|
blob->blp_length);
|
|
fprintf(file, "\tcontains %s\n",
|
|
(blob->pag_flags & blp_pointers) ? "pointers" : "data");
|
|
}
|
|
break;
|
|
|
|
case pag_ids:
|
|
fprintf(file, "generator page, checksum %d\n\n", page->pag_checksum);
|
|
break;
|
|
|
|
case pag_log:
|
|
fprintf(dbg_file, "write-ahead log info page, checksum %d\n\n", page->pag_checksum);
|
|
break;
|
|
|
|
default:
|
|
fprintf(file, "unknown page type\n\n");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|