/*
 * vacuumd.c --
 *	The POSTGRES vacuum daemon.
 *
 * NOTES:
 *	I don't know if the HUP signal handler is exactly right.
 *
 *	This file contains very bad coding style.  Four-space indentions
 *	are used because the functions are over a hundred lines long!!!
 */

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

#include "tmp/postgres.h"

RcsId("$Header: RCS/vacuumd.c,v 1.22 90/10/16 00:28:53 kemnitz Exp $");

#include "utils/log.h"
#include "tmp/miscadmin.h"
#include "tmp/portal.h"
#include "access/xcxt.h"

#include "daemon.h"
#include "suputils.h"
#include "access/genam.h"
#include "access/heapam.h"
#include "storage/itemid.h"

/*
 * VACTIMOPT --
 *	Write heap pages with times stored.
 *
 * Note:
 *	This should be done (automatically) for heavy archive access relations.
 */
#define	VACTIMOPT	1

/* #define VACUUMDLOG */	/* XXX elog(should dlog!!!) vacuum actions */
/* #define VACUUMDNOISE */
/* #define VACOUTPUT */
/* #define NOQUIET */
/* #define VACDEBUG */
/* #define VACONESHOT */
/* #define TESTDAEMON */	/* For testing just the daemon portion */

#ifdef	TESTDAEMON
#ifndef NOQUIET
#define NOQUIET	1
#endif
#endif	/* TESTDAEMON */

#ifdef	VACOUTPUT
#ifndef NOQUIET
#define NOQUIET	1
#endif
#ifndef PRINTLIST
#define PRINTLIST	1
#endif
#endif	/* VACOUTPUT */

#ifdef	VACDEBUG
#define PRINTLIST	1
#undef IFDEBUG
#define IFDEBUG(a)	a
#endif	/* VACDEBUG */

#ifdef	TESTBUFREL
#define PRINTLIST	1
#endif	/* TESTBUFREL */

static char	Buf[MAXPGPATH];
static char	*cmdname = (char *) NULL;
static char	*datname = (char *) NULL;
static short	cmdpid = 0;
static jmp_buf	Warn_restart;

char pg_pathname[256];

Relation	getarchrel();

/*
 *  The portal that we use to control transactions is global.  We don't
 *  nest transactions anywhere in this program; each ends before the
 *  next begins, so we can re-use the portal.
 */

Portal	portal;

/*
 *	WarnSignalHandler
 *
 *	An elog(WARN) sends a HUP to the process.
 *	Catch the HUP and clean up from the aborted transaction.
 */
WarnSignalHandler()
{
	int		mask;
	extern jmp_buf	Warn_restart;

	mask = sigblock(sigmask(SIGHUP)) & ~sigmask(SIGHUP);
	if (IsTransactionState()) {
#ifdef	VACUUMDLOG
		elog(NOTICE, "%s: %d: aborting transaction %s on %s",
			cmdname, cmdpid,
			TransactionIdFormString(GetCurrentTransactionId()),
			datname);
#endif	/* VACUUMDLOG */
		AbortCurrentTransaction();
	}

#ifdef	VACUUMDLOG
	IFDEBUG(elog(NOTICE, "%s: %d: pausing on new mask = %#o\n",
		     cmdname, cmdpid, mask));
#endif	/* VACUUMDLOG */
	(void) sigpause(mask);
	longjmp(Warn_restart);
}

/*
 *	KillSignalHandler
 *
 *	"destroydb" and "createdb -e -v off" send one of
 *	SIGKILLDAEMON1 or SIGKILLDAEMON2 to the process.
 *	Catch the HUP and clean up from the aborted transaction.
 */
KillSignalHandler()
{
	if (IsTransactionState()) {

#ifdef	VACUUMDLOG
		elog(NOTICE, "%s: %d: aborting transaction %s on %s",
			cmdname, cmdpid,
			TransactionIdFormString(GetCurrentTransactionId()),
			datname);
#endif	/* VACUUMDLOG */
		AbortCurrentTransaction();
	}

	DBNameCleanupVacuumDaemon(datname);

#ifdef	VACUUMDLOG
	elog(NOTICE, "%s: %d: dying gracefully ...", cmdname, cmdpid);
#endif	/* VACUUMDLOG */

	exitpg(0);
}


extern bool ProcessPage ARGS((Relation, Page, int blockNumber, int pageNumber));


main(argc, argv)
	int	argc;
	char	*argv[];
{
	extern char	*getenv();
	char		**curarg;
	extern char	*DBName;
	
	cmdname = argv[0];
	cmdpid = getpid();

	curarg = &argv[0];
	datname = (char *) NULL;

	while (*(++curarg) != (char *) NULL) {
		if (**curarg != '-')
			DBName = datname = *curarg;
	}

	if (datname == (char *) NULL) {
		if (!PointerIsValid((DBName = datname = getenv("USER")))) {
			fprintf(stderr, "%s: failed getenv(\"USER\")",
				cmdname);
			exitpg(1);
		}
	}

	(void) signal(SIGHUP, ProcessExit);
	(void) signal(SIGKILLDAEMON1, ProcessExit);
	(void) signal(SIGKILLDAEMON2, ProcessExit);

#ifndef	NOQUIET
	{
		register	fd;
		
		fflush(stdout);
		fflush(stderr);
		close(0);
		fd = open("/dev/null", O_RDWR);
		(void) dup2(fd, 1);
		(void) dup2(fd, 2);
	}
#endif /* !NOQUIET */

	DBNameRegisterVacuumDaemon(datname, cmdpid);

	InitPostgres(Buf, datname);

	if (chdir(Buf) < 0) {
		/*
		DBNameCleanupVacuumDaemon(datname);
		*/
		elog(FATAL, "%s: %d: chdir(%s) failed: %m",
		     cmdname, cmdpid, Buf);
	}

	if (setuid(geteuid()) < 0) {
		/*
		DBNameCleanupVacuumDaemon(datname);
		*/
		elog(FATAL, "%s: %d: setuid(%d) failed: %m",
		     cmdname, cmdpid, geteuid());
	}

	(void) signal(SIGHUP, WarnSignalHandler);
	(void) signal(SIGKILLDAEMON1, KillSignalHandler);
	(void) signal(SIGKILLDAEMON2, KillSignalHandler);

	if (setjmp(Warn_restart)) {
#ifdef	VACUUMDLOG
		elog(NOTICE, "%s: %d: restarting vacuuming of database %s ...",
		     cmdname, cmdpid, datname);
#endif	/* VACUUMDLOG */
	} else {
#ifdef	VACUUMDLOG
		elog(NOTICE, "%s: %d: starting to vacuum database %s ...",
		     cmdname, cmdpid, datname);
#endif	/* VACUUMDLOG */
	}

	StartVacuum();

	/*
	DBNameCleanupVacuumDaemon(datname);
	*/

#ifdef	VACUUMDLOG
	IFDEBUG(elog(NOTICE, "%s: %d: exiting normally ...", cmdname, cmdpid));
#endif	/* VACUUMDLOG */
}

