Net2/usr/src/contrib/isode/snmp/snmpb.c

Compare this file to the similar file:
Show the results in this format:

/* snmpb.c - snmpi bulk load */

#ifndef	lint
static char *rcsid = "$Header: /f/osi/snmp/RCS/snmpb.c,v 7.15 91/02/22 09:44:08 mrose Interim $";
#endif

/* 
 * $Header: /f/osi/snmp/RCS/snmpb.c,v 7.15 91/02/22 09:44:08 mrose Interim $
 *
 * Contributed by NYSERNet Inc.  This work was partially supported by the
 * U.S. Defense Advanced Research Projects Agency and the Rome Air Development
 * Center of the U.S. Air Force Systems Command under contract number
 * F30602-88-C-0016.
 *
 *
 * $Log:	snmpb.c,v $
 * Revision 7.15  91/02/22  09:44:08  mrose
 * Interim 6.8
 * 
 * Revision 7.14  90/09/17  11:18:42  mrose
 * update
 * 
 * Revision 7.13  90/09/10  13:52:21  mrose
 * kzm
 * 
 * Revision 7.11  90/09/03  12:57:30  mrose
 * update
 * 
 * Revision 7.10  90/08/28  10:29:24  mrose
 * kzm
 * 
 * Revision 7.9  90/08/23  12:34:29  mrose
 * update
 * 
 * Revision 7.8  90/08/20  21:25:56  mrose
 * touch-up
 * 
 * Revision 7.7  90/08/20  14:00:14  mrose
 * kzm
 * 
 * Revision 7.6  90/08/19  16:26:25  mrose
 * again
 * 
 * Revision 7.5  90/08/18  15:24:08  mrose
 * one more time
 * 
 * Revision 7.4  90/08/18  01:28:40  mrose
 * again
 * 
 * Revision 7.3  90/08/18  00:44:39  mrose
 * touch-up
 * 
 * Revision 7.2  90/08/16  16:50:37  mrose
 * again
 * 
 * Revision 7.1  90/08/14  14:28:43  mrose
 * pktin
 * 
 * Revision 7.0  90/08/08  14:00:19  mrose
 * *** empty log message ***
 * 
 */

/*
 *				  NOTICE
 *
 *    Acquisition, use, and distribution of this module and related
 *    materials are subject to the restrictions of a license agreement.
 *    Consult the Preface in the User's Manual for the full terms of
 *    this agreement.
 *
 */


#include <stdio.h>
#include "SNMP-types.h"
#include "objects.h"
#include "tailor.h"

#ifdef	BSD42

#define	MAXTIME		(60 * 1000L)	/* in milli-seconds */
#define	MINTIME		 1		/*   .. */

#define	SETTLETIME	30		/* # of iterations to gauge RTT */

#define	MAXTRIES	 3		/* for retries */

#define	MAXSPACE   0x10000		/* in between threads */
#define	MAXTHREADS	10		/* maximum #-simultaneous */
#define	MAXBOUNDS	10
#define	MINBOUNDS	 3

/*  */

					/* TIMING INFORMATION */
static	long	timeout;
static	u_long	timenow;
static	long	timemin;
static	long	timemax;
static	int	timelap;
static	int	timelap2;
static  int     RTTperthread = MAXTIME;


					/* BINDING INFORMATION (results) */
struct binding {
    OID	    b_name;

    union {
	PE	        un_value;

	struct binding *un_cols;
    }	    b_un;
#define	b_value	b_un.un_value
#define	b_cols	b_un.un_cols

    struct binding *b_next;
};


					/* INVOCATION INFORMATION */
static	int	last_id = 0;

struct invocation {
    struct type_SNMP_Message *i_msg;
    int	    i_info;		/* info >  0: response
				   info == 0: request
				   info <  0: not yet ready */

    int	    i_rid;		/* request-id of interest */
    int	    i_mid;		/* exclusive ceiling on i_rid */

    PE	    i_pe;		/* message to retry */
    int	    i_retries;		/* number of times request retried */
    u_long  i_lastime;		/* time last request sent */
    int     i_curinvokes;       /* current # threads when last request sent */
};

static	int	totreqs = 0;
static	int	totretr = 0;
static	int	totrsps = 0;
static	int	totdups = 0;


					/* THREAD INFORMATION */
struct thread {
    struct thread *t_forw;	/* doubly-linked list */
    struct thread *t_back;	/*   .. */

    OID	    t_lo;		/* inclusive lower-bound */
    OID	    t_arg;		/* current pointer */
    OID	    t_hi;		/* exclusive upper-bound */

    struct binding *t_binding;	/* for bulk2_aux() */

    struct invocation t_invoke;	/* invocation in progress */
#define	t_msg	      t_invoke.i_msg
#define	t_info	      t_invoke.i_info
#define	t_rid	      t_invoke.i_rid
#define	t_mid	      t_invoke.i_mid
#define	t_pe	      t_invoke.i_pe
#define	t_retries     t_invoke.i_retries
#define	t_lastime     t_invoke.i_lastime
#define	t_curinvokes  t_invoke.i_curinvokes

				/* statistics */
    int	    t_gns;		/* how many gn's done */
};

static int	once_only = 0;

static struct thread tque;	/* active thread list */
static struct thread *THead = &tque;

static	int	curthreads = 0;
static	int	maxthreads = 0;
static	int	tothreads = 0;
static	int	nilthreads = 0;
static  int	dedthreads = 0;

static  int     threadlimit = MAXTHREADS;


					/* REQUEST INFORMATION */
struct request {
    struct request *r_forw;	/* doubly-linked list */
    struct request *r_back;	/*   .. */

    int	    r_nbound;		/* number of bounds active */
    struct bound {
	OID	r_lo;		    /* inclusive lower bound */
	OID	r_arg;		    /* current pointer */
	OID	r_hi;		    /* exclusive upper bound */
        int     b_gns;		    /* stats: how many gn's done */

	struct bound *r_next;
    }	    *r_bounds;

    struct invocation r_invoke;	/* invocation in progress... */
#define	r_msg	      r_invoke.i_msg
#define	r_info	      r_invoke.i_info
#define	r_rid	      r_invoke.i_rid
#define	r_mid	      r_invoke.i_mid
#define	r_pe	      r_invoke.i_pe
#define	r_retries     r_invoke.i_retries
#define	r_lastime     r_invoke.i_lastime
#define	r_curinvokes  r_invoke.i_curinvokes
};
    
static struct request rque;	/* active request list */
static struct request *RHead = &rque;

static	int	currequests = 0;
static	int	maxrequests = 0;
static	int	totbounds = 0;
static	int	maxbounds = 0;
static	int	nilbounds = 0;
static	int	dedrequests = 0;

