/*-
 * rmt.c --
 *	Functions to handle the exportation of targets using the
 *	customs daemon.
 *
 * Copyright (c) 1988, 1989 by the Regents of the University of California
 * Copyright (c) 1988, 1989 by Adam de Boor
 * Copyright (c) 1989 by Berkeley Softworks
 *
 * Permission to use, copy, modify, and distribute this
 * software and its documentation for any non-commercial purpose
 * and without fee is hereby granted, provided that the above copyright
 * notice appears in all copies.  The University of California,
 * Berkeley Softworks and Adam de Boor make no representations about
 * the suitability of this software for any purpose.  It is provided
 * "as is" without express or implied warranty.
 *
 * Interface:
 *	Rmt_Init  	    	Initialize things for this module
 *
 *	Rmt_AddServer	    	Add the given name as the address of
 *	    	  	    	an export server.
 *
 *	Rmt_ReExport	    	Re-export a job that has come home to roost.
 *
 *	Rmt_Begin 	    	Prepare to export another job and tell
 *	    	  	    	if it can actually be exported.
 *
 *	Rmt_Exec  	    	Execute the given shell with argument vector
 *	    	  	    	elsewhere.
 *
 *	Rmt_LastID	    	Return an unique identifier for the last
 *	    	  	    	job exported.
 *
 *	Rmt_Done  	    	Take note that a remote job has finished.
 *
 *	Rmt_Defer		Defer a job for later execution.
 *
 *	Rmt_Watch   	    	Pay attention to a stream.
 *
 *	Rmt_Ignore  	    	Ignore a stream
 *
 *	Rmt_Wait    	    	Wait for something to happen
 *
 *	Rmt_Event		Schedule an asychronous function call.
 *
 *	Rmt_Signal  	    	Deliver a signal to a job.
 *
 */
#ifndef lint
static char *rcsid =
"$Id: rmt.c,v 1.82 1995/04/22 09:38:01 stolcke Exp $ ICSI (Berkeley)";
#endif /* not lint */

#include    <sys/time.h>
#include    <fcntl.h>
#include    <sys/file.h>
#include    <stdio.h>
#include    <string.h>
#include    <sys/wait.h>
#include    <signal.h>
#include    <errno.h>
extern int  errno;

/* 
 * Some loser systems know SIGCHLD only by the name SIGCLD ...
 */
#ifndef SIGCHLD
#define SIGCHLD SIGCLD
#endif

#include    "make.h"
#include    "job.h"

#include    "customs.h"

#if defined(RMT_WANTS_SIGNALS) && !defined(RMT_NO_EXEC)
RMT_WANTS_SIGNALS_not_supported_without_RMT_NO_EXEC
#endif
#if defined(RMT_NO_EXEC) && !defined(RMT_WILL_WATCH)
RMT_NO_EXEC_not_supported_without_RMT_WILL_WATCH
#endif

/*
 * Private data attached to each job exported by this module. The address of
 * such a structure is returned as the remote ID for the job.
 */
typedef struct {
    ExportPermit  permit;   	/* Permit under which job was exported */
    int	    	  rmtFd;    	/* FD of stream to remote job. Needed if
				 * usePipes is FALSE */
    int	    	  cmdFd;  	/* FD of file containing commands */
    Rpc_Event	  checkEvent;	/* Event for check on remote process */
} CustomsPriv;

static char 	  	cwd[MAXPATHLEN];
				 /* The current working directory */
static Boolean	  	noAgent; /* TRUE if agent not present */

static Boolean		exportSame = FALSE;
				/* TRUE if the OP_EXPORTSAME attribute should
				 * be applied globally */
static Boolean		exportLocal = FALSE;
				/* TRUE if the OP_USELOCAL attribute should
				 * be applied globally */
static Boolean		exportExclusive = FALSE;
				/* TRUE if EXPORT_EXCLUSIVE should be applied */
static Lst		defaultAttributes = NILLST;
				/* host attributes to request for every job */

/*
 * From customslib.c
 */
extern int  	  	customs_Socket;	/* Socket opened by Customs calls */
extern struct timeval	customs_RetryTimeOut;	/* Default retry interval */
extern struct sockaddr_in customs_AgentAddr;

/*
 * For Make-mode exportation.
 */
static int returnFD;
static int exportFD;
static ExportPermit permit;

#include    <sys/ioctl.h>
#include    <netdb.h>

#ifdef RMT_NO_EXEC
/*-
 *-----------------------------------------------------------------------
 * RmtSwapExit --
 *	Byte-swap an Exit_Data structure.
 *
 * Results:
 *	None.
 *	
 * Side Effects:
 *	The words in the Exit_Data structure are byte-swapped.
 *
 *-----------------------------------------------------------------------
 */
/*ARGSUSED*/
static void
RmtSwapExit(length, data)
    int	    	  length;
    Exit_Data	  *data;
{
    Rpc_SwapLong(sizeof(data->id), &data->id);
    Rpc_SwapLong(sizeof(data->status), &data->status);
    Rpc_SwapLong(sizeof(data->signal), &data->signal);
}

static Boolean printEm = FALSE;	/* Set true if debugging and RmtCmpID
				 * should print out the Job structures as it
				 * checks them. */


/*-
 *-----------------------------------------------------------------------
 * RmtCmpID --
 *	See if a job is remote and has the given ID number
 *
 * Results:
 *	0 if it matches, non-zero if it doesn't
 *
 * Side Effects:
 *	None
 *-----------------------------------------------------------------------
 */
