Subject: Software: pgdump dumps pages from an OnLine system
From: Jacob Salomon <jake@garpac.com>
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 <jake@garpk200> 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 <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
extern char *optarg;
extern int optind, opterr, optopt;
#include <errno.h>

/*
pgdump:
This program dumps a page that is presumably from an OnLine system.
Usage:
pgdump  -h                      HELP page
        -d<device/file path>    No default
        -s<k>                   Page Size in K;  Default 2
        -p<n>                   Page Number; Default 0
        -n<pages>               Number of pages to dump
        -f<format>              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<device-path>    # Required",
           "      [-s<page-size>]     # Page size in K (Def: 2)",
           "      [-p<n>]             # Page Number; Default 0",
           "      [-n<pages>]         # Number of pages to dump",
           "      [-f<format>]        # 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<device-path>    # Required
      [-s<page-size>]     # Page size in K (Def: 2)
      [-p<n>]             # Page Number; Default 0
      [-n<pages>]         # Number of pages to dump
      [-f<format>]        # 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: </dev/chunk1>; 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