/*
 *	pvm_gsulib.c
 *
 *	Group server user library routines
 *
 *	05 Mar 1993	Adam Beguelin adamb@cs.cmu.edu
 *	05 Mar 1993	Fixed malloc() in pvm_bcast()
 *	05 Mar 1993	pvm_barrier now returns 0 or an error
 *	20 Mar 1993	pvm_bcast does not send to itself, beguelin
 *  01 Jun 1993 Fixed saving and restoring of mbufs, beguelin
 *	
 */

#include <stdio.h>
#include "pvm3.h"
#include "pvmgdef.h"

int gstid = -1;
extern int pvm_errno;

/*
	gs_getgstid returns the tid of the group server
	if the groupserver isn't running, start it
*/

int
gs_getgstid()
{
	int info;

	info = pvm_lookup(GSNAME, 0, &gstid);
	/* if it's not there */
	if (info == PvmNoEntry) {
		info = pvm_spawn("pvmgs", (char **)0, PvmMppFront,
				(char *)0, 1, &gstid);
		if (gstid < 0) {
			pvm_perror("gs_getgstid");
			}
		/* wait for it to register */
		while(pvm_lookup(GSNAME, 0, &gstid))
			;
			/*
			fputs("waiting on group server to register\n",stderr);
			*/
		}
	return(gstid);
}

/*

int inum = pvm_joingroup(char* group)

  Adds the calling tid to the named group and returns its instance number.
  Always adds the task in the first available slot such that
  if one task leaves a group and another later joins, then the later
  task will get the instance number freed by the earlier task.
  This allows users to keep a contiguous block of instance numbers [0,p-1].

*/

int
pvm_joingroup(group)
char *group;
{
	int sbuf, rbuf, gid;

	if (group == (char*)0) 
		return(pvm_errno = PvmNullGroup);

	/* find out who the server's tid, start the server if need be */
	gstid = gs_getgstid();
	if (gstid < 0) {
		return(pvm_errno = PvmSysErr);
		}

	/* send the group name to the server */
	if((sbuf = pvm_mkbuf(PvmDataDefault)) < 0)
		pvm_perror("pvm_joingroup");
	if((sbuf = pvm_setsbuf(sbuf)) < 0)
		pvm_perror("pvm_joingroup");
	if (pvm_pkstr(group) < 0)
			pvm_perror("pvm_joingroup");
	if (pvm_send(gstid, JOIN) < 0)
			pvm_perror("pvm_joingroup");

	/* get the group id back from the server */
	if ((rbuf = pvm_setrbuf(0)) < 0)
		pvm_perror("pvm_joingroup");
	if (pvm_recv(gstid, JOIN) < 0)
			pvm_perror("pvm_joingroup");
	if (pvm_upkint(&gid, 1, 1) < 0)
			pvm_perror("pvm_joingroup");


	/* restore the users mbufs */
	pvm_freebuf(pvm_setsbuf(sbuf));
	pvm_freebuf(pvm_setrbuf(rbuf));

	if (gid < 0) pvm_errno = gid;
	return(gid);

}


/*

int info = pvm_lvgroup(char* group)

  Removes the calling tid from the named group.
  Returns only after getting confirmation from server.
  This allows users to coordinate leaving and joining.

*/

int
pvm_lvgroup(group)
char *group;
{
	int sbuf, rbuf;
	int rc;

	/* check for null group */
	if (group == (char*)0) 
		return(pvm_errno = PvmNullGroup);

	/* if the group server isn't started */
	if (gstid == -1)
		return(pvm_errno = PvmNotInGroup);

	/* send the group name to the server */
	sbuf = pvm_mkbuf(PvmDataDefault);
	sbuf = pvm_setsbuf(sbuf);
	pvm_pkstr(group);
	pvm_send(gstid, LEAVE);

	/* get the return code back from the server */
	rbuf = pvm_setrbuf(0);
	pvm_recv(gstid, LEAVE);
	pvm_upkint(&rc, 1, 1);

	/* restore the users mbufs */
	pvm_freebuf(pvm_setsbuf(sbuf));
	pvm_freebuf(pvm_setrbuf(rbuf));

	if (rc < 0) pvm_errno = rc;
	return(rc);
	
}


