
/*
 *         PVM version 3.3:  Parallel Virtual Machine System
 *               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 version 3 was funded in part by the U.S. Department of Energy,
 * the National Science Foundation and the State of Tennessee.
 */

/*
 *	cmds.c
 *
 *	PVM console commands.
 *
$Log: cmds.c,v $
 * Revision 1.6  1995/07/03  18:59:51  manchek
 * help text
 *
 * Revision 1.5  1995/07/03  18:57:29  manchek
 * added ps 's' flag, tickle help 100.
 * ps doesn't print tasks with tid 0 unless -x flag used
 *
 * Revision 1.4  1995/05/17  15:26:17  manchek
 * added "return 0" to lots of commands.
 * added idump command
 *
 * Revision 1.3  1994/10/15  18:38:56  manchek
 * updated task flags and ps list headers
 *
 * Revision 1.2  1994/06/03  20:01:51  manchek
 * version 3.3.0
 *
 * Revision 1.1  1993/08/30  23:30:32  manchek
 * Initial revision
 *
 */

#include <stdio.h>
#include <fcntl.h>
#ifdef	SYSVSTR
#include <string.h>
#else
#include <strings.h>
#endif
#include <ctype.h>
#include <signal.h>
#include <pvm3.h>
#include <pvmtev.h>
#include "cmd.h"
#include "myalloc.h"
#include "../src/listmac.h"
#include "../src/bfunc.h"
#include "job.h"
#include "tevfmt.h"

#define	PVMERRMSG(n)	((n) <= 0 && (n) > -pvm_nerr \
						? pvm_errlist[-(n)] : "Unknown Error")

extern char *getenv();

extern char **environ;

extern char *pvm_errlist[];
extern int pvm_nerr;

int add_cmd();
int alias_cmd();
int conf_cmd();
int delete_cmd();
int echo_cmd();
int halt_cmd();
int help_cmd();
int id_cmd();
int idump_cmd();
int insert_cmd();
int jobs_cmd();
int kill_cmd();
int lookup_cmd();
int mstat_cmd();
int quit_cmd();
int pstat_cmd();
int remove_cmd();
int reset_cmd();
int setenv_cmd();
int sig_cmd();
int spawn_cmd();
int start_cmd();
int tasks_cmd();
int tickle_cmd();
int trace_cmd();
int unalias_cmd();
int version_cmd();

extern struct alias *aliases;			/* from cons.c */
extern struct pvmhostinfo *hostlist;	/* from cons.c */
extern struct job *joblist;				/* from job.c */
extern int mytid;						/* from cons.c */
extern int narchs;						/* from cons.c */
extern int nextjob;						/* from cons.c */
extern int nhosts;						/* from cons.c */
extern struct tevfmt tev_formats[];		/* from tevfmt.c */


struct cmdsw commands[] = {
	{ "add",     2, 0, add_cmd },
	{ "alias",   1, 0, alias_cmd },
	{ "conf",    1, 1, conf_cmd },
	{ "delete",  2, 0, delete_cmd },
	{ "echo",    1, 0, echo_cmd },
	{ "halt",    1, 1, halt_cmd },
	{ "help",    1, 2, help_cmd },
	{ "id",      1, 1, id_cmd },
	{ "idump",   1, 2, idump_cmd },
	{ "insert",  4, 4, insert_cmd },
	{ "jobs",    1, 2, jobs_cmd },
	{ "kill",    2, 0, kill_cmd },
	{ "lookup",  3, 3, lookup_cmd },
	{ "mstat",   2, 0, mstat_cmd },
	{ "ps",      1, 2, tasks_cmd },
	{ "pstat",   2, 0, pstat_cmd },
	{ "quit",    1, 1, quit_cmd },
	{ "remove",  3, 3, remove_cmd },
	{ "reset",   1, 1, reset_cmd },
	{ "setenv",  1, 0, setenv_cmd },
	{ "sig",     3, 0, sig_cmd },
	{ "spawn",   2, 0, spawn_cmd },
	{ "trace",   1, 0, trace_cmd },
	{ "tickle",  2, 11, tickle_cmd },
	{ "unalias", 2, 0, unalias_cmd },
	{ "version", 1, 1, version_cmd },
	{ 0, 0, 0, 0 }
};