static	int	boundlimit = MAXBOUNDS;


					/* MISCELLANEOUS INFORMATION */
OID	oid_median (), oid_copy ();


extern	int	debug;
extern	int	watch;

void	adios (), advise ();
char   *snmp_error ();

/*    BULK1 */

/* algorithm assumes that first variable in VarBind has ubiquitous agent
   support...
 */

bulk1 (ps, sd, vb, community)
PS	ps;
int	sd;
struct type_SNMP_VarBindList *vb;
char   *community;
{
    int	    backoff,
	    rows;
    register struct thread *t;
    register struct binding **bp;
    struct binding *bl;
    struct timeval  tvs,
		    now;
    register OID    a,
		    b;
    OID	    arg;

    timeout = 2 * 1000L;
    timemin = timemax = timeout;

    curthreads = maxthreads = tothreads = nilthreads = dedthreads = 0;

    threadlimit = MAXTHREADS;
    RTTperthread = MAXTIME;

    totreqs = totretr = totrsps = totdups = 0;

    (void) gettimeofday (&tvs, (struct timezone *) 0);
    timenow = tvs.tv_sec * 1000L + tvs.tv_usec / 1000L;

    a = oid_copy (arg = vb -> VarBind -> name);
    a -> oid_elements[a -> oid_nelem++] = 127;
    if (new_thread (ps, vb, community, arg, a) == NOTOK)
	goto losing;

    b = oid_copy (arg);
    b -> oid_elements[b -> oid_nelem++] = 192;
    if (new_thread (ps, vb, community, a, b) == NOTOK)
	goto losing;

    a -> oid_elements[(--a -> oid_nelem) - 1]++;
    if (new_thread (ps, vb, community, b, a) == NOTOK)
	goto losing;

    oid_free (a);
    oid_free (b);

    bp = &bl, bl = NULL, rows = 0;
    for (timelap = 0; THead -> t_forw != THead; timelap++) {
	register struct binding *bv;
	struct thread *u;

	if ((backoff = wait_for_action (sd, ps)) == NOTOK)
	    break;

	for (t = THead -> t_forw; t != THead; t = u) {
	    register struct binding **bz;
	    register struct type_SNMP_VarBindList *vp,
						  *vb2;
	    struct OIDentifier oids;

	    u = t -> t_forw;
	    if (!t -> t_info)
		continue;

	    if (oid_cmp (t -> t_arg, b = t -> t_msg -> data -> un.get__response
			 		   -> variable__bindings -> VarBind
								 -> name)
		    >= 0) {
		char buffer[BUFSIZ];

		(void) strcpy (buffer, oid2ode (t -> t_arg));
		advise (NULLCP,
			"agent botched get-next (%s -> %s), thread dead",
			buffer, oid2ode (b));

		free_thread (t);
		dedthreads++;
		continue;
	    }

	    if (oid_cmp (b, t -> t_hi) >= 0) {
		free_thread (t);
		continue;
	    }
	    oid_free (t -> t_arg);
	    t -> t_arg = oid_copy (b);

	    a = vb -> VarBind -> name, b = t -> t_arg;
	    oids.oid_nelem = b -> oid_nelem - a -> oid_nelem;
	    oids.oid_elements = b -> oid_elements + a -> oid_nelem;

	    if ((bv = (struct binding *) calloc (1, sizeof *bv)) == NULL)
		adios (NULLCP, "out of memory");
	    *bp = bv, bp = &bv -> b_next;

	    bv -> b_name = oid_copy (&oids);
	    bz = &bv -> b_cols;
	    rows++;

	    for (vp = t -> t_msg -> data -> un.get__response 
					 -> variable__bindings, vb2 = vb;
		     vp;
		     vp = vp -> next, vb2 = vb2 -> next) {
		int	fixup = 0;
		a = vb2 -> VarBind -> name, b = vp -> VarBind -> name;

		if (a -> oid_nelem > b -> oid_nelem
		        || bcmp ((char *) a -> oid_elements,
				 (char *) b -> oid_elements,
				 a -> oid_nelem
				         * sizeof a -> oid_elements[0])) {
		    oid_free (b);
		    vp -> VarBind -> name = b = oid_copy (t -> t_arg);
		    bcopy ((char *) a -> oid_elements,
			   (char *) b -> oid_elements,
			   a -> oid_nelem * sizeof *a -> oid_elements);

/* not really needed...
		    pe_free (vp -> VarBind -> value);
		    if ((vp -> VarBind -> value = pe_alloc (PE_CLASS_UNIV,
							     PE_FORM_PRIM,
							     PE_PRIM_NULL))
			    == NULL)
			adios (NULLCP, "out of memory");
 */
		    fixup = 1;
		}

		if ((bv = (struct binding *) calloc (1, sizeof *bv)) == NULL)
		    adios (NULLCP, "out of memory");
		*bz = bv, bz = &bv -> b_next;

		bv -> b_name = oid_copy (b);
		if (!fixup) {
		    bv -> b_value = vp -> VarBind -> value;
		    if ((vp -> VarBind -> value = pe_alloc (PE_CLASS_UNIV,
							    PE_FORM_PRIM,
							    PE_PRIM_NULL))
		            == NULLPE)
			adios (NULLCP, "out of memory");
		}
	    }

	    if (curthreads < threadlimit
		    && !backoff
		    && (a = oid_median (t -> t_arg, t -> t_hi))) {
		if (new_thread (ps, vb, community, a, t -> t_hi) == NOTOK)
		    goto losing;

		remque (t);		/* fairness in splits... */
		insque (t, THead -> t_back);

		oid_free (t -> t_hi);
		t -> t_hi = a;
		backoff = 1;
	    }

	    if (next_thread (t, ps, 1) == NOTOK)
		goto losing;
	}
    }

    if (backoff == NOTOK) {
losing: ;
	advise (NULLCP, "aborting bulk retrieval...");

	while ((t = THead -> t_forw) != THead)
	    free_thread (t);
    }

    (void) gettimeofday (&now, (struct timezone *) 0);
    now.tv_sec -= tvs.tv_sec;
    if ((now.tv_usec -= tvs.tv_usec) < 0)
	now.tv_sec--, now.tv_usec += 1000000;
    advise (NULLCP,
	    "%d row%s retrieved in %d.%06d seconds during %d iterations",
	    rows, rows != 1 ? "s" : "", now.tv_sec, now.tv_usec, timelap);
    advise (NULLCP,
	    "threads:  at most %d active, total of %d created, and %d did nothing",
	    maxthreads, tothreads, nilthreads);
    advise (NULLCP, "messages: %d request%s sent,  along with %d retr%s",
	    totreqs, totreqs != 1 ? "s" : "", totretr,
	    totretr != 1 ? "ies" : "y");
    advise (NULLCP, "          %d response%s rcvd, along with %d duplicate%s",
	    totrsps, totrsps != 1 ? "s" : "", totdups,
	    totdups != 1 ? "s" : "");
    advise (NULLCP, "timeouts: min=%d.%03d fin=%d.%03d max=%d.%03d seconds",
	    timemin / 1000, timemin % 1000, timeout / 1000, timeout % 1000,
	    timemax / 1000, timemax % 1000);

    print_bulk (bl, vb, dedthreads);
}

