
/*
 *           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.
 */

/*
 *	tdpro.c
 *
 *	Entry points for messages from local tasks.
 *
$Log: tdpro.c,v $
 * Revision 1.1  1993/08/30  23:26:52  manchek
 * Initial revision
 *
 */


#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>
#ifdef	SYSVSTR
#include <string.h>
#else
#include <strings.h>
#endif
#include <errno.h>
#include <stdio.h>

#include "global.h"
#include "fromlib.h"
#include "tdpro.h"
#include "ddpro.h"
#include "protoglarp.h"
#include "pvmalloc.h"
#include "mesg.h"
#include "task.h"
#include "host.h"
#include "waitc.h"
#include "listmac.h"
#include "bfunc.h"

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


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

extern void task_dump();
char *debug_flags();

extern int debugmask;				/* from pvmd.c */
extern struct htab *hosts;			/* from pvmd.c */
extern struct task *locltasks;		/* from task.c */
extern int myndf;					/* from pvmd.c */
extern int myhostpart;				/* from pvmd.c */
extern int mytid;					/* from pvmd.c */
extern int nopax;					/* from pvmd.c */
extern int ourudpmtu;				/* from pvmd.c */
extern int runstate;				/* from pvmd.c */
extern int tidhmask;				/* from pvmd.c */
extern int tidlmask;				/* from pvmd.c */


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

static char rcsid[] = "$Id: tdpro.c,v 1.1 1993/08/30 23:26:52 manchek Exp $";
static char pvmtxt[512];		/* scratch for error log */

int tm_addhost();
int tm_config();
int tm_conn2();
int tm_connect();
int tm_db();
int tm_delhost();
int tm_exit();
int tm_halt();
int tm_mca();
int tm_mstat();
int tm_notify();
int tm_pstat();
int tm_sendsig();
int tm_spawn();
int tm_task();
int tm_tickle();

int (*loclswitch[])() = {
	0,
	tm_connect,
	tm_conn2,
	tm_exit,
	tm_addhost,
	tm_delhost,
	tm_config,
	tm_mstat,
	tm_halt,
	tm_tickle,
	tm_spawn,
	tm_pstat,
	tm_sendsig,
	tm_task,
	tm_mca,
	tm_notify,
	tm_db,
};


char *tm_names[] = {
	"tm_connect",
	"tm_conn2",
	"tm_exit",
	"tm_addhost",
	"tm_delhost",
	"tm_config",
	"tm_mstat",
	"tm_halt",
	"tm_tickle",
	"tm_spawn",
	"tm_pstat",
	"tm_sendsig",
	"tm_task",
	"tm_mca",
	"tm_notify",
	"tm_db",
};

char *
tmname(code)
	int code;
{
	if (code < 1 || code > sizeof(tm_names)/sizeof(tm_names[0]))
		return "unknown";
	return tm_names[code - 1];
}


int
loclentry(tp, mp)
	struct task *tp;
	struct mesg *mp;
{
	int c = mp->m_cod;

	if (debugmask & PDMMESSAGE) {
		sprintf(pvmtxt, "loclentry() from t%x code %s\n", mp->m_src,
				tmname(mp->m_cod));
		pvmlogerror(pvmtxt);
/*
		pvmhdump(mp->m_cfrag->fr_dat, mp->m_cfrag->fr_len, "frag: ");
*/
	}

	if (mp->m_enc != 1) {
		sprintf(pvmtxt, "loclentry() message from t%x with bad enc %d\n",
				tp->t_tid, mp->m_enc);
		pvmlogerror(pvmtxt);
		goto bail;
	}

	if (c < 1 || c >= sizeof(loclswitch)/sizeof(loclswitch[0])) {
		sprintf(pvmtxt, "loclentry() message from t%x with bogus code %d\n",
				tp->t_tid, c);
		pvmlogerror(pvmtxt);
		goto bail;
	}

	if ((!(tp->t_flag & TF_CONN) && c != TM_CONNECT && c != TM_CONN2)
	|| ((tp->t_flag & TF_AUTH) && c != TM_CONN2)) {
		pvmlogerror("loclentry() non-connect message from anon task\n");
		tp->t_flag |= TF_CLOSE;
		goto bail;
	}

	(loclswitch[c])(tp, mp);

bail:
	mesg_unref(mp);
	return 0;
}


/*********************************
 **  Task request entry points  **
 **                             **
 *********************************/


#ifdef	NOTMPNAM
#define	LEN_OF_TMP_NAM	32
char *pvmtmpnam();

#else	/*NOTMPNAM*/
#ifdef	L_tmpnam
#define	LEN_OF_TMP_NAM	L_tmpnam
#else
#define	LEN_OF_TMP_NAM	64
#endif

#endif	/*NOTMPNAM*/

/*	tm_connect()
*
*	Task connecting to pvmd phase 1.
*	We assign it a context, write in t-auth file to prove our ident,
*	then make d-auth file for task to prove itself.
*
*	TM_CONNECT
*	call {
*		int tdprotocol		// t-d protocol number compiled into libpvm
*		string authfile		// t-auth file pvmd must write
*	}
*	ret {
*		int tdprotocol		// pvmd's td protocol version
*		int acknack			// 1 if pvmd accepts connection
*		string authfile		// d-auth file task must write
*	}
*/

int
tm_connect(tp, mp)
	struct task *tp;
	struct mesg *mp;
{
	int ver;						/* task's libpvm t-d proto version */
	char authfn[LEN_OF_TMP_NAM];	/* t-auth file name */
	int d;

	if (upkint(mp, &ver) || upkstr(mp, authfn, sizeof(authfn))) {
		pvmlogerror("tm_connect() bad msg format\n");
		goto bail;
	}

