/*
 *  pagedoc.c -- postgres page doctor.
 *
 *	This program understands page formats for postgres heap and index
 *	relations, and can be used to dump the pages.  it doesn't know
 *	about user data in tuples; it only knows about tuple headers.
 *
 *	Usage:
 *		pagedoc [-h|b|r] [-d level] filename
 *
 *		-h, -b, and -r are for heap, btree, and rtree files,
 *			respectively.
 *		-d level sets the detail level:  0 is just page summaries,
 *			1 is page summaries plus line pointer summaries,
 *			and 2 is 1 plus tuples.
 *
 *	-h and -d0 are the defaults.
 */

#include <stdio.h>
#include <sys/file.h>

static char *RcsId = "$Header: /private/postgres/src/support/RCS/pagedoc.c,v 1.1 1992/08/12 03:24:01 mao Exp $";

extern char	*optarg;
extern int	optind, opterr;

typedef struct ItemIdData {
    unsigned		lp_off:13,		/* offset to tuple */
			lp_flags:6,		/* itemid flags */
			lp_len:13;		/* length of tuple */
} ItemIdData;

#define LP_USED		0x01	/* this line pointer is being used */
#define LP_IVALID	0x02	/* this tuple is known to be insert valid */
#define LP_DOCNT	0x04	/* this tuple continues on another page */
#define LP_CTUP		0x08	/* this is a continuation tuple */
#define LP_LOCK		0x10	/* this is a lock */
#define LP_ISINDEX	0x20	/* this is an internal index tuple */

typedef struct ItemPointerData {
    unsigned short	block[2];
    unsigned short	offset;
} ItemPointerData;

typedef struct HeapTupleData {
    unsigned int	t_len;
    ItemPointerData	t_ctid;
    ItemPointerData	t_chain;
    union {
	ItemPointerData		l_ltid;
	char			*l_lock;	/* actually a RuleLock */
    } t_lock;
    unsigned long	t_oid;
    unsigned short	t_cmin;
    unsigned short	t_cmax;
    unsigned long	t_xmin;
    unsigned long	t_xmax;
    unsigned long	t_tmin;
    unsigned long	t_tmax;
    unsigned short	t_natts;
    char		t_vtype;
    char		t_infomask;
    char		t_locktype;
    char		t_bits[1];
} HeapTupleData;

typedef struct IndexTupleData {
    ItemPointerData		t_tid;

#define ITUP_HASNULLS	0x8000
#define ITUP_HASVARLENA	0x4000
#define ITUP_HASRULES	0x2000
#define ITUP_LENMASK	0x1fff

    unsigned short		t_info;
} IndexTupleData;

typedef struct BTItemData {
    unsigned long	bti_oid;
    IndexTupleData	bti_itup;
} BTItemData;

typedef struct PageHeaderData {
    unsigned short	pd_lower;
    unsigned short	pd_upper;
    unsigned short	pd_special;
    unsigned short	pd_opaque;
    ItemIdData		pd_linp[1];		/* line pointers start here */
} PageHeaderData;

typedef struct BTPageOpaqueData {
	unsigned short	btpo_prev;
	unsigned short	btpo_next;
	unsigned short	btpo_flags;
} BTPageOpaqueData;

typedef struct BTMetaPageData {
    unsigned long	btm_magic;
    unsigned long	btm_version;
    unsigned long	btm_root;
    unsigned long	btm_freelist;
} BTMetaPageData;

typedef struct RTreePageOpaqueData {
    unsigned long	rtpo_flags;

#define RTF_LEAF	(1 << 0)

} RTreePageOpaqueData;

#define BTP_LEAF	(1 << 0)
#define BTP_ROOT	(1 << 1)
#define BTP_FREE	(1 << 2)

#define	HEAP	0
#define BTREE	1
#define RTREE	2

extern void		pagedoc();
extern char		*readpage();
extern void		heappage();
extern void		btreepage();
extern void		rtreepage();
extern void		showlinp();
extern void		showheaptup();
extern void		showindextup();
extern int		PageGetNEntries();
extern unsigned long	ItemPointerGetBlockNumber();

main(argc, argv)
    int argc;
    char **argv;
{
    int reltype;
    char *relname;
    int level;
    char c;
    int errs;
    int fd;

    errs = 0;
    level = 0;
    reltype = HEAP;

    while ((c = getopt(argc, argv, "hbrd:")) != EOF) {
	switch (c) {
	    case 'h':
		reltype = HEAP;
		break;
	    case 'b':
		reltype = BTREE;
		break;
	    case 'r':
		reltype = RTREE;
		break;
	    case 'd':
		level = atoi(optarg);
		if  (level < 0)
		    errs++;
		break;
	    default:
		errs++;
		break;
	}
    }

    if (optind != argc - 1)
	errs++;

    if (errs) {
	fprintf(stderr, "usage: %s [-hbr] [-d level] file\n", argv[0]);
	fflush(stderr);
	exit (1);
    }

    relname = argv[optind];
    if ((fd = open(relname, O_RDONLY, 0600)) < 0) {
	perror(relname);
	fflush(stderr);
	exit (1);
    }

    pagedoc(fd, level, reltype);

    if (close(fd) < 0) {
	fprintf(stderr, "close: ");
	perror(relname);
	fflush(stderr);
	exit (1);
    }

    exit (0);
}