/*  */

static int  wait_for_action (sd, ps)
int	sd;
PS	ps;
{
    int	    backoff,
	    n,
	    nfds,
	    request_id;
    long    maxrtt;
    u_long  lastime;
    fd_set  rfds;
    struct timeval tvs;
    struct type_SNMP_Message *msg;
    register struct type_SNMP_PDU *parm;
    register struct invocation *i;
    register struct request *r,
                            *u;
    register struct thread  *t,
			    *s;
    PE	    pe;

    FD_ZERO (&rfds);

    nfds = sd + 1;
    FD_SET (sd, &rfds);

    backoff = 1;

    lastime = timenow;
    for (t = THead -> t_forw; t != THead; t = t -> t_forw) {
	if (t -> t_info)	/* should always fail... */
	    continue;

	for (s = t -> t_forw; s != THead; s = s -> t_forw)
	    if (!s -> t_info && s -> t_lastime < t -> t_lastime)
		t = s;

	if (lastime > t -> t_lastime)
	    lastime = t -> t_lastime;
    }
    for (r = RHead -> r_forw; r != RHead; r = r -> r_forw) {
	if (r -> r_info)	/* should always fail... */
	    continue;

	for (u = r -> r_forw; u != RHead; u = u -> r_forw)
	    if (!u -> r_info && u -> r_lastime < r -> r_lastime)
		r = u;

	if (lastime > r -> r_lastime)
	    lastime = r -> r_lastime;
    }
    if ((maxrtt = timeout - (timenow - lastime)) < 0)
	maxrtt = 0;
    if (debug && maxrtt < timeout)
	fprintf (stderr, "timeout reduced from %u to %u (delta %d)\n",
		 timeout, (u_long) maxrtt, (int) (timeout - maxrtt));
    tvs.tv_sec = maxrtt / 1000L, tvs.tv_usec = (maxrtt % 1000) * 1000L;
    maxrtt = 0;

    switch (n = select (nfds, &rfds, NULLFD, NULLFD, &tvs)) {
	case NOTOK:
	    advise ("failed", "select");
	    return NOTOK;

	case OK:
	default:
	    (void) gettimeofday (&tvs, (struct timezone *) 0);
	    timenow = tvs.tv_sec * 1000L + tvs.tv_usec / 1000L;

	    if (n == OK)
		break;

again: ;
	    if ((pe = ps2pe (ps)) == NULLPE) {
		advise (NULLCP, "ps2pe: %s", ps_error (ps -> ps_errno));
		return NOTOK;
	    }

	    msg = NULL;
	    if (decode_SNMP_Message (pe, 1, NULLIP, NULLVP, &msg) == NOTOK) {
		advise (NULLCP, "decode_SNMP_Message: %s", PY_pepy);
oops: ;
		if (msg)
		    free_SNMP_Message (msg);
		pe_free (pe);
		return NOTOK;
	    }
	    if (watch) {
		fprintf (stdout, "read PDU\n");
		(void) print_SNMP_Message (pe, 1, NULLIP, NULLVP, NULLCP);
		(void) fflush (stdout);
	    }
	    totrsps++;
	    if (msg -> data -> offset != type_SNMP_PDUs_get__response) {
		advise (NULLCP, "unexpected message type %d",
			msg -> data -> offset);
		goto oops;
	    }
	    
	    request_id =
		    (parm = msg -> data -> un.get__response) -> request__id;

	    for (t = THead -> t_forw; t != THead; t = s) {
		s = t -> t_forw;
		if (t -> t_rid != request_id)
		    continue;

		if (t -> t_info) {
		    advise (NULLCP, "duplicated response for request-id %d",
			    request_id);
		    goto duplicate_id;
		}

		t -> t_info = 1;
		free_SNMP_Message (t -> t_msg);
		t -> t_msg = msg;
		if (t -> t_pe)
		    pe_free (t -> t_pe);
		t -> t_pe = pe;

		if (parm -> error__status != int_SNMP_error__status_noError) {
		    if (parm -> error__status
			    != int_SNMP_error__status_noSuchName)
			advise (NULLCP, "%s for get-next of %s, thread dead",
				snmp_error (parm -> error__status),
				oid2ode (t -> t_arg));
		    free_thread (t);
		    dedthreads++;
		}

		i = &t -> t_invoke;
		goto finish_invoke;
	    }

	    for (r = RHead -> r_forw; r != RHead; r = u) {
		u = r -> r_forw;
		if (r -> r_rid != request_id)
		    continue;

		if (r -> r_info) {
		    advise (NULLCP, "duplicated response for request-id %d",
			    request_id);
		    goto duplicate_id;
		}

		switch (parm -> error__status) {
		    case int_SNMP_error__status_noError:
		        r -> r_info = 1;
			free_SNMP_Message (r -> r_msg);
			r -> r_msg = msg;
			if (r -> r_pe)
			    pe_free (r -> r_pe);
			r -> r_pe = pe;
			break;

		    case int_SNMP_error__status_tooBig:
			advise (NULLCP, "got %s on request of size %d",
				snmp_error (parm -> error__status),
				r -> r_nbound);
			if (1 < r -> r_nbound && r -> r_nbound <= boundlimit)
			    boundlimit = r -> r_nbound - 1;

toss_it: ;
			r -> r_info = -1;
			free_SNMP_Message (msg);
			pe_free (pe);
			goto next_request;

		    case int_SNMP_error__status_noSuchName:
			if (parm -> error__index == 0) {
invalid_index: ;
			    advise (NULLCP,
				    "got %s with invalid index (%d)",
				    snmp_error (parm -> error__status),
				    parm -> error__index);
			    goto drop_request;
			}
			{
			    register int    j;
			    register struct bound  *b,
						  **bp;
			    register struct type_SNMP_VarBindList  *v,
							          **vp;

			    bp = &r -> r_bounds;
			    vp = &r -> r_msg -> data -> un.get__request
							-> variable__bindings;
			    for (j = parm -> error__index;
				     --j > 0;
				     bp = &((*bp) -> r_next),
				 	vp = &((*vp) -> next))
				if (*bp == NULL)
				    goto invalid_index;
			    advise (NULLCP,
				    "got %s on %s",
				    snmp_error (parm -> error__status),
				    oid2ode ((*bp) -> r_arg));

			    b = *bp, bp = &b -> r_next;
	                    free_bound (b);
			    r -> r_nbound--;

			    v = *vp, vp = &v -> next;
			    v -> next = NULL;
			    free_SNMP_VarBindList (v);

			    goto toss_it;
			}

		    default:
			advise (NULLCP, "%s for get-next of %s, et. al.",
				    snmp_error (parm -> error__status),
				    oid2ode (r -> r_bounds ->  r_arg));
drop_request: ;
			free_request (r);
			dedrequests++;
			goto next_request;
		}
		i = &r -> r_invoke;

finish_invoke: ;
		if (i -> i_retries == 0) {
		    long    val = timenow - i -> i_lastime;

		    if (maxrtt < val)
			maxrtt = val;
		    if (timelap < SETTLETIME) {
		        int  rtt = maxrtt / i -> i_curinvokes;

			if (RTTperthread > rtt) {
			    RTTperthread = rtt;

			    if ((threadlimit = i -> i_curinvokes + 1) 
						> MAXTHREADS)
			        threadlimit = MAXTHREADS;
			}
		    }
		    if (debug)
			fprintf (stderr,
                                 "time now %u, xmit-time %u, RTT %u\n",
				 timenow, i -> i_lastime,
				 timenow - i -> i_lastime);
		    backoff = 0;
		}
		else
		    i -> i_retries = 0;
		goto next_request;
	    }
	    if (debug)
		fprintf (stderr, "request-id mismatch, not expecting %d\n",
			 request_id);
duplicate_id: ;
	    free_SNMP_Message (msg);
	    pe_free (pe);
	    totrsps--, totdups++;
next_request: ;

	    FD_SET (sd, &rfds);
	    if (select_dgram_socket (nfds, &rfds, NULLFD, NULLFD, OK) > OK)
		goto again;

	    break;
    }

    if (curthreads > maxthreads)
	maxthreads = curthreads;
    if (currequests > maxrequests)
	maxrequests = currequests;

    if (backoff) {
	if ((timeout <<= 1) > MAXTIME)
	    timeout = MAXTIME;
	if (debug)
	    fprintf (stderr, "adjusted timeout to %g seconds(0)\n",
		     timeout / 1000.0);
    }
    else
	if (maxrtt > 0 && maxrtt != timeout) {
	    long    timedelta = maxrtt + (maxrtt >> 1);

	    if (timedelta > timeout) {
		if ((timeout = timedelta) > MAXTIME)
		    timeout = MAXTIME;
		backoff = 1;
		goto outta_time;
	    }
	    else {
		timeout -= (timeout - timedelta) >> 1;
		if (timeout  < MINTIME)
		    timeout = MINTIME;

outta_time: ;
		if (debug)
		    fprintf (stderr,
			    "adjusted timeout to %g seconds(1)\n",
			    timeout / 1000.0);
	    }
	}

    if (timeout < timemin)
	timemin = timeout;
    else
	if (timeout > timemax)
	    timemax = timeout;

    i = NULL;
    for (r = RHead -> r_forw; r != RHead; r = r -> r_forw) {
	if (r -> r_info)
	    continue;

	for (u = r -> r_forw; u != RHead; u = u -> r_forw)
	    if (!u -> r_info && u -> r_lastime < r -> r_lastime)
		r = u;

	i = &r -> r_invoke;
    }
    for (t = THead -> t_forw; t != THead; t = t -> t_forw) {
	if (t -> t_info)
	    continue;

	for (s = t -> t_forw; s != THead; s = s -> t_forw)
	    if (!s -> t_info && s -> t_lastime < t -> t_lastime)
		t = s;

	if (i == NULL || t -> t_lastime < i -> i_lastime)
	    i = &t -> t_invoke;
    }

    if (i && i -> i_lastime + timeout < timenow) {
	if (++i -> i_retries > MAXTRIES) {
	    advise (NULLCP, "too many retries (%d)", MAXTRIES);
	    return NOTOK;
	}

	if (pe2ps (ps, i -> i_pe) == NOTOK) {
	    advise (NULLCP, "pe2ps: %s", ps_error (ps -> ps_errno));
	    return NOTOK;
	}
	if (watch) {
	    fprintf (stdout, "retry ID %d\n", i -> i_rid);
	    (void) print_SNMP_Message (i -> i_pe, 1, NULLIP, NULLVP, NULLCP);
	    (void) fflush (stdout);
	}
	else
	    if (debug)
		fprintf (stderr,
			 "retry ID %d, time now %u, waiting %u, timeout %u\n",
			 i -> i_rid, timenow, timenow - i -> i_lastime,
			 timeout);

	totretr++;

	i -> i_lastime = timenow;
	i -> i_curinvokes = currequests ? currequests : 1;
	backoff = 1;
    }

    return backoff;
}

