/*
 *	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     29-Oct-1991	Added debug flag for interactive pgms.
*       V01.30  JRR     16-Jan-1992	Added support for RS6000.
*       V01.4   JRR     12-Feb-1992	Fixed references to static routines.
*       V01.5   JRR     02-Mar-1992	Added Cosmic V2 changes.
*       V01.6   JRR     09-Apr-1992     Added CERN enhancements.
*	V01.7	JRR	17-Jun-1992	Added header.
*	V01.8	JRR	10-Nov-1992	Added support for HPUX.
*	V01.9	JRR	26-Feb-1993	Added Boeing enhancement for Mids.
*	V01.10	JRR	12-Aug-1993	Removed non-networked code.
*	V01.11	JRR	15-Mar-1994	Added some debug messages.
*/
/*++ pipeclient.c - Network Queueing System
 *
 * $Source: /usr2/jrroma/nqs/nqs-3.35.6/src/RCS/pipeclient.c,v $
 *
 * DESCRIPTION:
 *
 *	NQS pipe client.  Enqueues requests at pipe queue destinations.
 *
 *
 *	Authors:
 *	--------
 *	Brent A. Kingsbury, Robert W. Sandstrom.
 *	Sterling Software Incorporated.
 *	May 24, 1986.
 *
 *
 * STANDARDS VIOLATIONS:
 *   None.
 *
 * REVISION HISTORY: ($Revision: 1.11 $ $Date: 1994/03/30 20:37:02 $ $State: Exp $)
 * $Log: pipeclient.c,v $
 * Revision 1.11  1994/03/30  20:37:02  jrroma
 * Version 3.35.6
 *
 * Revision 1.10  93/09/10  13:57:24  jrroma
 * Version 3.35
 * 
 * Revision 1.9  93/07/13  21:34:05  jrroma
 * Version 3.34
 * 
 * Revision 1.8  92/12/22  15:42:48  jrroma
 * Version 3.30
 * 
 * Revision 1.7  92/06/18  17:31:36  jrroma
 * Added gnu header
 * 
 * Revision 1.6  92/05/06  10:45:12  jrroma
 *  Version 3.20
 * 
 * Revision 1.5  92/03/02  16:48:40  jrroma
 * Added Cosmic V2 changes.
 * 
 * Revision 1.4  92/02/12  11:39:02  jrroma
 * Fixed references to static routines
 * 
 * Revision 1.3  92/01/17  11:21:27  jrroma
 * Added support for RS6000.
 * 
 * Revision 1.2  91/10/29  10:16:46  jrroma
 * Added debug flag for interactive pgms.
 * 
 * Revision 1.1  91/10/29  10:16:05  jrroma
 * Initial revision
 * 
 *
 */

#include "nqs.h"			/* Types and definitions */
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include "nqspacket.h"			/* Packets to the local daemon */
#include "netpacket.h"			/* Packets to remote machines */
#include "informcc.h"			/* Information completion codes */
#include "requestcc.h"			/* Request completion codes */
#include "transactcc.h"			/* Transaction completon codes */
#include "nqsdirs.h"			/* For nqslib library functions */



#ifndef __CEXTRACT__
#if __STDC__

static int badfailure ( long transactcc );
static long commit ( int sd );
static long deleteold ( Mid_t destmid, struct rawreq *rawreq, int cuid, char *cusername );
static long deliver ( Mid_t destmid, struct rawreq *rawreq, int cuid, char *cusername );
static void evalandexit ( long transactcc, char *quename, Mid_t destmid, long wait, long basercm, struct rawreq *rawreq );
static void evalmaybeexit ( long transactcc, char *quename, Mid_t destmid, long wait, short *thereshope, long *finalrcm );
static void ourserexit ( long rcm, char *servermssg );
static void pipe_brokenpipe ( void );
static void reportdep ( struct rawreq *rawreq );
static void reportenable ( Mid_t mid );
static void reportpredep ( struct rawreq *rawreq, Mid_t destmid, char *destqueue );
static void reportschdes ( char *quename, Mid_t destmid, long wait );
static void reportschreq ( struct rawreq *rawreq, long wait );
static void reportstasis ( struct rawreq *rawreq );

#else /* __STDC__ */

static int badfailure (/* long transactcc */);
static long commit (/* int sd */);
static long deleteold (/* Mid_t destmid, struct rawreq *rawreq, int cuid, char *cusername */);
static long deliver (/* Mid_t destmid, struct rawreq *rawreq, int cuid, char *cusername */);
static void evalandexit (/* long transactcc, char *quename, Mid_t destmid, long wait, long basercm, struct rawreq *rawreq */);
static void evalmaybeexit (/* long transactcc, char *quename, Mid_t destmid, long wait, short *thereshope, long *finalrcm */);
int main (/* int argc, char *argv [], char *envp [] */);
static void pipe_brokenpipe (/* void */);
static void ourserexit (/* long rcm, char *servermssg */);
static void reportdep (/* struct rawreq *rawreq */);
static void reportenable (/* Mid_t mid */);
static void reportpredep (/* struct rawreq *rawreq, Mid_t destmid, char *destqueue */);
static void reportschdes (/* char *quename, Mid_t destmid, long wait */);
static void reportschreq (/* struct rawreq *rawreq, long wait */);
static void reportstasis (/* struct rawreq *rawreq */);

