/*
 * Copyright (c) 2012-2018,2021 Red Hat.
 * Copyright (c) 1995-2001 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 "logger.h"

/* This crawls over the data structure looking for weirdness */
void
reality_check(void)
{
    __pmHashNode		*hp;
    task_t		*tp;
    task_t		*tp2;
    fetchctl_t		*fp;
    optreq_t		*rqp;
    pmID		pmid;
    int			i = 0, j, k;

    /* check that all fetch_t's f_aux point back to their parent task */
    for (tp = tasklist; tp != NULL; tp = tp->t_next, i++) {
	if (tp->t_fetch == NULL)
	    fprintf(stderr, "task[%d] @" PRINTF_P_PFX "%p has no fetch group\n", i, tp);
	j = 0;
	for (fp = tp->t_fetch; fp != NULL; fp = fp->f_next) {
	    if (fp->f_aux != (void *)tp)
		fprintf(stderr, "task[%d] fetch group[%d] has invalid task pointer\n",
			i, j);
	    j++;
	}

	/* check that all optreq_t's in hash list have valid r_fetch->f_aux
	 * pointing to a task in the task list.
	 */
	for (j = 0; j < tp->t_numpmid; j++) {
	    pmid = tp->t_pmidlist[j];
	    for (hp = __pmHashSearch(pmid, &pm_hash); hp != NULL; hp = hp->next) {
		if (pmid != (pmID)hp->key)
		continue;
		rqp = (optreq_t *)hp->data;
		for (tp2 = tasklist; tp2 != NULL; tp2 = tp2->t_next)
		    if (rqp->r_fetch->f_aux == (void *)tp2)
			break;
		if (tp2 == NULL) {
		    fprintf(stderr, "task[%d] pmid %s optreq " PRINTF_P_PFX "%p for [",
			    i, pmIDStr(pmid), rqp);
		    if (rqp->r_numinst == 0)
			fputs("`all instances' ", stderr);
		    else
			for (k = 0; k < rqp->r_numinst; k++)
			    fprintf(stderr, "%d ", rqp->r_instlist[k]);
		    fputs("] bad task pointer\n", stderr);
		}
	    }
	}
    }
}

void
dumpit(void)
{
    int		i;
    task_t	*tp;

    reality_check();
    for (tp = tasklist, i = 0; tp != NULL; tp = tp->t_next, i++) {
	fprintf(stderr,
		"\ntask[%d] @" PRINTF_P_PFX "%p: %s %s \ndelta = %f\n", i, tp,
		PMLC_GET_MAND(tp->t_state) ? "mandatory " : "advisory ",
		PMLC_GET_ON(tp->t_state) ? "on " : "off ",
		tp->t_delta.tv_sec + (float)tp->t_delta.tv_usec / 1.0e6);
	__pmOptFetchDump(stderr, tp->t_fetch);
    }
}

/*
 * stolen from __pmDumpResult
 */
static void
dumpcontrol(FILE *f, const pmResult *resp, int dovalue)
{
    int		i;
    int		j;

    fprintf(f,"LogControl dump from " PRINTF_P_PFX "%p", resp);
    fprintf(f, " numpmid: %d\n", resp->numpmid);
    for (i = 0; i < resp->numpmid; i++) {
	pmValueSet	*vsp = resp->vset[i];
	fprintf(f,"  %s :", pmIDStr(vsp->pmid));
	if (vsp->numval == 0) {
	    fprintf(f, " No values!\n");
	    continue;
	}
	else if (vsp->numval < 0) {
	    fprintf(f, " %s\n", pmErrStr(vsp->numval));
	    continue;
	}
	fprintf(f, " numval: %d", vsp->numval);
	fprintf(f, " valfmt: %d", vsp->valfmt);
	for (j = 0; j < vsp->numval; j++) {
	    pmValue	*vp = &vsp->vlist[j];
	    if (vsp->numval > 1 || vp->inst != PM_INDOM_NULL) {
		fprintf(f," inst [%d]", vp->inst);
	    }
	    else
		fprintf(f, " singular");
	    if (dovalue) {
		fprintf(f, " value ");
		pmPrintValue(f, vsp->valfmt, PM_TYPE_U32, vp, 1); 
	    }
	    fputc('\n', f);
	}
    }
}

/* Called when optFetch or _pmHash routines fail.  This is terminal. */
void
die(char *name, int sts)
{
    pmNotifyErr(LOG_ERR, "%s error unrecoverable: %s\n", name, pmErrStr(sts));
    exit(1);
}

optreq_t *
findoptreq(pmID pmid, int inst)
{
    __pmHashNode	*hp;
    optreq_t	*rqp;
    optreq_t	*all_rqp = NULL;
    int		j;

    /*
     * Note:
     * The logic here assumes that for each metric-inst pair, there is
     * at most one optreq_t structure, corresponding to the logging
     * state of ON (mandatory or advisory) else OFF (mandatory).  Other
     * requests change the data structures, but do not leave optreq_t
     * structures lying about, i.e. MAYBE (mandatory) is the default,
     * and does not have to be explicitly stored, while OFF (advisory)
     * reverts to MAYBE (mandatory).
     * There is one exception to the above assumption, namely for
     * cases where the initial specification includes "all" instances,
     * then some later concurrent specification may refer to specific
     * instances ... in this case, the specific optreq_t structure is
     * the one that applies.
     */
    
    for (hp = __pmHashSearch(pmid, &pm_hash); hp != NULL; hp = hp->next) {
	if (pmid != (pmID)hp->key)
	    continue;
	rqp = (optreq_t *)hp->data;
	if (rqp->r_numinst == 0) {
	    all_rqp = rqp;
	    continue;
	}
	for (j = 0; j < rqp->r_numinst; j++)
	    if (inst == rqp->r_instlist[j])
		return rqp;
    }

    if (all_rqp != NULL)
	return all_rqp;
    else
	return NULL;
}

/* Determine whether a metric is currently known.  Returns
 *	-1 if metric not known
 *	inclusive OR of the flags below if it is known
 */
#define MF_HAS_INDOM	0x1		/* has an instance domain */
#define MF_HAS_ALL	0x2		/* has an "all instances" */
#define MF_HAS_INST	0x4		/* has specific instance(s) */
#define MF_HAS_MAND	0x8		/* has at least one inst with mandatory */
					/* logging (or is mandatory if no indom) */