/*  */

static	print_bulk (bl, vb, partial)
struct binding *bl;
struct type_SNMP_VarBindList *vb;
int	partial;
{
    int	    i;
    register struct binding *bv,
			    *bz;

    if (partial)
	printf ("partial results only...\n");

    i = strlen ("row");
    for (bv = bl; bv; bv = bv -> b_next) {
	register int    j;
	char   *cp = sprintoid (bv -> b_name);

	if (i < (j = strlen (cp)))
	    i = j;
    }

    printf ("%-*s", i, "row");
    for (; vb; vb = vb -> next)
	printf ("\t%s", oid2ode (vb -> VarBind -> name));

    for (bv = bl; bv; bv = bz) {
	register struct binding *bp,
				*bq;

	bz = bv -> b_next;

	printf ("\n%-*s", i, sprintoid (bv -> b_name));

	for (bp = bv -> b_cols; bp; bp = bq) {
	    bq = bp -> b_next;

	    printf ("\t");

	    if (bp -> b_value) {
		caddr_t	value;
		register OI	oi;
		register OS	os;

		if ((oi = name2inst (bp -> b_name)) == NULL
		    	|| (os = oi -> oi_type -> ot_syntax) == NULL
		    	|| (*os -> os_decode) (&value, bp -> b_value) == NOTOK)
		    vunknown (bp -> b_value);
		else {
		    (*os -> os_print) (value, os);
		    (*os -> os_free) (value);
		}

		pe_free (bp -> b_value);
	    }
	    else
		printf ("NULL");

	    oid_free (bp -> b_name);

	    free ((char *) bp);
	}

	oid_free (bv -> b_name);

	free ((char *) bv);
    }
    printf ("\n");
}

/*  */

