/*
 *	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.40  JRR     17-Jan-1992	Missed a mid_t reference.
*       V01.50  JRR     20-Jan-1992	Fix up a few more IBM-isms.
*       V01.60  JRR     12-Feb-1992	Fix up calls to static routines.
*       V01.7   JRR     02-Mar-1992	Added Cosmic V2 changes.
*       V01.8   JRR     13-Mar-1992	Add support for last chance.
*       V01.9   JRR     13-Mar-1992	Forgot the cases where trying to send
*					it over the network!
*       V01.10  JRR     20-Apr-1992     Make sure entire last chance path is there!
*	V01.11	JRR	17-Jun-1992	Added header.
*	V01.12	JRR	09-Nov-1992	Add support for HPUX.
*	V01.13	JRR	29-Dec-1992	Added groups security patches.
*	V01.14	JRR	22-Jul-1993	Fix some bugs (thanks to Karsten Gaier
*					(gaier@tat.physik.uni-tuebingen.de)
*	V01.15	JRR	21-Oct-1993	Fix format for mid.
*	V01.16	JRR	03-Mar-1994	Locmid is a Mid_t.
*/
/*++ netclient.c - Network Queueing System
 *
 * $Source: /usr2/jrroma/nqs/nqs-3.35.6/src/RCS/netclient.c,v $
 *
 * DESCRIPTION:
 *
 *
 *	Default NQS network client.  Empties network queues.
 *	Returns stdout, stderr, and stage-out files.
 *
 *
 *	Author:
 *	-------
 *	Robert W. Sandstrom, Sterling Software Incorporated.
 *	April 22, 1986.
 *
 *
 * STANDARDS VIOLATIONS:
 *   None.
 *
 * REVISION HISTORY: ($Revision: 1.16 $ $Date: 1994/03/30 20:36:23 $ $State: Exp $)
 * $Log: netclient.c,v $
 * Revision 1.16  1994/03/30  20:36:23  jrroma
 * Version 3.35.6
 *
 * Revision 1.15  94/02/24  21:30:32  jrroma
 * Version 3.35.3
 * 
 * Revision 1.14  93/09/10  13:56:58  jrroma
 * Version 3.35
 * 
 * Revision 1.13  93/02/05  23:16:42  jrroma
 * Version 3.31
 * 
 * Revision 1.12  92/12/22  15:39:42  jrroma
 * Version 3.30
 * 
 * Revision 1.11  92/06/18  17:30:47  jrroma
 * Added gnu header
 * 
 * Revision 1.10  92/05/06  10:37:34  jrroma
 *  Version 3.20
 * 
 * Revision 1.9  92/03/20  10:58:12  jrroma
 * *** empty log message ***
 * 
 * Revision 1.8  92/03/13  16:53:35  jrroma
 * Finished up last chance processing.
 * 
 * Revision 1.7  92/03/02  11:24:19  jrroma
 * Started to add Cosmic V2 changes.
 * 
 * Revision 1.6  92/02/12  11:22:54  jrroma
 * Fix up calls to static routines.
 * 
 * Revision 1.5  92/01/20  11:18:43  jrroma
 * Fixed a few more IBM-isms.
 * 
 * Revision 1.4  92/01/17  14:49:32  jrroma
 * Missed a mid_t reference
 * 
 * Revision 1.3  92/01/17  09:00:28  jrroma
 * Added support for RS6000.
 * 
 * Revision 1.2  91/10/29  10:13:19  jrroma
 * Added DEBUG flag for interactive pgms.
 * 
 * Revision 1.1  91/10/29  10:12:28  jrroma
 * Initial revision
 * 
 *
 */

#include "nqs.h"			/* Types and definitions */
#include <stdlib.h>
#include <grp.h>
#include <limits.h>
#include <signal.h>			/* For SIGPIPE, SIGTERM */
#include "string.h"
#include "netpacket.h"			/* Network packet types */
#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 */
#include <errno.h>			/* Error numbers */
#if	IBMRS
#include <sys/mode.h>
#endif
#if	HPUX
#include <unistd.h>
#endif

#ifndef __CEXTRACT__
#if __STDC__

static int copy ( char *temp, char *dest, int mode );
static void descravail ( void );
static long do_delivery ( struct rawreq *rawreq, uid_t cuid, char *cusername );
static long do_stageout ( struct rawreq *rawreq, uid_t cuid, char *cusername );
static long do_stderr ( struct rawreq *rawreq, struct passwd *passwd );
static long do_stdout ( struct rawreq *rawreq, struct passwd *passwd );
static void inodeavail ( void );
static void lostsink ( void );
static long rightrcm ( long remotercm, long localrcm );
static int robustopen ( char *path, int flags, int mode );
static long tryhere ( char *temp, char *dest, struct stat *statbuf, int mode );
static long trythere ( char *tempname, char *dest, Mid_t dest_mid, struct stat *statbuf, struct passwd *passwd );
static void waitdescr ( void );
static void waitinode ( void );

