/*
 * Copyright (c) 2014 Red Hat.
 * Copyright (c) 2013 Ken McDonell.
 * Copyright (c) 1995-2002 Silicon Graphics, Inc.  All Rights Reserved.
 * 
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 */

#include "pmapi.h"
#include "impl.h"
#include <ctype.h>
#include <limits.h>
#include <sys/stat.h>

#define STS_FATAL	-2
#define STS_WARNING	-1
#define STS_OK		0

static struct timeval	tv;
static int		numpmid;
static pmID		*pmid;
static pmID		pmid_flags;
static pmID		pmid_missed;
static char		*archpathname;	/* from the command line */
static char		*archbasename;	/* after basename() */
static char		*archdirname;	/* after dirname() */
#define STATE_OK	1
#define STATE_MISSING	2
#define STATE_BAD	3
static int		index_state = STATE_MISSING;
static int		meta_state = STATE_MISSING;
static int		log_state = STATE_MISSING;
static int		sflag;
static int		vflag;
static int		sep;

/*
 * return -1, 0 or 1 as the struct timeval's compare
 * a < b, a == b or a > b
 */
int
tvcmp(struct timeval a, struct timeval b)
{
    if (a.tv_sec < b.tv_sec)
	return -1;
    if (a.tv_sec > b.tv_sec)
	return 1;
    if (a.tv_usec < b.tv_usec)
	return -1;
    if (a.tv_usec > b.tv_usec)
	return 1;
    return 0;
}

static int
do_size(pmResult *rp)
{
    int		nbyte = 0;
    int		i;
    int		j;
    /*
     *  Externally the log record looks like this ...
     *  :----------:-----------:..........:---------:
     *  | int len  | timestamp | pmResult | int len |
     *  :----------:-----------:..........:---------:
     *
     * start with sizes of the header len, timestamp, numpmid, and
     * trailer len
     */
    nbyte = sizeof(int) + sizeof(__pmTimeval) + sizeof(int);
    						/* len + timestamp + len */
    nbyte += sizeof(int);
    							/* numpmid */
    for (i = 0; i < rp->numpmid; i++) {
	pmValueSet	*vsp = rp->vset[i];
	nbyte += sizeof(pmID) + sizeof(int);		/* + pmid[i], numval */
	if (vsp->numval > 0) {
	    nbyte += sizeof(int);			/* + valfmt */
	    for (j = 0; j < vsp->numval; j++) {
		nbyte += sizeof(__pmValue_PDU);		/* + pmValue[j] */
		if (vsp->valfmt != PM_VAL_INSITU)
							/* + pmValueBlock */
							/* rounded up */
		    nbyte += PM_PDU_SIZE_BYTES(vsp->vlist[j].value.pval->vlen);
	    }
	}
    }

    return nbyte;
}

static void
setup_event_derived_metrics(void)
{
    int sts;

    if (pmid_flags == 0) {
	/*
	 * get PMID for event.flags and event.missed
	 * note that pmUnpackEventRecords() will have called
	 * __pmRegisterAnon(), so the anon metrics
	 * should now be in the PMNS
	 */
	char	*name_flags = "event.flags";
	char	*name_missed = "event.missed";

	if ((sts = pmLookupName(1, &name_flags, &pmid_flags)) < 0) {
	    /* should not happen! */
	    fprintf(stderr, "Warning: cannot get PMID for %s: %s\n",
		    name_flags, pmErrStr(sts));
	    /* avoid subsequent warnings ... */
	    __pmid_int(&pmid_flags)->item = 1;
	}
	sts = pmLookupName(1, &name_missed, &pmid_missed);
	if (sts < 0) {
	    /* should not happen! */
	    fprintf(stderr, "Warning: cannot get PMID for %s: %s\n",
		    name_missed, pmErrStr(sts));
	    /* avoid subsequent warnings ... */
	    __pmid_int(&pmid_missed)->item = 1;
	}
    }
}

static int
dump_nrecords(int nrecords, int nmissed)
{
    printf("%d", nrecords);
    if (nmissed > 0)
	printf(" (and %d missed)", nmissed);
    if (nrecords + nmissed == 1)
	printf(" event record\n");
    else
	printf(" event records\n");
    return 0;
}

static int
dump_nparams(int numpmid)
{
    if (numpmid == 0) {
	printf(" ---\n");
	printf("	          No parameters\n");
	return -1;
    }
    if (numpmid < 0) {
	printf(" ---\n");
	printf("	          Error: illegal number of parameters (%d)\n",
		numpmid);
	return -1;
    }
    return 0;
}