static char rcsid[] = "$Id: cmds.c,v 1.6 1995/07/03 18:59:51 manchek Exp $";


freealias(ap)
	struct alias *ap;
{
	LISTDELETE(ap, a_link, a_rlink);
	MY_FREE(ap->a_name);
	while (ap->a_num-- > 0)
		MY_FREE(ap->a_args[ap->a_num]);
	MY_FREE(ap->a_args);
	MY_FREE(ap);
	return 0;
}


struct alias *
newalias(name, num, args)
	char *name;
	int num;
	char **args;
{
	struct alias *ap, *ap2;

	ap = TALLOC(1, struct alias, "alias");
	ap->a_name = STRALLOC(name);
	ap->a_args = TALLOC(num + 1, char *, "aargs");
	ap->a_num = num;
	while (num-- > 0)
		ap->a_args[num] = STRALLOC(args[num]);
	for (ap2 = aliases->a_link; ap2 != aliases; ap2 = ap2->a_link)
		if (strcmp(ap2->a_name, name) > 0)
			break;
	LISTPUTBEFORE(ap2, ap, a_link, a_rlink);
	return ap;
}


struct alias *
findalias(name)
	char *name;
{
	struct alias *ap;

	for (ap = aliases->a_link; ap != aliases; ap = ap->a_link)
		if (!strcmp(ap->a_name, name))
			return ap;
	return (struct alias*)0;
}


docmd(cmd)
	char *cmd;
{
	char *p;
	struct cmdsw *csp;
	struct alias *ap;
	int i;
	int ac;
	char *av[128];
	int self;

	/*
	* parse command
	*/

	ac = sizeof(av)/sizeof(av[0]) - 1;
	if (acav(cmd, &ac, av)) {
		fputs("command too long\n", stdout);
		return 0;
	}
	if (!ac)
		return 0;

	/*
	* resolve aliases
	*/

	for (ap = aliases->a_link; ap != aliases; ap = ap->a_link)
		ap->a_flag = 0;

	while (ap = findalias(av[0])) {
		if (ap->a_flag) {
			printf("alias loop\n");
			return 0;
		}
		ap->a_flag = 1;
		if (ap->a_num > 1) {
			if (ac + ap->a_num > sizeof(av)/sizeof(av[0])) {
				fputs("command too long\n", stdout);
				return 0;
			}
			for (i = ac; --i > 0; )
				av[i + ap->a_num - 1] = av[i];
			ac += ap->a_num - 1;
		}
		self = !strcmp(av[0], ap->a_args[0]);
		for (i = ap->a_num; i-- > 0; )
			av[i] = ap->a_args[i];
		if (self)
			break;
	}

	/*
	* find command and call it
	*/

	av[ac] = 0;
	p = av[0];
	for (csp = commands; csp->cmd; csp++) {
		if (!strcmp(csp->cmd, p)) {
			if (ac >= csp->a1 && (ac <= csp->a2 || !csp->a2))
				(csp->fun)(ac, av);
			else
				printf("%s: wrong #args\n", p);
			break;
		}
	}
	if (!csp->cmd)
		printf("%s: not found\n", p);
	return 0;
}


/****************
 **  Commands  **
 **            **
 ****************/

add_cmd(ac, av)
	int ac;
	char **av;
{
	int cc;
	int *sv;
	int i;

	av++;
	ac--;
	sv = TALLOC(ac, int, "int");
	if ((cc = pvm_addhosts(av, ac, sv)) >= 0) {
		if (cc > 0)
			pvm_recv(-1, 0);	/* waste the notify message */

		printf("%d successful\n", cc);
		fputs("                    HOST     DTID\n", stdout);
		for (i = 0; i < ac; i++)
			if ((cc = sv[i]) < 0)
				printf("%24s %8s\n", av[i], PVMERRMSG(cc));
			else
				printf("%24s %8x\n", av[i], cc);
	}
	MY_FREE(sv);
	pvm_config(&nhosts, &narchs, &hostlist);
	return 0;
}


alias_cmd(ac, av)
	int ac;
	char **av;
{
	struct alias *ap;
	char *p;
	int i;

	if (ac < 3) {
		for (ap = aliases->a_link; ap != aliases; ap = ap->a_link) {
			fputs(ap->a_name, stdout);
			p = "\t";
			for (i = 0; i < ap->a_num; i++) {
				printf("%s%s", p, ap->a_args[i]);
				p = " ";
			}
			fputs("\n", stdout);
		}

	} else {
		if (ap = findalias(av[1]))
			freealias(ap);
		newalias(av[1], ac - 2, av + 2);
	}
	return 0;
}