/*

int inum = pvm_getinst(char* group, int tid)

  Returns the instance number of the specified tid in the named group.
  Can be called by any task.

*/

int
pvm_getinst(group, tid)
char *group;
int tid;
{
	int sbuf, rbuf, inst;

	/* check for a null group name */
	if (group == (char*)0) 
		return(pvm_errno = PvmNullGroup);

	/* find out who the server's tid, start the server if need be */
	gstid = gs_getgstid();
	if (gstid < 0) {
		return(pvm_errno = PvmSysErr);
		}

	/* send the group name to the server */
	sbuf = pvm_mkbuf(PvmDataDefault);
	sbuf = pvm_setsbuf(sbuf);
	pvm_pkstr(group);
	pvm_pkint(&tid, 1, 1);

	pvm_send(gstid, GETINST);

	/* get the group id back from the server */
	rbuf = pvm_setrbuf(0);
	pvm_recv(gstid, GETINST);
	pvm_upkint(&inst, 1, 1);

	/* restore the users mbufs */
	pvm_freebuf(pvm_setsbuf(sbuf));
	pvm_freebuf(pvm_setrbuf(rbuf));

	if (inst < 0) pvm_errno = inst;
	return(inst);

}

/*

int tid = pvm_gettid(char * group, int inum)

  Returns the tid of the task defined by the group/inum pair.
  Can be called by any task.


*/

int 
pvm_gettid(group, inst)
char *group;
int inst;
{
	int sbuf, rbuf, tid;

	if (group == (char*)0) 
		return(pvm_errno = PvmNullGroup);

	/* find out who the server's tid, start the server if need be */
	gstid = gs_getgstid();
	if (gstid < 0) {
		return(pvm_errno = PvmSysErr);
		}

	/* send the group name to the server */
	sbuf = pvm_mkbuf(PvmDataDefault);
	sbuf = pvm_setsbuf(sbuf);
	if (pvm_pkstr(group) < 0)
		pvm_perror("pvm_gettid");
	if (pvm_pkint(&inst, 1, 1) < 0)
		pvm_perror("pvm_gettid");

	if (pvm_send(gstid, GETTID) < 0)
		pvm_perror("pvm_gettid");

	/* get the group id back from the server */
	rbuf = pvm_setrbuf(0);
	if (pvm_recv(gstid, GETTID) < 0)
		pvm_perror("pvm_gettid");
	if (pvm_upkint(&tid, 1, 1) < 0)
		pvm_perror("pvm_gettid");

	/* restore the users mbufs */
	pvm_freebuf(pvm_setsbuf(sbuf));
	pvm_freebuf(pvm_setrbuf(rbuf));

	if (tid < 0) pvm_errno = tid;
	return(tid);
}

/*

int gsize = pvm_gsize(char* group)

  Returns the present size of the named group.

*/

int
pvm_gsize(group)
char *group;
{
	int sbuf, rbuf, size;

	if (group == (char*)0) 
		return(pvm_errno = PvmNullGroup);

	/* find out who the server's tid, start the server if need be */
	gstid = gs_getgstid();
	if (gstid < 0) {
		return(pvm_errno = PvmSysErr);
		}

	/* send the group name to the server */
	sbuf = pvm_mkbuf(PvmDataDefault);
	sbuf = pvm_setsbuf(sbuf);
	pvm_pkstr(group);
	pvm_send(gstid, GSIZE);

	/* get the group id back from the server */
	rbuf = pvm_setrbuf(0);
	pvm_recv(gstid, GSIZE);
	pvm_upkint(&size, 1, 1);

	/* restore the users mbufs */
	pvm_freebuf(pvm_setsbuf(sbuf));
	pvm_freebuf(pvm_setrbuf(rbuf));

	if (size < 0) pvm_errno = size;
	return(size);
}

/*

int info = pvm_bcast(char* group, int msgtag)

  Broadcast message to all members presently in the named group
  (excluding yourself if you are in the group).
  Calling tid need not be in the group.

*/