	/*
	*	if protocols are not compatible, send nack
	*	context will get flushed after reply is sent
	*/

	if (ver != TDPROTOCOL) {
		sprintf(pvmtxt, "tm_connect() t-d protocol mismatch (%d/%d)\n",
			ver, TDPROTOCOL);
		pvmlogerror(pvmtxt);

		mp = mesg_new(0);
		pkint(mp, TDPROTOCOL);
		pkint(mp, 0);
		pkstr(mp, "");
		mp->m_cod = TM_CONNECT;
		mp->m_flag |= MM_PRIO;
		mesg_to_task(tp, mp);
		mesg_unref(mp);
		goto bail;
	}

	/*
	*	write in t-auth file, create empty d-auth file that task
	*	must write and we'll check later
	*/

	if ((d = open(authfn, O_WRONLY, 0)) == -1) {
		pvmlogperror("tm_connect() can't open t-auth file");
		goto bail;
	}
	(void)write(d, authfn, 1);
	(void)close(d);

	tp->t_authnam = TALLOC(LEN_OF_TMP_NAM, char, "auth");
#ifdef	NOTMPNAM
	(void)pvmtmpnam(tp->t_authnam);
#else
	(void)tmpnam(tp->t_authnam);
#endif

	if ((tp->t_authfd = open(tp->t_authnam, O_RDONLY|O_CREAT|O_TRUNC, 0600))
	== -1) {
		pvmlogperror("tm_connect() can't create d-auth file");
		goto bail;
	}

	/*
	*	task's turn to auth
	*/

	tp->t_flag |= TF_AUTH;

	mp = mesg_new(0);
	pkint(mp, TDPROTOCOL);
	pkint(mp, 1);
	pkstr(mp, tp->t_authnam);
	mp->m_cod = TM_CONNECT;
	mp->m_flag |= MM_PRIO;
	mesg_to_task(tp, mp);
	mesg_unref(mp);
	return 0;

bail:
	tp->t_flag |= TF_CLOSE;
	return 0;
}


/*	tm_conn2()
*
*	Task connecting to pvmd phase 2.
*	We check d-auth file and give it config info, attach to real context.
*
*	TM_CONN2
*	call {
*		int unixpid			// real pid
*		int identpid		// pid assigned by pvmd or 0
*	}
*	ret {
*		int acknack			// 1 if pvmd accepts connection
*		int tid				// task tid
*		int ptid			// parent tid
*		int outtid			// output dst and code
*		int outcod
*		int trctid			// trace dst and code
*		int trccod
*		int udpmax
*		int nativecode
*	}
*/

int
tm_conn2(tp, mp)
	struct task *tp;
	struct mesg *mp;
{
	int tid;
	int pid;						/* real pid of task */
	int ipid;						/* fake pid to identify task or 0 */
	struct task *tp2;				/* to search for existing context */
	int cc;
	char c;

	if (upkint(mp, &pid) || upkint(mp, &ipid)) {
		pvmlogerror("tm_conn2() bad msg format\n");
		goto bail;
	}
	if (!ipid)
		ipid = pid;

	if (!(tp->t_flag & TF_AUTH)) {
		sprintf(pvmtxt, "tm_conn2() message from t%x, TF_AUTH not set\n",
				tp->t_tid);
		pvmlogerror(pvmtxt);
		return 0;
	}

	/*
	*	check that task could write d-auth file
	*/

	if ((cc = read(tp->t_authfd, &c, 1)) == -1) {
		pvmlogperror("tm_conn2() can't read d-auth file");
		return 0;
	}

	if (cc != 1) {
		pvmlogerror("tm_conn2() task didn't validate itself\n");
		goto bail;
	}

	(void)close(tp->t_authfd);
	tp->t_authfd = -1;
	(void)unlink(tp->t_authnam);
	PVM_FREE(tp->t_authnam);
	tp->t_authnam = 0;

	/*
	*	if task spawned by us, already has a context,
	*	else make it one
	*/

	if (tp2 = task_findpid(ipid)) {
		if (debugmask & PDMTASK) {
			sprintf(pvmtxt, "tm_conn2() reconnect task t%x\n", tp2->t_tid);
			pvmlogerror(pvmtxt);
		}

	} else {
		if ((tid = tid_new()) < 0) {
			pvmlogerror("tm_conn2() out of tids?\n");
			goto bail;		/* XXX should disconnect nicely */
		}
		tp2 = task_new(tid);
		if (debugmask & PDMTASK) {
			sprintf(pvmtxt, "tm_conn2() new task t%x\n", tp2->t_tid);
			pvmlogerror(pvmtxt);
		}
	}

	/*
	*	brundle-fly the contexts together
	*/

	tp2->t_sock = tp->t_sock;
	tp2->t_sad = tp->t_sad;
	tp2->t_salen = tp->t_salen;
	if (tp2->t_pid != pid)
		task_setpid(tp2, pid);
	tp2->t_rxp = tp->t_rxp;
	tp->t_sock = -1;	/* tp will be freed by loclinput() */
	tp->t_rxp = 0;
	tp = tp2;

	/*
	*	kick it in the butt; it's ready to go
	*/

	tp->t_flag &= ~TF_AUTH;
	tp->t_flag |= TF_CONN;