static int
find_metric(pmID pmid)
{
    __pmHashNode	*hp;
    optreq_t	*rqp;
    int		result = 0;
    int		found = 0;

    for (hp = __pmHashSearch(pmid, &pm_hash); hp != NULL; hp = hp->next) {
	if (pmid != (pmID)hp->key)
	    continue;
	rqp = (optreq_t *)hp->data;
	if (found++ == 0)
	    if (rqp->r_desc->indom != PM_INDOM_NULL) {
		result |= MF_HAS_INDOM;
		if (rqp->r_numinst == 0)
		    result |= MF_HAS_ALL;
		else
		    result |= MF_HAS_INST;
	    }
	if (PMLC_GET_MAND(((task_t *)(rqp->r_fetch->f_aux))->t_state))
	    result |= MF_HAS_MAND;
    }
    return found ? result : -1;
}

/* Find an optreq_t suitable for adding a new instance */

/* Add a new metric (given a pmValueSet and a pmDesc) to the specified task.
 * Allocate and return a new task_t if the specified task pointer is nil.
 *
 * Note that this should only be called for metrics not currently in the
 * logging data structure.  All instances in the pmValueSet are added!
 */
static int
add_metric(pmValueSet *vsp, task_t **result)
{
    pmID	pmid = vsp->pmid;
    task_t	*tp = *result;
    optreq_t	*rqp;
    pmDesc	*dp;
    char	*name;
    int		sts, i, need = 0;

    dp = (pmDesc *)malloc(sizeof(pmDesc));
    if (dp == NULL) {
	pmNoMem("add_metric: new pmDesc malloc", sizeof(pmDesc), PM_FATAL_ERR);
    }
    if ((sts = pmLookupDesc(pmid, dp)) < 0)
	die("add_metric: lookup desc", sts);
    if ((sts = pmNameID(pmid, &name)) < 0)
	die("add_metric: lookup name", sts);

    /* allocate a new task if null task pointer passed in */
    if (tp == NULL) {
	tp = calloc(1, sizeof(task_t));
	if (tp == NULL) {
	    pmNoMem("add_metric: new task calloc", sizeof(task_t), PM_FATAL_ERR);
	}
	*result = tp;
    }

    /* add metric (and any instances specified) to task */
    i = tp->t_numpmid++;
    need = tp->t_numpmid * sizeof(pmID);
    if (!(tp->t_pmidlist = (pmID *)realloc(tp->t_pmidlist, need)))
	pmNoMem("add_metric: new task pmidlist realloc", need, PM_FATAL_ERR);
    need = tp->t_numpmid * sizeof(char *);
    if (!(tp->t_namelist = (char **)realloc(tp->t_namelist, need)))
	pmNoMem("add_metric: new task namelist realloc", need, PM_FATAL_ERR);
    need = tp->t_numpmid * sizeof(pmDesc);
    if (!(tp->t_desclist = (pmDesc *)realloc(tp->t_desclist, need)))
	pmNoMem("add_metric: new task desclist realloc", need, PM_FATAL_ERR);
    tp->t_pmidlist[i] = pmid;
    tp->t_namelist[i] = name;
    tp->t_desclist[i] = *dp;	/* struct assignment */
    /* is this a derived metric? */
    if (IS_DERIVED(pmid))
	tp->t_dm++;

    rqp = (optreq_t *)calloc(1, sizeof(optreq_t));
    if (rqp == NULL) {
	pmNoMem("add_metric: new task optreq calloc", need, PM_FATAL_ERR);
    }
    rqp->r_desc = dp;

    /* Now copy instances if required.  Remember that metrics with singular
     * values actually have one instance specified to distinguish them from the
     * "all instances" case (which has no instances).  Use the pmDesc to check
     * for this.
     */
    if (dp->indom != PM_INDOM_NULL)
	need = rqp->r_numinst = vsp->numval;
    if (need) {
	need *= sizeof(rqp->r_instlist[0]);
	rqp->r_instlist = (int *)malloc(need);
	if (rqp->r_instlist == NULL) {
	    pmNoMem("add_metric: new task optreq instlist malloc", need,
		     PM_FATAL_ERR);
	}
	for (i = 0; i < vsp->numval; i++)
	    rqp->r_instlist[i] = vsp->vlist[i].inst;
    }

    /* Add new metric to task's fetchgroup(s) and global hash table */
    __pmOptFetchAdd(&tp->t_fetch, rqp);
    linkback(tp);
    if ((sts = __pmHashAdd(pmid, (void *)rqp, &pm_hash)) < 0)
	die("add_metric: __pmHashAdd", sts);
    return 0;
}

/* Return true if a request for a new logging state (newstate) will be honoured
 * when current state is curstate.
 */
static int
update_ok(int curstate, int newstate)
{
    /* If new state is advisory and current is mandatory, reject request.
     * Any new mandatory state is accepted.  If the new state is advisory
     * and the current state is advisory, it is accepted.
     * Note that a new state of maybe (mandatory maybe) counts as mandatory
     */
    if (PMLC_GET_MAND(newstate) == 0 && PMLC_GET_MAYBE(newstate) == 0 &&
	PMLC_GET_MAND(curstate))
	return 0;
    else
	return 1;
}

/* Given a task and a pmID, find an optreq_t associated with the task suitable
 * for inserting a new instance into.
 * The one with the smallest number of instances is chosen.  We could also
 * have just used the first, but smallest-first gives a more even distribution.
 */
static optreq_t *
find_instoptreq(task_t *tp, pmID pmid)
{
    optreq_t	*result = NULL;
    optreq_t	*rqp;
    int		ni = 0;
    __pmHashNode	*hp;

    for (hp = __pmHashSearch(pmid, &pm_hash); hp != NULL;
	 hp = hp->next) {
	if (pmid != (pmID)hp->key)
	    continue;
	rqp = (optreq_t *)hp->data;
	if ((task_t *)rqp->r_fetch->f_aux != tp)
	    continue;
	if (rqp->r_numinst == 0)
	    continue;			/* don't want "all instances" cases */
	if (ni == 0 || rqp->r_numinst < ni) {
	    result = rqp;
	    ni = rqp->r_numinst;
	}
    }
    return result;
}

/* Delete an optreq_t from its task, free it and remove it from the hash list.
 */
static void
del_optreq(optreq_t *rqp)
{
    int		sts;
    task_t	*tp = (task_t *)rqp->r_fetch->f_aux;

    if ((sts = __pmOptFetchDel(&tp->t_fetch, rqp)) < 0)
	die("del_optreq: __pmOptFetchDel", sts);
    if ((sts = __pmHashDel(rqp->r_desc->pmid, (void *)rqp, &pm_hash)) < 0)
	die("del_optreq: __pmHashDel", sts);
    free(rqp->r_desc);
    if (rqp->r_numinst)
	free(rqp->r_instlist);
    free(rqp);
    /* TODO: remove pmid from task if that was the last optreq_t for it */
    /* TODO: remove task if last pmid removed */
}

/* Delete every instance of a given metric from the data structure.
 * The pmid is deleted from the pmidlist of every task containing an instance.
 * Return a pointer to the first pmDesc found (the only thing salvaged from the
 * smoking ruins), or nil if no instances were found.
 */