#ifdef TESTDAEMON

StartVacuum()
{
	Portal masterXact;

	elog(NOTICE, "%s: %d: beginning xact", cmdname, cmdpid);

	/*
	 *  The daemon runs in a master transaction.  Individual updates
	 *  and retrieves run inside an inner transaction.  We reuse the
	 *  portal on which the inner xact runs for efficiency.  We create
	 *  it here for simplicity.
	 */

	masterXact = CreatePortal("MasterXact");
	portal = CreatePortal("<blank>");

	StartTransactionCommand(masterXact);

	for (;;) {
		sleep(5);
		elog(NOTICE, "%s: %d: vacuuming", cmdname, cmdpid);
	}
}

#else /* !TESTDAEMON */

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

#include "tmp/c.h"

#include "storage/bufmgr.h"
#include "storage/bufpage.h"
#include "catalog/catname.h"
#include "utils/fmgr.h"
#include "access/ftup.h"
#include "access/genam.h"
#include "access/heapam.h"
#include "utils/lmgr.h"
#include "utils/log.h"
#include "access/tqual.h"
#include "access/htup.h"
#include "access/xlog.h"

#include "catalog/pg_index.h"
#include "catalog/pg_relation.h"

#define LIVELIST 0
#define DEADLIST 1
#define DOCNTLIST 2
#define CTUPLIST 3
#define RULELIST 4
#define TEMPLIST 5
#define NLISTS 6

struct {
    char *listname;
} lists[] = {
	"LIVELIST",
	"DEADLIST",
	"DOCNTLIST",
	"CTUPLIST",
	"RULELIST",
	"TEMPLIST"
};

/* #define VACDEBUG 1 */
/* #define VACOUTPUT 1 */
/* #define VACONESHOT 1 */
/* #define VACPRINTTIMES	1 */

#define RULEREASON 0
#define ABORTREASON 1
#define PURGEREASON 2
#define DOCNTREASONA 3
#define DOCNTREASONB 4
#define CTUPREASON 5
#define LIVEREASON 6
#define CTUPTOLIVEREASON 7
#define DOCNTTOLIVEREASON 8
#define CTUPTODEADREASON 9
#define RULETODEADREASON 10
#define RULETOLIVEREASON 11
#define DEADTOTEMPREASONA 12
#define DEADTOTEMPREASONB 13

struct {
    char *reasonline;
} reason[] = {
    "rule; add to rule list",
    "aborted; add to dead list",
    "purged; add to dead list",
    "docnt; add to docnt list",
    "docnt; add to ctup list",
    "ctup; add to ctup list",
    "default; add to live list",
    "changing from ctup to live list",
    "changing from docnt to live list",
    "changing from ctup to dead list",
    "changing from rule to dead list",
    "changing from rule to live list",
	"changing from dead to temp list",
	"changing from dead to temp list"	/* XXX extraneous? */
};

struct VacTidListData {
	ObjectId VacRelOID;
    ItemPointerData VacTid;
    ItemPointerData VacContTid;
    bool VacHasContTid;
    ItemPointerData VacRuleTid;
    bool VacHasRuleTid;
    int VacReasonCode;
    struct VacTidListData *NextVacTid;
};

struct RelNameListData {
	char relname[20];
	char relkind;
	struct RelNameListData *next;
};

struct {
    struct VacTidListData *ListHead;
    struct VacTidListData *ListTail;
    int ListCount;
} ListHT[NLISTS];

struct RelNameListData *RelNameList;
ObjectId RelOID;
#ifdef	TESTBUFREL
ObjectId	testbufrelOID;	/* XXX style */
#endif	/* TESTBUFREL */
int RelNpages;

extern char *abstimeout(), *reltimeout();	/* XXX adt/date.c */

/* XXX VACSLEEPTIME() is a hack to return a random amount of time less
 * than one day.  It is used to allow the vacuum daemon to wake up at
 * various times during the day (rather than every 24 hours after it
 * is started).  The vacuum daemon ought to be controlled to start at
 * a specific time in the night.
 */
#define VACSLEEPTIME()	\
	((void)srandom(time((char *)0)), 1 + (long)random()%(24*60*60-1))

vacuuminit()
{
	int i;
	struct RelNameListData *ptr, *saveptr;

	for (i=0; i<NLISTS; i++) {
		ListHT[i].ListHead = ListHT[i].ListTail = NULL;
		ListHT[i].ListCount = 0;
	}
	RelOID = InvalidObjectId;
	RelNpages = 0;
	if (RelNameList != NULL) {
		ptr = RelNameList;
		while (ptr != NULL) {
			saveptr = ptr->next;
			free(ptr);
			ptr = saveptr;
		}
	}
	RelNameList = NULL;
}