	mp = mesg_new(0);
	pkint(mp, 1);
	pkint(mp, tp->t_tid);
	pkint(mp, tp->t_ptid);
	pkint(mp, tp->t_outtid);
	pkint(mp, tp->t_outcod);
	pkint(mp, tp->t_trctid);
	pkint(mp, tp->t_trccod);
	pkint(mp, ourudpmtu);
	pkint(mp, myndf);
	pkstr(mp, inadport_hex(&(hosts->ht_hosts[hosts->ht_local]->hd_sad)));
	mp->m_dst = tp->t_tid;
	mp->m_cod = TM_CONN2;
	mp->m_flag |= MM_PRIO;
	sendmessage(mp);
	return 0;

bail:
	tp->t_flag |= TF_CLOSE;
	return 0;
}


/*	tm_exit()
*
*	Last message from a task.  This is the nice way of disconnecting.
*
*	TM_EXIT
*	call { }
*	ret { }
*/

int
tm_exit(tp, mp)
	struct task *tp;
	struct mesg *mp;
{
	mp = mesg_new(0);
	mp->m_dst = tp->t_tid;
	mp->m_cod = TM_EXIT;
	tp->t_flag |= TF_CLOSE;
	sendmessage(mp);
	task_cleanup(tp);
	return 0;
}


/*	tm_pstat()
*
*	Task wants status of another task.
*
*	TM_PSTAT
*	call {
*		int tid
*	}
*	ret {
*		int status
*	}
*/

int
tm_pstat(tp, mp)
	struct task *tp;
	struct mesg *mp;
{
	int tid;
	struct hostd *hp;
	struct waitc *wp;

	/* unpack and sanity check tid */

	if (upkuint(mp, &tid)) {
		pvmlogerror("tm_pstat() bad msg format\n");
		return 0;
	}
	if (!TIDISTASK(tid)) {
		sprintf(pvmtxt, "tm_pstat() bad tid %x\n", tid);
		pvmlogerror(pvmtxt);
		return 0;
	}

	/* nack if no such host */

	if (!(hp = tidtohost(hosts, tid))) {
		mp = mesg_new(0);
		mp->m_dst = tp->t_tid;
		mp->m_cod = TM_PSTAT;
		pkint(mp, PvmNoTask);
		sendmessage(mp);
		return 0;
	}

	/* else make a wait context and send query */

	wp = wait_new(WT_PSTAT);
	wp->wa_tid = tp->t_tid;
	wp->wa_on = hp->hd_hostpart;

	mp = mesg_new(0);
	mp->m_dst = hp->hd_hostpart | TIDPVMD;
	mp->m_cod = DM_PSTAT;
	mp->m_wid = wp->wa_wid;
	pkint(mp, tid);
	sendmessage(mp);
	return 0;
}


/*	tm_addhost()
*
*	Task requesting to add hosts to virtual machine.  Exit point is
*	here or dm_addhostack().
*
*	TM_ADDHOST
*	call {
*		int nhosts
*		string names[nhosts]
*	}
*	ret {
*		int nhosts			// or negative for error
*		int status[nhosts]	// status (tid if successful) of each host
*	}
*/

int
tm_addhost(tp, mp)
	struct task *tp;
	struct mesg *mp;
{
	int count;
	char buf[128];
	struct waitc *wp;

	/* sanity check the message */

	if (upkint(mp, &count))
		goto bad;
	if (count < 1 || count > (tidhmask >> (ffs(tidhmask) - 1)))
		goto bad;
	while (count-- > 0)
		if (upkstr(mp, buf, sizeof(buf)))
			goto bad;

	/* make a wait channel for the task */

	wp = wait_new(WT_ADDHOST);
	wp->wa_tid = tp->t_tid;
	wp->wa_on = hosts->ht_hosts[hosts->ht_master]->hd_hostpart;

	/* forward message to master pvmd */

	mp->m_dst = hosts->ht_hosts[hosts->ht_master]->hd_hostpart | TIDPVMD;
	mp->m_cod = DM_ADDHOST;
	mp->m_wid = wp->wa_wid;
	mp->m_ref++;
	sendmessage(mp);
	return 0;

bad:
	sprintf(pvmtxt, "tm_addhost() from t%x bad msg format\n", mp->m_src);
	pvmlogerror(pvmtxt);
	return 0;
}


free_wait_spawn(wxp)
	struct waitc_spawn *wxp;
{
	int i;

	if (wxp->w_file)
		PVM_FREE(wxp->w_file);
	if (wxp->w_argv) {
		for (i = 0; i < wxp->w_argc; i++)
			if (wxp->w_argv[i])
				PVM_FREE(wxp->w_argv[i]);
		PVM_FREE(wxp->w_argv);
	}
	if (wxp->w_env) {
		for (i = 0; i < wxp->w_nenv; i++)
			if (wxp->w_env[i])
				PVM_FREE(wxp->w_env[i]);
		PVM_FREE(wxp->w_env);
	}
	if (wxp->w_ht)
		ht_free(wxp->w_ht);
	if (wxp->w_vec)
		PVM_FREE(wxp->w_vec);
	PVM_FREE(wxp);
	return 0;
}


/*	tm_spawn()
*
*	Task requesting to spawn other tasks.  Exit point for this
*	request is here or dm_spawnack().
*
*	TM_SPAWN
*	call {
*		string file
*		int flags
*		string where
*		int count
*		int nargs
*		string argv[nargs]
*		int outtid
*		int outcod
*		int trctid
*		int trccod
*		int nenv
*		string env[nenv]
*	}
*	ret {
*		int ntids  // or nack
*		int tids[acknack]
*	}
*/

int
tm_spawn(tp, mp)
	struct task *tp;
	struct mesg *mp;
{
	char *file = 0;				/* executable */
	int flags;					/* options */
	char *where = 0;			/* location from req */
	int count;					/* num of tasks */
	int argc;					/* num of args */
	char **argv = 0;			/* args */
	int outtid, outcod;			/* stdout dst and code */
	int trctid, trccod;			/* trace dst and code */
	int nenv;					/* num of envars */
	char **env = 0;				/* envars */

