
/*
 *           PVM 3.2:  Parallel Virtual Machine System 3.2
 *               University of Tennessee, Knoxville TN.
 *           Oak Ridge National Laboratory, Oak Ridge TN.
 *                   Emory University, Atlanta GA.
 *      Authors:  A. L. Beguelin, J. J. Dongarra, G. A. Geist,
 *    W. C. Jiang, R. J. Manchek, B. K. Moore, and V. S. Sunderam
 *                   (C) 1992 All Rights Reserved
 *
 *                              NOTICE
 *
 * Permission to use, copy, modify, and distribute this software and
 * its documentation for any purpose and without fee is hereby granted
 * provided that the above copyright notice appear in all copies and
 * that both the copyright notice and this permission notice appear in
 * supporting documentation.
 *
 * Neither the Institutions (Emory University, Oak Ridge National
 * Laboratory, and University of Tennessee) nor the Authors make any
 * representations about the suitability of this software for any
 * purpose.  This software is provided ``as is'' without express or
 * implied warranty.
 *
 * PVM 3.2 was funded in part by the U.S. Department of Energy, the
 * National Science Foundation and the State of Tennessee.
 */

/*
 *	startup.c
 *
 *	Exec more pvmds.  It's good for you.
 *
$Log: startup.c,v $
 * Revision 1.2  1993/10/04  20:30:30  manchek
 * mksocks() now uses pvmdsockfile() instead of TDSOCKNAME
 *
 * Revision 1.1  1993/08/30  23:26:51  manchek
 * Initial revision
 *
 */

#ifdef IMA_TITN
#include <bsd/sys/types.h>
#else
#include <sys/types.h>
#endif
#include <sys/time.h>
#ifdef IMA_RS6K
#include <sys/select.h>
#endif
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <ctype.h>
#ifdef	SYSVSTR
#include <string.h>
#define	CINDEX(s,c)	strchr(s,c)
#else
#include <strings.h>
#define	CINDEX(s,c)	index(s,c)
#endif
#include <netdb.h>
#include <signal.h>

#include "global.h"
#include "fromlib.h"
#include "ddpro.h"
#include "protoglarp.h"
#include "pvmalloc.h"
#include "mesg.h"
#include "host.h"
#include "listmac.h"
#include "tvdefs.h"
#include "bfunc.h"

#ifndef	RSHCOMMAND
#define	RSHCOMMAND	"/usr/ucb/rsh"
#endif

#ifndef	RSHTIMEOUT
#define	RSHTIMEOUT	60
#endif

/* if > 1, uses parallel startup strategy */

#ifndef	RSHNPLL
#define	RSHNPLL	5
#endif

#ifndef	max
#define	max(a,b)	((a)>(b)?(a):(b))
#endif

#ifndef	min
#define	min(a,b)	((a)<(b)?(a):(b))
#endif

#ifndef	SOMAXCONN
#define	SOMAXCONN	5
#endif


/***************
 **  Globals  **
 **           **
 ***************/

extern int errno;

extern char *inadport_hex();
extern void pvmbailout();
extern char *pvmgetrpvmd();
char *pvmdsockfile();

extern int debugmask;				/* from pvmd.c */
extern char *debugger;				/* from pvmd.c */
extern char **epaths;				/* from pvmd.c */
extern struct htab *filehosts;		/* from pvmd.c */
extern struct htab *hosts;			/* from pvmd.c */
extern char *loclsnam;				/* from pvmd.c */
extern int loclsock;				/* from pvmd.c */
extern int log_fd;					/* from logging.c */
extern int log_how;					/* from logging.c */
extern char *myarchname;			/* from pvmd.c */
extern int mytid;					/* from pvmd.c */
extern int myunixpid;				/* from pvmd.c */
extern int netsock;					/* from pvmd.c */
extern struct htab *oldhosts;		/* from pvmd.c */
extern int ourudpmtu;				/* from pvmd.c */
extern int ppnetsock;				/* from pvmd.c */
extern int pprwid;					/* from pvmd.c */
extern int runstate;				/* from pvmd.c */
extern int tidhmask;				/* from pvmd.c */
extern char *username;				/* from pvmd.c */


/***************
 **  Private  **
 **           **
 ***************/

static char rcsid[] = "$Id: startup.c,v 1.2 1993/10/04 20:30:30 manchek Exp $";
static char pvmtxt[1024];		/* scratch for error log */


/*	mksocs()
*
*	Make UDP sockets netsock and ppnetsock.  Make TCP master socket
*	loclsock.
*
*	Returns 0 if ok else 1.
*/