#endif /* __STDC__ */
#endif /* __CEXTRACT__ */

/*
 *	Variables global to this module.
 */
static int Debug;
int DEBUG;				/* Debug flag for interactive pgms. */

/*** main
 *
 *
 *	main():
 *
 *	This process is exec'd by the child of a shepherd process (child of
 *	the NQS daemon) with the following file descriptors open as described:
 *
 *		File descriptor 0: Control file (O_RDWR).
 *		File descriptor 1: Stdout writes to the NQS log process.
 *		File descriptor 2: Stderr writes to the NQS log process.
 *		File descriptor 3: Write file descriptor for serexit()
 *				   back to the shepherd process.
 *		File descriptor 4: Connected to the local NQS daemon
 *				   request FIFO pipe.
 *		File descriptors [5.._NFILE] are closed.
 *
 *
 *	At this time, this process is running with a real AND effective
 *	user-id of root!
 *
 *
 *	The current working directory of this process is the NQS
 *	root directory.
 *
 *
 *	All signal actions are set to SIG_DFL.
 *
 *
 *	The environment of this process contains the environment string:
 *
 *		DEBUG=n
 *
 *	where n specifies (in integer ASCII decimal format), the debug
 *	level in effect when this client was exec'd over the child of
 *	an NQS shepherd.
 *
 *
 *	The user upon whose behalf this work is being performed (the
 *	owner of the request), is identified by the environment variables:
 *
 *		UID=n
 *		USERNAME=username-of-request-owner
 *
 *	where n specifies (in integer ASCII decimal format), the integer
 *	user-id of the request owner.
 * 
 *
 *	For System V based implementations of NQS, the environment
 *	variable:
 *
 *		TZ=timezonename
 *
 *	will also be present.  Berkeley based implementations of NQS will
 *	not contain this environment variable.
 *
 *
 *	The environment of this process also contains the default retry
 *	state tolerance time, and the default retry wait time recommenda-
 *	tions (in seconds):
 *
 *		DEFAULT_RETRYWAIT=n
 *		DEFAULT_RETRYTIME=n
 *
 *	where n is an ASCII decimal format long integer number.
 *
 *
 *	Additional environment vars define the possible destination set
 *	for the request:
 *
 *		Dnnn=mmm rrr queuename
 *
 *	where:
 *
 *		nnn	  specifies the destination number (in ASCII
 *			  decimal) in the range [0..#of destinations-1];
 *		mmm	  specifies the machine-id of the destination
 *			  (in ASCII decimal).
 *		rrr	  specifies the amount of time that this
 *			  destination has spent in a retry mode.
 *		queuename specifies the name of the destination queue
 *			  at the destination machine.
 *
 *
 *	Lastly, the presence of the environment variable:
 *
 *		PERSIST=Y
 *
 *	indicates that the request should be requeued, even if all of the
 *	destinations indicate that they will never accept the request.  The
 *	presence of the PERSIST environment variable is a signal to the
 *	pipe queue server (pipeclient), that there are presently disabled
 *	destination(s) for the associated pipe queue, that MIGHT accept the
 *	request at a later time.
 *
 */
