/*
 *	pvmgs.c
 *	PVM group server
 *
 *	This program maintains group lists.  Processes may join or add
 *	by sending the appropriate request to the group server.
 *	
 *	4 Mar 1993 adamb: Fixed gs_barrier to reset the barrier 
 *		after it's been reached
 *	8 Jul 1993 adamb: Fixed gs_register'pvm_delete so it won't
 *		print bogus error messages
*/



#include <stdio.h>

#include "pvmgs.h"
#include "pvmgdef.h"
#include "pvm3.h"
#include "../src/bfunc.h"

/* ought to be dynamic at some point */
static struct group_struct group[MAXNGROUPS];
int ngroups;

void gs_init();
void gs_handle();
void gs_pstate();
int  gs_register();

int
main(argc, argv)
int argc;
char *argv[];
{

	int gstid;

	/* attach to the virtual machine */
	if((gstid = pvm_mytid()) < 0 ) {
		pvm_perror("pvmgs");
		return -1;
		}

	/* register this group server */
	if (!gs_register("pvmgs", gstid)) {
		/* initialize the data structures */
		gs_init();
		/* handle the group server functions */
		gs_handle();
		}
	pvm_exit();
	return(0);
}

/* handle group server requests */
void
gs_handle()
{
	int len, msgtag, tid;


	while (1) {
		/* recv a request */
		if (pvm_recv(-1, -1) < 0) {
			pvm_perror("gs_handle");
			return;
			}
		if (pvm_bufinfo(pvm_getrbuf(), &len, &msgtag, &tid) < 0) {
			pvm_perror("gs_handle");
			return;
			}
		switch(msgtag) {
			case(DIE): {
				/* leave a suicide note */
				gs_pstate("pvmgs");
				return;
				}
			case(RESET):
				gs_init();
				break;
			case(JOIN): {
				/* join a group with the lowest avail gid */
				char groupname[MAXGNAME];
				int gid;

				/* get the name */
				pvm_upkstr(groupname);

				/* add the tid */
				gid = gs_join(groupname, tid);

				/* send back the gid */
				pvm_initsend(PvmDataDefault);
				pvm_pkint(&gid, 1, 1);
				pvm_send(tid, msgtag);
				}

				break;
			case(LEAVE): {
				/* leave a group */
				char groupname[MAXGNAME];
				int cc;

				/* unpack the name and gid */
				pvm_upkstr(groupname);
				
				/* delete the gid from the group */
				cc = gs_leave(groupname, tid);

				/* send back an ack */
				pvm_initsend(PvmDataDefault);
				pvm_pkint(&cc, 1, 1);
				pvm_send(tid, msgtag);

				}

				break;
			case(BARRIER): {
				/* barrier on a  group */
				char groupname[MAXGNAME];
				int cnt, cc;

				/* unpack the name and gid */
				pvm_upkstr(groupname);
				pvm_upkint(&cnt, 1, 1);
				
				/* register the barrier request */
				cc = gs_barrier(groupname, cnt, tid);

				/* send back a response only if there is an error */
				if (cc < 0) {
					pvm_initsend(PvmDataDefault);
					pvm_pkint(&cc, 1, 1);
					pvm_send(tid, msgtag);
					}
					
				} 
				break;
			case(BCAST): {
				char groupname[MAXGNAME];
				int cc;

				pvm_upkstr(groupname);
				/* this builds a message group is valid */
				cc = gs_bcast(groupname);
				if (cc) {
					/* there was some error, so send the error code */
					pvm_initsend(PvmDataDefault);
					pvm_pkint(&cc, 1, 1);
					}
				/* send the error code or the list of tids */
				pvm_send(tid, msgtag);
				}
				break;
			case(GSIZE): {
				char groupname[MAXGNAME];
				int size;

				/* unpack the name and gid */
				pvm_upkstr(groupname);

				/* get the group size */
				size = gs_gsize(groupname);

				/* send back an ack */
				pvm_initsend(PvmDataDefault);
				pvm_pkint(&size, 1, 1);
				pvm_send(tid, msgtag);
				}
				break;
			case(GETINST): {
				/* find the instance number given the group and tid */
				char groupname[MAXGNAME];
				int ftid, inst;

				/* unpack the name and gid */
				pvm_upkstr(groupname);
				pvm_upkint(&ftid, 1, 1);
				
				/* get the instance number */
				inst = gs_getinst(groupname, ftid);

				/* send back an ack */
				pvm_initsend(PvmDataDefault);
				pvm_pkint(&inst, 1, 1);
				pvm_send(tid, msgtag);
				}
				break;
			case(GETTID): {
				/* find the task id  given the group and instance number */
				char groupname[MAXGNAME];
				int ftid, inst;

				/* unpack the name and gid */
				pvm_upkstr(groupname);
				pvm_upkint(&inst, 1, 1);
				
				/* get the instance number */
				ftid = gs_gettid(groupname, inst);

				/* send back an ack */
				pvm_initsend(PvmDataDefault);
				pvm_pkint(&ftid, 1, 1);
				pvm_send(tid, msgtag);
				}
				break;
			case DUMP:
				gs_pstate("DUMP");
				break;
			case GSLS: {
				int i;

				pvm_initsend(PvmDataDefault);
				pvm_pkint(&ngroups, 1, 1);
				for (i = 0; i < MAXNGROUPS; i++) {
					if (group[i].name[0] != '\0') {
						pvm_pkstr(group[i].name);
						pvm_pkint(&group[i].ntids, 1, 1);
						pvm_pkint(&group[i].barrier_count, 1, 1);
						pvm_pkint(&group[i].barrier_reached, 1, 1);
						pvm_pkint(group[i].tids, MAXGTIDS, 1);
						pvm_pkint(group[i].btids, group[i].barrier_reached, 1);
						}
					}
				pvm_send(tid, msgtag);
				}
				break;
			default:
				break;
			}  /* switch */
		
		} /* while */

} /* gs_handle */