static int
RmtCmpID(job, id)
    Job	    	*job;
    Rpc_ULong	id;
{
    if (printEm) {
	Debug ("\t%s: ", job->node->name);
	if (job->flags & JOB_REMOTE) {
	    Debug ("remote #%d\n",
		job->rmtID ? ((CustomsPriv *)job->rmtID)->permit.id : 0);
	} else if (job->pid) {
	    Debug ("local #%d\n", job->pid);
	} else {
	    Debug ("not running\n");
	}
    }
    if (job->flags & JOB_REMOTE && job->rmtID) {
	return (id - ((CustomsPriv *)job->rmtID)->permit.id);
    } else {
	return(1);
    }
}
/*-
 *-----------------------------------------------------------------------
 * RmtExit --
 *	Handle a call on the returnFD to tell us a job has exited.
 *
 * Results:
 *	Nothing.
 *
 * Side Effects:
 *	A Job is finished out.
 *	
 *-----------------------------------------------------------------------
 */
/*ARGSUSED*/
static void
RmtExit(from, msg, len, data)
    struct sockaddr_in	*from;	    /* Call from... */
    Rpc_Message	  	msg;	    /* Token for return */
    int			len;	    /* Length of passed data */
    Rpc_Opaque		data;	    /* Data passed */
{
    Exit_Data	  	*eData;	    /* Data in our format */
    register LstNode	ln; 	    /* Node of finished job */
    register Job  	*job;	    /* The job itself */
    WAIT_STATUS  	status;	    /* How it died */
    CustomsPriv	    	*pdata;	    /* Our private data */

    if (msg) {
	/*
	 * Acknowledge the call
	 */
	Rpc_Return(msg, 0, (Rpc_Opaque)0);
    }

    eData = (Exit_Data *)data;
    WSTATUS(status) = eData->status;

    /*
     * XXX: Aarrrgh! There is no non-kludgy, portable way to do this
     * on systems that don't have a union wait.
     */
    if (WIFSTOPPED(status)) {
	WSTATUS(status) = (Signal_ToHost(eData->signal)<<8) | WSTOPPED;
		/* status.w_stopsig = Signal_ToHost(eData->signal); */
    } else if (WIFSIGNALED(status)) {
	WSTATUS(status) = Signal_ToHost(eData->signal);
		/* status.w_termsig = Signal_ToHost(eData->signal); */
    }

    if (DEBUG(RMT) || DEBUG(JOB)) {
        Debug ("Remote job %d %sed.\n",
	       eData->id, (WIFSTOPPED(status) ? "stopp" :
	                   (WIFSIGNALED(status) &&	/* Sprite only */
			    (WTERMSIG(status) == SIGCONT) ? "continu" :
	                    (WIFSIGNALED(status) ? "kill" : "exit"))));
    }
    /*
     * This piece of code basically duplicates Job_CatchChildren(),
     * but for remote jobs.
     */
    ln = Lst_Find(jobs, eData->id, RmtCmpID);
    if (ln == NILLNODE) {
	ln = Lst_Find(stoppedJobs, eData->id, RmtCmpID);
	if (ln == NILLNODE) {
	    Error("Received exit for unknown job id %d from %s", eData->id,
		  Customs_Hostname(&from->sin_addr));
	    if (DEBUG(QUEUE)) {
		printEm = TRUE;
		Debug ("jobs running:\n");
		(void)Lst_Find(jobs, eData->id, RmtCmpID);
		Debug ("jobs stopped:\n");
		(void)Lst_Find(stoppedJobs, eData->id, RmtCmpID);
		printEm = FALSE;
	    }
	    return;
	}
	job = (Job *)Lst_Datum(ln);
	if (!WIFSTOPPED(status)) {
	    (void)Lst_Remove(stoppedJobs, ln);
	    QUEUE_REMOVED("RmtExit", job, "stoppedJobs");
	}
    } else {
	job = (Job *)Lst_Datum(ln);

	/*
	 * Take it out of the list
	 */
	QUEUE_REMOVED("RmtExit", job, "jobs");
	(void)Lst_Remove(jobs, ln);

	if (WIFSTOPPED(status)) {
	    (void)Lst_AtFront(stoppedJobs, (ClientData)job);
	    QUEUE_ADDED("RmtExit", job, "stoppedJobs");
	}
	nJobs -= 1;

	/*
	 * Table no longer full
	 */
	if (jobFull && (DEBUG(RMT) || DEBUG(JOB))) {
	    Debug ("Job queue is no longer full.\n");
	}
	jobFull = FALSE;
    }
    
    pdata = (CustomsPriv *)job->rmtID;

    /*
     * Stop periodical aliveness checks if the job is finished
     */
    if (!WIFSTOPPED(status) && pdata->checkEvent) {
	Rpc_EventDelete(pdata->checkEvent);
    }

    /*
     * If the remove customs agent has sent us a termination notice then
     * it's safe to assume we will get an EOF on the socket soon.
     * Put the socket back in blocking mode to ensure all final output is
     * collected (either in the following loop or by JobDoOutput).
     */
    if (!WIFSTOPPED(status) && msg) {
	fcntl(pdata->rmtFd, F_SETFL, 0);	/* clear FNDELAY */
    }
	
    if (!usePipes) {
	/*
	 * Flush all data from the socket into the file before calling
	 * JobFinish
	 */
	int	nb;
	char  	buf[512];
	
	do {
	    do {
		nb = read(pdata->rmtFd, buf, sizeof(buf));
	    } while ((nb < 0) && (errno == EINTR));
	    if (nb > 0) {
		while ((write(job->outFd, buf, nb) < 0) && (errno == EINTR));
	    }
	} while (nb > 0);
	/*
	 * In non-pipe mode the socket is not identical to the job's output 
	 * file decriptor, so JobFinish won't close it for us.
	 */
	if (!WIFSTOPPED(status)) {
	    Rpc_Ignore(pdata->rmtFd);
	    (void)close(pdata->rmtFd);
	}
    } else {
	/*
	 * In pipe mode, stop watching the remote stream, but don't close
	 * it yet, since pdata->rmtFd is identical to job->inPipe, so
	 * JobFinish (on termination) or Rmt_Export (on restarting) will
	 * do the closing for us.
	 */
	if (!WIFSTOPPED(status)) {
	    int oldPos;

	    Rpc_Ignore(pdata->rmtFd);
	    do {
		oldPos = job->curPos;
		JobDoOutput(job, FALSE);
	    } while (job->curPos != oldPos);
	}
    }
    
    if (!WIFSTOPPED(status)) {
	free((char *)pdata);
    }

    /*
     * See if the job died because it was evicted or the remote machine was
     * shut down.
     */
    if (WIFSIGNALED(status) &&
	(WTERMSIG(status) == EVICT_SIGNAL || WTERMSIG(status) == SIGTERM) &&
	aborting != ABORT_INTERRUPT)
    {
	if (usePipes) {
	    Job_PrintTarget (job, stdout);
	}
	fprintf (stderr, "*** %s (restarting)\n",
		 WTERMSIG(status) == EVICT_SIGNAL ? "evicted" : "terminated");
	fflush (stderr);

	/*
	 * Restart the job.
	 */
	job->flags &= ~JOB_REMOTE;
	job->flags |= JOB_RESTART;
	JobRestart(job);
    } else {
	/*
	 * Finish it out
	 */
	JobFinish(job, status);
    }
}