StartVacuum()
{
    struct RelNameListData *currentrel, *newrel;
    Relation relation;
    HeapScanDesc sdesc;
    HeapTuple tup;
    int i, j, fd;
    Buffer buffer;
    Page page;
#ifdef VACONESHOT
	bool firsttime = true;
#endif
	
	vacuuminit();

#ifdef	VACUUMDNOISE
	IFDEBUG(elog(NOTICE, "%s: %d: starting VacuumStart ...",
		     cmdname, cmdpid));
#endif	/* VACUUMDNOISE */

	portal = CreatePortal("<blank>");

	while (true) {
		StartTransactionCommand(portal);

#ifdef	VACUUMDNOISE
		IFDEBUG(elog(NOTICE, "%s: %d: started xact %s...",
			     cmdname, cmdpid,
			     TransactionIdFormString(
				GetCurrentTransactionId())));
#endif	/* VACUUMDNOISE */

		relation = amopenr(RelationRelationName);
		sdesc = ambeginscan(relation, false, NowTimeQual, 0, 
			(ScanKey) NULL);

		while ((tup = amgetnext(sdesc, false, (Buffer *) 0)) != NULL) {
			newrel = (struct RelNameListData *)
				     calloc(1, sizeof(struct RelNameListData));

			newrel->next = RelNameList;
			RelNameList = newrel;

			strcpy(newrel->relname,
				((struct relation *)GETSTRUCT(tup))->relname);
			newrel->relkind =
				((struct relation *)GETSTRUCT(tup))->relkind;
		}
		amendscan(sdesc);
		amclose(relation);

#ifdef	VACUUMDNOISE
		IFDEBUG(elog(NOTICE, "%s: %d: committing xact %s...",
			     cmdname, cmdpid,
			     TransactionIdFormString(
				GetCurrentTransactionId())));
#endif	/* VACUUMDNOISE */

		CommitTransactionCommand();

		currentrel = RelNameList;
		while (currentrel != NULL) {

			if (currentrel->relkind != 'r') {
				currentrel = currentrel->next;
				continue;
			}

			StartTransactionCommand(portal);

#ifdef	VACUUMDNOISE
			IFDEBUG(elog(NOTICE,
				     "%s: %d: started xact %s...",
				     cmdname, cmdpid,
				     TransactionIdFormString(
				     GetCurrentTransactionId())));
#endif	/* VACUUMDNOISE */

#ifdef VACOUTPUT
			printf("%s; ", currentrel->relname);
#endif
			relation = amopenr(currentrel->relname);

			/* exclusively lock the relation, for now */
			RelationSetLockForWrite(relation);

			RelOID = (ObjectId) relation->rd_att.data[0]->attrelid;
#ifdef VACOUTPUT
			printf("relation OID: %d; ", RelOID);
#ifdef	TESTBUFREL
			if (strcmp(currentrel->relname,
					"testbufrel") == 0) {
				retbufrelOID = RelOID;
				printf("testbufrel OID: %d\n",
					testbufrelOID);
			}
#endif	/* TESTBUFREL */
#endif
			RelNpages = RelationGetNumberOfBlocks(relation);
#ifdef VACOUTPUT
			printf("number of blocks: %d\n", RelNpages);
#endif
			for (j=0; j < RelNpages; j++) {

				buffer = ReadBuffer(relation, j, 0x0);
				page = BufferSimpleGetPage(buffer);

				if (ProcessPage(relation, page, j, 0)) {
					WriteBuffer(buffer);
				} else {
					ReleaseBuffer(buffer);
				}
			}
			amclose(relation);

			if (j != 0) {
#ifdef VACOUTPUT
				printf("before analysis:\n");
				printlistcounts();
#endif
				analyzelists();
#ifdef VACOUTPUT
				printf("after analysis:\n");
				printlistcounts();
#endif
				scavenge_space();
			}

			for (i=0; i<NLISTS; i++)
				deletefromlist(i, InvalidObjectId, (ItemPointer) NULL, 
					(struct VacTidListData **) NULL);

#ifdef	VACUUMDNOISE
			IFDEBUG(elog(NOTICE,
				     "%s: %d: committing xact %s...",
				     cmdname, cmdpid,
				     TransactionIdFormString(
				     GetCurrentTransactionId())));
#endif	/* VACUUMDNOISE */

			CommitTransactionCommand();

			currentrel = currentrel->next;
		}

		/*
		 *  Below, we go to sleep.  If we're interrupted while we're
		 *  asleep, we don't always successfully flush out the
		 *  changes made in the last transaction.  Starting and
		 *  committing a fake transaction guarantees that the stats
		 *  for the last relation we vacuumed get written reliably
		 *  to disk if the vacuum daemon is stopped.
		 *
		 *  Yes, you're right, there really *must* be a better way.
		 */

		StartTransactionCommand(portal);
		CommitTransactionCommand();

		if (RelNameList != NULL) {
			currentrel = RelNameList;
			while (currentrel != NULL) {
				newrel = currentrel->next;
				free(currentrel);
				currentrel = newrel;
			}
		}

		RelNameList = NULL;

#ifdef VACONESHOT
		if (firsttime == true) {
			firsttime = false;
#ifdef VACOUTPUT
	printf("----------------------------------------------------\n");
#endif
		} else
			break;
#else
		sleep(VACSLEEPTIME());
#endif
	}
#ifdef VACONESHOT
	IFDEBUG(elog(NOTICE, "%s: %d: exiting VacuumStart ...",
		     cmdname, cmdpid));
#endif
}

#ifdef VACOUTPUT
printlistcounts()
{
    printf("# of elements on LIVELIST: %d\n", ListHT[LIVELIST].ListCount);
#ifdef	TESTBUFREL
	if (RelOID == testbufrelOID) {
		printlist(LIVELIST);
	}
#endif	/* TESTBUFREL */
    printf("# of elements on DEADLIST: %d\n", ListHT[DEADLIST].ListCount);
#ifdef	TESTBUFREL
	if (RelOID == testbufrelOID) {
		printlist(DEADLIST);
	}
#endif	/* TESTBUFREL */
    printf("# of elements on DOCNTLIST: %d\n", ListHT[DOCNTLIST].ListCount);
#ifdef	TESTBUFREL
	if (RelOID == testbufrelOID) {
		printlist(DOCNTLIST);
	}
#endif	/* TESTBUFREL */
    printf("# of elements on CTUPLIST: %d\n", ListHT[CTUPLIST].ListCount);
#ifdef	TESTBUFREL
	if (RelOID == testbufrelOID) {
		printlist(CTUPLIST);
	}
#endif	/* TESTBUFREL */
    printf("# of elements on RULELIST: %d\n", ListHT[RULELIST].ListCount);
#ifdef	TESTBUFREL
	if (RelOID == testbufrelOID) {
		printlist(RULELIST);
	}
#endif	/* TESTBUFREL */
}
#endif VACOUTPUT

analyzelists()
{
    checkdocntctup();
    checkrules();

	updatestats(RelOID, RelNpages, ListHT[LIVELIST].ListCount);
	updateindexstats(RelOID, ListHT[LIVELIST].ListCount);
}

checkdocntctup()
{
    struct VacTidListData *docntp = ListHT[DOCNTLIST].ListHead;
    struct VacTidListData *ctupp;
    struct VacTidListData *savenext;
    struct VacTidListData *searchlist();
	ObjectId reloid;
    ItemPointer tidp;
    int i, limit = ListHT[DOCNTLIST].ListCount;

    for (i=0; i<limit; i++) {
        Assert(docntp != NULL);
		reloid = docntp->VacRelOID;
        tidp = &(docntp->VacContTid);
        /* follow list of DOCNTs to end.  not really necessary, due to */
        /* order of insertions (1st DOCNT inserted last)... */
        while (1) {
            ctupp = searchlist(CTUPLIST, reloid, tidp, false, false);
            Assert(ctupp != NULL);
            changelists(CTUPLIST, LIVELIST, ctupp, CTUPTOLIVEREASON);
            if (ctupp->VacHasContTid == true) {
				reloid = ctupp->VacRelOID;
                tidp = &(ctupp->VacContTid);
            } else
                break;
        }
        /* note: due to order of insertions (1st DOCNT inserted last), */
        /* no need to check if DOCNTLIST items go on DEADLIST... */
		savenext = docntp->NextVacTid;
        changelists(DOCNTLIST, LIVELIST, docntp, DOCNTTOLIVEREASON);
        docntp = savenext;
    }

    /* fragments go on dead list */
    limit = ListHT[CTUPLIST].ListCount;
    ctupp = ListHT[CTUPLIST].ListHead;
    for (i=0; i<limit; i++) {
        Assert(ctupp != NULL);
		savenext = ctupp->NextVacTid;
        changelists(CTUPLIST, DEADLIST, ctupp, CTUPTODEADREASON);
        ctupp = savenext;
    }
}

