/*
 *	Network Queueing System (NQS)
 *  This version of NQS is Copyright (C) 1992  John Roman
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 1, or (at your option)
 *  any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
/*
*  PROJECT:     Network Queueing System
*  AUTHOR:      John Roman
*
*  Modification history:
*
*       Version Who     When            Description
*       -------+-------+---------------+-------------------------
*       V01.10  JRR                     Initial version.
*       V01.20  JRR     16-Jan-1992	Added support for RS6000.
*	V01.3	JRR	17-Jun-1992	Added header.
*	V01.4	JRR	20-Nov-1992	Modified for C prototypes.
*	V01.5	JRR	01-Mar-1994	Added support for SOLARIS.
*/
/*++ nqs_upd.c - Network Queueing System
 *
 * $Source: /usr2/jrroma/nqs/nqs-3.35.6/src/RCS/nqs_upd.c,v $
 *
 * DESCRIPTION:
 *
 *	NQS queue destination state update module.
 *
 *
 *	Author:
 *	-------
 *	Brent A. Kingsbury, Sterling Software Incorporated.
 *	June 17, 1986.
 *
 *
 * STANDARDS VIOLATIONS:
 *   None.
 *
 * REVISION HISTORY: ($Revision: 1.5 $ $Date: 1994/03/30 20:36:55 $ $State: Exp $)
 * $Log: nqs_upd.c,v $
 * Revision 1.5  1994/03/30  20:36:55  jrroma
 * Version 3.35.6
 *
 * Revision 1.4  92/12/22  15:41:35  jrroma
 * Version 3.30
 * 
 * Revision 1.3  92/06/18  17:31:26  jrroma
 * Added gnu header
 * 
 * Revision 1.2  92/01/17  11:16:46  jrroma
 * Added support for RS6000.
 * 
 * Revision 1.1  92/01/17  11:15:45  jrroma
 * Initial revision
 * 
 *
 */

#include "nqs.h"
#include "nqsxvars.h"
#include "transactcc.h"		/* Transaction completion codes */
#include <errno.h>
#include <string.h>
#include <malloc.h>


/*** upd_addquedes
 *
 *
 *	long upd_addquedes():
 *	Add a queue destination for the specified local pipe queue.
 *
 *	Returns:
 *		TCML_COMPLETE:	if successful.
 *		TCML_ALREADEXI:	if the destination is already in the
 *				destination set for the specified queue.
 *		TCML_INSUFFMEM:	if insufficient memory to create queue
 *				destination descriptor.
 *		TCML_MAXQDESTS: if the maximum allowable number of
 *				destinations has already been reached
 *				for this pipe queue.
 *		TCML_NOSUCHQUE:	if the named local queue did not exist.
 *		TCML_SELREFDES: if the new destination to be added was
 *				self-referential (prohibited).
 *		TCML_WROQUETYP:	if the queue is not a pipe queue.
 */
