
static char rcsid[] =
	"$Id: pvmcruft.c,v 1.38 2001/09/27 21:25:10 pvmsrc Exp $";

/*
 *         PVM version 3.4:  Parallel Virtual Machine System
 *               University of Tennessee, Knoxville TN.
 *           Oak Ridge National Laboratory, Oak Ridge TN.
 *                   Emory University, Atlanta GA.
 *      Authors:  J. J. Dongarra, G. E. Fagg, M. Fischer
 *          G. A. Geist, J. A. Kohl, R. J. Manchek, P. Mucci,
 *         P. M. Papadopoulos, S. L. Scott, and V. S. Sunderam
 *                   (C) 1997 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.
 */

/*
 *	cruft.c
 *
 *	Missing links and other wonk.
 *
 * $Log: pvmcruft.c,v $
 * Revision 1.38  2001/09/27 21:25:10  pvmsrc
 * BEOSCYLD port.
 * 	- submitted by Joe Vitale <vitale@scyld.com>.
 * 	(we renamed it from BEOWULF to BEOSCYLD, but it's his port... :-)
 * (Spanker=kohl)
 *
 * Revision 1.37  2001/09/26 22:04:20  pvmsrc
 * Modified temp file prefix in pvmtmpnam().
 * 	- tmp* -> pvmtmp*.
 * (Spanker=kohl)
 *
 * Revision 1.36  2001/09/26 21:22:59  pvmsrc
 * Append VMID string to PVMD socket file name in pvmdsockfile().
 * 	- if set in PVM_VMID env var.
 * 	- not restricted to be integer as in SGI, can be arbitrary
 * 		string...  heh, heh...  :-}
 * (Spanker=kohl)
 *
 * Revision 1.35  2001/09/25 21:21:02  pvmsrc
 * Yanked "char *pvmgettmp();" decl - now in pvm3.h...
 * (Spanker=kohl)
 *
 * Revision 1.34  2001/02/09 15:23:47  pvmsrc
 * Minor tweak to pvmgettmp() for Markus.
 * 	- check %PVM_TMP% & registry before resorting to %TEMP%.
 * (Spanker=kohl)
 *
 * Revision 1.33  2001/02/07 23:15:51  pvmsrc
 * 2nd Half of CYGWIN Check-ins...
 * (Spanker=kohl)
 *
 * Revision 1.32  2001/02/02 14:50:26  pvmsrc
 * Win32 fixes & additions.
 * (Spanker=kohl)
 *
 * Revision 1.31  2000/03/29 20:04:12  pvmsrc
 * Added prototype for pvm_readregistry() routine.
 * (Spanker=sscott)
 *
 * Revision 1.30  2000/02/17 23:12:15  pvmsrc
 * *** Changes for new BEOLIN port ***
 * 	- MPP-like, similar to SP2, etc.
 * 	- submitted by Paul Springer <pls@smokeymt.jpl.nasa.gov>.
 * 	- format-checked & cleaned up by Jeembo...  :-)
 * (Spanker=kohl)
 *
 * Revision 1.29  2000/02/16 23:30:24  pvmsrc
 * Added back in read_pvmregistry() calls.
 * 	- for PVM_ROOT and PVM_TMP, if env vars not present on WIN32.
 * 	- couldn't find reference to PVM_ARCH anywhere, Markus?!
 * (Spanker=kohl)
 *
 * Revision 1.28  2000/02/14 20:32:11  pvmsrc
 * Added new pvmgetrsh() routine
 * 	- checks for PVM_RSH or else uses old RSHCOMMAND interface.
 * 	- one-stop shopping for rsh...
 * (Spanker=kohl)
 *
 * Revision 1.27  2000/02/10 20:45:15  pvmsrc
 * Replaced hard-coded /tmp usage.
 * 	- modified pvmgettmp() to try $PVM_TMP env var for Unix, too.
 * 	- use for TDSOCKNAME / TDSOCKNAME_CSPP in pvmdsockfile().
 * Also in pvmdsockfile():
 * 	- fixed WIN32 chdir() problem, cleaned up string manip.
 * (Spanker=kohl)
 *
 * Revision 1.26  1999/12/13 18:13:55  pvmsrc
 * Moved pvmmatchstring() here from msgbox.c.
 * 	- for use by pvmtester stuff...
 * (Spanker=kohl)
 *
 * Revision 1.25  1999/07/08 19:00:06  kohl
 * Fixed "Log" keyword placement.
 * 	- indent with " * " for new CVS.
 *
 * Revision 1.24  1999/03/16  16:29:56  pvmsrc
 * Oops...  Fixed typing problem for WIN32 in pvmsleep()...
 * (Spanker=kohl)
 *
 * Revision 1.23  1999/03/15  19:06:32  pvmsrc
 * Added new pvmsleep() routine.
 * 	- Unix / Win32, C / Fortran compat...
 * 	- invokes Sleep() on WIN32 (with secs * 1000 :-)~
 * (Spanker=kohl)
 *
 * Revision 1.22  1999/03/05  17:21:15  pvmsrc
 * improved to work with registry/environment on NT/win95-98
 * and devstudio 5/6
 * (Spanker=sscott)
 *
 * Revision 1.21  1999/02/09  23:06:53  pvmsrc
 * Cleaned up temp file stuff (mainly for WIN32).
 * 	- extracted new pvmgettmp() routine for returning tmp directory.
 * (Spanker=kohl)
 *
 * Revision 1.20  1999/02/09  20:10:10  pvmsrc
 * Cleaned up pvmgetroot().
 * 	- some arch cases not properly executed.
 * 	- for WIN32, if read_pvmregistry() fails, give the env var a shot.
 * (Spanker=kohl)
 *
 * Revision 1.19  1998/10/02  15:44:04  pvmsrc
 * Single source code merge of Win32 and Unix code.
 * (Spanker=sscott)
 *
 * Revision 1.18  1998/09/23  15:23:42  pvmsrc
 * 	changes to use WIN32 registry as per Markus.
 * 	ifdef in pvmd.c::colonsep() to include WIN32 and OS2
 * (Spanker=phil)
 *
 * Revision 1.17  1997/12/01  19:20:48  pvmsrc
 * Replaced #ifdef IMA_OS2 fd_set declarations:
 * 	- new #ifdef FDSETNOTSTRUCT.
 * 	- choose between "fd_set foo" and "struct fd_set foo"...
 * (Spanker=kohl)
 *
 * Revision 1.16  1997/11/04  23:19:25  pvmsrc
 * Cleaned up fd_set stuff (hopefully).
 * (Spanker=kohl)
 *
 * Revision 1.15  1997/06/27  19:19:34  pvmsrc
 * Integrated WIN32 changes.
 *
 * Revision 1.14  1997/05/27  14:47:04  pvmsrc
 * New message type added: TM_SHMCONN.
 *
 * Revision 1.13  1997/04/30  21:26:10  pvmsrc
 * SGI Compiler Warning Cleanup.
 *
 * Revision 1.12  1997/04/21  14:58:23  pvmsrc
 * Changed #ifdefs that checked IMA_RS6K,IMA_SP2MPI & IMA_AIX46K
 * 	to see if select.h was needed into single define NEEDSSELECTH.
 * 	New archs need to set this in conf/
 *
 * Revision 1.11  1997/04/08  19:41:41  pvmsrc
 * Added "DM_RESET", "DM_RESETACK" to taglist2...
 *
 * Revision 1.10  1997/04/08  17:23:33  pvmsrc
 * Damn, Damn, Damn...
 * 	- pvmreset() must be in library side only, pvmds use different
 * 		message buffer mgt, packing and sending routines...  :-Q
 *
 * Revision 1.9  1997/04/08  17:08:17  pvmsrc
 * Oops...  need to pass in caller's tid, lest they be reset, too...
 *
 * Revision 1.8  1997/04/07  20:51:18  pvmsrc
 * Added pvmreset() routine.
 * 	- for use by "pvm" console / XPVM, etc.
 * 	- wipe out tasks, and tell master pvmd to clean up mboxes.
 *
 * Revision 1.7  1997/03/06  21:50:23  pvmsrc
 * Yanked out #includes for <netinet/in.h> and <netinet/tcp.h>.
 * 	- dups with lpvm.h #includes...
 *
 * Revision 1.6  1997/03/06  21:09:47  pvmsrc
 * 	use dclock() for pvmgetclock()
 *
 * Revision 1.5  1997/01/28  19:27:00  pvmsrc
 * New Copyright Notice & Authors.
 *
 * Revision 1.4  1996/10/25  13:57:57  pvmsrc
 * Replaced old #includes for protocol headers:
 * 	- <pvmsdpro.h>, "ddpro.h", "tdpro.h"
 * With #include of new combined header:
 * 	- <pvmproto.h>
 *
 * Revision 1.3  1996/10/24  21:30:39  pvmsrc
 * Moved #include "global.h" to below other #includes for typing.
 * Added #include of new "lpvm.h" & replaced explicit externs.
 * Added new trace control constants:
 * 	- TM_TRACER, for pvm_reg_tracer() messages.
 * 	- TC_SETTRACE, TC_SETTRCBUF, TC_SETTRCOPT, for on-the-fly setopt
 * 		control message / handlers.
 *
 * Revision 1.2  1996/09/27  13:17:09  pvmsrc
 * Added special #include of <sys/select.h> for old RS6K (AIX 3.2):
 * 	- fd_set declarations not found in <sys/time.h> on those systems,
 * 		or evidently in SP2MPI, need <sys/select.h>.
 * 	- similar fixes already existed in hoster.c, lpvm.c and pvmd.c.
 *
 * Revision 1.1  1996/09/23  23:44:29  pvmsrc
 * Initial revision
 *
 * Revision 1.10  1995/11/02  16:21:34  manchek
 * made pvmhdump write to pvm log.
 * new switch NEEDSFFS for ffs.
 * added pvmcopyenv, pvmenvinsert and pvmfreeenv
 *
 * Revision 1.9  1995/07/19  21:26:27  manchek
 * new function pvmnametag converts message tag to name or decimal string
 *
 * Revision 1.8  1995/07/18  17:01:35  manchek
 * added function pvmcrcappend to generate 32-bit crcs
 *
 * Revision 1.7  1995/05/24  16:58:42  manchek
 * whacked doofy coding style in pvmhdump
 *
 * Revision 1.6  1995/05/17  16:27:52  manchek
 * added support for CSPP subcomplexes (/tmp file naming)
 *
 * Revision 1.5  1994/11/07  22:48:09  manchek
 * added tweaks for SX3, SCO
 *
 * Revision 1.4  1994/07/18  19:21:05  manchek
 * pvmputenv() matched variable name substrings (need to compare = too)
 *
 * Revision 1.3  1994/06/03  20:38:22  manchek
 * version 3.3.0
 *
 * Revision 1.2  1993/10/04  20:28:27  manchek
 * added pvmdsockfile() function
 *
 * Revision 1.1  1993/08/30  23:26:50  manchek
 * Initial revision
 *
 */