static void
dump_parameter(pmValueSet *xvsp, int index, int *flagsptr)
{
    int		sts, flags = *flagsptr;
    pmDesc	desc;
    char	*name;

    if (pmNameID(xvsp->pmid, &name) >= 0) {
	if (index == 0) {
	    if (xvsp->pmid == pmid_flags) {
		flags = xvsp->vlist[0].value.lval;
		printf(" flags 0x%x", flags);
		printf(" (%s) ---\n", pmEventFlagsStr(flags));
		free(name);
		*flagsptr = flags;
		return;
	    }
	    printf(" ---\n");
	}
	if ((flags & PM_EVENT_FLAG_MISSED) && index == 1 &&
	    (xvsp->pmid == pmid_missed)) {
	    printf("              ==> %d missed event records\n",
		    xvsp->vlist[0].value.lval);
	    free(name);
	    return;
	}
	printf("                %s (%s):", pmIDStr(xvsp->pmid), name);
	free(name);
    }
    else
	printf("	            PMID: %s:", pmIDStr(xvsp->pmid));
    if ((sts = pmLookupDesc(xvsp->pmid, &desc)) < 0) {
	printf(" pmLookupDesc: %s\n", pmErrStr(sts));
	return;
    }
    printf(" value ");
    pmPrintValue(stdout, xvsp->valfmt, desc.type, &xvsp->vlist[0], 1);
    putchar('\n');
}

static void
dump_event(const char *name, pmValueSet *vsp, int index, int indom, int type)
{
    int		r;		/* event records */
    int		p;		/* event parameters */
    int		nrecords;
    int		nmissed = 0;
    int		highres = (type == PM_TYPE_HIGHRES_EVENT);
    int		flags;
    char	*iname;
    pmValue	*vp = &vsp->vlist[index];

    setup_event_derived_metrics();

    if (index > 0)
	printf("            ");
    printf("  %s (%s", pmIDStr(vsp->pmid), name);
    if (indom != PM_INDOM_NULL) {
	putchar('[');
	if (pmNameInDom(indom, vp->inst, &iname) < 0)
	    printf("%d or ???])", vp->inst);
	else {
	    printf("%d or \"%s\"])", vp->inst, iname);
	    free(iname);
	}
    }
    else {
	printf(")");
    }
    printf(": ");

    if (highres) {
	pmHighResResult	**hr;

	if ((nrecords = pmUnpackHighResEventRecords(vsp, index, &hr)) < 0)
	    return;
	if (nrecords == 0) {
	    printf("No event records\n");
	    pmFreeHighResEventResult(hr);
	    return;
	}

	for (r = 0; r < nrecords; r++) {
	    if (hr[r]->numpmid == 2 && hr[r]->vset[0]->pmid == pmid_flags &&
		(hr[r]->vset[0]->vlist[0].value.lval & PM_EVENT_FLAG_MISSED) &&
		hr[r]->vset[1]->pmid == pmid_missed) {
		nmissed += hr[r]->vset[1]->vlist[0].value.lval;
	    }
	}
	dump_nrecords(nrecords, nmissed);

	for (r = 0; r < nrecords; r++) {
	    printf("              --- event record [%d] timestamp ", r);
	    __pmPrintHighResStamp(stdout, &hr[r]->timestamp);
	    if (dump_nparams(hr[r]->numpmid) < 0)
		continue;
	    flags = 0;
	    for (p = 0; p < hr[r]->numpmid; p++)
		dump_parameter(hr[r]->vset[p], p, &flags);
	}
	pmFreeHighResEventResult(hr);
    }
    else {
	pmResult	**res;

	if ((nrecords = pmUnpackEventRecords(vsp, index, &res)) < 0)
	    return;
	if (nrecords == 0) {
	    printf("No event records\n");
	    pmFreeEventResult(res);
	    return;
	}

	for (r = 0; r < nrecords; r++) {
	    if (res[r]->numpmid == 2 && res[r]->vset[0]->pmid == pmid_flags &&
		(res[r]->vset[0]->vlist[0].value.lval & PM_EVENT_FLAG_MISSED) &&
		res[r]->vset[1]->pmid == pmid_missed) {
		nmissed += res[r]->vset[1]->vlist[0].value.lval;
	    }
	}
	dump_nrecords(nrecords, nmissed);

	for (r = 0; r < nrecords; r++) {
	    printf("              --- event record [%d] timestamp ", r);
	    __pmPrintStamp(stdout, &res[r]->timestamp);
	    if (dump_nparams(res[r]->numpmid) < 0)
		continue;
	    flags = 0;
	    for (p = 0; p < res[r]->numpmid; p++)
		dump_parameter(res[r]->vset[p], p, &flags);
	}
	pmFreeEventResult(res);
    }
}