long upd_addquedes (
	char *lquename,		/* Local queue name */
	char *rquename,		/* Remote queue name */
	Mid_t rhost_mid)	/* Machine-id of destination machine */
{
	struct nqsqueue *queue;
	struct qdestmap *mapdest;
	struct qdestmap *prevmapdest;
	int res;				/* Result code */
	int dest_count;				/* #of destinations */
						/* for this queue */

	queue = nqs_fndnnq (lquename);
	if (queue == (struct nqsqueue *) 0) {
		return (TCML_NOSUCHQUE);	/* No such local queue */
	}
	if (queue->q.type != QUE_PIPE) return (TCML_WROQUETYP);
	/*
	 *  Make sure that the destination is not self-referential.
	 */
	if (strcmp (rquename, queue->q.namev.name) == 0 &&
	    rhost_mid == Locmid) {
		/*
		 *  The destination is self-referential!
		 */
		return (TCML_SELREFDES);
	}
	/*
	 *  Search the existing destination set to see if the destination
	 *  is already present for the specified queue.
	 */
	dest_count = 0;
	prevmapdest = (struct qdestmap *) 0;
	mapdest = queue->v1.pipe.destset;
	while (mapdest != (struct qdestmap *) 0) {
		if (mapdest->pipeto->rhost_mid < rhost_mid) {
			/*
			 *  Keep searching by machine-id.
			 */
			prevmapdest = mapdest;
			mapdest = mapdest->next;
			dest_count += 1;	/* One more destination */
		}
		else if (mapdest->pipeto->rhost_mid > rhost_mid) {
			/*
			 *  The destination to add does not exist.
			 *  Tally the number of remaining destinations
			 *  and exit the outer loop.
			 *
			 *  Prevmapdest already points at the proper
			 *  place....
			 */
			do {
				mapdest = mapdest->next;
				dest_count += 1;	/* One more */
			} while (mapdest != (struct qdestmap *) 0);
		}
		else {
			/*
			 *  Queue(s) going to the machine as specified in
			 *  the new destination already exist.  Now search
			 *  by queue name.
			 */
			do {
				/*
				 *  We are still dealing with queues
				 *  going to the same machine.
				 */
				res = strcmp (mapdest->pipeto->rqueue,
					      rquename);
				if (res < 0) {
					/*
					 *  Keep searching.
					 */
					prevmapdest = mapdest;
					mapdest = mapdest->next;
					dest_count += 1;/* One more dest */
				}
				else if (res == 0) {
					/*
					 *  The queue destination is
					 *  already present in the
					 *  destination set for the
					 *  specified queue.
					 */
					return (TCML_ALREADEXI);
				}
				else {
					/*
					 *  The destination to add does
					 *  not exist.  Tally the number
					 *  of remaining destinations
					 *  and exit the loop.
					 */
					do {
						mapdest = mapdest->next;
						dest_count += 1;/* One more */
					} while (mapdest!=(struct qdestmap*)0);
				}
			} while (mapdest != (struct qdestmap *) 0 &&
				 mapdest->pipeto->rhost_mid == rhost_mid);
		}
	}
	/*
	 *  The destination is not present in the destination set for the
	 *  specified queue, and when added, the destination should be added
	 *  with "prevmapdest" as its predecessor in the set (unless
	 *  "prevmapdest" is null, in which case the new destination must be
	 *  added at the very beginning of the set.
	 */
	if (dest_count >= MAX_QDESTS) {
		/*
		 *  Too many queue destinations would exist, if the
		 *  new destination were added.
		 */
		return (TCML_MAXQDESTS);
	}
	mapdest = (struct qdestmap *) malloc (sizeof (struct qdestmap));
	if (mapdest == (struct qdestmap *) 0) {
		return (TCML_INSUFFMEM);	/* Insufficient memory */
	}
	mapdest->pipeto = upd_credes (rquename, rhost_mid);
	if (mapdest->pipeto == (struct pipeto *) 0) {
		free ((char *) mapdest);	/* Free mapping structure */
		return (TCML_INSUFFMEM);	/* Insufficient memory */
	}
	mapdest->pipeto->refcount++;		/* Increment reference count */
	/*
	 *  Add the destination to the destination set for the specified
	 *  queue in lexicographic order.
	 */
	if (prevmapdest == (struct qdestmap *) 0) {
		mapdest->next = queue->v1.pipe.destset;	/* Add to set head */
		queue->v1.pipe.destset = mapdest;	/* Update head ptr */
	}
	else {					/* Add to set */
		mapdest->next = prevmapdest->next;
		prevmapdest->next = mapdest;
	}
	if (Booted) {
		/*
		 *  NQS is completely "booted", and so this queue/destination
		 *  mapping needs to be explicitly added to the NQS database.
		 *  Furthermore, it may be necessary to spawn a pipe queue
		 *  request....
		 */
		udb_addquedes (queue, mapdest);	/* Update the database image */
		if ((queue->q.status & QUE_RUNNING) && queue->q.queuedcount &&
		    (mapdest->pipeto->status & DEST_ENABLED)) {
			psc_spawn();		/* Maybe spawn a pipe req */
		}
	}
	else New_qdestmap = mapdest;		/* Set ptr to new dest map */
	return (TCML_COMPLETE);			/* for the benefit of */
}						/* ../src/nqs_ldconf.c */