#include <stdio.h>
#ifdef IMA_TITN
#include <bsd/sys/types.h>
#else
#include <sys/types.h>
#endif
#ifndef WIN32
#include <sys/time.h>
#include <sys/socket.h>
#else 
#include <time.h>
#endif
#ifdef NEEDSSELECTH
#include <sys/select.h>
#endif
#include <sys/stat.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 <pvm3.h>
#include <pvmproto.h>
#include "pvmalloc.h"
#include "lpvm.h"
#include "global.h"

#ifdef WIN32
char *read_pvmregistry	__ProtoGlarp__(( const char * ));
#endif

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

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

char *getenv();

#ifdef WIN32
extern char *username;
#endif

#ifdef IMA_BEOSCYLD
#include <sys/bproc.h>
#endif


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


int
pvmput32(p, n)
	char *p;
	int n;
{
	*p++ = n >> 24;
	*p++ = n >> 16;
	*p++ = n >> 8;
	*p = n;
	return 0;
}


int
pvmput16(p, n)
	char *p;
	int n;
{
	*p++ = n >> 8;
	*p = n;
	return 0;
}


int
pvmput8(p, n)
	char *p;
	int n;
{
	*p = n;
	return 0;
}


int
pvmget32(p)
	char *p;
{
	return ((0xff & p[0]) << 24)
	+ ((0xff & p[1]) << 16)
	+ ((0xff & p[2]) << 8)
	+ (0xff & p[3]);
}