conf_cmd(ac, av)
	int ac;
	char **av;
{
	int i;

	ac = ac;
	av = av;
	if (!pvm_config(&nhosts, &narchs, &hostlist)) {
		printf("%d host%s, %d data format%s\n",
			nhosts, (nhosts > 1 ? "s" : ""), narchs, (narchs > 1 ? "s" : ""));
		fputs("                    HOST     DTID     ARCH   SPEED\n", stdout);
		for (i = 0; i < nhosts; i++)
			printf("%24s %8x %8s%8d\n",
					hostlist[i].hi_name,
					hostlist[i].hi_tid,
					hostlist[i].hi_arch,
					hostlist[i].hi_speed);
	}
	return 0;
}


delete_cmd(ac, av)
	int ac;
	char **av;
{
	int cc;
	int i;
	int *sv;

	av++;
	ac--;
	sv = TALLOC(ac, int, "int");
	if ((cc = pvm_delhosts(av, ac, sv)) >= 0) {
		printf("%d successful\n", cc);
		fputs("                    HOST  STATUS\n", stdout);
		for (i = 0; i < ac; i++) {
			printf("%24s  ", av[i]);
			if ((cc = sv[i]) < 0)
				printf("%8s\n", PVMERRMSG(cc));
			else
				printf("deleted\n");
		}
	}
	MY_FREE(sv);
	return 0;
}


echo_cmd(ac, av)
	int ac;
	char **av;
{
	int i;
	char *p = "";

	for (i = 1; i < ac; i++) {
		printf("%s%s", p, av[i]);
		p = " ";
	}
	printf("\n");
	return 0;
}


halt_cmd(ac, av)
	int ac;
	char **av;
{
	ac = ac;
	av = av;
	if (!pvm_halt()) {
		pvmendtask();
		exit(0);
	}
	return 0;
}