void
pagedoc(fd, level, reltype)
    int fd;
    int level;
    int reltype;
{
    int i;
    char *buf;

    i = 0;
    while ((buf = readpage(fd)) != (char *) NULL) {
	switch (reltype) {
	    case HEAP:
		heappage(i, buf, level);
		break;
	    case BTREE:
		btreepage(i, buf, level);
		break;
	    case RTREE:
		rtreepage(i, buf, level);
		break;
	    default:
		/* should never happen */
		fprintf(stderr, "invalid reltype %d\n", reltype);
		fflush(stderr);
		return;
	}
	i++;
    }
}

char pagebuf[8192];

char *
readpage(fd)
    int fd;
{
    int nbytes;

    nbytes = read(fd, pagebuf, 8192);
    if (nbytes == 0)
	return ((char *) NULL);
    if (nbytes < 0) {
	perror("read");
	fflush(stderr);
	exit (1);
    }
    if (nbytes != 8192) {
	fprintf(stderr, "read: expected 8192 bytes, got %d (partial page?)\n",
			nbytes);
	fflush(stderr);
	exit (1);
    }

    return (pagebuf);
}

void
heappage(pgno, buf, level)
    int pgno;
    char *buf;
    int level;
{
    PageHeaderData *phdr;
    ItemIdData *linp;
    int nlinps;
    int i;
    HeapTupleData *htup;

    phdr = (PageHeaderData *) buf;
    nlinps = PageGetNEntries(phdr);
    printf("[%03d]\tlower: %d upper: %d special: %d opaque 0x%hx (%d items)\n",
		pgno, phdr->pd_lower, phdr->pd_upper, phdr->pd_special,
		phdr->pd_opaque, nlinps);
    if (phdr->pd_lower < 8)
	printf("    **** lower too low!\n");
    if (phdr->pd_lower > 8192)
	printf("    **** lower too high!\n");
    if (phdr->pd_upper < 12)
	printf("    **** upper too low!\n");
    if (phdr->pd_upper > 8192)
	printf("    **** upper too high!\n");
    if (phdr->pd_special > 8192)
	printf("    **** special too high!\n");

    /* level 0 is page headers only */
    if (level == 0)
	return;

    for (i = 0, linp = &(phdr->pd_linp[0]); i < nlinps; linp++, i++) {
	showlinp(i, linp);
	/* level > 1 means show everything */
	if (level > 1) {
	    htup = (HeapTupleData *) &(buf[linp->lp_off]);
	    showheaptup(htup);
	}
    }
}

void
btreepage(pgno, buf, level)
    int pgno;
    char *buf;
    int level;
{
    PageHeaderData *phdr;
    ItemIdData *linp;
    int nlinps;
    int i;
    BTItemData *bti;
    BTPageOpaqueData *btpo;
    BTMetaPageData *meta;

    /* if this is the btree metadata page, handle it specially */
    if (pgno == 0) {
	meta = (BTMetaPageData *) buf;
	printf("[meta]\tmagic 0x%06lx version %ld root %ld freelist %ld\n",
		meta->btm_magic, meta->btm_version, meta->btm_root,
		meta->btm_freelist);
	if (meta->btm_magic != 0x053162)
	    printf("    **** magic number is bogus!\n");
	return;
    }

    phdr = (PageHeaderData *) buf;
    nlinps = PageGetNEntries(phdr);
    printf("[%03d]\tlower: %d upper: %d special: %d opaque 0x%hx (%d items)\n",
		pgno, phdr->pd_lower, phdr->pd_upper, phdr->pd_special,
		phdr->pd_opaque, nlinps);
    btpo = (BTPageOpaqueData *) &(buf[phdr->pd_special]);
    printf("\tprev %d next %d", btpo->btpo_prev, btpo->btpo_next);
    if (btpo->btpo_flags & BTP_LEAF)
	printf(" <leaf>");
    if (btpo->btpo_flags & BTP_ROOT)
	printf(" <root>");
    if (!(btpo->btpo_flags & (BTP_LEAF|BTP_ROOT)))
	printf(" <internal>");
    if (btpo->btpo_flags & BTP_FREE)
	printf(" <free>");
    if (btpo->btpo_next != 0)
	printf(" (item 001 is high key on page)");
    else
	printf(" (no high key)");
    printf("\n");
    if (phdr->pd_lower < 8)
	printf("    **** lower too low!\n");
    if (phdr->pd_lower > 8192)
	printf("    **** lower too high!\n");
    if (phdr->pd_upper < 12)
	printf("    **** upper too low!\n");
    if (phdr->pd_upper > 8192)
	printf("    **** upper too high!\n");
    if (phdr->pd_special > 8192)
	printf("    **** special too high!\n");

    /* level 0 is page headers only */
    if (level == 0)
	return;

    for (i = 0, linp = &(phdr->pd_linp[0]); i < nlinps; linp++, i++) {
	showlinp(i, linp);
	/* level > 1 means show everything */
	if (level > 1) {
	    bti = (BTItemData *) &(buf[linp->lp_off]);
	    printf("\t\toid %ld ", bti->bti_oid);
	    showindextup(&bti->bti_itup);
	}
    }
}