int
pvmget16(p)
	char *p;
{
	return ((0xff & p[0]) << 8) + (0xff & p[1]);
}


int
pvmget8(p)
	char *p;
{
	return 0xff & *p;
}


#ifdef NOGETDTBLSIZ

#ifdef IMA_TITN

int
getdtablesize()
{
	return 64;
}

#else	/*IMA_TITN*/

#ifdef IMA_SX3

int
getdtablesize()
{
	return 64*8;
}

#else /*IMA_SX3*/

#ifdef WIN32
int
getdtablesize()
{
	return 64;
}
#else
#include <unistd.h>
int
getdtablesize()
{
	return sysconf(_SC_OPEN_MAX);
}
#endif

#endif /*IMA_SX3*/

#endif	/*IMA_TITN*/

#endif /*NOGETDTBLSIZ*/

#ifdef	NEEDSFFS
int
ffs(x)
	int x;
{
	int n = 1, m = 1;

	if (!x)
		return 0;
	while (!(x & m)) {
		m += m;
		n++;
	}
	return n;
}

#endif

#ifndef IMA_WIN32_WATCOM
extern char **environ;
#endif

/*	pvmputenv()
*
*	Replacement for systems that don't have putenv.
*	Tries to be conservative, i.e. doesn't assume that environ
*	points to malloc'd space.
*
*	String must never be freed...
*/