static pmDesc *
del_insts(pmID pmid)
{
    optreq_t	*rqp;
    __pmHashNode	*hp;
    task_t	*tp;
    pmDesc	*dp = NULL;
    int		i, sts, keep;

    for (hp = __pmHashSearch(pmid, &pm_hash); hp != NULL; ) {
	/* Do that BEFORE we nuke the node */
    	__pmHashNode * nextnode = hp->next;

	if (pmid == (pmID)hp->key) {
	    rqp = (optreq_t *)hp->data;
	    tp = (task_t *)rqp->r_fetch->f_aux;
	    if ((sts = __pmOptFetchDel(&tp->t_fetch, rqp)) < 0)
		die("del_insts: __pmOptFetchDel", sts);
	    if ((sts = __pmHashDel(pmid, (void *)rqp, &pm_hash)) < 0)
		die("del_insts: __pmHashDel", sts);

	    /* save the first pmDesc pointer for return and subsequent
	     *  re-use, but free all the others
	     */
	    if (dp != NULL)
		free(rqp->r_desc);
	    else
		dp = rqp->r_desc;

	    if (rqp->r_numinst)
		free(rqp->r_instlist);
	    free(rqp);

	    /* remove pmid from the task's pmid list */
	    for (i = 0; i < tp->t_numpmid; i++)
		if (tp->t_pmidlist[i] == pmid)
		    break;
	    keep = (tp->t_numpmid - 1 - i) * sizeof(tp->t_pmidlist[0]);
	    if (keep) {
		memmove(&tp->t_pmidlist[i], &tp->t_pmidlist[i+1], keep);
		memmove(&tp->t_desclist[i], &tp->t_desclist[i+1], keep);
		memmove(&tp->t_namelist[i], &tp->t_namelist[i+1], keep);
	    }

	    /* don't bother shrinking the pmidlist */
	    tp->t_numpmid--;
	    if (tp->t_numpmid == 0) {
		/* TODO: nuke the task if that was the last pmID */
	    }
	}
	hp = nextnode;
    }

    return dp;
}

/* Update an existing metric (given a pmValueSet) adding it to the specified
 * task. Allocate and return a new task_t if the specified task pointer is nil.
 */