checkrules()
{
    struct VacTidListData *rulep = ListHT[RULELIST].ListHead;
    struct VacTidListData *livep;
    struct VacTidListData *savenext;
    struct VacTidListData *searchlist();
    int i, rulelimit = ListHT[RULELIST].ListCount;
    int j, livelimit = ListHT[LIVELIST].ListCount;

    for(i=0; i<rulelimit; i++) {
        Assert(rulep != NULL);
        livep = searchlist(LIVELIST, rulep->VacRelOID, &(rulep->VacTid),
			true, false);
        savenext = rulep->NextVacTid;
        if (livep == NULL)
            changelists(RULELIST, DEADLIST, rulep, RULETODEADREASON);
        else
            changelists(RULELIST, LIVELIST, rulep, RULETOLIVEREASON);
        rulep = savenext;
    }
}

#ifdef	PRINTLIST
printlist(list)
	int list;
{
	int i;
	struct VacTidListData *vtlp;

#ifndef VACDEBUG
	return;
#else /* VACDEBUG */
	for (i=0, vtlp = ListHT[list].ListHead; 
			i < ListHT[list].ListCount && vtlp != NULL;
			i++, vtlp = vtlp->NextVacTid) {
		printf("VacRelOID: %d\n", vtlp->VacRelOID);
		printf("VacTid: "); displaytid(&(vtlp->VacTid)); printf("\n");
		printf("VacContTid: "); displaytid(&(vtlp->VacContTid)); 
			printf("\n");
		printf("VacHasContTid: %d\n", vtlp->VacHasContTid);
		printf("VacRuleTid: "); displaytid(&(vtlp->VacRuleTid)); 
			printf("\n");
		printf("VacHasRuleTid: %d\n", vtlp->VacHasRuleTid);
		printf("VacReasonCode: %d\n", vtlp->VacReasonCode);
		printf("\t(reason translation: %s)\n",
			reason[vtlp->VacReasonCode].reasonline);
	}
#endif /* !VACDEBUG */
}
#endif	/* PRINTLIST */

scavenge_space()
{
	int i;
	BlockNumber blkno;
	Relation relation;
	Relation archrel;
	Buffer buffer;
	Page page;
	ObjectId reloid;
	ItemPointer tidp;
	struct VacTidListData *searchlist(), *vtlp, *listentry;
	ItemId lp;
	struct VacTidListData *auxp;
	OffsetIndex offsetindex;
	Offset upper;
	Size alignedSize;
	RelationTupleForm form;
	double ignore;


	while (ListHT[DEADLIST].ListCount > 0) {
#ifdef VACDEBUG
		printf("DEADLIST follows:\n");
		printlist(DEADLIST);
#endif
		/* free entries on TEMPLIST */
		deletefromlist(TEMPLIST, InvalidObjectId, (ItemPointer) NULL, 
			(struct VacTidListData **) NULL);

		listentry = ListHT[DEADLIST].ListHead;
		reloid = listentry->VacRelOID;
		tidp = &(listentry->VacTid);
		changelists(DEADLIST, TEMPLIST, listentry, DEADTOTEMPREASONA);

		relation = amopen(ListHT[TEMPLIST].ListHead->VacRelOID);

		blkno = ItemPointerGetBlockNumber(
				&(ListHT[TEMPLIST].ListHead->VacTid));
		buffer = RelationGetBuffer(relation, blkno, L_EX);
		page = BufferSimpleGetPage(buffer);

		form = RelationGetRelationTupleForm(relation);
		if (form->relarch != 'n')
			archrel = getarchrel(relation, form);

		while ((vtlp = searchlist(DEADLIST, reloid, tidp, false, true)) 
			!= NULL)
				changelists(DEADLIST, TEMPLIST, vtlp, DEADTOTEMPREASONB);
#ifdef VACDEBUG
		printf("after finding dead items on same page as 1st in list,\n");
		printf("TEMPLIST follows:\n");
		printlist(TEMPLIST);
#endif
		auxp = ListHT[TEMPLIST].ListHead;
		for (i=0; i<ListHT[TEMPLIST].ListCount; i++) {
			Assert(auxp != NULL);
			offsetindex = ItemPointerGetOffsetIndex(&(auxp->VacTid),
				SinglePagePartition);
#ifdef VACDEBUG
			printf("marking the following item as unused: ");
			displaytid(&(auxp->VacTid)); printf("\n");
#endif
			lp = ((PageHeader)page)->pd_linp + offsetindex;
#ifdef VACDEBUG
			printf("\tflags: %x\n", (*lp).lp_flags);
#endif
			/* archive the tuple for archived relations */
			if (form->relarch != 'n')
				doinsert(archrel, (page + lp->lp_off), &ignore);

			/* logically delete heap tuple */
			(*lp).lp_flags &= ~LP_USED;

			/* phys delete index tuple */
			deleteindexrecord(auxp->VacRelOID, &(auxp->VacTid)); 

			/* next one */
			auxp = auxp->NextVacTid;
		}

		/* physically delete heap tups on pg */
		PageRepairFragmentation(page);
		BufferPut(buffer,L_WRITE|L_UN|L_EX);

		amclose(relation);
	}
}

/*
 *  getrelarch -- Get the archive relation descriptor for the supplied
 *		  heap relation.
 *
 *	If no archive relation exists for this heap relation yet, then
 *	we create one.
 */

#define ARCHIVE_PREFIX "a,"

Relation
getarchrel(heaprel, form)
	Relation heaprel;
	RelationTupleForm form;
{
	Relation archrel;
	Name archrelname;
	OID heapOid;

	heapOid = RelationGetRelationId(heaprel);

	archrelname = (Name) palloc(sizeof(NameData));
	sprintf(&archrelname->data[0], "%s%ld", ARCHIVE_PREFIX, heapOid);

	archrel = RelationNameOpenHeapRelation(archrelname->data);
	printf("**** %s opened (0x%lx)\n",
		RelationGetRelationName(archrel),
		archrel);

	return (archrel);
}

/*
 * algorithm for deleteindexrecord(): 
 *
 * scan IndexRelationName, foreach tuple which has heaprelOID == 
 *		OID of heap rel:
 *	amopen the index OID
 *	scan that (index) relation, using
 *		IndexScanGetRetrieveIndexResult() to get 
 *		back heap/index tids
 *	if heap tid of heap tuple == heap tid of index tuple
 *		call RelationDeleteIndexTuple(indexrelation, index tid)
 */