	struct waitc *wp;			/* 'seed' waitc */
	struct waitc_spawn *wxp;	/* extra data */
	int *vec;					/* result/status vector */
	struct htab *htp;			/* set of usable hosts */
	struct hostd *hp;
	int hh;
	int i;

	/*
	* unpack spawn command from task
	* XXX should clean up this code and upk directly into waitc_spawn
	*/

	if (upkstralloc(mp, &file)
	|| upkint(mp, &flags)
	|| upkstralloc(mp, &where)
	|| upkint(mp, &count)
	|| upkint(mp, &argc))
		goto bad;

	if (count < 1)
		goto bad;

	argc++;
	BZERO((char*)(argv = TALLOC(argc, char*, "argv")),
			argc * sizeof(char*));
	argc--;
	for (i = 0; i < argc; i++)
		if (upkstralloc(mp, &argv[i]))
			goto bad;

	if (upkuint(mp, &outtid)
	|| upkuint(mp, &outcod)
	|| upkuint(mp, &trctid)
	|| upkuint(mp, &trccod)
	|| upkuint(mp, &nenv))
		goto bad;
	BZERO((char*)(env = TALLOC((nenv + 1), char*, "env")),
			(nenv + 1) * sizeof(char*));
	for (i = 0; i < nenv; i++)
		if (upkstralloc(mp, &env[i]))
			goto bad;

	/*
	* make host set containing hosts (matching where option)
	*/

	if ((flags & (PvmTaskHost|PvmTaskArch)) && !where)
		goto bad;

	htp = ht_new(1);

	if (flags & PvmTaskHost) {			/* given host */
		if (hp = nametohost(hosts, where))
			ht_insert(htp, hp);

	} else {
		if (flags & PvmTaskArch) {		/* given arch */
			for (hh = hosts->ht_last; hh > 0; hh--)
				if ((hp = hosts->ht_hosts[hh]) && !strcmp(where, hp->hd_arch))
					ht_insert(htp, hp);

		} else {						/* anywhere */
			ht_merge(htp, hosts);
		}
	}

	if (flags & PvmHostCompl) {
		for (hh = hosts->ht_last; hh > 0; hh--) {
			if (hh <= htp->ht_last && (hp = htp->ht_hosts[hh]))
				ht_delete(htp, hp);
			else
				if (hp = hosts->ht_hosts[hh])
					ht_insert(htp, hp);
		}
	}

	if (debugmask & PDMTASK) {
		pvmlogerror("tm_spawn() host set:\n");
		ht_dump(htp);
	}

	/*
	* assign each task to a host
	*/

	wp = wait_new(WT_SPAWN);
	wp->wa_tid = tp->t_tid;

	wxp = TALLOC(1, struct waitc_spawn, "waix");
	BZERO((char*)wxp, sizeof(struct waitc_spawn));
	wp->wa_spec = (void*)wxp;

	wxp->w_file = file;
	file = 0;
	wxp->w_flags = flags;
	wxp->w_argc = argc;
	wxp->w_argv = argv;
	argv = 0;
	wxp->w_outtid = outtid;
	wxp->w_outcod = outcod;
	wxp->w_trctid = trctid;
	wxp->w_trccod = trccod;
	wxp->w_nenv = nenv;
	wxp->w_env = env;
	env = 0;
	wxp->w_ht = htp;
	vec = TALLOC(count, int, "vec");
	BZERO((char*)vec, count * sizeof(int));
	wxp->w_vec = vec;
	wxp->w_togo = wxp->w_veclen = count;

	assign_tasks(wp);

	/* if already done, reply to task */

	if (wp->wa_peer == wp) {
		assign_tasks(wp);
	}

	wait_delete(wp);
	goto cleanup;

bad:
	sprintf(pvmtxt, "tm_spawn() from t%x bad msg format\n", mp->m_src);
	pvmlogerror(pvmtxt);

cleanup:
	if (file)
		PVM_FREE(file);
	if (where)
		PVM_FREE(where);
	if (argv) {
		for (i = 0; i < argc; i++)
			if (argv[i])
				PVM_FREE(argv[i]);
		PVM_FREE(argv);
	}
	if (env) {
		for (i = 0; i < nenv; i++)
			if (env[i])
				PVM_FREE(env[i]);
		PVM_FREE(env);
	}
	return 0;
}


/*	assign_tasks()
*
*	This is only called when no replies are pending (at the beginning
*	of the operation or when all pvmds have checked back in).
*/

assign_tasks(wp)
	struct waitc *wp;		/* (any) waitc in peer group for this op */
{
	struct waitc_spawn *wxp = (struct waitc_spawn*)wp->wa_spec;
	struct htab *htp;				/* set of hosts to use */
	int *vec;						/* result/status vector */
	int veclen;						/* length of vector */
	int count = 0;					/* num of tasks to be assigned */
	int nh;							/* num of hosts to assign tasks */
	int a = 0;						/* accum for finding hosts */
	int na = 0;						/* num tasks assigned to a host */
	struct waitc *wp2;
	struct hostd *hp;
	struct mesg *mp, *mp2;
	int t;
	int i;
	int tid;

	static int lasthh = -1;		/* for assigning hosts */

	if (!wxp)
		return 0;

	htp = wxp->w_ht;
	vec = wxp->w_vec;
	veclen = wxp->w_veclen;

	/*
	* if no hosts left, fill unassigned entries with PvmNoHost
	*/