/*** upd_credes
 *
 *
 *	struct pipeto *upd_credes():
 *
 *	Create a global pipe queue destination, if it is not already
 *	present in the global pipe queue destination set.
 *
 *	Returns:
 *		A pointer to the pipeto structure allocated for the
 *		destination, if successful.  Otherwise, NIL is returned.
 */
struct pipeto *upd_credes (char *rquename, Mid_t rhost_mid)
{
	struct pipeto *dest;		/* Destination descr walking */
	struct pipeto *prevdest;	/* Previous dest descr */
	short res;			/* Result of strcmp() */

	prevdest = (struct pipeto *) 0;
	dest = Pipetoset;			/* Global destination set */
	while (dest != (struct pipeto *) 0) {
		if (dest->rhost_mid < rhost_mid) {
			/*
			 *  Keep searching by machine-id.
			 */
			prevdest = dest;
			dest = dest->next;
		}
		else if (dest->rhost_mid > rhost_mid) {
			/*
			 *  The destination to add does not exist.
			 *  Prevdest points at the proper place....
			 */
			dest = (struct pipeto *) 0;	/* Exit loop */
		}
		else {
			/*
			 *  There are one or more pipe queue destinations
			 *  already in existence for the machine in question.
			 *  Now search by queue name.
			 */
			do {
				/*
				 *  We are still dealing with queues
				 *  going to the same machine.
				 */
				res = strcmp (dest->rqueue, rquename);
				if (res < 0) {
					/*
					 *  Keep searching.
					 */
					prevdest = dest;
					dest = dest->next;
				}
				else if (res == 0) {
					/*
					 *  The pipe queue destination already
					 *  exists.
					 */
					return (dest);
				}
				else {
					/*
					 *  The destination to add does
					 *  not exist.  Prevdest points
					 *  at the proper place.
					 */
					dest = (struct pipeto *) 0;
				}
			} while (dest != (struct pipeto *) 0 &&
				 dest->rhost_mid == rhost_mid);
		}
	}
	/*
	 *  The destination is not present in the global destination set, and
	 *  when added, the destination should be added with "prevdest" as
	 *  its predecessor in the set (unless "prevdest" is null, in which
	 *  case the new destination must be added at the very beginning of
	 *  the set).
	 */
	dest = (struct pipeto *) malloc (sizeof (struct pipeto));
	if (dest == (struct pipeto *) 0) {
		/*
		 *  There is not sufficient memory to create the
		 *  global destination descriptor.
		 */
		return ((struct pipeto *) 0);	/* Return NIL pointer */
	}
	dest->refcount = 0;		/* No references yet made */
	dest->status = DEST_ENABLED;	/* New destinations are enabled */
	dest->retry_at = 0;		/* Not in retry mode */
	dest->retrytime = 0;		/* Not in retry mode */
	dest->retrydelta = 0;		/* No retry delta defined */
	strcpy (dest->rqueue, rquename);/* Save name of destination queue */
	dest->rhost_mid = rhost_mid;	/* Remember destination machine */
	/*
	 *  Now, add the destination to the global destination set.
	 */
	if (prevdest == (struct pipeto *) 0) {
		dest->next = Pipetoset;	/* Add to set head */
		Pipetoset = dest;	/* Update head ptr */
	}
	else {				/* Add to set */
		dest->next = prevdest->next;
		prevdest->next = dest;
	}
	if (Booted) {
		/*
		 *  NQS is completely "booted", and so this destination
		 *  descriptor needs to be explicitly added to the NQS
		 *  database.
		 */
		udb_credes (dest);	/* Update the database image */
	}
	return (dest);			/* Return ptr to destination */
}


/*** upd_deldes
 *
 *
 *	void upd_deldes():
 *
 *	Delete a pipe queue destination from the global pipe queue
 *	destination set.
 */