deleteindexrecord(heapreloid, heaptidp)
	ObjectId heapreloid;
	ItemPointer heaptidp;
{
	Relation	heapRelation;
	Relation	indexRelation;
	HeapScanDesc	heapscan;
	IndexScanDesc	indexscan;
	HeapTuple	heapTuple;
	Buffer		buffer;
	TupleDescriptor	heapDescriptor;
	ObjectId	indexRelationId;
	ObjectId	heapRelationId;
	Boolean		attributeIsNull;
	RetrieveIndexResult retrieveIndexResult;
	int nindextuples;

	heapRelation = RelationNameOpenHeapRelation(IndexRelationName);
	heapDescriptor = RelationGetTupleDescriptor(heapRelation);

	heapscan = RelationBeginHeapScan(heapRelation, 0, (struct trange *)NULL,
		0, (ScanKey)NULL);

	while (HeapTupleIsValid(
			heapTuple = HeapScanGetNextTuple(heapscan, 0, &buffer))) {

		indexRelationId = DatumGetObjectId(
			HeapTupleGetAttributeValue(heapTuple, buffer, 
				IndexRelationIdAttributeNumber, heapDescriptor,
				&attributeIsNull));

		heapRelationId = DatumGetObjectId(
			HeapTupleGetAttributeValue(heapTuple, buffer, 
				IndexHeapRelationIdAttributeNumber,
				heapDescriptor, &attributeIsNull));

		/* process heapRelationId, indexRelationID ... */
		if (heapRelationId == heapreloid) {
			/*
			 *	amopen the index OID
			 *	scan that (index) relation, using
			 *		IndexScanGetRetrieveIndexResult()
			 *			to get back heap/index tids
			 *	if heap tid of heap tuple == heap tid
			 *		of index tuple, call
			 *		RelationDeleteIndexTuple()
			 */
			indexRelation = ObjectIdOpenIndexRelation(indexRelationId);
			indexscan = RelationGetIndexScan(indexRelation,
							 false,
							 0, 
							 (ScanKey) NULL);

			nindextuples = 0;

			/* scan the index */
			while (RetrieveIndexResultIsValid(retrieveIndexResult =
				  IndexScanGetRetrieveIndexResult(indexscan, 1))) {

				if (ItemPointerEquals(heaptidp,
					RetrieveIndexResultGetHeapItemPointer(
						retrieveIndexResult))) {

#ifdef VACUUMDNOISE
					fprintf(stderr, "Deleting one tuple from index.\n");
#endif /* VACUUMDNOISE */
					AMdelete(indexRelation,
						RetrieveIndexResultGetIndexItemPointer(
							retrieveIndexResult));

					/*
					 * XXX  at this point, we need to
					 * restore state on the scan.  The
					 * act of deleting the index tuple
					 * may have (actually has) changed the
					 * layout of the base page, which,
					 * in turn, affects the "current
					 * tuple" pointer for the scan.
					 */
				} else {
#ifdef VACUUMDNOISE
					fprintf(stderr, "nindextuples++\n");
#endif /* VACUUMDNOISE */
					nindextuples++;
				}
			}
			AMendscan(indexscan);
			RelationCloseIndexRelation(indexRelation);
		}
	}
	HeapScanEnd(heapscan);
	RelationCloseHeapRelation(heapRelation);
}

/*
 *  updateindexstats -- Given a heap relation, find all indices for it and
 *			update their statistics.
 */

updateindexstats(heapreloid, nindextuples)
	ObjectId heapreloid;
	int nindextuples;
{
	Relation	heapRelation;
	Relation	indexRelation;
	HeapScanDesc	heapscan;
	IndexScanDesc	indexscan;
	HeapTuple	heapTuple;
	Buffer		buffer;
	TupleDescriptor	heapDescriptor;
	ObjectId	indexRelationId;
	ObjectId	heapRelationId;
	Boolean		attributeIsNull;
	RetrieveIndexResult retrieveIndexResult;

	heapRelation = RelationNameOpenHeapRelation(IndexRelationName);
	heapDescriptor = RelationGetTupleDescriptor(heapRelation);

	heapscan = RelationBeginHeapScan(heapRelation, 0, (struct trange *)NULL,
		0, (ScanKey)NULL);

	while (HeapTupleIsValid(
			heapTuple = HeapScanGetNextTuple(heapscan, 0, &buffer))) {

		indexRelationId = DatumGetObjectId(
			HeapTupleGetAttributeValue(heapTuple, buffer, 
				IndexRelationIdAttributeNumber, heapDescriptor,
				&attributeIsNull));

		heapRelationId = DatumGetObjectId(
			HeapTupleGetAttributeValue(heapTuple, buffer, 
				IndexHeapRelationIdAttributeNumber,
				heapDescriptor, &attributeIsNull));

		/* process heapRelationId, indexRelationID ... */
		if (heapRelationId == heapreloid) {
			/*
			 *	amopen the index OID
			 *	get the number of blocks in the index
			 *	update with blocks, tuples
			 */
			indexRelation = ObjectIdOpenIndexRelation(indexRelationId);
			RelationCloseIndexRelation(indexRelation);

			updatestats(indexRelationId, 
				RelationGetNumberOfBlocks(indexRelation),
				nindextuples);
		}
	}
	HeapScanEnd(heapscan);
	RelationCloseHeapRelation(heapRelation);
}