	if (!htp->ht_cnt)
		for (t = veclen; t-- > 0; )
			if (!vec[t])
				vec[t] = PvmNoHost;

	/*
	* count tasks to be assigned, if none left reply to task
	*/

	for (t = veclen; t-- > 0; )
		if (!vec[t])
			count++;

	if (!count) {
		mp = mesg_new(0);
		mp->m_dst = wp->wa_tid;
		mp->m_cod = TM_SPAWN;
		pkint(mp, wxp->w_veclen);
		for (t = 0; t < wxp->w_veclen; t++) {
			tid = wxp->w_vec[t];
			pkint(mp, tid);
			if (TIDISTASK(tid) && wxp->w_outtid) {
				mp2 = mesg_new(0);
				mp2->m_cod = wxp->w_outcod;
				mp2->m_dst = wxp->w_outtid;
				pkint(mp2, tid);
				pkint(mp2, -1);
				pkint(mp2, wp->wa_tid);
				sendmessage(mp2);
			}
		}
		sendmessage(mp);

		free_wait_spawn(wxp);
		wp->wa_spec = 0;
		return 0;
	}

	/*
	* assign tasks to hosts
	*/

	nh = min(htp->ht_cnt, count);

	/* find first host to assign */

	if (lasthh == -1)
		lasthh = hosts->ht_local + 1;
	if (lasthh > htp->ht_last)
		lasthh = 0;
	while (!htp->ht_hosts[lasthh])
		if (++lasthh > htp->ht_last)
			lasthh = 1;
	hp = htp->ht_hosts[lasthh];

	for (t = 0; t < veclen && vec[t]; t++);

	while (t < veclen) {
/*
		sprintf(pvmtxt, "assign_tasks() %s <- %d\n", hp->hd_name, t);
		pvmlogerror(pvmtxt);
*/

		vec[t] = hp->hd_hostpart;
		na++;

	/* when enough tasks for this host, move to next */

		if ((a += nh) >= count) {
			a -= count;

			wp2 = wait_new(WT_SPAWN);
			wp2->wa_tid = wp->wa_tid;
			wp2->wa_on = hp->hd_hostpart;
			wp2->wa_spec = wp->wa_spec;
			LISTPUTBEFORE(wp, wp2, wa_peer, wa_rpeer);

			mp = mesg_new(0);
			pkint(mp, wp->wa_tid);
			pkstr(mp, wxp->w_file);
			pkint(mp, wxp->w_flags);
			pkint(mp, na);
			pkint(mp, wxp->w_argc);
			for (i = 0; i < wxp->w_argc; i++)
				pkstr(mp, wxp->w_argv[i]);
			pkint(mp, wxp->w_outtid);
			pkint(mp, wxp->w_outcod);
			pkint(mp, wxp->w_trctid);
			pkint(mp, wxp->w_trccod);
			pkint(mp, wxp->w_nenv);
			for (i = 0; i < wxp->w_nenv; i++)
				pkstr(mp, wxp->w_env[i]);
			mp->m_dst = hp->hd_hostpart | TIDPVMD;
			mp->m_cod = DM_SPAWN;
			mp->m_wid = wp2->wa_wid;

			do {
				if (++lasthh > htp->ht_last)
					lasthh = 1;
			} while (!htp->ht_hosts[lasthh]);

			sendmessage(mp);

			hp = htp->ht_hosts[lasthh];
			na = 0;
		}
		for (t++ ; t < veclen && vec[t]; t++);
	}
	return 0;
}


/*	tm_sendsig()
*
*	Task sending a signal to another task.
*
*	TM_SENDSIG
*	call {
*		int tid
*		int signum
*	}
*	ret { }
*/

int
tm_sendsig(tp, mp)
	struct task *tp;
	struct mesg *mp;
{
	int tid;

	if (upkuint(mp, &tid)) {
		pvmlogerror("tm_sendsig() bad msg format\n");
		return 0;
	}
	if (!TIDISTASK(tid)) {
		sprintf(pvmtxt, "tm_sendsig() bad tid %x\n", tid);
		pvmlogerror(pvmtxt);
		return 0;
	}
	mp->m_dst = (tid & TIDHOST) | TIDPVMD;
	mp->m_cod = DM_SENDSIG;
	mp->m_wid = 0;
	mp->m_ref++;
	sendmessage(mp);

	mp = mesg_new(0);
	mp->m_dst = tp->t_tid;
	mp->m_cod = TM_SENDSIG;
	sendmessage(mp);
	return 0;
}


/*	tm_config()
*
*	Task wants machine configuration.
*
*	TM_CONFIG
*	call { }
*	ret {
*		int nhosts
*		int narches
*		{
*			int index
*			string name
*			string arch
*			int mtu
*			int speed
*		} [nhosts]
*	}
*/

int
tm_config(tp, mp)
	struct task *tp;
	struct mesg *mp;
{
	int hh;
	struct hostd *hp;

	mp = mesg_new(0);
	mp->m_dst = tp->t_tid;
	mp->m_cod = TM_CONFIG;

	pkint(mp, hosts->ht_cnt);
	pkint(mp, hosts->ht_narch);
	for (hh = 1; hh <= hosts->ht_last; hh++) {
		if (hp = hosts->ht_hosts[hh]) {
			pkint(mp, hp->hd_hostpart);
			pkstr(mp, hp->hd_name);
			pkstr(mp, hp->hd_arch ? hp->hd_arch : "");
			pkint(mp, hp->hd_speed);
		}
	}
	sendmessage(mp);
	return 0;
}


/*	tm_halt()
*
*	Command from task to stop master pvmd.
*
*	TM_HALT
*	call { }
*	ret { }
*/