#else /* __STDC__ */

static int copy (/* char *temp, char *dest, int mode */);
static void descravail (/* void */);
static long do_delivery (/* struct rawreq *rawreq, uid_t cuid, char *cusername */);
static long do_stageout (/* struct rawreq *rawreq, uid_t cuid, char *cusername */);
static long do_stderr (/* struct rawreq *rawreq, struct passwd *passwd */);
static long do_stdout (/* struct rawreq *rawreq, struct passwd *passwd */);
static void inodeavail (/* void */);
static void lostsink (/* void */);
static long rightrcm ( /* long remotercm, long localrcm */ );
static int robustopen (/* char *path, int flags, int mode */);
static long tryhere (/* char *temp, char *dest, struct stat *statbuf, int mode */);
static long trythere (/* char *tempname, char *dest, Mid_t dest_mid, struct stat *statbuf, struct passwd *passwd */);
static void waitdescr (/* void */);
static void waitinode (/* void */);

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

/*
 * Variables global to this module.
 */
static Mid_t Locmid; 
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
 *	transaction owner), is identified by the environment variables:
 *
 *		UID=n
 *		USERNAME=username-of-transaction-owner
 *
 *	where n specifies (in integer ASCII decimal format), the integer
 *	user-id of the transaction 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, the default retry wait time, and the amount
 *	of time that the network destination has been in a retry mode
 *	(all times are in seconds):
 *
 *		DEFAULT_RETRYWAIT=n
 *		DEFAULT_RETRYTIME=n
 *		ELAPSED_RETRYTIME=n
 *
 *	where n is an ASCII decimal format long integer number.
 *
 *
 *	The environment variable of:
 *
 *		OP=opstring
 *
 *	defines the operation that is to be performed by this instance of
 *	the network queue server.
 *
 *		OP=D	-- deliver this request to its destination;
 *		OP=O	-- perform a stage-out file hierarchy event;
 *
 *	In the OP=O case, the additional environment variable of:
 *
 *		EVENT=n
 *
 *	is also present with n indicating the file staging event
 *	number [0..31] (see ../lib/transact.c).
 *
 */
main (argc, argv, envp)
int argc;
char *argv [];
char *envp [];
{

	struct rawreq rawreq;	/* To hold the control file header */
	char *cp;		/* Ascii value of environmental var */
	int cuid;		/* Client's user id */
	char *cusername;	/* Client's username */
	
	/*
	 *  It is necessary to block buffer all output to the NQS log
	 *  process so that messages from multiple NQS processes appear
	 *  coherently in the log output.
	 *
	 *  This is not optional.  This is REQUIRED!
	 */
	bufstderr();
	bufstdout();
	/*
	 *  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 */
	if (getuid() != 0 || geteuid() != 0) {
		printf ("I$Netclient: uid or euid not root.\n");
		exit (1);
	}
	/*
	 * SIGTERM is sent to all NQS processes upon NQS shutdown.
	 * Ignore it.
	 */
	signal (SIGTERM, SIG_IGN);
	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 (Debug > 2) {
		while (*envp != NULL) {
			printf ("D$Testing netclient envp: %s\n", *envp);
			envp++;
		}
		fflush (stdout);
	}
	if (readreq (0, &rawreq) != 0) {
		serexit (RCM_BADCDTFIL, (char *) 0);
	}
	if ((cp = getenv ("UID")) == (char *) 0) {
		serexit (RCM_BADSRVARG, (char *) 0);
	}
	cuid = atoi (cp); 
	if ((cusername = getenv ("USERNAME")) == (char *) 0) {
		serexit (RCM_BADSRVARG, (char *) 0);
	}
	/*
	 * Which operation are we to perform?
	 */
	if ((cp = getenv ("OP")) == (char *) 0) {
		serexit (RCM_BADSRVARG, (char *) 0);
	}
	switch (*cp) {
	case 'D':
		if (localmid (&Locmid) != 0) {
			serexit (RCM_UNAFAILURE, (char *) 0);
		}
		serexit (do_delivery (&rawreq, cuid, cusername), (char *) 0);
		break;
	case 'O':
		if (localmid (&Locmid) != 0) {
			serexit (RCM_UNAFAILURE, (char *) 0);
		}
		serexit (do_stageout (&rawreq, cuid, cusername), (char *) 0);
		break;
	default:
		serexit (RCM_BADSRVARG, (char *) 0);/* Exit */
		break;
	}
}