static void
dump_metric(const char *mname, pmValueSet *vsp, int index, int indom, int type)
{
    pmValue	*vp = &vsp->vlist[index];
    char	*iname;

    if (index == 0) {
	printf("  %s (%s):", pmIDStr(vsp->pmid), mname);
	if (vsp->numval > 1) {
	    putchar('\n');
	    printf("               ");
	}
    }
    else
	printf("               ");

    if (indom != PM_INDOM_NULL) {
	printf(" inst [");
	if (pmNameInDom(indom, vp->inst, &iname) < 0)
	    printf("%d or ???]", vp->inst);
	else {
	    printf("%d or \"%s\"]", vp->inst, iname);
	    free(iname);
	}
    }
    printf(" value ");
    pmPrintValue(stdout, vsp->valfmt, type, vp, 1);
    putchar('\n');
}

static void
dumpResult(pmResult *resp)
{
    int		i;
    int		j;
    int		n;
    char	*mname;
    pmDesc	desc;

    if (sflag) {
	int		nbyte;
	nbyte = do_size(resp);
	printf("[%d bytes]\n", nbyte);
    }

    __pmPrintStamp(stdout, &resp->timestamp);

    if (resp->numpmid == 0) {
	printf("  <mark>\n");
	return;
    }

    for (i = 0; i < resp->numpmid; i++) {
	pmValueSet	*vsp = resp->vset[i];

	if (i > 0)
	    printf("            ");
	if ((n = pmNameID(vsp->pmid, &mname)) < 0)
	    mname = strdup("<noname>");
	if (vsp->numval == 0) {
	    printf("  %s (%s): No values returned!\n", pmIDStr(vsp->pmid), mname);
	    goto next;
	}
	else if (vsp->numval < 0) {
	    printf("  %s (%s): %s\n", pmIDStr(vsp->pmid), mname, pmErrStr(vsp->numval));
	    goto next;
	}

	if (pmLookupDesc(vsp->pmid, &desc) < 0) {
	    /* don't know, so punt on the most common cases */
	    desc.indom = PM_INDOM_NULL;
	    if (vsp->valfmt == PM_VAL_INSITU)
		desc.type = PM_TYPE_32;
	    else
		desc.type = PM_TYPE_AGGREGATE;
	}

	for (j = 0; j < vsp->numval; j++) {
	    if (desc.type == PM_TYPE_EVENT ||
		desc.type == PM_TYPE_HIGHRES_EVENT)
		dump_event(mname, vsp, j, desc.indom, desc.type);
	    else
		dump_metric(mname, vsp, j, desc.indom, desc.type);
	}
next:
	if (mname)
	    free(mname);
    }
}

static void
dumpDesc(__pmContext *ctxp)
{
    int		i;
    int		sts;
    char	*p;
    __pmHashNode	*hp;
    pmDesc	*dp;

    printf("\nDescriptions for Metrics in the Log ...\n");
    for (i = 0; i < ctxp->c_archctl->ac_log->l_hashpmid.hsize; i++) {
	for (hp = ctxp->c_archctl->ac_log->l_hashpmid.hash[i]; hp != NULL; hp = hp->next) {
	    dp = (pmDesc *)hp->data;
	    sts = pmNameID(dp->pmid, &p);
	    if (sts < 0)
		printf("PMID: %s (%s)\n", pmIDStr(dp->pmid), "<noname>");
	    else {
		printf("PMID: %s (%s)\n", pmIDStr(dp->pmid), p);
		free(p);
	    }
	    __pmPrintDesc(stdout, dp);
	}
    }
}

static void
dumpInDom(__pmContext *ctxp)
{
    int		i;
    int		j;
    __pmHashNode	*hp;
    __pmLogInDom	*idp;
    __pmLogInDom	*ldp;

    printf("\nInstance Domains in the Log ...\n");
    for (i = 0; i < ctxp->c_archctl->ac_log->l_hashindom.hsize; i++) {
	for (hp = ctxp->c_archctl->ac_log->l_hashindom.hash[i]; hp != NULL; hp = hp->next) {
	    printf("InDom: %s\n", pmInDomStr((pmInDom)hp->key));
	    /*
	     * in reverse chronological order, so iteration is a bit funny
	     */
	    ldp = NULL;
	    for ( ; ; ) {
		for (idp = (__pmLogInDom *)hp->data; idp->next != ldp; idp =idp->next)
			;
		tv.tv_sec = idp->stamp.tv_sec;
		tv.tv_usec = idp->stamp.tv_usec;
		__pmPrintStamp(stdout, &tv);
		printf(" %d instances\n", idp->numinst);
		for (j = 0; j < idp->numinst; j++) {
		    printf("                 %d or \"%s\"\n",
			idp->instlist[j], idp->namelist[j]);
		}
		if (idp == (__pmLogInDom *)hp->data)
		    break;
		ldp = idp;
	    }
	}
    }
}

/*
 * check the temporal index
 */