int
tm_halt(tp, mp)
	struct task *tp;
	struct mesg *mp;
{
	mp = mesg_new(0);
	mp->m_cod = DM_HALT;
	mp->m_dst = hosts->ht_hosts[hosts->ht_master]->hd_hostpart | TIDPVMD;
	sendmessage(mp);
	return 0;
}


/*	tm_task()
*
*	Task wants a list of tasks.
*	If where is a host or task tid, give a list of matching tasks.
*	If where is zero, give all running tasks.
*
*	TM_TASK
*	call {
*		int where
*	}
*	ret {
*		{
*			int tid
*			int ptid
*			int hostpart
*			int flag
*		} [count]    // implied
*	}
*/

int
tm_task(tp, mp)
	struct task *tp;
	struct mesg *mp;
{
	int where;
	struct task *tp2;
	struct mesg *mp2;
	struct waitc *wp;
	struct waitc *wp2 = 0;	/* master waitc of peer group */
	int hh;
	struct hostd *hp;

	if (upkuint(mp, &where)) {
		pvmlogerror("tm_task() bad msg format\n");
		return 0;
	}

	mp = mesg_new(0);
	mp->m_dst = tp->t_tid;
	mp->m_cod = TM_TASK;

	if (where) {		/* specific host or task requested */
		if (!(hp = tidtohost(hosts, where))) {
			pkint(mp, PvmNoHost);
			sendmessage(mp);
			return 0;
		}
		pkint(mp, 0);
		wp = wait_new(WT_TASK);
		wp->wa_mesg = mp;
		wp->wa_tid = tp->t_tid;
		wp->wa_on = hp->hd_hostpart;

		mp = mesg_new(0);
		mp->m_cod = DM_TASK;
		mp->m_dst = hp->hd_hostpart | TIDPVMD;
		mp->m_wid = wp->wa_wid;
		pkint(mp, where);
		sendmessage(mp);

	} else {			/* all tasks requested */
		pkint(mp, 0);

		wp2 = wait_new(WT_TASK);
		mp->m_ref++;
		wp2->wa_mesg = mp;
		wp2->wa_tid = tp->t_tid;

		mp2 = mesg_new(0);
		mp2->m_cod = DM_TASK;
		pkint(mp2, 0);

		for (hh = hosts->ht_last; hh > 0; hh--) {
			if (!hosts->ht_hosts[hh])
				continue;

			wp = wait_new(WT_TASK);
			mp->m_ref++;
			wp->wa_mesg = mp;
			wp->wa_tid = tp->t_tid;
			wp->wa_on = hosts->ht_hosts[hh]->hd_hostpart;

			LISTPUTBEFORE(wp2, wp, wa_peer, wa_rpeer);

			mp2->m_dst = hosts->ht_hosts[hh]->hd_hostpart | TIDPVMD;
			mp2->m_wid = wp->wa_wid;
			mp2->m_ref++;
			sendmessage(mp2);
		}
		mesg_unref(mp2);
		mesg_unref(mp);

		/* send message if all waiters are in */

		if (wp2->wa_peer == wp2) {
			mp->m_ref++;
			sendmessage(mp);
		}
		wait_delete(wp2);
	}
	return 0;
}


/*	tm_tickle()
*
*	Task wants to poke at pvmd.
*
*	TM_TICKLE
*	call {
*		int nargs
*		int args[nargs]
*	}
*	ret {
*		int nresult
*		int results[nresult]
*	}
*/

int
tm_tickle(tp, mp)
	struct task *tp;
	struct mesg *mp;
{
	int nar;
	int arg[10];
	int i;
	char *p;
	struct hostd *hp;

	upkint(mp, &nar);
	if (nar < 1 || nar > 10) {
		pvmlogerror("tm_tickle() bad msg format\n");
		return 0;
	}
	for (i = 0; i < nar; i++)
		upkint(mp, &arg[i]);
	while (i < sizeof(arg)/sizeof(arg[0]))
		arg[i++] = 0;

	strcpy(pvmtxt, "tm_tickle() #");
	p = pvmtxt + strlen(pvmtxt);
	for (i = 0; i < nar; i++) {
		sprintf(p, " %d", arg[i]);
		p += strlen(p);
	}
	strcat(p, "\n");
	pvmlogerror(pvmtxt);

	mp = mesg_new(0);

	switch (arg[0]) {

	case 0:
		i_dump(1);
		pkint(mp, 0);
		break;

	case 1:
		ht_dump(hosts);
		pkint(mp, 0);
		break;

	case 2:
		task_dump();
		pkint(mp, 0);
		break;

	case 3:
		wait_dumpall();
		pkint(mp, 0);
		break;

	case 4:
		nmd_dumpall();
		pkint(mp, 0);
		break;

	case 5:
		pkint(mp, 1);
		pkint(mp, debugmask);
		break;

	case 6:
		debugmask = arg[1];
		sprintf(pvmtxt, "tm_tickle() debugmask is %x (%s)\n",
				debugmask, debug_flags(debugmask));
		pvmlogerror(pvmtxt);
		pkint(mp, 0);
		break;

	case 7:
		if (arg[1] > 0 && arg[1] < 50) {
			nopax = arg[1];
			sprintf(pvmtxt, "tm_tickle() nopax is %d\n", nopax);
		} else
			sprintf(pvmtxt, "tm_tickle() bogus nopax %d\n", arg[1]);
		pvmlogerror(pvmtxt);
		pkint(mp, 0);
		break;

	case 8:
		pkint(mp, 1);
		if ((hp = tidtohost(hosts, arg[1]))
		&& hp != hosts->ht_hosts[hosts->ht_local]) {
			sprintf(pvmtxt, "tm_tickle() failing %s\n", hp->hd_name);
			hostfailentry(hp);
			ht_delete(hosts, hp);
			pkint(mp, 1);
		} else {
			if (hp)
				sprintf(pvmtxt, "tm_tickle() can't fail %s\n", hp->hd_name);
			else
				sprintf(pvmtxt, "tm_tickle() no such host %x\n", arg[1]);
			pkint(mp, 0);
		}
		pvmlogerror(pvmtxt);
		break;

	case 9:
#ifdef	STATISTICS
		dump_statistics();
		if (arg[1])
			reset_statistics();
#else
		pvmlogerror("tm_tickle() statistics not compiled in\n");
#endif
		pkint(mp, 0);
		break;

	default:
		sprintf(pvmtxt, "tm_tickle() don't know #%d\n", arg[0]);
		pvmlogerror(pvmtxt);
		pkint(mp, 0);
		break;
	}