/*-
 *-----------------------------------------------------------------------
 * RmtIO  --
 *	Handle I/O transfers between a remote job and the local machine.
 *
 * Results:
 *	Nothing.
 *
 * Side Effects:
 *	None
 *-----------------------------------------------------------------------
 */
static Boolean RmtBlockTimeout() { return (TRUE); }
/*ARGSUSED*/
static void
RmtIO(stream, job, state)
    int	    	  stream;
    Job		  *job;
    int		  state;
{
    CustomsPriv	    *pdata = (CustomsPriv *)job->rmtID;
    
    if (state & RPC_READABLE) {
	if (usePipes) {
	    /*
	     * If we're using pipes, we can just use JobDoOutput to transfer
	     * the data to the screen.
	     */
	    JobDoOutput(job, FALSE);
	} else {
	    /*
	     * Otherwise, we have to actually write the data to the output
	     * file. Just read a single 1K block each time through.
	     * Magic numbers R us.
	     */
	    char  	buf[1024];
	    int		nb;

	    do {
		nb = read(pdata->rmtFd, buf, sizeof(buf));
	    } while ((nb < 0) && (errno == EINTR));
	    if (nb > 0) {
		while ((write(job->outFd, buf, nb) < 0) && (errno == EINTR));
	    }
	}
    }
    if (state & RPC_WRITABLE) {
	char	  buf[512];
	int 	  nb;

	do {
	    nb = read(pdata->cmdFd, buf, sizeof(buf));
	} while ((nb < 0) && (errno == EINTR));
	if (nb > 0) {
	    char  *cp;
	    struct timeval tv;
	    Rpc_Event ev;

	    /*
	     * Stop watching for this stream to be writable until this buffer
	     * is written out
	     */
	    Rpc_Watch(pdata->rmtFd, RPC_READABLE, RmtIO, (ClientData)job);

	    /*
	     * Write out the buffer in whatever chunks the socket can swallow
	     */
	    cp = buf;

	    /*
	     * Create a timeout event to be invoked every 200 ms that makes
	     * sure Rpc_Wait will return with reasonable speed.
	     */
	    tv.tv_sec = 0;
	    tv.tv_usec = 200000;
	    ev = Rpc_EventCreate(&tv, RmtBlockTimeout, (Rpc_Opaque)0);
	    
	    while (nb > 0) {
		int	cc;
		
		do {
		    cc = write(pdata->rmtFd, cp, nb);
		} while ((cc < 0) && (errno == EINTR));

		if (cc < 0) {
		    if (errno != EWOULDBLOCK) {
			break;
		    } else {
			cc = 0;
		    }
		}
		cp += cc;
		nb -= cc;
		if (nb > 0) {
		    /*
		     * Give other things a chance while we wait for the
		     * socket to drain enough.
		     */
		    Rpc_Wait();

		    /*
		     * There is a chance the job has terminated while
		     * we were waiting.  If so, the stream has been
		     * closed and the job structure freed, so there
		     * is nothing more for us to do here.
		     */
		    if (!JobIsValid(job) || job->rmtID == (char *)0)
		    {
			Rpc_EventDelete(ev);
			return;
		    }
		}
	    }
	    Rpc_EventDelete(ev);
	    Rpc_Watch(pdata->rmtFd, RPC_READABLE|RPC_WRITABLE, RmtIO,
		      (ClientData)job);
	    
	    if (nb > 0) {
		Exit_Data   eData;
		
		Job_PrintTarget (job, stdout);
		if (errno == EPIPE) {
		    fprintf(stderr, "*** connection closed\n");
		    fflush(stderr);
		} else {
		    Error("Writing to remote: %s", strerror(errno));
		}
		eData.id = pdata->permit.id;
		eData.status = 1;
		eData.signal = SIGNAL_TERM;
		RmtExit((struct sockaddr_in *)0, (Rpc_Message)0,
			sizeof(eData), (Rpc_Opaque)&eData);
	    }
	} else {
	    if (nb < 0) {
		Job_PrintTarget (job, stdout);
		Error ("Reading cmd file: %s", strerror(errno));
	    }
	    /*
	     * Nothing more to read, so force an EOF on the other side
	     * by doing an out-going shutdown, then only pay attention
	     * to the beast being readable.
	     */
	    shutdown(pdata->rmtFd, 1);
	    Rpc_Watch(pdata->rmtFd, RPC_READABLE, RmtIO, (Rpc_Opaque)job);
	}
    }
}
#endif /* RMT_NO_EXEC */