static char *helptx[] = {

	"add ADD    - Add hosts to virtual machine",
	"add Syntax:  add hostname ...",

	"alias ALIAS  - Define/list command aliases",
	"alias Syntax:  alias [name command args ...]",

	"conf CONF   - List virtual machine configuration",
	"conf Syntax:  conf",
	"conf Output fields:",
	"conf   HOST    host name",
	"conf   DTID    tid base of pvmd",
	"conf   ARCH    xhost architecture",
	"conf   SPEED   host relative speed",

	"delete DELETE - Delete hosts from virtual machine",
	"delete Syntax:  delete hostname ...",

	"echo ECHO   - Echo arguments",
	"echo Syntax:  echo [ arg ... ]",

	"halt HALT   - Stop pvmds",
	"halt Syntax:  halt",

	"help HELP   - Print helpful information about a command",
	"help Syntax:  help [ command ]",

	"id ID     - Print console task id",
	"id Syntax:  id",

	"idump-IDUMP     - Call i_dump to display heap contents",
	"idump-Syntax:  idump [ how ]",

	"insert-INSERT - Add entry to database",
	"insert-Syntax:  insert name index|-1 hexvalue",

	"jobs JOBS   - Display list of running jobs",
	"jobs Syntax:  jobs [ options ]",
	"jobs Options:  -l   give long listing",

	"kill KILL   - Terminate tasks",
	"kill Syntax:  kill [ options ] tid ...",
	"kill Options:  -c   kill children of tid",

	"lookup-LOOKUP - Find entry in database",
	"lookup-Syntax:  lookup name index|-1",

	"mstat MSTAT  - Show status of hosts",
	"mstat Syntax:  mstat name ...",

	"ps PS     - List tasks",
	"ps Syntax:  ps [ -axh ]",
	"ps Options:  -a       all hosts (default is local)",
	"ps           -hhost   specific host tid",
	"ps           -nhost   specific host name",
	"ps           -l       long (show process id)",
	"ps           -x       show all tasks (e.g. console and nulls)",
	"ps Output fields:",
	"ps   HOST    host name",
	"ps   TID     task id",
	"ps   PTID    parent task id",
	"ps   PID     task process id",
	"ps   FLAG    status",
	"ps   COMMAND executable name",
	"ps FLAG values:",
	"ps   f   task process is child of pvmd",
	"ps   c   task connected to pvmd",
	"ps   a   task waiting authorization",
	"ps   o   task connection being closed",
	"ps   s   task needs too many shared pages, is deadlocked",
	"ps   H   hoster task",
	"ps   R   resource manager task",
	"ps   T   tasker task",

	"pstat PSTAT  - Show status of tasks",
	"pstat Syntax:  pstat tid ...",

	"quit QUIT   - Exit console",
	"quit Syntax:  quit",

	"remove-REMOVE - Delete entry from database",
	"remove-Syntax:  remove name index",

	"reset RESET  - Kill all tasks",
	"reset Syntax:  reset",

	"setenv SETENV - Display or set environment variables",
	"setenv Syntax:  setenv [ name [ value ] ]",

	"sig SIG    - Send signal to task",
	"sig Syntax:  sig signum task ...",

	"spawn SPAWN  - Spawn task",
	"spawn Syntax:  spawn [ options ] file [ arg ... ]",
	"spawn Options:  -(count)  number of tasks, default is 1",
	"spawn           -(host)   spawn on host, default is any",
	"spawn           -(ARCH)   spawn on hosts of ARCH",
	"spawn           -?        enable debugging",
	"spawn           ->        redirect output of job to console",
	"spawn           ->(file)  redirect output of job to file",
	"spawn           ->>(file) append output of job to file",
	"spawn           -@        trace job, display output on console",
	"spawn           -@(file)  trace job, output to file",

	"tickle-TICKLE - Tickle pvmd",
	"tickle-Syntax:  tickle how [ arg ... ]",
	"tickle-How:",
	"tickle-  0   dump heap",
	"tickle-  1   dump host table",
	"tickle-  2   dump local task table",
	"tickle-  3   dump waitc list",
	"tickle-  4   dump class-name list",
	"tickle-  5   get debugmask",
	"tickle-  6   (mask) set debugmask",
	"tickle-  7   (num) set nopax",
	"tickle-  8   (dtid) trigger hostfail",
	"tickle-  9   (rst) dump pvmd statistics, clear if rst true",
	"tickle-  100 dump shared memory data structures",

	"trace TRACE  - Set/display trace event mask",
	"trace Syntax:  trace",
	"trace          trace [+] name ...",
	"trace          trace - name ...",
	"trace          trace [+] *",
	"trace          trace - *",

	"unalias UNALIAS - Undefine command alias",
	"unalias Syntax:  unalias name ...",

	"version VERSION - Show libpvm version",
	"version Syntax:  version",
	0
};


help_cmd(ac, av)
	int ac;
	char **av;
{
	char **p;
	char *topic;
	int l;
	struct cmdsw *csp;

	/* if not specified, topic = help */
	if (ac > 1)
		topic = av[1];
	else
		topic = "help";

	l = strlen(topic);

	/* search through messages for ones matching topic */
	for (p = helptx; *p; p++) {
		if (!strncmp(topic, *p, l) && ((*p)[l] == ' ' || (*p)[l] == '-'))
			printf("%s\n", (*p) + l + 1);
	}

	if (!strcmp(topic, "help")) {
		printf("Commands are:\n");
		for (csp = commands; csp->cmd; csp++) {
			l = strlen(csp->cmd);
			for (p = helptx; *p; p++)
				if (!strncmp(csp->cmd, *p, l) && (*p)[l] == ' ') {
					printf("  %s\n", (*p) + l + 1);
					break;
				}
		}
	}
	return 0;
}


id_cmd(ac, av)
	int ac;
	char **av;
{
	ac = ac;
	av = av;
	printf("t%x\n", mytid);
	return 0;
}


idump_cmd(ac, av)
	int ac;
	char **av;
{
	int how = 0;

	if (ac > 1)
		how = atoi(av[1]);
	i_dump(how);
	return 0;
}


insert_cmd(ac, av)
	int ac;
	char **av;
{
	int cc;
	int data;

	ac = ac;
	data = axtoi(av[3]);
	if ((cc = pvm_insert(av[1], axtoi(av[2]), data)) >= 0)
		printf("%s, %d = 0x%08x\n", av[1], cc, data);
	else
		if (cc == PvmDupEntry)
			printf("already exists\n");
	return 0;
}