main (argc, argv, envp)
int argc;
char *argv [];
char *envp [];
{

	char *cp;			/* Ascii value of env var */
	int cuid;			/* Client user id */
	char *cusername;		/* Client username */
	/* long dretrytime; */		/* Default seconds before we give up */
	long dretrywait;		/* Default seconds per wait */
	struct rawreq rawreq;		/* What we are about to send */
	struct transact transact;	/* Holds output of tra_read() */
	short thereshope;		/* Boolean */
	long finalrcm;			/* Or several rcms into here */
	register int destno;		/* Destination number */
	long transactcc;		/* Transaction completion code */
	long quereqinfo;		/* Info from NPK_QUEREQ transaction */
	register char *destqueue;	/* Destination queue */
	Mid_t destmid;			/* Destination machine-id */
	long retrytime;			/* The amount of time that the */
					/* current destination has spent */
					/* in a retry mode */
	int sd;				/* Socket descriptor */

	/*
	 *  On some UNIX implementations, stdio functions alter errno to
	 *  ENOTTY when they first do something where the file descriptor
	 *  corresponding to the stream happens to be a pipe (which is
	 *  the case here).
	 *
	 *  The fflush() calls are added to get this behavior out of the
	 *  way, since bugs have occurred in the past when a server ran
	 *  into difficultly, and printed out an errno value in situations
	 *  where the diagnostic printf() displaying errno occurred AFTER
	 *  the first stdio function call invoked.
	 */
	fflush (stdout);		/* Stdout is a pipe */
	fflush (stderr);		/* Stderr is a pipe */
	/*
	 * We must be root to bind to a reserved port.
	 */
	if (getuid() != 0 || geteuid() != 0) {
	        printf("F$pipeclient: unable to get uid or euid, UNAFAILURE.\n");
		fflush(stdout);
		ourserexit (RCM_UNAFAILURE, (char *) 0);
	}
	if ((cp = getenv ("DEBUG")) == (char *) 0) {
		Debug = 0;
	}
	else Debug = atoi (cp);
	interset (4);		/* Use this file descriptor to communicate */
				/* with the local NQS daemon */
	if ((cp = getenv ("UID")) == (char *) 0) {
		ourserexit (RCM_BADSRVARG, (char *) 0);
	}
	cuid = atoi (cp);
	if ((cusername = getenv ("USERNAME")) == (char *) 0) {
		ourserexit (RCM_BADSRVARG, (char *) 0);
	}
	if ((cp = getenv ("DEFAULT_RETRYTIME")) == (char *) 0) {
		ourserexit (RCM_BADSRVARG, (char *) 0);
	}
	/* dretrytime = atoi (cp); */
	if ((cp = getenv ("DEFAULT_RETRYWAIT")) == (char *) 0) {
		ourserexit (RCM_BADSRVARG, (char *) 0);
	}
	dretrywait = atoi (cp);
	if (Debug > 2) {
		while (*envp != (char *) 0) {
			printf ("D$Pipeclient envp: %s\n", *envp);
			envp++;
		}
		fflush (stdout);
	}
	/*
	 * Readreq leaves the file pointer pointing to the first
	 * byte beyond the header.
	 */
	if (readreq (STDIN, &rawreq) == -1) {
		ourserexit (RCM_BADCDTFIL, (char *) 0);
	}
	/*
	 * Find out whether we are starting from scratch.
	 */
	if (tra_read (rawreq.trans_id, &transact) == -1) {
	        printf("F$pipeclient, tra_read failed for %d UNAFAILURE\n",
		       rawreq.orig_seqno);
		fflush(stdout);
		ourserexit (RCM_UNAFAILURE, (char *) 0);
	}
	switch (transact.state) {
	/*
	 * After network queues are implemented,
	 * it will no longer be possible for pipeclient
	 * to called on a request in RTS_DEPART state.
	 */
	case RTS_DEPART:
		transactcc = deliver (transact.v.peer_mid, &rawreq,
				      cuid, cusername);
		if (Debug > 2) {
			tcmmsgs (transactcc, stdout, "D$Pipeclient: redeliver ");
		}
		if (pipeqenable (transactcc)) {
			reportenable (transact.v.peer_mid);
		}
		switch (transactcc) {
		case TCMP_COMPLETE:
			ourserexit (RCM_DELIVERED, (char *) 0);
		case TCMP_NOSUCHREQ:
			ourserexit (RCM_PIPREQDEL, (char *) 0);
		default:
			evalandexit (transactcc, rawreq.trans_quename,
				transact.v.peer_mid, dretrywait,
				RCM_DELIVERFAI, &rawreq);
		}
	case RTS_PREDEPART:
		transactcc = deleteold (transact.v.peer_mid, &rawreq,
					cuid, cusername);
		if (Debug > 2) {
			tcmmsgs (transactcc, stdout, "D$Pipeclient: deleteold ");
		}
		if (transactcc != TCMP_CONTINUE &&
		    transactcc != TCMP_NOSUCHREQ) {
			evalandexit (transactcc, rawreq.trans_quename,
				     transact.v.peer_mid, dretrywait,
				     RCM_ROUTEFAI, &rawreq);
		}
		reportenable (transact.v.peer_mid);
		reportstasis (&rawreq);
		/*
		 * Now we have a clean slate.
		 * FALL THROUGH.
		 */
	case RTS_ARRIVE:
	case RTS_STASIS:
		/*
		 * Our assumptions, until better information can be obtained:
		 * (finalrcm): We don't know WHY any unretryable destinations
		 * are unretryable.
		 * (thereshope): There are NO known retryable destinations.
		 */
		finalrcm = RCM_ROUTEFAI;
		thereshope = 0;
		destno = 1;		/* Try destination #1 first */
		if ((destqueue = pipeqdest (destno, &destmid,
					    &retrytime)) == (char *) 0) {
			if (getenv ("PERSIST") != (char *) 0) {
				/*
				 *  The request should be allowed to persist.
				 */
				reportschreq (&rawreq, dretrywait);
				ourserexit (RCM_RETRYLATER, (char *) 0);
			}
			ourserexit (RCM_ROUTEFAI, (char *) 0);
		}
		do {
			/*
			 *  Another destination exists.  Try it.
			 */
			if (Debug > 2) {
				printf ("D$Pipeclient: dest=%1d: ", destno);
				printf ("mid=%1u;  queue=%s\n", destmid,
					destqueue);
				fflush (stdout);
			}
			/*
			 * Pipeqreq() returns when 
			 * 1) the request is in pre-arrive state
			 *	on the remote machine, or
			 * 2) the request has been submitted
			 *	on the local machine.
			 */
			transactcc = pipeqreq (destmid, destqueue, &rawreq,
					       cuid, cusername, &sd);
			if (Debug > 2) {
			    tcmmsgs (transactcc, stdout, "D$Pipeclient: pipeqreq ");
			}
			if (pipeqenable (transactcc)) reportenable (destmid);
			if (transactcc != TCMP_CONTINUE &&
			   (transactcc & XCI_FULREA_MASK) != TCML_SUBMITTED) {
				evalmaybeexit (transactcc, destqueue,
					destmid, dretrywait,
					&thereshope, &finalrcm);
			}
		} while (
			transactcc != TCMP_CONTINUE &&
			(transactcc & XCI_FULREA_MASK) != TCML_SUBMITTED &&
			(destqueue = pipeqdest (++destno, &destmid,
				&retrytime)) != (char *) 0
			);
		if (Debug > 2) {
		    tcmmsgs (transactcc, stdout, "D$Pipeclient: pipeqdest ");
		}
		if ((transactcc & XCI_FULREA_MASK) == TCML_SUBMITTED) {
			signal (SIGPIPE, pipe_brokenpipe);
			ourserexit (RCM_ROUTEDLOC, (char *) 0);
		}
		if (destqueue == (char *) 0) {
			if (thereshope == 1 ||
			    getenv ("PERSIST") != (char *) 0) {
				/*
				 *  The request should be allowed to persist.
				 */
				reportschreq (&rawreq, dretrywait);
				ourserexit (RCM_RETRYLATER, (char *) 0);
			}
			else {
				ourserexit (finalrcm, (char *) 0);
			}
		}
		/*
		 * Only TCMP_CONTINUE can reach this point.
		 * Tell the local daemon that the remote machine liked packet 2.
		 */
		if (Debug > 2) {
		    printf ("D$Pipeclient: before reppredep ");
		    fflush (stdout);
		}
		reportpredep (&rawreq, destmid, destqueue);
		break;
	default:
		printf("F$pipeclient: Unknown transaction state %d UNAFAILURE\n",
		       transact.state);
		fflush(stdout);
		ourserexit (RCM_UNAFAILURE, (char *) 0);
	}
	/*
	 * At this point, the request is in the RTS_PREDEPART state,
	 * we know the destination, and we have a socket open
	 * to the destination machine.  Send packet 3.
	 */
	transactcc = commit (sd);
	if (Debug > 2) {
		tcmmsgs (transactcc, stdout, "D$Pipeclient: commit ");
	}
	if ((transactcc & XCI_FULREA_MASK) != TCMP_SUBMITTED) {
		evalandexit (transactcc, rawreq.trans_quename,
			transact.v.peer_mid, dretrywait,
			RCM_ROUTEFAI, &rawreq);
	}
	/*
	 * Remember the information bits, so that we can tell
	 * our parent when we exit.
	 * Tell the local daemon that the remote machine liked packet 3.
	 */
	quereqinfo = transactcc & XCI_INFORM_MASK;
	reportdep (&rawreq);
	/*
	 * After network queues are implemented, we will exit here
	 * with RCM_ROUTED, passing any information bits (quereqinfo)
	 * back to our caller.
	 */
	/*
	 * Start a new conversation.  Send the tail of the
	 * control file. Send all of the data files.
	 * Eventually, CODE BELOW THIS POINT WILL BECOME PART OF A
	 * DIFFERENT PROCESS, hence the duplication of effort.
	 * That different process will have cuid and cusername as
	 * environmental variables.
	 * Readreq leaves the file pointer pointing to the first
	 * byte beyond the header.
	 */
	if (readreq (STDIN, &rawreq) == -1) {
		ourserexit (RCM_BADCDTFIL, (char *) 0);
	}
	/*
	 * Find out whether we are starting from scratch.
	 */
	if (tra_read (rawreq.trans_id, &transact) == -1) {
		ourserexit (RCM_BADCDTFIL, (char *) 0);
	}
	if (transact.state != RTS_DEPART) {
		ourserexit (RCM_BADCDTFIL, (char *) 0);
	}
	transactcc = deliver (transact.v.peer_mid, &rawreq, cuid, cusername);
	if (Debug > 2) {
		tcmmsgs (transactcc, stdout, "D$Pipeclient: deliver ");
	}
	switch (transactcc) {
	case TCMP_COMPLETE:
		ourserexit (RCM_DELIVERED | quereqinfo, (char *) 0);
	case TCMP_NOSUCHREQ:
		ourserexit (RCM_PIPREQDEL, (char *) 0);
	default:
		evalandexit (transactcc, rawreq.trans_quename,
			transact.v.peer_mid, dretrywait,
			RCM_DELIVERFAI, &rawreq);
	}
}