#ifdef RMT_WILL_WATCH
static SIGRET RmtSigchldHandler();
/*-
 *-----------------------------------------------------------------------
 * RmtCheckChildren --
 *	Timeout handler for seeing if anyone has died. Simply calls
 *	Job_CatchChildren with block set to FALSE, then returns TRUE to
 *	force Rpc_Wait to exit.
 *
 * Results:
 *	TRUE.
 *
 * Side Effects:
 *	Job_CatchChildren is called.
 *	
 *-----------------------------------------------------------------------
 */
static Boolean
RmtCheckChildren()
{
    int	    oldnj = nJobs;
    
    if (DEBUG(JOB)) {
	Debug("RmtCheckChildren triggered\n");
    }

    /*
     * Rearm handler now, rather than after Job_CatchChildren, lest we 
     * run the risk of losing children.
     */
    (void)SIGNAL(SIGCHLD, RmtSigchldHandler);

    Job_CatchChildren(FALSE);

    return(oldnj != nJobs);
}

/*ARGSUSED*/
static SIGRET
RmtSigchldHandler(signo)
    int	signo;			/* always SIGCHLD */
{
    struct timeval timeout;

#ifndef SYSV
    /*
     * No point in catching more signals until we reap them
     * (SYSV signal semantics does this automatically)
     */
    (void)SIGNAL(SIGCHLD, SIG_DFL);
#endif

    if (DEBUG(JOB)) {
	Debug("Caught SIGCHLD -- ");
    }
    timeout.tv_sec = 0;
    timeout.tv_usec = 0;
    (void)Rpc_EventOnce(&timeout, RmtCheckChildren, (Rpc_Opaque)0);
}
#endif /* RMT_WILL_WATCH */

#ifdef RMT_WANTS_SIGNALS
/*-
 *-----------------------------------------------------------------------
 * Rmt_Signal --
 *	Pass a signal to a job. The job module ensures the thing is remote
 *
 * Results:
 *	TRUE on success, FALSE on failure and errno set.
 *
 * Side Effects:
 *	An Rpc is issued to the server for the job to kill the thing
 *	with the same signal.
 *	
 *-----------------------------------------------------------------------
 */
Boolean
Rmt_Signal(job, signo)
    Job	    	  *job;
    int	    	  signo;
{
    Kill_Data 	    	packet;
    struct sockaddr_in  server;
    Rpc_Stat  	    	rstat;
    CustomsPriv	    	*pdata;
    GNode		*node;

    if (!job->rmtID) {
	/*
	 * Job has exited or isn't yet running.
	 */
	errno = ESRCH;
	return FALSE;
    }

    pdata = (CustomsPriv *)job->rmtID;
    
    memset(&server, 0, sizeof(server));
    server.sin_family =     AF_INET;
    server.sin_addr = 	    pdata->permit.addr;
    server.sin_port = 	    customs_AgentAddr.sin_port;

    packet.id =     	    pdata->permit.id;
    packet.signal =  	    Signal_FromHost(signo);

    /*
     * XXX: Do this now in case job finishes while in Rpc_Call
     */
    node = job->node;

    rstat = Rpc_Call(customs_Socket, &server, (Rpc_Proc)CUSTOMS_KILL,
		     sizeof(packet), (Rpc_Opaque)&packet,
		     0, (Rpc_Opaque)0,
		     CUSTOMS_KILL_NRETRY, &customs_RetryTimeOut);
    
    if (rstat != RPC_SUCCESS) {
	Error("Sending signal %d to %s: %s",
	      signo, node->name,
	      Rpc_ErrorMessage(rstat));
	/*
	 * We map errno values one-to-one to rpc error codes to be
	 * able to give more informative error message at the other end.
	 * See ImportHandleKill() for the reverse map.
	 */
	switch (rstat) {
	case RPC_ACCESS:
	    errno = EPERM;
	    break;
	case RPC_BADARGS:
	    errno = ESRCH;
	    break;
	case RPC_SYSTEMERR:
	    errno = EINVAL;
	    break;
	default:
	    errno = 0;
	}
	return FALSE;
    }

    return TRUE;
}
#endif /* RMT_WANTS_SIGNALS */

/*-
 *-----------------------------------------------------------------------
 * Rmt_Init --
 *	Initialize this module...
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	The current working directory is placed in cwd and noAgent set
 *	FALSE if the local customs agent could be contacted.
 *
 *-----------------------------------------------------------------------
 */