/* initialize the groups datastructures */
void
gs_init()
{
	int g;

	ngroups = 0;

	for (g = 0; g < MAXNGROUPS; g++) {
		group[g].name[0] = '\0';
		group[g].ntids = 0;
		group[g].barrier_count = -1;
		}
}

/* 
	add tid to the group, the gid returned is the smallest available
	there may be holes in the gid list from an earlier leave
*/

int
gs_join(gname, tid)
char *gname;
int tid;
{
	int gi;
	int inst;

	/* check for valid group name */
	if (gname == (char*) 0) return(PvmNullGroup);
	
	if (strlen(gname) > MAXGNAME) {
		fprintf(stderr, "gs_join: group name too long %s\n", gname);
		return(-1);
		}

	gi = gindex(gname);
	/* if the group is new */
	if (gi == -1) {
		/* make sure there is room for another group */
		if (ngroups >= MAXNGROUPS) {
			fprintf(stderr, "gs_join: too many groups\n");
			return(-1);
			}
		/* find an empty group slot */
		for (gi = 0; gi < MAXNGROUPS; gi++)
			if (group[gi].name[0] == '\0') break;
		strncpy(group[gi].name, gname, MAXGNAME);
		group[gi].ntids = 0;
		group[gi].barrier_count = -1;
		/* null out the tids */
		for (inst = 0; inst < MAXGTIDS; inst++) 
			group[gi].tids[inst] = -1;
		ngroups++;
		}
	
	/* check to see if there is room */
	if (group[gi].ntids >= MAXGTIDS) return(-1);
	/* check to see the tid isn't in the group */
	/* search entire list because there may be holes */
	for (inst = 0; inst < MAXGTIDS; inst++) 
		if (group[gi].tids[inst] == tid) return(PvmDupGroup);


	/* find the lowest available instance number */
	for (inst = 0; inst < MAXGTIDS; inst++) {
		if (group[gi].tids[inst] == -1) {
			/* add the tid */
			group[gi].tids[inst] = tid;
			group[gi].ntids++;
			break;
			}
		}

	/* sanity check */
	if (inst == MAXGTIDS) {
		fprintf(stderr, "gs_join: book keeping error, shouldn't happen\n");
		fprintf(stderr, "\t inst %d, ntids %d\n", inst, group[gi].ntids);
		}
			
	return(inst);
}

/* 
	remove the tid from the group, remove the group if there are no
	more tids left in the group
*/