void upd_deldes (struct pipeto *pipeto)
{
	struct pipeto *dest;
	struct pipeto *prevdest;

	/*
	 *  Locate the pipe queue destination.
	 */
	prevdest = (struct pipeto *) 0;
	dest = Pipetoset;
	while (dest != (struct pipeto *) 0 && dest != pipeto) {
		prevdest = dest;	/* Keep searching */
		dest = dest->next;	/* destination set */
	}
	if (dest != (struct pipeto *) 0) {
		/*
		 *  Delete the queue destination.
		 */
		if (dest->refcount) {
			printf ("F$NQS internal error: ");
			printf ("upd_deldes() ref != 0.\n");
			errno = 0;	/* Not a system call error */
			nqs_abort();	/* Abort execution */
		}
		udb_deldes (dest);	/* Update the NQS database image */
		if (prevdest == (struct pipeto *) 0) {
			Pipetoset = dest->next;
		}
		else prevdest->next = dest->next;
		free ((char *) dest);
	}
	else {
		printf ("F$NQS internal error: upd_deldes() no exist.\n");
		errno = 0;		/* Not a system call error */
		nqs_abort();		/* Abort execution */
	}
}


/*** upd_delquedes
 *
 *
 *	long upd_delquedes():
 *	Delete a pipe queue destination.
 *
 *	Returns:
 *		TCML_COMPLETE:	if successful.
 *		TCML_NOSUCHQUE:	if no such queue exists.
 *		TCML_NOSUCHDES:	if no such destination exists for queue.
 *		TCML_WROQUETYP:	if the queue is not a pipe queue.
 */
long upd_delquedes (
	char *lquename,		/* Local queue name */
	char *rquename,		/* Remote queue name */
	Mid_t rhost_mid)	/* Machine-id for destination machine */
{
	struct nqsqueue *queue;
	struct qdestmap *mapdest;
	struct qdestmap *prevmapdest;
	short found;
	short res;

	queue = nqs_fndnnq (lquename);
	if (queue == (struct nqsqueue *)0) return (TCML_NOSUCHQUE);
	if (queue->q.type != QUE_PIPE) return (TCML_WROQUETYP);
	/*
	 *  Locate the queue destination.
	 */
	prevmapdest = (struct qdestmap *) 0;
	mapdest = queue->v1.pipe.destset;
	found = 0;
	while (mapdest != (struct qdestmap *) 0 && !found) {
		if (mapdest->pipeto->rhost_mid > rhost_mid) {
			mapdest = (struct qdestmap *) 0;
		}
		else if (mapdest->pipeto->rhost_mid == rhost_mid) {
			/*
			 *  We have the correct machine-destination.
			 *  Now, try to find the queue.
			 */
			res = strcmp (mapdest->pipeto->rqueue, rquename);
			if (res < 0) {
				prevmapdest = mapdest;	/* Keep searching */
				mapdest = mapdest->next;/* destination set */
			}
			else if (res == 0) found = 1;	/* Found! */
			else {				/* No such dest */
				mapdest = (struct qdestmap *) 0;
			}
		}
		else {
			prevmapdest = mapdest;
			mapdest = mapdest->next;
		}
	}
	if (!found) return (TCML_NOSUCHDES);
	/*
	 *  Delete the queue/destination mapping.
	 */
	udb_delquedes (mapdest);	/* Update the NQS database image */
	if (--mapdest->pipeto->refcount == 0) {
		/*
		 *  No more references exist to this pipe queue destination.
		 *  Delete it.
		 */
		upd_deldes (mapdest->pipeto);
	}
	if (prevmapdest == (struct qdestmap *) 0) {
		queue->v1.pipe.destset = mapdest->next;
	}
	else prevmapdest->next = mapdest->next;
	free ((char *) mapdest);
	return (TCML_COMPLETE);
}


/*** upd_destination
 *
 *
 *	long upd_destination():
 *	Update the state of a pipe queue destination.
 *
 *	Returns:
 *		TCML_COMPLETE:	if successful.
 *		TCML_NOSUCHDES:	if no such pipe queue destination.
 */