int
mksocs()
{
	struct hostd *hp = hosts->ht_hosts[hosts->ht_local];
	struct hostd *hp0 = hosts->ht_hosts[0];
	struct sockaddr_in sin;
	char buf[128];
	char *sfn;
	int d;
#ifndef NOSOCKOPT
	int bsz;
#endif
	char *p;
	int cc;

	/*
	* make pvmd-pvmd socket
	*/

	if ((netsock = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
		pvmlogperror("mksocs() socket netsock");
		return 1;
	}

	hp->hd_sad.sin_port = 0;
	if (bind(netsock, (struct sockaddr*)&hp->hd_sad, sizeof(hp->hd_sad)) == -1)
	{
	  pvmlogperror("mksocs() bind netsock");
	  return 1;
	}
	cc = sizeof(hp->hd_sad);
	if (getsockname(netsock, (struct sockaddr*)&hp->hd_sad, &cc) == -1) {
		pvmlogperror("mksocs() getsockname netsock");
		return 1;
	}

	/*
	* make pvmd-pvmd' socket
	*/

	if ((ppnetsock = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
		pvmlogperror("mksocs() socket ppnetsock");
		return 1;
	}

	hp0->hd_sad.sin_port = 0;
	if (bind(ppnetsock, (struct sockaddr*)&hp0->hd_sad, sizeof(hp0->hd_sad))
	== -1) {
		pvmlogperror("mksocs() bind ppnetsock");
		return 1;
	}
	cc = sizeof(hp0->hd_sad);
	if (getsockname(ppnetsock, (struct sockaddr*)&hp0->hd_sad, &cc) == -1) {
		pvmlogperror("mksocs() getsockname ppnetsock");
		return 1;
	}

	/*
	* make pvmd-local task socket
	*/

	if ((loclsock = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
		pvmlogperror("mksocs() socket loclsock");
		return 1;
	}

	/*
	* first try localhost address (loopback) then regular address
	* XXX 127.0.0.1 is a hack, we should really gethostbyaddr()
	*/

	BZERO((char*)&sin, sizeof(sin));
	sin.sin_family = AF_INET;
	sin.sin_addr.s_addr = htonl(0x7f000001);
	sin.sin_port = 0;

	if (bind(loclsock, (struct sockaddr*)&sin, sizeof(sin)) == -1) {
		sin = hp->hd_sad;
		if (bind(loclsock, (struct sockaddr*)&sin, sizeof(sin)) == -1) {
			pvmlogperror("mksocs() bind loclsock");
			return 1;
		}
	}
	cc = sizeof(sin);
	if (getsockname(loclsock, (struct sockaddr*)&sin, &cc) == -1) {
		pvmlogperror("mksocs() getsockname loclsock");
		return 1;
	}

	if (listen(loclsock, SOMAXCONN) == -1) {
		pvmlogperror("mksocs() listen loclsock");
		return 1;
	}

	/*
	* make pvmd-local task socket address file
	*/

	p = inadport_hex(&sin);

	if (!(sfn = pvmdsockfile())) {
		pvmlogerror("mksocs() pvmdsockfile() failed\n");
		pvmbailout(0);
	}

	if ((d = open(sfn, O_CREAT|O_EXCL|O_WRONLY|O_TRUNC, 0600)) == -1) {
		if (errno == EEXIST) {
#ifndef	OVERLOADHOST
			(void)sprintf(pvmtxt,
					"mksocs() %s exists.  pvmd already running?\n", sfn);
			pvmlogerror(pvmtxt);
			return 1;
#endif

		} else {
			pvmlogperror(sfn);
			pvmlogerror("mksocs() can't write address file\n");
			return 1;
		}

	} else {
		cc = write(d, p, strlen(p));
		if (cc != strlen(p)) {
			if (cc == -1) {
				pvmlogperror(sfn);
				pvmlogerror("mksocs() can't write address file\n");

			} else {
				(void)sprintf(pvmtxt,
						"mksocs() aargh, short write on %s: %d\n", sfn, cc);
				pvmlogerror(pvmtxt);
				pvmlogerror("mksocs() is the partition full?\n");
			}
			(void)close(d);
			(void)unlink(sfn);
			return 1;
		}
		loclsnam = STRALLOC(sfn);
		(void)close(d);
	}

	/* set PVMSOCK envar */

	sprintf(buf, "PVMSOCK=%s", p);
	p = STRALLOC(buf);
	pvmputenv(p);

#ifndef NOSOCKOPT
	bsz = ourudpmtu * 2;
	if (setsockopt(netsock, SOL_SOCKET, SO_SNDBUF,
			(char*)&bsz, sizeof(bsz)) == -1
	|| setsockopt(netsock, SOL_SOCKET, SO_RCVBUF,
			(char*)&bsz, sizeof(bsz)) == -1
	|| setsockopt(ppnetsock, SOL_SOCKET, SO_SNDBUF,
			(char*)&bsz, sizeof(bsz)) == -1
	|| setsockopt(ppnetsock, SOL_SOCKET, SO_RCVBUF,
			(char*)&bsz, sizeof(bsz)) == -1) {
		pvmlogperror("mksocs() setsockopt");
		return 1;
	}
#endif /*NOSOCKOPT*/
	return 0;
}


/*	pathsep()
*
*	Break path string into one or more strings on a delimiter character.
*	Also do macro substitution on another character.
*	Return null-terminated array of strings, in new malloc'd space.
*/

char **
pathsep(s, bc, ms, mc)
	char *s;	/* the string to break up */
	char bc;	/* the char to break on */
	char *ms;	/* string to replace macro char */
	char mc;	/* macro char */
{
	char **els;
	int nel = 2;			/* length of els */
	char *p, *q, *r;
	char buf[1024];			/* XXX danger, static limit */

	for (p = s; p = CINDEX(p, bc); p++)
		nel++;
	els = TALLOC(nel, char*, "psep");

	nel = 0;
	for (p = s; p; p = q) {
		if (q = CINDEX(p, bc))
			*q++ = 0;
		for (r = buf; *p; p++) {
			if (*p == mc) {
				strcpy(r, ms);
				r += strlen(r);
			} else
				*r++ = *p;
		}
		*r = 0;
		els[nel++] = STRALLOC(buf);
	}
	els[nel] = 0;
	return els;
}


/*	crunchzap()
*
*	Parse a string into words delimited by <> pairs.
*	Max number of words is original value of *acp.
*
*	Trashes out the original string.
*	Returns 0 with av[0]..av[*acp - 1] pointing to the words.
*	Returns 1 if too many words.
*/

crunchzap(s, acp, av)
	char *s;		/* the string to parse */
	int *acp;		/* max words in, ac out */
	char **av;		/* pointers to args */
{
	register int ac;
	register char *p = s;
	register n = *acp;

	/* separate out words of command */

	ac = 0;
	while (*p) {
		while (*p && *p++ != '<');
		if (*p) {
			if (ac >= n) {
	/* command too long */
				*acp = ac;
				return 1;
			}
			av[ac++] = p;
			while (*p && *p != '>') p++;
			if (*p) *p++ = 0;
		}
	}
	*acp = ac;
	return 0;
}


/*	master_config()
*
*	Master pvmd.  Config a host table with length 1.
*/

master_config(hn, argc, argv)
	char *hn;			/* hostname or null */
	int argc;
	char **argv;
{
	struct hostent *he;
	struct hostd *hp;
	struct hostd *hp2;

	if (argc > 2) {
		pvmlogerror("usage: pvmd3 [-ddebugmask] [-nhostname] [hostfile]\n");
		pvmbailout(0);
	}
	if (argc == 2)
		filehosts = readhostfile(argv[1]);

	hosts = ht_new(1);
	hosts->ht_serial = 1;
	hosts->ht_master = 1;
	hosts->ht_cons = 1;
	hosts->ht_local = 1;

	if (!(he = gethostbyname(hn))) {
		sprintf(pvmtxt, "main() %s: can't gethostbyname\n", hn);
		pvmlogerror(pvmtxt);
		pvmbailout(0);
	}

	hp = hd_new(1);
	hp->hd_name = STRALLOC(hn);
	hp->hd_arch = STRALLOC(myarchname);
	hp->hd_mtu = UDPMAXLEN;
	BCOPY(he->h_addr_list[0], (char*)&hp->hd_sad.sin_addr,
		sizeof(struct in_addr));
	ht_insert(hosts, hp);
	hd_unref(hp);

	hp = hd_new(0);
	hp->hd_name = STRALLOC("pvmd'");
	hp->hd_arch = STRALLOC(myarchname);
	hp->hd_mtu = UDPMAXLEN;
	BCOPY(he->h_addr_list[0], (char*)&hp->hd_sad.sin_addr,
		sizeof(struct in_addr));
	ht_insert(hosts, hp);
	hd_unref(hp);

	if (mksocs())
		pvmbailout(0);

	/*
	* get attributes from host file if available
	*/

	hp = hosts->ht_hosts[1];
	if (filehosts && (hp2 = nametohost(filehosts, hp->hd_name))) {
		if (hp2->hd_epath)
			hp->hd_epath = STRALLOC(hp2->hd_epath);
		if (hp2->hd_bpath)
			hp->hd_bpath = STRALLOC(hp2->hd_bpath);
		hp->hd_speed = hp2->hd_speed;
	}

	if (!hp->hd_epath)
		hp->hd_epath = STRALLOC(DEFBINDIR);
	epaths = pathsep(hp->hd_epath, ':', myarchname, '%');
	debugger = hp->hd_bpath ? hp->hd_bpath : STRALLOC(DEFDEBUGGER);

	runstate = PVMDNORMAL;
	return 0;
}


/*	slave_config()
*
*	Slave pvmd being started by master.  Trade minimal config info
*	so we can send packets back and forth.
*/

slave_config(hn, argc, argv)
	char *hn;
	int argc;
	char **argv;
{
	int lh;			/* local host index */
	int mh;			/* master host index */
	struct hostd *hp;
	int i;
	int ms = 0;		/* manual (humanoid) startup */

	if (argc == 7 && !strcmp(argv[1], "-S")) {
		ms = 1;
		argv++;
		argc--;
	}

	if (argc != 6) {
		pvmlogerror("slave_config: bad args\n");
		pvmbailout(0);
	}

	mh = atoi(argv[1]);
	lh = atoi(argv[4]);
	hosts = ht_new(1);
	hosts->ht_serial = 1;
	hosts->ht_master = mh;
	hosts->ht_cons = mh;
	hosts->ht_local = lh;

	hp = hd_new(mh);
	hp->hd_name = STRALLOC("?");
	hex_inadport(argv[2], &hp->hd_sad);
	hp->hd_mtu = atoi(argv[3]);
	ht_insert(hosts, hp);
	hd_unref(hp);

	hp = hd_new(0);
	hp->hd_name = STRALLOC("pvmd'");
	hp->hd_arch = STRALLOC(myarchname);
	hp->hd_mtu = UDPMAXLEN;
	hex_inadport(argv[5], &hp->hd_sad);
	ht_insert(hosts, hp);
	hd_unref(hp);

	hp = hd_new(lh);
	hp->hd_name = STRALLOC(hn);
	hp->hd_arch = STRALLOC(myarchname);
	hp->hd_mtu = UDPMAXLEN;
	hex_inadport(argv[5], &hp->hd_sad);
	ht_insert(hosts, hp);
	hd_unref(hp);
	if (mksocs())
		pvmbailout(0);

	printf("ddpro<%d> arch<%s> ip<%s> mtu<%d>\n",
		DDPROTOCOL,
		myarchname,
		inadport_hex(&hp->hd_sad),
		ourudpmtu);
	fflush(stdout);

	if (!ms)
		read(0, (char*)&i, 1);

	if (i = fork()) {
		if (i == -1)
			pvmlogperror("slave_config() fork");
		exit(0);
	}

	for (i = getdtablesize(); --i >= 0; )
/* XXX don't like this - hard to maintain */
		if (i != netsock && i != loclsock && i != log_fd)
			(void)close(i);

	log_how &= ~1;

	if ((myunixpid = getpid()) == -1) {
		pvmlogerror("main() can't getpid()\n");
		pvmbailout(0);
	}

	epaths = pathsep(STRALLOC(DEFBINDIR), ':', myarchname, '%');
	debugger = STRALLOC(DEFDEBUGGER);
	runstate = PVMDSTARTUP;
	return 0;
}


/*	start_slaves()
*
*	This is the main function for pvmd'.  It attempts to start slave
*	pvmds on a list of hosts.
*/

start_slaves(htp)
	struct htab *htp;		/* table of new slaves */
{
	struct mesg *mp;		/* to inform parent pvmd when finished */
	struct hostent *he;
	struct hostd *hp, *hp2;
	int hh, hh2;

	/*
	* for each host in table without errors, get ipaddr
	*/

	for (hh = 1; hh <= htp->ht_last; hh++) {
		hp = htp->ht_hosts[hh];
		if (!hp || hp->hd_err)
			continue;

	/*
	* first check table from host file if available
	*/

		if (filehosts && (hp2 = nametohost(filehosts, hp->hd_name))) {
			hp->hd_flag |= hp2->hd_flag;
			if (hp2->hd_login)
				hp->hd_login = STRALLOC(hp2->hd_login);
			if (hp2->hd_dpath)
				hp->hd_dpath = STRALLOC(hp2->hd_dpath);
			if (hp2->hd_epath)
				hp->hd_epath = STRALLOC(hp2->hd_epath);
			if (hp2->hd_bpath)
				hp->hd_bpath = STRALLOC(hp2->hd_bpath);
			BCOPY((char*)&hp2->hd_sad.sin_addr, (char*)&hp->hd_sad.sin_addr,
					sizeof(struct in_addr));
			hp->hd_speed = hp2->hd_speed;

		} else {
			if (he = gethostbyname(hp->hd_name)) {
				BCOPY(he->h_addr_list[0], (char*)&hp->hd_sad.sin_addr,
						sizeof(struct in_addr));

			} else {
				if (debugmask & PDMSTARTUP) {
					sprintf(pvmtxt, "start_slaves() can't gethostbyname: %s\n",
							hp->hd_name);
					pvmlogerror(pvmtxt);
				}
				hp->hd_err = PvmNoHost;
			}
		}

	/*
	* check that it's not already configured
	*/

		if (!hp->hd_err) {
			for (hh2 = oldhosts->ht_last; hh2 > 0; hh2--) {
				if ((hp2 = oldhosts->ht_hosts[hh2])
				&& (hp2->hd_sad.sin_addr.s_addr == hp->hd_sad.sin_addr.s_addr))
					hp->hd_err = PvmDupHost;
			}
		}
	}

	/*
	* check that new hosts aren't duplicated
	*/

	for (hh = htp->ht_last; hh > 0; hh--) {
		if (!(hp = htp->ht_hosts[hh]) || hp->hd_err)
			continue;
		for (hh2 = hh; --hh2 > 0; )
			if ((hp2 = htp->ht_hosts[hh2])
			&& !hp2->hd_err
			&& (hp2->hd_sad.sin_addr.s_addr == hp->hd_sad.sin_addr.s_addr))
				hp->hd_err = PvmDupHost;
	}

	/*
	* try to start each host that doesn't have an error
	*/


	if (debugmask & PDMSTARTUP) {
		pvmlogerror("start_slaves() using host table:\n");
		ht_dump(htp);
	}

#if	RSHNPLL > 1
	pl_startup(htp);

#else	/*RSHNPLL > 1*/
	for (hh = 1; hh <= htp->ht_last; hh++) {
		hp = htp->ht_hosts[hh];
		if (!hp || hp->hd_err)
			continue;
		slave_exec(hp);
	}
#endif	/*RSHNPLL > 1*/

	if (debugmask & PDMSTARTUP) {
		pvmlogerror("start_slaves() result host table:\n");
		ht_dump(htp);
	}

	/*
	* send host table with addresses and errors back to parent
	*/

	mp = mesg_new(0);
	mp->m_dst = hosts->ht_hosts[hosts->ht_master]->hd_hostpart | TIDPVMD;
	mp->m_cod = DM_STARTACK;
	mp->m_wid = pprwid;
	pkint(mp, htp->ht_cnt);
	for (hh = 1; hh <= htp->ht_last; hh++) {
		if (hp = htp->ht_hosts[hh]) {
			pkint(mp, hh);
			pkint(mp, hp->hd_err);
			if (!hp->hd_err) {
				pkstr(mp, hp->hd_arch);
				pkstr(mp, inadport_hex(&hp->hd_sad));
				pkint(mp, hp->hd_mtu);
				if (hp->hd_epath) {
					pkint(mp, DM_SLCONF_EP);
					pkstr(mp, hp->hd_epath);
				}
				if (hp->hd_bpath) {
					pkint(mp, DM_SLCONF_BP);
					pkstr(mp, hp->hd_bpath);
				}
				pkint(mp, 0);
			}
		}
	}
	if (debugmask & PDMSTARTUP)
		pvmlogerror("start_slaves() pvmd' sending back host table\n");
	sendmessage(mp);

	work();
	return 0;	/* not reached */
}


#if RSHNPLL > 1

/********************************************
*  this is the new (parallel) startup code  *
*                                           *
********************************************/

struct slot {
	struct slot *s_link, *s_rlink;		/* free/active list */
	struct hostd *s_hostd;				/* host table entry */
	struct timeval s_bail;				/* timeout time */
	int s_rfd, s_wfd, s_efd;			/* slave stdin/out/err */
	char s_buf[256];					/* config reply line */
	int s_len;							/* length of s_buf */
};


static struct slot slots[RSHNPLL+2];	/* state var/context for each slot */
static struct slot *slfree = 0;			/* free list of slots */

close_slot(sp)
	struct slot *sp;
{
	if (sp->s_wfd != -1)
		(void)close(sp->s_wfd);
	if (sp->s_rfd != -1)
		(void)close(sp->s_rfd);
	if (sp->s_efd != -1)
		(void)close(sp->s_efd);
	LISTDELETE(sp, s_link, s_rlink);
	LISTPUTBEFORE(slfree, sp, s_link, s_rlink);
	return 0;
}


pl_startup(htp)
	struct htab *htp;
{
	int hhnext = 1;						/* next host in htp to try */
	struct slot *slact = 0;				/* active list of slots */
	struct hostd *hp;
	struct slot *sp, *sp2;
	int i;
	struct timeval tnow;
	struct timeval tout;
	int n;
	struct fd_set rfds;
	int nfds;
	char ebuf[256];						/* for reading stderr */
	int ver;
	char *av[16];						/* for reply parsing */
	int ac;

	/* init slot free list */

	slfree = &slots[RSHNPLL+1];
	slfree->s_link = slfree->s_rlink = slfree;
	slact = &slots[RSHNPLL];
	slact->s_link = slact->s_rlink = slact;
	for (i = RSHNPLL; i-- > 0; ) {
		LISTPUTAFTER(slfree, &slots[i], s_link, s_rlink);
	}

	/*
	* keep at this until all hosts in table are completed
	*/

	for (; ; ) {

		/*
		* if empty slots, start on new hosts
		*/

		for (; ; ) {

			/* find a host for slot */
			hp = 0;
			if (slfree->s_link != slfree)
				while (hhnext <= htp->ht_last)
					if (hp = htp->ht_hosts[hhnext++]) {
						if (hp->hd_err)
							hp = 0;
						else
							break;
					}
			if (!hp)
				break;

			sp = slfree->s_link;
			LISTDELETE(sp, s_link, s_rlink);
			sp->s_hostd = hp;
			sp->s_len = 0;
			if (debugmask & PDMSTARTUP) {
				sprintf(pvmtxt, "pl_startup() trying %s\n", hp->hd_name);
				pvmlogerror(pvmtxt);
			}
			phase1(sp);
			if (hp->hd_err || hp->hd_arch) {
				/* error or fully started (manual startup) */

				LISTPUTBEFORE(slfree, sp, s_link, s_rlink);

			} else {
				/* partially started */

				LISTPUTBEFORE(slact, sp, s_link, s_rlink);
				gettimeofday(&sp->s_bail, (struct timezone*)0);
				tout.tv_sec = RSHTIMEOUT;
				tout.tv_usec = 0;
				TVXADDY(&sp->s_bail, &sp->s_bail, &tout);
			}
		}

		/* if no hosts in progress, we are finished */

		if (slact->s_link == slact)
			break;

		/*
		* until next timeout, get output from any slot
		*/

		FD_ZERO(&rfds);
		nfds = 0;
		TVCLEAR(&tout);
		gettimeofday(&tnow, (struct timezone*)0);
		for (sp = slact->s_link; sp != slact; sp = sp->s_link) {
			if (TVXLTY(&sp->s_bail, &tnow)) {
				sprintf(pvmtxt, "pl_startup() %s timed out after %d secs\n",
						sp->s_hostd->hd_name, RSHTIMEOUT);
				pvmlogerror(pvmtxt);
				sp->s_hostd->hd_err = PvmCantStart;
				sp2 = sp->s_rlink;
				close_slot(sp);
				sp = sp2;
				continue;
			}

			if (!TVISSET(&tout) || TVXLTY(&sp->s_bail, &tout))
				tout = sp->s_bail;
			if (sp->s_rfd >= 0)
				FD_SET(sp->s_rfd, &rfds);
			if (sp->s_rfd > nfds)
				nfds = sp->s_rfd;
			if (sp->s_efd >= 0)
				FD_SET(sp->s_efd, &rfds);
			if (sp->s_efd > nfds)
				nfds = sp->s_efd;
		}

		if (slact->s_link == slact)
			break;

		nfds++;

		if (TVXLTY(&tnow, &tout)) {
			TVXSUBY(&tout, &tout, &tnow);
		} else {
			TVCLEAR(&tout);
		}
		if (debugmask & PDMSTARTUP) {
			sprintf(pvmtxt, "pl_startup() select timeout is %d.%06d\n",
					tout.tv_sec, tout.tv_usec);
			pvmlogerror(pvmtxt);
		}
		if ((n = select(nfds, &rfds, (fd_set*)0, (fd_set*)0, &tout)) == -1) {
			if (errno != EINTR) {
				pvmlogperror("work() select");
				pvmbailout(0);
			}
		}
		if (debugmask & PDMSTARTUP) {
			(void)sprintf(pvmtxt, "pl_startup() select returns %d\n", n);
			pvmlogerror(pvmtxt);
		}
		if (n < 1) {
			if (n == -1 && errno != EINTR) {
				pvmlogperror("pl_startup() select");
				pvmbailout(0);	/* XXX this is too harsh */
			}
			continue;
		}

		/*
		* check for response on stdout or stderr of any slave.
		*/

		for (sp = slact->s_link; sp != slact; sp = sp->s_link) {

			/*
			* stdout ready.  get complete line then scan config info from it.
			*/
			if (sp->s_rfd >= 0 && FD_ISSET(sp->s_rfd, &rfds)) {
				n = read(sp->s_rfd, sp->s_buf + sp->s_len,
						sizeof(sp->s_buf) - sp->s_len);
				if (n > 0) {
					sp->s_len += n;
					if (sp->s_len >= sizeof(sp->s_buf)) {
						sprintf(pvmtxt, "pl_startup() pvmd@%s: big read\n",
								sp->s_hostd->hd_name);
						pvmlogerror(pvmtxt);
						sp->s_hostd->hd_err = PvmCantStart;
						sp2 = sp->s_rlink;
						close_slot(sp);
						sp = sp2;
						continue;
					}
					sp->s_buf[sp->s_len] = 0;
					if (CINDEX(sp->s_buf + sp->s_len - n, '\n')) {
						if (debugmask & PDMSTARTUP) {
							sprintf(pvmtxt, "pvmd@%s: %s",
									sp->s_hostd->hd_name, sp->s_buf);
							pvmlogerror(pvmtxt);
						}
						ac = sizeof(av)/sizeof(av[0]);
						if (crunchzap(sp->s_buf, &ac, av) || ac != 4) {
							pvmlogerror("pl_startup() expected version\n");
							sp->s_hostd->hd_err = PvmCantStart;

						} else {
							ver = atoi(av[0]);
							if (ver != DDPROTOCOL) {
								sprintf(pvmtxt, "pl_startup() host %s d-d protocol mismatch (%d/%d)\n",
										sp->s_hostd->hd_name, ver, DDPROTOCOL);
								pvmlogerror(pvmtxt);
								sp->s_hostd->hd_err = PvmBadVersion;
							} else {

								sp->s_hostd->hd_arch = STRALLOC(av[1]);
								hex_inadport(av[2], &sp->s_hostd->hd_sad);
								sp->s_hostd->hd_mtu = atoi(av[3]);
							}
						}
						sp2 = sp->s_rlink;
						close_slot(sp);
						sp = sp2;
						continue;
					}

				} else {
					if (n) {
						sprintf(pvmtxt, "pl_startup() pvmd@%s",
								sp->s_hostd->hd_name);
						pvmlogperror(pvmtxt);
					} else {
						sprintf(pvmtxt, "pl_startup() pvmd@%s: EOF\n",
								sp->s_hostd->hd_name);
						pvmlogerror(pvmtxt);
					}
					sp->s_hostd->hd_err = PvmCantStart;
					sp2 = sp->s_rlink;
					close_slot(sp);
					sp = sp2;
					continue;
				}
			}

			/*
			* response on stderr.  log prefixed by remote's host name.
			*/
			if (sp->s_efd >= 0 && FD_ISSET(sp->s_efd, &rfds)) {
				if ((n = read(sp->s_efd, ebuf, sizeof(ebuf)-1)) > 0) {
					char *p = ebuf, *q, c;

					ebuf[n] = 0;
					sprintf(pvmtxt, "pvmd@%s: ", sp->s_hostd->hd_name);
					q = pvmtxt + strlen(pvmtxt);
					while (c = *p++ & 0x7f) {
						if (isprint(c))
							*q++ = c;

						else {
							*q++ = '^';
							*q++ = (c + '@') & 0x7f;
						}
					}
					*q++ = '\n';
					*q = 0;
					pvmlogerror(pvmtxt);

				} else {
					(void)close(sp->s_efd);
					sp->s_efd = -1;
				}
			}
		}
	}
	return 0;
}


phase1(sp)
	struct slot *sp;
{
	struct hostd *hp;
	int byrsh;
	char *hn;
	char *av[16];			/* for rsh args and reply parsing */
	int ac;
	char buf[512];
	int pid = -1;			/* pid of rsh */
	char *p;
	int ver;

#ifndef NOREXEC
	struct servent *se;
	static u_short execport = 0;

	if (!execport) {
		if (!(se = getservbyname("exec", "tcp"))) {
			sprintf(pvmtxt, "phase1() can't getservbyname(): %s\n", "exec");
			pvmbailout(0);
		}
		execport = se->s_port;
		endservent();
	}
#endif

	hp = sp->s_hostd;
	hn = hp->hd_name;
	sp->s_rfd = sp->s_wfd = sp->s_efd = -1;

	/*
	* XXX manual startup hack... this is if we can't use rexec or rsh
	*/

	if (hp->hd_flag & HF_MANUAL) {
		fprintf(stderr, "*** Manual startup ***\n");
		fprintf(stderr, "Login to \"%s\" and type:\n", hn);
		fprintf(stderr, "%s -S -d%x -n%s %d %s %d",
				hp->hd_dpath ? hp->hd_dpath : pvmgetrpvmd(),
				debugmask,
				hn,
				hosts->ht_master,
				inadport_hex(&hosts->ht_hosts[hosts->ht_master]->hd_sad),
				hosts->ht_hosts[hosts->ht_master]->hd_mtu);
		fprintf(stderr, " %d %s\n",
				((hp->hd_hostpart & tidhmask) >> (ffs(tidhmask) - 1)),
				inadport_hex(&hp->hd_sad));

	/* get version */

		fprintf(stderr, "Type response: ");
		fflush(stderr);
		if (!(fgets(buf, sizeof(buf), stdin))) {
			sprintf(pvmtxt, "host %s read error\n", hn);
			pvmlogerror(pvmtxt);
			goto oops;
		}
		ac = sizeof(av)/sizeof(av[0]);
		if (crunchzap(buf, &ac, av) || ac != 4) {
			pvmlogerror("slave_exec() expected version\n");
			goto oops;
		}
		ver = atoi(av[0]);
		if (ver != DDPROTOCOL) {
			sprintf(pvmtxt, "slave_exec() host %s d-d protocol mismatch (%d/%d)\n",
					hn, ver, DDPROTOCOL);
			pvmlogerror(pvmtxt);
			hp->hd_err = PvmBadVersion;
			goto oops2;
		}

		hp->hd_arch = STRALLOC(av[1]);
		hex_inadport(av[2], &hp->hd_sad);
		hp->hd_mtu = atoi(av[3]);

		fprintf(stderr, "Thanks\n");
		fflush(stderr);
		return 0;
	}

	/*
	* XXX end manual startup hack
	*/

	byrsh = !(hp->hd_flag & HF_ASKPW);

	if (byrsh) {			/* use rsh to start */
		int wpfd[2], rpfd[2], epfd[2];
		int i;

		if (debugmask & PDMSTARTUP) {
			sprintf(pvmtxt, "phase1() trying rsh to %s\n", hn);
			pvmlogerror(pvmtxt);
		}

	/* fork an rsh to startup the slave pvmd */

#ifdef	IMA_TITN
		if (socketpair(AF_UNIX, SOCK_STREAM, 0, wpfd) == -1
		|| socketpair(AF_UNIX, SOCK_STREAM, 0, rpfd) == -1
		|| socketpair(AF_UNIX, SOCK_STREAM, 0, epfd) == -1) {
			pvmlogperror("phase1() socketpair");
			goto oops;
		}
#else
		if (pipe(wpfd) == -1 || pipe(rpfd) == -1 || pipe(epfd) == -1) {
			pvmlogperror("phase1() pipe");
			goto oops;
		}
#endif

		if (debugmask & PDMSTARTUP) {
			sprintf(pvmtxt, "phase1() pipes: %d %d %d %d %d %d\n",
					wpfd[0], wpfd[1], rpfd[0], rpfd[1], epfd[0], epfd[1]);
			pvmlogerror(pvmtxt);
		}

		if ((pid = fork()) == -1) {
			pvmlogperror("phase1() fork");
			pvmbailout(0);
		}
		if (!pid) {
			(void)dup2(wpfd[0], 0);
			(void)dup2(rpfd[1], 1);
			(void)dup2(epfd[1], 2);
			for (i = getdtablesize(); --i > 2; )
				(void)close(i);
			ac = 0;
			av[ac++] = RSHCOMMAND;
			av[ac++] = hn;
			if (hp->hd_login) {
				av[ac++] = "-l";
				av[ac++] = hp->hd_login;
			}
			av[ac++] = hp->hd_dpath ? hp->hd_dpath : pvmgetrpvmd();
			av[ac++] = "-s";
			(void)sprintf(buf, "-d%x", debugmask);
			av[ac++] = STRALLOC(buf);

			(void)sprintf(buf, "-n%s", hn);
			av[ac++] = STRALLOC(buf);

			/* master host index */
			sprintf(buf, "%d", hosts->ht_master);
			av[ac++] = STRALLOC(buf);

			/* master host sockaddr */
			p = inadport_hex(&hosts->ht_hosts[hosts->ht_master]->hd_sad);
			av[ac++] = STRALLOC(p);

			/* master host mtu */
			sprintf(buf, "%d", hosts->ht_hosts[hosts->ht_master]->hd_mtu);
			av[ac++] = STRALLOC(buf);

			/* this slave index */
			sprintf(buf, "%d",
					((hp->hd_hostpart & tidhmask) >> (ffs(tidhmask) - 1)));
			av[ac++] = STRALLOC(buf);

			/* this slave sockaddr */
			p = inadport_hex(&hp->hd_sad);
			av[ac++] = STRALLOC(p);

			av[ac++] = 0;
			execvp(av[0], av);
			fputs("phase1() execvp failed\n", stderr);
			fflush(stderr);
			_exit(1);
		}
		(void)close(wpfd[0]);
		(void)close(rpfd[1]);
		(void)close(epfd[1]);
		sp->s_wfd = wpfd[1];
		sp->s_rfd = rpfd[0];
		sp->s_efd = epfd[0];

	} else {		/* use rexec to start */

#ifdef NOREXEC
		sprintf(pvmtxt, "slconfg() sorry, no rexec()\n");
		pvmlogerror(pvmtxt);
		goto oops;
#else
		(void)sprintf(buf, "%s -s -d%x -n%s %d %s %d",
				hp->hd_dpath ? hp->hd_dpath : pvmgetrpvmd(),
				debugmask,
				hn,
				hosts->ht_master,
				inadport_hex(&hosts->ht_hosts[hosts->ht_master]->hd_sad),
				hosts->ht_hosts[hosts->ht_master]->hd_mtu);
		(void)sprintf(buf + strlen(buf), " %d %s\n",
				((hp->hd_hostpart & tidhmask) >> (ffs(tidhmask) - 1)),
				inadport_hex(&hp->hd_sad));
		if (debugmask & PDMSTARTUP) {
			sprintf(pvmtxt, "phase1() rexec \"%s\"\n", buf);
			pvmlogerror(pvmtxt);
		}
		if ((sp->s_wfd = sp->s_rfd = rexec(&hn, execport,
				(hp->hd_login ? hp->hd_login : username),
				(char*)0, buf, &sp->s_efd))
		== -1) {
			sprintf(pvmtxt, "phase1() rexec failed for host %s\n", hn);
			pvmlogerror(pvmtxt);
			goto oops;
		}
#endif
	}
	return 0;

oops:
	hp->hd_err = PvmCantStart;
oops2:
	if (sp->s_wfd != -1)
		close(sp->s_wfd);
	if (sp->s_rfd != -1)
		close(sp->s_rfd);
	if (sp->s_efd != -1)
		close(sp->s_efd);
	sp->s_wfd = sp->s_rfd = sp->s_efd = -1;
	return 1;
}


#else	/*RSHNPLL > 1*/

/******************************************
*  this is the old (serial) startup code  *
*                                         *
******************************************/

/*	eread()
*
*	Read an fd until \n is reached.  Also pass input from a second fd
*	to logfile.
*/

int
eread(rfd, buf, len, efd, hname)
	int rfd;			/* fd to read from */
	char *buf;			/* buffer for data */
	int len;			/* buffer length */
	int efd;			/* alt fd to pass to stderr or -1 if none */
	char *hname;		/* hostname we're talking to */
{
	int got = 0;			/* length written into buf */
	fd_set rfds, the_fds;
	struct timeval tout;
	char lbuf[256];			/* for std err */
	int n;
	int i;
	int mfd = max(rfd, efd) + 1;

	FD_ZERO(&the_fds);
	FD_SET(rfd, &the_fds);
	if (efd >= 0)
		FD_SET(efd, &the_fds);
	tout.tv_sec = RSHTIMEOUT;	/* max to get response */
	tout.tv_usec = 0;

	while (1) {
		do {
			rfds = the_fds;
			if ((n = select(mfd, &rfds, (fd_set*)0, (fd_set*)0, &tout)) == -1) {
				pvmlogperror("eread() select");
				if (errno != EINTR)
					return -1;
			}
		} while (n == -1);
		if (!n) {
	/*
	* select timeout - no response from slave pvmd
	*/
			sprintf(pvmtxt, "eread() no response from pvmd@%s in %d seconds\n",
					hname, RSHTIMEOUT);
			pvmlogerror(pvmtxt);
			return 0;
		}

		if (FD_ISSET(rfd, &rfds)) {
			if ((n = read(rfd, buf, len)) == -1) {
				pvmlogperror("eread() read");
				return 0;
			}
			if (!n)
				return got;
			got += n;
			for (i = 0; i < n; i++)
				if (buf[i] == '\n')
					return got;
			buf += n;
			len -= n;
		}

		if (efd >= 0 && FD_ISSET(efd, &rfds)) {
	/*
	* response on standard error.  log prefixed by remote's host name.
	*/
			if ((n = read(efd, lbuf, sizeof(lbuf)-1)) > 0) {
				char *p = lbuf, *q, c;

				lbuf[n] = 0;
				sprintf(pvmtxt, "eread() pvmd@%s: ", hname);
				q = pvmtxt + strlen(pvmtxt);
				while (c = *p++ & 0x7f) {
					if (isprint(c))
						*q++ = c;

					else {
						*q++ = '^';
						*q++ = (c + '@') & 0x7f;
					}
				}
				*q++ = '\n';
				*q = 0;
				pvmlogerror(pvmtxt);
			}
		}
	}
}


/*	slave_exec()
*
*	Exec a new slave pvmd and do first-stage configuration.
*	Returns with hp->hd_err set if error occurred, else
*	other fields of hp filled in with config info.
*/

slave_exec(hp)
	struct hostd *hp;
{
	int wfd = -1;			/* slave fd[0] */
	int rfd = -1;			/* slave fd[1] */
	int efd = -1;			/* slave fd[2] */
	char *hn = hp->hd_name;
	char *av[16];			/* for rsh args and reply parsing */
	int ac;
	char buf[512];
	int n;
	int ver;
	int byrsh = !(hp->hd_flag & HF_ASKPW);
	int pid = -1;			/* pid of rsh */
	char *p;

#ifndef NOREXEC
	struct servent *se;
	static u_short execport = 0;

	if (!execport) {
		if (!(se = getservbyname("exec", "tcp"))) {
			sprintf(pvmtxt, "slave_exec() can't getservbyname(): %s\n", "exec");
			pvmbailout(0);
		}
		execport = se->s_port;
		endservent();
	}
#endif

	/*
	* XXX gratuitous hack... this is if we can't use rexec or rsh
	* XXX some day we'll have real network auth functions
	*/

	if (hp->hd_flag & HF_MANUAL) {
		fprintf(stderr, "*** Manual startup ***\n");
		fprintf(stderr, "Login to \"%s\" and type:\n", hp->hd_name);
		fprintf(stderr, "%s -S -d%x -n%s %d %s %d",
				hp->hd_dpath ? hp->hd_dpath : pvmgetrpvmd(),
				debugmask,
				hn,
				hosts->ht_master,
				inadport_hex(&hosts->ht_hosts[hosts->ht_master]->hd_sad),
				hosts->ht_hosts[hosts->ht_master]->hd_mtu);
		fprintf(stderr, " %d %s\n",
				((hp->hd_hostpart & tidhmask) >> (ffs(tidhmask) - 1)),
				inadport_hex(&hp->hd_sad));

	/* get version */

		fprintf(stderr, "Type response: ");
		fflush(stderr);
		if (!(fgets(buf, sizeof(buf), stdin))) {
			sprintf(pvmtxt, "host %s read error\n", hn);
			pvmlogerror(pvmtxt);
			goto oops;
		}
		ac = sizeof(av)/sizeof(av[0]);
		if (crunchzap(buf, &ac, av) || ac != 4) {
			pvmlogerror("slave_exec() expected version\n");
			goto oops;
		}
		ver = atoi(av[0]);
		if (ver != DDPROTOCOL) {
			sprintf(pvmtxt, "slave_exec() host %s d-d protocol mismatch (%d/%d)\n",
					hn, ver, DDPROTOCOL);
			pvmlogerror(pvmtxt);
			hp->hd_err = PvmBadVersion;
			goto done;
		}

		hp->hd_arch = STRALLOC(av[1]);
		hex_inadport(av[2], &hp->hd_sad);
		hp->hd_mtu = atoi(av[3]);

		fprintf(stderr, "Thanks\n");
		fflush(stderr);
		goto done;
	}

	/*
	* XXX end manual startup hack
	*/

	if (byrsh) {			/* use rsh to start */
		int wpfd[2], rpfd[2], epfd[2];
		int i;

		if (debugmask & PDMSTARTUP) {
			sprintf(pvmtxt, "slave_exec() trying rsh to %s\n", hn);
			pvmlogerror(pvmtxt);
		}

	/* fork an rsh to startup the slave pvmd */

#ifdef	IMA_TITN
		if (socketpair(AF_UNIX, SOCK_STREAM, 0, wpfd) == -1
		|| socketpair(AF_UNIX, SOCK_STREAM, 0, rpfd) == -1
		|| socketpair(AF_UNIX, SOCK_STREAM, 0, epfd) == -1) {
			pvmlogperror("slave_exec() socketpair");
			goto oops;
		}
#else
		if (pipe(wpfd) == -1 || pipe(rpfd) == -1 || pipe(epfd) == -1) {
			pvmlogperror("slave_exec() pipe");
			goto oops;
		}
#endif

		if (debugmask & PDMSTARTUP) {
			sprintf(pvmtxt, "slave_exec() pipes: %d %d %d %d %d %d\n",
					wpfd[0], wpfd[1], rpfd[0], rpfd[1], epfd[0], epfd[1]);
			pvmlogerror(pvmtxt);
		}

		if ((pid = fork()) == -1) {
			pvmlogperror("slave_exec() fork");
			pvmbailout(0);
		}
		if (!pid) {
			(void)dup2(wpfd[0], 0);
			(void)dup2(rpfd[1], 1);
			(void)dup2(epfd[1], 2);
			for (i = getdtablesize(); --i > 2; )
				(void)close(i);
			ac = 0;
			av[ac++] = RSHCOMMAND;
			av[ac++] = hn;
			if (hp->hd_login) {
				av[ac++] = "-l";
				av[ac++] = hp->hd_login;
			}
			av[ac++] = hp->hd_dpath ? hp->hd_dpath : pvmgetrpvmd();
			av[ac++] = "-s";
			(void)sprintf(buf, "-d%x", debugmask);
			av[ac++] = STRALLOC(buf);

			(void)sprintf(buf, "-n%s", hn);
			av[ac++] = STRALLOC(buf);

			/* master host index */
			sprintf(buf, "%d", hosts->ht_master);
			av[ac++] = STRALLOC(buf);

			/* master host sockaddr */
			p = inadport_hex(&hosts->ht_hosts[hosts->ht_master]->hd_sad);
			av[ac++] = STRALLOC(p);

			/* master host mtu */
			sprintf(buf, "%d", hosts->ht_hosts[hosts->ht_master]->hd_mtu);
			av[ac++] = STRALLOC(buf);

			/* this slave index */
			sprintf(buf, "%d",
					((hp->hd_hostpart & tidhmask) >> (ffs(tidhmask) - 1)));
			av[ac++] = STRALLOC(buf);

			/* this slave sockaddr */
			p = inadport_hex(&hp->hd_sad);
			av[ac++] = STRALLOC(p);

			av[ac++] = 0;
			execvp(av[0], av);
			fputs("slave_exec() execvp failed\n", stderr);
			fflush(stderr);
			_exit(1);
		}
		(void)close(wpfd[0]);
		(void)close(rpfd[1]);
		(void)close(epfd[1]);
		wfd = wpfd[1];
		rfd = rpfd[0];
		efd = epfd[0];

	} else {		/* use rexec to start */

#ifdef NOREXEC
		sprintf(pvmtxt, "slconfg() sorry, no rexec()\n");
		pvmlogerror(pvmtxt);
		goto oops;
#else
		(void)sprintf(buf, "%s -s -d%x -n%s %d %s %d",
				hp->hd_dpath ? hp->hd_dpath : pvmgetrpvmd(),
				debugmask,
				hn,
				hosts->ht_master,
				inadport_hex(&hosts->ht_hosts[hosts->ht_master]->hd_sad),
				hosts->ht_hosts[hosts->ht_master]->hd_mtu);
		(void)sprintf(buf + strlen(buf), " %d %s\n",
				((hp->hd_hostpart & tidhmask) >> (ffs(tidhmask) - 1)),
				inadport_hex(&hp->hd_sad));
		if (debugmask & PDMSTARTUP) {
			sprintf(pvmtxt, "slave_exec() rexec \"%s\"\n", buf);
			pvmlogerror(pvmtxt);
		}
		if ((wfd = rfd = rexec(&hn, execport,
				(hp->hd_login ? hp->hd_login : username),
				(char*)0, buf, &efd))
		== -1) {
			sprintf(pvmtxt, "slave_exec() rexec failed for host %s\n", hn);
			pvmlogerror(pvmtxt);
			goto oops;
		}
#endif
	}

	/* get its version */

	if ((n = eread(rfd, buf, sizeof(buf)-1, efd, hn)) == -1) {
		sprintf(pvmtxt, "host %s read error\n", hn);
		pvmlogerror(pvmtxt);
		goto oops;
	}
	buf[n] = 0;
	if (debugmask & PDMSTARTUP)
		pvmlogerror(buf);
	ac = sizeof(av)/sizeof(av[0]);
	if (crunchzap(buf, &ac, av) || ac != 4) {
		pvmlogerror("slave_exec() expected version\n");
		goto oops;
	}

	ver = atoi(av[0]);
	if (ver != DDPROTOCOL) {
		sprintf(pvmtxt, "slave_exec() host %s d-d protocol mismatch (%d/%d)\n",
				hn, ver, DDPROTOCOL);
		pvmlogerror(pvmtxt);
		hp->hd_err = PvmBadVersion;
		goto done;
	}

	hp->hd_arch = STRALLOC(av[1]);
	hex_inadport(av[2], &hp->hd_sad);
	hp->hd_mtu = atoi(av[3]);
	goto done;

oops:
	if (pid != -1)
		(void)kill(pid, SIGTERM);
	hp->hd_err = PvmCantStart;
done:
	close(wfd);
	close(rfd);
	close(efd);
	wait(0);
	return;
}
#endif	/*RSHNPLL > 1*/