/*** do_delivery
 *
 *
 *	long do_delivery():
 *
 *	Delivery of requests across machines will soon be done using
 *	the one-two punch of pipe queues and network queues.
 *	This routine empties network queues.
 */
static long do_delivery (rawreq, cuid, cusername)
struct rawreq *rawreq;
uid_t cuid;
char *cusername;
{
	return (mergertcm (RCM_DELIVERFAI, TCML_FATALABORT));
}


/*** do_stageout
 *
 *
 *	long do_stageout():
 *
 *	A batch request has spooled an output file.  Move that file
 *	from the output spooling area to a reasonable final location,
 *	possibly on another machine.
 */
static long do_stageout (rawreq, cuid, cusername)
struct rawreq *rawreq;
uid_t cuid;
char *cusername;
{

	char *asciievent;		/* Ascii val of env var */
	struct passwd *passwd;		/* Pointer to password info */

	signal (SIGPIPE, lostsink);
	if ((asciievent = getenv ("EVENT")) == (char *) 0) {
		return (RCM_BADSRVARG);
	}
	/*
	 * It will be nice to know the user's home directory
	 */
	if ((passwd = fetchpwuid (cuid)) == (struct passwd *) 0) {
		return (mergertcm (RCM_STAGEOUTFAI, TCML_UNAFAILURE));
	}
	switch (atoi (asciievent)) {
	case 30:
		return (do_stdout (rawreq, passwd));
	case 31:
		return (do_stderr (rawreq, passwd));
	default:
		/*
		 * Stage-out events 0-29 are not supported yet.
		 */
		return (mergertcm (RCM_STAGEOUTFAI, TCML_FATALABORT));
	}
}


/*** do_stdout
 *
 *
 *	long do_stdout():
 *
 *	Stdout has already been spooled to a regular file.  Move that
 *	file from the output spooling area to a reasonable final
 *	location, possibly on another machine.
 *	Returns: RCM_STAGEOUT (might have information bits)
 *		 RCM_STAGEOUTFAI (will have information bits)
 */
static long do_stdout (rawreq, passwd)
struct rawreq *rawreq;
struct passwd *passwd;
{
	char *cp;				/* Character pointer */
	char *tempname;				/* Spooled file pathname */
	struct stat statbuf;			/* To hold stat() output */
	char path [MAX_REQPATH + 256 + 1];	/* 256 for home dir */
	char last_chance_path [MAX_REQPATH + 256 + 1];	/* 256 for home dir */
	long primaryrcm;			/* Rcm from first try */
	long secondaryrcm;			/* Rcm from second try */
	int ngroups;				/* size of group set */
	gid_t gidset[NGROUPS_MAX];		/* group set */