int
pvmputenv(s)
	char *s;	/* string of form "NAME=Value" */
{
	char *p;					/* gp */
	int l;						/* length of env var name or env */
	static char **myen = 0;		/* last environ that i created */
	char **newen;				/* new env if extending */
	char **ep, **ep2;			/* gp */

	/* sanity check the string and locate the '=' */

	if (!s)
		return -1;
	for (p = s; *p && *p != '='; p++);
	if (p == s || !*p)
		return -1;
	l = p - s + 1;	/* must include '=' */

	/* search current environ for name */

	for (ep = environ; *ep; ep++)
		if (!strncmp(*ep, s, l))
			break;

	if (*ep) {	/* already there, replace it */
		*ep = s;

	} else {	/* not there, must extend environ */

	/* length needed is? */

		for (l = 2, ep = environ; *ep; l++, ep++);

		if (environ == myen) {	/* mine, reallocate */
			if (!(newen = (char**)realloc((char *)environ,
					(unsigned)(l * sizeof(char*)))))
				return -1;

		} else {	/* not mine, alloc new and copy */
			if (!(newen = (char**)malloc((unsigned)(l * sizeof(char*)))))
				return -1;
			for (ep = environ, ep2 = newen; *ep2++ = *ep++; );
		}
		newen[l - 2] = s;
		newen[l - 1] = 0;
		environ = myen = newen;
	}
	return 0;
}


/*	pvmcopyenv()
*
*	Make copy of an environment list (null-terminated vector of strings).
*	This is just like environ, but we own all the strings.
*	Can also be used to create an empty list, by passing a null pointer.
*
*	Returns pointer to environment or NULL if malloc fails.
*/

char **
pvmcopyenv(anyep)
	char **anyep;		/* old environment to copy, or NULL */
{
	char **newep;
	int i, n;

	if (anyep) {
		for (n = 0; anyep[n]; n++) ;
		if (newep = TALLOC(n + 1, char *, "env")) {
			newep[n] = 0;
			for (i = 0; i < n; i++) {
				if (!(newep[i] = STRALLOC(anyep[i]))) {
					while (i-- > 0)
						PVM_FREE(newep[i]);
					PVM_FREE(newep);
					newep = 0;
					break;
				}
			}
		}

	} else {
		if (newep = TALLOC(1, char *, "env"))
			newep[0] = 0;
	}
	return newep;
}


/*	pvmenvinsert()
*
*	Put a copy of a string into an environment list (made by pvmcopyenv).
*	List must be one of ours, since we malloc and free the strings.
*
*	Returns zero or success, or -1 if malloc fails.
*	May update pointer to environment on return.
*/

int
pvmenvinsert(anep, s)
	char ***anep;		/* environment pointer, may change (NOT environ) */
	char *s;			/* string of form "NAME=Value" */
{
	char **ep;
	char **newep;
	char *p;
	int l;						/* length of env var name or env */

	newep = *anep;

	if (!s || !(p = CINDEX(s, '=')))
		return -1;
	l = p - s + 1;			/* must include the '=' */

	/* search environ for name */

	for (ep = newep; *ep; ep++)
		if (!strncmp(*ep, s, l))
			break;

	if (*ep) {				/* already there, replace it */
		PVM_FREE(*ep);
		*ep = STRALLOC(s);

	} else {				/* not there, must extend environ */

	/* length needed is? */

		for (l = 2, ep = newep; *ep; l++, ep++);

		if (!(newep = TREALLOC((char *)newep, l, char *)))
			return -1;

		newep[l - 2] = STRALLOC(s);
		newep[l - 1] = 0;
		*anep = newep;
	}
	return 0;
}


/*	pvmfreeenv()
*
*	Free an environment list (made by pvmcopyenv).
*/

int
pvmfreeenv(anep)
	char **anep;
{
	char **p;

	for (p = anep; *p; p++)
		PVM_FREE(*p);
	PVM_FREE(anep);
	return 0;
}


/*	pvmxtoi()
*
*	Yet another version of ascii hex to integer
*/

pvmxtoi(p)
	char *p;
{
	int i = 0;
	char c;

	if (p[0] == '0' && (p[1] == 'x' || p[1] == 'X'))
		p += 2;
	while (isxdigit(c = *p++)) {
		i = (i << 4) + c - (isdigit(c) ? '0' : (isupper(c) ? 'A' : 'a') - 10);
	}
	return i;
}