static int
update_metric(pmValueSet *vsp, int reqstate, int mflags, task_t **result)
{
    pmID	pmid = vsp->pmid;
    task_t	*ntp = *result;		/* pointer to new task */
    task_t	*ctp;			/* pointer to current task */
    optreq_t	*rqp;
    pmDesc	*dp;
    int		i, j, inst;
    int		sts, need = 0;
    int		addpmid = 0;
    int		freedp;

    /* allocate a new task if null task pointer passed in */
    if (ntp == NULL) {
	ntp = calloc(1, sizeof(task_t));
	if (ntp == NULL) {
	    pmNoMem("update_metric: new task calloc", sizeof(task_t),
		     PM_FATAL_ERR);
	}
	*result = ntp;
    }

    if ((mflags & MF_HAS_INDOM) == 0) {
	rqp = findoptreq(pmid, 0);
	ctp = (task_t *)(rqp->r_fetch->f_aux);
	if (!update_ok(ctp->t_state, reqstate))
	    return 1;

	/* if the new state is advisory off, just remove the metric */
	if ((PMLC_GET_MAYBE(reqstate)) ||
	    (PMLC_GET_MAND(reqstate) == 0 && PMLC_GET_ON(reqstate) == 0))
	    del_optreq(rqp);
	else {
	    /* update the optreq.  For single valued metrics there are no
	     * instances involved so the sole optreq can just be re-used.
	     */
	    if ((sts = __pmOptFetchDel(&ctp->t_fetch, rqp)) < 0)
		die("update_metric: 1 metric __pmOptFetchDel", sts);
	    __pmOptFetchAdd(&ntp->t_fetch, rqp);
	    linkback(ntp);
	    addpmid = 1;
	}
    }
    else {
	/* metric has an instance domain */
	if (vsp->numval > 0) {
	    /* tricky: since optFetch can't handle instance profiles of the
	     * form "all except these specific instances", and managing it
	     * manually is just too hard, reject requests for specific
	     * metric instances if "all instances" of the metric are already
	     * being logged.
	     * Note: advisory off "all instances" is excepted since ANY request
	     * overrides and advisory off.  E.g. "advisory off all" followed by
	     * "advisory on someinsts" turns on advisory logging for
	     * "someinsts".  mflags will be zero for "advisory off" metrics.
	     */
	    if (mflags & MF_HAS_ALL)
		return 1;		/* can't turn "all" into specific insts */

	    for (i = 0; i < vsp->numval; i++) {
		dp = NULL;
		freedp = 0;
		inst = vsp->vlist[i].inst;
		rqp = findoptreq(pmid, inst);
		if (rqp != NULL) {
		    dp = rqp->r_desc;
		    ctp = (task_t *)(rqp->r_fetch->f_aux);
		    /* no work required if new task and current are the same */
		    if (ntp == ctp)
			continue;
		    if (!update_ok(ctp->t_state, reqstate))
			continue;

		    /* remove inst's group from current task */
		    if ((sts = __pmOptFetchDel(&ctp->t_fetch, rqp)) < 0)
			die("update_metric: instance add __pmOptFetchDel", sts);

		    /* put group back if there are any instances left */
		    if (rqp->r_numinst > 1) {
			/* remove inst from group */
			for (j = 0; j < rqp->r_numinst; j++)
			    if (inst == rqp->r_instlist[j])
				break;
			/* don't call memmove to move zero bytes */
			if (j < rqp->r_numinst - 1)
			    memmove(&rqp->r_instlist[j], &rqp->r_instlist[j+1],
				    (rqp->r_numinst - 1 - j) *
				    sizeof(rqp->r_instlist[0]));
			rqp->r_numinst--;
			/* (don't bother realloc-ing the instlist to a smaller size) */

			__pmOptFetchAdd(&ctp->t_fetch, rqp);
			linkback(ctp);
			/* no need to update hash list, rqp already there */
		    }
		    /* if that was the last instance, free the group */
		    else {
			if (( sts = __pmHashDel(pmid, (void *)rqp, &pm_hash)) < 0)
			    die("update_metric: instance __pmHashDel", sts);
			freedp = 1;
			free(rqp->r_instlist);
			free(rqp);
		    }
		}

		/* advisory off (mandatory maybe) metrics don't get put into
		 * the data structure
		 */
		if (PMLC_GET_MAYBE(reqstate) ||
		    (PMLC_GET_MAND(reqstate) == 0 && PMLC_GET_ON(reqstate) == 0)) {
		    if (freedp)
			free(dp);
		    continue;
		}
		addpmid = 1;

		/* try to find an existing optreq_t for the instance */
		rqp = find_instoptreq(ntp, pmid);
		if (rqp != NULL) {
		    if ((sts = __pmOptFetchDel(&ntp->t_fetch, rqp)) < 0)
			die("update_metric: instance add __pmOptFetchDel", sts);
		}
		/* no existing optreq_t found, allocate & populate a new one */
		else {
		    rqp = (optreq_t *)calloc(1, sizeof(optreq_t));
		    if (rqp == NULL) {
			pmNoMem("update_metric: optreq calloc",
				 sizeof(optreq_t), PM_FATAL_ERR);
		    }
		    /* if the metric existed but the instance didn't, we don't
		     * have a valid pmDesc (dp), so find one.
		     */
		    if (dp == NULL)  {
			/* find metric and associated pmDesc */
			__pmHashNode	*hp;

			for (hp = __pmHashSearch(pmid, &pm_hash);
			     hp != NULL; hp = hp->next) {
			    if (pmid == (pmID)hp->key)
				break;
			}
			assert(hp != NULL);
			dp = ((optreq_t *)hp->data)->r_desc;
		    }
		    /* recycle pmDesc from the old group, if possible */
		    if (freedp) {
			rqp->r_desc = dp;
			freedp = 0;
		    }
		    /* otherwise allocate & copy a new pmDesc via dp */
		    else {
			need = sizeof(pmDesc);
			rqp->r_desc = (pmDesc *)malloc(need);
			if (rqp->r_desc == NULL) {
			    pmNoMem("update_metric: new inst pmDesc malloc",
				     need, PM_FATAL_ERR);
			}
			memcpy(rqp->r_desc, dp, need);
		    }
		    if ((sts = __pmHashAdd(pmid, (void *)rqp, &pm_hash)) < 0)
			die("update_metric: __pmHashAdd", sts);
		}
		    
		need = (rqp->r_numinst + 1) * sizeof(rqp->r_instlist[0]);
		rqp->r_instlist = (int *)realloc(rqp->r_instlist, need);
		if (rqp->r_instlist == NULL) {
		    pmNoMem("update_metric: inst list resize", need,
			     PM_FATAL_ERR);
		}
		rqp->r_instlist[rqp->r_numinst++] = inst;
		__pmOptFetchAdd(&ntp->t_fetch, rqp);
		linkback(ntp);
		if (freedp)
		    free(dp);
	    }
	}
	/* the vset has numval == 0, a request for "all instances" */
	else {
	    /* if the metric is a singular instance that has mandatory logging
	     * or has at least one instance with mandatory logging on, a
	     * request for advisory logging cannot be honoured
	     */
	    if ((mflags & MF_HAS_MAND) &&
		PMLC_GET_MAND(reqstate) == 0 && PMLC_GET_MAYBE(reqstate) == 0)
		return 1;

	    if (mflags & MF_HAS_ALL) {
		/* if there is an "all instances" for the metric, it will be
		 * the only optreq_t for the metric
		 */
		rqp = findoptreq(pmid, 0);
		ctp = (task_t *)rqp->r_fetch->f_aux;

		/* if the metric is "advisory on, all instances"  and the
		 * request is for "mandatory maybe, all instances" the current
		 * advisory logging state of the metric is retained
		 */
		if (PMLC_GET_MAND(ctp->t_state) == 0 && PMLC_GET_MAYBE(reqstate))
		    return 0;

		/* advisory off & mandatory maybe metrics don't get put into
		 * the data structure
		 */
		if (PMLC_GET_MAYBE(reqstate) ||
		    (PMLC_GET_MAND(reqstate) == 0 && PMLC_GET_ON(reqstate) == 0)) {
		    del_optreq(rqp);
		    return 0;
		}

		addpmid = 1;
		if ((sts = __pmOptFetchDel(&ctp->t_fetch, rqp)) < 0)
		    die("update_metric: all inst __pmOptFetchDel", sts);
		/* don't delete from hash list, rqp re-used */
		__pmOptFetchAdd(&ntp->t_fetch, rqp);
		linkback(ntp);
	    }
	    else {
		/* there are one or more specific instances for the metric.
		 * The metric cannot have an "all instances" at the same time.
		 *
		 * if the request is for "mandatory maybe, all instances" and
		 * the only instances of the metric all have advisory logging
		 * on, retain the current advisory semantics.
		 */
		if (PMLC_GET_MAYBE(reqstate) &&
		    (mflags & MF_HAS_INST) && !(mflags & MF_HAS_MAND))
		    return 0;

		dp = del_insts(pmid);

		/* advisory off (mandatory maybe) metrics don't get put into
		 * the data structure
		 */
		if (PMLC_GET_MAYBE(reqstate) ||
		    (PMLC_GET_MAND(reqstate) == 0 && PMLC_GET_ON(reqstate) == 0)) {
		    free(dp);
		    return 0;
		}

		addpmid = 1;
		rqp = (optreq_t *)calloc(1, sizeof(optreq_t));
		if (rqp == NULL) {
		    pmNoMem("update_metric: all inst calloc",
			     sizeof(optreq_t), PM_FATAL_ERR);
		}
		rqp->r_desc = dp;
		__pmOptFetchAdd(&ntp->t_fetch, rqp);
		linkback(ntp);
		if ((sts = __pmHashAdd(pmid, (void *)rqp, &pm_hash)) < 0)
		    die("update_metric: all inst __pmHashAdd", sts);
	    }
	}
    }

    if (!addpmid)
	return 0;

    /* add pmid to new task if not already there */
    for (i = 0; i < ntp->t_numpmid; i++)
	if (pmid == ntp->t_pmidlist[i])
	    break;
    if (i >= ntp->t_numpmid) {
	pmDesc	desc;
	char	*name;

	if ((sts = pmLookupDesc(pmid, &desc)) < 0)
	    die("update_metric: cannot lookup desc", sts);
	if ((sts = pmNameID(pmid, &name)) < 0)
	    die("update_metric: cannot lookup name", sts);

	need = (ntp->t_numpmid + 1) * sizeof(pmID);
	if (!(ntp->t_pmidlist = (pmID *)realloc(ntp->t_pmidlist, need)))
	    pmNoMem("update_metric: grow task pmidlist", need, PM_FATAL_ERR);
	need = (ntp->t_numpmid + 1) * sizeof(char *);
	if (!(ntp->t_namelist = (char **)realloc(ntp->t_namelist, need)))
	    pmNoMem("update_metric: grow task namelist", need, PM_FATAL_ERR);
	need = (ntp->t_numpmid + 1) * sizeof(pmDesc);
	if (!(ntp->t_desclist = (pmDesc *)realloc(ntp->t_desclist, need)))
	    pmNoMem("update_metric: grow task desclist", need, PM_FATAL_ERR);
	i = ntp->t_numpmid;
	ntp->t_pmidlist[i] = pmid;
	ntp->t_namelist[i] = name;
	ntp->t_desclist[i] = desc;
	ntp->t_numpmid++;
    }
    return 0;
}