int
gs_leave(gname, tid)
char gname[MAXGNAME];
int tid;
{
	int gi;
	int gid;

	/* check for valid group name */
	if (gname == (char*) 0) return(PvmNullGroup);
	
	if (strlen(gname) > MAXGNAME) {
		fprintf(stderr, "gs_leave: group name too long %s\n", gname);
		return(-1);
		}

	/* find the group index */
	gi = gindex(gname);
	if (gi == -1) return(PvmNoGroup);

	/* find the tid */
	for (gid = 0; gid < MAXGTIDS; gid++) 
		if(group[gi].tids[gid] == tid)
			break;

	/* if the tid's not there */
	if (gid == MAXGTIDS)
		return(PvmNotInGroup);

	/* delete the tid from the group */
	group[gi].tids[gid] = -1;
	group[gi].ntids--;

	/* delete the group if the count is now zero */
	if(group[gi].ntids == 0) {
		group[gi].name[0] = '\0';
		group[gi].barrier_count = -1;
		ngroups--;
		}
		
	return(PvmOk);
}


/* return the index of group gname in the group array */
/* return -1 if gname doesn't exist */
int
gindex(gname)
char *gname;
{
	int i, len;

	len = strlen(gname)+1;

	/* check for valid group name */
	if ((gname == (char*) 0) || (len > MAXGNAME)) {
		fprintf(stderr, "gindex: invalid group name %s\n", 
			(gname ? gname : "NULL"));
		return(-1);
		}

	for (i = 0; i < MAXNGROUPS; i++)
		if (BCMP(group[i].name, gname, len) == 0) return(i);

	return(-1);
}

/*
	register the name with pvm so other processes can get it
	return -1 if can't register, 0 otherwise
*/

int
gs_register(name, tid)
char *name;
int tid;
{
	
	int cc;

	cc = pvm_insert(name, -1, tid);

	/* make sure I'm the first */
	if (cc == 0) return(0);
	/* some error in the register */
	if (cc < 0) {
		pvm_perror("gs_register'pvm_insert");
		return (-1);
		}
	/* some other server registered so quit */
	if (pvm_delete(name, cc) < 0) {
		pvm_perror("gs_register'pvm_delete");
		return (-1);
		}
	/* I'm not instance 0 */
	return(-1);
}

int
gs_getinst(gname, tid)
char gname[MAXGNAME];
int tid;
{
	int gi;
	int gid;

	/* check for valid group name */
	if (gname == (char*) 0) return(PvmNullGroup);
	
	if (strlen(gname) > MAXGNAME) {
		fprintf(stderr, "gs_leave: group name too long %s\n", gname);
		return(-1);
		}

	/* find the group index */
	gi = gindex(gname);
	if (gi == -1) return(PvmNoGroup);

	/* find the tid */
	for (gid = 0; gid < MAXGTIDS; gid++) 
		if(group[gi].tids[gid] == tid)
			break;

	/* if the tid's not there */
	if (gid == MAXGTIDS)
		return(PvmNotInGroup);
	/* otherwise return the gid */
	return(gid);
}


/*
	find the tid given the group name and id
*/

int
gs_gettid(gname, inst)
char gname[MAXGNAME];
int inst;
{
	int gi;

	/* check for valid group name */
	if (gname == (char*) 0) return(PvmNullGroup);
	
	if (strlen(gname) > MAXGNAME) {
		fprintf(stderr, "gs_leave: group name too long %s\n", gname);
		return(-1);
		}

	/* check for valid instance number */
	if (inst < 0 || inst > MAXGTIDS) return(PvmNoInst);

	/* find the group index */
	gi = gindex(gname);
	if (gi == -1) return(PvmNoGroup);

	/* return the group instance */
	return(group[gi].tids[inst]);
}


/*
	find the size of the given group
*/

int
gs_gsize(gname)
char gname[MAXGNAME];
{
	int gi;

	/* check for valid group name */
	if (gname == (char*) 0) return(PvmNullGroup);
	
	if (strlen(gname) > MAXGNAME) {
		fprintf(stderr, "gs_leave: group name too long %s\n", gname);
		return(-1);
		}

	/* find the group index */
	gi = gindex(gname);
	if (gi == -1) return(PvmNoGroup);

	/* return the group size */
	return(group[gi].ntids);
}