/*	pvmstrtoi()
*
*	Convert string in decimal, hex or octal to integer.
*/

int
pvmstrtoi(p)
	char *p;
{
	int i = 0;
	char c;

	if (*p == '0') {
		p++;
		if (*p == 'x' || *p == 'X')
			while (isxdigit(c = *++p))
				i = (i << 4) + c
						- (isdigit(c) ? '0' : (isupper(c) ? 'A' : 'a') - 10);
		else
			while (c = *p++, c >= '0' && c <= '7')
				i = (i << 3) + c - '0';
		return i;
	} else
		return (int)atoi(p);
}


#ifndef	I860_NODE

/*	hex_inadport()
*
*	Takes a string of format 00000000:0000 and returns a sockaddr_in.
*/

void
hex_inadport(s, sad)
	char *s;
	struct sockaddr_in *sad;
{
	sad->sin_addr.s_addr = htonl((unsigned)pvmxtoi(s));
	s = (s = CINDEX(s, ':')) ? s + 1 : "";
	sad->sin_port = htons((unsigned short)pvmxtoi(s));
}


/*	inadport_decimal()
*
*	Returns printable string corr. to sockaddr_in with addr in decimal.
*/

char *
inadport_decimal(sad)
	struct sockaddr_in *sad;
{
	static char buf[32];
	int a;

	a = ntohl(0xffffffff & sad->sin_addr.s_addr);
	sprintf(buf, "%d.%d.%d.%d:%d",
			0xff & (a >> 24),
			0xff & (a >> 16),
			0xff & (a >> 8),
			0xff & a,
			0xffff & (int)ntohs(sad->sin_port));
	return buf;
}


/*	inadport_hex()
*
*	Returns printable string corr. to sockaddr_in with addr in hex.
*/

char *
inadport_hex(sad)
	struct sockaddr_in *sad;
{
	static char buf[16];
	int a;

	a = ntohl(0xffffffff & sad->sin_addr.s_addr);
	sprintf(buf, "%08x:%04x", a, 0xffff & (int)ntohs(sad->sin_port));
	return buf;
}

#endif	/*I860_NODE*/


#ifdef	NOTMPNAM
char *
pvmtmpnam(buf)
	char *buf;
{
	static int n = 0;
	static char scratch[255];

	char *pvmtmp;

	if (!buf)
		buf = scratch;

	pvmtmp = pvmgettmp();

#ifndef WIN32
	sprintf(buf, "%s/pvmtmp%06d.%d", pvmtmp, getpid(), n++ % 10000);
#else
	sprintf(buf, "%s\\pvmtmp%06d.%d", pvmtmp, getpid(), n++ % 10000);
#endif

	return buf;
}
#endif	/*NOTMPNAM*/


#ifdef IMA_BEOSCYLD

/*
 * Implement a version of gethostname() that gives the "right" answer
 * for a Scyld Beowulf cluster; calling bproc_currnode() here is why
 * both the pvm daemon and the pvm library are linked with libbproc
 */

int
gethostname(name, len)
	char * name;
	size_t len;
{
	int retval;

	retval = snprintf(name,len,".%d",bproc_currnode());

	return( (retval == -1) ? retval : 0 );
}

#endif

pvmhdump(cp, n, pad)
	char *cp;		/* bytes */
	int n;			/* length */
	char *pad;		/* tag string */
{
	static char *buf = 0;
	static int bufl = 0;

	int i, l;
	char *r;

	pad = pad ? pad : "";
	l = strlen(pad) + 50;
	if (l > bufl) {
		if (buf)
			PVM_FREE(buf);
		if (!(buf = TALLOC(l, char, "hdump"))) {
			bufl = 0;
			pvmlogerror("pvmhdump() malloc failed\n");
			return 1;
		}
		bufl = l;
	}

	r = buf;
	for (i = 0; n-- > 0; i = (i + 1) & 15) {
		sprintf(r, "%s%02x",
			i ? " " : pad,
			0xff & *cp++);
		r += strlen(r);
		if (!n || i == 15) {
			strcpy(r, "\n");
			pvmlogerror(buf);
			r = buf;
		}
	}

	return 0;
}


/*	pvmgethome()
*
*	Return path of user homedir.
*/

char *
pvmgethome()
{
	static char *hd = 0;
#ifndef WIN32 /* which registry should we use otherwise ? */
	if (!hd) {
		if (hd = getenv("HOME")) {
			hd = STRALLOC(hd);

		} else {
			pvmlogerror("warning: can't getenv(HOME), using /\n");
			hd = "/";
		}
	}
#else
	hd="\\";
#endif
	return hd;
}