	mp->m_dst = tp->t_tid;
	mp->m_cod = TM_TICKLE;
	sendmessage(mp);
	return 0;
}


/*	tm_delhost()
*
*	Task requesting to add hosts to virtual machine.  Exit point is
*	here or dm_delhostack().
*
*	TM_DELHOST
*	call {
*		int nhosts
*		string names[nhosts]
*	}
*	ret {
*		int nhosts			// or negative for error
*		int status[nhosts]	// status of each host
*	}
*/

int
tm_delhost(tp, mp)
	struct task *tp;
	struct mesg *mp;
{
	int count;
	char buf[128];
	struct waitc *wp;

	/* sanity check the message */

	if (upkint(mp, &count))
		goto bad;
	if (count < 1 || count > (tidhmask >> (ffs(tidhmask) - 1)))
		goto bad;
	while (count-- > 0)
		if (upkstr(mp, buf, sizeof(buf)))
			goto bad;

	/* make a wait channel for the task */

	wp = wait_new(WT_DELHOST);
	wp->wa_tid = tp->t_tid;
	wp->wa_on = hosts->ht_hosts[hosts->ht_master]->hd_hostpart;

	/* forward message to master pvmd */

	mp->m_dst = hosts->ht_hosts[hosts->ht_master]->hd_hostpart | TIDPVMD;
	mp->m_cod = DM_DELHOST;
	mp->m_wid = wp->wa_wid;
	mp->m_ref++;
	sendmessage(mp);
	return 0;

bad:
	sprintf(pvmtxt, "tm_delhost() from t%x bad msg format\n", mp->m_src);
	pvmlogerror(pvmtxt);
	return 0;
}


static int
int_compare(i, j)
	int *i, *j;
{
	return *i - *j;
}


/*	tm_mca()
*
*	Task wants multicast address to use for subsequent message.
*
*	TM_MCA
*	call {
*		int count			// number of addresses
*		int tids[count]		// addresses
*	}
*	ret {
*		int gtid			// multicast tid to use
*	}
*/

int
tm_mca(tp, mp)
	struct task *tp;
	struct mesg *mp;
{
	static int lastmca = 0;		/* last-assigned mca local part */
	struct mca *mcap;			/* mca descriptor */
	int ndst;					/* num of dst tids */
	int *dsts;					/* dst tids */
	int tid;
	int i1, i2;

	/* generate new mca local part */

	if (++lastmca > tidlmask)
		lastmca = 0;

	/*
	* unpack list of dst tids from message.
	* normalize local dsts to nonzero hostpart.
	* discard tids for nonexistent local tasks or foreign hosts or the sender.
	*/

	mcap = mca_new();
	mcap->mc_tid = myhostpart | TIDGID | lastmca;
	upkint(mp, &ndst);
	dsts = TALLOC(ndst, int, "dsts");
	for (i1 = 0; i1 < ndst; ) {
		upkuint(mp, &tid);
		if (!(tid & tidhmask) || (tid & tidhmask) == myhostpart) {
			tid |= myhostpart;
			if (tid == tp->t_tid) {
				ndst--;
				continue;
			}

		} else
			if (!tidtohost(hosts, tid)) {
				ndst--;
				continue;
			}
		dsts[i1++] = tid;
	}

	if (ndst < 1)		/* if no dsts left then just reply to task */
		goto noguys;

	/*
	* sort dst tids
	* XXX we could easily remove duplicates here
	* send DM_MCA messages containing tids to dst hosts
	* make list of dst hosts for us
	*/

	qsort((char*)dsts, ndst, sizeof(int), int_compare);

	mcap->mc_dsts = TALLOC(ndst, int, "mcad");	/* XXX cheat, too much space */
	mcap->mc_ndst = 0;

	for (i2 = 0; (i1 = i2) < ndst; ) {
		tid = dsts[i1] & tidhmask;
		while (++i2 < ndst && tid == (dsts[i2] & tidhmask)) ;
		mp = mesg_new(0);
		mp->m_dst = (tid |= TIDPVMD);
		mp->m_cod = DM_MCA;
		pkint(mp, mcap->mc_tid);
		pkint(mp, i2 - i1);
		while (i1 < i2)
			pkint(mp, dsts[i1++]);
		sendmessage(mp);
		mcap->mc_dsts[mcap->mc_ndst++] = tid;
	}

noguys:
	PVM_FREE(dsts);

	/*
	* tag task descriptor with mca desc and send mca back to task
	*/

	tp->t_mca = mcap;

	if (debugmask & PDMMESSAGE) {
		sprintf(pvmtxt, "tm_mca() made mca %x for t%x\n",
				mcap->mc_tid, tp->t_tid);
		pvmlogerror(pvmtxt);
	}