/*** badfailure
 *
 *	int badfailure:
 *
 *	Return 1 if this is a bad failure.
 *	Return 0 if this is a success or a mild failure.
 */
static int badfailure (transactcc)
long transactcc;
{
	return (transactcc == TCML_NOLOCALDAE || transactcc == TCML_PROTOFAIL);
}


/*** commit
 *
 *	long commit:
 *
 *	Send a packet to the remote machine committing enqueueing
 *	at the previously attempted destination.
 *	This is the 2nd phase of the 2 phase commit.
 *	Returns: a TCM.
 */
static long commit (sd)
int sd;						/* Socket descriptor */
{
	char packet [MAX_PACKET];
	int packetsize;
	int integers;
	int strings;
	
	interclear ();
	interw32i (NPK_COMMIT);
	if ((packetsize = interfmt (packet)) == -1) {
		return (TCML_INTERNERR);
		
	}
	if (write (sd, packet, packetsize) != packetsize) {
		return (TCML_INTERNERR);
			
	}
	switch (interread (getsockch)) {
	case 0:
		break;
	case -1:
		return (TCMP_CONNBROKEN);
	case -2:
		return (TCMP_PROTOFAIL);
	}
	integers = intern32i ();
	strings = internstr ();
	if (integers == 1 && strings == 0) {
		return (interr32i (1));
	}
	else return (TCMP_PROTOFAIL);
}