void
rtreepage(pgno, buf, level)
    int pgno;
    char *buf;
    int level;
{
    PageHeaderData *phdr;
    ItemIdData *linp;
    int nlinps;
    int i;
    RTreePageOpaqueData *rtpo;
    IndexTupleData *itup;

    phdr = (PageHeaderData *) buf;
    rtpo = (RTreePageOpaqueData *) &(buf[phdr->pd_special]);
    nlinps = PageGetNEntries(phdr);
    printf("[%03d]\tlower: %d upper: %d special: %d opaque 0x%hx %s (%d items)\n",
	    pgno, phdr->pd_lower, phdr->pd_upper, phdr->pd_special,
	    phdr->pd_opaque,
	    ((rtpo->rtpo_flags & RTF_LEAF) ? "<leaf>":"internal"), nlinps);
    if (phdr->pd_lower < 8)
	printf("    **** lower too low!\n");
    if (phdr->pd_lower > 8192)
	printf("    **** lower too high!\n");
    if (phdr->pd_upper < 12)
	printf("    **** upper too low!\n");
    if (phdr->pd_upper > 8192)
	printf("    **** upper too high!\n");
    if (phdr->pd_special > 8192)
	printf("    **** special too high!\n");

    /* level 0 is page headers only */
    if (level == 0)
	return;

    for (i = 0, linp = &(phdr->pd_linp[0]); i < nlinps; linp++, i++) {
	showlinp(i, linp);
	/* level > 1 means show everything */
	if (level > 1) {
	    itup = (IndexTupleData *) &(buf[linp->lp_off]);
	    showindextup(itup);
	}
    }
}

void
showlinp(itemno, linp)
    int itemno;
    ItemIdData *linp;
{
    int off;

    printf("\t{%03d}\t off %d length %d flags [",
	   itemno + 1, linp->lp_off, linp->lp_len);
    if (linp->lp_flags & LP_USED)
	printf(" LP_USED");
    if (linp->lp_flags & LP_IVALID)
	printf(" LP_IVALID");
    if (linp->lp_flags & LP_DOCNT)
	printf(" LP_DOCNT");
    if (linp->lp_flags & LP_CTUP)
	printf(" LP_CTUP");
    if (linp->lp_flags & LP_LOCK)
	printf(" LP_LOCK");
    if (linp->lp_flags & LP_ISINDEX)
	printf(" LP_ISINDEX");
    printf(" ]\n");
    if ((off = linp->lp_off) > 8192)
	printf("\t    **** off too high!\n");
    if (off & 0x3)
	printf("\t    **** off is bogus -- unaligned tuple pointer!");
    if (linp->lp_len > 8192)
	printf("\t    **** len too high!\n");
    if (!(linp->lp_flags & LP_USED))
	printf("\t    **** item not used!\n");
}

void
showheaptup(htup)
    HeapTupleData *htup;
{
    printf("\t\tlen %d ctid <%d,0,%d> chain <%d,0,%d> oid %ld\n",
	   htup->t_len, ItemPointerGetBlockNumber(htup->t_ctid.block),
	   htup->t_ctid.offset, ItemPointerGetBlockNumber(htup->t_chain.block),
	   htup->t_chain.offset, htup->t_oid);
    printf("\t\tcmin/max %d/%d xmin/max %ld/%ld tmin/max %ld/%ld\n",
	   htup->t_cmin, htup->t_cmax, htup->t_xmin, htup->t_xmax,
	   htup->t_tmin, htup->t_tmax);
    printf("\t\tnatts %d vtype %c infomask 0x%x locktype %c\n",
	   htup->t_natts, htup->t_vtype, htup->t_infomask, htup->t_locktype);
}

void
showindextup(itup)
    IndexTupleData *itup;
{
    printf("heap tid <%d,0,%d> info [",
	   ItemPointerGetBlockNumber(itup->t_tid.block), itup->t_tid.offset);
    if (itup->t_info & ITUP_HASNULLS)
	printf(" HASNULLS");
    if (itup->t_info & ITUP_HASVARLENA)
	printf(" HASVARLENA");
    if (itup->t_info & ITUP_HASRULES)
	printf(" HASRULES");
    printf(" ] length %d\n", itup->t_info & ITUP_LENMASK);
}

int
PageGetNEntries(phdr)
    PageHeaderData *phdr;
{
    int n;

    n = (phdr->pd_lower - (2 * sizeof(unsigned long))) / sizeof(ItemIdData);

    return (n);
}

unsigned long
ItemPointerGetBlockNumber(iptr)
    ItemPointerData *iptr;
{
    unsigned long b;

    b = (unsigned long) ((iptr->block[0] << 16) + iptr->block[1]);

    return (b);
}