void
Rmt_Init()
{
    Rpc_Stat	  status;

    /*
     * Create customs socket.
     */
    if (!noExport) {
	Main_Access(ACCESS_MAKE);
	Customs_Init();
	Main_Access(ACCESS_USER);
    }

    if (DEBUG(RMT)) {
	Rpc_Debug(1);
    }

    if (noExport || ((status = Customs_Ping()) != RPC_SUCCESS)) {
	if (DEBUG(RMT) && !noExport) {
	    Debug ("Could not contact customs agent: %s\n",
		   Rpc_ErrorMessage(status));
	}
	noAgent = TRUE;
    } else {
	noAgent = FALSE;
	    
#ifdef RMT_NO_EXEC
	Rpc_ServerCreate(customs_Socket, (Rpc_Proc)CUSTOMS_EXIT,
			 RmtExit, RmtSwapExit, Rpc_SwapNull,
			 (Rpc_Opaque)0);
#endif /* RMT_NO_EXEC */
	    
	if (!GETWD(cwd)) {
	    Fatal("Couldn't get current directory: %s", strerror(errno));
	}
	if (Customs_NormPath(cwd, sizeof(cwd)) < 0) {
	    Fatal("Could not normalize path %s: %s", cwd, strerror(errno));
	}

	if (DEBUG(RMT)) {
	    Debug ("Customs agent present. cwd = \"%s\"\n", cwd);
	}
		
	SIGNAL(SIGPIPE, SIG_IGN); /* This isn't right. Should use signal
				    * to check on remote jobs, or something. */
    }
    
#ifdef RMT_WILL_WATCH
    /*
     * We can use a SIGCHLD handler to reap children only when
     * there is something to wait() for -- this is much faster than
     * polling at fixed intervals.
     * XXX: Also have a check at larger intervals in case SIGCHLD delivery
     * isn't reliable.
     */
    if (!amMake) {
	struct timeval timeout;

	(void)SIGNAL(SIGCHLD, RmtSigchldHandler);

	timeout.tv_sec = 10;
	timeout.tv_usec = 0;
	(void)Rpc_EventCreate(&timeout, RmtCheckChildren, (Rpc_Opaque)0);
    }
#endif /* RMT_WILL_WATCH */
}

/*-
 *-----------------------------------------------------------------------
 * Rmt_AddServer --
 *	Add a server to the list of those known.
 *	In the customs implementation, this is really used to set some
 *	export directives, so we process keywords and attributes here.
 *	Passing a NULL argument returns export directives to their
 *	default values.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	None.
 *
 *-----------------------------------------------------------------------
 */
/*ARGSUSED*/
void
Rmt_AddServer (name)
    char    *name;
{
    if (DEBUG(RMT)) {
	if (name) {
	    Debug ("Rmt_AddServer: setting default attribute %s\n", name);
	} else {
	    Debug ("Rmt_AddServer: resetting default attributes\n");
	}
    }
    if (!name) {
	exportSame = FALSE;
	exportLocal = FALSE;
	exportExclusive = FALSE;
	Lst_Destroy(defaultAttributes, NOFREE);
	defaultAttributes = NILLST;
    } else if (strcmp(name, "SAME") == 0) {
	exportSame = TRUE;
    } else if (strcmp(name, "USELOCAL") == 0) {
	exportLocal = TRUE;
    } else if (strcmp(name, "EXCLUSIVE") == 0) {
	exportExclusive = TRUE;
    } else {
	char *nodeName;

	/*
	 * Everything else is taken to be a host attribute that should
	 * be requested for every exported job. We actually create an
	 * OP_EXPORT_ATTR node for each attribute so we can later use
	 * RmtExportAttr.
	 */
	if (defaultAttributes == NILLST) {
	    defaultAttributes = Lst_Init(FALSE);
	}

	nodeName = (char *)emalloc (sizeof(EXPORT_ATTR) + strlen(name));
	sprintf(nodeName, "%s%s", EXPORT_ATTR, name);
	Lst_AtEnd (defaultAttributes, Targ_NewGN(nodeName));
	free(nodeName);
    }
}

/*-
 *-----------------------------------------------------------------------
 * RmtExportAttr --
 *	Callback for Lst_ForEach to extract all export attributes from
 *	a node.
 *
 * Results:
 *	0.
 *
 * Side Effects:
 *	None.
 *
 *-----------------------------------------------------------------------
 */
static int
RmtExportAttr (cgn, lst)
    GNode    *cgn;
    Lst	     lst;
{
    if (cgn->type & OP_EXPORT_ATTR) {
	char *attr = cgn->name + sizeof(EXPORT_ATTR) - 1;
	if (*attr) {
	    if (DEBUG(RMT)) {
		Debug (" %s", attr);
	    }
	    (void)Lst_AtEnd(lst, (ClientData)attr);
	}
    }
    return (0);
}

/*-
 *-----------------------------------------------------------------------
 * Rmt_Begin --
 *	Prepare to export a job -- the Make-mode interface to Customs.
 *
 * Results:
 *	TRUE if the job can be exported. FALSE if it cannot.
 *
 * Side Effects:
 *	A TCP connection is opened to an available server and the
 *	CUSTOMS_IMPORT command issued (i.e. the job is started
 *	over there). exportFD is set to the fd of the connection and
 *	returnFD to the fd of the socket to be used to return the exit
 *	status.
 *
 *-----------------------------------------------------------------------
 */
