/* * pg_filedump.c - PostgreSQL file dump utility for dumping and * formatting heap (data), index and control files. * Version 9.0.0 for PostgreSQL 9.0 * * Copyright (c) 2002-2010 Red Hat, Inc. All rights reserved. * * 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 * * Author: Patrick Macdonald * * Component of: PostgreSQL - Red Hat Edition - Utilities / Tools * */ #include "pg_filedump.h" // 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 (); // Send properly formed usage information to the user. static void DisplayOptions (unsigned int validOptions) { if (validOptions == OPT_RC_COPYRIGHT) printf ("\nVersion 9.0.0 (PostgreSQL 9.0) Copyright (c) 2002-2010 Red Hat, Inc.\n"); printf ("\nUsage: pg_filedump [-abcdfhixy] [-R startblock [endblock]] [-S blocksize] file\n\n" "Display formatted contents of a PostgreSQL heap/index/control file\n" " Defaults are: relative addressing, range of the entire file, block\n" " 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" "\nReport bugs to \n"); } // Iterate through the provided options and set the option flags. // An error will result in a positive rc and will force a display // of the usage information. This routine returns enum // 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 // instead of having the tool determine it. This is useful if // 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 { // Could be the case where the help flag is used without a // filename. Otherwise, the last option isn't a file 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]) { // Use absolute addressing case 'a': SET_OPTION (blockOptions, BLOCK_ABSOLUTE, 'a'); break; // Dump the binary contents of the page 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; // Display the usage screen case 'h': rc = OPT_RC_COPYRIGHT; break; // Format the items in detail case 'i': SET_OPTION (itemOptions, ITEM_DETAIL, 'i'); break; // Interpret items as index values case 'x': SET_OPTION (itemOptions, ITEM_INDEX, 'x'); if (itemOptions & ITEM_HEAP) { rc = OPT_RC_INVALID; printf ("Error: Options and 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 and 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 // all other block level options (with a few exceptions) 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 may be used with .\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); } // Given the index into the parameter list, convert and return the // current string to a number if possible 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; // If this is not a partial header, check the validity of the // 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 { specialSize = blockSize - specialOffset; // If there is a special section, use its size to guess its // contents if (specialSize == 0) rc = SPEC_SECT_NONE; else if (specialSize == MAXALIGN (sizeof (uint32))) { // If MAXALIGN is 8, this could be either a sequence or GIN if (bytesToFormat == blockSize) { specialValue = *((int *) (buffer + specialOffset)); if (specialValue == SEQUENCE_MAGIC) rc = SPEC_SECT_SEQUENCE; else if (specialSize == MAXALIGN (sizeof (GinPageOpaqueData))) rc = SPEC_SECT_INDEX_GIN; else rc = SPEC_SECT_ERROR_UNKNOWN; } else rc = SPEC_SECT_ERROR_UNKNOWN; } 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. uint16 ptype = *(uint16 *) (buffer + blockSize - sizeof(uint16)); if (ptype <= MAX_BT_CYCLE_ID && specialSize == MAXALIGN (sizeof (BTPageOpaqueData))) rc = SPEC_SECT_INDEX_BTREE; else if (ptype == HASHO_PAGE_ID && specialSize == MAXALIGN (sizeof (HashPageOpaqueData))) rc = SPEC_SECT_INDEX_HASH; else if (ptype == GIST_PAGE_ID && 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. // The maximum we can display is 50 option characters + spaces. for (x = 1; x < (numOptions - 1); x++) { if ((strlen (optionBuffer) + strlen (options[x])) > 50) break; strcat (optionBuffer, options[x]); strcat (optionBuffer, " "); } printf ("\n*******************************************************************\n" "* PostgreSQL File/Block Formatted Dump Utility - Version 9.0.0\n*\n" "* File: %s\n" "* Options used: %s\n*\n" "* Dump created on: %s" "*******************************************************************\n", fileName, (strlen (optionBuffer)) ? optionBuffer : "None", 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 ("
-----\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|"); 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" " TLI: 0x%04x Prune XID: 0x%08x Flags: 0x%04x (%s)\n" " Length (including item array): %u\n\n", pageOffset, pageHeader->pd_lower, pageHeader->pd_lower, (int) PageGetPageSize (page), blockVersion, pageHeader->pd_upper, pageHeader->pd_upper, pageLSN.xlogid, pageLSN.xrecoff, pageHeader->pd_special, pageHeader->pd_special, maxOffset, pageHeader->pd_upper - pageHeader->pd_lower, pageHeader->pd_tli, pageHeader->pd_prune_xid, 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); // A request to dump the formatted binary of the block (header, // 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); } // Dump out formatted items that reside on this block 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 (" ------ \n"); // Loop through the items on the block. Check if the block is // empty and has a sensible item array listed before running // through each item 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]; // First, honour requests to format items a special way, then // 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 if (specialType != SPEC_SECT_NONE) formatAs = ITEM_INDEX; else formatAs = ITEM_HEAP; 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 // heap or index items... if (itemOptions & ITEM_DETAIL) FormatItem (itemSize, itemOffset, formatAs); // Dump the items contents in hex and ascii 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) { // It is an index item, so dump the index header if (formatAs == ITEM_INDEX) { 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)), itup->t_tid.ip_posid, (int) IndexTupleSize (itup), IndexTupleHasNulls (itup), IndexTupleHasVarwidths (itup)); if (numBytes != IndexTupleSize (itup)) printf (" Error: Item size difference. Given <%u>, " "Internal <%d>.\n", numBytes, (int) IndexTupleSize (itup)); } } else { // It is a heap item, so dump the heap header 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), HeapTupleHeaderGetXmax(htup), 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); // Place readable versions of the tuple info mask into a buffer. // 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|"); if (infoMask & HEAP_COMBOCID) strcat (flagString, "COMBOCID|"); if (infoMask & HEAP_XMAX_EXCL_LOCK) strcat (flagString, "XMAX_EXCL_LOCK|"); if (infoMask & HEAP_XMAX_SHARED_LOCK) strcat (flagString, "XMAX_SHARED_LOCK|"); 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|"); 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 // the header proper 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"); } } } // On blocks that have special sections, we have to interpret the // contents based on size of the special section (since there is // no other way) 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 (" -----\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; // Btree index section 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; // Hash index section 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|"); 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|"); 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; // No idea what type of special section this is default: printf (" Unknown special section type. Type: <%u>.\n", specialType); break; } // Dump the formatted contents of the special section 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 // interpret the data based on block structure if (blockOptions & BLOCK_NO_INTR) FormatBinary (bytesToFormat, 0); else { int rc; // Every block contains a header, items and possibly a special // section. Beware of partial block reads though rc = FormatHeader (page); // If we didn't encounter a partial read in the header, carry on... 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; printf ("\n *********************************************\n\n"); // Check the version 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; } 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, ctime (&(controlData->time)), controlData->checkPoint.xlogid, controlData->checkPoint.xrecoff, controlData->prevCheckPoint.xlogid, controlData->prevCheckPoint.xrecoff, checkPoint->redo.xlogid, checkPoint->redo.xrecoff, checkPoint->ThisTimeLineID, checkPoint->nextXidEpoch, checkPoint->nextXid, checkPoint->nextOid, checkPoint->nextMulti, checkPoint->nextMultiOffset, ctime (&checkPoint->time), controlData->minRecoveryPoint.xlogid, controlData->minRecoveryPoint.xrecoff, 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); // If we have an error, force a formatted dump so we can see // where things are going wrong controlOptions |= CONTROL_FORMAT; } // Dump hex and ascii representation of data if (controlOptions & CONTROL_FORMAT) { printf (" *****************" "**********************\n\n"); FormatBinary (bytesToFormat, 0); } } // Dump out the contents of the block in hex and ascii. // 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 // address, the hex and ascii values 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 // subsequent read gets the error. 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) { // If there is a parameter list, validate the options 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 { // Don't dump the header if we're dumping binary pages if (!(blockOptions & BLOCK_BINARY)) CreateDumpFileHeader (argv, argc); // If the user has not forced a block size, use the size of the // control file data or the information from the block 0 header 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); }