jobs_cmd(ac, av)
	int ac;
	char **av;
{
	struct job *jp;
	struct obuf *op;
	int l = 0;
	int ntask;
	struct pvmtaskinfo *tip;

	if (ac > 1 && !strcmp(av[1], "-l")) {
		l = 1;
		print_task_hdr(0);
		if (!hostlist)
			pvm_config(&nhosts, &narchs, &hostlist);
	}

	for (jp = joblist->j_link; jp != joblist; jp = jp->j_link) {
		printf("%c%d:%s", (jp->j_flag & JOB_TRACE ? 'T' : ' '),
				jp->j_jid, (l ? "\n" : ""));
		if (jp->j_obufs) {
			for (op = jp->j_obufs->o_link; op != jp->j_obufs; op = op->o_link)
				if (l) {
					if (!pvm_tasks(op->o_tid, &ntask, &tip) && ntask == 1)
						print_task_rec(&tip[0], 0);
					else
						printf(" t%x", op->o_tid);
					printf("\n");
				} else
					printf(" t%x", op->o_tid);
			if (!l)
				printf("\n");
		}
	}
	return 0;
}


kill_cmd(ac, av)
	int ac;
	char **av;
{
	int i;
	int tid, tid2;
	char *p;
	int host = 0;
	int cflg = 0;
	struct pvmtaskinfo *tip;
	int ntask;

	if (ac > 1 && av[1][0] == '-') {
		ac--;
		for (p = *++av; *p; p++)
			switch (*p) {

			case 'c':
				cflg = 1;
				break;

			case '-':
				break;

			default:
				printf("unknown flag -%c\n", *p);
				break;
			}
	}

	if (ac < 2) {
		fputs("incorrect arg count\n", stdout);
		return 1;
	}

	if (cflg && pvm_tasks(host, &ntask, &tip) < 0)
		return 1;

	while (ac > 1) {
		ac--;
		tid = tidtoi(*++av);

		if (cflg) {
			for (i = 0; i < ntask; i++)
				if (tip[i].ti_ptid == tid) {
					tid2 = tip[i].ti_tid;
					if (tid2 != mytid)
						pvm_kill(tid2);
				}

		} else {
			if (tid == mytid)
				printf("t%x: that's me.\n", tid);
			else
				pvm_kill(tid);
		}
	}
	return 0;
}


lookup_cmd(ac, av)
	int ac;
	char **av;
{
	int cc, data = 0;

	ac = ac;
	if ((cc = pvm_lookup(av[1], axtoi(av[2]), &data)) >= 0)
		printf("%s, %d = 0x%08x\n", av[1], cc, data);
	else
		if (cc == PvmNoEntry)
			printf("no such entry\n");
	return 0;
}


mstat_cmd(ac, av)
	int ac;
	char **av;
{
	int i;
	int cc;

	for (i = 1; i < ac; i++) {
		cc = pvm_mstat(av[i]);
		printf("%24s  %s\n", av[i], (cc < 0 ? PVMERRMSG(cc) : "ok"));
	}
	return 0;
}


pstat_cmd(ac, av)
	int ac;
	char **av;
{
	int i;
	int tid;
	int cc;

	for (i = 1; i < ac; i++) {
		tid = tidtoi(av[i]);
		cc = pvm_pstat(tid);
		printf("t%8x  %s\n", tid, (cc < 0 ? PVMERRMSG(cc) : "run"));
	}
	return 0;
}


quit_cmd()
{
	printf("\n");
	if (mytid > 0) {
		pvm_exit();
		printf("pvmd still running.\n");
	}
	exit(0);
}


remove_cmd(ac, av)
	int ac;
	char **av;
{
	int cc;

	ac = ac;
	if ((cc = pvm_delete(av[1], axtoi(av[2]))) > 0)
		printf("deleted %s, %d\n", av[1], cc);
	else
		if (cc == PvmNoEntry)
			printf("no such entry\n");
	return 0;
}