	if (rawreq->v.bat.stdout_acc & OMD_M_KEEP) {
		rawreq->v.bat.stdout_mid = Locmid;
	}
	tempname = namstdout (rawreq->orig_seqno, rawreq->orig_mid);
	if (stat (tempname, &statbuf) == -1) {
		/*
		 * Assume that a previous attempt worked.
		 */
		return (RCM_STAGEOUT);
	}
	/*
	 * Make sure last chance directory exists for this user.
	 * The directory is: /usr/spool/nqs/dump/<username>
	 */
	sprintf(path, "%s/%s", NQS_SPOOL, Nqs_dump);
	mkdir (path, 0777);
	sprintf(last_chance_path, "%s/%s", path, passwd->pw_name);
	if ( mkdir (last_chance_path, 0700) == -1) {
		if (errno != EEXIST) {
			printf ("D$Netclient: unable to make directory \
%s for stdout, errno = %d\n", last_chance_path, errno); 
			fflush (stdout);
		}
	}
	if (chown (last_chance_path, passwd->pw_uid, passwd->pw_gid) == -1) {
		printf("D$Netclient: unable to change owner \
of %s for stdout, errno = %d\n", last_chance_path, errno);
		fflush (stdout);
		return (mergertcm (RCM_STAGEOUTFAI, TCML_UNAFAILURE));
	}
	/*
	 * Assume that any previous attempts failed.
	 */
	if (rawreq->v.bat.stdout_mid == Locmid) {
		/*
		 * Lower privileges.
		 */
		initgroups (passwd->pw_name, passwd->pw_gid);
		ngroups = getgroups (NGROUPS_MAX, gidset);
		setgroups (ngroups, gidset);
		setgid (passwd->pw_gid);
		setuid (passwd->pw_uid);
		if (rawreq->v.bat.stdout_name [0] != '/') {
			sprintf (path, "%s/%s", passwd->pw_dir,
				rawreq->v.bat.stdout_name);
		}
		else strcpy (path, rawreq->v.bat.stdout_name);
		/*
		 * The following check handles the case of
		 * a bad password file home directory.
		 */
		if (path [0] != '/') {
			return (mergertcm (RCM_STAGEOUTFAI, TCML_UNAFAILURE));
		}
		primaryrcm = tryhere (tempname, path, &statbuf,
			(~rawreq->v.bat.umask & 0666));
		if ((primaryrcm & XCI_REASON_MASK) == RCM_STAGEOUTFAI) {
			/*
			 * We cannot return it to the primary location.
			 * Try to return it to a backup location
			 * on the execution machine.
			 */
			mkdefault (path, passwd->pw_dir, rawreq->reqname,
				rawreq->orig_seqno, 'o');
			/*
			 * Try the secondary location.  If we can't get it
			 * there, then try the last chance location.
			 */
			secondaryrcm =	tryhere (tempname, path, &statbuf,
					(~rawreq->v.bat.umask & 0666));
			if ((secondaryrcm & XCI_REASON_MASK) 
							!= RCM_STAGEOUTFAI)
				return (rightrcm (primaryrcm,secondaryrcm));
			/*
			 * Hmm, neither of those worked, lets try the last
			 * chance location:  /usr/spool/nqs/dump/<username>
			 */
			cp = strrchr(path, '/');
			cp++;
			sprintf(last_chance_path, "%s/%s/%s/%s", NQS_SPOOL,
				Nqs_dump, passwd->pw_name, cp);
			if (Debug > 2) {
				printf ("D$Netclient: last chance trying %s\n",
					last_chance_path);
				fflush (stdout);
			}
			secondaryrcm = tryhere (tempname, last_chance_path, 
					&statbuf,
					(~rawreq->v.bat.umask & 0666));
			return (mergertcm (RCM_STAGEOUTLCH, TCML_UNAFAILURE));

		}
		else return (primaryrcm);
	}
	else {
		/*
		 * Try to return this file across the net.
		 */
		primaryrcm = trythere (tempname, rawreq->v.bat.stdout_name,
			rawreq->v.bat.stdout_mid, &statbuf, passwd);
		if ( (primaryrcm & XCI_REASON_MASK) == RCM_STAGEOUT) {
		    if (Debug > 2) {
			printf("D$Netclient: trythere(stdout) succeeded\n");
			fflush(stdout);
		    }
		}
		else  tcmmsgs (primaryrcm, stdout, 
				"D$Netclient: trythere(stdout) ");
		if ((primaryrcm & XCI_REASON_MASK) == RCM_STAGEOUTFAI) {
			/*
			 * We cannot return it across the net.
			 * Try to return it to a backup location
			 * on the execution machine.
			 * First, lower privileges.
			 */
			initgroups (passwd->pw_name, passwd->pw_gid);
			ngroups = getgroups (NGROUPS_MAX, gidset);
			setgroups (ngroups, gidset);
			setgid (passwd->pw_gid);
			setuid (passwd->pw_uid);
			mkdefault (path, passwd->pw_dir,
				rawreq->reqname, rawreq->orig_seqno, 'o');
			/*
			 * Try the secondary location.  If we can't get it
			 * there, then try the last chance location.
			 */
			secondaryrcm =	tryhere (tempname, path, &statbuf,
					(~rawreq->v.bat.umask & 0666));
			if ((secondaryrcm & XCI_REASON_MASK) 
							!= RCM_STAGEOUTFAI)
				return (rightrcm (primaryrcm,secondaryrcm));
			/*
			 * Hmm, neither of those worked, lets try the last
			 * chance location:  /usr/spool/nqs/dump/<username>
			 */
			cp = strrchr(path, '/');
			cp++;
			sprintf(last_chance_path, "%s/%s/%s/%s", NQS_SPOOL,
				Nqs_dump, passwd->pw_name, cp);
			if (Debug > 2) {
				printf ("D$Netclient: last chance trying %s\n",
					last_chance_path);
				fflush (stdout);
			}
			secondaryrcm = tryhere (tempname, last_chance_path, 
					&statbuf,
					(~rawreq->v.bat.umask & 0666));
			return (mergertcm (RCM_STAGEOUTLCH, TCML_UNAFAILURE));

		}
		else return (primaryrcm);
	}
}


/*** do_stderr
 *
 *
 *	long do_stderr():
 *
 *	Stderr has already been spooled to a regular file.  Move that
 *	file from the output spooling area to a reasonable final
 *	location, possibly on another machine.
 *	Returns: RCM_STAGEOUT (might have information bits)
 *		 RCM_STAGEOUTFAI (will have information bits)
 *
 */
static long do_stderr (rawreq, passwd)
struct rawreq *rawreq;
struct passwd *passwd;
{
	