/*** deleteold
 *
 *	long deleteold:
 *
 *	This function establishes a fresh connection with
 *	destmid.  We tell destmid that it should delete
 *	the request in question.
 *
 */
static long deleteold (destmid, rawreq, cuid, cusername)
Mid_t destmid;				/* Destination machine id */
struct rawreq *rawreq;			/* Request info */
int cuid;				/* Client user id */
char *cusername;			/* Client username */
{
	int sd;				/* Socket descriptor */
	short timeout;			/* Seconds between tries */
	long transactcc;		/* Holds establish() return value */

	interclear ();
	interw32i ((long) cuid);		/* Client uid */
	interwstr (cusername);			/* Client username */
	interw32i (rawreq->orig_seqno);		/* Original sequence number */
	interw32u (rawreq->orig_mid);		/* Original machine id */
	interw32i (0L);				/* Signal (none) */
	interw32i ((long) RQS_ARRIVING);	/* Request queueing state */
	/*
	 * Send the first and only packet of the deleteold connection.
	 */
	sd = establish (NPK_DELREQ, destmid, cuid, cusername, &transactcc);
	if (sd == -2) {
		/*
		 * Retry is in order.
		 */
		timeout = 1;
		do {
			nqssleep (timeout);
			interclear ();
			interw32i ((long) cuid);
			interwstr (cusername);
			interw32i (rawreq->orig_seqno);
			interw32u (rawreq->orig_mid);
			interw32i (0L);
			interw32i ((long) RQS_ARRIVING);
			sd = establish (NPK_DELREQ, destmid,
				cuid, cusername, &transactcc);
			timeout *= 2;
		} while (sd == -2 && timeout <= 16);
	}
	return (transactcc);
}


/*** deliver
 *
 *	long deliver:
 *
 *	This function establishes a fresh connection with
 *	destmid. We send over the tail of the control file.
 *	We send over all data files.
 *	In the near future, the network queue client
 *	take the place of deliver().
 *
 */