/*ARGSUSED*/
Boolean
Rmt_Begin (file, argv, gn)
    char    	  *file;
    char    	  **argv;
    GNode   	  *gn;
{
    if (noAgent) {
	return (FALSE);
    } else {
	int flags;
	Lst attributes;
	Boolean needsAttributes;

	returnFD = -1;
	errno = 0;

	flags = ((gn->type & OP_EXPORTSAME) ? EXPORT_SAME : EXPORT_ANY)|
		((gn->type & OP_M68020) ? EXPORT_68020 : 0);

	/* 
	 * Apply default flags
	 */
	if ((exportLocal || (gn->type & OP_USELOCAL)) &&
	    !(gn->type & OP_EXPORT) &&
	    (nLocal < maxLocal)) {
	    flags |= EXPORT_USELOCAL;
	}
	if (exportSame) {
	    flags = (flags & ~EXPORT_ANY) | EXPORT_SAME;
	}
	if (exportExclusive) {
	    flags |= EXPORT_EXCLUSIVE;
	}

	if (DEBUG(RMT)) {
	    Debug ("Rmt_Begin: flags = 0x%x, attributes =", flags);
	}

	attributes = Lst_Init(FALSE);
	Lst_ForEach(gn->children, RmtExportAttr, (ClientData)attributes);

	/*
	 * Add default attributes.
	 */
	Lst_ForEach(defaultAttributes, RmtExportAttr, (ClientData)attributes);

	needsAttributes = !Lst_IsEmpty(attributes);

	if (DEBUG(RMT)) {
	    Debug ("\n");
	}

	/*
	 * Revert uid/gid to real user at this time so the right values
	 * end up in the waybill.  This will affect the entire make process,
	 * but we have no business running priviledged commands at this
	 * point anyway.
	 */
	Main_Access(ACCESS_CHILD);

	exportFD = Customs_RawExportAttr(file, argv, cwd,
					 flags, attributes,
				         &returnFD,
				         &permit);
	Lst_Destroy (attributes, NOFREE);

	if (exportFD < 0) {
	    if (DEBUG(RMT)) {
		perror("Customs_RawExport");
		Customs_PError(file);
	    }
	    /*
	     * XXX: If the export failed and attributes were requested that
	     * the local host doesn't have (permit.id == 0), set the
	     * OP_EXPORT on the gnode to inform the job module that it
	     * must not run the thing locally.
	     * We also force exportation if maxLocal is 0, since the job
	     * module normally ignores that to prevent endless waiting.
	     */
	    if ((maxLocal == 0) ||
		(!permit.id && needsAttributes))
	    {
		gn->type |= OP_EXPORT;
	    }
	    return (FALSE);
	} else {
	    if (!beSilent) {
		fprintf(stderr, "*** exported to %s (id %u)\n",
			Customs_Hostname(&permit.addr),
			permit.id);
		fflush(stderr);
	    }

	    return (TRUE);
	}
    }
}

/*-
 *-----------------------------------------------------------------------
 * Rmt_Exec --
 *	Execute a process elsewhere. If the exportation actually succeeded
 *	(exportFD > 0), the "export" program is executed (must be on the
 *	search path) with the -id flag.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	That remains to be seen.
 *
 *-----------------------------------------------------------------------
 */
/*ARGSUSED*/
void
Rmt_Exec (file, args, traceMe)
    char    *file;
    char    **args;
    Boolean traceMe;
{
    if (exportFD > 0) {
	char	  fd1[4], fd2[4];
	char	  id[10];
	char	  *argv[6];

	sprintf (fd1, "%d", exportFD);
	sprintf (fd2, "%d", returnFD);
	sprintf (id, "%08x", permit.id);

	argv[0] = "export to";
	argv[1] = "-id";
	argv[2] = fd1;
	argv[3] = fd2;
	argv[4] = id;
	argv[5] = (char *)0;
	if (DEBUG(RMT)) {
	    Debug ("export -id %s %s %s\n", argv[2], argv[3], argv[4]);
	}
	(void)execvp ("export", argv);
	Error ("Could not execute export: %s", strerror(errno));
    } else {
	if (DEBUG(RMT)) {
	    Debug ("Rmt_Exec called when exportFD == %d\n", exportFD);
	}
	(void)execvp (file, args);
	Error ("%s: %s", file, strerror(errno));
    }
}

#ifdef RMT_NO_EXEC

#ifndef RMT_WANTS_SIGNALS
#undef REMOTE_CHECK_INTERVAL
#endif

#ifdef REMOTE_CHECK_INTERVAL
/*-
 *-----------------------------------------------------------------------
 * RmtCheckRemote --
 *	Check well-being of a remote job by sending a signal 0.
 *
 * Results:
 *	TRUE.
 *
 * Side Effects:
 *	If the signal cannot be delivered we fake an exit status for
 *	the job.
 *
 *-----------------------------------------------------------------------
 */
static Boolean
RmtCheckRemote (job)
    Job	*job;
{
    /*
     * After sending the probe signal we have to make sure the job
     * hasn't terminated in the meantime.
     */
    if (!Rmt_Signal(job, 0) && JobIsValid(job)) {
	Exit_Data   eData;
	
	Job_PrintTarget (job, stdout);
	fprintf(stderr, "*** remote process vanished\n");
	fflush(stderr);

	eData.id = ((CustomsPriv *)job->rmtID)->permit.id;
	eData.status = 1;
	eData.signal = SIGNAL_TERM;
	RmtExit((struct sockaddr_in *)0, (Rpc_Message)0,
		sizeof(eData), (Rpc_Opaque)&eData);
    }
    
    return (TRUE);
}
#endif /* REMOTE_CHECK_INTERVAL */

/*-
 *-----------------------------------------------------------------------
 * Rmt_Export --
 *	Prepare to export a job -- the PMake-mode interface to customs.
 *
 * Results:
 *	TRUE if the job can be exported. FALSE if it cannot.
 *
 * Side Effects:
 *	A TCP connection is opened to an available server and the
 *	CUSTOMS_IMPORT command issued (i.e. the job is started
 *	over there). job->rmtFd is set to the fd of the connection and
 *	job->permit to the permit under which the job is running. If
 *	usePipes is TRUE, the pipes that were opened in JobStart are
 *	closed again and job->inPipe is set to job->rmtFd to allow
 *	us to use JobDoOutput to handle the output.
 *
 *-----------------------------------------------------------------------
 */