/*
	build a list of tids in the group to send back to the client
	for multicast
*/

int
gs_bcast(gname)
char gname[MAXGNAME];
{
	int i, cnt, gi, tids[MAXGTIDS]; 
	
	/* check for valid group name */
	if (gname == (char*) 0) return(PvmNullGroup);
	
	if (strlen(gname) > MAXGNAME) {
		fprintf(stderr, "gs_leave: group name too long %s\n", gname);
		return(-1);
		}

	/* find the group index */
	gi = gindex(gname);
	if (gi == -1) return(PvmNoGroup);

	pvm_initsend(PvmDataDefault);
	pvm_pkint(&(group[gi].ntids), 1, 1);

	for (i  = 0, cnt = 0; i < MAXGTIDS; i++) 
		if (group[gi].tids[i] != -1) 
			tids[cnt++] = group[gi].tids[i];

	if (cnt != group[gi].ntids) {
		fprintf(stderr, "gs_bcast: cnt (%d) != ntids (%d) shouldn't happen\n", 
			cnt, group[gi].ntids);
		return(PvmSysErr);
		}

	/* pack 'em up */
	pvm_pkint(tids,  cnt, 1);
	return(PvmOk);

}

int
gs_barrier(gname, cnt, tid)
char gname[MAXGNAME];
int cnt, tid;
{
	int i, gi;
	
	/* check for valid group name */
	if (gname == (char*) 0) return(PvmNullGroup);
	
	if (strlen(gname) > MAXGNAME) {
		fprintf(stderr, "gs_leave: group name too long %s\n", gname);
		return(-1);
		}

	/* find the group index */
	gi = gindex(gname);
	if (gi == -1) return(PvmNoGroup);

	/* check to make sure the tid is in the group */
	for (i = 0; i < group[gi].ntids; i++) 
		if (group[gi].tids[i] == tid) break;
	if (i == group[gi].ntids) return(PvmNotInGroup);

	/* if it's the whole group the set the count to the # in the group */
	if (cnt == -1) cnt = group[gi].ntids;

	/* is it a new barrier ? */
	if (group[gi].barrier_count == -1) {
		group[gi].barrier_count = cnt;
		group[gi].barrier_reached = 0;
		}
	else {
		/* make sure the counts match */
		if (cnt != group[gi].barrier_count) return(PvmMismatch);
		}
	/* store the tid */
	group[gi].btids[group[gi].barrier_reached++] = tid;

	 /* if reached is bigger than count then there is a problem */
	 /* this would happen if count was zero */
	 if (group[gi].barrier_count < group[gi].barrier_reached)
		return(PvmMismatch);

	 /* if we're not at the barrier then return */
	 if (group[gi].barrier_count != group[gi].barrier_reached)
		return(PvmOk);
		
	/* let everyone continue, send them the count */
	pvm_initsend(PvmDataDefault);
	pvm_pkint(&(group[gi].barrier_count), 1, 1);
	pvm_mcast(group[gi].btids, group[gi].barrier_count, BARRIER);

	/* reset the barrier */
	group[gi].barrier_count = -1;

	return(PvmOk);

}

void
gs_pstate(name)
char *name;
{
	int i, j;

	fprintf(stderr, "%s 0x%x, %d groups:\n", name, pvm_mytid(), ngroups); 

	for (i = 0; i < MAXNGROUPS; i++) {
		if (group[i].name[0] != '\0') {
			fprintf(stderr, 
				"group: %s, size: %d,  barrier_count %d, barrier_reached %d\n",
				group[i].name, group[i].ntids, group[i].barrier_count, 
				group[i].barrier_reached);
			fputs("tids:\n", stderr);
			for (j = 0; j < MAXGTIDS; j++)
				if (group[i].tids[j] > 0)
					fprintf(stderr, "%d 0x%x\t", j, group[i].tids[j]);
			fputs("\n", stderr);
			if (group[i].barrier_reached > 0) {
				fputs("tids waiting on barrier:\n", stderr);
				for (j = 0; j < group[i].barrier_reached; j++)
					fprintf(stderr, "0x%x\t", group[i].btids[j]);
				fputs("\n", stderr);
				}
			}
		}
}