static struct type_SNMP_Message *new_message (arg, vb, community, next)
OID	arg;
struct type_SNMP_VarBindList *vb;
char   *community;
int	next;
{
    register struct type_SNMP_Message *msg;
    register struct type_SNMP_PDUs *pdu;
    register struct type_SNMP_PDU *parm;
    register struct type_SNMP_VarBindList **vp;

    if ((msg = (struct type_SNMP_Message *) calloc (1, sizeof *msg)) == NULL)
	adios (NULLCP, "out of memory");

    msg -> version = int_SNMP_version_version__1;

    if ((msg -> community = str2qb (community, strlen (community), 1)) == NULL)
	adios (NULLCP, "out of memory");

    if ((pdu = (struct type_SNMP_PDUs *) calloc (1, sizeof *pdu)) == NULL)
	adios (NULLCP, "out of memory");
    msg -> data = pdu;

    pdu -> offset = next ? type_SNMP_PDUs_get__next__request
			 : type_SNMP_PDUs_get__request;
    
/* for now, always a PDU... */

    if ((parm = (struct type_SNMP_PDU *) calloc (1, sizeof *parm)) == NULL)
	adios (NULLCP, "out of memory");
    pdu -> un.get__request = parm;

    for (vp = &parm -> variable__bindings; vb; vb = vb -> next) {
	register struct type_SNMP_VarBindList *bind;
	register struct type_SNMP_VarBind *v;

	if ((bind = (struct type_SNMP_VarBindList *) calloc (1, sizeof *bind))
	    	    == NULL)
	    adios (NULLCP, "out of memory");
	*vp = bind, vp = &bind -> next;

	if ((v = (struct type_SNMP_VarBind *) calloc (1, sizeof *v)) == NULL)
	    adios (NULLCP, "out of memory");
	bind -> VarBind = v;

	v -> name = oid_copy (arg);
	bcopy ((char *) vb -> VarBind -> name -> oid_elements,
	       (char *) v -> name -> oid_elements,
	       vb -> VarBind -> name -> oid_nelem
	           * sizeof *v -> name -> oid_elements);

	if ((v -> value = pe_alloc (PE_CLASS_UNIV, PE_FORM_PRIM, PE_PRIM_NULL))
	        == NULL)
	    adios (NULLCP, "out of memory");
    }
    
    return msg;
}

/*    THREADS */

static int  new_thread (ps, vb, community, start, stop)
PS	ps;
struct type_SNMP_VarBindList *vb;
char   *community;
OID	start,
	stop;
{
    register struct thread *t;

    t = (struct thread *) calloc (1, sizeof *t);
    if (t == NULL)
	adios (NULLCP, "new_thread: out of memory");

    t -> t_rid = last_id;
    t -> t_mid = (last_id += MAXSPACE);

    t -> t_lo = oid_copy (start);
    t -> t_arg = oid_copy (start);
    t -> t_hi = oid_copy (stop);

    t -> t_msg = new_message (t -> t_arg, vb, community, 1);

    if (once_only == 0) {
	THead -> t_forw = THead -> t_back = THead;
	RHead -> r_forw = RHead -> r_back = RHead;
	once_only++;
    }

    insque (t, THead -> t_back);

    curthreads++;
    tothreads++;

    return next_thread (t, ps, 1);
}

/*  */

static int  new_string (ps, vb, community, bp)
PS	ps;
struct type_SNMP_VarBindList *vb;
char   *community;
struct binding *bp;
{
    register struct thread *t;

    t = (struct thread *) calloc (1, sizeof *t);
    if (t == NULL)
	adios (NULLCP, "new_thread: out of memory");

    t -> t_rid = last_id;
    t -> t_mid = (last_id += MAXSPACE);

    t -> t_binding = bp;

    t -> t_msg = new_message (bp -> b_cols -> b_name, vb, community, 0);

    insque (t, THead -> t_back);

    curthreads++;
    tothreads++;

    return next_thread (t, ps, 0);
}

/*  */

static	free_thread (t)
register struct thread *t;
{
    if (debug && t -> t_lo) {
	fprintf (stderr, "thread from %s to ", oid2ode (t -> t_lo));
	fprintf (stderr, "%s did %d get-nexts\n", oid2ode (t -> t_hi),
		 t -> t_gns);
    }
    curthreads--;
    if (t -> t_gns <= 1)
	nilthreads++;

    if (t -> t_pe)
	pe_free (t -> t_pe);

    oid_free (t -> t_lo);
    oid_free (t -> t_arg);
    oid_free (t -> t_hi);

    if (t -> t_msg)
	free_SNMP_Message (t -> t_msg);

    remque (t);
    free ((char *) t);
}

/*  */

static int  next_thread (t, ps, next)
register struct thread *t;
PS	ps;
int	next;
{
    if (++t -> t_rid >= t -> t_mid)
	t -> t_rid = t -> t_mid - MAXSPACE;

    t -> t_info = 0;
    t -> t_msg -> data -> offset =
	next ? type_SNMP_PDUs_get__next__request : type_SNMP_PDUs_get__request;
    t -> t_msg -> data -> un.get__response -> request__id = t -> t_rid;

    if (t -> t_pe)
	pe_free (t -> t_pe), t -> t_pe = NULL;

    if (encode_SNMP_Message (&t -> t_pe, 1, 0, NULLCP, t -> t_msg) == NOTOK) {
	advise (NULLCP, "encode_SNMP_Message: %s", PY_pepy);
	return NOTOK;
    }

    if (pe2ps (ps, t -> t_pe) == NOTOK) {
	advise (NULLCP, "pe2ps: %s", ps_error (ps -> ps_errno));
	return NOTOK;
    }
    if (watch) {
	fprintf (stdout, "write PDU\n");
	(void) print_SNMP_Message (t -> t_pe, 1, NULLIP, NULLVP, NULLCP);
	(void) fflush (stdout);
    }
    totreqs++;

    t -> t_lastime = timenow;
    t -> t_curinvokes = curthreads ? curthreads : 1;
    t -> t_gns++;

    return OK;
}

/*    BULK2 */