long upd_destination (
	char *rquename,		/* Name of remote queue */
	Mid_t rhost_mid,	/* Machine-id of destination */
	int cmd,		/* Command: 0=enable; 1=fail; */
				/* 2=schedule for retry */
	long retry_wait)	/* Schedule retry time */
{
	struct pipeto *dest;	/* Pipe queue destination */
	short found;		/* Found flag */
	short res;		/* strcmp() result */
	time_t timenow;	/* Current time */

	/*
	 *  Locate the pipe queue destination.
	 */
	dest = Pipetoset;
	found = 0;
	while (dest != (struct pipeto *) 0 && !found) {
		if (dest->rhost_mid > rhost_mid) dest = (struct pipeto *) 0;
		else if (dest->rhost_mid == rhost_mid) {
			/*
			 *  We have the correct machine-destination.
			 *  Now, try to find the queue.
			 */
			res = strcmp (dest->rqueue, rquename);
			if (res < 0) dest = dest->next;	/* Next destination */
			else if (res == 0) found = 1;	/* Found! */
			else dest = (struct pipeto *) 0;/* No such dest */
		}
		else dest = dest->next;	/* Next destination */
	}
	if (found) {
		/*
		 *  Update the queue destination state.
		 */
		if (cmd == 0) dest->status = DEST_ENABLED;
		else if (cmd == 1) dest->status = DEST_FAILED;
		else {			/* Schedule destination for retry */
			timenow = time ((time_t *) 0);
			if (!(dest->status & DEST_RETRY)) {
				/*
				 *  The destination was not previously in
				 *  retry mode.  Record the current time as
				 *  the time at which this destination entered
				 *  the retry state.
				 */
				dest->retrytime = timenow;
			}
			dest->status = DEST_RETRY; /* In retry wait state */
			dest->retry_at = timenow + retry_wait;
			/*
			 *  Set timer to re-enable the destination.
			 */
			nqs_vtimer (&dest->retry_at, nqs_wakdes);
		}
		udb_destination (dest);	/* Update the NQS database image */
		return (TCML_COMPLETE);	/* Update complete */
	}
	return (TCML_NOSUCHDES);	/* No such destination */
}


/*** upd_machine
 *
 *
 *	long upd_machine():
 *	Update the state of a machine destination.
 *
 *	Returns:
 *		TCML_COMPLETE:	if successful.
 *		TCML_NOSUCHMAC:	if no such machine is mentioned
 *				in any pipe queue destination.
 */
long upd_machine (
	Mid_t rhost_mid,		/* Machine-id of destination */
	int cmd,			/* Command: 0=enable; 1=fail; */
					/* 2=schedule for retry */
	long retry_wait)		/* Schedule retry time */
{
	struct pipeto *dest;	/* Pipe queue destination */
	short found;		/* Boolean */
	time_t timenow;	/* Current time */

	/*
	 *  Locate and operate upon all pipe queue destinations
	 *  at the stated machine.
	 */
	found = 0;
	dest = Pipetoset;
	while (dest != (struct pipeto *) 0) {
		if (dest->rhost_mid == rhost_mid) {
			/*
			 *  Perform update on this destination.
			 */
			found = 1;
			if (cmd == 0) dest->status = DEST_ENABLED;
			else if (cmd == 1) dest->status = DEST_FAILED;
			else {			/* Schedule destination */
				timenow = time ((time_t *) 0);
				if (!(dest->status & DEST_RETRY)) {
					/*
					 *  The destination was not previously
					 *  in retry mode.  Record the current
					 *  time as the time at which this
					 *  destination entered the retry
					 *  state.
					 */
					dest->retrytime = timenow;
				}
				dest->status = DEST_RETRY;
				dest->retry_at = timenow + retry_wait;
				/*
				 *  Set timer to re-enable the destination.
				 */
				nqs_vtimer (&dest->retry_at, nqs_wakdes);
			}
			udb_destination (dest);	/* Update NQS database image */
		}
		dest = dest->next;		/* Next destination */
	}
	if (found) return (TCML_COMPLETE);	/* Update complete */
	return (TCML_NOSUCHMAC);		/* No such destination */
}


/*** upd_setquedes
 *
 *
 *	long upd_setquedes():
 *	Set the queue destination set for the specified local pipe queue.
 *
 *	Returns:
 *		TCML_COMPLETE:	if successful.
 *		TCML_INSUFFMEM:	if insufficient memory to create queue
 *				destination descriptor.
 *		TCML_NOSUCHQUE:	if the named local queue did not exist.
 *		TCML_SELREFDES: if the the destination was self-
 *				referential (prohibited).
 *		TCML_WROQUETYP:	if the queue is not a pipe queue.
 */