static void
pass1(__pmContext *ctxp)
{
    int		i;
    char	path[MAXPATHLEN];
    off_t	meta_size = -1;		/* initialize to pander to gcc */
    off_t	log_size = -1;		/* initialize to pander to gcc */
    struct stat	sbuf;
    __pmLogTI	*tip;
    __pmLogTI	*lastp;

    if (vflag) printf("pass1: check temporal index\n");
    lastp = NULL;
    for (i = 1; i <= ctxp->c_archctl->ac_log->l_numti; i++) {
	/*
	 * Integrity Checks
	 *
	 * this(tv_sec) < 0
	 * this(tv_usec) < 0 || this(tv_usec) > 999999
	 * this(timestamp) < last(timestamp)
	 * this(vol) >= 0
	 * this(vol) < last(vol)
	 * this(vol) == last(vol) && this(meta) <= last(meta)
	 * this(vol) == last(vol) && this(log) <= last(log)
	 * file_exists(<base>.meta) && this(meta) > file_size(<base>.meta)
	 * file_exists(<base>.this(vol)) &&
	 *		this(log) > file_size(<base>.this(vol))
	 *
	 * Integrity Warnings
	 *
	 * this(vol) != last(vol) && !file_exists(<base>.this(vol))
	 */
	tip = &ctxp->c_archctl->ac_log->l_ti[i-1];
	tv.tv_sec = tip->ti_stamp.tv_sec;
	tv.tv_usec = tip->ti_stamp.tv_usec;
	if (i == 1) {
	    sprintf(path, "%s%c%s.meta", archdirname, sep, archbasename);
	    if (stat(path, &sbuf) == 0)
		meta_size = sbuf.st_size;
	    else {
		/* should not get here ... as detected in after pass0 */
		fprintf(stderr, "%s: pass1: Botch: cannot open metadata file (%s)\n", pmProgname, path);
		exit(1);
	    }
	}
	if (tip->ti_vol < 0) {
	    printf("%s.index[entry %d]: illegal volume number %d\n",
		    archbasename, i, tip->ti_vol);
	    index_state = STATE_BAD;
	    log_size = -1;
	}
	else if (lastp == NULL || tip->ti_vol != lastp->ti_vol) { 
	    sprintf(path, "%s%c%s.%d", archdirname, sep, archbasename, tip->ti_vol);
	    if (stat(path, &sbuf) == 0)
		log_size = sbuf.st_size;
	    else {
		log_size = -1;
		printf("%s: File missing or compressed for log volume %d\n", path, tip->ti_vol);
	    }
	}
	if (tip->ti_stamp.tv_sec < 0 ||
	    tip->ti_stamp.tv_usec < 0 || tip->ti_stamp.tv_usec > 999999) {
	    printf("%s.index[entry %d]: Illegal timestamp value (%d sec, %d usec)\n",
		archbasename, i, tip->ti_stamp.tv_sec, tip->ti_stamp.tv_usec);
	    index_state = STATE_BAD;
	}
	if (tip->ti_meta < sizeof(__pmLogLabel)+2*sizeof(int)) {
	    printf("%s.index[entry %d]: offset to metadata (%ld) before end of label record (%ld)\n",
		archbasename, i, (long)tip->ti_meta, (long)(sizeof(__pmLogLabel)+2*sizeof(int)));
	    index_state = STATE_BAD;
	}
	if (meta_size != -1 && tip->ti_meta > meta_size) {
	    printf("%s.index[entry %d]: offset to metadata (%ld) past end of file (%ld)\n",
		archbasename, i, (long)tip->ti_meta, (long)meta_size);
	    index_state = STATE_BAD;
	}
	if (tip->ti_log < sizeof(__pmLogLabel)+2*sizeof(int)) {
	    printf("%s.index[entry %d]: offset to log (%ld) before end of label record (%ld)\n",
		archbasename, i, (long)tip->ti_log, (long)(sizeof(__pmLogLabel)+2*sizeof(int)));
	    index_state = STATE_BAD;
	}
	if (log_size != -1 && tip->ti_log > log_size) {
	    printf("%s.index[entry %d]: offset to log (%ld) past end of file (%ld)\n",
		archbasename, i, (long)tip->ti_log, (long)log_size);
	    index_state = STATE_BAD;
	}
	if (lastp != NULL) {
	    if (tip->ti_stamp.tv_sec < lastp->ti_stamp.tv_sec ||
	        (tip->ti_stamp.tv_sec == lastp->ti_stamp.tv_sec &&
	         tip->ti_stamp.tv_usec < lastp->ti_stamp.tv_usec)) {
		printf("%s.index: timestamp went backwards in time %d.%06d[entry %d]-> %d.%06d[entry %d]\n",
			archbasename, (int)lastp->ti_stamp.tv_sec, (int)lastp->ti_stamp.tv_usec, i-1,
			(int)tip->ti_stamp.tv_sec, (int)tip->ti_stamp.tv_usec, i);
		index_state = STATE_BAD;
	    }
	    if (tip->ti_vol < lastp->ti_vol) {
		printf("%s.index: volume number decreased %d[entry %d] -> %d[entry %d]\n",
			archbasename, lastp->ti_vol, i-1, tip->ti_vol, i);
		index_state = STATE_BAD;
	    }
	    if (tip->ti_vol == lastp->ti_vol && tip->ti_meta < lastp->ti_meta) {
		printf("%s.index: offset to metadata decreased %ld[entry %d] -> %ld[entry %d]\n",
			archbasename, (long)lastp->ti_meta, i-1, (long)tip->ti_meta, i);
		index_state = STATE_BAD;
	    }
	    if (tip->ti_vol == lastp->ti_vol && tip->ti_log < lastp->ti_log) {
		printf("%s.index: offset to log decreased %ld[entry %d] -> %ld[entry %d]\n",
			archbasename, (long)lastp->ti_log, i-1, (long)tip->ti_log, i);
		index_state = STATE_BAD;
	    }
	}
	lastp = tip;
    }
}

