Subject: Software: pgdump dumps pages from an OnLine system From: Jacob Salomon To: software@iiug.org Date: Tue, 28 Jul 1998 18:27:15 -0400 (EDT) #---------------------------------- cut here ---------------------------------- # This is a shell archive. Remove anything before this line, # then unpack it by saving it in a file and typing "sh file". # # Wrapped by Jacob Salomon on Tue Jul 28 18:25:31 1998 # # This archive contains: # pgdump.c Makefile README TO-DO # LANG=""; export LANG PATH=/bin:/usr/bin:/usr/sbin:/usr/ccs/bin:$PATH; export PATH echo x - pgdump.c cat >pgdump.c <<'@EOF' #include #include #include #include #include extern char *optarg; extern int optind, opterr, optopt; #include /* pgdump: This program dumps a page that is presumably from an OnLine system. Usage: pgdump -h HELP page -d No default -s Page Size in K; Default 2 -p Page Number; Default 0 -n Number of pages to dump -f Format of the dump - Combination of: x Hex - 4 byte columns (Default) s Slots, where applicable */ #define PAGE_SIZE_K 2 #define K_SIZE 1024 typedef struct /* Structure to hold flags for the */ { /* flags for page formatting */ char hex; char slot; } page_format_t; typedef enum { FM_NARROW = 1, FM_WIDE = 2 } fm_width_t; /* Flag dump-format width*/ /* Informix disk/page structures */ typedef struct page_header { long page_id; /* Chunk #[3], page number[5] nybbles*/ unsigned long timestamp; /* really - event ID number */ short num_slots; /* Number of slots in this page */ short pg_type; /* Number representing page type */ short free_ptr; /* "pointer" past last row in page */ short free_cnt; /* # bytes in page not in a row */ long next; /* For index pages - -> next page */ long prev; /* For index pages - -> prev page */ } page_header_t; /* Break down the page_id to components. */ typedef struct { unsigned int chunk_num : 12; unsigned int page_num : 20; } page_id_t; typedef struct { unsigned short row_offset; unsigned short row_length; } slot_t; unsigned short row_mask = 0x0FFF; /* Mask off flags in slot table */ typedef enum pg_type /* For page types in pg_type */ { PG_RSRVD = 0x1000, /* OnLine reserved Page */ PG_CHUNK = 0x0008, /* Chunk Free List Page */ PG_FREE = 0x0004, /* Bitmap Page (free list for * * tblspace) */ PG_PARTN = 0x0002, /* Partition Header Page */ PG_DATA = 0x0001, /* Data page */ PG_REMAINDER = 0x0009, /* Remainder data page */ PG_PBLOB = 0x000b, /* Partition resident BLOB page */ PG_BLOB = 0x000c, /* BlobSpace resident BLOB page */ PG_BBIT = 0x000d, /* Blob Chunk Free List Bit page*/ PG_BMAP = 0x000e, /* Blob Chunk Blob Map Page */ PG_BTREE = 0x0010, /* B-tree node page */ PG_BTROOT = 0x0020, /* B-tree root node */ PG_BTTWIG = 0x0040, /* B-tree twig node */ PG_BTLEAF = 0x0080, /* B-tree leaf node */ PG_WHOLIX = 0x00b0, /* Whole index in 1 node: BTREE,* * BTROOT, BTLEAF */ PG_LOGPG = 0x0100, /* Logical Log page */ PG_LOGLASTPG = 0x0200, /* Last Page of Logical Log */ PG_LOGSYNCPG = 0x0400, /* Sync Page of Log Log */ /* Modifier flags for most pages*/ PG_PHYLOG = 0x0800, /* Page is in physical log */ PG_NOPHLOG = 0x2000, /* No phys log required */ PG_BTWDEL = 0x8000, /* B-tree leaf w/DELFG's */ } pg_type_t; struct pgtype_entry { pg_type_t ptype; char ptype_name[16]; char ptype_desc[41]; } pgtype_list[] = { { PG_RSRVD, "PG_RSRVD", "OnLine reserved Page" }, { PG_CHUNK, "PG_CHUNK", "Chunk Free List Page" }, { PG_FREE, "PG_FREE", "TBLSpace Bitmap Page" }, { PG_PARTN, "PG_PARTN", "Partition Header Page" }, { PG_DATA, "PG_DATA", "Data page" }, { PG_REMAINDER, "PG_REMAINDER", "Remainder data page" }, { PG_PBLOB, "PG_PBLOB", "Partition resident BLOB page" }, { PG_BLOB, "PG_BLOB", "BlobSpace resident BLOB page" }, { PG_BBIT, "PG_BBIT", "Blob Chunk Free List Bit page" }, { PG_BMAP, "PG_BMAP", "Blob Chunk Blob Map Page" }, { PG_BTREE, "PG_BTREE", "B-tree node page" }, { PG_BTROOT, "PG_BTROOT", "B-tree root node" }, { PG_BTTWIG, "PG_BTTWIG", "B-tree twig node" }, { PG_BTLEAF, "PG_BTLEAF", "B-tree leaf node" }, { PG_WHOLIX, "PG_WHOLIX", "BTREE, BTROOT, BTLEAF in 1 node" }, { PG_LOGPG, "PG_LOGPG", "Logical Log page" }, { PG_LOGLASTPG, "PG_LOGLASTPG", "Last Page of Logical Log" }, { PG_LOGSYNCPG, "PG_LOGSYNCPG", "Sync Page of Log Log" }, }; short pglist_entries = (sizeof(pgtype_list) / sizeof(struct pgtype_entry)); /* Additional Global Variables: */ char device_name[80]; int page_size_k = PAGE_SIZE_K, page_size = (PAGE_SIZE_K * K_SIZE), page_offset = 0, /* Offset from device start - Pages */ page_offset_bytes = 0; /* Offset from device start - Bytes */ page_format_t page_format = {'N', 'N'}; int num_pages = 1; char option_string[] = ":hd:s:p:n:f:"; fm_width_t x_width = FM_NARROW, /* Default width for hex dump */ s_width = FM_NARROW, /* Default width for slot dump */ use_width; /* May be set to either of above*/ int bytes_per_line, groups_per_line, lines_per_pg, bytes_per_group, dash_line_length; char hex_line[74], /* Form hex part of dump here */ byte_line[33], /* Form byte part here */ group_buf[10], /* 1 group of bytes here */ byte_buf[3], /* 1 hex byte here */ char_buf[2], /* 1 char in here */ dash_line[140], separator_line[140], output_line[140]; static char heading_format[] = "Device: <%s>; Page <%04d/0x%04X>; OnLine Page ID: <%d/%d>\n%s\n"; extern char *sys_errlist[]; /*====================================================================*/ void dummy_call(fname) /* For development by parts */ char *fname; { fprintf(stderr, "Sorry, function %s() is not yet implemented\n", fname); } /*====================================================================*/ /*====================================================================*/ main(argc, argv) int argc; char *argv[]; { int params_ok; int pgs_dumped = 0; if (argc <= 1) /* If too few arguments */ { /* emit help message and exit */ help_page(); exit (1); } if ((params_ok = validate_params(argc, argv)) > 0) pgs_dumped = dump_pages (device_name, page_size, page_offset, num_pages, &page_format); else if (params_ok == -1) /* T'was merely a help line */ exit(0); else { fprintf(stderr, "Unable to dump specified pages\n"); exit (2); } if (pgs_dumped > 0) exit (0); else exit (1); } /*====================================================================*/ help_page() /* Display help */ { int lc; /* Loop counter */ static char *help_lines[] = { "Usage:", "pgdump -h # This help page", "pgdump -d # Required", " [-s] # Page size in K (Def: 2)", " [-p] # Page Number; Default 0", " [-n] # Number of pages to dump", " [-f] # Format of the dump:", " Combination of:", " x Hex dump - 4 byte columns (Default)", " s Hex dump of Slots, where applicable", "" /* A null string to end it all */ }; for (lc = 0; strlen(help_lines[lc]) > 0; lc++) printf("%s\n", help_lines[lc]); } /*====================================================================*/ int validate_params(argc, argv) int argc; char *argv[]; { int cur_option; /* Running return from getopt */ int rval = 1; /* Return value to caller - assume OK */ char page_format_set=0; /* Flag if a step has happened */ char *term_char; /* Pointer to terminating byte; for numeric * conversions with strtol */ /* Loop while fetching & validating command parameters */ while ((cur_option = getopt(argc, argv, option_string)) != -1) { if (rval == 0) /* If anything has set rval FALSE */ break; /* get out of the loop */ switch(cur_option) { case 'h': /* Requesting help */ help_page(); /* Display help page */ rval = -1; /* Indicate to get out */ break; case 'd': /* Specifying the device */ strcpy(device_name, optarg); /* Copy device name */ break; case 's': /* Specifying page size */ /* Initially assume base 10 numbers */ page_size_k = strtol(optarg, &term_char, 10); if (page_size_k == 0) /* Ridiculous page size - why? */ { /* Because user specified a hex page size? */ if (*term_char == 'x' || *term_char == 'X') page_size_k = strtol(optarg, &term_char, 16); } if (page_size_k == 0) /* Still the bad page size */ { /* No more excuses */ fprintf(stderr, "Invalid page size <%s> specified\n", optarg); rval = 0; /* set loop-break flag */ break; /* get out of this switch now */ } /* OK, we have a normal-looking page size now. */ page_size = page_size_k * K_SIZE; /* in bytes also */ break; case 'p': /* Page offset into device */ /* Initially assume base 10 numbers */ page_offset = strtol(optarg, &term_char, 10); /* * If user had specified a hex offset, the above con- * * version would result in 0. Is it a real 0 or mis- * * understanding of the parameter? If the 0 is caused * * by a hex format (0xnnnn) then follow up with a hex * * conversion. * */ if ( page_offset == 0 /* 0 may be legit offset*/ && (*term_char == 'x' || *term_char == 'X') ) page_offset = strtol(optarg, &term_char, 16); /* Dont convert to K yet*/ break; case 'n': /* Number of pages to dump */ num_pages = strtol(optarg, &term_char, 10); if (num_pages == 0) /* Bad page count */ { /* Look for hex excuse as above */ if (*term_char == 'x' || *term_char == 'X') num_pages = strtol(optarg, &term_char, 16); } if (num_pages == 0) /* STILL a silly number */ { /* No more excuses */ fprintf(stderr, "Invalid page count <%s> specified\n", optarg); rval = 0; /* set loop-break flag */ } break; /* If rval was cleared, will break loop */ case 'f': /* Specifying a format flag */ if (strpbrk(optarg, "Xx") != NULL) /* Hex dump */ { page_format.hex = 'Y'; page_format_set = 1; /* Indicate it *was* set */ if (strchr(optarg, 'X')) /* Upper case X used? */ x_width = FM_WIDE; page_format_set = 1; /* Indicate page format set */ } if (strpbrk(optarg, "Ss") != NULL) /* Slot dump */ { page_format.slot = 'Y'; page_format_set = 1; /* Indicate it *was* set */ if (strchr(optarg, 'S')) /* Upper case S used? */ s_width = FM_WIDE; page_format_set = 1; /* Indicate page format set */ } break; case '?': /* Unknown option */ fprintf (stderr, "Unknown option/argument <-%c>\n", optopt); rval = 0; /* FALSE indicator to caller */ break; case ':': /* Missing an argument */ fprintf (stderr, "Option <-%c> missing its argument\n", optopt); rval = 0; /* FALSE indicator to caller */ break; } /* End switch */ } /* End while loop */ if (rval > 0) /* Passed above parse - follow-up */ { /* It is now safe to run some followups that might be screwed up by a user entering params in a funny order. */ page_offset_bytes = page_offset * page_size; /* for lseek */ if (page_format_set == 0) /* User omitted -f param */ { /* Apply documented defaults*/ page_format.hex = 'Y'; /* Hex dump only */ page_format.slot = 'N'; /* No slot dump */ page_format_set = 1; /* Indicate set *now* */ } #if 0 printf ("Device: %s\n", device_name); printf ("Page Size: %d/%d\n", page_size_k, page_size); printf ("Page Offset: %d\n", page_offset); printf ("N-Pages: %d\n", num_pages); printf ("Page_format: Hex: <%c>; Slot: <%c>\n", page_format.hex, page_format.slot); #endif } /* if (rval > 0) */ /* Insure correct width used for all output */ if (x_width == FM_WIDE || s_width == FM_WIDE) use_width = FM_WIDE; else use_width = FM_NARROW; return rval; } dump_pages(devname, pg_size, pg_offset, n_pages, pg_format) char *devname; int pg_size, pg_offset, n_pages; page_format_t *pg_format; { int lc; /* Loop counter */ int dev_file; int pages_dumped = 0; int bytes_read; /* Number of bytes received in READ */ off_t lseek_target, lseek_result, cur_lseek; int lseek_whence = SEEK_SET; /* Initial offset parameter */ char *page_buffer; /* Will malloc a page-size buffer here */ if ( (dev_file = open(devname, O_RDONLY)) == -1) /* Open file, prepare for failure */ { fprintf (stderr, "Error %d opening device/file %s\n", errno, devname); fprintf (stderr, "System message is:\n<%s>\n", sys_errlist[errno]); goto exit_point; /* Cleanest way out of here */ } /* Still here - File open was successful. Now start using it. */ if ( (page_buffer = calloc(1, (size_t) page_size)) == NULL) { fprintf (stderr, "Memory allocation failure - Errno = <%d>\n", errno); goto exit_point; } lseek_target = pg_offset * pg_size; /* Where to start dump */ /* Only this one lseek should be necessary */ lseek_result = lseek(dev_file, lseek_target, lseek_whence); if (lseek_result == -1) { fprintf(stderr, "Lseek failure <%d> on device %s; message:\n", errno, devname, sys_errlist[errno]); goto exit_point; } cur_lseek = lseek_result; /* Initial postion to display*/ /* * Now that I know it is possible to proceed, set up global * variables to be used by all dumping functions. */ bytes_per_group = 4; /* Dump bytes in sets of 4 */ switch (use_width) { case FM_NARROW: bytes_per_line = 16; groups_per_line = 4; break; case FM_WIDE: bytes_per_line = 32; groups_per_line = 8; break; default: fprintf (stderr, "Program error in dump_pages(): Unknown width format <%d>\n", pg_format); exit(3); break; } dash_line_length = 5 + 5 /* Length of line heading */ + (groups_per_line * ((2 * bytes_per_group) + 1)) + 1 /* hex part + separator */ + bytes_per_line /* Byte part of line */ + 2; /* | at end of line */ for (lc = 0; lc < n_pages; lc++) /* Loop dumps 1 page/round */ { /* Will sneak param changes */ char break_loop = 0; /* Assume I keep going */ bytes_read = read(dev_file, page_buffer, pg_size); switch (bytes_read) { case 0: /* End of file reached */ break_loop = 1; break; case -1: fprintf(stderr, "Read error <%d> on device <%s>: Message:\n<%s>\n", errno, devname, sys_errlist[errno]); break_loop = 1; break; default: if (page_format.hex == 'Y') hex_dump (devname, cur_lseek, pg_size, page_buffer, x_width); if (page_format.slot == 'Y') slot_dump(devname, cur_lseek, pg_size, page_buffer, s_width); break; } /* End SWITCH */ if (break_loop) /* Set exit-flag - result */ break; /* is kinda obvious */ cur_lseek += bytes_read; /* Advance addr in heading */ } /* End FOR */ exit_point: return pages_dumped; } hex_dump(device, byte_pos, pg_size, page_buf, wformat) char *device; off_t byte_pos; int pg_size; char *page_buf; fm_width_t wformat; { static char output_format[] = "%04d/%04X||%s|%s|"; int lc_line, lc_group, lc_byte; /* Loop counters */ int page_address; int last_line_addr; /* Offset: last line in page */ char null_line, prev_null_line; /* Just dumped a null line */ int count_skipped_lines; /* Tally while it happening */ page_id_t *page_addr; last_line_addr = pg_size - bytes_per_line; /* Initialize buffers, counters, variables to print heading */ bzero(dash_line, dash_line_length + 1); bzero(separator_line, dash_line_length + 1); memset(dash_line, '-', dash_line_length); memset(separator_line, '=', dash_line_length); lines_per_pg = pg_size / bytes_per_line; page_addr = (page_id_t *) page_buf; /* Type cast to treat page * * address like 2 fields */ page_address = byte_pos / pg_size; /* Needed for page heading */ printf (heading_format, device, page_address, page_address, page_addr->chunk_num, page_addr->page_num, dash_line); null_line = 0; /* I haven't dumped a null line yet */ prev_null_line = 0; /* I haven't dumped a null line yet */ count_skipped_lines = 0; /* Ditto */ for (lc_line = 0; lc_line < lines_per_pg; lc_line++) { /* Dump 1 line of output per round */ int line_addr = lc_line * bytes_per_line; /* Line heading */ /* Initialize major output buffers */ bzero(hex_line, sizeof(hex_line)); bzero(byte_line, sizeof(byte_line)); bzero(output_line, sizeof(output_line)); /* * Am I about to dump a line full of nulls? If I am, I will * * print the dump the first time only (this page). There- * * after I would consolidate them all into a single notice * */ if (line_is_null(page_buf, line_addr, bytes_per_line)) null_line = 1; else null_line = 0; /* * The story on null lines: * - If current line is not null, print it, of course. * - If current line is null but previous line was not null, * print it. * - If current line is null and so was previous line, skip * printing it but keep count of how many lines I skipped * for their nullness. * - If current line is first non-null line after a set of * null lines, print the message "Skipped nnn null lines" * before printing the current line. * - If this is the last line of a page, print it regardless * of null state. And if it follows a sequence of null lines * precede it withthe same "Skipped" message. Note that * this is very unlikely due to the structure of an OnLine * page with the slot table and event-number stamp at end * of a page. * So before I start the next loop, I look for reasons to * skip printing it. */ if (null_line && prev_null_line) /* Into a null set */ { /* Print if last line */ if (line_addr != last_line_addr) { /* Not last line - skip */ count_skipped_lines++; /* Up this tally */ continue; /* skip to next line */ } } /* * If I am resuming print (for whatever reason) after skipping * one or more null lines, issue a "Skipped" message before * printing the current line. */ if ( (count_skipped_lines > 0) && ( !null_line || line_addr == last_line_addr) ) { printf ("%s Skipped <%d> null lines %s\n", "-------------------", count_skipped_lines, "-------------------"); count_skipped_lines = 0; /* reset it! */ } /* Following loop prints 1 whole line */ for (lc_group = 0; lc_group < groups_per_line; lc_group++) { /* Format 1 byte group per round */ int sprintf_chars = 0; bzero(group_buf, sizeof(group_buf)); for (lc_byte = 0; lc_byte < bytes_per_group; lc_byte++) { /* Format 1 byte per round */ int cur_byte_offset = line_addr + (lc_group * bytes_per_group) + lc_byte; char cur_char; bytohex(page_buf[cur_byte_offset], byte_buf); if ( page_buf[cur_byte_offset] >= ' ' && page_buf[cur_byte_offset] <= '~') /*Printable?*/ cur_char = page_buf[cur_byte_offset]; else /* Not printable - substitute */ cur_char = '~'; sprintf(char_buf, "%c", cur_char); /* Put in string*/ /* * Now that the byte has been formatted both ways, * * append it to the group and byte buffers. * */ strcat(group_buf, byte_buf); strcat(byte_line, char_buf); } /* * Coming out of above loop, I have completed formatting * * one byte group (not to mention contribution to * * byte_line). Append this formatted group to the line. * */ strcat(group_buf, " "); /* Patch a blank behind it */ strcat(hex_line, group_buf); /* Append to bigger line */ } /* * Coming out of above loop, I have completed formatting 1 * * line of hex output, as well as the byte portion of the * * line to be displayed. Time to display all this, provided* * I am not in middle of dumping a bunch of null lines. * */ prev_null_line = null_line; /* In next round, this line * * will be the previous line*/ sprintf(output_line, output_format, line_addr, line_addr, hex_line, byte_line); printf("%s\n", output_line); } /* * Coming out of above loop, I have just completed dumping 1 page * */ printf("%s\n\n", separator_line); } slot_dump(device, byte_pos, pg_size, page_buf, wformat) char *device; off_t byte_pos; int pg_size; char *page_buf; fm_width_t wformat; { static slotted_page /* Flags indicated page w/ slots */ = PG_RSRVD | PG_PARTN | PG_DATA | PG_REMAINDER | PG_PBLOB | PG_BTREE | PG_BTROOT | PG_BTTWIG | PG_BTLEAF | PG_WHOLIX ; static log_page /* Flags to indicate a log page */ = PG_LOGPG | PG_LOGLASTPG | PG_LOGSYNCPG ; page_header_t * ph; /* Page header structure pointer */ short lc; /* Loop counter */ dump_page_header(page_buf); ph = (page_header_t *) page_buf; if ((slotted_page & ph->pg_type) != 0) /* Has slots? */ { for (lc = 1; lc <= ph->num_slots; lc++) dump_1slot(page_buf, pg_size, lc, wformat); } else if ((log_page & ph->pg_type) != 0) /* Log entries? */ dump_log_page(page_buf, pg_size, wformat); else printf("This page is does not have identifiable rows\n"); } /* Standard page heading for all page dumps. */ display_page_heading(dev, byte_pos, pb, wf) char *dev; /* Name of device file */ unsigned long byte_pos; /* Byte position within file */ char *pb; /* -> page buffer */ fm_width_t wf; /* Width flag - for dashed line */ { int dash_line_length, dash_line[140], groups_per_line, separator_line[140]; static char heading_format[] = "Device: <%s>; Page <%04d/0x%04X>; OnLine Page ID: <%d/%d>\n%s\n"; switch (wf) { case FM_NARROW: bytes_per_line = 16; groups_per_line = 4; break; case FM_WIDE: bytes_per_line = 32; groups_per_line = 8; break; default: fprintf (stderr, "Program error in display_page_heading(): Unknown width format <%d>\n", wf); exit(3); break; } dash_line_length = 5 + 5 /* Length of line heading */ + (groups_per_line * ((2 * bytes_per_group) + 1)) + 1 /* hex part + separator */ + bytes_per_line /* Byte part of line */ + 2; /* | at end of line */ } dump_page_header(pb) char *pb; /* Page Buffer */ { page_header_t *ph = (page_header_t *) pb; /* Ref as page header*/ static char ph_format1[] = "Event ID: <%08X>; Slots: <%03d>;\nPage type:<%04X/%s/%s>\n"; static char ph_format2[] = "Total free bytes: <%04d>; Free area begins at: <0x%04X>\n"; static char ph_format3[] = "Next/Previous pages at this B-Tree level: <%08X/%08X>\n"; char *out_pgtype_name, /* Page type name & description to */ *out_pgtype_desc; /* appear in the heading display */ short lc; /* Loop counter */ /* Search for the pgtype entry fot this page's type */ for (lc = 0; lc < pglist_entries; lc++) if (ph->pg_type == pgtype_list[lc].ptype) break; /* At exit, I found it or I didn't */ if (lc >= pglist_entries) /* Well, did I find it? */ { /* *&^%!-it! I missed! */ out_pgtype_name = "Unknown"; out_pgtype_desc = "Unknown page type"; } else { out_pgtype_name = pgtype_list[lc].ptype_name; out_pgtype_desc = pgtype_list[lc].ptype_desc; } printf(ph_format1, ph->timestamp, ph->num_slots, ph->pg_type, out_pgtype_name, out_pgtype_desc); printf(ph_format2, ph->free_cnt, ph->free_ptr); if (ph->next != 0 || ph->prev != 0) /* If these have something */ printf(ph_format3, ph->next, ph->prev); } /* dump_1slot() - Function to dump 1 slot of a slotted page */ /* Parameters: */ /* - Address of a page buffer */ /* - Length of the buffer (Normally 2K of 4K) */ /* - Slot number to dump */ /* - Flag to use wide or narrow format */ /* Globals: */ /* - bytes_per_line */ /* - groups_per_line */ /* - The various global buffers */ dump_1slot(pb, psiz, slot_num, fmw) char *pb; /* -> Page buffer */ short psiz; /* Length of buffer */ short slot_num; /* Which slot to dump */ fm_width_t fmw; /* Format width flag */ { static char n_slot_format[] /* Format string for*/ = "%04d/%04X||%-36s|%-16s|"; /* narrow slot dump */ static char w_slot_format[] /* Format string for*/ = "%04d/%04X||%-72s|%-32s|"; /* wide slot dump */ char * slot_format; /* Which one to use?*/ short lines_to_dump, bytes_to_dump, lc_line, lc_group; /* Loop counters for lines, * group/line */ short row_offset; unsigned short raw_row_offset; short row_size; unsigned short raw_row_size; slot_t * slot_ptr; /* -> slot entry */ short line_addr; /* Line heading address */ char * row_addr; char * cslot = pb + page_size /* -> Past end of page buf */ - 4 /* -> before "time stamp" */ - (4 * slot_num); /* -> slot entry to dump */ /* */ switch (fmw) /* Decide which format string I will use */ { case FM_NARROW: slot_format = n_slot_format; break; case FM_WIDE: slot_format = w_slot_format; break; default: fprintf(stderr, "Program bug in dump_1slot; fmw=<%d>\n", fmw); exit(3); } /* END switch(fmw) */ slot_ptr = (slot_t *) cslot; /* -> slot entry typed right*/ raw_row_size = slot_ptr -> row_length; /* With flags */ row_size = raw_row_size & row_mask; /* Without flags */ raw_row_offset = slot_ptr -> row_offset; /* With flags */ row_offset = raw_row_offset & row_mask; /* Without flags */ row_addr = pb + row_offset; /* Addr to get data */ /* Calculate lines to dump for this row */ lines_to_dump = (row_size + bytes_per_line) / bytes_per_line; bytes_to_dump = row_size; /* Start data countdown */ dump_slot_info(slot_num, /* Preface with slot info */ raw_row_offset, row_offset, raw_row_size, row_size); for (lc_line = 0; lc_line < lines_to_dump; lc_line++) { /* Set output buffers to nulls */ bzero(hex_line, sizeof(hex_line)); bzero(byte_line, sizeof(byte_line)); bzero(output_line, sizeof(output_line)); line_addr = lc_line * bytes_per_line; /* Line heading */ /* Following loop prints 1 whole line */ for (lc_group = 0; lc_group < groups_per_line; lc_group++) { /* Format 1 byte group per round */ int sprintf_chars = 0; short lc_byte; /* Loop counter - bytes per group */ bzero(group_buf, sizeof(group_buf)); for (lc_byte = 0; lc_byte < bytes_per_group; lc_byte++) { /* Format 1 byte per round */ int cur_byte_offset; char cur_char; /* */ cur_byte_offset /* Calculate exact offset to */ = line_addr /* the current byte */ + (lc_group * bytes_per_group) + lc_byte; cur_char = row_addr[cur_byte_offset]; /* Get char */ bytohex(cur_char, byte_buf); if ( cur_char >= ' ' && cur_char <= '~') /*Printable?*/ ; /* Yes - leav it as it stands */ else /* Not printable - substitute */ cur_char = '~'; /* a suitable (IMO) replacement */ sprintf(char_buf, "%c", cur_char); /* Put in string*/ /* * Now that the byte has been formatted both ways, * * append it to the group and byte buffers. * */ strcat(group_buf, byte_buf); strcat(byte_line, char_buf); if (--bytes_to_dump <= 0) /* Done formatting row? */ break; /* Stop formatting group*/ } /* * Coming out of above loop, I have completed formatting * * one byte group (not to mention contribution to * * byte_line). Append this formatted group to the line. * */ strcat(group_buf, " "); /* Patch a blank behind it */ strcat(hex_line, group_buf); /* Append to bigger line */ if (bytes_to_dump <= 0) /* If I hit end-of-row while*/ break; /* processing group, stop */ /* formatting the line now */ } sprintf (output_line, slot_format, line_addr, line_addr, hex_line, byte_line); printf("%s\n", output_line); } return; } /* dump_slot_info() - Function to somply present info about the slot*/ /* Parameters: */ /* - Slot number */ /* - The raw offset to the row, including flags */ /* - The offset to the row. */ /* - Length of the row, including flags (raw) */ /* - Refined row length, no flags */ dump_slot_info(slot_n, raw_row_offs, row_offs, raw_row_sz, row_sz) short slot_n, row_offs, row_sz; unsigned short raw_row_offs, raw_row_sz; { static char slot_format[] = "\nSlot %03d at offset: <%04X/%04d>; Length: <%04X; %04d>\n"; /* */ printf(slot_format, slot_n, raw_row_offs, row_offs, raw_row_sz, row_sz); } dump_log_page(pb, psiz, fmw) char *pb; /* -> Page buffer */ short psiz; /* Length of buffer */ fm_width_t fmw; /* Format width flag */ { dummy_call("dump_log_page"); } bytohex(inbyte, outbuf) unsigned char inbyte; char *outbuf; /* Pointer to where to put result */ { unsigned char nybbles[2]; /* To separate nybbles */ int lc; /* Loop counter */ static unsigned char below_10_add = 0x30, above_10_add = 0x37; nybbles[0] = (inbyte >> 4) & 0xF; nybbles[1] = inbyte & 0xF; for (lc = 0; lc < 2; lc++) { unsigned char nyb_add; /* Value to add to a nybble to make */ /* it an ascii printable character */ if (nybbles[lc] <= 9) /* Digit below 10 (dec)? */ outbuf[lc] = nybbles[lc] + below_10_add; /* Straightforward addition */ else outbuf[lc] = nybbles[lc] + above_10_add; } return 2; } /*====================================================================*/ int line_is_null(pgbuf, line_addr, bpl) unsigned char *pgbuf; int line_addr, bpl; { int rval = 1; /* Assume true - that line is null */ int lc; /* Loop counter */ for (lc = 0; lc < bpl; lc++) { if (pgbuf[line_addr+lc] != 0) /* Found a non-null in range*/ break; /* get out of loop now */ } if (lc < bpl) /* Did I finish the scan? */ rval = 0; /* No - Line is not null */ return rval; } @EOF chmod 664 pgdump.c echo x - Makefile cat >Makefile <<'@EOF' # Makefile for pgdump # TARGET = pgdump OBJECTS = pgdump.o SOURCE = $(OBJECTS:.o=.c) CC = cc CFLAGS = -Aa +O2 -D_INCLUDE_POSIX_SOURCE .c.o: $(CC) $(CFLAGS) -c $*.c $(TARGET): $(OBJECTS) $(CC) +O2 -o $@ $(OBJECTS) $(OBJECTS): $(SOURCE) @EOF chmod 664 Makefile echo x - README cat >README <<'@EOF' ##### #### ##### # # # # ##### # # # # # # # # ## ## # # # # # # # # # # ## # # # ##### # ### # # # # # # ##### # # # # # # # # # # # #### ##### #### # # # This is a brief (for me) explanation of a fairly simple program: pgdump. The purpose of this program is to print a dump of a page in an Informix OnLine system. As it happens, it CAN dump part of ANY file but it assumes it is an OnLine page and the predictably unpredictable results will occur. Usage: The pgdump command requires a few basic parameters, like the name of the device, the page offset (starting from 0) where to start dumping pages, the number of pages to dump, y'know, the usual stuff. Should you forget the parameters (easy to do when there are lots of 'em), try the -h option: $ pgdump -h Usage: pgdump -h # This help page pgdump -d # Required [-s] # Page size in K (Def: 2) [-p] # Page Number; Default 0 [-n] # Number of pages to dump [-f] # Format of the dump: Combination of: x Hex dump - 4 byte columns (Default) s Hex dump of Slots, where applicable For example, suppose I run onstat -d and determine that the rootdbs is on device /dev/rdisk1 at offset 0 (let's keep it simple). The following command will print a neat 80-column dump of the configuration reserved page: pgdump -d /dev/rdisk1 -p 1 -n 1 This produces a dump that looks like this on my system: Device: ; Page <0001/0x0001>; OnLine Page ID: <1/1> ----------------------------------------------------------------- 0000/0000||00100001 16B7B2B0 004B1000 049C0234 |~~~~~~~~~K~~~~~4| 0016/0010||00000000 00000000 524F4F54 4E414D45 |~~~~~~~~ROOTNAME| 0032/0020||20726F6F 74646273 00524F4F 54504154 | rootdbs~ROOTPAT| 0048/0030||48202F64 65762F63 68756E6B 3100524F |H /dev/chunk1~RO| 0064/0040||4F544F46 46534554 20300052 4F4F5453 |OTOFFSET 0~ROOTS| 0080/0050||495A4520 33313230 3030004D 4952524F |IZE 312000~MIRRO| 0096/0060||52203100 4D495252 4F525041 54482000 |R 1~MIRRORPATH ~| 0112/0070||4D495252 4F524F46 46534554 20300050 |MIRROROFFSET 0~P| 0128/0080||48595344 42532070 68797369 6C6F6700 |HYSDBS physilog~| 0144/0090||50485953 46494C45 20383030 30004C4F |PHYSFILE 8000~LO| 0160/00A0||4746494C 45532036 004C4F47 53495A45 |GFILES 6~LOGSIZE| 0176/00B0||20383030 3030004D 53475041 5448202F | 80000~MSGPATH /| 0192/00C0||7573722F 696E666F 726D6978 2F6F6E6C |usr/informix/onl| 0208/00D0||696E652E 6C6F6700 434F4E53 4F4C4520 |ine.log~CONSOLE | 0224/00E0||2F757372 2F696E66 6F726D69 782F6D65 |/usr/informix/me| 0240/00F0||73736167 652E6C6F 67005441 50454445 |ssage.log~TAPEDE| 0256/0100||56202F75 73722F69 6E666F72 6D69782F |V /usr/informix/| 0272/0110||61726368 2E676172 70616300 54415045 |arch.garpac~TAPE| 0288/0120||424C4B20 31303234 00544150 4553495A |BLK 1024~TAPESIZ| 0304/0130||45203130 30303030 30004C54 41504544 |E 1000000~LTAPED| 0320/0140||4556202F 6465762F 6E756C6C 004C5441 |EV /dev/null~LTA| 0336/0150||5045424C 4B203130 3234004C 54415045 |PEBLK 1024~LTAPE| 0352/0160||53495A45 20343039 36303030 00444253 |SIZE 4096000~DBS| 0368/0170||45525645 524E414D 45206870 67617200 |ERVERNAME hpgar~| 0384/0180||53455256 45524E55 4D203000 44454144 |SERVERNUM 0~DEAD| 0400/0190||4C4F434B 5F54494D 454F5554 20363000 |LOCK_TIMEOUT 60~| --- SNIP --- OK, what does that all mean? The first column in each line, with the nnnn/nnnn numbers, is the offset within the page, of that line. It is displayed first in decimal, then in hex. The next 4 columns are bytes in order of their addresses. I have no intention of worrying about the byte order in 32 and 16-bit integers. That is not the purpose of the pgdump utility. The next broad column is, obviously, the same bytes in printable format. Of course, unprintables have been replaced with the tilde (~) character. One more comment about parameter convention: The same command could have been typed as: $ pgdump -d/dev/rdisk1 -p1 -n1 i.e. No blanks between an option and its value. (getopts() just hppens to work that way.) Now this dump is not much easier to read than your run-of-the-mill dump (though I have yet to get something this readable from the UNIX od command). The problem is separating rows. For this we have another option. Note that I omitted the -f option from the sample command. Let's try the same command with this variation: $ pgdump -d /dev/chunk1 -n 1 -p 1 -fs Here I specified slot formatting, producing the following: Event ID: <16B7B2B0>; Slots: <075>; Page type:<1000/PG_RSRVD/OnLine reserved Page> Total free bytes: <0564>; Free area begins at: <0x049C> Slot 001 at offset: <0018/0024>; Length: <0011; 0017> 0000/0000||524F4F54 4E414D45 20726F6F 74646273 |ROOTNAME rootdbs| 0016/0010||00 |~ | Slot 002 at offset: <0029/0041>; Length: <0015; 0021> 0000/0000||524F4F54 50415448 202F6465 762F6368 |ROOTPATH /dev/ch| 0016/0010||756E6B31 00 |unk1~ | Slot 003 at offset: <003E/0062>; Length: <000D; 0013> 0000/0000||524F4F54 4F464653 45542030 00 |ROOTOFFSET 0~ | Slot 004 at offset: <004B/0075>; Length: <0010; 0016> 0000/0000||524F4F54 53495A45 20333132 30303000 |ROOTSIZE 312000~| 0016/0010||4D |M | Slot 005 at offset: <005B/0091>; Length: <0009; 0009> 0000/0000||4D495252 4F522031 00 |MIRROR 1~ | Slot 006 at offset: <0064/0100>; Length: <000C; 0012> 0000/0000||4D495252 4F525041 54482000 |MIRRORPATH ~ | Slot 007 at offset: <0070/0112>; Length: <000F; 0015> 0000/0000||4D495252 4F524F46 46534554 203000 |MIRROROFFSET 0~ | Slot 008 at offset: <007F/0127>; Length: <0011; 0017> 0000/0000||50485953 44425320 70687973 696C6F67 |PHYSDBS physilog| 0016/0010||00 |~ | Slot 009 at offset: <0090/0144>; Length: <000E; 0014> 0000/0000||50485953 46494C45 20383030 3000 |PHYSFILE 8000~ | Slot 010 at offset: <009E/0158>; Length: <000B; 0011> 0000/0000||4C4F4746 494C4553 203600 |LOGFILES 6~ | Slot 011 at offset: <00A9/0169>; Length: <000E; 0014> 0000/0000||4C4F4753 495A4520 38303030 3000 |LOGSIZE 80000~ | Slot 012 at offset: <00B7/0183>; Length: <0021; 0033> 0000/0000||4D534750 41544820 2F757372 2F696E66 |MSGPATH /usr/inf| 0016/0010||6F726D69 782F6F6E 6C696E65 2E6C6F67 |ormix/online.log| 0032/0020||00 |~ | Slot 013 at offset: <00D8/0216>; Length: <0022; 0034> 0000/0000||434F4E53 4F4C4520 2F757372 2F696E66 |CONSOLE /usr/inf| 0016/0010||6F726D69 782F6D65 73736167 652E6C6F |ormix/message.lo| 0032/0020||6700 |g~ | --- SNIP --- Now we're getting somewhere! If you can locate a corrupted index page (the online.log will give you its page address) by some other means, you can dump that page and examine the index entries. Of course, that means you are (or have been) an Informix propeller-head but you can really dazzle the tech support guy with the detailed information and get escalated to advanced support real quick! Another note about the parameters: Supposing I run the same command as the first but with one slight variation: $ pgdump -d /dev/chunk1 -n 1 -p 1 -fX Note the capital X in the format option. This will give you the basic same dump as the first example (where I omitted the format option). However, this will be in wide format for your 132-character printer. The slot dump can also specify -fS (instead of -fs) to get a wide format slot dump. You can get both dumps in one output, page by page, by specifying -fxs or -fXS. No matter what order you specify the format, you will always get the hex dump of each page before the slot dump for that page. Note that the syntax allows you to specify -fXs but, due to a minor flaw, mixing formats produces ugly results in the slot dump. Hey, you wouldn't want to mix them anyway, wooould you? ;-) Note that the default assumed page size is 2K. You can override this on the command line with the -s option, e.g. -s4 for a 4-K page size. Limitations: - You must be user informix (or root) in order to run this program. The file permissions on the devices would prevent any other user from getting into the disk devices this way. - pgdump is not (yet) smart enough to dump a whole blob page. It is smart enough to refuse to produce a slot dump if given a blob page to dump. Other limitations will be discussed in the file TO-DO. Installation: If you are reading this, you have obviously untarred the installation file already. The "make" command is all you need to run. So far, the entire utility fits in a single C file. pgdump was developed on an HP-UX with an old ANSI-C compiler. Perhaps on your system the -D option is not needed, or you can use gcc compiler, which will probably make life easier in any case. If you do not happen to have an ANSI-C compiler, you will need to modify the Makefile and change the options to the cc command. ====EOF==== @EOF chmod 664 README echo x - TO-DO cat >TO-DO <<'@EOF' Features I'd like to add to pgdump: - Dump log records for -s option, instead of the whole-page hex dump. - Intelligent Blob Dump When the device/offset combination point into a blob page on a blobspace, dump the entire page, not merely 2 or 4K. Also dump the 36-byte blob-page header, with flags interpreted. - Null line skipping doring the slot dump. Currently, pgdump skips multiple null lines only the whole-page dump. - Mimic some functionality of some oncheck options, chasing up from the partition table-space and/or the database tablespace to dump pages of a named table, with the system off-line. This requires inteligent access via the partition tblspace. - Ability to recognize the key part of an index row and separate it from the rowid list. This requires info on the index taken from the partition header for the table. @EOF chmod 664 TO-DO exit 0