bulk2 (ps, sd, vb, community)
PS	ps;
int	sd;
register struct type_SNMP_VarBindList *vb;
char   *community;
{
    int	    backoff,
	    evalreq,
	    rows;
    register struct request  *r;
    register struct binding **bp;
    struct binding *bl;
    struct timeval  tvs,
		    now;
    register OID    a,
		    b;
    OID	    arg;

    timeout = 2 * 1000L;
    timemin = timemax = timeout;

    currequests = maxrequests = totbounds = maxbounds = dedrequests = 0;
    nilbounds = 0;

    threadlimit = MAXTHREADS;
    boundlimit = MAXBOUNDS;
    RTTperthread = MAXTIME;

    totreqs = totretr = totrsps = totdups = 0;

    (void) gettimeofday (&tvs, (struct timezone *) 0);
    timenow = tvs.tv_sec * 1000L + tvs.tv_usec / 1000L;

    a = oid_copy (arg = vb -> VarBind -> name);
    a -> oid_elements[a -> oid_nelem++] = 127;
    new_bound (community, arg, a);

    b = oid_copy (arg);
    b -> oid_elements[b -> oid_nelem++] = 192;
    new_bound (community, a, b);

    a -> oid_elements[(--a -> oid_nelem) - 1]++;
    new_bound (community, b, a);

    oid_free (a);
    oid_free (b);

    if (push_requests (ps, community, 0) == NOTOK)
	goto losing;

    bp = &bl, bl = NULL, rows = 0;
    for (timelap = 0; RHead -> r_forw != RHead; timelap++) {
	struct request *u;

	if ((backoff = wait_for_action (sd, ps)) == NOTOK)
	    goto losing;

	for (r = RHead -> r_forw; r != RHead; r = u) {
	    register struct bound  *br,
				   *bz,
				  **bb;
	    register struct type_SNMP_VarBindList  *vr,
						  **vv;

	    u = r -> r_forw;
	    if (r -> r_info < 1)
		continue;

	    bb = &r -> r_bounds;
	    vv = &r -> r_msg -> data -> un.get__request -> variable__bindings;
	    evalreq = 0;
	    while (br = *bb) {
		register struct binding *bv,
					*bv2;
		struct OIDentifier oids;

		if ((vr = *vv) == NULL) {
		    advise (NULLCP,
			    "missing variable in response, continuing");
		    dedrequests++;

		    do {
			bz = br -> r_next;
			free_bound (br);
			r -> r_nbound--;
		    }
		    while (br = bz);
		    *bb = NULL;

		    break;
		}

		if (oid_cmp (br -> r_arg, b = vr -> VarBind -> name) >= 0) {
		    char    buffer[BUFSIZ];

		    (void) strcpy (buffer, oid2ode (br -> r_arg));
		    advise (NULLCP,
			    "agent botched get-next (%s -> %s), continuing",
			    oid2ode (b));

		    dedrequests++;
		    goto drop_bound;
		}

		if (oid_cmp (b, br -> r_hi) >= 0) {
		    if (br -> b_gns <=1)
			evalreq--;
drop_bound: ;
		    *bb = br -> r_next;
		    free_bound (br);
		    r -> r_nbound--;

		    *vv = vr -> next;
		    vr -> next = NULL;
		    free_SNMP_VarBindList (vr);

		    continue;
		}
		evalreq++;
		oid_free (br -> r_arg);
		br -> r_arg = oid_copy (b);

		oids.oid_nelem = b -> oid_nelem - arg -> oid_nelem;
		oids.oid_elements = b -> oid_elements + arg -> oid_nelem;

		if ((bv = (struct binding *) calloc (1, sizeof *bv)) == NULL)
		    adios (NULLCP, "out of memory");
		*bp = bv, bp = &bv -> b_next;

		bv -> b_name = oid_copy (&oids);
		rows++;

		if ((bv2 = (struct binding *) calloc (1, sizeof *bv2)) == NULL)
		    adios (NULLCP, "out of memory");
		bv -> b_cols = bv2;

		bv2 -> b_name = oid_copy (b);
		bv2 -> b_value = vr -> VarBind -> value;
		if ((vr -> VarBind -> value = pe_alloc (PE_CLASS_UNIV,
							PE_FORM_PRIM,
							PE_PRIM_NULL))
		        == NULLPE)
		    adios (NULLCP, "out of memory");

		bb = &br -> r_next, vv = &vr -> next;
	    }
	    if (vr = *vv) {
		advise (NULLCP, "too many variables in response");

		free_SNMP_VarBindList (vr);
		*vv = NULL;
	    }
	    if (timelap >= SETTLETIME
		    && evalreq < 0 && boundlimit != MINBOUNDS)
		boundlimit--;
	}

	if (push_requests (ps, community,
			   currequests < threadlimit && !backoff) == NOTOK)
	    goto losing;
    }

    if (bulk2_aux (ps, sd, bl, vb, community) == NOTOK) {
losing: ;
	advise (NULLCP, "aborting bulk retrieval...");

	while ((r = RHead -> r_forw) != RHead)
	    free_request (r);
    }

    (void) gettimeofday (&now, (struct timezone *) 0);
    now.tv_sec -= tvs.tv_sec;
    if ((now.tv_usec -= tvs.tv_usec) < 0)
	now.tv_sec--, now.tv_usec += 1000000;
    advise (NULLCP,
	    "%d row%s retrieved in %d.%06d seconds during %d/%d iterations",
	    rows, rows != 1 ? "s" : "", now.tv_sec, now.tv_usec, timelap,
	    timelap2);
    advise (NULLCP, "requests: at most %d active", maxrequests);
    advise (NULLCP,
	    "bounds:   %d created, at most %d active, %d integral, %d did nothing",
	    totbounds, maxbounds, boundlimit, nilbounds);
    if (timelap2)
	advise (NULLCP,
		"threads:  at most %d active, total of %d created",
		maxthreads, tothreads);
    advise (NULLCP, "messages: %d request%s sent,  along with %d retr%s",
	    totreqs, totreqs != 1 ? "s" : "", totretr,
	    totretr != 1 ? "ies" : "y");
    advise (NULLCP, "          %d response%s rcvd, along with %d duplicate%s",
	    totrsps, totrsps != 1 ? "s" : "", totdups,
	    totdups != 1 ? "s" : "");
    advise (NULLCP, "timeouts: min=%d.%03d fin=%d.%03d max=%d.%03d seconds",
	    timemin / 1000, timemin % 1000, timeout / 1000, timeout % 1000,
	    timemax / 1000, timemax % 1000);

    print_bulk (bl, vb, dedrequests || dedthreads);
}

/*  */

/*  */