/*	pvmgetroot()
*
*	Return absolute path of PVM top level directory.
*/

char *
pvmgetroot()
{
	static char *rd = 0;

	if (!rd) {

#ifdef IMA_CSPP

		struct stat buf;

		rd = STRALLOC("/usr/convex/pvm");
		if (stat(rd, &buf) == -1) {
			pvmlogperror(
				"Unable to default PVM_ROOT to /usr/convex/pvm");
			pvmbailout(0);
			exit(1);		/* the other meaning of bail out */
		}
		pvmputenv("PVM_ROOT=/usr/convex/pvm");
		pvmlogerror("Defaulting PVM_ROOT to /usr/convex/pvm");

#else

#ifdef CYGWIN
		rd = getenv("PVM_ROOT_U");
#else
		rd = getenv("PVM_ROOT");
#endif

#ifdef WIN32
		if (!rd)
			rd = read_pvmregistry("PVM_ROOT");
#endif

#endif

		if (!rd) {
			pvmlogerror("PVM_ROOT environment variable not set.\n");
			pvmbailout(0);
			exit(1);		/* the other meaning of bail out */
		}
	}

	return rd;
}


/*	pvmgettmp()
*
*	Return absolute path of PVM temporary directory.
*/

char *
pvmgettmp()
{
	static char *td = 0;

	if (!td) {
#ifndef WIN32
		if ( !(td=getenv("PVM_TMP")) )
#ifdef IMA_BEOLIN
			td = "/tmps";	/* the shared tmp */
#else
			td = "/tmp";
#endif
#else
		if ( !(td=getenv("PVM_TMP")) ) {
			if ( !(td=read_pvmregistry("PVM_TMP")) ) {
				if ( !(td=getenv("TEMP")) ) {
					td = "C:\\TEMP";
					fprintf(stderr,
							"Could not get %%PVM_TMP%% or %%TEMP%% ");
					fprintf(stderr, "-> using \"%s\".\n", td);
					fprintf(stderr,
							"Please check your PVM installation.\n");
				}
			}
		}
#endif
	}

	return td;
}


/*	pvmgetrsh()
*
*	Return absolute path of the rsh executable for the local system.
*/

char *
pvmgetrsh()
{
	static char *rd = 0;

	if (!rd) {
		if ( !(rd=getenv("PVM_RSH")) )
			rd = RSHCOMMAND;
	}

	return rd;
}


/*	pvmgetpvmd()
*
*	Return absolute path of pvmd startup script.
*/

char *
pvmgetpvmd()
{
	char *r;
	static char *pp = 0;

	if (!pp) {
		if (PVMDFILE[0] == '/')
			pp = PVMDFILE;
		else {
			r = pvmgetroot();
			pp = TALLOC(strlen(r) + strlen(PVMDFILE) + 2, char, "pvmdpath");
			sprintf(pp, "%s/%s", r, PVMDFILE);
		}
	}
	return pp;
}


/*	pvmdsockfile()
*
*	Returns name of file where pvmd advertises its sockaddr.
*	Assumes global pvm_useruid is set before calling.
*/

char *
pvmdsockfile()
{
	static char buf[255];

	char hna[128];
	char *pvmtmp;
	char *p;

#ifdef	IMA_CSPP
	int scid = get_scid();	/* default (system) subcomplex ID is 1 */
#endif

	pvmtmp = pvmgettmp();

#ifdef	SHAREDTMP

	if (gethostname(hna, sizeof(hna)-1) == -1) {
		pvmlogerror("pvmdsockfile() can't gethostname()\n");
		return (char*)0;
	}
	if (p = CINDEX(hna, '.'))
		*p = 0;

#ifdef	IMA_CSPP
	if (scid > 1)
		(void)sprintf(buf, TDSOCKNAME_CSPP,
				pvmtmp, pvm_useruid, scid, hna);
	else
#endif
		(void)sprintf(buf, TDSOCKNAME, pvmtmp, pvm_useruid, hna);

#else

#ifdef	IMA_CSPP
	if (scid > 1)
		(void)sprintf(buf, TDSOCKNAME_CSPP, pvmtmp, pvm_useruid, scid);
	else
#endif
#ifdef WIN32
		sprintf( buf, "%s\\pvmd.%s", pvmtmp, username );
#else
		(void)sprintf(buf, TDSOCKNAME, pvmtmp, pvm_useruid);
#endif

#endif

	/* Append a Virtual Machine ID, If Set */
	if ( p = getenv("PVM_VMID") ) {
		strcat( buf, "." );
		strcat( buf, p );
	}

	return buf;
}