static int
filter(const_dirent *dp)
{
    static int	len = -1;

    if (len == -1)
	len = strlen(archbasename);
    if (strncmp(dp->d_name, archbasename, len) != 0)
	return 0;
    if (strcmp(&dp->d_name[len], ".meta") == 0)
	return 1;
    if (strcmp(&dp->d_name[len], ".index") == 0)
	return 1;
    if (dp->d_name[len] == '.' && isdigit(dp->d_name[len+1])) {
	const char	*p = &dp->d_name[len+2];
	for ( ; *p; p++) {
	    if (!isdigit(*p))
		return 0;
	}
	return 1;
    }
    return 0;
}

#define IS_UNKNOWN	0
#define IS_INDEX	1
#define IS_META		2
#define IS_LOG		3
/*
 * Pass 1 for all files
 * - should only come here if fname exists
 * - check that file contains a number of complete records
 * - the label record and the records for the data and metadata
 *   have this format:
 *   :----------:----------------------:---------:
 *   | int len  |        stuff         | int len |
 *   | header   |        stuff         | trailer |
 *   :----------:----------------------:---------:
 *   and the len fields are in network byte order.
 *   For these records, check that the header length is equal to
 *   the trailer length
 * - for index files, following the label record there should be
 *   a number of complete records, each of which is a __pmLogTI
 *   record, with the fields converted network byte order
 *
 * TODO - repair
 * - truncate metadata and data files ... unconditional or interactive confirm?
 * - mark index as bad and needing rebuild
 * - move access check into here (if cannot open file for reading we're screwed)
 */
static int
pass0(char *fname)
{
    int		len;
    int		check;
    int		i;
    int		sts;
    int		nrec = 0;
    int		is = IS_UNKNOWN;
    char	*p;
    FILE	*f;

    if (vflag)
	printf("pass0: %s:\n", fname);
    if ((f = fopen(fname, "r")) == NULL) {
	fprintf(stderr, "%s: Cannot open \"%s\": %s\n", pmProgname, fname, osstrerror());
	sts = STS_FATAL;
	goto done;
    }
    p = strrchr(fname, '.');
    if (p != NULL) {
	if (strcmp(p, ".index") == 0)
	    is = IS_INDEX;
	else if (strcmp(p, ".meta") == 0)
	    is = IS_META;
	else if (isdigit(*++p)) {
	    p++;
	    while (*p && isdigit(*p))
		p++;
	    if (*p == '\0')
		is = IS_LOG;
	}
    }
    if (is == IS_UNKNOWN) {
	/*
	 * should never get here because filter() is supposed to
	 * only include PCP archive file names from scandir()
	 */
	fprintf(stderr, "%s: pass0: Botch: bad file name? %s\n", pmProgname, fname);
	exit(1);
    }

    while ((sts = fread(&len, 1, sizeof(len), f)) == sizeof(len)) {
	len = ntohl(len);
	len -= 2 * sizeof(len);
	/* gobble stuff between header and trailer without looking at it */
	for (i = 0; i < len; i++) {
	    check = fgetc(f);
	    if (check == EOF) {
		if (nrec == 0)
		    printf("%s: unexpected EOF in label record body\n", fname);
		else
		    printf("%s[record %d]: unexpected EOF in record body\n", fname, nrec);
		sts = STS_FATAL;
		goto done;
	    }
	}
	if ((sts = fread(&check, 1, sizeof(check), f)) != sizeof(check)) {
	    if (nrec == 0)
		printf("%s: unexpected EOF in label record trailer\n", fname);
	    else
		printf("%s[record %d]: unexpected EOF in record trailer\n", fname, nrec);
	    sts = STS_FATAL;
	    goto done;
	}
	check = ntohl(check);
	len += 2 * sizeof(len);
	if (check != len) {
	    if (nrec == 0)
		printf("%s: label record length mismatch: header %d != trailer %d\n", fname, len, check);
	    else
		printf("%s[record %d]: length mismatch: header %d != trailer %d\n", fname, nrec, len, check);
	    sts = STS_FATAL;
	    goto done;
	}
	nrec++;
	if (is == IS_INDEX) {
	    /* for index files, done label record, now eat index records */
	    __pmLogTI	tirec;
	    while ((sts = fread(&tirec, 1, sizeof(tirec), f)) == sizeof(tirec)) { 
		nrec++;
	    }
	    if (sts != 0) {
		printf("%s[record %d]: unexpected EOF in index entry\n", fname, nrec);
		index_state = STATE_BAD;
		sts = STS_FATAL;
		goto done;
	    }
	    goto empty_check;
	}
    }
    if (sts != 0) {
	printf("%s[record %d]: unexpected EOF in record header\n", fname, nrec);
	sts = STS_FATAL;
    }
empty_check:
    if (sts != STS_FATAL && nrec < 2) {
	printf("%s: contains no PCP data\n", fname);
	sts = STS_WARNING;
    }
    /*
     * sts == 0 (from fread) => STS_OK
     */
done:
    if (is == IS_INDEX) {
	if (sts == STS_OK)
	    index_state = STATE_OK;
	else
	    index_state = STATE_BAD;
    }
    else if (is == IS_META) {
	if (sts == STS_OK)
	    meta_state = STATE_OK;
	else
	    meta_state = STATE_BAD;
    }
    else {
	if (log_state == STATE_OK && sts != STS_OK)
	    log_state = STATE_BAD;
	else if (log_state == STATE_MISSING) {
	    if (sts == STS_OK)
		log_state = STATE_OK;
	    else
		log_state = STATE_BAD;
	}
    }

    return sts;
}