bool ProcessPage(relation, page, blockNumber, pageNumber)
    Relation relation;
    Page    page;
    int blockNumber;
    int pageNumber;
{
    ItemId      lp;
    HeapTuple   tup;
    int     flags, i, nline;
    ItemPointerData tidstruct;
    ItemPointer tidptr;
	bool isctup;
	ABSTIME newtime;
	bool changed = false;

#ifdef	VACPRINTTIMES
	ABSTIME	currenttime;	/* XXX */
#endif	/* VACPRINTTIMES */

#ifdef VACDUMPBPAGE
    printf("\t[%d]:lower=%d:upper=%d:special=%d\n", pageNumber,
        ((PageHeader)page)->pd_lower, ((PageHeader)page)->pd_upper,
        ((PageHeader)page)->pd_special);

    printf("\t:MaxOffsetIndex=%d:InternalFragmentation=%d\n",
        (int16)PageGetMaxOffsetIndex(page),
        PageGetInternalFragmentation(page));
#endif

    nline = 1 + (int16)PageGetMaxOffsetIndex(page);

#ifdef VACDUMPBPAGE
    /* add printing of the specially allocated fields */
{
    int i;
    char    *cp;

    i = PageGetSpecialSize(page);
    cp = PageGetSpecialPointer(page);

    printf("\t:SpecialData=");

    while (i > 0) {
        printf(" 0x%02x", *cp);
        cp += 1;
        i -= 1;
    }
    printf("\n");
}
#endif

    for (i = 0; i < nline; i++) {
        lp = ((PageHeader)page)->pd_linp + i;
        flags = (*lp).lp_flags;

        if ((flags & LP_USED) == 0)
            continue;
        if (flags & LP_LOCK) {
            ItemPointerSimpleSet(&tidstruct, blockNumber, 1 + i);
            addtolist(RULELIST, &tidstruct, 
                (struct VacTidListData *) NULL, (ItemPointer) NULL,
                (ItemPointer) NULL, RULEREASON);
            continue;
        }

#ifdef VACDUMPBPAGE
        printf("[%u,%u,%u]:off=%d:flags=0x%x:len=%d", blockNumber,
            pageNumber, i + 1, (*lp).lp_off, flags, (*lp).lp_len);
        if (flags & LP_USED)
            printf(":USED");
        if (flags & LP_IVALID) 
            printf(":IVALID"); 
        if (flags & LP_DOCNT) {
            PagePartition   partition;
            ItemPointer pointer;

            partition = CreatePagePartition(BLCKSZ,
                PageGetPageSize(page));
            pointer = (ItemPointer)(uint16 *)
                ((char *)page + (*lp).lp_off);
            
            printf(":DOCNT@[%u,%u,%u]",
                ItemPointerGetBlockNumber(pointer),
                ItemPointerGetPageNumber(pointer, partition),
                ItemPointerGetOffsetNumber(pointer, partition));
        }
        if (flags & LP_CTUP)
            printf(":CTUP");
        if (flags & LP_LOCK)
            printf(":LOCK");
        if (flags & LP_IVALID)
            printf(":IVALID");
        putchar('\n');
#endif
		if ((flags & LP_CTUP) == 0) {
			isctup = false;
			tup = (HeapTuple)&((char *)page)[(*lp).lp_off +
				((flags & LP_DOCNT) ? TCONTPAGELEN : 0)];
		} else {
			isctup = true;
			tup = (HeapTuple) NULL;
		}

		/* if has a tuple header (!isctup), check for aborted insert
		 * and purged tup
		 */
		if (!isctup) {
#ifdef CURT
        	/* check for insert that aborted */
			if ((flags & LP_IVALID) == 0) {
				if (TransactionIdIsValid(tup->t_xmin)) {
					if (TransactionIdDidAbort(tup->t_xmin) == true) {
						addtolist(DEADLIST, &(tup->t_ctid), 
						  (struct VacTidListData *) NULL, (ItemPointer) NULL,
						  &(tup->t_lock.l_ltid), ABORTREASON);
						continue;
					} else if (TransactionIdDidCommit(tup->t_xmin) == true &&
						TimeIsValid(tup->t_tmin) == false)
						tup->t_tmin = TransactionIdGetCommitTime(tup->t_xmin);
				}
			}
#else	/* !CURT */
	    /* check for insert that aborted */
	    if ((flags & LP_IVALID) == 0x0) {
		if (AbsoluteTimeIsValid(tup->t_tmin)) {
		    /* set IVALID ! */
		} else {
		    if (TransactionIdIsValid(tup->t_xmin)) {
			if (TransactionIdDidAbort(tup->t_xmin)) {

			    addtolist(DEADLIST, &tup->t_ctid,
				(struct VacTidListData *)NULL,
				(ItemPointer)NULL, &tup->t_lock.l_ltid,
				ABORTREASON);

			    continue;

			} else if (TransactionIdDidCommit(tup->t_xmin)) {
			    /*
			     * Note:
			     *	It should be impossible for a transaction
			     *	to be in progress, due to locking and recovery
			     *	if the transaction did not abort.
			     */
			    tup->t_tmin =
				TransactionIdGetCommitTime(tup->t_xmin);
			    /* set IVALID ! */
				changed = true;
			}
		    }
		}
	    }
#endif	/* !CURT */

#ifdef	CURT
        	/* check for purged tup */
			if (TransactionIdIsValid(tup->t_xmax)) {
				if (TransactionIdDidCommit(tup->t_xmax) == true) {
					if (TimeIsValid(tup->t_tmax) == false) 
						tup->t_tmax = 
							TransactionIdGetCommitTime(tup->t_xmax);

					/* use either restrictive time... either could be invalid,
					 * if both are invalid, preserve forever... 
					 */
#else	/* !CURT */
	    /* check for purged tup */
	    if (!AbsoluteTimeIsValid(tup->t_tmax) &&
		    TransactionIdIsValid(tup->t_xmax) &&
		    TransactionIdDidCommit(tup->t_xmax)) {
		tup->t_tmax = TransactionIdGetCommitTime(tup->t_xmax);
				changed = true;
	    }
#endif	/* !CURT */
#ifdef VACPRINTTIMES
	if (AbsoluteTimeIsValid(relation->rd_rel->relexpires) &&
			AbsoluteTimeIsValid(tup->t_tmax)) {

		printf("relexpires: %s\n", 
			abstimeout(relation->rd_rel->relexpires));
		printf("tup->t_tmax: %s\n", abstimeout(tup->t_tmax));
	}

	if (RelativeTimeIsValid(relation->rd_rel->relpreserved) &&
			AbsoluteTimeIsValid(tup->t_tmax)) {

		printf("relpreserved: %s\n",
			reltimeout(relation->rd_rel->relpreserved));
		currenttime = GetCurrentTransactionStartTime();
		printf("transaction time: %s\n", abstimeout(currenttime));
		newtime = currenttime + relation->rd_rel->relpreserved;
		printf("newtime (current + relpreserved): %s\n",
			abstimeout(newtime));
		printf("tup->t_tmax: %s\n", abstimeout(tup->t_tmax));
	}
#endif	/* VACPRINTTIMES */
#ifdef	CURT
					if ((TimeIsValid(relation->rd_rel->relexpires) == true &&
						tup->t_tmax < relation->rd_rel->relexpires) ||
						(TimeIsValid(relation->rd_rel->relpreserved) == true &&
						tup->t_tmax < (newtime = GetCurrentTime() +
						relation->rd_rel->relpreserved))) {
							addtolist(DEADLIST, &(tup->t_ctid), 
								(struct VacTidListData *) NULL, 
								(ItemPointer) NULL, &(tup->t_lock.l_ltid),
								PURGEREASON);
							continue;
					}
				} else if (TransactionIdDidAbort(tup->t_xmax) == true) {
					tup->t_tmax = InvalidTime;
					PointerStoreInvalidTransactionId(tup->t_xmax);
				}
			}
#else	/* !CURT */
	    if (AbsoluteTimeIsValid(tup->t_tmax)) {
		/* use either restrictive time... either could be invalid,
		 * if both are invalid, preserve forever... 
		 */
		if ((AbsoluteTimeIsValid(relation->rd_rel->relexpires) &&
			tup->t_tmax < relation->rd_rel->relexpires) ||
			(RelativeTimeIsValid(relation->rd_rel->relpreserved) &&
			    tup->t_tmax < (newtime = GetCurrentTransactionStartTime() + 
				relation->rd_rel->relpreserved))) {

		    addtolist(DEADLIST, &tup->t_ctid,
			(struct VacTidListData *)NULL, 
			(ItemPointer)NULL, &tup->t_lock.l_ltid,
			PURGEREASON);

		    continue;
		}
	    } else if (TransactionIdIsValid(tup->t_xmax)) {
		if (!TransactionIdDidAbort(tup->t_xmax)) {
		    /* XXX this should not happen (locking), but it does */
#ifdef	VACUUMDLOG
		    elog(NOTICE, "vacuumd: killing transaction %s\n",
			TransactionIdFormString(tup->t_xmax));
#endif	/* VACUUMDLOG */

		    TransactionIdAbort(tup->t_xmax);
		}
		PointerStoreInvalidTransactionId(tup->t_xmax);
	    }
#endif	/* !CURT */
		}

        if (flags & LP_DOCNT) {
            /* if first in DOCNT-CTUP(,DOCNT)-...-CTUP chain, */
            /* put on DOCNTLIST.  Otherwise, put on CTUPLIST. */
            ItemPointerSimpleSet(&tidstruct, blockNumber, 1 + i);
            tidptr = (ItemPointer)&((char *)page)[(*lp).lp_off];
            addtolist(!isctup? DOCNTLIST:CTUPLIST,
                &tidstruct, (struct VacTidListData *) NULL, tidptr,
                !isctup? &(tup->t_lock.l_ltid) : (ItemPointer) NULL, 
                !isctup? DOCNTREASONA:DOCNTREASONB);
			continue;
        }
        if (isctup) {	/* flags & LP_CTUP */
            ItemPointerSimpleSet(&tidstruct, blockNumber, 1 + i);
            addtolist(CTUPLIST, &tidstruct,
                (struct VacTidListData *) NULL, (ItemPointer) NULL,
				(ItemPointer) NULL, CTUPREASON);
			continue;
        }

		addtolist(LIVELIST, &(tup->t_ctid),
			(struct VacTidListData *) NULL, (ItemPointer) NULL,
			&(tup->t_lock.l_ltid), LIVEREASON);

#ifdef VACDUMPBPAGE
        printf("\tlen=%ld:ctid=", tup->t_len);
        displaytid(&tup->t_ctid);
        printf(":oid=%ld:", tup->t_oid);
        printf("chain=");
        displaytid(&tup->t_chain);
        printf(":anchor=");
        displaytid(&tup->t_anchor);
        printf("natts=%d:thoff=%d:vtype=`%c'\n", tup->t_natts,
            tup->t_hoff, tup->t_vtype);
#endif
    }
#ifdef	VACTIMOPT
	return (false);
#else
	return (changed);
#endif
}