static long deliver (destmid, rawreq, cuid, cusername)
Mid_t destmid;				/* Destination machine id */
struct rawreq *rawreq;			/* Request info */
int cuid;				/* Client user id */
char *cusername;			/* Client username */
{
	int sd;				/* Socket descriptor */
	short timeout;			/* Seconds between tries */
	long transactcc;		/* Holds establish() return value */
	long headersize;		/* Bytes in control file header */
	long totalsize;			/* Bytes in control file */
	char packet [MAX_PACKET];	/* Buffer area */
	int packetsize;			/* Bytes in this packet */
	int strings;			/* Number of strings */
	int integers;			/* Number of integers */
	int i;				/* Loop index */
	char packedname [29];		/* Dir:14, file:14, slash:1 */
	struct stat statbuf;		/* Holds stat() output */
	int datafd;			/* File descriptor of data file */

	interclear ();
	/*
	 * Send the first packet of the delivery connection.
	 */
	sd = establish (NPK_REQFILE, destmid, cuid, cusername, &transactcc);
	if (sd < 0) {
		if (sd == -2) {
			/*
			 * Retry is in order.
			 */
			timeout = 1;
			do {
				nqssleep (timeout);
				interclear ();
				sd = establish (NPK_REQFILE, destmid,
					cuid, cusername, &transactcc);
				timeout *= 2;
			} while (sd == -2 && timeout <= 16);
			/*
			 * Beyond this point, give up on retry.
			 */
			if (sd < 0) {
				return (transactcc);
			}
		}
		else {
			/*
			 * Retry would surely fail.
			 */
			return (transactcc);
		}
	}
	/*
	 * The server liked us.  The next step is to determine
	 * the length of the tail of the control file.
	 * We count on the file pointer being undisturbed from above.
	 */
	if ((headersize = lseek (STDIN, 0L, 1)) == -1) {
		return (errnototcm());
	}
	if ((totalsize = lseek (STDIN, 0L, 2)) == -1) {
		return (errnototcm());
	}
	interclear();
	interw32i ((long) NPK_REQFILE);
	interw32i ((long) -1);		/* "-1" means "the control file" */
	interw32i ((long) totalsize - headersize);
	interw32i ((long) rawreq->orig_seqno);
	interw32u ((long) rawreq->orig_mid);
	if ((packetsize = interfmt (packet)) == -1) {
		return (TCML_INTERNERR);
	}
	if (write (sd, packet, packetsize) != packetsize) {
		return (TCMP_CONNBROKEN);
	}
	/*
	 * See if the server got packet 2 of the delivery connection.
	 */
	switch (interread (getsockch)) {
	case 0:
		break;
	case -1:
		return (TCMP_CONNBROKEN);
	case -2:
		return (TCML_PROTOFAIL);
	}
	integers = intern32i();
	strings = internstr();
	if (integers != 1 || strings != 0) {
		return (TCML_PROTOFAIL);
	}
	switch (transactcc = interr32i (1)) {
	case TCMP_CONTINUE:
		break;
	case TCMP_NOSUCHREQ:			/* Deleted or already ran */
	case TCMP_COMPLETE:			/* Already delivered */
	default:
		return (transactcc);
	}
	/*
	 * Send the tail of the control file as the beginning of
	 * packet 3 of the delivery connection.
	 */
	lseek (STDIN, (long) headersize, 0);
	if (Debug > 2) {
		printf ("D$Pipeclient (deliver): about to send tail\n");
		fflush (stdout);
	}
	if (filecopyentire (STDIN, sd) != totalsize - headersize) {
		return (errnototcm ());
	}
	for (i = 0; i < rawreq->ndatafiles; i++) {
		pack6name (packedname, Nqs_data,
			(int) rawreq->orig_seqno % MAX_DATASUBDIRS,
			(char *) 0, rawreq->orig_seqno, 5,
			(Mid_t) rawreq->orig_mid, 6, i, 3);
		if (stat (packedname, &statbuf) == -1) {
			return (TCML_INTERNERR);
		}
		if ((datafd = open (packedname, O_RDONLY)) == -1) {
			return (TCML_INTERNERR);
		}
		interclear ();
		interw32i ((long) NPK_REQFILE);
		interw32i ((long) i);
		interw32i (statbuf.st_size);
		if ((packetsize = interfmt (packet)) == -1) {
			return (TCML_INTERNERR);
		}
		if (write (sd, packet, packetsize) != packetsize) {
			return (TCMP_CONNBROKEN);
		}
		switch (interread (getsockch)) {
		case 0:
			break;
		case -1:
			return (TCMP_CONNBROKEN);
		case -2:
			return (TCML_PROTOFAIL);
		}
		integers = intern32i();
		strings = internstr();
		if (integers == 1 && strings == 0) {
			if ((transactcc = interr32i (1)) != TCMP_CONTINUE) {
				return (transactcc);
			}
		}
		/*
		 * Send the data file here
		 */
		if (Debug > 2) {
			printf ("D$Pipeclient (deliver): about to send data #%d\n", i);
			fflush (stdout);
		}
		if (filecopyentire (datafd, sd) != statbuf.st_size) {
			return (errnototcm ());
		}
	}			/* End of for loop */
	/*
	 * We have already put a file into this packet.
	 * Tack a note onto the end saying that there are no more files.
	 */
	interclear ();
	interw32i ((long) NPK_DONE);
	interw32i ((long) 0);
	interw32i ((long) 0);
	if ((packetsize = interfmt (packet)) == -1) {
		return (TCML_INTERNERR);
	}
	if (write (sd, packet, packetsize) != packetsize) {
		return (TCMP_CONNBROKEN);
	}
	switch (interread (getsockch)) {
	case 0:
		break;
	case -1:
		return (TCMP_CONNBROKEN);
	case -2:
		return (TCML_PROTOFAIL);
	}
	integers = intern32i();
	strings = internstr();
	if (integers == 1 && strings == 0) {
		return (interr32i (1));
	}
	return (TCML_PROTOFAIL);
}