#ifdef IMA_CSPP
#include <sys/cnx_pattr.h>

/*	get_scid ()
*
*	(Convex CSPP only)
*	Returns the subcomplex identifier for the calling process
*/

int
get_scid ()
{
	static int my_scid = -1;

	int rc;
	struct cnx_pattributes pattrib;

	if (my_scid < 0) {
		my_scid = 0;
		if ((rc = cnx_getpattr(getpid(), CNX_PATTR_SCID, &pattrib)) != 0) {
			pvmlogperror ("get_scid: Unable to get scid");
			return 0;
		}
		my_scid = pattrib.pattr_scid;
	}
	return my_scid;
}
#endif	/*IMA_CSPP*/


#define	PVMCRCPOLY	0xedb88320

unsigned int
pvmcrcappend(buf, len, crc)
	char *buf;			/* buffer to append to crc value */
	int len;			/* length */
	unsigned int crc;	/* initial value */
{
	static unsigned int crctbl[256];
	static int once = 1;

	if (once) {
		int i, j;
		unsigned int v;

		for (i = 0; i < 256; i++) {
			v = i;
			for (j = 8; j--; )
				v = v & 1 ? (v >> 1) ^ PVMCRCPOLY : v >> 1;
			crctbl[i] = v;
		}
		once = 0;
	}

	while (len-- > 0)
		crc = ((crc >> 8) & 0x00ffffff) ^ crctbl[(crc ^ *buf++) & 0xff];
	return crc;
}


static char *taglist1[] = {
	"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",
	"TM_SCHED",
	"TM_TASKER",
	"TM_HOSTER",
	"TM_TRACER",
	"TM_HOSTSYNC",
	"TM_SETOPT",
	"TM_GETOPT",
	"TM_CONTEXT",
	"TM_SHMCONN",
};

static char *taglist2[] = {
	"DM_ADD", "DM_ADDACK",
	"DM_EXEC", "DM_EXECACK",
	"DM_SENDSIG",
	"DM_HTUPD", "DM_HTUPDACK", "DM_HTCOMMIT",
	"DM_SLCONF",
	"DM_STARTACK",
	"DM_TASK", "DM_TASKACK",
	"DM_DELHOST", "DM_DELHOSTACK",
	"DM_NULL",
	"DM_TASKOUT",
	"DM_PSTAT", "DM_PSTATACK",
	"DM_HALT",
	"DM_MCA",
	"DM_NOTIFY", "DM_NOTIFYACK",
	"DM_DB", "DM_DBACK",
	"DM_RESET", "DM_RESETACK",
	"DM_HTDEL",
	"DM_HOSTSYNC", "DM_HOSTSYNCACK",
};

static char *taglist3[] = {
	"TC_CONREQ",
	"TC_CONACK",
	"TC_TASKEXIT",
	"TC_NOOP",
	"TC_OUTPUT",
	"TC_SETTRACE",
	"TC_SETTMASK",
	"TC_SETTRCBUF",
	"TC_SETTRCOPT",
	"TC_SHMAT",
	"TC_OUTPUTX",
	"TC_SIBLINGS",
};

static char *taglist4[] = {
	"SM_SPAWN",
	"SM_EXEC", "SM_EXECACK",
	"SM_TASK",
	"SM_CONFIG",
	"SM_ADDHOST",
	"SM_DELHOST",
	"SM_ADD", "SM_ADDACK",
	"SM_NOTIFY",
	"SM_TASKX",
	"SM_HOSTX",
	"SM_HANDOFF",
	"SM_SCHED",
	"SM_STHOST", "SM_STHOSTACK",
	"SM_STTASK",
};

struct nametag {
	int first;
	int last;
	char **names;
};

static struct nametag nametaglist[] = {
	{ TM_CONNECT, TM_SHMCONN, taglist1 },
	{ DM_ADD, DM_HOSTSYNCACK, taglist2 },
	{ TC_CONREQ, TC_SIBLINGS, taglist3 },
	{ SM_SPAWN, SM_STTASK, taglist4 },
};


/*	pvmnametag()
*
*	Convert message tag to symbolic name for reserved tags.
*	Returns name string or decimal tag if not found.
*	If found is nonnull, return whether found.
*/

char *
pvmnametag(tag, found)
	int tag;
	int *found;
{
	static char buf[32];

	int i, j;

	for (i = sizeof(nametaglist)/sizeof(nametaglist[0]); i-- > 0; )
		if (tag >= nametaglist[i].first && tag <= nametaglist[i].last) {
			j = tag - nametaglist[i].first;
			if (found)
				*found = 1;
			return nametaglist[i].names[j];
		}
	sprintf(buf, "%d", tag);
	if (found)
		*found = 0;
	return buf;
}