reset_cmd(ac, av)
	int ac;
	char **av;
{
	struct pvmtaskinfo *tip;
	int ntask;
	int i;
	int j;

	ac = ac;
	av = av;
	if (!pvm_tasks(0, &ntask, &tip))
		for (i = 0; i < ntask; i++)
			if (tip[i].ti_tid && tip[i].ti_tid != mytid)
				pvm_kill(tip[i].ti_tid);

	/* XXX this is gnasty... */
	while ((i = pvm_lookup("pvmgs", -1, &j)) >= 0)
		pvm_delete("pvmgs", i);
	return 0;
}


setenv_cmd(ac, av)
	int ac;
	char **av;
{
	char **pp;
	char *p;
	char *sep;
	int n;
	int i;

	switch (ac) {

	case 1:
		for (pp = environ; *pp; pp++)
			printf("%s\n", *pp);
		break;

	case 2:
		if (p = getenv(av[1]))
			printf("%s\n", p);
		break;

	default:
		if (ac > 2) {
			n = 0;
			for (i = ac; i-- > 1; )
				n += strlen(av[i]) + 1;
			p = TALLOC(n, char, "env");
			strcpy(p, av[1]);
			strcat(p, "=");
			sep = "";
			for (i = 2; i < ac; i++) {
				strcat(p, sep);
				strcat(p, av[i]);
				sep = " ";
			}
			pvmputenv(p);
		}
		break;
	}
	return 0;
}


sig_cmd(ac, av)
	int ac;
	char **av;
{
	int i;
	int signum;
	int tid;

	signum = atoi(av[1]);
	for (i = 2; i < ac; i++) {
		tid = tidtoi(av[i]);
		pvm_sendsig(tid, signum);
	}
	return 0;
}


spawn_cmd(ac, av)
	int ac;
	char **av;
{
	int *tids = 0;
	char *where = 0;
	int flags = 0;
	int count = 1;
	int i;
	int oflg = 0;
	int tflg = 0;
	int app;
	char *ofn = 0;
	char *tfn = 0;
	struct job *jp, *jp2;

	while (av[1][0] == '-') {
		if (ac < 3) {
			fputs("incorrect arg count\n", stdout);
			return 1;
		}
		if (av[1][1] == '~') {
			flags |= PvmHostCompl;
			av[1]++;
		}
		if (av[1][1] == '.' || islower(av[1][1])) {
			where = av[1] + 1;
			flags |= PvmTaskHost;
		}
		if (isupper(av[1][1])) {
			where = av[1] + 1;
			flags |= PvmTaskArch;
		}
		if (av[1][1] == '?')
			flags |= PvmTaskDebug;
		if (isdigit(av[1][1]))
			count = atoi(av[1] + 1);
		if (av[1][1] == '>') {
			oflg = 1;
			app = 0;
			ofn = av[1] + 2;
			if (av[1][2] == '>') {
				app = 1;
				ofn++;
			}
			if (!*ofn)
				ofn = 0;
/*
			printf("%s to %s\n", (app ? "Append" : "Write"),
					(ofn ? ofn : "(console)"));
*/
		}
		if (av[1][1] == '@') {
			tflg = 1;
			tfn = av[1] + 2;
			if (!*tfn)
				tfn = 0;
		}
		av++;
		ac--;
	}
	if (oflg) {
		pvm_setopt(PvmOutputTid, mytid);
		pvm_setopt(PvmOutputCode, nextjob);
		jp = job_new(nextjob);
		printf("[%d]\n", nextjob++);
		if (ofn) {
			jp->j_ff = fopen(ofn, (app ? "a" : "w"));
			if (!jp->j_ff) {
				perror(ofn);
				job_free(jp);
				return 1;
			}
		}
	} else
		pvm_setopt(PvmOutputTid, 0);

	if (tflg) {
		pvm_setopt(PvmTraceTid, mytid);
		pvm_setopt(PvmTraceCode, nextjob);
		jp2 = job_new(nextjob);
		jp2->j_flag |= JOB_TRACE;
		printf("[%d]\n", nextjob++);
		if (tfn) {
			jp2->j_ff = fopen(tfn, "w");
			if (!jp2->j_ff) {
				perror(tfn);
				job_free(jp2);
				return 1;
			}
		}

	} else {
		pvm_setopt(PvmTraceTid, 0);
	}

	tids = TALLOC(count > 1 ? count : 1, int, "int");
	if ((i = pvm_spawn(av[1], &av[2], flags, where, count, tids)) >= 0) {
		if (oflg & !i)
			job_free(jp);
		if (tflg & !i)
			job_free(jp2);
		printf("%d successful\n", i);
		for (i = 0; i < count; i++)
			if (tids[i] < 0)
				printf("%s\n", PVMERRMSG(tids[i]));
			else
				printf("t%x\n", tids[i]);
	}
	MY_FREE(tids);
	return 0;
}