/*** evalandexit
 *
 *	void evalandexit:
 *
 *	Evaluate a transaction completion code, and act on any retry
 *	information.  Then exit, passing a request completion code to
 *	the shepherd process.
 */
static void evalandexit (transactcc, quename, destmid, wait, basercm, rawreq)
long transactcc;			/* Transaction completion code */
char *quename;				/* Name without @machine */
Mid_t destmid;				/* Destination machine id */
long wait;				/* Seconds before next try */
long basercm;				/* Generalized reason for failure */
struct rawreq *rawreq;			/* Pointer to request header info */
{
	
	switch (pipeqretry (transactcc)) {
	case 1:
		reportschdes (quename, destmid, wait);
		reportschreq (rawreq, wait);
		ourserexit (RCM_RETRYLATER, (char *) 0);
	case 0:
		pipeqexitifbad (transactcc);
		ourserexit (basercm | pipeqfailinfo (transactcc), (char *) 0);
	case -1:
		reportschdes (quename, destmid, wait);
		ourserexit (basercm | pipeqfailinfo (transactcc), (char *) 0);
	}
}


/*** evalmaybeexit
 *
 *
 *	void evalmaybeexit:
 *
 *	Evaluate a transaction completion code.
 *	If it indicates that a destination retry is in order, then tell the
 *	local daemon when to schedule the destination retry event.
 *	If it indicates that retry is not in order, or in a reason into
 *	finalrcm.  If you want to customize the pipeclient, this is a good
 *	place to start.
 */
static void evalmaybeexit (transactcc, quename, destmid, wait, thereshope,
			   finalrcm)
long transactcc;			/* Transaction completion code */
char *quename;				/* Name without @machine */
Mid_t destmid;				/* Machine id */
long wait;				/* Seconds before next try */
short *thereshope;			/* Pointer to boolean */
long *finalrcm;				/* Pointer to composite of reasons */
					/* for failure */
{
	
	switch (pipeqretry (transactcc)) {
	case 1:
		/* *thereshope = 1; */
		reportschdes (quename, destmid, wait);
	case 2:
		*thereshope = 1;
		break;
	case 0:
		pipeqexitifbad (transactcc);
		*finalrcm |= pipeqfailinfo (transactcc);
		break;
	case -1:
		reportschdes (quename, destmid, wait);
		break;
	}
}


/*** ourserexit
 *
 *	void ourserexit:
 *
 *	Give up any IPC file, close the FIFO pipe, and
 *	then tell the shepherd process what happened.
 */
static void ourserexit (rcm, servermssg)
long rcm;				/* Request completion code */
char *servermssg;			/* Additional text */
{
	fflush (stdout);
	exiting ();
	serexit (rcm, servermssg);
}


/*** reportdep
 *
 *	void reportdep:
 *
 *	Report that the request is ready to go to the departed state.
 *	Exit if inter() fails badly.
 */
static void reportdep (rawreq)
struct rawreq *rawreq;			/* Request info */
{
	long transactcc;
	struct transact transact;
	
	interclear ();
	interw32i (rawreq->orig_seqno);
	interw32u (rawreq->orig_mid);
	transactcc = inter (PKT_RMTDEPART);
	if (badfailure (transactcc)) {
	        printf("F$pipeclient: inter failed with oct %o UNAFAILURE\n",
		       transactcc);
		fflush(stdout);
		ourserexit (RCM_UNAFAILURE, (char *) 0);
	}
	if (transactcc == TCML_NOSUCHREQ)  {
		/*
		 * Should never happen.
		 */
		ourserexit (RCM_PIPREQDEL, (char *) 0);
	}
	/*
	 * Synchronously leave a record in non-volatile memory.
	 */
	tra_read (rawreq->trans_id, &transact);
	transact.state = RTS_DEPART;
	tra_setstate (rawreq->trans_id, &transact);
}


/*** reportenable
 *
 *	void reportenable:
 *
 *	Report that all destinations at a machine should be reenabled.
 *	Exit if inter() fails badly.
 */
static void reportenable (mid)
Mid_t mid;				/* Machine id */
{
	interclear ();
	interw32u ( mid);
	if (badfailure (inter (PKT_RMTENAMAC))) {
	        printf("F$pipeclient: Nolocaldae or Protofail UNAFAILURE\n");
		fflush(stdout);
		ourserexit (RCM_UNAFAILURE, (char *) 0);
	}
}