/*	debug_flags()
*
*	Convert debug mask to flag names
*/

static char *dflgs[] = {
	"pkt",	/* 1 */
	"msg",	/* 2 */
	"tsk",	/* 4 */
	"slv",	/* 8 */
	"hst",	/* 10 */
	"sel",	/* 20 */
	"net",	/* 40 */
	"mpp",	/* 80 */
	"sch",	/* 100 */
	"app",	/* 200 */
	"wai",	/* 400 */
	"mem",	/* 800 */
	"sem",	/* 1000 */
	"lck",	/* 2000 */
	"rtg",	/* 4000 */
	"mbx"	/* 8000 */
};

char *
debug_flags(mask)
	int mask;
{
	static char buf[64];
	int bit, i;

	buf[0] = 0;
	for (bit = 1, i = 0; i < sizeof(dflgs)/sizeof(dflgs[0]); i++, bit *= 2)
		if (mask & bit) {
			if (buf[0])
				strcat(buf, ",");
			strcat(buf, dflgs[i]);
		}
	return buf;
}


/*	print_fdset()
*
*	Print an fd_set in human-readable format.
*/

print_fdset(pad, n, f)
	char *pad;			/* label at head */
	int n;				/* max fd + 1 */
#ifdef FDSETNOTSTRUCT
	fd_set *f;			/* fd set */
#else
	struct fd_set *f;	/* fd set */
#endif
{
	int i;
	char *s = "";

	pvmlogprintf("%s", pad);
	for (i = 0; i < n; i++)
		if (FD_ISSET(i, f)) {
			pvmlogprintf("%s%d", s, i);
			s = ",";
		}
	pvmlogprintf("\n");
	return 0;
}


#ifdef I860_NODE
int
gettimeofday(tp, tzp)
	struct timeval *tp;
	struct timezone *tzp;
{
	double t, dclock();

	t = dclock();
	tp->tv_sec = t;
	tp->tv_usec = (t - tp->tv_sec)*1.0E06;
	return 0;
}
#endif


/*	pvmgetclock()
*
*	Use this whenever we need a clock value in seconds and microseconds.
*	Return values shouldn't be compared to those from gettimeofday,
*	because it doesn't necessarily return the actual time of day.
*/

int
pvmgetclock(tm)
	struct timeval *tm;
{
#if defined(IMA_PGON)
	double dclock();
	double t;

	t = dclock();
	tm->tv_sec = t;
	tm->tv_usec = (t - tm->tv_sec)*1.0E06;
#else
	return gettimeofday(tm, (struct timezone*)0);
#endif
}


/*  pvmsleep()
*
*   A consistent way to invoke sleep(), from:
*     Unix / Windows, C / Fortran
*/

int
pvmsleep( secs )
int secs;
{
	int ret;
#ifdef WIN32
	Sleep( secs * 1000 );
	ret = secs;
#else
	ret = (int) sleep( secs );
#endif
	return( ret );
}


/*	pvmmatchstring()
*
*	Replacement for GNU Regex pattern matching.
*/

int
pvmmatchstring( str, pattern )
char *str;
char *pattern;
{
	char *start;
	char *save;
	char *x;
	char *y;

	/* Find Start of Search String (Ignore Preceding '*'s) */

	start = pattern;

	while ( *start == '*' && *start != '\0' )
		start++;

	/* Null Search - Always Matches */

	if ( *start == '\0' )
		return( 1 );

	/* Initialize Pointers */

	x = str;

	y = start;

	/* Search Line for Start of Search String */

	while ( *x != '\0' )
	{
		/* Starting Match Found...  Check it Out */

		if ( *x == *y )
		{
			/* Save Continuation Pointer */

			save = x + 1;

			/* Traverse Line Until No Longer Matches */

			while ( *x != '\0' && *y != '\0' && *x == *y )
			{
				x++;
				y++;

				/* Nested '*' Search Expansion */

				if ( *y == '*' )
				{
					if ( pvmmatchstring( x, y + 1 ) )
						return( 1 );

					else
						break;
				}

				/* Skip Over '\' for Escaped '*'s */

				else if ( *y == '\\' && *(y+1) == '*' )
					y++;
			}

			/* It's a Match! */

			if ( *y == '\0' )
				return( 1 );

			/* Reset and Continue */

			x = save;

			y = start;
		}

		else
			x++;
	}

	return( 0 );
}