static void
dometric(const char *name)
{
    int		sts;

    if (*name == '\0') {
	printf("PMNS appears to be empty!\n");
	return;
    }
    numpmid++;
    pmid = (pmID *)realloc(pmid, numpmid * sizeof(pmID));
    if ((sts = pmLookupName(1, (char **)&name, &pmid[numpmid-1])) < 0) {
	fprintf(stderr, "%s: pmLookupName(%s): %s\n", pmProgname, name, pmErrStr(sts));
	numpmid--;
    }
}

static pmLongOptions longopts[] = {
    PMAPI_OPTIONS_HEADER("Options"),
    PMOPT_DEBUG,
    { "all", 0, 'a', 0, "dump everything" },
    { "descs", 0, 'd', 0, "dump metric descriptions" },
    { "indoms", 0, 'i', 0, "dump instance domain descriptions" },
    { "", 0, 'L', 0, "more verbose form of label dump" },
    { "label", 0, 'l', 0, "dump the archive label" },
    { "", 0, 'm', 0, "dump values of the metrics (default)" },
    { "reverse", 0, 'r', 0, "process archive in reverse chronological order" },
    { "size", 0, 's', 0, "report size of data records in archive" },
    { "", 0, 't', 0, "dump the temporal index" },
    { "verbose", 0, 'v', 0, "verbose output" },
    PMOPT_TIMEZONE,
    PMOPT_HOSTZONE,
    PMOPT_HELP,
    PMAPI_OPTIONS_END
};

static pmOptions opts = {
    .short_options = "aD:dilLmst:vZ:z?",
    .long_options = longopts,
    .short_usage = "[options] archive [metricname ...]",
};

