2011-01-18 01:33:27 +00:00
|
|
|
/*
|
|
|
|
* pg_filedump.c - PostgreSQL file dump utility for dumping and
|
|
|
|
* formatting heap (data), index and control files.
|
|
|
|
*
|
2011-01-18 02:28:26 +00:00
|
|
|
* Copyright (c) 2002-2010 Red Hat, Inc.
|
2012-03-12 16:08:33 +00:00
|
|
|
* Copyright (c) 2011-2012, PostgreSQL Global Development Group
|
2011-01-18 01:33:27 +00:00
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
*
|
2012-03-12 16:08:33 +00:00
|
|
|
* Original Author: Patrick Macdonald <patrickm@redhat.com>
|
2011-01-18 01:33:27 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include "pg_filedump.h"
|
|
|
|
|
2012-03-12 16:08:33 +00:00
|
|
|
#include "utils/pg_crc_tables.h"
|
|
|
|
|
2011-01-18 01:33:27 +00:00
|
|
|
// Global variables for ease of use mostly
|
|
|
|
static FILE *fp = NULL; // File to dump or format
|
|
|
|
static char *fileName = NULL; // File name for display
|
|
|
|
static char *buffer = NULL; // Cache for current block
|
|
|
|
static unsigned int blockSize = 0; // Current block size
|
|
|
|
static unsigned int currentBlock = 0; // Current block in file
|
|
|
|
static unsigned int pageOffset = 0; // Offset of current block
|
|
|
|
static unsigned int bytesToFormat = 0; // Number of bytes to format
|
|
|
|
static unsigned int blockVersion = 0; // Block version number
|
|
|
|
|
|
|
|
// Function Prototypes
|
|
|
|
static void DisplayOptions (unsigned int validOptions);
|
|
|
|
static unsigned int ConsumeOptions (int numOptions, char **options);
|
|
|
|
static int GetOptionValue (char *optionString);
|
|
|
|
static void FormatBlock ();
|
|
|
|
static unsigned int GetBlockSize ();
|
|
|
|
static unsigned int GetSpecialSectionType (Page page);
|
|
|
|
static bool IsBtreeMetaPage(Page page);
|
|
|
|
static void CreateDumpFileHeader (int numOptions, char **options);
|
|
|
|
static int FormatHeader (Page page);
|
|
|
|
static void FormatItemBlock (Page page);
|
|
|
|
static void FormatItem (unsigned int numBytes, unsigned int startIndex,
|
|
|
|
unsigned int formatAs);
|
|
|
|
static void FormatSpecial ();
|
|
|
|
static void FormatControl ();
|
|
|
|
static void FormatBinary (unsigned int numBytes, unsigned int startIndex);
|
|
|
|
static void DumpBinaryBlock ();
|
|
|
|
static void DumpFileContents ();
|
|
|
|
|
|
|
|
|
2012-03-12 16:08:33 +00:00
|
|
|
// Send properly formed usage information to the user.
|
2011-01-18 01:33:27 +00:00
|
|
|
static void
|
|
|
|
DisplayOptions (unsigned int validOptions)
|
|
|
|
{
|
|
|
|
if (validOptions == OPT_RC_COPYRIGHT)
|
|
|
|
printf
|
2011-01-18 02:28:26 +00:00
|
|
|
("\nVersion %s (for %s)"
|
|
|
|
"\nCopyright (c) 2002-2010 Red Hat, Inc."
|
2012-03-12 16:08:33 +00:00
|
|
|
"\nCopyright (c) 2011-2012, PostgreSQL Global Development Group\n",
|
2011-01-18 02:28:26 +00:00
|
|
|
FD_VERSION, FD_PG_VERSION);
|
2011-01-18 01:33:27 +00:00
|
|
|
|
|
|
|
printf
|
|
|
|
("\nUsage: pg_filedump [-abcdfhixy] [-R startblock [endblock]] [-S blocksize] file\n\n"
|
|
|
|
"Display formatted contents of a PostgreSQL heap/index/control file\n"
|
2011-01-18 02:28:26 +00:00
|
|
|
"Defaults are: relative addressing, range of the entire file, block\n"
|
2011-01-18 01:33:27 +00:00
|
|
|
" size as listed on block 0 in the file\n\n"
|
|
|
|
"The following options are valid for heap and index files:\n"
|
|
|
|
" -a Display absolute addresses when formatting (Block header\n"
|
|
|
|
" information is always block relative)\n"
|
|
|
|
" -b Display binary block images within a range (Option will turn\n"
|
|
|
|
" off all formatting options)\n"
|
|
|
|
" -d Display formatted block content dump (Option will turn off\n"
|
|
|
|
" all other formatting options)\n"
|
|
|
|
" -f Display formatted block content dump along with interpretation\n"
|
|
|
|
" -h Display this information\n"
|
|
|
|
" -i Display interpreted item details\n"
|
|
|
|
" -R Display specific block ranges within the file (Blocks are\n"
|
|
|
|
" indexed from 0)\n" " [startblock]: block to start at\n"
|
|
|
|
" [endblock]: block to end at\n"
|
|
|
|
" A startblock without an endblock will format the single block\n"
|
|
|
|
" -S Force block size to [blocksize]\n"
|
|
|
|
" -x Force interpreted formatting of block items as index items\n"
|
|
|
|
" -y Force interpreted formatting of block items as heap items\n\n"
|
|
|
|
"The following options are valid for control files:\n"
|
|
|
|
" -c Interpret the file listed as a control file\n"
|
|
|
|
" -f Display formatted content dump along with interpretation\n"
|
|
|
|
" -S Force block size to [blocksize]\n"
|
2011-01-18 02:28:26 +00:00
|
|
|
"\nReport bugs to <pgsql-bugs@postgresql.org>\n");
|
2011-01-18 01:33:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Iterate through the provided options and set the option flags.
|
|
|
|
// An error will result in a positive rc and will force a display
|
2012-03-12 16:08:33 +00:00
|
|
|
// of the usage information. This routine returns enum
|
2011-01-18 01:33:27 +00:00
|
|
|
// optionReturnCode values.
|
|
|
|
static unsigned int
|
|
|
|
ConsumeOptions (int numOptions, char **options)
|
|
|
|
{
|
|
|
|
unsigned int rc = OPT_RC_VALID;
|
|
|
|
unsigned int x;
|
|
|
|
unsigned int optionStringLength;
|
|
|
|
char *optionString;
|
|
|
|
char duplicateSwitch = 0x00;
|
|
|
|
|
|
|
|
for (x = 1; x < numOptions; x++)
|
|
|
|
{
|
|
|
|
optionString = options[x];
|
|
|
|
optionStringLength = strlen (optionString);
|
|
|
|
|
|
|
|
// Range is a special case where we have to consume the next 1 or 2
|
|
|
|
// parameters to mark the range start and end
|
|
|
|
if ((optionStringLength == 2) && (strcmp (optionString, "-R") == 0))
|
|
|
|
{
|
|
|
|
int range = 0;
|
|
|
|
|
|
|
|
SET_OPTION (blockOptions, BLOCK_RANGE, 'R');
|
|
|
|
// Only accept the range option once
|
|
|
|
if (rc == OPT_RC_DUPLICATE)
|
|
|
|
break;
|
|
|
|
|
|
|
|
// Make sure there are options after the range identifier
|
|
|
|
if (x >= (numOptions - 2))
|
|
|
|
{
|
|
|
|
rc = OPT_RC_INVALID;
|
|
|
|
printf ("Error: Missing range start identifier.\n");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Mark that we have the range and advance the option to what should
|
|
|
|
// be the range start. Check the value of the next parameter
|
|
|
|
optionString = options[++x];
|
|
|
|
if ((range = GetOptionValue (optionString)) < 0)
|
|
|
|
{
|
|
|
|
rc = OPT_RC_INVALID;
|
|
|
|
printf ("Error: Invalid range start identifier <%s>.\n",
|
|
|
|
optionString);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// The default is to dump only one block
|
|
|
|
blockStart = blockEnd = (unsigned int) range;
|
|
|
|
|
|
|
|
// We have our range start marker, check if there is an end
|
|
|
|
// marker on the option line. Assume that the last option
|
|
|
|
// is the file we are dumping, so check if there are options
|
|
|
|
// range start marker and the file
|
|
|
|
if (x <= (numOptions - 3))
|
|
|
|
{
|
|
|
|
if ((range = GetOptionValue (options[x + 1])) >= 0)
|
|
|
|
{
|
|
|
|
// End range must be => start range
|
|
|
|
if (blockStart <= range)
|
|
|
|
{
|
|
|
|
blockEnd = (unsigned int) range;
|
|
|
|
x++;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
rc = OPT_RC_INVALID;
|
|
|
|
printf ("Error: Requested block range start <%d> is "
|
|
|
|
"greater than end <%d>.\n", blockStart, range);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Check for the special case where the user forces a block size
|
2012-03-12 16:08:33 +00:00
|
|
|
// instead of having the tool determine it. This is useful if
|
2011-01-18 01:33:27 +00:00
|
|
|
// the header of block 0 is corrupt and gives a garbage block size
|
|
|
|
else if ((optionStringLength == 2)
|
|
|
|
&& (strcmp (optionString, "-S") == 0))
|
|
|
|
{
|
|
|
|
int localBlockSize;
|
|
|
|
|
|
|
|
SET_OPTION (blockOptions, BLOCK_FORCED, 'S');
|
|
|
|
// Only accept the forced size option once
|
|
|
|
if (rc == OPT_RC_DUPLICATE)
|
|
|
|
break;
|
|
|
|
|
|
|
|
// The token immediately following -S is the block size
|
|
|
|
if (x >= (numOptions - 2))
|
|
|
|
{
|
|
|
|
rc = OPT_RC_INVALID;
|
|
|
|
printf ("Error: Missing block size identifier.\n");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Next option encountered must be forced block size
|
|
|
|
optionString = options[++x];
|
|
|
|
if ((localBlockSize = GetOptionValue (optionString)) > 0)
|
|
|
|
blockSize = (unsigned int) localBlockSize;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
rc = OPT_RC_INVALID;
|
|
|
|
printf ("Error: Invalid block size requested <%s>.\n",
|
|
|
|
optionString);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// The last option MUST be the file name
|
|
|
|
else if (x == (numOptions - 1))
|
|
|
|
{
|
|
|
|
// Check to see if this looks like an option string before opening
|
|
|
|
if (optionString[0] != '-')
|
|
|
|
{
|
|
|
|
fp = fopen (optionString, "rb");
|
|
|
|
if (fp)
|
|
|
|
fileName = options[x];
|
|
|
|
else
|
|
|
|
{
|
|
|
|
rc = OPT_RC_FILE;
|
|
|
|
printf ("Error: Could not open file <%s>.\n", optionString);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2012-03-12 16:08:33 +00:00
|
|
|
// Could be the case where the help flag is used without a
|
|
|
|
// filename. Otherwise, the last option isn't a file
|
2011-01-18 01:33:27 +00:00
|
|
|
if (strcmp (optionString, "-h") == 0)
|
|
|
|
rc = OPT_RC_COPYRIGHT;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
rc = OPT_RC_FILE;
|
|
|
|
printf ("Error: Missing file name to dump.\n");
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
unsigned int y;
|
|
|
|
|
|
|
|
// Option strings must start with '-' and contain switches
|
|
|
|
if (optionString[0] != '-')
|
|
|
|
{
|
|
|
|
rc = OPT_RC_INVALID;
|
|
|
|
printf ("Error: Invalid option string <%s>.\n", optionString);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Iterate through the singular option string, throw out
|
|
|
|
// garbage, duplicates and set flags to be used in formatting
|
|
|
|
for (y = 1; y < optionStringLength; y++)
|
|
|
|
{
|
|
|
|
switch (optionString[y])
|
|
|
|
{
|
2012-03-12 16:08:33 +00:00
|
|
|
// Use absolute addressing
|
2011-01-18 01:33:27 +00:00
|
|
|
case 'a':
|
|
|
|
SET_OPTION (blockOptions, BLOCK_ABSOLUTE, 'a');
|
|
|
|
break;
|
|
|
|
|
2012-03-12 16:08:33 +00:00
|
|
|
// Dump the binary contents of the page
|
2011-01-18 01:33:27 +00:00
|
|
|
case 'b':
|
|
|
|
SET_OPTION (blockOptions, BLOCK_BINARY, 'b');
|
|
|
|
break;
|
|
|
|
|
|
|
|
// Dump the listed file as a control file
|
|
|
|
case 'c':
|
|
|
|
SET_OPTION (controlOptions, CONTROL_DUMP, 'c');
|
|
|
|
break;
|
|
|
|
|
|
|
|
// Do not interpret the data. Format to hex and ascii.
|
|
|
|
case 'd':
|
|
|
|
SET_OPTION (blockOptions, BLOCK_NO_INTR, 'd');
|
|
|
|
break;
|
|
|
|
|
|
|
|
// Format the contents of the block with interpretation
|
|
|
|
// of the headers
|
|
|
|
case 'f':
|
|
|
|
SET_OPTION (blockOptions, BLOCK_FORMAT, 'f');
|
|
|
|
break;
|
|
|
|
|
2012-03-12 16:08:33 +00:00
|
|
|
// Display the usage screen
|
2011-01-18 01:33:27 +00:00
|
|
|
case 'h':
|
|
|
|
rc = OPT_RC_COPYRIGHT;
|
|
|
|
break;
|
|
|
|
|
|
|
|
// Format the items in detail
|
|
|
|
case 'i':
|
|
|
|
SET_OPTION (itemOptions, ITEM_DETAIL, 'i');
|
|
|
|
break;
|
|
|
|
|
2011-12-18 00:11:29 +00:00
|
|
|
// Interpret items as standard index values
|
2011-01-18 01:33:27 +00:00
|
|
|
case 'x':
|
|
|
|
SET_OPTION (itemOptions, ITEM_INDEX, 'x');
|
|
|
|
if (itemOptions & ITEM_HEAP)
|
|
|
|
{
|
|
|
|
rc = OPT_RC_INVALID;
|
|
|
|
printf ("Error: Options <y> and <x> are "
|
|
|
|
"mutually exclusive.\n");
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
// Interpret items as heap values
|
|
|
|
case 'y':
|
|
|
|
SET_OPTION (itemOptions, ITEM_HEAP, 'y');
|
|
|
|
if (itemOptions & ITEM_INDEX)
|
|
|
|
{
|
|
|
|
rc = OPT_RC_INVALID;
|
|
|
|
printf ("Error: Options <x> and <y> are "
|
|
|
|
"mutually exclusive.\n");
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
rc = OPT_RC_INVALID;
|
|
|
|
printf ("Error: Unknown option <%c>.\n", optionString[y]);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (rc)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (rc == OPT_RC_DUPLICATE)
|
|
|
|
printf ("Error: Duplicate option listed <%c>.\n", duplicateSwitch);
|
|
|
|
|
|
|
|
// If the user requested a control file dump, a pure binary
|
|
|
|
// block dump or a non-interpreted formatted dump, mask off
|
2012-03-12 16:08:33 +00:00
|
|
|
// all other block level options (with a few exceptions)
|
2011-01-18 01:33:27 +00:00
|
|
|
if (rc == OPT_RC_VALID)
|
|
|
|
{
|
|
|
|
// The user has requested a control file dump, only -f and
|
|
|
|
// -S are valid... turn off all other formatting
|
|
|
|
if (controlOptions & CONTROL_DUMP)
|
|
|
|
{
|
|
|
|
if ((blockOptions & ~(BLOCK_FORMAT | BLOCK_FORCED))
|
|
|
|
|| (itemOptions))
|
|
|
|
{
|
|
|
|
rc = OPT_RC_INVALID;
|
|
|
|
printf ("Error: Invalid options used for Control File dump.\n"
|
|
|
|
" Only options <Sf> may be used with <c>.\n");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
controlOptions |=
|
|
|
|
(blockOptions & (BLOCK_FORMAT | BLOCK_FORCED));
|
|
|
|
blockOptions = itemOptions = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// The user has requested a binary block dump... only -R and
|
|
|
|
// -f are honoured
|
|
|
|
else if (blockOptions & BLOCK_BINARY)
|
|
|
|
{
|
|
|
|
blockOptions &= (BLOCK_BINARY | BLOCK_RANGE | BLOCK_FORCED);
|
|
|
|
itemOptions = 0;
|
|
|
|
}
|
|
|
|
// The user has requested a non-interpreted dump... only -a,
|
|
|
|
// -R and -f are honoured
|
|
|
|
else if (blockOptions & BLOCK_NO_INTR)
|
|
|
|
{
|
|
|
|
blockOptions &=
|
|
|
|
(BLOCK_NO_INTR | BLOCK_ABSOLUTE | BLOCK_RANGE | BLOCK_FORCED);
|
|
|
|
itemOptions = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return (rc);
|
|
|
|
}
|
|
|
|
|
2012-03-12 16:08:33 +00:00
|
|
|
// Given the index into the parameter list, convert and return the
|
|
|
|
// current string to a number if possible
|
2011-01-18 01:33:27 +00:00
|
|
|
static int
|
|
|
|
GetOptionValue (char *optionString)
|
|
|
|
{
|
|
|
|
unsigned int x;
|
|
|
|
int value = -1;
|
|
|
|
int optionStringLength = strlen (optionString);
|
|
|
|
|
|
|
|
// Verify the next option looks like a number
|
|
|
|
for (x = 0; x < optionStringLength; x++)
|
|
|
|
if (!isdigit ((int) optionString[x]))
|
|
|
|
break;
|
|
|
|
|
|
|
|
// Convert the string to a number if it looks good
|
|
|
|
if (x == optionStringLength)
|
|
|
|
value = atoi (optionString);
|
|
|
|
|
|
|
|
return (value);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read the page header off of block 0 to determine the block size
|
|
|
|
// used in this file. Can be overridden using the -S option. The
|
|
|
|
// returned value is the block size of block 0 on disk
|
|
|
|
static unsigned int
|
|
|
|
GetBlockSize ()
|
|
|
|
{
|
|
|
|
unsigned int pageHeaderSize = sizeof (PageHeaderData);
|
|
|
|
unsigned int localSize = 0;
|
|
|
|
int bytesRead = 0;
|
|
|
|
char localCache[pageHeaderSize];
|
|
|
|
|
|
|
|
// Read the first header off of block 0 to determine the block size
|
|
|
|
bytesRead = fread (&localCache, 1, pageHeaderSize, fp);
|
|
|
|
rewind (fp);
|
|
|
|
|
|
|
|
if (bytesRead == pageHeaderSize)
|
|
|
|
localSize = (unsigned int) PageGetPageSize (&localCache);
|
|
|
|
else
|
|
|
|
printf ("Error: Unable to read full page header from block 0.\n"
|
|
|
|
" ===> Read %u bytes", bytesRead);
|
|
|
|
return (localSize);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Determine the contents of the special section on the block and
|
|
|
|
// return this enum value
|
|
|
|
static unsigned int
|
|
|
|
GetSpecialSectionType (Page page)
|
|
|
|
{
|
|
|
|
unsigned int rc;
|
|
|
|
unsigned int specialOffset;
|
|
|
|
unsigned int specialSize;
|
|
|
|
unsigned int specialValue;
|
|
|
|
PageHeader pageHeader = (PageHeader) page;
|
|
|
|
|
2012-03-12 16:08:33 +00:00
|
|
|
// If this is not a partial header, check the validity of the
|
2011-01-18 01:33:27 +00:00
|
|
|
// special section offset and contents
|
|
|
|
if (bytesToFormat > sizeof (PageHeaderData))
|
|
|
|
{
|
|
|
|
specialOffset = (unsigned int) pageHeader->pd_special;
|
|
|
|
|
|
|
|
// Check that the special offset can remain on the block or
|
|
|
|
// the partial block
|
|
|
|
if ((specialOffset == 0) ||
|
|
|
|
(specialOffset > blockSize) || (specialOffset > bytesToFormat))
|
|
|
|
rc = SPEC_SECT_ERROR_BOUNDARY;
|
|
|
|
else
|
|
|
|
{
|
2011-12-18 00:11:29 +00:00
|
|
|
// we may need to examine last 2 bytes of page to identify index
|
|
|
|
uint16 *ptype = (uint16 *) (buffer + blockSize - sizeof(uint16));
|
|
|
|
|
2011-01-18 01:33:27 +00:00
|
|
|
specialSize = blockSize - specialOffset;
|
|
|
|
|
2012-03-12 16:08:33 +00:00
|
|
|
// If there is a special section, use its size to guess its
|
2011-12-18 00:11:29 +00:00
|
|
|
// contents, checking the last 2 bytes of the page in cases
|
|
|
|
// that are ambiguous. Note we don't attempt to dereference
|
|
|
|
// the pointers without checking bytesToFormat == blockSize.
|
2011-01-18 01:33:27 +00:00
|
|
|
if (specialSize == 0)
|
|
|
|
rc = SPEC_SECT_NONE;
|
|
|
|
else if (specialSize == MAXALIGN (sizeof (uint32)))
|
|
|
|
{
|
2011-12-18 00:11:29 +00:00
|
|
|
// If MAXALIGN is 8, this could be either a sequence or
|
|
|
|
// SP-GiST or GIN.
|
2011-01-18 01:33:27 +00:00
|
|
|
if (bytesToFormat == blockSize)
|
|
|
|
{
|
|
|
|
specialValue = *((int *) (buffer + specialOffset));
|
|
|
|
if (specialValue == SEQUENCE_MAGIC)
|
|
|
|
rc = SPEC_SECT_SEQUENCE;
|
2011-12-18 00:11:29 +00:00
|
|
|
else if (specialSize == MAXALIGN (sizeof (SpGistPageOpaqueData)) &&
|
|
|
|
*ptype == SPGIST_PAGE_ID)
|
|
|
|
rc = SPEC_SECT_INDEX_SPGIST;
|
2011-01-18 01:33:27 +00:00
|
|
|
else if (specialSize == MAXALIGN (sizeof (GinPageOpaqueData)))
|
|
|
|
rc = SPEC_SECT_INDEX_GIN;
|
|
|
|
else
|
|
|
|
rc = SPEC_SECT_ERROR_UNKNOWN;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
rc = SPEC_SECT_ERROR_UNKNOWN;
|
|
|
|
}
|
2011-12-18 00:11:29 +00:00
|
|
|
// SP-GiST and GIN have same size special section, so check
|
|
|
|
// the page ID bytes first.
|
|
|
|
else if (specialSize == MAXALIGN (sizeof (SpGistPageOpaqueData)) &&
|
|
|
|
bytesToFormat == blockSize &&
|
|
|
|
*ptype == SPGIST_PAGE_ID)
|
|
|
|
rc = SPEC_SECT_INDEX_SPGIST;
|
2011-01-18 01:33:27 +00:00
|
|
|
else if (specialSize == MAXALIGN (sizeof (GinPageOpaqueData)))
|
|
|
|
rc = SPEC_SECT_INDEX_GIN;
|
|
|
|
else if (specialSize > 2 && bytesToFormat == blockSize)
|
|
|
|
{
|
|
|
|
// As of 8.3, BTree, Hash, and GIST all have the same size
|
|
|
|
// special section, but the last two bytes of the section
|
|
|
|
// can be checked to determine what's what.
|
2011-12-18 00:11:29 +00:00
|
|
|
if (*ptype <= MAX_BT_CYCLE_ID &&
|
2011-01-18 01:33:27 +00:00
|
|
|
specialSize == MAXALIGN (sizeof (BTPageOpaqueData)))
|
|
|
|
rc = SPEC_SECT_INDEX_BTREE;
|
2011-12-18 00:11:29 +00:00
|
|
|
else if (*ptype == HASHO_PAGE_ID &&
|
2011-01-18 01:33:27 +00:00
|
|
|
specialSize == MAXALIGN (sizeof (HashPageOpaqueData)))
|
|
|
|
rc = SPEC_SECT_INDEX_HASH;
|
2011-12-18 00:11:29 +00:00
|
|
|
else if (*ptype == GIST_PAGE_ID &&
|
2011-01-18 01:33:27 +00:00
|
|
|
specialSize == MAXALIGN (sizeof (GISTPageOpaqueData)))
|
|
|
|
rc = SPEC_SECT_INDEX_GIST;
|
|
|
|
else
|
|
|
|
rc = SPEC_SECT_ERROR_UNKNOWN;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
rc = SPEC_SECT_ERROR_UNKNOWN;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
rc = SPEC_SECT_ERROR_UNKNOWN;
|
|
|
|
|
|
|
|
return (rc);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check whether page is a btree meta page
|
|
|
|
static bool
|
|
|
|
IsBtreeMetaPage(Page page)
|
|
|
|
{
|
|
|
|
PageHeader pageHeader = (PageHeader) page;
|
|
|
|
|
|
|
|
if ((PageGetSpecialSize (page) == (MAXALIGN (sizeof (BTPageOpaqueData))))
|
|
|
|
&& (bytesToFormat == blockSize))
|
|
|
|
{
|
|
|
|
BTPageOpaque btpo =
|
|
|
|
(BTPageOpaque) ((char *) page + pageHeader->pd_special);
|
|
|
|
|
|
|
|
// Must check the cycleid to be sure it's really btree.
|
|
|
|
if ((btpo->btpo_cycleid <= MAX_BT_CYCLE_ID) &&
|
|
|
|
(btpo->btpo_flags & BTP_META))
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Display a header for the dump so we know the file name, the options
|
|
|
|
// used and the time the dump was taken
|
|
|
|
static void
|
|
|
|
CreateDumpFileHeader (int numOptions, char **options)
|
|
|
|
{
|
|
|
|
unsigned int x;
|
|
|
|
char optionBuffer[52] = "\0";
|
|
|
|
time_t rightNow = time (NULL);
|
|
|
|
|
|
|
|
// Iterate through the options and cache them.
|
2012-03-12 16:08:33 +00:00
|
|
|
// The maximum we can display is 50 option characters + spaces.
|
2011-01-18 01:33:27 +00:00
|
|
|
for (x = 1; x < (numOptions - 1); x++)
|
|
|
|
{
|
|
|
|
if ((strlen (optionBuffer) + strlen (options[x])) > 50)
|
|
|
|
break;
|
|
|
|
strcat (optionBuffer, options[x]);
|
|
|
|
strcat (optionBuffer, " ");
|
|
|
|
}
|
|
|
|
|
|
|
|
printf
|
|
|
|
("\n*******************************************************************\n"
|
2011-01-18 02:28:26 +00:00
|
|
|
"* PostgreSQL File/Block Formatted Dump Utility - Version %s\n"
|
|
|
|
"*\n"
|
2011-01-18 01:33:27 +00:00
|
|
|
"* File: %s\n"
|
|
|
|
"* Options used: %s\n*\n"
|
|
|
|
"* Dump created on: %s"
|
|
|
|
"*******************************************************************\n",
|
2011-01-18 02:28:26 +00:00
|
|
|
FD_VERSION, fileName, (strlen (optionBuffer)) ? optionBuffer : "None",
|
2011-01-18 01:33:27 +00:00
|
|
|
ctime (&rightNow));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Dump out a formatted block header for the requested block
|
|
|
|
static int
|
|
|
|
FormatHeader (Page page)
|
|
|
|
{
|
|
|
|
int rc = 0;
|
|
|
|
unsigned int headerBytes;
|
|
|
|
PageHeader pageHeader = (PageHeader) page;
|
|
|
|
|
|
|
|
printf ("<Header> -----\n");
|
|
|
|
|
|
|
|
// Only attempt to format the header if the entire header (minus the item
|
|
|
|
// array) is available
|
|
|
|
if (bytesToFormat < offsetof (PageHeaderData, pd_linp[0]))
|
|
|
|
{
|
|
|
|
headerBytes = bytesToFormat;
|
|
|
|
rc = EOF_ENCOUNTERED;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
XLogRecPtr pageLSN = PageGetLSN (page);
|
|
|
|
int maxOffset = PageGetMaxOffsetNumber (page);
|
|
|
|
char flagString[100];
|
|
|
|
|
|
|
|
headerBytes = offsetof (PageHeaderData, pd_linp[0]);
|
|
|
|
blockVersion = (unsigned int) PageGetPageLayoutVersion (page);
|
|
|
|
|
|
|
|
// The full header exists but we have to check that the item array
|
|
|
|
// is available or how far we can index into it
|
|
|
|
if (maxOffset > 0)
|
|
|
|
{
|
|
|
|
unsigned int itemsLength = maxOffset * sizeof (ItemIdData);
|
|
|
|
if (bytesToFormat < (headerBytes + itemsLength))
|
|
|
|
{
|
|
|
|
headerBytes = bytesToFormat;
|
|
|
|
rc = EOF_ENCOUNTERED;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
headerBytes += itemsLength;
|
|
|
|
}
|
|
|
|
|
|
|
|
flagString[0] = '\0';
|
|
|
|
if (pageHeader->pd_flags & PD_HAS_FREE_LINES)
|
|
|
|
strcat (flagString, "HAS_FREE_LINES|");
|
|
|
|
if (pageHeader->pd_flags & PD_PAGE_FULL)
|
|
|
|
strcat (flagString, "PAGE_FULL|");
|
2011-11-26 22:14:08 +00:00
|
|
|
if (pageHeader->pd_flags & PD_ALL_VISIBLE)
|
|
|
|
strcat (flagString, "ALL_VISIBLE|");
|
2011-01-18 01:33:27 +00:00
|
|
|
if (strlen (flagString))
|
|
|
|
flagString[strlen (flagString) - 1] = '\0';
|
|
|
|
|
|
|
|
// Interpret the content of the header
|
|
|
|
printf
|
|
|
|
(" Block Offset: 0x%08x Offsets: Lower %4u (0x%04hx)\n"
|
|
|
|
" Block: Size %4d Version %4u Upper %4u (0x%04hx)\n"
|
|
|
|
" LSN: logid %6d recoff 0x%08x Special %4u (0x%04hx)\n"
|
|
|
|
" Items: %4d Free Space: %4u\n"
|
2013-06-06 18:33:16 +00:00
|
|
|
" Checksum: 0x%04x Prune XID: 0x%08x Flags: 0x%04x (%s)\n"
|
2011-01-18 01:33:27 +00:00
|
|
|
" Length (including item array): %u\n\n",
|
|
|
|
pageOffset, pageHeader->pd_lower, pageHeader->pd_lower,
|
|
|
|
(int) PageGetPageSize (page), blockVersion,
|
|
|
|
pageHeader->pd_upper, pageHeader->pd_upper,
|
2013-06-06 18:33:16 +00:00
|
|
|
(uint32) (pageLSN >> 32), (uint32) pageLSN,
|
2011-01-18 01:33:27 +00:00
|
|
|
pageHeader->pd_special, pageHeader->pd_special,
|
|
|
|
maxOffset, pageHeader->pd_upper - pageHeader->pd_lower,
|
2013-06-06 18:33:16 +00:00
|
|
|
pageHeader->pd_checksum, pageHeader->pd_prune_xid,
|
2011-01-18 01:33:27 +00:00
|
|
|
pageHeader->pd_flags, flagString,
|
|
|
|
headerBytes);
|
|
|
|
|
|
|
|
// If it's a btree meta page, print the contents of the meta block.
|
|
|
|
if (IsBtreeMetaPage(page))
|
|
|
|
{
|
|
|
|
BTMetaPageData *btpMeta = BTPageGetMeta (buffer);
|
|
|
|
printf (" BTree Meta Data: Magic (0x%08x) Version (%u)\n"
|
|
|
|
" Root: Block (%u) Level (%u)\n"
|
|
|
|
" FastRoot: Block (%u) Level (%u)\n\n",
|
|
|
|
btpMeta->btm_magic, btpMeta->btm_version,
|
|
|
|
btpMeta->btm_root, btpMeta->btm_level,
|
|
|
|
btpMeta->btm_fastroot, btpMeta->btm_fastlevel);
|
|
|
|
headerBytes += sizeof (BTMetaPageData);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Eye the contents of the header and alert the user to possible
|
|
|
|
// problems.
|
|
|
|
if ((maxOffset < 0) ||
|
|
|
|
(maxOffset > blockSize) ||
|
|
|
|
(blockVersion != PG_PAGE_LAYOUT_VERSION) || /* only one we support */
|
|
|
|
(pageHeader->pd_upper > blockSize) ||
|
|
|
|
(pageHeader->pd_upper > pageHeader->pd_special) ||
|
|
|
|
(pageHeader->pd_lower <
|
|
|
|
(sizeof (PageHeaderData) - sizeof (ItemIdData)))
|
|
|
|
|| (pageHeader->pd_lower > blockSize)
|
|
|
|
|| (pageHeader->pd_upper < pageHeader->pd_lower)
|
|
|
|
|| (pageHeader->pd_special > blockSize))
|
|
|
|
printf (" Error: Invalid header information.\n\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we have reached the end of file while interpreting the header, let
|
|
|
|
// the user know about it
|
|
|
|
if (rc == EOF_ENCOUNTERED)
|
|
|
|
printf
|
|
|
|
(" Error: End of block encountered within the header."
|
|
|
|
" Bytes read: %4u.\n\n", bytesToFormat);
|
|
|
|
|
2012-03-12 16:08:33 +00:00
|
|
|
// A request to dump the formatted binary of the block (header,
|
2011-01-18 01:33:27 +00:00
|
|
|
// items and special section). It's best to dump even on an error
|
|
|
|
// so the user can see the raw image.
|
|
|
|
if (blockOptions & BLOCK_FORMAT)
|
|
|
|
FormatBinary (headerBytes, 0);
|
|
|
|
|
|
|
|
return (rc);
|
|
|
|
}
|
|
|
|
|
2012-03-12 16:08:33 +00:00
|
|
|
// Dump out formatted items that reside on this block
|
2011-01-18 01:33:27 +00:00
|
|
|
static void
|
|
|
|
FormatItemBlock (Page page)
|
|
|
|
{
|
|
|
|
unsigned int x;
|
|
|
|
unsigned int itemSize;
|
|
|
|
unsigned int itemOffset;
|
|
|
|
unsigned int itemFlags;
|
|
|
|
ItemId itemId;
|
|
|
|
int maxOffset = PageGetMaxOffsetNumber (page);
|
|
|
|
|
|
|
|
// If it's a btree meta page, the meta block is where items would normally
|
|
|
|
// be; don't print garbage.
|
|
|
|
if (IsBtreeMetaPage(page))
|
|
|
|
return;
|
|
|
|
|
|
|
|
printf ("<Data> ------ \n");
|
|
|
|
|
|
|
|
// Loop through the items on the block. Check if the block is
|
2012-03-12 16:08:33 +00:00
|
|
|
// empty and has a sensible item array listed before running
|
|
|
|
// through each item
|
2011-01-18 01:33:27 +00:00
|
|
|
if (maxOffset == 0)
|
|
|
|
printf (" Empty block - no items listed \n\n");
|
|
|
|
else if ((maxOffset < 0) || (maxOffset > blockSize))
|
|
|
|
printf (" Error: Item index corrupt on block. Offset: <%d>.\n\n",
|
|
|
|
maxOffset);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
int formatAs;
|
|
|
|
char textFlags[16];
|
|
|
|
|
2012-03-12 16:08:33 +00:00
|
|
|
// First, honour requests to format items a special way, then
|
2011-01-18 01:33:27 +00:00
|
|
|
// use the special section to determine the format style
|
|
|
|
if (itemOptions & ITEM_INDEX)
|
|
|
|
formatAs = ITEM_INDEX;
|
|
|
|
else if (itemOptions & ITEM_HEAP)
|
|
|
|
formatAs = ITEM_HEAP;
|
|
|
|
else
|
2011-12-18 00:11:29 +00:00
|
|
|
switch (specialType)
|
|
|
|
{
|
|
|
|
case SPEC_SECT_INDEX_BTREE:
|
|
|
|
case SPEC_SECT_INDEX_HASH:
|
|
|
|
case SPEC_SECT_INDEX_GIST:
|
|
|
|
case SPEC_SECT_INDEX_GIN:
|
|
|
|
formatAs = ITEM_INDEX;
|
|
|
|
break;
|
|
|
|
case SPEC_SECT_INDEX_SPGIST:
|
|
|
|
{
|
|
|
|
SpGistPageOpaque spgpo =
|
|
|
|
(SpGistPageOpaque) ((char *) page +
|
|
|
|
((PageHeader) page)->pd_special);
|
|
|
|
if (spgpo->flags & SPGIST_LEAF)
|
|
|
|
formatAs = ITEM_SPG_LEAF;
|
|
|
|
else
|
|
|
|
formatAs = ITEM_SPG_INNER;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
formatAs = ITEM_HEAP;
|
|
|
|
break;
|
|
|
|
}
|
2011-01-18 01:33:27 +00:00
|
|
|
|
|
|
|
for (x = 1; x < (maxOffset + 1); x++)
|
|
|
|
{
|
|
|
|
itemId = PageGetItemId (page, x);
|
|
|
|
itemFlags = (unsigned int) ItemIdGetFlags (itemId);
|
|
|
|
itemSize = (unsigned int) ItemIdGetLength (itemId);
|
|
|
|
itemOffset = (unsigned int) ItemIdGetOffset (itemId);
|
|
|
|
|
|
|
|
switch (itemFlags)
|
|
|
|
{
|
|
|
|
case LP_UNUSED:
|
|
|
|
strcpy (textFlags, "UNUSED");
|
|
|
|
break;
|
|
|
|
case LP_NORMAL:
|
|
|
|
strcpy (textFlags, "NORMAL");
|
|
|
|
break;
|
|
|
|
case LP_REDIRECT:
|
|
|
|
strcpy (textFlags, "REDIRECT");
|
|
|
|
break;
|
|
|
|
case LP_DEAD:
|
|
|
|
strcpy (textFlags, "DEAD");
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
// shouldn't be possible
|
|
|
|
sprintf (textFlags, "0x%02x", itemFlags);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
printf (" Item %3u -- Length: %4u Offset: %4u (0x%04x)"
|
|
|
|
" Flags: %s\n", x, itemSize, itemOffset, itemOffset,
|
|
|
|
textFlags);
|
|
|
|
|
|
|
|
// Make sure the item can physically fit on this block before
|
|
|
|
// formatting
|
|
|
|
if ((itemOffset + itemSize > blockSize) ||
|
|
|
|
(itemOffset + itemSize > bytesToFormat))
|
|
|
|
printf (" Error: Item contents extend beyond block.\n"
|
|
|
|
" BlockSize<%d> Bytes Read<%d> Item Start<%d>.\n",
|
|
|
|
blockSize, bytesToFormat, itemOffset + itemSize);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// If the user requests that the items be interpreted as
|
2012-03-12 16:08:33 +00:00
|
|
|
// heap or index items...
|
2011-01-18 01:33:27 +00:00
|
|
|
if (itemOptions & ITEM_DETAIL)
|
|
|
|
FormatItem (itemSize, itemOffset, formatAs);
|
|
|
|
|
2012-03-12 16:08:33 +00:00
|
|
|
// Dump the items contents in hex and ascii
|
2011-01-18 01:33:27 +00:00
|
|
|
if (blockOptions & BLOCK_FORMAT)
|
|
|
|
FormatBinary (itemSize, itemOffset);
|
|
|
|
|
|
|
|
if (x == maxOffset)
|
|
|
|
printf ("\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Interpret the contents of the item based on whether it has a special
|
|
|
|
// section and/or the user has hinted
|
|
|
|
static void
|
|
|
|
FormatItem (unsigned int numBytes, unsigned int startIndex,
|
|
|
|
unsigned int formatAs)
|
|
|
|
{
|
2011-12-18 00:11:29 +00:00
|
|
|
static const char * const spgist_tupstates[4] = {
|
|
|
|
"LIVE",
|
|
|
|
"REDIRECT",
|
|
|
|
"DEAD",
|
|
|
|
"PLACEHOLDER"
|
|
|
|
};
|
|
|
|
|
2011-01-18 01:33:27 +00:00
|
|
|
if (formatAs == ITEM_INDEX)
|
|
|
|
{
|
2011-12-18 00:11:29 +00:00
|
|
|
// It is an IndexTuple item, so dump the index header
|
2011-01-18 01:33:27 +00:00
|
|
|
if (numBytes < SizeOfIptrData)
|
|
|
|
{
|
|
|
|
if (numBytes)
|
|
|
|
printf (" Error: This item does not look like an index item.\n");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
IndexTuple itup = (IndexTuple) (&(buffer[startIndex]));
|
|
|
|
printf (" Block Id: %u linp Index: %u Size: %d\n"
|
|
|
|
" Has Nulls: %u Has Varwidths: %u\n\n",
|
|
|
|
((uint32) ((itup->t_tid.ip_blkid.bi_hi << 16) |
|
|
|
|
(uint16) itup->t_tid.ip_blkid.bi_lo)),
|
2011-01-18 02:36:38 +00:00
|
|
|
itup->t_tid.ip_posid,
|
|
|
|
(int) IndexTupleSize(itup),
|
|
|
|
IndexTupleHasNulls(itup) ? 1 : 0,
|
|
|
|
IndexTupleHasVarwidths(itup) ? 1 : 0);
|
2011-01-18 01:33:27 +00:00
|
|
|
|
|
|
|
if (numBytes != IndexTupleSize (itup))
|
|
|
|
printf (" Error: Item size difference. Given <%u>, "
|
|
|
|
"Internal <%d>.\n", numBytes, (int) IndexTupleSize (itup));
|
|
|
|
}
|
|
|
|
}
|
2011-12-18 00:11:29 +00:00
|
|
|
else if (formatAs == ITEM_SPG_INNER)
|
|
|
|
{
|
|
|
|
// It is an SpGistInnerTuple item, so dump the index header
|
|
|
|
if (numBytes < SGITHDRSZ)
|
|
|
|
{
|
|
|
|
if (numBytes)
|
|
|
|
printf (" Error: This item does not look like an SPGiST item.\n");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
SpGistInnerTuple itup = (SpGistInnerTuple) (&(buffer[startIndex]));
|
|
|
|
printf (" State: %s allTheSame: %d nNodes: %u prefixSize: %u\n\n",
|
|
|
|
spgist_tupstates[itup->tupstate],
|
|
|
|
itup->allTheSame,
|
|
|
|
itup->nNodes,
|
|
|
|
itup->prefixSize);
|
|
|
|
|
|
|
|
if (numBytes != itup->size)
|
|
|
|
printf (" Error: Item size difference. Given <%u>, "
|
|
|
|
"Internal <%d>.\n", numBytes, (int) itup->size);
|
|
|
|
else if (itup->prefixSize == MAXALIGN(itup->prefixSize))
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
SpGistNodeTuple node;
|
|
|
|
|
|
|
|
// Dump the prefix contents in hex and ascii
|
|
|
|
if ((blockOptions & BLOCK_FORMAT) &&
|
|
|
|
SGITHDRSZ + itup->prefixSize <= numBytes)
|
|
|
|
FormatBinary (SGITHDRSZ + itup->prefixSize, startIndex);
|
|
|
|
|
|
|
|
// Try to print the nodes, but only while pointer is sane
|
|
|
|
SGITITERATE(itup, i, node)
|
|
|
|
{
|
|
|
|
int off = (char *) node - (char *) itup;
|
|
|
|
if (off + SGNTHDRSZ > numBytes)
|
|
|
|
break;
|
|
|
|
printf (" Node %2u: Downlink: %u/%u Size: %d Null: %u\n",
|
|
|
|
i,
|
|
|
|
((uint32) ((node->t_tid.ip_blkid.bi_hi << 16) |
|
|
|
|
(uint16) node->t_tid.ip_blkid.bi_lo)),
|
|
|
|
node->t_tid.ip_posid,
|
|
|
|
(int) IndexTupleSize(node),
|
|
|
|
IndexTupleHasNulls(node) ? 1 : 0);
|
|
|
|
// Dump the node's contents in hex and ascii
|
|
|
|
if ((blockOptions & BLOCK_FORMAT) &&
|
|
|
|
off + IndexTupleSize(node) <= numBytes)
|
|
|
|
FormatBinary (IndexTupleSize(node), startIndex + off);
|
|
|
|
if (IndexTupleSize(node) != MAXALIGN(IndexTupleSize(node)))
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
printf ("\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (formatAs == ITEM_SPG_LEAF)
|
|
|
|
{
|
|
|
|
// It is an SpGistLeafTuple item, so dump the index header
|
|
|
|
if (numBytes < SGLTHDRSZ)
|
|
|
|
{
|
|
|
|
if (numBytes)
|
|
|
|
printf (" Error: This item does not look like an SPGiST item.\n");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
SpGistLeafTuple itup = (SpGistLeafTuple) (&(buffer[startIndex]));
|
|
|
|
printf (" State: %s nextOffset: %u Block Id: %u linp Index: %u\n\n",
|
|
|
|
spgist_tupstates[itup->tupstate],
|
|
|
|
itup->nextOffset,
|
|
|
|
((uint32) ((itup->heapPtr.ip_blkid.bi_hi << 16) |
|
|
|
|
(uint16) itup->heapPtr.ip_blkid.bi_lo)),
|
|
|
|
itup->heapPtr.ip_posid);
|
|
|
|
|
|
|
|
if (numBytes != itup->size)
|
|
|
|
printf (" Error: Item size difference. Given <%u>, "
|
|
|
|
"Internal <%d>.\n", numBytes, (int) itup->size);
|
|
|
|
}
|
|
|
|
}
|
2011-01-18 01:33:27 +00:00
|
|
|
else
|
|
|
|
{
|
2011-12-18 00:11:29 +00:00
|
|
|
// It is a HeapTuple item, so dump the heap header
|
2011-01-18 01:33:27 +00:00
|
|
|
int alignedSize = MAXALIGN (sizeof (HeapTupleHeaderData));
|
|
|
|
|
|
|
|
if (numBytes < alignedSize)
|
|
|
|
{
|
|
|
|
if (numBytes)
|
|
|
|
printf (" Error: This item does not look like a heap item.\n");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
char flagString[256];
|
|
|
|
unsigned int x;
|
|
|
|
unsigned int bitmapLength = 0;
|
|
|
|
unsigned int oidLength = 0;
|
|
|
|
unsigned int computedLength;
|
|
|
|
unsigned int infoMask;
|
|
|
|
unsigned int infoMask2;
|
|
|
|
int localNatts;
|
|
|
|
unsigned int localHoff;
|
|
|
|
bits8 *localBits;
|
|
|
|
unsigned int localBitOffset;
|
|
|
|
|
|
|
|
HeapTupleHeader htup = (HeapTupleHeader) (&buffer[startIndex]);
|
|
|
|
|
|
|
|
infoMask = htup->t_infomask;
|
|
|
|
infoMask2 = htup->t_infomask2;
|
|
|
|
localBits = &(htup->t_bits[0]);
|
|
|
|
localNatts = HeapTupleHeaderGetNatts(htup);
|
|
|
|
localHoff = htup->t_hoff;
|
|
|
|
localBitOffset = offsetof (HeapTupleHeaderData, t_bits);
|
|
|
|
|
|
|
|
printf (" XMIN: %u XMAX: %u CID|XVAC: %u",
|
|
|
|
HeapTupleHeaderGetXmin(htup),
|
2013-06-06 18:33:16 +00:00
|
|
|
HeapTupleHeaderGetRawXmax(htup),
|
2011-01-18 01:33:27 +00:00
|
|
|
HeapTupleHeaderGetRawCommandId(htup));
|
|
|
|
|
|
|
|
if (infoMask & HEAP_HASOID)
|
|
|
|
printf (" OID: %u",
|
|
|
|
HeapTupleHeaderGetOid(htup));
|
|
|
|
|
|
|
|
printf ("\n"
|
|
|
|
" Block Id: %u linp Index: %u Attributes: %d Size: %d\n",
|
|
|
|
((uint32)
|
|
|
|
((htup->t_ctid.ip_blkid.bi_hi << 16) | (uint16) htup->
|
|
|
|
t_ctid.ip_blkid.bi_lo)), htup->t_ctid.ip_posid,
|
|
|
|
localNatts, htup->t_hoff);
|
|
|
|
|
2012-03-12 16:08:33 +00:00
|
|
|
// Place readable versions of the tuple info mask into a buffer.
|
2011-01-18 01:33:27 +00:00
|
|
|
// Assume that the string can not expand beyond 256.
|
|
|
|
flagString[0] = '\0';
|
|
|
|
if (infoMask & HEAP_HASNULL)
|
|
|
|
strcat (flagString, "HASNULL|");
|
|
|
|
if (infoMask & HEAP_HASVARWIDTH)
|
|
|
|
strcat (flagString, "HASVARWIDTH|");
|
|
|
|
if (infoMask & HEAP_HASEXTERNAL)
|
|
|
|
strcat (flagString, "HASEXTERNAL|");
|
|
|
|
if (infoMask & HEAP_HASOID)
|
|
|
|
strcat (flagString, "HASOID|");
|
2013-06-06 18:33:16 +00:00
|
|
|
if (infoMask & HEAP_XMAX_KEYSHR_LOCK)
|
|
|
|
strcat (flagString, "XMAX_KEYSHR_LOCK|");
|
2011-01-18 01:33:27 +00:00
|
|
|
if (infoMask & HEAP_COMBOCID)
|
|
|
|
strcat (flagString, "COMBOCID|");
|
|
|
|
if (infoMask & HEAP_XMAX_EXCL_LOCK)
|
|
|
|
strcat (flagString, "XMAX_EXCL_LOCK|");
|
2013-06-06 18:33:16 +00:00
|
|
|
if (infoMask & HEAP_XMAX_LOCK_ONLY)
|
|
|
|
strcat (flagString, "XMAX_LOCK_ONLY|");
|
2011-01-18 01:33:27 +00:00
|
|
|
if (infoMask & HEAP_XMIN_COMMITTED)
|
|
|
|
strcat (flagString, "XMIN_COMMITTED|");
|
|
|
|
if (infoMask & HEAP_XMIN_INVALID)
|
|
|
|
strcat (flagString, "XMIN_INVALID|");
|
|
|
|
if (infoMask & HEAP_XMAX_COMMITTED)
|
|
|
|
strcat (flagString, "XMAX_COMMITTED|");
|
|
|
|
if (infoMask & HEAP_XMAX_INVALID)
|
|
|
|
strcat (flagString, "XMAX_INVALID|");
|
|
|
|
if (infoMask & HEAP_XMAX_IS_MULTI)
|
|
|
|
strcat (flagString, "XMAX_IS_MULTI|");
|
|
|
|
if (infoMask & HEAP_UPDATED)
|
|
|
|
strcat (flagString, "UPDATED|");
|
|
|
|
if (infoMask & HEAP_MOVED_OFF)
|
|
|
|
strcat (flagString, "MOVED_OFF|");
|
|
|
|
if (infoMask & HEAP_MOVED_IN)
|
|
|
|
strcat (flagString, "MOVED_IN|");
|
|
|
|
|
2013-06-06 18:33:16 +00:00
|
|
|
if (infoMask2 & HEAP_KEYS_UPDATED)
|
|
|
|
strcat (flagString, "KEYS_UPDATED|");
|
2011-01-18 01:33:27 +00:00
|
|
|
if (infoMask2 & HEAP_HOT_UPDATED)
|
|
|
|
strcat (flagString, "HOT_UPDATED|");
|
|
|
|
if (infoMask2 & HEAP_ONLY_TUPLE)
|
|
|
|
strcat (flagString, "HEAP_ONLY|");
|
|
|
|
|
|
|
|
if (strlen (flagString))
|
|
|
|
flagString[strlen (flagString) - 1] = '\0';
|
|
|
|
|
|
|
|
printf (" infomask: 0x%04x (%s) \n", infoMask, flagString);
|
|
|
|
|
|
|
|
// As t_bits is a variable length array, determine the length of
|
2012-03-12 16:08:33 +00:00
|
|
|
// the header proper
|
2011-01-18 01:33:27 +00:00
|
|
|
if (infoMask & HEAP_HASNULL)
|
|
|
|
bitmapLength = BITMAPLEN (localNatts);
|
|
|
|
else
|
|
|
|
bitmapLength = 0;
|
|
|
|
|
|
|
|
if (infoMask & HEAP_HASOID)
|
|
|
|
oidLength += sizeof (Oid);
|
|
|
|
|
|
|
|
computedLength =
|
|
|
|
MAXALIGN (localBitOffset + bitmapLength + oidLength);
|
|
|
|
|
|
|
|
// Inform the user of a header size mismatch or dump the t_bits array
|
|
|
|
if (computedLength != localHoff)
|
|
|
|
printf
|
|
|
|
(" Error: Computed header length not equal to header size.\n"
|
|
|
|
" Computed <%u> Header: <%d>\n", computedLength,
|
|
|
|
localHoff);
|
|
|
|
else if ((infoMask & HEAP_HASNULL) && bitmapLength)
|
|
|
|
{
|
|
|
|
printf (" t_bits: ");
|
|
|
|
for (x = 0; x < bitmapLength; x++)
|
|
|
|
{
|
|
|
|
printf ("[%u]: 0x%02x ", x, localBits[x]);
|
|
|
|
if (((x & 0x03) == 0x03) && (x < bitmapLength - 1))
|
|
|
|
printf ("\n ");
|
|
|
|
}
|
|
|
|
printf ("\n");
|
|
|
|
}
|
|
|
|
printf ("\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2011-12-18 00:11:29 +00:00
|
|
|
// On blocks that have special sections, print the contents
|
|
|
|
// according to previously determined special section type
|
2011-01-18 01:33:27 +00:00
|
|
|
static void
|
|
|
|
FormatSpecial ()
|
|
|
|
{
|
|
|
|
PageHeader pageHeader = (PageHeader) buffer;
|
|
|
|
char flagString[100] = "\0";
|
|
|
|
unsigned int specialOffset = pageHeader->pd_special;
|
|
|
|
unsigned int specialSize =
|
|
|
|
(blockSize >= specialOffset) ? (blockSize - specialOffset) : 0;
|
|
|
|
|
|
|
|
printf ("<Special Section> -----\n");
|
|
|
|
|
|
|
|
switch (specialType)
|
|
|
|
{
|
|
|
|
case SPEC_SECT_ERROR_UNKNOWN:
|
|
|
|
case SPEC_SECT_ERROR_BOUNDARY:
|
|
|
|
printf (" Error: Invalid special section encountered.\n");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SPEC_SECT_SEQUENCE:
|
|
|
|
printf (" Sequence: 0x%08x\n", SEQUENCE_MAGIC);
|
|
|
|
break;
|
|
|
|
|
2012-03-12 16:08:33 +00:00
|
|
|
// Btree index section
|
2011-01-18 01:33:27 +00:00
|
|
|
case SPEC_SECT_INDEX_BTREE:
|
|
|
|
{
|
|
|
|
BTPageOpaque btreeSection = (BTPageOpaque) (buffer + specialOffset);
|
|
|
|
if (btreeSection->btpo_flags & BTP_LEAF)
|
|
|
|
strcat (flagString, "LEAF|");
|
|
|
|
if (btreeSection->btpo_flags & BTP_ROOT)
|
|
|
|
strcat (flagString, "ROOT|");
|
|
|
|
if (btreeSection->btpo_flags & BTP_DELETED)
|
|
|
|
strcat (flagString, "DELETED|");
|
|
|
|
if (btreeSection->btpo_flags & BTP_META)
|
|
|
|
strcat (flagString, "META|");
|
|
|
|
if (btreeSection->btpo_flags & BTP_HALF_DEAD)
|
|
|
|
strcat (flagString, "HALFDEAD|");
|
|
|
|
if (btreeSection->btpo_flags & BTP_SPLIT_END)
|
|
|
|
strcat (flagString, "SPLITEND|");
|
|
|
|
if (btreeSection->btpo_flags & BTP_HAS_GARBAGE)
|
|
|
|
strcat (flagString, "HASGARBAGE|");
|
|
|
|
if (strlen (flagString))
|
|
|
|
flagString[strlen (flagString) - 1] = '\0';
|
|
|
|
|
|
|
|
printf (" BTree Index Section:\n"
|
|
|
|
" Flags: 0x%04x (%s)\n"
|
|
|
|
" Blocks: Previous (%d) Next (%d) %s (%d) CycleId (%d)\n\n",
|
|
|
|
btreeSection->btpo_flags, flagString,
|
|
|
|
btreeSection->btpo_prev, btreeSection->btpo_next,
|
|
|
|
(btreeSection->
|
|
|
|
btpo_flags & BTP_DELETED) ? "Next XID" : "Level",
|
|
|
|
btreeSection->btpo.level,
|
|
|
|
btreeSection->btpo_cycleid);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2012-03-12 16:08:33 +00:00
|
|
|
// Hash index section
|
2011-01-18 01:33:27 +00:00
|
|
|
case SPEC_SECT_INDEX_HASH:
|
|
|
|
{
|
|
|
|
HashPageOpaque hashSection = (HashPageOpaque) (buffer + specialOffset);
|
|
|
|
if (hashSection->hasho_flag & LH_UNUSED_PAGE)
|
|
|
|
strcat (flagString, "UNUSED|");
|
|
|
|
if (hashSection->hasho_flag & LH_OVERFLOW_PAGE)
|
|
|
|
strcat (flagString, "OVERFLOW|");
|
|
|
|
if (hashSection->hasho_flag & LH_BUCKET_PAGE)
|
|
|
|
strcat (flagString, "BUCKET|");
|
|
|
|
if (hashSection->hasho_flag & LH_BITMAP_PAGE)
|
|
|
|
strcat (flagString, "BITMAP|");
|
|
|
|
if (hashSection->hasho_flag & LH_META_PAGE)
|
|
|
|
strcat (flagString, "META|");
|
|
|
|
if (strlen (flagString))
|
|
|
|
flagString[strlen (flagString) - 1] = '\0';
|
|
|
|
printf (" Hash Index Section:\n"
|
|
|
|
" Flags: 0x%04x (%s)\n"
|
|
|
|
" Bucket Number: 0x%04x\n"
|
|
|
|
" Blocks: Previous (%d) Next (%d)\n\n",
|
|
|
|
hashSection->hasho_flag, flagString,
|
|
|
|
hashSection->hasho_bucket,
|
|
|
|
hashSection->hasho_prevblkno, hashSection->hasho_nextblkno);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
// GIST index section
|
|
|
|
case SPEC_SECT_INDEX_GIST:
|
|
|
|
{
|
|
|
|
GISTPageOpaque gistSection = (GISTPageOpaque) (buffer + specialOffset);
|
|
|
|
if (gistSection->flags & F_LEAF)
|
|
|
|
strcat (flagString, "LEAF|");
|
|
|
|
if (gistSection->flags & F_DELETED)
|
|
|
|
strcat (flagString, "DELETED|");
|
|
|
|
if (gistSection->flags & F_TUPLES_DELETED)
|
|
|
|
strcat (flagString, "TUPLES_DELETED|");
|
2011-11-26 22:14:08 +00:00
|
|
|
if (gistSection->flags & F_FOLLOW_RIGHT)
|
|
|
|
strcat (flagString, "FOLLOW_RIGHT|");
|
2011-01-18 01:33:27 +00:00
|
|
|
if (strlen (flagString))
|
|
|
|
flagString[strlen (flagString) - 1] = '\0';
|
|
|
|
printf (" GIST Index Section:\n"
|
|
|
|
" NSN: 0x%08x/0x%08x\n"
|
|
|
|
" RightLink: %d\n"
|
|
|
|
" Flags: 0x%08x (%s)\n\n",
|
|
|
|
gistSection->nsn.xlogid, gistSection->nsn.xrecoff,
|
|
|
|
gistSection->rightlink,
|
|
|
|
gistSection->flags, flagString);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
// GIN index section
|
|
|
|
case SPEC_SECT_INDEX_GIN:
|
|
|
|
{
|
|
|
|
GinPageOpaque ginSection = (GinPageOpaque) (buffer + specialOffset);
|
|
|
|
if (ginSection->flags & GIN_DATA)
|
|
|
|
strcat (flagString, "DATA|");
|
|
|
|
if (ginSection->flags & GIN_LEAF)
|
|
|
|
strcat (flagString, "LEAF|");
|
|
|
|
if (ginSection->flags & GIN_DELETED)
|
|
|
|
strcat (flagString, "DELETED|");
|
2011-11-26 22:14:08 +00:00
|
|
|
if (ginSection->flags & GIN_META)
|
|
|
|
strcat (flagString, "META|");
|
|
|
|
if (ginSection->flags & GIN_LIST)
|
|
|
|
strcat (flagString, "LIST|");
|
|
|
|
if (ginSection->flags & GIN_LIST_FULLROW)
|
|
|
|
strcat (flagString, "FULLROW|");
|
2011-01-18 01:33:27 +00:00
|
|
|
if (strlen (flagString))
|
|
|
|
flagString[strlen (flagString) - 1] = '\0';
|
|
|
|
printf (" GIN Index Section:\n"
|
|
|
|
" Flags: 0x%08x (%s) Maxoff: %d\n"
|
|
|
|
" Blocks: RightLink (%d)\n\n",
|
|
|
|
ginSection->flags, flagString,
|
|
|
|
ginSection->maxoff,
|
|
|
|
ginSection->rightlink);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2011-12-18 00:11:29 +00:00
|
|
|
// SP-GIST index section
|
|
|
|
case SPEC_SECT_INDEX_SPGIST:
|
|
|
|
{
|
|
|
|
SpGistPageOpaque spgistSection = (SpGistPageOpaque) (buffer + specialOffset);
|
|
|
|
if (spgistSection->flags & SPGIST_META)
|
|
|
|
strcat (flagString, "META|");
|
|
|
|
if (spgistSection->flags & SPGIST_DELETED)
|
|
|
|
strcat (flagString, "DELETED|");
|
|
|
|
if (spgistSection->flags & SPGIST_LEAF)
|
|
|
|
strcat (flagString, "LEAF|");
|
2012-03-12 16:08:33 +00:00
|
|
|
if (spgistSection->flags & SPGIST_NULLS)
|
|
|
|
strcat (flagString, "NULLS|");
|
2011-12-18 00:11:29 +00:00
|
|
|
if (strlen (flagString))
|
|
|
|
flagString[strlen (flagString) - 1] = '\0';
|
|
|
|
printf (" SPGIST Index Section:\n"
|
|
|
|
" Flags: 0x%08x (%s)\n"
|
|
|
|
" nRedirection: %d\n"
|
|
|
|
" nPlaceholder: %d\n\n",
|
|
|
|
spgistSection->flags, flagString,
|
|
|
|
spgistSection->nRedirection,
|
|
|
|
spgistSection->nPlaceholder);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2011-01-18 01:33:27 +00:00
|
|
|
// No idea what type of special section this is
|
|
|
|
default:
|
|
|
|
printf (" Unknown special section type. Type: <%u>.\n", specialType);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2012-03-12 16:08:33 +00:00
|
|
|
// Dump the formatted contents of the special section
|
2011-01-18 01:33:27 +00:00
|
|
|
if (blockOptions & BLOCK_FORMAT)
|
|
|
|
{
|
|
|
|
if (specialType == SPEC_SECT_ERROR_BOUNDARY)
|
|
|
|
printf (" Error: Special section points off page."
|
|
|
|
" Unable to dump contents.\n");
|
|
|
|
else
|
|
|
|
FormatBinary (specialSize, specialOffset);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// For each block, dump out formatted header and content information
|
|
|
|
static void
|
|
|
|
FormatBlock ()
|
|
|
|
{
|
|
|
|
Page page = (Page) buffer;
|
|
|
|
pageOffset = blockSize * currentBlock;
|
|
|
|
specialType = GetSpecialSectionType (page);
|
|
|
|
|
|
|
|
printf ("\nBlock %4u **%s***************************************\n",
|
|
|
|
currentBlock,
|
|
|
|
(bytesToFormat ==
|
|
|
|
blockSize) ? "***************" : " PARTIAL BLOCK ");
|
|
|
|
|
|
|
|
// Either dump out the entire block in hex+acsii fashion or
|
2012-03-12 16:08:33 +00:00
|
|
|
// interpret the data based on block structure
|
2011-01-18 01:33:27 +00:00
|
|
|
if (blockOptions & BLOCK_NO_INTR)
|
|
|
|
FormatBinary (bytesToFormat, 0);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
// Every block contains a header, items and possibly a special
|
2012-03-12 16:08:33 +00:00
|
|
|
// section. Beware of partial block reads though
|
2011-01-18 01:33:27 +00:00
|
|
|
rc = FormatHeader (page);
|
|
|
|
|
2012-03-12 16:08:33 +00:00
|
|
|
// If we didn't encounter a partial read in the header, carry on...
|
2011-01-18 01:33:27 +00:00
|
|
|
if (rc != EOF_ENCOUNTERED)
|
|
|
|
{
|
|
|
|
FormatItemBlock (page);
|
|
|
|
|
|
|
|
if (specialType != SPEC_SECT_NONE)
|
|
|
|
FormatSpecial ();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Dump out the content of the PG control file
|
|
|
|
static void
|
|
|
|
FormatControl ()
|
|
|
|
{
|
|
|
|
unsigned int localPgVersion = 0;
|
|
|
|
unsigned int controlFileSize = 0;
|
2011-12-18 00:11:29 +00:00
|
|
|
time_t cd_time;
|
|
|
|
time_t cp_time;
|
2011-01-18 01:33:27 +00:00
|
|
|
|
|
|
|
printf
|
|
|
|
("\n<pg_control Contents> *********************************************\n\n");
|
|
|
|
|
2012-03-12 16:08:33 +00:00
|
|
|
// Check the version
|
2011-01-18 01:33:27 +00:00
|
|
|
if (bytesToFormat >= offsetof (ControlFileData, catalog_version_no))
|
|
|
|
localPgVersion = ((ControlFileData *) buffer)->pg_control_version;
|
|
|
|
|
|
|
|
if (localPgVersion >= 72)
|
|
|
|
controlFileSize = sizeof (ControlFileData);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
printf ("pg_filedump: pg_control version %u not supported.\n",
|
|
|
|
localPgVersion);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Interpret the control file if it's all there
|
|
|
|
if (bytesToFormat >= controlFileSize)
|
|
|
|
{
|
|
|
|
ControlFileData *controlData = (ControlFileData *) buffer;
|
|
|
|
CheckPoint *checkPoint = &(controlData->checkPointCopy);
|
|
|
|
pg_crc32 crcLocal;
|
|
|
|
char *dbState;
|
|
|
|
|
|
|
|
// Compute a local copy of the CRC to verify the one on disk
|
|
|
|
INIT_CRC32 (crcLocal);
|
|
|
|
COMP_CRC32 (crcLocal, buffer, offsetof(ControlFileData, crc));
|
|
|
|
FIN_CRC32 (crcLocal);
|
|
|
|
|
|
|
|
// Grab a readable version of the database state
|
|
|
|
switch (controlData->state)
|
|
|
|
{
|
|
|
|
case DB_STARTUP:
|
|
|
|
dbState = "STARTUP";
|
|
|
|
break;
|
|
|
|
case DB_SHUTDOWNED:
|
|
|
|
dbState = "SHUTDOWNED";
|
|
|
|
break;
|
|
|
|
case DB_SHUTDOWNING:
|
|
|
|
dbState = "SHUTDOWNING";
|
|
|
|
break;
|
|
|
|
case DB_IN_CRASH_RECOVERY:
|
|
|
|
dbState = "IN CRASH RECOVERY";
|
|
|
|
break;
|
|
|
|
case DB_IN_ARCHIVE_RECOVERY:
|
|
|
|
dbState = "IN ARCHIVE RECOVERY";
|
|
|
|
break;
|
|
|
|
case DB_IN_PRODUCTION:
|
|
|
|
dbState = "IN PRODUCTION";
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
dbState = "UNKNOWN";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2011-12-18 00:11:29 +00:00
|
|
|
/* convert timestamps to system's time_t width */
|
|
|
|
cd_time = controlData->time;
|
|
|
|
cp_time = checkPoint->time;
|
|
|
|
|
2011-01-18 01:33:27 +00:00
|
|
|
printf (" CRC: %s\n"
|
|
|
|
" pg_control Version: %u%s\n"
|
|
|
|
" Catalog Version: %u\n"
|
|
|
|
" System Identifier: " UINT64_FORMAT "\n"
|
|
|
|
" State: %s\n"
|
|
|
|
" Last Mod Time: %s"
|
|
|
|
" Last Checkpoint Record: Log File (%u) Offset (0x%08x)\n"
|
|
|
|
" Previous Checkpoint Record: Log File (%u) Offset (0x%08x)\n"
|
|
|
|
" Last Checkpoint Record Redo: Log File (%u) Offset (0x%08x)\n"
|
|
|
|
" |- TimeLineID: %u\n"
|
|
|
|
" |- Next XID: %u/%u\n"
|
|
|
|
" |- Next OID: %u\n"
|
|
|
|
" |- Next Multi: %u\n"
|
|
|
|
" |- Next MultiOff: %u\n"
|
|
|
|
" |- Time: %s"
|
|
|
|
" Minimum Recovery Point: Log File (%u) Offset (0x%08x)\n"
|
|
|
|
" Maximum Data Alignment: %u\n"
|
|
|
|
" Floating-Point Sample: %.7g%s\n"
|
|
|
|
" Database Block Size: %u\n"
|
|
|
|
" Blocks Per Segment: %u\n"
|
|
|
|
" XLOG Block Size: %u\n"
|
|
|
|
" XLOG Segment Size: %u\n"
|
|
|
|
" Maximum Identifier Length: %u\n"
|
|
|
|
" Maximum Index Keys: %u\n"
|
|
|
|
" TOAST Chunk Size: %u\n"
|
|
|
|
" Date and Time Type Storage: %s\n\n",
|
|
|
|
EQ_CRC32 (crcLocal,
|
|
|
|
controlData->crc) ? "Correct" : "Not Correct",
|
|
|
|
controlData->pg_control_version,
|
|
|
|
(controlData->pg_control_version == PG_CONTROL_VERSION ?
|
|
|
|
"" : " (Not Correct!)"),
|
|
|
|
controlData->catalog_version_no,
|
|
|
|
controlData->system_identifier,
|
|
|
|
dbState,
|
2011-12-18 00:11:29 +00:00
|
|
|
ctime (&(cd_time)),
|
2013-06-06 18:33:16 +00:00
|
|
|
(uint32) (controlData->checkPoint >> 32), (uint32) controlData->checkPoint,
|
|
|
|
(uint32) (controlData->prevCheckPoint >> 32), (uint32) controlData->prevCheckPoint,
|
|
|
|
(uint32) (checkPoint->redo >> 32), (uint32) checkPoint->redo,
|
2011-01-18 01:33:27 +00:00
|
|
|
checkPoint->ThisTimeLineID,
|
|
|
|
checkPoint->nextXidEpoch, checkPoint->nextXid,
|
|
|
|
checkPoint->nextOid,
|
|
|
|
checkPoint->nextMulti, checkPoint->nextMultiOffset,
|
2011-12-18 00:11:29 +00:00
|
|
|
ctime (&cp_time),
|
2013-06-06 18:33:16 +00:00
|
|
|
(uint32) (controlData->minRecoveryPoint >> 32), (uint32) controlData->minRecoveryPoint,
|
2011-01-18 01:33:27 +00:00
|
|
|
controlData->maxAlign,
|
|
|
|
controlData->floatFormat,
|
|
|
|
(controlData->floatFormat == FLOATFORMAT_VALUE ?
|
|
|
|
"" : " (Not Correct!)"),
|
|
|
|
controlData->blcksz,
|
|
|
|
controlData->relseg_size,
|
|
|
|
controlData->xlog_blcksz,
|
|
|
|
controlData->xlog_seg_size,
|
|
|
|
controlData->nameDataLen,
|
|
|
|
controlData->indexMaxKeys,
|
|
|
|
controlData->toast_max_chunk_size,
|
|
|
|
(controlData->enableIntTimes ?
|
|
|
|
"64 bit Integers" : "Floating Point"));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
printf (" Error: pg_control file size incorrect.\n"
|
|
|
|
" Size: Correct <%u> Received <%u>.\n\n",
|
|
|
|
controlFileSize, bytesToFormat);
|
|
|
|
|
2012-03-12 16:08:33 +00:00
|
|
|
// If we have an error, force a formatted dump so we can see
|
2011-01-18 01:33:27 +00:00
|
|
|
// where things are going wrong
|
|
|
|
controlOptions |= CONTROL_FORMAT;
|
|
|
|
}
|
|
|
|
|
2012-03-12 16:08:33 +00:00
|
|
|
// Dump hex and ascii representation of data
|
2011-01-18 01:33:27 +00:00
|
|
|
if (controlOptions & CONTROL_FORMAT)
|
|
|
|
{
|
|
|
|
printf ("<pg_control Formatted Dump> *****************"
|
|
|
|
"**********************\n\n");
|
|
|
|
FormatBinary (bytesToFormat, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-03-12 16:08:33 +00:00
|
|
|
// Dump out the contents of the block in hex and ascii.
|
2011-01-18 01:33:27 +00:00
|
|
|
// BYTES_PER_LINE bytes are formatted in each line.
|
|
|
|
static void
|
|
|
|
FormatBinary (unsigned int numBytes, unsigned int startIndex)
|
|
|
|
{
|
|
|
|
unsigned int index = 0;
|
|
|
|
unsigned int stopIndex = 0;
|
|
|
|
unsigned int x = 0;
|
|
|
|
unsigned int lastByte = startIndex + numBytes;
|
|
|
|
|
|
|
|
if (numBytes)
|
|
|
|
{
|
|
|
|
// Iterate through a printable row detailing the current
|
2012-03-12 16:08:33 +00:00
|
|
|
// address, the hex and ascii values
|
2011-01-18 01:33:27 +00:00
|
|
|
for (index = startIndex; index < lastByte; index += BYTES_PER_LINE)
|
|
|
|
{
|
|
|
|
stopIndex = index + BYTES_PER_LINE;
|
|
|
|
|
|
|
|
// Print out the address
|
|
|
|
if (blockOptions & BLOCK_ABSOLUTE)
|
|
|
|
printf (" %08x: ", (unsigned int) (pageOffset + index));
|
|
|
|
else
|
|
|
|
printf (" %04x: ", (unsigned int) index);
|
|
|
|
|
|
|
|
// Print out the hex version of the data
|
|
|
|
for (x = index; x < stopIndex; x++)
|
|
|
|
{
|
|
|
|
if (x < lastByte)
|
|
|
|
printf ("%02x", 0xff & ((unsigned) buffer[x]));
|
|
|
|
else
|
|
|
|
printf (" ");
|
|
|
|
if ((x & 0x03) == 0x03)
|
|
|
|
printf (" ");
|
|
|
|
}
|
|
|
|
printf (" ");
|
|
|
|
|
|
|
|
// Print out the ascii version of the data
|
|
|
|
for (x = index; x < stopIndex; x++)
|
|
|
|
{
|
|
|
|
if (x < lastByte)
|
|
|
|
printf ("%c", isprint (buffer[x]) ? buffer[x] : '.');
|
|
|
|
else
|
|
|
|
printf (" ");
|
|
|
|
}
|
|
|
|
printf ("\n");
|
|
|
|
}
|
|
|
|
printf ("\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Dump the binary image of the block
|
|
|
|
static void
|
|
|
|
DumpBinaryBlock ()
|
|
|
|
{
|
|
|
|
unsigned int x;
|
|
|
|
for (x = 0; x < bytesToFormat; x++)
|
|
|
|
putchar (buffer[x]);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Control the dumping of the blocks within the file
|
|
|
|
static void
|
|
|
|
DumpFileContents ()
|
|
|
|
{
|
|
|
|
unsigned int initialRead = 1;
|
|
|
|
unsigned int contentsToDump = 1;
|
|
|
|
|
|
|
|
// If the user requested a block range, seek to the correct position
|
|
|
|
// within the file for the start block.
|
|
|
|
if (blockOptions & BLOCK_RANGE)
|
|
|
|
{
|
|
|
|
unsigned int position = blockSize * blockStart;
|
|
|
|
if (fseek (fp, position, SEEK_SET) != 0)
|
|
|
|
{
|
|
|
|
printf ("Error: Seek error encountered before requested "
|
|
|
|
"start block <%d>.\n", blockStart);
|
|
|
|
contentsToDump = 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
currentBlock = blockStart;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Iterate through the blocks in the file until you reach the end or
|
|
|
|
// the requested range end
|
|
|
|
while (contentsToDump)
|
|
|
|
{
|
|
|
|
bytesToFormat = fread (buffer, 1, blockSize, fp);
|
|
|
|
|
|
|
|
if (bytesToFormat == 0)
|
|
|
|
{
|
|
|
|
// fseek() won't pop an error if you seek passed eof. The next
|
2012-03-12 16:08:33 +00:00
|
|
|
// subsequent read gets the error.
|
2011-01-18 01:33:27 +00:00
|
|
|
if (initialRead)
|
|
|
|
printf ("Error: Premature end of file encountered.\n");
|
|
|
|
else if (!(blockOptions & BLOCK_BINARY))
|
|
|
|
printf ("\n*** End of File Encountered. Last Block "
|
|
|
|
"Read: %d ***\n", currentBlock - 1);
|
|
|
|
|
|
|
|
contentsToDump = 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (blockOptions & BLOCK_BINARY)
|
|
|
|
DumpBinaryBlock ();
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (controlOptions & CONTROL_DUMP)
|
|
|
|
{
|
|
|
|
FormatControl ();
|
|
|
|
contentsToDump = false;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
FormatBlock ();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check to see if we are at the end of the requested range.
|
|
|
|
if ((blockOptions & BLOCK_RANGE) &&
|
|
|
|
(currentBlock >= blockEnd) && (contentsToDump))
|
|
|
|
{
|
|
|
|
//Don't print out message if we're doing a binary dump
|
|
|
|
if (!(blockOptions & BLOCK_BINARY))
|
|
|
|
printf ("\n*** End of Requested Range Encountered. "
|
|
|
|
"Last Block Read: %d ***\n", currentBlock);
|
|
|
|
contentsToDump = 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
currentBlock++;
|
|
|
|
|
|
|
|
initialRead = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Consume the options and iterate through the given file, formatting as
|
|
|
|
// requested.
|
|
|
|
int
|
|
|
|
main (int argv, char **argc)
|
|
|
|
{
|
2012-03-12 16:08:33 +00:00
|
|
|
// If there is a parameter list, validate the options
|
2011-01-18 01:33:27 +00:00
|
|
|
unsigned int validOptions;
|
|
|
|
validOptions = (argv < 2) ? OPT_RC_COPYRIGHT : ConsumeOptions (argv, argc);
|
|
|
|
|
|
|
|
// Display valid options if no parameters are received or invalid options
|
|
|
|
// where encountered
|
|
|
|
if (validOptions != OPT_RC_VALID)
|
|
|
|
DisplayOptions (validOptions);
|
|
|
|
else
|
|
|
|
{
|
2012-03-12 16:08:33 +00:00
|
|
|
// Don't dump the header if we're dumping binary pages
|
2011-01-18 01:33:27 +00:00
|
|
|
if (!(blockOptions & BLOCK_BINARY))
|
|
|
|
CreateDumpFileHeader (argv, argc);
|
|
|
|
|
|
|
|
// If the user has not forced a block size, use the size of the
|
2012-03-12 16:08:33 +00:00
|
|
|
// control file data or the information from the block 0 header
|
2011-01-18 01:33:27 +00:00
|
|
|
if (controlOptions)
|
|
|
|
{
|
|
|
|
if (!(controlOptions & CONTROL_FORCED))
|
|
|
|
blockSize = sizeof (ControlFileData);
|
|
|
|
}
|
|
|
|
else if (!(blockOptions & BLOCK_FORCED))
|
|
|
|
blockSize = GetBlockSize ();
|
|
|
|
|
|
|
|
// On a positive block size, allocate a local buffer to store
|
|
|
|
// the subsequent blocks
|
|
|
|
if (blockSize > 0)
|
|
|
|
{
|
|
|
|
buffer = (char *) malloc (blockSize);
|
|
|
|
if (buffer)
|
|
|
|
DumpFileContents ();
|
|
|
|
else
|
|
|
|
printf ("\nError: Unable to create buffer of size <%d>.\n",
|
|
|
|
blockSize);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close out the file and get rid of the allocated block buffer
|
|
|
|
if (fp)
|
|
|
|
fclose (fp);
|
|
|
|
|
|
|
|
if (buffer)
|
|
|
|
free (buffer);
|
|
|
|
|
|
|
|
exit (0);
|
|
|
|
}
|