/*** reportpredep
 *
 *	void reportpredep:
 *
 *	Report that the request is ready to go to the predeparted state.
 */
static void reportpredep (rawreq, destmid, destqueue)
struct rawreq *rawreq;			/* Pointer to request header info */
Mid_t destmid;				/* Destination machine id */
char *destqueue;			/* Destination queue name */
{
	long transactcc;
	struct transact transact;
	
	interclear ();
	interw32i (rawreq->orig_seqno);
	interw32u (rawreq->orig_mid);
	transactcc = inter (PKT_RMTACCEPT);
	if (badfailure (transactcc)) {
	        printf("F$pipeclient: RMTACCEPT failed with nolocaldae or protofail \
UNAFAILURE\n");
		fflush(stdout);
		ourserexit (RCM_UNAFAILURE, (char *) 0);
	}
	if (transactcc == TCML_NOSUCHREQ)  {
		ourserexit (RCM_PIPREQDEL, (char *) 0);
	}
	strncpy (rawreq->trans_quename, destqueue, MAX_QUEUENAME + 1);
	writehdr (STDIN, rawreq);
	/*
	 * BEWARE!
	 * The machine might crash after the tra_setstate() call has
	 * changed the state to RTS_PREDEPART, but before the write
	 * buffer containing the new trans_quename has been written
	 * to disk.
	 */
	tra_read (rawreq->trans_id, &transact);
	transact.v.peer_mid = destmid;
	transact.state = RTS_PREDEPART;
	/*
	 * The order of update must guarantee that: IF the state has
	 * already been set, then the info fields have also been set.
	 */
	tra_setinfo (rawreq->trans_id, &transact);
	tra_setstate (rawreq->trans_id, &transact);
}


/*** reportschdes
 *
 *	void reportschdes:
 *
 *	Report that the destination should be retried after
 *	retrywait seconds.
 */
static void reportschdes (quename, destmid, wait)
char *quename;				/* Queue name missing @machine */
Mid_t destmid;				/* Destination machine id */
long wait;				/* Seconds before next retry */
{
	interclear ();
	interwstr (quename);
	interw32u (destmid);
	interw32i (wait);
	if (badfailure (inter (PKT_RMTSCHDES))) {
	        printf("F$pipeclient:  RMTSCHDES failed with nolocaldae or protofail\
 UNAFAILURE\n");
		fflush (stdout);
		ourserexit (RCM_UNAFAILURE, (char *) 0);
	}
}


/*** reportschreq
 *
 *	void reportschreq:
 *
 *	Report that the request should be retried after
 *	retrywait seconds.
 */
static void reportschreq (rawreq, wait)
struct rawreq *rawreq;			/* Pointer to request header info */
long wait;				/* Seconds before next retry */
{
	interclear ();
	interw32i (rawreq->orig_seqno);
	interw32u (rawreq->orig_mid);
	interw32i (wait);
	if (badfailure (inter (PKT_SCHEDULEREQ))) {
	        printf("F$pipeclient:  SCHEDULEREQ failed with nolocaldae or protofail\
 UNAFAILURE\n");
		fflush (stdout);
		ourserexit (RCM_UNAFAILURE, (char *) 0);
	}
}


/*** reportstasis
 *
 *	void reportstasis:
 *
 *	Report that the request should go back to the stasis state.
 */
static void reportstasis (rawreq)
struct rawreq *rawreq;			/* Pointer to request header info */
{
	struct transact transact;
	register long transactcc;
	
	/*
	 * Synchronously leave a record in non-volatile memory.
	 */
	tra_read (rawreq->trans_id, &transact);
	transact.v.peer_mid = 0;
	transact.state = RTS_STASIS;
	/*
	 * The order of update must guarantee that: IF the state has
	 * already been set, then the info fields have also been set.
	 */
	tra_setinfo (rawreq->trans_id, &transact);
	tra_setstate (rawreq->trans_id, &transact);
	/*
	 *  Tell the local NQS daemon, that the request is now back
	 *  in the stasis state.
	 */
	interclear ();
	interw32i (rawreq->orig_seqno);
	interw32u (rawreq->orig_mid);
	transactcc = inter (PKT_RMTSTASIS);
	if (badfailure (transactcc)) {
	        printf("F$pipeclient:  RMTSTASIS failed with nolocaldae or protofail\
 UNAFAILURE\n");
		fflush (stdout);
		ourserexit (RCM_UNAFAILURE, (char *) 0);
	}
	if (transactcc == TCML_NOSUCHREQ)  {
		ourserexit (RCM_PIPREQDEL, (char *) 0);
	}
}
static void
pipe_brokenpipe()
{
	if (Debug) {
	    printf ("D$pipeclient: caught SIGPIPE!!!!\n");
	    fflush (stdout);
	}
	exit (0);
}
