/*
 *	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
 *	6 Feb 1994 adamb: Added DEADTID functionality, including removing
 *		a taskid from the blocking barrier list if needed
 *     24 Apr 1994 Donato: Added gs_tidlist() in exchange for gs_bcast()
 *      1 May 1994 Papadopoulos: removed length limit for groupnames
 *
*/



#include <stdio.h>
/* #include <malloc.h>        XXX - yacks on NeXT */
#include "pvmgs.h"
#include "pvmgdef.h"
#include "pvm3.h"
#include "../src/pvmalloc.h"

#define NEWMEMGNAME(length,mxlen,gname) { if ( (length) > (mxlen) )         \
                    {                                                       \
                      if (gname != (char *) NULL)                           \
                         PVM_FREE(gname);                                   \
                      gname=(char *)PVM_ALLOC( sizeof(char) *(length + 1), "pvmgs");\
                      mxlen = length;                                       \
                    }                                                       \
         }
#define MAX(x,y) ((x) > (y) ? (x) : (y))                               
/* 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;
        int mxupklen = 0;
        char *groupname = (char *) NULL;
	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 */
				int gid;
                                NEWMEMGNAME(len,mxupklen,groupname);
				/* get the name */
				pvm_upkstr(groupname);

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

				/* tell me if the tid dies */
				if (pvm_notify(PvmTaskExit, DEADTID, 1, &tid) < 0) 
					pvm_perror("pvmgs");

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

				break;
			case(LEAVE): {
				/* leave a group */
				int cc;

                                NEWMEMGNAME(len,mxupklen,groupname);
				/* 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(DEADTID): {
				/* a task in a group has died */
				int i, gid, gi;

				pvm_upkint(&tid,1,1);
				if (tid < 0) {
					fprintf(stderr, "pvmgs: weird dead tid x%x %d\n",tid,tid);
					break;
					}

				/* find the tid in all groups */
				for (gi = 0; gi < MAXNGROUPS; gi++) 
					for (gid = 0; gid < MAXGTIDS; gid++)
						if (group[gi].tids[gid] == tid) {

							/* 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--;
								}
							else {
								/* remove the tid from the barrier */
								for (i = 0; i < group[gi].barrier_reached;i++){
									if (group[gi].btids[i] == tid) {
									  --group[gi].barrier_reached;
									  while(i < group[gi].barrier_reached) {
										group[gi].btids[i]=group[gi].btids[i+1];
										i++;
										}
									  }
									}
								}
							}
				}

				break;
			case(BARRIER): {
				/* barrier on a  group */
				int cnt, cc;

                                NEWMEMGNAME(len,mxupklen,groupname);
				/* 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): {
				int cc;

                                NEWMEMGNAME(len,mxupklen,groupname);
				pvm_upkstr(groupname);
				/* this builds a message group is valid */
				cc = gs_tidlist(groupname, 0); 
				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(TIDLIST): {
                                int cc;

                                NEWMEMGNAME(len,mxupklen,groupname);
                                pvm_upkstr(groupname);
                                /* this builds a message group is valid */
                                cc = gs_tidlist(groupname, 1);
                                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): {
				int size;

                                NEWMEMGNAME(len,mxupklen,groupname);
				/* 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 */
				int ftid, inst;

                                NEWMEMGNAME(len,mxupklen,groupname);
				/* 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 */
				int ftid, inst;

                                NEWMEMGNAME(len,mxupklen,groupname);
				/* 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_pkint(&group[i].len,1,1);
						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].len = 0;
                group[g].name = (char *) PVM_ALLOC(sizeof(char),"pvmgs:init()");
		*(group[g].name) = '\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);

	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;
                NEWMEMGNAME(strlen(gname),group[gi].len,group[gi].name);
		strcpy(group[gi].name, gname);
		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;
int tid;
{
	int gi;
	int gid;

	/* check for valid group name */
	if (gname == (char*) 0) return(PvmNullGroup);
	
	/* 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) {
		fprintf(stderr, "gindex: invalid group name %s\n", 
			 "NULL");
		return(-1);
		}

	for (i = 0; i < MAXNGROUPS; i++)
            if (strncmp(group[i].name,gname,MAX(group[i].len,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;
int tid;
{
	int gi;
	int gid;

	/* check for valid group name */
	if (gname == (char*) 0) return(PvmNullGroup);

	/* 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;
int inst;
{
	int gi;

	/* check for valid group name */
	if (gname == (char*) 0) return(PvmNullGroup);
	
	/* 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;
{
	int gi;

	/* check for valid group name */
	if (gname == (char*) 0) return(PvmNullGroup);
	
	/* find the group index */
	gi = gindex(gname);
	if (gi == -1) return(PvmNoGroup);

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



/*
   Gets the list of tids for the specified group to send back to the client.
   This is used for doing multicast and for scatter and gather.
*/

int
gs_tidlist(gname, holes_not_allowed)
char *gname;
int holes_not_allowed;
{
  int i, cnt, gi, ntids, tids[MAXGTIDS], hole_exists = 0;

  /* check for valid group name */
  if (gname == (char*) 0) return(PvmNullGroup);

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

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

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

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

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

  /* detected missing instance number(s). scatter and gather require no holes */
  if (holes_not_allowed && hole_exists)
    return(PvmNoInst);

  return(PvmOk);

}  /* end tidlist() */

int
gs_barrier(gname, cnt, tid)
char *gname;
int cnt, tid;
{
	int i, gi;
	
	/* check for valid group name */
	if (gname == (char*) 0) return(PvmNullGroup);
	
	/* 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);
				}
			}
		}
}