static int  bulk2_aux (ps, sd, bl, vb, community)
PS	ps;
int	sd;
struct binding *bl;
register struct type_SNMP_VarBindList *vb;
char   *community;
{
    int	    backoff;
    register struct thread *t;

    curthreads = maxthreads = tothreads = nilthreads = dedthreads = 0;
    timelap2 = 0;

    if (bl == NULL || (vb = vb -> next) == NULL)
	return OK;

    while (bl)
	if (curthreads < threadlimit) {
	    if (new_string (ps, vb, community, bl) == NOTOK)
		goto losing;
	    bl = bl -> b_next;
	}
	else
	    break;

    for (; THead -> t_forw != THead; timelap2++) {
	struct thread *u;

	if ((backoff = wait_for_action (sd, ps)) == NOTOK)
	    break;

	for (t = THead -> t_forw; t != THead; t = u) {
	    register struct binding  *bv,
				    **bz;
	    register struct type_SNMP_VarBindList *vp;

	    u = t -> t_forw;
	    if (!t -> t_info)
		continue;

	    bv = t -> t_binding;
	    bz = &bv -> b_cols -> b_next;

	    for (vp = t -> t_msg -> data -> un.get__response
		 			 -> variable__bindings;
		     vp;
		     vp = vp -> next) {
		if ((bv = (struct binding *) calloc (1, sizeof *bv)) == NULL)
		    adios (NULLCP, "out of memory");
		*bz = bv, bz = &bv -> b_next;

		bv -> b_name = oid_copy (vp -> VarBind -> name);
		bv -> b_value = vp -> VarBind -> value;
		if ((vp -> VarBind -> value = pe_alloc (PE_CLASS_UNIV,
							PE_FORM_PRIM,
							PE_PRIM_NULL))
		        == NULLPE)
		    adios (NULLCP, "out of memory");
	    }

	    if (curthreads < threadlimit && !backoff && bl) {
		if (new_string (ps, vb, community, bl) == NOTOK)
		    goto losing;
		bl = bl -> b_next;
		backoff = 1;
	    }

	    free_thread (t);
	    if (bl) {
		if (new_string (ps, vb, community, bl) == NOTOK)
		    goto losing;
		bl = bl -> b_next;
	    }		
	}
    }

    if (backoff == NOTOK) {
losing: ;
	while ((t = THead -> t_forw) != THead)
	    free_thread (t);
    }

    return backoff;
}

/*    REQUESTS */

static struct request *new_request (community)
char   *community;
{
    register struct request *r;

    r = (struct request *) calloc (1, sizeof *r);
    if (r == NULL)
	adios (NULLCP, "new_request: out of memory");

    r -> r_rid = last_id;
    r -> r_mid = (last_id += MAXSPACE);

    r -> r_info = -1;
    r -> r_msg = new_message (NULLOID, (struct type_SNMP_VarBindList *) NULL,
			      community, 1);

    insque (r, RHead -> r_back);

    currequests++;

    return r;
}

/*  */

static	free_request (r)
register struct request *r;
{
    register struct bound *bp,
			  *bq;

    currequests--;

    if (r -> r_pe)
	pe_free (r -> r_pe);

    for (bp = r -> r_bounds; bp; bp = bq) {
	bq = bp -> r_next;
	free_bound (bp);
    }

    if (r -> r_msg)
	free_SNMP_Message (r -> r_msg);

    remque (r);
    free ((char *) r);
}

/*  */

static int  new_bound (community, start, stop)
char   *community;
OID	start,
	stop;
{
    register struct request *r;
    register struct bound *b;
    register struct type_SNMP_VarBindList *vb;
    register struct type_SNMP_VarBind *v;

    if (once_only == 0) {
	THead -> t_forw = THead -> t_back = THead;
	RHead -> r_forw = RHead -> r_back = RHead;
	once_only++;
    }

    if ((r = RHead -> r_forw) == RHead)
	r = new_request (community);

    if ((b = (struct bound *) calloc (1, sizeof *b)) == NULL)
	adios (NULLCP, "new_bound: out of memory");
    b -> r_next = r -> r_bounds, r -> r_bounds = b;

    b -> b_gns = 0;
    b -> r_lo = oid_copy (start);
    b -> r_arg = oid_copy (start);
    b -> r_hi = oid_copy (stop);
    totbounds++;

    if ((vb = (struct type_SNMP_VarBindList *) calloc (1, sizeof *vb)) == NULL)
	adios (NULLCP, "new_bound: out of memory");
    vb -> next = r -> r_msg -> data -> un.get__request -> variable__bindings,
	r -> r_msg -> data -> un.get__request -> variable__bindings = vb;

    if ((v = (struct type_SNMP_VarBind *) calloc (1, sizeof *v)) == NULL)
	adios (NULLCP, "new_bound: out of memory");
    vb -> VarBind = v;

    v -> name = oid_copy (start);
    if ((v -> value = pe_alloc (PE_CLASS_UNIV, PE_FORM_PRIM, PE_PRIM_NULL))
	    == NULL)
	adios (NULLCP, "new_bound: out of memory");

    r -> r_nbound++;
}

/*  */

static	free_bound (b)
register struct bound *b;
{
    if (debug && b -> r_lo)  {
	fprintf (stderr, "%d get-nexts on bound: %s to",
                                   b -> b_gns, oid2ode (b -> r_lo));
        fprintf (stderr, " %s\n", oid2ode (b -> r_hi));
    }

    oid_free (b -> r_lo);
    oid_free (b -> r_arg);
    oid_free (b -> r_hi);

    if (b -> b_gns <= 1) 
        nilbounds++;

    free ((char *) b);
}

/*  */