int
pvm_bcast(group, msgtag)
char *group;
int msgtag;
{
	int sbuf, rbuf;

	int ntids, *tids, cc, i, mytid;

	if (group == (char*)0) 
		return(pvm_errno = PvmNullGroup);

	/* find out who the server's tid, start the server if need be */
	gstid = gs_getgstid();
	if (gstid < 0) {
		return(pvm_errno = PvmSysErr);
		}

	/* send the group name to the server */
	sbuf = pvm_mkbuf(PvmDataDefault);
	sbuf = pvm_setsbuf(sbuf);
	pvm_pkstr(group);
	pvm_send(gstid, BCAST);

	/* get the list of tids back from the server */
	rbuf = pvm_setrbuf(0);
	pvm_recv(gstid, BCAST);
	pvm_upkint(&ntids, 1, 1);

	/* check for number of tids group */
	if (ntids < 0) {
		pvm_freebuf(pvm_setsbuf(sbuf));
		pvm_freebuf(pvm_setrbuf(rbuf));
		return(pvm_errno = ntids);
		}
	/* if there is no one in the group */
	if (ntids == 0) {
		pvm_freebuf(pvm_setsbuf(sbuf));
		pvm_freebuf(pvm_setrbuf(rbuf));
		return(pvm_errno = PvmNoInst);
		}


	/* make room for the tids */
	if((tids = (int *)malloc(ntids * sizeof(int))) == 0) {
		pvm_freebuf(pvm_setsbuf(sbuf));
		pvm_freebuf(pvm_setrbuf(rbuf));
		perror("pvm_bcast");
		return(pvm_errno = PvmSysErr);
		}

	pvm_upkint(tids, ntids, 1);


	/* restore the users mbufs */
	pvm_freebuf(pvm_setsbuf(sbuf));
	pvm_freebuf(pvm_setrbuf(rbuf));

	/* get my tid */
	if ((mytid = pvm_mytid()) < 0) {
		perror("pvm_bcast");
		free(tids);
		return(pvm_errno = PvmSysErr);
		}

	/* if I'm the only one in the group */
	if ((ntids == 1) && (tids[0] == mytid )) {
		free(tids);
		return(pvm_errno = PvmNoInst);
		}

	/* remove my tid */
	for (i = 0; i < ntids; i++)
		if (tids[i] == mytid) {
			/* move the last tid to here and shorten the list */
			tids[i] = tids[--ntids];
			break;
			}

	/* finally send the damn thing */
	cc = pvm_mcast(tids, ntids, msgtag);
	free(tids);
	return(cc);
}


/*

int info = pvm_barrier(char* group, int count)

  Calling task waits until count members of named group also
  call pvm_barrier. If user places -1 for count then the present
  size of the group is used. Note this option is not useful if
  the size of the group is changing.

  A process must be a member of a group to call pvm_barrier on
  that group

*/

int
pvm_barrier(group, cnt)
char  *group;
int cnt;
{
	int sbuf, rbuf;

	int cc;

	if (group == (char*)0) 
		return(pvm_errno = PvmNullGroup);

	/* find out who the server's tid, start the server if need be */
	gstid = gs_getgstid();
	if (gstid < 0) {
		return(pvm_errno = PvmSysErr);
		}

	/* send the group name and barrier count to the server */
	sbuf = pvm_mkbuf(PvmDataDefault);
	sbuf = pvm_setsbuf(sbuf);
	pvm_pkstr(group);
	pvm_pkint(&cnt, 1, 1);
	rbuf = pvm_setrbuf(0);
	pvm_send(gstid, BARRIER);

	/* get the barrier ack back from the server */
	pvm_recv(gstid, BARRIER);
	pvm_upkint(&cc, 1, 1);

	/* restore the users mbufs */
	pvm_freebuf(pvm_setsbuf(sbuf));
	pvm_freebuf(pvm_setrbuf(rbuf));

	/* check for error code */
	if (cc < 0) 
		pvm_errno = cc;

	/* don't return the number of members in the barrier */
	if (cc > 0) cc = 0;

	return(cc);

}

/*
	ask pvmgs to dump it's state 
	assumes pvmgs is running
*/

void
pvm_gsdump()
{
	int sbuf;

	sbuf = pvm_mkbuf(PvmDataDefault);
	sbuf = pvm_setsbuf(sbuf);
	pvm_send(gstid, DUMP);

	pvm_freebuf(pvm_setsbuf(sbuf));

}