#ifdef	VACDEBUG
vacuumdebug(p1, p2)
	BlockNumber	p1, p2;
{
}
#endif	/* VACDEBUG */

addtolist(list, tidp, vtlp, conttidp, ruletidp, reasoncode)
    int list;
    ItemPointer tidp;
    struct VacTidListData *vtlp;
    ItemPointer conttidp, ruletidp;
    int reasoncode;
{
    struct VacTidListData *oldlast;

	/*
	if (list == DEADLIST)
		vacuumdebug();
	*/

#ifdef	VACDEBUG
	if (reasoncode == ABORTREASON) {
		printf("found aborted insert in relation id: %d\n", RelOID);
	}
#endif	/* VACDEBUG */
    if (vtlp == NULL) {
        vtlp = (struct VacTidListData *) palloc(sizeof(struct VacTidListData));
		bzero((char *) vtlp, sizeof(struct VacTidListData));
        ItemPointerCopy(tidp, &(vtlp->VacTid));
		vtlp->VacRelOID = RelOID;
        /* if (conttidp != NULL) { */
        if (ItemPointerIsValid(conttidp)) {
            ItemPointerCopy(conttidp, &(vtlp->VacContTid));
            vtlp->VacHasContTid = true;
        } else
            vtlp->VacHasContTid = false;
        /* if (ruletidp != NULL) { */
        if (ItemPointerIsValid(ruletidp)) {
            ItemPointerCopy(ruletidp, &(vtlp->VacRuleTid));
            vtlp->VacHasRuleTid = true;
        } else
            vtlp->VacHasRuleTid = false;
    }
    vtlp->VacReasonCode = reasoncode;

		/* check for empty list */
    if (ListHT[list].ListHead == NULL && ListHT[list].ListTail == NULL) {
        ListHT[list].ListHead = ListHT[list].ListTail = vtlp;
    } else {
		Assert(ListHT[list].ListTail != NULL);
        oldlast = ListHT[list].ListTail;
        oldlast->NextVacTid = ListHT[list].ListTail = vtlp;
    }
    vtlp->NextVacTid = NULL;
    ListHT[list].ListCount++;
#ifdef VACDEBUG
    printf("addtolist: list: %s, reloid: %d, reason: %s\n",
		lists[list].listname, vtlp->VacRelOID, reason[reasoncode].reasonline);
	if (ItemPointerIsValid(tidp)) {
		printf("tidp: ");
		displaytid(tidp);
		printf("; ");
	}
	if (ItemPointerIsValid(conttidp)) {
		printf("conttidp: ");
		displaytid(conttidp);
		printf("; ");
	}
	if (ItemPointerIsValid(ruletidp)) {
		printf("ruletidp: ");
		displaytid(ruletidp);
		printf("; ");
	}
	printf("\n");
#endif
}

deletefromlist(list, reloid, tidp, vtlpp)
    int list;
	ObjectId reloid;
    ItemPointer tidp;
    struct VacTidListData **vtlpp;
{
    struct VacTidListData *element;
    struct VacTidListData *p; /* predecessor */
    struct VacTidListData *s; /* successor */

    if (tidp == NULL) {	/* delete entire list if tidp == NULL */
        element = ListHT[list].ListHead;
        while (element != NULL) {
            s = element->NextVacTid;
            pfree(element);
            element = s;
        }
        ListHT[list].ListHead = ListHT[list].ListTail = NULL;
        ListHT[list].ListCount = 0;
        return(0);
    }

    Assert(ListHT[list].ListCount > 0);

    p = NULL;
    element = ListHT[list].ListHead;

    while (element != NULL) {
        if (element->VacRelOID == reloid &&
			ItemPointerEquals(&(element->VacTid), tidp))
            break;
        p = element;
        element = element->NextVacTid;
    }
    if (element == NULL)
        return(-1);

    /* check for 1 element list */
    if (ListHT[list].ListHead == ListHT[list].ListTail) 
        ListHT[list].ListHead = ListHT[list].ListTail = NULL;

    else {	/* >1 elements in list */

        /* check for search element == 1st element */
        if (element == ListHT[list].ListHead)
            ListHT[list].ListHead = element->NextVacTid;

		/* check for search element == last element */
        else if (element == ListHT[list].ListTail) { 
			ListHT[list].ListTail = p;
			p->NextVacTid = NULL;

		} else
            p->NextVacTid = element->NextVacTid;
    }
    if (vtlpp == NULL)
        pfree(element);
    else
        *vtlpp = element;
    ListHT[list].ListCount--;
    return(0);
}