/*ARGSUSED*/
Boolean
Rmt_Export (file, argv, job)
    char    	  *file;    	    	/* File to exec */
    char    	  **argv;   	    	/* Arguments to it */
    Job   	  *job;	    	    	/* Job descriptor for it */
{
    if (noAgent) {
	return (FALSE);
    } else {
	struct timeval now, then;
	int flags;
	Lst attributes;
	Boolean needsAttributes;
	CustomsPriv *data = (CustomsPriv *)emalloc(sizeof(CustomsPriv));

	errno = 0;
	flags = ((job->node->type & OP_EXPORTSAME) ? EXPORT_SAME : EXPORT_ANY)|
		((job->node->type & OP_M68020) ? EXPORT_68020 : 0);

	/* 
	 * Apply default flags
	 */
	if ((exportLocal || (job->node->type & OP_USELOCAL)) &&
	    !(job->node->type & OP_EXPORT) &&
	    (nLocal < maxLocal)) {
	    flags |= EXPORT_USELOCAL;
	}
	if (exportSame) {
	    flags = (flags & ~EXPORT_ANY) | EXPORT_SAME;
	}
	if (exportExclusive) {
	    flags |= EXPORT_EXCLUSIVE;
	}

	if (DEBUG(RMT)) {
	    Debug ("Rmt_Export: flags = 0x%x, attributes =", flags);
	}

	attributes = Lst_Init(FALSE);
	Lst_ForEach(job->node->children, RmtExportAttr, (ClientData)attributes);

	/*
	 * Add default attributes.
	 */
	Lst_ForEach(defaultAttributes, RmtExportAttr, (ClientData)attributes);

	needsAttributes = !Lst_IsEmpty(attributes);

	if (DEBUG(RMT)) {
	    Debug ("\n");
	    gettimeofday(&now, 0);
	}

	/*
	 * Revert uid/gid to real user at this time so the right values
	 * end up in the waybill.  This will affect the entire make process,
	 * but we have no business running priviledged commands at this
	 * point anyway.
	 */
	Main_Access(ACCESS_CHILD);

	data->rmtFd = Customs_RawExportAttr(file, argv, cwd,
					    flags, attributes,
					    &customs_Socket,
					    &data->permit);
	if (DEBUG(RMT)) {
	    gettimeofday(&then, 0);
	    then.tv_usec -= now.tv_usec;
	    if (then.tv_usec < 0) {
		then.tv_usec += 1000000;
		then.tv_sec -= now.tv_sec + 1;
	    } else {
		then.tv_sec -= now.tv_sec;
	    }
	    Debug ("*** time spent calling: %d.%06d s\n",
		   then.tv_sec, then.tv_usec);
	}

	Lst_Destroy (attributes, NOFREE);

	if (data->rmtFd < 0) {
	    /*
	     * Print message for errors other than simple host denial
	     */
	    if (data->rmtFd != CUSTOMS_NOEXPORT) {
		if (usePipes) {
		    Job_PrintTarget (job, stdout);
		}
		Error("%s", Customs_ErrorMessage(data->rmtFd,
		                                 &data->permit.addr));
	    }
	    /*
	     * XXX: If the export failed and attributes were requested that
	     * the local host doesn't have (permit.id == 0), set the
	     * OP_EXPORT on the gnode to inform the job module that it
	     * must not run the thing locally.
	     * We also force exportation if maxLocal is 0, since the job
	     * module normally ignores that to prevent endless waiting.
	     */
	    if ((maxLocal == 0) ||
		(!data->permit.id && needsAttributes))
	    {
		job->node->type |= OP_EXPORT;
	    }
	    free(data);
	    return (FALSE);
	} else {
	    struct timeval checkInterval;

	    job->rmtHost = Str_New(Customs_Hostname(&data->permit.addr));

	    if (!(job->flags & JOB_SILENT)) {
		if (usePipes) {
		    Job_PrintTarget (job, stdout);
		}
		fprintf(stderr, "*** exported to %s (id %u)\n",
			job->rmtHost, data->permit.id);
		fflush(stderr);
	    }

	    /*
	     * Make sure the other side can send us its return status
	     */
	    if (!Rpc_IsAllowed(customs_Socket, &data->permit.addr)) {
		(void)Rpc_Allow(data->permit.addr.s_addr);
	    }
	    
	    if (usePipes) {
		/*
		 * Close down the pipes that were opened for this job since
		 * we dinnae need them
		 */
		(void)close(job->outPipe);
		(void)close(job->inPipe);
		job->outPipe = job->inPipe = data->rmtFd;
		job->curPos = 0;
		if (DEBUG(RMT)) {
		    Debug ("rmtFd = %d\n", data->rmtFd);
		}
	    }
	    
	    fflush(stdout);

	    fcntl(data->rmtFd, F_SETFL, FNDELAY);
	    fcntl(data->rmtFd, F_SETFD, 1);

	    /*
	     * Record command file's descriptor and rewind the thing to its
	     * start.
	     */
	    data->cmdFd = fileno(job->cmdFILE);
	    lseek(data->cmdFd, 0L, L_SET);

#ifdef REMOTE_CHECK_INTERVAL
	    /*
	     * Set up the checking event for the remote process
	     */
	    checkInterval.tv_sec = REMOTE_CHECK_INTERVAL;
	    checkInterval.tv_usec = 0;
	    data->checkEvent = Rpc_EventCreate(&checkInterval, RmtCheckRemote,
					       (Rpc_Opaque)job);
#else
	    data->checkEvent = (Rpc_Event)0;
#endif
	    /*
	     * Pay attention to the remote connection for two-way communication.
	     */
	    Rpc_Watch(data->rmtFd, RPC_READABLE|RPC_WRITABLE, RmtIO,
		      (Rpc_Opaque)job);

	    /*
	     * Record the private data in the job record.
	     */
	    job->rmtID = (char *)data;
	    job->pid = 0;
	    
	    /*
	     * Success R Us
	     */
	    return (TRUE);
	}
    }
}
#endif /* RMT_NO_EXEC */