	char *cp;				/* Character pointer */
	char *tempname;				/* Spooled file pathname */
	struct stat statbuf;			/* To hold stat() output */
	char path [MAX_REQPATH + 256 + 1];	/* 256 for home dir */
	char last_chance_path [MAX_REQPATH + 256 + 1];	/* 256 for home dir */
	long primaryrcm;			/* Rcm from first try */
	long secondaryrcm;			/* Rcm from second try */
	int ngroups;				/* size of group set */
	gid_t gidset[NGROUPS_MAX];		/* group set */

	if (rawreq->v.bat.stderr_acc & OMD_M_KEEP) {
		rawreq->v.bat.stderr_mid = Locmid;
	}
	tempname = namstderr (rawreq->orig_seqno, rawreq->orig_mid);
	if (stat (tempname, &statbuf) == -1) {
		/*
		 * Assume that a previous attempt worked.
		 */
		return (RCM_STAGEOUT);
	}
	sprintf (path, "%s/%s", NQS_SPOOL, Nqs_dump);
	mkdir (path, 0777);
	sprintf(last_chance_path, "%s/%s", path, passwd->pw_name);
	if (mkdir(last_chance_path, 0700) == -1) {
		if (errno != EEXIST) {
			printf ("D$Netclient: unable to create \
directory %s for stderr, errno = %d\n", last_chance_path, errno);
			fflush(stdout);
			return (mergertcm (RCM_STAGEOUTFAI, TCML_UNAFAILURE));
		}
	}
	if (chown (last_chance_path, passwd->pw_uid, passwd->pw_gid) == -1) {
		printf ("D$Netclient: unable to change owner \
of %s for stderr, errno = %d\n", last_chance_path, errno);
		fflush(stdout);
		return (mergertcm (RCM_STAGEOUTFAI, TCML_UNAFAILURE));
	}
	/*
	 * Assume that any previous attempts failed.
	 */
	if (rawreq->v.bat.stderr_mid == Locmid) {
		/*
		 * Lower privileges.
		 */
		initgroups (passwd->pw_name, passwd->pw_gid);
		ngroups = getgroups (NGROUPS_MAX, gidset);
		setgroups (ngroups, gidset);
		setgid (passwd->pw_gid);
		setuid (passwd->pw_uid);
		if (rawreq->v.bat.stderr_name [0] != '/') {
			sprintf (path, "%s/%s", passwd->pw_dir,
				rawreq->v.bat.stderr_name);
		}
		else strcpy (path, rawreq->v.bat.stderr_name);
		/*
		 * The following check handles the case of
		 * a bad password file home directory.
		 */
		if (path [0] != '/') {
			return (mergertcm (RCM_STAGEOUTFAI, TCML_UNAFAILURE));
		}
		primaryrcm = tryhere (tempname, path, &statbuf,
			(~rawreq->v.bat.umask & 0666));
		if ((primaryrcm & XCI_REASON_MASK) == RCM_STAGEOUTFAI) {
			/*
			 * We cannot return it to the primary location.
			 * Try to return it to a backup location
			 * on the execution machine.
			 */
			mkdefault (path, passwd->pw_dir, rawreq->reqname,
				rawreq->orig_seqno, 'e');
			/*
			 * Try the secondary location.  If we can't get it
			 * there, then try the last chance location.
			 */
			secondaryrcm =	tryhere (tempname, path, &statbuf,
					(~rawreq->v.bat.umask & 0666));
			if ((secondaryrcm & XCI_REASON_MASK) 
							!= RCM_STAGEOUTFAI)
				return (rightrcm (primaryrcm,secondaryrcm));
			/*
			 * Hmm, neither of those worked, lets try the last
			 * chance location:  /usr/spool/nqs/dump/<username>
			 */
			cp = strrchr(path, '/');
			cp++;
			sprintf(last_chance_path, "%s/%s/%s/%s", NQS_SPOOL,
				Nqs_dump, passwd->pw_name, cp);
			if (Debug > 2) {
				printf ("D$Netclient: last chance trying %s\n",
					last_chance_path);
				fflush (stdout);
			}
			secondaryrcm = tryhere (tempname, last_chance_path, 
					&statbuf,
					(~rawreq->v.bat.umask & 0666));
			return (mergertcm (RCM_STAGEOUTLCH, TCML_UNAFAILURE));

		}
		else return (primaryrcm);
	}
	else {
		/*
		 * Try to return this file across the net.
		 */
		primaryrcm = trythere (tempname, rawreq->v.bat.stderr_name,
			rawreq->v.bat.stderr_mid, &statbuf, passwd);
		if ( (primaryrcm & XCI_REASON_MASK) == RCM_STAGEOUT) {
		    if (Debug > 2) {
			printf("D$Netclient: trythere(stderr) succeeded\n");
			fflush(stdout);
		    }
		} else  tcmmsgs( primaryrcm, stdout, 
				"D$Netclient: trythere(stderr) ");
		if ((primaryrcm & XCI_REASON_MASK) == RCM_STAGEOUTFAI) {
			/*
			 * We cannot return it across the net.
			 * Try to return it to a backup location
			 * on the execution machine.
			 * First, lower privileges.
			 */
			initgroups (passwd->pw_name, passwd->pw_gid);
			ngroups = getgroups (NGROUPS_MAX, gidset);
			setgroups (ngroups, gidset);
			setgid (passwd->pw_gid);
			setuid (passwd->pw_uid);
			mkdefault (path, passwd->pw_dir,
				rawreq->reqname, rawreq->orig_seqno, 'e');
			/*
			 * Try the secondary location.  If we can't get it
			 * there, then try the last chance location.
			 */
			secondaryrcm =	tryhere (tempname, path, &statbuf,
					(~rawreq->v.bat.umask & 0666));
			if ((secondaryrcm & XCI_REASON_MASK) 
							!= RCM_STAGEOUTFAI)
				return (rightrcm (primaryrcm,secondaryrcm));
			/*
			 * Hmm, neither of those worked, lets try the last
			 * chance location:  $(NQS_SPOOL)/dump/<username>
			 */
			cp = strrchr(path, '/');
			cp++;
			sprintf(last_chance_path, "%s/%s/%s/%s", NQS_SPOOL,
				Nqs_dump, passwd->pw_name, cp);
			if (Debug > 2) {
				printf ("D$Netclient: last chance trying %s\n",
					last_chance_path);
				fflush (stdout);
			}
			secondaryrcm = tryhere (tempname, last_chance_path, 
					&statbuf,
					(~rawreq->v.bat.umask & 0666));
			return (mergertcm (RCM_STAGEOUTLCH, TCML_UNAFAILURE));

		}
		else return (primaryrcm);
	}
}