/* Given a state and a delta, return the first matching task.
 * Return NULL if a matching task was not found.
 */
task_t *
find_task(int state, struct timeval *arg_delta)
{
    task_t	*tp;

    for (tp = tasklist; tp != NULL; tp = tp->t_next) {
	if (state == (tp->t_state & 0x3) &&  /* MAND|ON */
	    arg_delta->tv_sec == tp->t_delta.tv_sec &&
	    arg_delta->tv_usec == tp->t_delta.tv_usec)
	    break;
    }
    return tp;
}

/* Return a mask containing the history flags for a given metric/instance.
 * the history flags indicate whether the metric/instance is in the log at all
 * and whether the last fetch of the metric/instance was successful.
 *
 * The result is suitable for ORing into the result returned by a control log
 * request.
 */
static int
gethistflags(pmID pmid, int inst)
{
    __pmHashNode	*hp;
    pmidhist_t		*php;
    insthist_t		*ihp;
    int			i, found;
    int			val;

    for (hp = __pmHashSearch(pmid, &hist_hash); hp != NULL; hp = hp->next)
	if ((pmID)hp->key == pmid)
	    break;
    if (hp == NULL)
	return 0;
    php = (pmidhist_t *)hp->data;
    ihp = &php->ph_instlist[0];
    val = 0;
    if (php->ph_indom != PM_INDOM_NULL) {
	for (i = 0; i < php->ph_numinst; i++, ihp++)
	    if (ihp->ih_inst == inst)
		break;
	found = i < php->ph_numinst;
    }
    else
	found = php->ph_numinst > 0;
    if (found) {
	PMLC_SET_INLOG(val, 1);
	val |= ihp->ih_flags;		/* only "available flag" is ever set */
    }
    return val;
}

/*
 * Take a pmResult (from a control log request) and half-clone it: return a
 * pointer to a new pmResult struct which shares the pmValueSets in the
 * original that have numval > 0, and has null pointers for the pmValueSets
 * in the original with numval <= 0
 *
 * Note:	do not convert to __pmResult, the pmlc protocol for a
 *		control log request uses pmResult
 */
static pmResult *
siamise_request(pmResult *request)
{
    int		i, need;
    pmValueSet	*vsp;
    pmResult	*result;

    need = sizeof(pmResult) + (request->numpmid - 1) * sizeof(pmValueSet *);
    result = (pmResult *)malloc(need);
    if (result == NULL) {
	pmNoMem("siamise_request: malloc pmResult", need, PM_FATAL_ERR);
    }
    for (i = 0; i < request->numpmid; i++) {
	vsp = request->vset[i];
	if (vsp->numval > 0)
	    result->vset[i] = request->vset[i];
	else
	    result->vset[i] = NULL;
    }
    result->timestamp = request->timestamp; /* structure assignment */
    result->numpmid = request->numpmid;

    return result;
}

/* Temporarily borrow a bit in the metric/instance history to indicate that
 * the instance currently exists in the instance domain.  The macros below
 * set and get the bit, which is cleared after we are finished with it here.
 */

#define PMLC_SET_USEINDOM(val, flag) (val = (val & ~0x1000) | (flag << 12 ))
#define PMLC_GET_USEINDOM(val) ((val & 0x1000) >> 12)

/* create a pmValueSet large enough to contain the union of the current
 * instance domain of the specified metric and any previous instances from
 * the history list.
 */
static pmValueSet *
build_vset(pmID pmid, int usehist)
{
    __pmHashNode		*hp;
    pmidhist_t		*php = NULL;
    insthist_t		*ihp;
    int			need = 0;
    int			i, numindom = 0;
    pmDesc		desc;
    int			have_desc;
    int			*instlist = NULL;
    char		**namelist = NULL;
    pmValueSet		*vsp;

   if (usehist) {
	/* find the number of instances of the metric in the history (1 if
	 * single-valued metric)
	 */
	for (hp = __pmHashSearch(pmid, &hist_hash); hp != NULL; hp = hp->next)
	    if ((pmID)hp->key == pmid)
		break;
	if (hp != NULL) {
	    php = (pmidhist_t *)hp->data;
	    need = php->ph_numinst;
	}
    }
    /*
     * get the current instance domain, so that if the metric hasn't been
     * logged yet a sensible result is returned.
     */
    if ((have_desc = pmLookupDesc(pmid, &desc)) < 0)
	goto no_info;
    if (desc.indom == PM_INDOM_NULL)
	need = 1;			/* will be same in history */
    else {
	int	j;
	    
	if ((numindom = pmGetInDom(desc.indom, &instlist, &namelist)) < 0) {
	    have_desc = numindom;
	    goto no_info;
	}
	/* php will be null if usehist is false or there is no history yet */
	if (php == NULL)
	    need = numindom;		/* no history => use indom */
	else
	    for (i = 0; i < numindom; i++) {
		int	inst = instlist[i];
		
		for (j = 0; j < php->ph_numinst; j++)
		    if (inst == php->ph_instlist[j].ih_inst)
			break;
		/*
		 * if instance is in history but not instance domain, leave
		 * extra space for it in vset, otherwise use the USEINDOM
		 * flag to avoid another NxM comparison when building the vset
		 * instances later.
		 */
		if (j >= php->ph_numinst)
		    need++;
		else
		    PMLC_SET_USEINDOM(php->ph_instlist[j].ih_flags, 1);
	    }
    }

no_info:

    need = sizeof(pmValueSet) + (need - 1) * sizeof(pmValue);
    vsp = (pmValueSet *)malloc(need);
    if (vsp == NULL) {
	pmNoMem("build_vset for control/enquire", need, PM_FATAL_ERR);
    }
    vsp->pmid = pmid;
    if (have_desc < 0) {
	vsp->numval = have_desc;
    }
    else if (desc.indom == PM_INDOM_NULL) {
	vsp->vlist[0].inst = PM_IN_NULL;
	vsp->numval = 1;
    }
    else {
	int	j;
	
	i = 0;
	/* get instances out of instance domain first */
	if (numindom > 0)
	    for (j = 0; j < numindom; j++)
		vsp->vlist[i++].inst = instlist[j];

	/* then any not in instance domain from history */
	if (php != NULL) {
	    ihp = &php->ph_instlist[0];
	    for (j = 0; j < php->ph_numinst; j++, ihp++)
		if (PMLC_GET_USEINDOM(ihp->ih_flags))
		    /* it's already in the indom */
		    PMLC_SET_USEINDOM(ihp->ih_flags, 0);
		else
		    vsp->vlist[i++].inst = ihp->ih_inst;
	}
	vsp->numval = i;
    }
    if (instlist)
	free(instlist);
    if (namelist)
	free(namelist);
    
    return vsp;
}