static char *tflgs[] = {
	0, "f", "c", "a", "o", "s", 0, 0,
	"R", "H", "T"
};

char *
task_flags(f)
	int f;
{
	static char buf[64];
	int bit, i;

	sprintf(buf, "%x/", f);
	i = sizeof(tflgs)/sizeof(tflgs[0]) - 1;
	bit = 1 << i;
	while (i >= 0) {
		if ((f & bit) && tflgs[i]) {
			strcat(buf, tflgs[i]);
			strcat(buf, ",");
		}
		bit /= 2;
		i--;
	}
	buf[strlen(buf) - 1] = 0;
	return buf;
}


print_task_hdr(lflg)
	int lflg;
{
	if (lflg)
		fputs("                    HOST      TID     PTID    PID   FLAG 0x COMMAND\n",
			stdout);
	else
		fputs("                    HOST      TID   FLAG 0x COMMAND\n",
			stdout);
	return 0;
}


print_task_rec(tip, lflg)
	struct pvmtaskinfo *tip;
	int lflg;
{
	struct pvmhostinfo *hip = 0;

	if (hostlist) {
		for (hip = hostlist + nhosts - 1; hip >= hostlist; hip--)
			if (hip->hi_tid == tip->ti_host)
				break;
		if (hip < hostlist)
			hip = 0;
	}
	if (hip)
		printf("%24s", hip->hi_name);
	else
		printf("%24x", tip->ti_host);
	if (tip->ti_tid == mytid)
		printf("   (cons)");
	else
		printf(" %8x", tip->ti_tid);
	if (lflg) {
		if (tip->ti_ptid == mytid)
			printf("   (cons)");
		else
			if (tip->ti_ptid)
				printf(" %8x", tip->ti_ptid);
			else
				printf("        -");
		printf(" %6d", tip->ti_pid);
	}
	printf(" %9s", task_flags(tip->ti_flag));
	printf(" %-12s", tip->ti_a_out[0] ? tip->ti_a_out : "-");
	return 0;
}


tasks_cmd(ac, av)
	int ac;
	char **av;
{
	struct pvmtaskinfo *tip;
	int ntask;
	int i;
	struct pvmhostinfo *hip = 0;
	int xflg = 0;
	char *p;
	int host = pvm_tidtohost(mytid);
	int lflg = 0;

	if (!hostlist)
		pvm_config(&nhosts, &narchs, &hostlist);

	if (ac > 1) {
		for (p = av[1]; *p; p++)
			switch (*p) {

			case 'n':
				p++;
				if (!strcmp(p, "."))
					pvm_tidtohost(mytid);
				else {
					for (hip = hostlist + nhosts; --hip >= hostlist; )
						if (!(strcmp(p, hip->hi_name))) {
							host = hip->hi_tid;
							break;
						}
					if (hip < hostlist)
						printf("unknown host %s\n", p);
				}
				while (*p)
					p++;
				p--;
				break;

			case 'h':
				host = tidtoi(++p);
				while (*p)
					p++;
				p--;
				break;

			case 'a':
				host = 0;
				break;

			case 'l':
				lflg = 1;
				break;

			case 'x':
				xflg = 1;
				break;

			case '-':
				break;

			default:
				printf("unknown flag -%c\n", *p);
				break;
			}
	}

	if (!pvm_tasks(host, &ntask, &tip)) {
		print_task_hdr(lflg);
		for (i = 0; i < ntask; i++) {
			if (!xflg && (tip[i].ti_tid == mytid || tip[i].ti_tid == 0))
				continue;
			print_task_rec(&tip[i], lflg);
			printf("\n");
		}
	}
	return 0;
}


tickle_cmd(ac, av)
	int ac;
	char **av;
{
	int nar;
	int arg[10];
	int i;

	ac--;
	av++;
	for (nar = 0; nar < ac; nar++)
		arg[nar] = axtoi(av[nar]);

	if (!pvm_tickle(nar, arg, &nar, arg)) {
		printf("(");
		for (i = 0; i < nar; i++)
			printf(" %d", arg[i]);
		printf(" )\n");
	}
	return 0;
}