/*** rightrcm
 *
 *
 *	long rightrcm ():
 *	Return the correct request completion message.
 *
 *	Given that return to the primary location failed,
 *	this function allows us to report one to the following:
 *
 *	1) Successful return to the backup location,
 *		 and why the primary location failed, or
 *	2) Unsuccessful, and why the primary location failed
 *
 *	instead of one of the following:
 *
 *	1) Successful return (neglecting to mention that
 *		it was to the backup location), or
 *	2) Unsuccessful, and why the backup location failed
 *		(never mentioning why return to the primary location failed)
 *
 */
static long rightrcm (remotercm, localrcm)
long remotercm;
long localrcm;
{
	if ((localrcm & XCI_REASON_MASK) == RCM_STAGEOUT) {
		return (RCM_STAGEOUTBAK | (remotercm & XCI_INFORM_MASK));
	}
	else return remotercm;
}

/*** trythere
 *
 *
 *	long trythere ():
 *
 *	Return an output file to a remote machine.
 *	If successful, return RCM_STAGEOUT and unlink tempname.
 *	If unsuccessful, return RCM_STAGEOUTFAI and do not unlink.
 */
static long trythere (tempname, dest, dest_mid, statbuf, passwd)
char *tempname;				/* Where we spooled it */
char *dest;				/* Path on remote machine */
Mid_t dest_mid;				/* Nmap mid for remote machine */
struct stat *statbuf;			/* Stat of spooled copy */
struct passwd *passwd;			/* Password on our machine */
{
	
	int fd;
	int sd;
	long transactcc;
	short timeout;			/* Seconds in between tries */
	int integers;
	int strings;
	char packet [MAX_PACKET];
	int packetsize;
	long tcm;