int
do_control_req(pmResult *request, int control, int state, int arg_delta, int sendresult)
{
    int			sts;
    pmResult		*result;
    int			siamised = 0;	/* the verb from siamese (as in twins) */
    int			i;
    int			j;
    int			val;
    pmValueSet		*vsp;
    optreq_t		*rqp;
    task_t		*tp;
    time_t		now;
    int			reqstate = 0;

    if (pmDebugOptions.pmlc) {
	fprintf(stderr, "do_control_req: control=%d state=%d delta=%d request ...\n",
		control, state, arg_delta);
	dumpcontrol(stderr, request, 0);
    }

    if (control == PM_LOG_MANDATORY || control == PM_LOG_ADVISORY) {
	time(&now);
	fprintf(stderr, "\n%s", ctime(&now));
	fprintf(stderr, "pmlc request from %s: %s",
	    pmlc_host[0] ? pmlc_host : "pmlogger", control == PM_LOG_MANDATORY ? "mandatory" : "advisory");
	if (state == PM_LOG_ON) {
	    if (arg_delta == 0)
		fprintf(stderr, " on once\n");
	    else
		fprintf(stderr, " on %.1f sec\n", (float)arg_delta/1000);
	}
	else if (state == PM_LOG_OFF)
	    fprintf(stderr, " off\n");
	else
	    fprintf(stderr, " maybe\n");
    }

    /*
     * access control checks
     */
    sts = 0;
    switch (control) {
	case PM_LOG_MANDATORY:
	    if (denyops & PM_OP_LOG_MAND)
		sts = PM_ERR_PERMISSION;
	    break;

	case PM_LOG_ADVISORY:
	    if (denyops & PM_OP_LOG_ADV)
		sts = PM_ERR_PERMISSION;
	    break;

	case PM_LOG_ENQUIRE:
	    /*
	     * Don't need to check [access] as you have to have _some_
	     * permission (at least one of PM_OP_LOG_ADV or PM_OP_LOG_MAND
	     * and PM_OP_LOG_ENQ) to make a connection ... and if you
	     * have either PM_OP_LOG_ADV or PM_OP_LOG_MAND it makes no
	     * sense to deny PM_OP_LOG_ENQ operations.
	     */
	    break;

	default:
	    fprintf(stderr, "Bad control PDU type %d\n", control);
	    sts = PM_ERR_IPC;
	    break;
    }
    if (sts < 0) {
	fprintf(stderr, "Error: %s\n", pmErrStr(sts));
	if (sendresult && (sts = __pmSendError(clientfd, FROM_ANON, sts)) < 0)
	    pmNotifyErr(LOG_ERR,
			 "do_control: error sending Error PDU to client: %s\n",
			 pmErrStr(sts));
	pmFreeResult(request);
	return sts;
    }

    /* handle everything except PM_LOG_ENQUIRE */
    if (control == PM_LOG_MANDATORY || control == PM_LOG_ADVISORY) {
	/* update the logging status of metrics */

	task_t		*newtp = NULL; /* task for metrics/insts in request */
	struct timeval	tdelta = { 0 };
	int		newtask;
	int		mflags;

	/* convert state and control to the bitmask used in pmlogger and values
	 * returned in results.  Remember that reqstate starts with nothing on.
	 */
	if (state == PM_LOG_ON)
	    PMLC_SET_ON(reqstate, 1);
	else
	    PMLC_SET_ON(reqstate, 0);
	if (control == PM_LOG_MANDATORY) {
	    if (state == PM_LOG_MAYBE)
		/* mandatory+maybe => maybe+advisory+off  */
		PMLC_SET_MAYBE(reqstate, 1);
	    else
		PMLC_SET_MAND(reqstate, 1);
	}

	/* try to find an existing task for the request
	 * Never return a "once only" task, it may have gone off already and just
	 * be hanging around like a bad smell.
	 */
	if (arg_delta != 0) {
	    tdelta.tv_sec = arg_delta / 1000;
	    tdelta.tv_usec = (arg_delta % 1000) * 1000;
	    newtp = find_task(reqstate, &tdelta);
	}
	newtask = (newtp == NULL);

	for (i = 0; i < request->numpmid; i++) {
	    vsp = request->vset[i];
	    if (vsp->numval < 0)
		/*
		 * request is malformed, as we cannot control logging
		 * for an undefined instance ... there is no way to
		 * return an error from here, so simply ignore this
		 * metric
		 */
		continue;
	    mflags = find_metric(vsp->pmid);
	    if (mflags < 0) {
		/* only add new metrics if they are ON or MANDATORY OFF
		 * Careful: mandatory+maybe is mandatory+maybe+off
		 */
		if (PMLC_GET_ON(reqstate) ||
		    (PMLC_GET_MAND(reqstate) && !PMLC_GET_MAYBE(reqstate)))
		    add_metric(vsp, &newtp);
	    }
	    else
		/* already a specification for this metric */
		update_metric(vsp, reqstate, mflags, &newtp);
	}

	/* schedule new logging task if new metric(s) specified */
	if (newtask && newtp != NULL) {
	    if (newtp->t_fetch == NULL) {
		/* the new task ended up with no fetch groups, throw it away */
		if (newtp->t_pmidlist != NULL)
		    free(newtp->t_pmidlist);
		free(newtp);
	    }
	    else {
		/* link new task into tasklist */
		newtp->t_next = tasklist;
		tasklist = newtp;

		/* use only the MAND/ADV and ON/OFF bits of reqstate */
		newtp->t_state = PMLC_GET_STATE(reqstate);
		if (PMLC_GET_ON(reqstate)) {
		    newtp->t_delta = tdelta;
		    newtp->t_afid = __pmAFsetup(NULL, &tdelta, (void *)newtp, log_callback);
		}
		else
		    newtp->t_delta.tv_sec = newtp->t_delta.tv_usec = 0;
		linkback(newtp);
	    }
	}
    }

    if (pmDebugOptions.appl0)
	dumpit();

    /* just ignore advisory+maybe---the returned pmResult will have the metrics
     * in their original state indicating that the request could not be
     * satisfied.
     */

    result = request;
    result->timestamp.tv_sec = result->timestamp.tv_usec = 0;	/* for purify */
    /* write the current state of affairs into the result _pmResult */
    for (i = 0; i < request->numpmid; i++) {

	if (control == PM_LOG_MANDATORY || control == PM_LOG_ADVISORY) {
	    char	**names;

	    sts = pmNameAll(request->vset[i]->pmid, &names);
	    if (sts < 0)
		fprintf(stderr, "  metric: %s", pmIDStr(request->vset[i]->pmid));
	    else {
		fprintf(stderr, "  metric: ");
		__pmPrintMetricNames(stderr, sts, names, " or ");
		free(names);
	    }
	}

	if (request->vset[i]->numval <= 0 && !siamised) {
	    result = siamise_request(request);
	    siamised = 1;
	}
	/*
	 * pmids with numval <= 0 in the request have a null vset ptr in the
	 * in the corresponding place in the siamised result.
	 */
	if (result->vset[i] != NULL)
	    vsp = result->vset[i];
	else {
	    /* the result should also contain the history for an all instances
	     * enquire request.  Control requests just get the current indom
	     * since the user of pmlc really wants to see what's being logged
	     * now rather than in the past.
	     */
	    vsp = build_vset(request->vset[i]->pmid, control == PM_LOG_ENQUIRE);
	    result->vset[i] = vsp;
	}
	vsp->valfmt = PM_VAL_INSITU;
	for (j = 0; j < vsp->numval; j++) {
	    rqp = findoptreq(vsp->pmid, vsp->vlist[j].inst);
	    val = 0;
	    if (rqp == NULL) {
		PMLC_SET_STATE(val, 0);
		PMLC_SET_DELTA(val, 0);
	    }
	    else {
		tp = rqp->r_fetch->f_aux;
		PMLC_SET_STATE(val, tp->t_state);
		PMLC_SET_DELTA(val, (tp->t_delta.tv_sec*1000 + tp->t_delta.tv_usec/1000));
	    }

	    val |= gethistflags(vsp->pmid, vsp->vlist[j].inst);
	    vsp->vlist[j].value.lval = val;

	    if (control == PM_LOG_MANDATORY || control == PM_LOG_ADVISORY) {
		int	expstate = 0;
		int	statemask = 0;
		int	expdelta;
		if (rqp != NULL && rqp->r_desc->indom != PM_INDOM_NULL) {
		    char	*p;
		    if (j == 0)
			fputc('\n', stderr);
		    if (pmNameInDom(rqp->r_desc->indom, vsp->vlist[j].inst, &p) >= 0) {
			fprintf(stderr, "    instance: %s", p);
			free(p);
		    }
		    else
			fprintf(stderr, "    instance: #%d", vsp->vlist[j].inst);
		}
		else {
		    /* no pmDesc ... punt */
		    if (vsp->numval > 1 || vsp->vlist[j].inst != PM_IN_NULL) {
			if (j == 0)
			    fputc('\n', stderr);
			fprintf(stderr, "    instance: #%d", vsp->vlist[j].inst);
		    }
		}
		if (state != PM_LOG_MAYBE) {
		    if (control == PM_LOG_MANDATORY)
			PMLC_SET_MAND(expstate, 1);
		    else
			PMLC_SET_MAND(expstate, 0);
		    if (state == PM_LOG_ON)
			PMLC_SET_ON(expstate, 1);
		    else
			PMLC_SET_ON(expstate, 0);
		    PMLC_SET_MAND(statemask, 1);
		    PMLC_SET_ON(statemask, 1);
		}
		else {
		    PMLC_SET_MAND(expstate, 0);
		    PMLC_SET_MAND(statemask, 1);
		}
		expdelta = PMLC_GET_ON(expstate) ? arg_delta : 0;
		if ((PMLC_GET_STATE(val) & statemask) != expstate ||
		    PMLC_GET_DELTA(val) != expdelta)
			fprintf(stderr, " [request failed]");
		fputc('\n', stderr);
	    }
	}
    }

    if (pmDebugOptions.pmlc) {
	__pmDumpResult(stderr, result);
    }

    if (sendresult && (sts = __pmSendResult(clientfd, FROM_ANON, result)) < 0)
		pmNotifyErr(LOG_ERR,
			     "do_control: error sending Error PDU to client: %s\n",
			     pmErrStr(sts));

    if (siamised) {
	for (i = 0; i < request->numpmid; i++)
	    if (request->vset[i]->numval <= 0)
		free(result->vset[i]);
	free(result);
    }
    pmFreeResult(request);

    return 0;
}