int
main(int argc, char *argv[])
{
    int			c;
    int			sts;
    int			i;
    int			n;
    int			dflag = 0;
    int			iflag = 0;
    int			Lflag = 0;
    int			lflag = 0;
    int			mflag = 0;
    int			tflag = 0;
    int			mode = PM_MODE_FORW;
    char		*p;
    __pmContext		*ctxp;
    pmResult		*result;
    struct timeval 	endTime = { INT_MAX, 0 };
    struct timeval	appStart;
    struct timeval	appEnd;
    pmLogLabel		label;			/* get hostname for archives */
    char		timebuf[32];		/* for pmCtime result + .xxx */
    struct timeval	done;
    struct dirent	**namelist;
    char 		*tz = NULL;		/* for -Z timezone */
    int			zflag = 0;		/* for -z */
    int			nfile;

    sep = __pmPathSeparator();
    setlinebuf(stdout);
    setlinebuf(stderr);

    while ((c = pmgetopt_r(argc, argv, &opts)) != EOF) {
	switch (c) {

	case 'a':	/* dump everything */
	    dflag = iflag = lflag = mflag = sflag = tflag = 1;
	    break;

	case 'd':	/* dump pmDesc structures */
	    dflag = 1;
	    break;

	case 'D':	/* debug flag */
	    sts = __pmParseDebug(opts.optarg);
	    if (sts < 0) {
		pmprintf("%s: unrecognized debug flag specification (%s)\n",
			pmProgname, opts.optarg);
		opts.errors++;
	    }
	    else
		pmDebug |= sts;
	    break;

	case 'i':	/* dump instance domains */
	    iflag = 1;
	    break;

	case 'L':	/* dump label, verbose */
	    Lflag = 1;
	    lflag = 1;
	    break;

	case 'l':	/* dump label */
	    lflag = 1;
	    break;

	case 'm':	/* dump metrics in log */
	    mflag = 1;
	    break;

	case 's':	/* report data size in log */
	    sflag = 1;
	    break;

	case 'v':	/* verbose */
	    vflag = 1;
	    break;

	case 'z':	/* timezone from host */
	    if (tz != NULL) {
		pmprintf("%s: at most one of -Z and/or -z allowed\n", pmProgname);
		opts.errors++;
	    }
	    zflag++;
	    break;

	case 'Z':	/* $TZ timezone */
	    if (zflag) {
		pmprintf("%s: at most one of -Z and/or -z allowed\n", pmProgname);
		opts.errors++;
	    }
	    tz = opts.optarg;
	    break;

	case '?':
	default:
	    opts.errors++;
	    break;
	}
    }

    if (opts.errors || opts.optind > argc - 1) {
	pmUsageMessage(&opts);
	exit(1);
    }

    archpathname = argv[opts.optind];
    archbasename = strdup(basename(strdup(archpathname)));
    /*
     * treat foo, foo.index, foo.meta, foo.NNN as all equivalent
     * to "foo"
     */
    p = strrchr(archbasename, '.');
    if (p != NULL) {
	if (strcmp(p, ".index") == 0 || strcmp(p, ".meta") == 0)
	    *p = '\0';
	else {
	    char	*q = ++p;
	    if (isdigit(*q)) {
		q++;
		while (*q && isdigit(*q))
		    q++;
		if (*q == '\0')
		    *p = '\0';
	    }
	}
    }
    archdirname = dirname(strdup(archpathname));
    if (vflag)
	printf("Scanning for components of archive \"%s\"\n", archpathname);
    nfile = scandir(archdirname, &namelist, filter, NULL);
    if (nfile < 1) {
	fprintf(stderr, "%s: No PCP archive files match \"%s\"\n", pmProgname, archpathname);
	exit(1);
    }
    /*
     * Pass 0 for data and metadata files
     */
    sts = STS_OK;
    for (i = 0; i < nfile; i++) {
	char	path[MAXPATHLEN];
	snprintf(path, sizeof(path), "%s%c%s", archdirname, sep, namelist[i]->d_name);
	if (pass0(path) == STS_FATAL)
	    /* unrepairable or unrepaired error */
	    sts = STS_FATAL;
    }
    if (meta_state == STATE_MISSING) {
	fprintf(stderr, "%s: Missing metadata file (%s%c%s.meta)\n", pmProgname, archdirname, sep, archbasename);
	sts = STS_FATAL;
    }
    if (log_state == STATE_MISSING) {
	fprintf(stderr, "%s: Missing log file (%s%c%s.0 or similar)\n", pmProgname, archdirname, sep, archbasename);
	sts = STS_FATAL;
    }

    if (sts == STS_FATAL) {
	if (vflag) printf("Cannot continue ... bye\n");
	exit(1);
    }

    if ((sts = pmNewContext(PM_CONTEXT_ARCHIVE, archpathname)) < 0) {
	fprintf(stderr, "%s: Cannot open archive \"%s\": %s\n", pmProgname, archpathname, pmErrStr(sts));
	fprintf(stderr, "Checking abandoned.\n");
	exit(1);
    }
    if ((n = pmWhichContext()) >= 0) {
	if ((ctxp = __pmHandleToPtr(n)) == NULL) {
	    fprintf(stderr, "%s: Botch: __pmHandleToPtr(%d) returns NULL!\n", pmProgname, n);
	    exit(1);
	}
    }
    else {
	fprintf(stderr, "%s: Botch: %s!\n", pmProgname, pmErrStr(PM_ERR_NOCONTEXT));
	exit(1);
    }
    /*
     * Note: ctxp->c_lock remains locked throughout ... __pmHandleToPtr()
     *       is only called once, and a single context is used throughout
     *       ... so there is no PM_UNLOCK(ctxp->c_lock) anywhere in the
     *       pmchecklog code.
     *       This works because ctxp->c_lock is a recursive lock and
     *       pmchecklog is single-threaded.
     */

    pass1(ctxp);
    exit(1);

    numpmid = argc - opts.optind;
    if (numpmid) {
	numpmid = 0;
	pmid = NULL;
	for (i = 0 ; opts.optind < argc; i++, opts.optind++) {
	    numpmid++;
	    pmid = (pmID *)realloc(pmid, numpmid * sizeof(pmID));
	    if ((sts = pmLookupName(1, &argv[opts.optind], &pmid[numpmid-1])) < 0) {
		if (sts == PM_ERR_NONLEAF) {
		    numpmid--;
		    if ((sts = pmTraversePMNS(argv[opts.optind], dometric)) < 0)
			fprintf(stderr, "%s: pmTraversePMNS(%s): %s\n", pmProgname, argv[opts.optind], pmErrStr(sts));
		}
		else
		    fprintf(stderr, "%s: pmLookupName(%s): %s\n", pmProgname, argv[opts.optind], pmErrStr(sts));
		if (sts < 0)
		    numpmid--;
	    }
	}
	if (numpmid == 0) {
	    fprintf(stderr, "No metric names can be translated, dump abandoned\n");
	    exit(1);
	}
    }

    if ((sts = pmGetArchiveLabel(&label)) < 0) {
	fprintf(stderr, "%s: Cannot get archive label record: %s\n",
	    pmProgname, pmErrStr(sts));
	exit(1);
    }

    if ((sts = pmGetArchiveEnd(&endTime)) < 0) {
	endTime.tv_sec = INT_MAX;
	endTime.tv_usec = 0;
	fflush(stdout);
	fprintf(stderr, "%s: Cannot locate end of archive: %s\n",
	    pmProgname, pmErrStr(sts));
	fprintf(stderr, "\nWARNING: This archive is sufficiently damaged that it may not be possible to\n");
	fprintf(stderr, "         produce complete information.  Continuing and hoping for the best.\n\n");
	fflush(stderr);
    }

    if (zflag) {
	if ((sts = pmNewContextZone()) < 0) {
	    fprintf(stderr, "%s: Cannot set context timezone: %s\n",
		pmProgname, pmErrStr(sts));
	    exit(1);
	}
	printf("Note: timezone set to local timezone of host \"%s\" from archive\n\n",
	    label.ll_hostname);
    }
    else if (tz != NULL) {
	if ((sts = pmNewZone(tz)) < 0) {
	    fprintf(stderr, "%s: Cannot set timezone to \"%s\": %s\n",
		pmProgname, tz, pmErrStr(sts));
	    exit(1);
	}
	printf("Note: timezone set to \"TZ=%s\"\n\n", tz);
    }

    if (sts < 0) {
	fprintf(stderr, "%s:\n%s\n", pmProgname, p);
	exit(1);
    }

    if (lflag) {
	char	       *ddmm;
	char	       *yr;

	printf("Log Label (Log Format Version %d)\n", label.ll_magic & 0xff);
	printf("Performance metrics from host %s\n", label.ll_hostname);

	ddmm = pmCtime(&label.ll_start.tv_sec, timebuf);
	ddmm[10] = '\0';
	yr = &ddmm[20];
	printf("  commencing %s ", ddmm);
	__pmPrintStamp(stdout, &label.ll_start);
	printf(" %4.4s\n", yr);

	if (endTime.tv_sec == INT_MAX) {
	    /* pmGetArchiveEnd() failed! */
	    printf("  ending     UNKNOWN\n");
	}
	else {
	    ddmm = pmCtime(&endTime.tv_sec, timebuf);
	    ddmm[10] = '\0';
	    yr = &ddmm[20];
	    printf("  ending     %s ", ddmm);
	    __pmPrintStamp(stdout, &endTime);
	    printf(" %4.4s\n", yr);
	}

	if (Lflag) {
	    printf("Archive timezone: %s\n", label.ll_tz);
	    printf("PID for pmlogger: %" FMT_PID "\n", label.ll_pid);
	}
    }

    if (dflag)
	dumpDesc(ctxp);

    if (iflag)
	dumpInDom(ctxp);


    if (mflag) {
	if (mode == PM_MODE_FORW) {
	    sts = pmSetMode(mode, &appStart, 0);
	    done = appEnd;
	}
	else {
	    appEnd.tv_sec = INT_MAX;
	    sts = pmSetMode(mode, &appEnd, 0);
	    done = appStart;
	}
	if (sts < 0) {
	    fprintf(stderr, "%s: pmSetMode: %s\n", pmProgname, pmErrStr(sts));
	    exit(1);
	}
	sts = 0;
	for ( ; ; ) {
	    sts = pmFetchArchive(&result);
	    if (sts < 0)
		break;
	    if ((mode == PM_MODE_FORW && tvcmp(result->timestamp, done) > 0) ||
		(mode == PM_MODE_BACK && tvcmp(result->timestamp, done) < 0)) {
		sts = PM_ERR_EOL;
		break;
	    }
	    putchar('\n');
	    dumpResult(result);
	    pmFreeResult(result);
	}
	if (sts != PM_ERR_EOL) {
	    fprintf(stderr, "%s: pmFetch: %s\n", pmProgname, pmErrStr(sts));
	    exit(1);
	}
    }

    exit(0);
}