struct VacTidListData *
searchlist(list, reloid, tidp, ruletidsearch, pagenumsearch)
    int list;
	ObjectId reloid;
    ItemPointer tidp;
    bool ruletidsearch;
	bool pagenumsearch;
{
    struct VacTidListData *auxp;
	bool comparetidpagenums();

    auxp = ListHT[list].ListHead;

    while (auxp != NULL) {
        if (ruletidsearch == false && pagenumsearch == false) {
#ifdef VACDEBUG
			printf("auxp->VacRelOID: %d, reloid: %d\n",
				auxp->VacRelOID, reloid);
			printf("\tauxp->VacTid: (block: %d, offset: %d)\n",
				ItemPointerGetBlockNumber(&(auxp->VacTid)),
				ItemPointerGetOffsetNumber(&(auxp->VacTid), 
				SinglePagePartition));
			printf("\ttid: (block: %d, offset: %d)\n",
				ItemPointerGetBlockNumber(tidp),
				ItemPointerGetOffsetNumber(tidp, SinglePagePartition));
#endif
            if (auxp->VacRelOID == reloid &&
				ItemPointerEquals(&(auxp->VacTid), tidp))
                return(auxp);
        } else if (ruletidsearch == true) {
            if (auxp->VacHasRuleTid == true &&
                auxp->VacRelOID == reloid &&
				ItemPointerEquals(&(auxp->VacRuleTid), tidp))
                    return(auxp);
        } else if (pagenumsearch == true) {
#ifdef VACDEBUG
			printf("searchlist trace data follows: \n");
			printf("auxp->VacRelOID: %d, reloid: %d\n",
				auxp->VacRelOID, reloid);
			printf("\tauxp->VacTid: (block: %d, offset: %d)\n",
				ItemPointerGetBlockNumber(&(auxp->VacTid)),
				ItemPointerGetOffsetNumber(&(auxp->VacTid), 
				SinglePagePartition));
			printf("\ttid: (block: %d, offset: %d)\n",
				ItemPointerGetBlockNumber(tidp),
				ItemPointerGetOffsetNumber(tidp, SinglePagePartition));
#endif
			if (auxp->VacRelOID == reloid &&
					comparetidpagenums(
						&auxp->VacTid, tidp)) {

				/* printf("page numbers match\n"); */
				return(auxp);
			}
		}
        auxp = auxp->NextVacTid;
    }
    return(NULL);
}

changelists(fromlist, tolist, listentry, reasoncode)
    int fromlist, tolist;
    struct VacTidListData *listentry;
    int reasoncode;
{
    struct VacTidListData *vtlp;
    int entryDeleted;

    entryDeleted = deletefromlist(fromlist,
				  listentry->VacRelOID,
				  &(listentry->VacTid),
				  &vtlp);

    Assert(entryDeleted != -1);

    addtolist(tolist, &(listentry->VacTid), vtlp, (ItemPointer) NULL,
		(ItemPointer) NULL, reasoncode);
}

bool
comparetidpagenums(tidp1, tidp2)
    ItemPointer tidp1, tidp2;
{
	BlockNumber p1, p2;

	p1 = ItemPointerGetBlockNumber(tidp1);
	p2 = ItemPointerGetBlockNumber(tidp2);

#ifdef VACDEBUG
	printf("comparetidpagenum trace data follows: \n");
	printf("tidp1: "); displaytid(tidp1); printf("\n");
	printf("tidp2: "); displaytid(tidp2); printf("\n");
	printf("tidp1's page number: %d\n", p1);
	printf("tidp2's page number: %d\n", p2);

	vacuumdebug(p1, p2);
#endif

	return ((bool)(p1 == p2));
}

#ifdef PRINTLIST
displaytid(tidP)
ItemPointer tidP;
{
    printf("(%d,%d,%d)", ((short *)tidP)[0], ((short *)tidP)[1],
        ((short *)tidP)[2]);
}
#endif

updatestats(reloid, npages, ntuples)
	ObjectId reloid;
	int npages, ntuples;
{
	register		i;
	Relation		relation;
	HeapScanDesc		scan;
	static ScanKeyEntryData	key[1] = {
		{ 0, T_OID, F_OIDEQ }
	};
	Buffer			buffer;
	HeapTuple		oldTuple;
	char			*values[RelationRelationNumberOfAttributes];
	char			nulls[RelationRelationNumberOfAttributes];
	char			replace[RelationRelationNumberOfAttributes];
	Boolean			isnull;

	/*
	 * Find the RELATION relation tuple for the given relation.
	 */
	relation = RelationNameOpenHeapRelation(RelationRelationName->data);
	if (!RelationIsValid(relation)) {
		elog(WARN, "updatestats: could not open RELATION relation");
		return(0);
	}
	key[0].argument = ObjectIdGetDatum(reloid);
	scan = RelationBeginHeapScan(relation, 0, NowTimeQual, 1, 
				     (ScanKey) key);
	if (!HeapScanIsValid(scan)) {
		RelationCloseHeapRelation(relation);
		elog(WARN, "updatestats: cannot scan RELATION relation");
		return(0);
	}
	oldTuple = HeapScanGetNextTuple(scan, 0, &buffer);
	if (!HeapTupleIsValid(oldTuple)) {
		HeapScanEnd(scan);
		RelationCloseHeapRelation(relation);
		elog(WARN, "updatestats: no such relation: %d", reloid);
		return(0);
	}

	/* catalog statistics iff the statistics are incorrect */
	if ((npages != -1 && npages !=
			(int32)amgetattr(oldTuple, buffer,
				RelationPagesAttributeNumber,
				RelationGetTupleDescriptor(relation), &isnull))
			|| (ntuples != -1 && ntuples !=
				(int32)amgetattr(oldTuple,
					buffer, RelationTuplesAttributeNumber,
					RelationGetTupleDescriptor(relation),
					&isnull))) {

		HeapTuple	newTuple;

		for (i = 0; i < RelationRelationNumberOfAttributes; ++i) {
			nulls[i] = ' ';
			values[i] = NULL;
			replace[i] = ' ';
		}
		if (npages != -1) {
			values[RelationPagesAttributeNumber-1] =
				(Pointer)npages;
			replace[RelationPagesAttributeNumber-1] = 'r';
		}
		if (ntuples != -1) {
			values[RelationTuplesAttributeNumber-1] =
				(Pointer)ntuples;
			replace[RelationTuplesAttributeNumber-1] = 'r';
		}
	
		/*
		 * Change the RELATION relation tuple for the given relation.
		 */
		newTuple = ModifyHeapTuple(oldTuple, buffer, relation, values,
			nulls, replace);

		if (!HeapTupleIsValid(newTuple)) {
			HeapScanEnd(scan);
			RelationCloseHeapRelation(relation);
			elog(WARN, "updatestats: modify RELATION(%d) failed",
				reloid);
			return(0);
		}
		(void)RelationReplaceHeapTuple(relation, &newTuple->t_ctid,
			newTuple);

		pfree(newTuple);
#ifdef	VACOUTPUT
		printf("relation(%d): statistics updated\n", reloid);
#endif	/* VACOUTPUT */
	}

	HeapScanEnd(scan);
	RelationCloseHeapRelation(relation);
	return(1);
}

#endif /* !TESTDAEMON */