long upd_setquedes (
	char *lquename,		/* Local queue name */
	char *rquename,		/* Remote queue name */
	Mid_t rhost_mid)	/* Machine-id for destination machine */
{
	struct nqsqueue *queue;
	struct qdestmap *mapdest;
	struct qdestmap *walkmapdest;
	struct qdestmap *nextmapdest;
	short found;
	short res;

	queue = nqs_fndnnq (lquename);
	if (queue == (struct nqsqueue *)0) {
		return (TCML_NOSUCHQUE);	/* No such local queue */
	}
	if (queue->q.type != QUE_PIPE) return (TCML_WROQUETYP);
	/*
	 *  Make sure that the destination is not self-referential.
	 */
	if (strcmp (rquename, queue->q.namev.name) == 0 &&
	    rhost_mid == Locmid) {
		/*
		 *  The destination is self-referential!
		 */
		return (TCML_SELREFDES);
	}
	/*
	 *  Determine if the new single destination for the queue already
	 *  exists in the destination set.
	 */
	mapdest = queue->v1.pipe.destset;
	found = 0;
	while (mapdest != (struct qdestmap *) 0 && !found) {
		if (mapdest->pipeto->rhost_mid > rhost_mid) {
			mapdest = (struct qdestmap *) 0;
		}
		else if (mapdest->pipeto->rhost_mid == rhost_mid) {
			/*
			 *  We have the correct machine-destination.
			 *  Now, try to find the queue.
			 */
			res = strcmp (mapdest->pipeto->rqueue, rquename);
			if (res < 0) {		/* Keep searching */
				mapdest = mapdest->next;
			}
			else if (res == 0) found = 1;	/* Found! */
			else {				/* No such dest */
				mapdest = (struct qdestmap *) 0;
			}
		}
		else mapdest = mapdest->next;
	}
	if (!found) {
		/*
		 *  The specified queue destination is not already present
		 *  within the destination set for the pipe queue.
		 *  Add the required destination.
		 */
		mapdest = (struct qdestmap *) malloc (sizeof (struct qdestmap));
		if (mapdest == (struct qdestmap *) 0) {
			return (TCML_INSUFFMEM);/* Insufficient memory */
		}
		mapdest->pipeto = upd_credes (rquename, rhost_mid);
		if (mapdest->pipeto == (struct pipeto *) 0) {
			free ((char *) mapdest);/* Free mapping structure */
			return (TCML_INSUFFMEM);/* Insufficient memory */
		}
		mapdest->pipeto->refcount++;	/* Increment reference count */
		udb_addquedes (queue, mapdest);	/* Update the database image */
		/*
		 *  Now, complete the process of adding the destination to
		 *  the destination set for this pipe queue (the new
		 *  destination is not added in lexicographic order, but
		 *  this does not matter, since we are are going to delete
		 *  all other destinations anyway....).
		 */
		mapdest->next = queue->v1.pipe.destset;	/* Add to set */
		queue->v1.pipe.destset = mapdest;	/* Update head ptr */
	}
	/*
	 *  Now, walk the destination set to delete ALL destinations with
	 *  the exception of the chosen single destination.
	 */
	walkmapdest = queue->v1.pipe.destset;
	while (walkmapdest != (struct qdestmap *) 0) {
		nextmapdest = walkmapdest->next;
		if (walkmapdest != mapdest) {
			/*
			 *  Delete the queue/destination mapping.
			 */
			udb_delquedes (walkmapdest);	/* Update database */
			if (--walkmapdest->pipeto->refcount == 0) {
				/*
				 *  No more references exist to this pipe queue
				 *  destination.  Delete it.
				 */
				upd_deldes (walkmapdest->pipeto);
			}
			free ((char *) walkmapdest);
		}
		walkmapdest = nextmapdest;	/* Keep walking the set */
	}					/* until done */
	queue->v1.pipe.destset = mapdest;
	mapdest->next = (struct qdestmap *) 0;
	if ((queue->q.status & QUE_RUNNING) && queue->q.queuedcount &&
	    (mapdest->pipeto->status & DEST_ENABLED)) {
		psc_spawn();			/* Maybe spawn a pipe req */
	}
	return (TCML_COMPLETE);			/* Return completion code */
}