static	push_requests (ps, community, onemore)
PS	ps;
char   *community;
int	onemore;
{
    register int    nbound,
		    nrequest,
		    tbound;
    int	    new,
    	    wen;
    register struct request *r;
    struct request *u;
    register struct bound  *bn,
			   *bz,
			  **bp;
    struct bound *b;
    register struct type_SNMP_VarBindList  *vz,
					  **vp;
    struct type_SNMP_VarBindList *v;

    bp = &b, b = NULL;
    vp = &v, v = NULL;
    nrequest = nbound = tbound = 0;
    for (r = RHead -> r_forw; r != RHead; r = r -> r_forw) {
	if (!r -> r_info) {
	    tbound += r -> r_nbound;
	    continue;
	}
	nrequest++;

	if (*bp = r -> r_bounds) {
	    for (bz = *bp; bz; bz = bz -> r_next)
		if (bz -> r_next == NULL)
		    bp = &bz -> r_next;

	    r -> r_bounds = NULL;
	    nbound += r -> r_nbound;
	}

	if (*vp = r -> r_msg -> data -> un.get__request -> variable__bindings){
	    for (vz = *vp; vz; vz = vz -> next)
		if (vz -> next == NULL)
		    vp = &vz -> next;
	    
	    r -> r_msg -> data -> un.get__request -> variable__bindings = NULL;
	}

	r -> r_nbound = 0;
    }
    if ((tbound += nbound) > maxbounds)
	maxbounds = tbound;
    if (nrequest == 0)
	return OK;

    if ((new = (nbound + (boundlimit - 1)) / boundlimit - nrequest) <= 0
	    && onemore)
	new = 1;
    for (; new-- > 0; nrequest++)
	(void) new_request (community);
    if ((new = nrequest * boundlimit) > (wen = nbound << 1))
	new = wen;

    for (new -= nbound, bn = b; (new > 0) && bn; new--, bn = bn -> r_next) {
	OID	mid;

	if ((mid = oid_median (bn -> r_arg, bn -> r_hi)) == NULLOID)
	    continue;

	if ((bz = (struct bound *) calloc (1, sizeof *bz)) == NULL)
	    adios (NULLCP, "push_requests: out of memory");
	*bp = bz, bp = &bz -> r_next;

	bz -> r_lo = oid_copy (mid);
	bz -> r_arg = oid_copy (mid);
	bz -> r_hi = bn -> r_hi;
	tbound++, totbounds++;

	bn -> r_hi = mid;

	if ((vz = (struct type_SNMP_VarBindList *) calloc (1, sizeof *vz))
	        == NULL)
	    adios (NULLCP, "push_requests: out of memory");
	*vp = vz, vp = &vz -> next;

	if ((vz -> VarBind = (struct type_SNMP_VarBind *)
						calloc (1, sizeof *v)) == NULL)
	    adios (NULLCP, "push_requests: out of memory");

	vz -> VarBind -> name = oid_copy (mid);
	if ((vz -> VarBind -> value = pe_alloc (PE_CLASS_UNIV, PE_FORM_PRIM,
						PE_PRIM_NULL)) == NULL)
	    adios (NULLCP, "push_requests: out of memory");
    }
    if (tbound > maxbounds)
	maxbounds = tbound;

    if (b == NULL)
	goto send_them;
    for (r = RHead -> r_forw; r != RHead; r = r -> r_forw) {
	register int	i;
	register struct bound **inb;
	register struct type_SNMP_VarBindList **inv;

	if (!r -> r_info)
	    continue;

	inb = &r -> r_bounds;
	inv = &r -> r_msg -> data -> un.get__request -> variable__bindings;
	for (i = boundlimit; i > 0; i--) {
	    bz = b -> r_next, vz = v -> next;

	    *inb = b, inb = &b -> r_next, b -> r_next = NULL;
	    *inv = v, inv = &v -> next, v -> next = NULL;

            b -> b_gns++;
	    r -> r_nbound++;

	    if ((b = bz) == NULL)
		goto send_them;
	    v = vz;
	}
    }
    /* should never get here, since we should run out bounds before
       running out of requests; if we do ... ?? */

    if (debug)
	fprintf (stderr, "push_requests: possibly loss of bounds\n");

send_them: ;
    for (r = RHead -> r_forw; r != RHead; r = u) {
	u = r -> r_forw;
	if (!r -> r_info)
	    continue;

	if (r -> r_nbound <= 0) {
	    free_request (r);
	    continue;
	}

	if (++r -> r_rid >= r -> r_mid)
	    r -> r_rid = r -> r_mid - MAXSPACE;

	r -> r_info = 0;
	r -> r_msg -> data -> offset = type_SNMP_PDUs_get__next__request;
	r -> r_msg -> data -> un.get__response -> request__id = r -> r_rid;

	if (r -> r_pe)
	    pe_free (r -> r_pe), r -> r_pe = NULL;

	if (encode_SNMP_Message (&r -> r_pe, 1, 0, NULLCP, r -> r_msg)
	        == NOTOK) {
	    advise (NULLCP, "encode_SNMP_Message: %s", PY_pepy);
	    return NOTOK;
	}

	if (pe2ps (ps, r -> r_pe) == NOTOK) {
	    advise (NULLCP, "pe2ps: %s", ps_error (ps -> ps_errno));
	    return NOTOK;
	}
	if (watch) {
	    fprintf (stdout, "write PDU\n");
	    (void) print_SNMP_Message (r -> r_pe, 1, NULLIP, NULLVP, NULLCP);
	    (void) fflush (stdout);
	}
	totreqs++;

	r -> r_lastime = timenow;
	r -> r_curinvokes = currequests ? currequests : 1;
    }

    return OK;
}

/*    OIDS */

static OID  oid_median (a, b)
OID	a,
	b;
{
    register int    i;
    register unsigned int *ap,
			  *bp;
    register OID    c = NULL;

    if (oid_cmp (a, b) >= 0) {
	char    buffer[BUFSIZ];

	(void) strcpy (buffer, sprintoid (a));
	adios (NULLCP, "oid_median(%s <= %s)", buffer, sprintoid (b));
    }

    for (i = 1, ap = a -> oid_elements, bp = b -> oid_elements;
	    i <= b -> oid_nelem;
	    i++, ap++, bp++) {
	if (i > a -> oid_nelem) {
	    for (; *bp == NULL; bp++)
		if (++i > b -> oid_nelem)
		    goto losing;
	    c = oid_copy (b);
	    c -> oid_elements[(c -> oid_nelem = i) - 1] = *bp >> 1;
	    break;
	}
	
	if (*ap == *bp)
	    continue;

	c = oid_copy (a);
	if ((c -> oid_elements[(c -> oid_nelem = i) - 1] =
						    *ap + ((*bp - *ap) >> 1))
	        == *ap) {
	    bp = &c -> oid_elements[c -> oid_nelem++];

	    if (a -> oid_nelem <= i)
		*bp = 127;
	    else {
                while (*++ap == 255) {
                    bp++, c -> oid_nelem++;
 		    if (++i >= a -> oid_nelem) {
                        *bp = 127;
                        goto testc;
	            }
	        }

		*bp = *ap >= 16383 ? *ap + 16383
		    : *ap >= 4095  ? *ap + 4095
		    : *ap >= 1023  ? *ap + 1023
		    : *ap >   255  ? *ap + 255
		    :               (*ap >> 1) + 128;
	    }
	}

testc: ;
	if (c -> oid_nelem < 2)
	    c -> oid_elements[c -> oid_nelem++] = 0;
	break;
    }

    if (c == NULL) {
	char    buffer[BUFSIZ];

losing: ;
	if (debug) {
	    (void) strcpy (buffer, sprintoid (a));
	    fprintf (stderr, "oid_median(%s, %s) fails",
		     buffer, sprintoid (b));
	}
	return NULL;
    }
    if (oid_cmp (a, c) >= 0) {
	char    buf1[BUFSIZ],
		buf2[BUFSIZ];

	(void) strcpy (buf1, sprintoid (a));
	(void) strcpy (buf2, sprintoid (b));
	adios (NULLCP, "oid_median(%s, %s) -> %s loses(1)",
	       buf1, buf2, sprintoid (c));
    }
    if (oid_cmp (c, b) >= 0) {
	char    buf1[BUFSIZ],
		buf2[BUFSIZ];

	(void) strcpy (buf1, sprintoid (a));
	(void) strcpy (buf2, sprintoid (b));
	adios (NULLCP, "oid_median(%s, %s) -> %s loses(2)",
	       buf1, buf2, sprintoid (c));
    }

    return c;
}

/*  */

static OID  oid_copy (a)
OID	a;
{
    OID	    b;

    if ((b = oid_cpy (a)) == NULL)
	adios (NULLCP, "oid_copy: out of memory");

    return b;
}

#else

/*    DUMMY */

bulk_dummy () {}

#endif