static int
do_control(__pmPDU *pb)
{
    int			sts;
    int			control;
    int			state;
    int			ldelta;
    int			sendresult = 1;
    pmResult		*request;

    /*
     * TODO	- encoding for logging interval in requests and results?
     */
    if ((sts = __pmDecodeLogControl(pb, &request, &control, &state, &ldelta)) < 0) {
	fprintf(stderr, "Error: %s\n", pmErrStr(sts));
	if ((sts = __pmSendError(clientfd, FROM_ANON, sts)) < 0)
	    pmNotifyErr(LOG_ERR,
			 "do_control: error sending Error PDU to client: %s\n",
			 pmErrStr(sts));
	pmFreeResult(request);
	return sts;
    }
    sts = do_control_req(request, control, state, ldelta, sendresult);
    return sts;
}

/*
 * sendstatus
 */
static int
sendstatus(void)
{
    int				rv;
    int				version;
    static int			firsttime = 1;
    static char			*tzlogger;
    struct timeval		now;

    if (firsttime) {
        tzlogger = __pmTimezone();
	firsttime = 0;
    }

    if ((version = __pmVersionIPC(clientfd)) < 0)
	return version;

    if (version >= LOG_PDU_VERSION2) {
	__pmLoggerStatus		ls;

	if ((ls.state = logctl.state) == PM_LOG_STATE_NEW) {
	    ls.start.sec = ls.start.nsec = 0;
	} else {
	    ls.start = logctl.label.start;	/* struct assignment */
	}

	ls.last.sec = last_stamp.tv_sec;
	ls.last.nsec = last_stamp.tv_usec * 1000;
	pmtimevalNow(&now);
	ls.now.sec = now.tv_sec;
	ls.now.nsec = now.tv_usec * 1000;
	ls.vol = archctl.ac_curvol;
	ls.size = __pmFtell(archctl.ac_mfp);
	assert(ls.size >= 0);

	ls.pmcd.hostname = ls.pmcd.fqdn = ls.pmcd.timezone = ls.pmcd.zoneinfo = NULL;
	ls.pmlogger.timezone = ls.pmlogger.zoneinfo = NULL;

	if ((ls.pmcd.hostname = strdup(logctl.label.hostname)) == NULL) {
	    int		sts = -oserror();
	    pmNoMem("sendstatus: hostname", strlen(logctl.label.hostname), PM_RECOV_ERR);
	    return sts;
	}
        /* BTW, that string should equal pmcd_host[]. */

        /* NB: FQDN cleanup: there is no such thing as 'the fully
           qualified domain name' of a server: it may have several or
           none; the set may have changed since the time the log
           archive was collected.  Now that we store the then-current
           pmcd.hostname in the hostname (and thus get it reported
           in hostname), we could pass something else informative
           in the fqdn slot.  Namely, pmcd_host_conn[], which is the
           access path pmlogger's using to get to the pmcd. */
	if ((ls.pmcd.fqdn = strdup(pmcd_host_conn)) == NULL) {
	    int		sts = -oserror();
	    pmNoMem("sendstatus: fqdn", strlen(pmcd_host_conn), PM_RECOV_ERR);
	    __pmFreeLogStatus(&ls, 0);
	    return sts;
	}

	if ((ls.pmcd.timezone = strdup(logctl.label.timezone)) == NULL) {
	    int		sts = -oserror();
	    pmNoMem("sendstatus: pmcd.timezone", strlen(logctl.label.timezone), PM_RECOV_ERR);
	    __pmFreeLogStatus(&ls, 0);
	    return sts;
	}
	if (tzlogger != NULL) {
	    if ((ls.pmlogger.timezone = strdup(tzlogger)) == NULL) {
		int	sts = -oserror();
		pmNoMem("sendstatus: pmlogger.timezone", strlen(tzlogger), PM_RECOV_ERR);
		__pmFreeLogStatus(&ls, 0);
		return sts;
	    }
	}

	rv = __pmSendLogStatus(clientfd, &ls);
	__pmFreeLogStatus(&ls, 0);
    }
    else
	rv = PM_ERR_IPC;
    return rv;
}