	mp = mesg_new(0);
	pkint(mp, mcap->mc_tid);
	mp->m_dst = tp->t_tid;
	mp->m_cod = TM_MCA;
	sendmessage(mp);
	return 0;
}


/*	tm_notify()
*
*	Task wants to be notified when an event happens.
*
*	TM_NOTIFY
*	call {
*		int what			// event type
*		int code			// message code to use in reply
*		int count			// number of addresses
*		int tids[count]		// addresses
*	}
*	// No Return
*/

int
tm_notify(tp, mp)
	struct task *tp;
	struct mesg *mp;
{
	int what, code, count, tid;
	struct hostd *hp;
	struct mesg *mp2;
	struct waitc *wp;
	int i;

	if (upkint(mp, &what) || upkint(mp, &code) || upkint(mp, &count)
	|| count < 0) {
		pvmlogerror("tm_notify() bad msg format\n");
		return 0;
	}

/*
	mp2 = mesg_new(0);
	mp2->m_dst = tp->t_tid;
	mp2->m_cod = TM_NOTIFY;
	sendmessage(mp2);
*/

	switch (what) {

	case PvmTaskExit:
		for (i = 0; i < count; i++) {
			if (upkuint(mp, &tid)) {
				pvmlogerror("tm_notify() bad msg format\n");
				return 0;
			}
			if (!(hp = tidtohost(hosts, tid)))
				goto dead;

			if (hp->hd_hostpart == myhostpart) {
				if (!task_find(tid))
					goto dead;
				wp = wait_new(WT_TASKX);
				wp->wa_tid = tp->t_tid;
				wp->wa_on = tid;
				wp->wa_dep = code;

			} else {
				wp = wait_new(WT_TASKX);
				wp->wa_tid = tp->t_tid;
				wp->wa_on = tid;
				wp->wa_dep = code;
				mp2 = mesg_new(0);
				pkint(mp2, what);
				pkint(mp2, tid);
				mp2->m_dst = hp->hd_hostpart | TIDPVMD;
				mp2->m_cod = DM_NOTIFY;
				mp2->m_wid = wp->wa_wid;
				sendmessage(mp2);
			}
			continue;
dead:
			mp2 = mesg_new(0);
			mp2->m_dst = tp->t_tid;
			mp2->m_cod = code;
			pkint(mp2, tid);
			sendmessage(mp2);
		}
		break;

	case PvmHostDelete:
		for (i = 0; i < count; i++) {
			if (upkuint(mp, &tid)) {
				pvmlogerror("tm_notify() bad msg format\n");
				return 0;
			}
			if (hp = tidtohost(hosts, tid)) {
				wp = wait_new(WT_HOSTF);
				wp->wa_tid = tp->t_tid;
				wp->wa_on = hp->hd_hostpart;
				wp->wa_dep = code;

			} else {
				mp2 = mesg_new(0);
				mp2->m_dst = tp->t_tid;
				mp2->m_cod = code;
				pkint(mp2, tid);
				sendmessage(mp2);
			}
		}
		break;

	case PvmHostAdd:
		wp = wait_new(WT_HOSTA);
		wp->wa_tid = tp->t_tid;
		wp->wa_on = tp->t_tid;
		wp->wa_dep = code;
		break;

	default:
		sprintf(pvmtxt, "tm_notify() unknown what=%d\n", what);
		pvmlogerror(pvmtxt);
		break;
	}
	return 0;
}


/*	tm_mstat()
*
*	Task wants status of a host.
*
*	TM_MSTAT
*	call {
*		string hostname
*	}
*	ret {
*		int status
*	}
*/

int
tm_mstat(tp, mp)
	struct task *tp;
	struct mesg *mp;
{
	char *name;
	struct hostd *hp;
	struct waitc *wp;

	if (upkstralloc(mp, &name)) {
		pvmlogerror("tm_mstat() bad msg format\n");
		return 0;
	}

	hp = nametohost(hosts, name);

	PVM_FREE(name);

	if (!hp) {
		mp = mesg_new(0);
		mp->m_dst = tp->t_tid;
		mp->m_cod = TM_MSTAT;
		pkint(mp, PvmNoHost);
		sendmessage(mp);
		return 0;
	}

	wp = wait_new(WT_MSTAT);
	wp->wa_tid = tp->t_tid;
	wp->wa_on = hp->hd_hostpart;

	mp = mesg_new(0);
	mp->m_dst = hp->hd_hostpart | TIDPVMD;
	mp->m_cod = DM_PSTAT;
	mp->m_wid = wp->wa_wid;
	pkint(mp, hp->hd_hostpart | TIDPVMD);
	sendmessage(mp);
	return 0;
}


/*	tm_db()
*
*	Task wants a database op.
*
*	TM_DB
*	call {
*		int opcode		// insert, delete, lookup
*		string name
*		int index
*		int data		// if 'insert'
*	}
*	ret {
*		int index		// and status
*		int data		// if 'lookup'
*	}
*/

int
tm_db(tp, mp)
	struct task *tp;
	struct mesg *mp;
{
	struct waitc *wp;

	wp = wait_new(WT_DB);
	wp->wa_tid = tp->t_tid;
	wp->wa_on = hosts->ht_hosts[hosts->ht_master]->hd_hostpart;

	mp->m_ref++;
	mp->m_dst = hosts->ht_hosts[hosts->ht_master]->hd_hostpart | TIDPVMD;
	mp->m_cod = DM_DB;
	mp->m_wid = wp->wa_wid;
	sendmessage(mp);
	return 0;
}