/*-
 *-----------------------------------------------------------------------
 * Rmt_ReExport --
 *	Supposed to re-export a job that's come home, but since jobs
 *	can't come home under customs, we just return FALSE to say
 *	we couldn't do it.
 *
 * Results:
 *	FALSE.
 *
 * Side Effects:
 *	None.
 *
 *-----------------------------------------------------------------------
 */
/*ARGSUSED*/
Boolean
Rmt_ReExport(pid)
    int	    pid;
{
    if (DEBUG(RMT)) {
	Debug ("Rmt_ReExport called?\n");
    }
    return(FALSE);
}

/*-
 *-----------------------------------------------------------------------
 * Rmt_LastID --
 *	Return an unique identifier for the last job exported with Rmt_Exec
 *
 * Results:
 *	Some sort of identifier. Just returns 1.
 *
 * Side Effects:
 *	returnFD and exportFD are closed
 *
 *-----------------------------------------------------------------------
 */
/*ARGSUSED*/
int
Rmt_LastID(pid)
    int	    	  pid;	    /* PID of job last exported */
{
    (void)close(returnFD);
    (void)close(exportFD);
    exportFD = 0;

    return (1);
}

/*-
 *-----------------------------------------------------------------------
 * Rmt_Done --
 *	Register the completion of a remote job.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	None.
 *
 *-----------------------------------------------------------------------
 */
/*ARGSUSED*/
void
Rmt_Done (id)
    int	    id;
{
}

#ifdef RMT_WILL_WATCH
/*-
 *-----------------------------------------------------------------------
 * Rmt_Watch --
 *	Watch a stream for the job module. It only requires us to
 *	notify it of the readability of the stream.
 *
 * Results:
 *	None
 *
 * Side Effects:
 *	A call to Rpc_Watch is performed.
 *	
 *-----------------------------------------------------------------------
 */
void
Rmt_Watch(stream, proc, data)
    int	    stream; 	    /* Stream to watch */
    void    (*proc)();	    /* Procedure to call */
    char    *data;  	    /* Data to pass it when stream is ready */
{
    Rpc_Watch(stream, RPC_READABLE, proc, (Rpc_Opaque)data);
}

/*-
 *-----------------------------------------------------------------------
 * Rmt_Ignore --
 *	Pay no further attention to a stream
 *
 * Results:
 *	None
 *
 * Side Effects:
 *	Rpc_Ignore is called
 *	
 *-----------------------------------------------------------------------
 */
void
Rmt_Ignore(stream)
    int	    	stream;	    /* Stream to ignore */
{
    Rpc_Ignore(stream);
}

/*-
 *-----------------------------------------------------------------------
 * Rmt_Wait --
 *	Wait for something to happen and return when it does
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	Those of the callback functions that are called.
 *	
 *-----------------------------------------------------------------------
 */
void
Rmt_Wait()
{
    Rpc_Wait();
}

/*-
 *-----------------------------------------------------------------------
 * Rmt_Event --
 *	Schedule a function to be called asynchronously ASAP.
 *	Typically used in signal handlers of the Job module.
 *
 * Results:
 *	None
 *
 * Side Effects:
 *	A self-deleting event is created in event-driven mode.
 *	
 *-----------------------------------------------------------------------
 */
void
Rmt_Event(handler, arg)
    Boolean	(*handler)();	/* function to be invoked */
    void	*arg;		/* its argument */
{
    struct timeval timeout;

    timeout.tv_sec = 0;
    timeout.tv_usec = 0;

    Rpc_EventOnce(&timeout, handler, (Rpc_Opaque)arg);
}
#endif /* RMT_WILL_WATCH */

#if defined(RMT_WILL_WATCH) && defined(EXPORT_RETRY)
static Rpc_Event retryEvent = (Rpc_Event)0;

/*-
 *-----------------------------------------------------------------------
 * RmtRetryExport --
 *	Timeout handler to try and restart some stopped jobs.
 *
 * Results:
 *	TRUE
 *
 * Side Effects:
 *	The timeout event may be removed if there is nothing left to do.
 *	
 *-----------------------------------------------------------------------
 */
static Boolean
RmtRetryExport ()
{
    if (DEBUG(RMT) || DEBUG(JOB)) {
	Debug ("RmtRetryExport timeout\n");
    }

    jobFull = FALSE;
    JobRestartAll("RmtRetryExport", FALSE);
    
    /*
     * If there are no stopped jobs left we might as well stop retrying.
     */
    if (Lst_IsEmpty(stoppedJobs)) {
	Rpc_EventDelete(retryEvent);
	retryEvent = (Rpc_Event)0;
    }

    return (TRUE);
}
/*-
 *-----------------------------------------------------------------------
 * Rmt_Defer --
 *	Defer a job for remote execution.
 *
 * Results:
 *	TRUE iff the job can be deferred.
 *
 * Side Effects:
 *	A timeout event is created if this is the first deferred job.
 *	
 *-----------------------------------------------------------------------
 */
/*ARGSUSED*/
Boolean
Rmt_Defer (job)
    Job		*job;
{
    if (noAgent) {
	return (FALSE);
    } else {
	if (!retryEvent) {
	    struct timeval timeout;

	    timeout.tv_sec = EXPORT_RETRY;
	    timeout.tv_usec = 0;
	    retryEvent = Rpc_EventCreate(&timeout, RmtRetryExport,
	                                 (ClientData)0);
	}
	return (TRUE);
    }
}

#else /* !RMT_WILL_WATCH || !EXPORT_RETRY */

/*ARGSUSED*/
Boolean
Rmt_Defer (job)
    Job		*job;
{
    return (FALSE);
}
#endif /* RMT_WILL_WATCH && EXPORT_RETRY */