static int
do_request(__pmPDU *pb)
{
    int		sts;
    int		type;

    if ((sts = __pmDecodeLogRequest(pb, &type)) < 0) {
	pmNotifyErr(LOG_ERR, "do_request: error decoding PDU: %s\n", pmErrStr(sts));
	return PM_ERR_IPC;
    }

    switch (type) {
	case LOG_REQUEST_STATUS:
	    /*
	     * Don't need to check [access] as you have to have _some_
	     * permission (at least one of PM_OP_LOG_ADV or PM_OP_LOG_MAND
	     * and PM_OP_LOG_ENQ) to make a connection ... and if you
	     * have either PM_OP_LOG_ADV or PM_OP_LOG_MAND it makes no
	     * sense to deny LOG_REQUEST_STATUS operations.
	     * Also, this is needed internally by pmlc to discover pmcd's
	     * hostname.
	     */
	    sts = sendstatus();
	    break;

	case LOG_REQUEST_NEWVOLUME:
	    if (denyops & PM_OP_LOG_MAND)
		sts = __pmSendError(clientfd, FROM_ANON, PM_ERR_PERMISSION);
	    else {
		sts = newvolume(VOL_SW_PMLC);
		if (sts >= 0)
		    sts = logctl.label.vol;
		sts = __pmSendError(clientfd, FROM_ANON, sts);
	    }
	    break;

	case LOG_REQUEST_SYNC:
	    /*
	     * Don't need to check access controls, as this is now
	     * a no-op with unbuffered I/O from pmlogger.
	     *
	     * Do nothing, simply send status 0 back to pmlc.
	     */
	    sts = __pmSendError(clientfd, FROM_ANON, 0);
	    break;

	/*
	 * QA support ... intended for error injection
	 * If the request is > QA_OFF then this is a code to enable
	 * a specific style of error behaviour.  If the request
	 * is QA_OFF, this disables the error behaviour.
	 *
	 * Supported behaviours.
	 * QA_SLEEPY
	 *	After this exchange with pmlc, sleep for 5 seconds
	 * 	after each incoming pmlc request ... allows testing
	 * 	of timeout logic in pmlc
	 */

	case QA_OFF:
	    if (denyops & PM_OP_LOG_MAND)
		sts = __pmSendError(clientfd, FROM_ANON, PM_ERR_PERMISSION);
	    else {
		qa_case = 0;
		sts = __pmSendError(clientfd, FROM_ANON, 0);
	    }
	    break;

	case QA_SLEEPY:
	    if (denyops & PM_OP_LOG_MAND)
		sts = __pmSendError(clientfd, FROM_ANON, PM_ERR_PERMISSION);
	    else {
		qa_case = type;
		sts = __pmSendError(clientfd, FROM_ANON, 0);
	    }
	    break;

	default:
	    fprintf(stderr, "do_request: bad request type %d\n", type);
	    sts = PM_ERR_IPC;
	    break;
    }
    return sts;
}

static int
do_creds(__pmPDU *pb)
{
    int		i;
    int		sts;
    int		version = UNKNOWN_VERSION;
    int		credcount;
    int		sender;
    __pmCred	*credlist = NULL;

    if ((sts = __pmDecodeCreds(pb, &sender, &credcount, &credlist)) < 0) {
	pmNotifyErr(LOG_ERR, "do_creds: error decoding PDU: %s\n", pmErrStr(sts));
		return PM_ERR_IPC;
    }

    for (i = 0; i < credcount; i++) {
	if (credlist[i].c_type == CVERSION) {
	    version = credlist[i].c_vala;
	    if ((sts = __pmSetVersionIPC(clientfd, version)) < 0) {
		free(credlist);
		return sts;
	    }
	}
    }

    if (credlist)
	free(credlist);

    if (pmDebugOptions.pmlc)
	fprintf(stderr, "do_creds: pmlc PDU version=%d\n", version);

    return sts;
}

/*
 * Service a request from the pmlogger client.
 * Return non-zero if the client has closed the connection.
 */
int
client_req(void)
{
    int		sts;
    __pmPDU	*pb;
    __pmPDUHdr	*php;
    int		pinpdu;

    if ((pinpdu = sts = __pmGetPDU(clientfd, ANY_SIZE, TIMEOUT_DEFAULT, &pb)) <= 0) {
	if (sts != 0)
	    fprintf(stderr, "client_req: %s\n", pmErrStr(sts));
	return 1;
    }
    if (qa_case == QA_SLEEPY) {
	/* error injection - delay before processing and responding */
	sleep(5);
    }
    php = (__pmPDUHdr *)pb;
    sts = 0;

    switch (php->type) {
	case PDU_CREDS:		/* version 2 PDU */
	    sts = do_creds(pb);
	    break;
	case PDU_LOG_REQUEST:	/* version 2 PDU */
	    sts = do_request(pb);
	    break;
	case PDU_LOG_CONTROL:	/* version 2 PDU */
	    sts = do_control(pb);
	    break;
	default:		/*  unknown PDU  */
	    fprintf(stderr, "client_req: bad PDU type 0x%x\n", php->type);
	    sts = PM_ERR_IPC;
	    break;
    }
    if (pinpdu > 0)
	__pmUnpinPDUBuf(pb);
    
    if (sts >= 0)
	return 0;
    else {
	/* the client isn't playing by the rules */
	__pmSendError(clientfd, FROM_ANON, sts);
	return 1;
    }
}