int sortedtevs[] = {
	0,   154, 2,   4,   6,
	156, 8,   10,  12,  14,  16,  178, 18,
	20,  158, 22,  24,  26,  28,  160, 30,
	32,  162, 34,  36,  38,  40,  42,  44,
	46,  48,  50,  52,  54,  56,  164, 58,
	60,  62,  64,  66,  68,  70,  72,  76,
	80,  84,  74,  78,  82,  166, 86,  168,
	88,  90,  92,  170, 142, 144, 146, 180,
	94,  96,  172, 98,  100, 102, 174, 104,
	106, 108, 110, 112, 114, 176, 116, 118,
	120, 122, 124, 126, 130, 134, 138, 128,
	132, 136, 140, 150, 148, 152,
};

printtm(who)
	int who;
{
	int maxt = sizeof(sortedtevs)/sizeof(sortedtevs[0]);
/*
	int maxt = TEV_MAX / 2 + 1;
*/
	int h[4];
	int i[3];
	int j;
	int b;
	int d;
	int l;
	char buf[64];
	Pvmtmask tm;

	pvm_gettmask(who, tm);

	i[0] = h[0] = 0;
	i[1] = h[1] = maxt / 3;
	i[2] = h[2] = (maxt * 2) / 3;
	h[3] = maxt;

	do {
		d = 0;
		for (j = 0; j < 3; j++) {
			if (i[j] < h[j + 1]) {
				b = TEV_CHECK_MASK(tm, sortedtevs[i[j]]);
				strcpy(buf, tev_formats[sortedtevs[i[j]]].name);
/*
				b = TEV_CHECK_MASK(tm, i[j] * 2);
				strcpy(buf, tev_formats[i[j] * 2].name);
*/
				l = strlen(buf) - 1;
				if (buf[l] == '0')
					buf[l] = 0;
				printf(" %c %-22s", (b ? '*' : ' '), buf);
				i[j]++;
			} else
				printf("                         ");
			if (i[j] >= h[j + 1])
				d++;
		}
		printf("\n");
	} while (d != 3);
	return 0;
}


trace_cmd(ac, av)
	int ac;
	char **av;
{
	int i;
	int onoff = 1;
	int e;
	int l;
	char buf[64];
	Pvmtmask tm;

	if (ac == 1) {
		printtm(PvmTaskChild);
		return 0;
	}

	pvm_gettmask(PvmTaskChild, tm);

	for (i = 1; i < ac; i++) {
		if (!strcmp(av[i], "+")) {
			onoff = 1;
		} else if (!strcmp(av[i], "-")) {
			onoff = 0;
		} else if (!strcmp(av[i], "*")) {
			for (e = TEV_FIRST; e <= TEV_MAX; e += 2)
				if (onoff)
					TEV_SET_MASK(tm, e);
				else
					TEV_UNSET_MASK(tm, e);
		} else {
			if (strncmp(av[i], "pvm_", 4))
				strcpy(buf, "pvm_");
			else
				buf[0] = 0;
			strcat(buf, av[i]);
			l = strlen(buf);
/*
			printf("<%s>\n", buf);
*/
			for (e = TEV_FIRST; e <= TEV_MAX; e += 2)
				if (!strncmp(tev_formats[e].name, buf, l)
				|| !strncmp(tev_formats[e].name, av[i], l))
					break;
			if (e <= TEV_MAX) {
				if (onoff)
					TEV_SET_MASK(tm, e);
				else
					TEV_UNSET_MASK(tm, e);

			} else {
				printf("no such event \"%s\"\n", av[i]);
			}
		}
	}

	pvm_settmask(PvmTaskChild, tm);

	return 0;
}


unalias_cmd(ac, av)
	int ac;
	char **av;
{
	struct alias *ap;
	int i;

	for (i = 1; i < ac; i++)
		if (ap = findalias(av[i]))
			freealias(ap);
	return 0;
}


version_cmd(ac, av)
	int ac;
	char **av;
{
	ac = ac;
	av = av;
	printf("%s\n", pvm_version());
	return 0;
}