	if (Debug > 2) {
	    printf("D$Netclient: trythere tempname: %s\n", tempname);
	    printf("D$Netclient: trythere dest: %s\n", dest);
	    printf("D$Netclient: trythere dest_mid: %lu\n", dest_mid);
	    printf("D$Netclient: trythere size: %d\n", statbuf->st_size);
	    printf("D$Netclient: trythere user: %s\n", passwd->pw_name);
	    fflush(stdout);
	}
	if ((fd = robustopen (tempname, O_RDONLY, 0)) == -1) {
		return (mergertcm (RCM_STAGEOUTFAI, errnototcm ()));
	}
	interclear ();
	sd = establish (NPK_MVOUTFILE, dest_mid, passwd->pw_uid,
		passwd->pw_name, &transactcc);
	/*
	 * Establish has just told the network server
	 * on the remote machine what we want.
	 */
	if (sd < 0) {
	    if (sd == -2) {
		/*
		 * Retry is in order.
		 */
		timeout = 1;
		do {
		    nqssleep (timeout);
		    interclear ();
		    sd = establish (NPK_MVOUTFILE, dest_mid,
			passwd->pw_uid, passwd->pw_name,
			&transactcc);
		    timeout *= 2;
		} while (sd == -2 && timeout <= 16);
		/*
		 * Beyond this point, give up on retry.
		 */
		if (sd < 0) {
		    return (mergertcm (RCM_STAGEOUTFAI,	transactcc));
		}
	    }
	    else {
		/*
		 * Retry would surely fail.
		 */
		return (mergertcm (RCM_STAGEOUTFAI, transactcc));
	    }
	}
	/*
	 * The server liked us.
	 */
	interclear();
	interw32i ((long) NPK_MVOUTFILE);
	interw32i ((long) statbuf->st_mode);
	interw32i ((long) statbuf->st_size);
	interwstr (dest);
	if ((packetsize = interfmt (packet)) == -1) {
	    return (mergertcm (RCM_STAGEOUTFAI, TCML_INTERNERR));
	}
	if (write (sd, packet, packetsize) != packetsize) {
	    return (mergertcm (RCM_STAGEOUTFAI, TCMP_CONNBROKEN));
	}
	setsockfd (sd);
	switch (interread (getsockch)) {
	case 0:
	    break;
	case -1:
	    return (mergertcm (RCM_STAGEOUTFAI, TCMP_CONNBROKEN));
	case -2:
	    return (mergertcm (RCM_STAGEOUTFAI, TCML_PROTOFAIL));
	}
	integers = intern32i();
	strings = internstr();
	if (integers == 1 && strings == 0) {
	    if ((tcm = interr32i (1)) != TCMP_CONTINUE) {
		return (mergertcm (RCM_STAGEOUTFAI, tcm));
	    }
	}
	else return (mergertcm (RCM_STAGEOUTFAI, TCML_PROTOFAIL));
	if (filecopyentire (fd, sd) != statbuf->st_size) {
	    return (mergertcm (RCM_STAGEOUTFAI, TCMP_CONNBROKEN));
	}
	interclear();
	interw32i ((long) NPK_DONE);
	interw32i ((long) 0);
	interw32i ((long) 0);
	interwstr ("");
	if ((packetsize = interfmt (packet)) == -1) {
	    return (mergertcm (RCM_STAGEOUTFAI, TCML_INTERNERR));
	}
	if (write (sd, packet, packetsize) != packetsize) {
	    return (mergertcm (RCM_STAGEOUTFAI, TCMP_CONNBROKEN));
	}
	/*
	 * We think the transfer was successful.
	 */
	switch (interread (getsockch)) {
	case 0:
		break;
	case -1:
		return (mergertcm (RCM_STAGEOUTFAI, TCMP_CONNBROKEN));
	case -2:
		return (mergertcm (RCM_STAGEOUTFAI, TCML_PROTOFAIL));
	}
	integers = intern32i();
	strings = internstr();
	if (integers == 1 && strings == 0) {
	    if ((tcm = interr32i(1)) == TCMP_COMPLETE) {
		unlink (tempname);
		return (RCM_STAGEOUT);
	    } else {
		fflush(stdout);
	        return (mergertcm (RCM_STAGEOUTFAI, tcm));
	    }
	}
	else return (mergertcm (RCM_STAGEOUTFAI, TCML_PROTOFAIL));
}


/*** tryhere
 *
 *
 *	long tryhere ():
 *
 *	Return an output file to the local machine.
 *	If successful, return RCM_STAGEOUT and unlink tempname.
 *	If unsuccessful, return RCM_STAGEOUTFAI and do not unlink.
 */
static long tryhere (temp, dest, statbuf, mode)
char *temp;				/* Temporary name */
char *dest;				/* Destination name */
struct stat *statbuf;			/* Result of stat() on temp */
int mode;				/* Destination file mode */
{

	short waitmsg;			/* Wait message flag */
	short linkstatus;		/* Result of link() call */

	if (statbuf->st_nlink != 1) {
		/*
		 *  The output process has already been completed by
		 *  linking the final file to the temporary.  We just
		 *  have not gotten around to deleting the temporary
		 *  file just yet.
		 */
		unlink (temp);
		return (RCM_STAGEOUT);
	}
	waitmsg = 0;				/* No lack of inodes yet */
	while ((linkstatus = link (temp, dest)) == -1 && errno == ENOSPC) {
		if (!waitmsg) {
			waitmsg = 1;
			waitinode();		/* Write log mssg that we are */
		}				/* waiting.... */
		nqssleep (RESOURCE_WAIT);	/* Sleep for a while */
	}
	if (waitmsg) inodeavail();		/* I-node became available */
	if (linkstatus == 0) {
		/*
		 *  The simple link case succeeded.
		 */
		chmod (dest, mode);		/* Update mode */
		unlink (temp);			/* Remove temp file */
		return (RCM_STAGEOUT);		/* Complete success */
	}
	/*
	 *  The link failed.  Try copying the file.
	 */
	if (copy (temp, dest, mode) == 0) {
		/*
		 *  File copy successful.
		 */
		unlink (temp);			/* Remove temp file */
		return (RCM_STAGEOUT);		/* Complete success */
	}
	return (mergertcm (RCM_STAGEOUTFAI, errnototcm ()));
}


/*** copy
 *
 *
 *	int copy():
 *	Copy temp output file to specified destination file.
 *
 *	Returns:
 *		0: if successful.
 *	       -1: if unsuccessful.
 */
static int copy (temp, dest, mode)
char *temp;				/* Name of temporary file */
char *dest;				/* Name of destination file */
int mode;				/* File creation mode */
{

	register int srcfd;
	register int desfd;

	if ((srcfd = robustopen (temp, O_RDONLY, 0)) == -1) {
		return (-1);
	}
	if ((desfd = robustopen (dest, O_WRONLY|O_CREAT|O_TRUNC, mode)) == -1) {
		return (-1);
	}
	/*
	 *  Copy the output file.
	 */
	if (filecopyentire (srcfd, desfd) == -1) {
		/*
		 *  The file copy failed.
		 */
		unlink (dest);		/* Unlink destination */
		return (-1);
	}
	/*
	 *  The file copy succeeded!
	 */
	return (0);			/* Success */
}


/*** robustopen
 *
 *
 *	int robustopen ():
 *
 *	Behave just as the open(2) system call does, only
 *	try to turn failure into success.
 */
static int robustopen (path, flags, mode)
char *path;					/* Path name */
int flags;					/* O_??? */
int mode;					/* Rwx permissions, not */
						/* used if we only read */
{
	
	short descr_wait;			/* Boolean */
	short inode_wait;			/* Boolean */
	int fd;					/* File descriptor */

	/*
	 *  If the system file table is full, or no free i-nodes exist,
	 *  then we wait, and wait, and wait...........
	 */
	descr_wait = 0;
	inode_wait = 0;
	/*
	 * C allows us to pass the wrong number of args to open().
	 */
	while (
		((fd = open (path, flags, mode)) == -1) &&
		(errno == ENFILE || errno == EINTR || errno == ENOSPC)
		) {
		if (errno != EINTR) {
			if (errno == ENFILE) {
				if (inode_wait) {
					inode_wait = 0;
					inodeavail ();
				}
				if (!descr_wait) {
					descr_wait = 1;
					waitdescr ();
				}
			}
			else {
				if (descr_wait) {
					descr_wait = 0;
					descravail ();
				}
				if (!inode_wait) {
					inode_wait = 1;
					waitinode ();
				}
			}
			nqssleep (RESOURCE_WAIT);
		}
	}
	return (fd);
}


/*** descravail
 *
 *
 *	void descravail():
 *	Write-out file-descriptor has become available message.
 */
static void descravail()
{
	printf ("I$File descriptor has become available.\n");
	printf ("I$Process %1d is no longer blocked.\n", getpid());
	fflush (stdout);
}


/*** inodeavail
 *
 *
 *	void inodeavail():
 *	Write-out i-node has become available message.
 */
static void inodeavail()
{
	printf ("I$Inode has become available.\n");
	printf ("I$Process %1d is no longer blocked.\n", getpid());
	fflush (stdout);
}


/*** waitdescr
 *
 *
 *	void waitdescr():
 *	Write-out waiting for file descriptor message.
 */
static void waitdescr()
{
	printf ("W$Process %1d waiting for system ", getpid());
	printf ("file table overflow\n");
	printf ("W$activity to cease in order to return output file.\n");
	fflush (stdout);
}


/*** waitinode
 *
 *
 *	void waitinode():
 *	Write-out waiting for available i-node message.
 */
static void waitinode()
{
	printf ("W$Process %1d waiting for free i-node to", getpid());
	printf ("become available\n");
	printf ("W$in order to return output file.\n");
	fflush (stdout);
}


/*** lostsink
 *
 *
 *	void lostsink():
 *	Called upon receiving SIGPIPE.
 */
static void lostsink()
{
	signal (SIGPIPE, lostsink);
	serexit (mergertcm (RCM_STAGEOUTFAI, errnototcm ()), (char *) 0);
				/* was EPIPE */
}

