/*-
 * import.c --
 *	Functions to import processes.
 *
 * 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.
 */
#ifndef lint
static char *rcsid =
"$Id: import.c,v 1.83 2001/02/17 16:39:50 stolcke Exp $ ICSI (Berkeley)";
#endif /* not lint */

#include    <sys/types.h>
#include    <signal.h>
#include    <sys/wait.h>
#include    <sys/time.h>
#include    <sys/resource.h>
#include    <sys/times.h>
#include    <sys/file.h>
#include    <stdio.h>
#include    <string.h>
#include    <pwd.h>
#include    <errno.h>

extern int   errno;
extern char *getusershell();

#include    "customsInt.h"
#include    "lst.h"

#ifdef NO_VFORK
#define vfork fork
#else

#ifdef sparc
#ifdef SVR4
#include    <unistd.h>
#else
#include    <vfork.h>
#endif
#endif

#endif /* NO_VFORK */

#ifndef SIGCHLD
#define SIGCHLD			SIGCLD
#endif

#ifndef NGROUPS
#define NGROUPS			16
#endif

/*
 * Permits published by the MCA are kept in ImportPermit structures on the
 * permits list until a process actually arrives for them. If one doesn't
 * arrive before the expiration timer goes off, the ImportPermit (and hence
 * the ExportPermit) is discarded.
 */
typedef struct {
    ExportPermit  permit;   	/* Actual permit from MCA */
    Rpc_Event	  expire;   	/* Expiration timer. If this goes off,
				 * the permit gets nuked */
    int		  flags;	/* EXPORT_* flags */
} ImportPermit;

/*
 * A Process structure is assigned to each imported process so it can be
 * killed, its status returned, etc. The ExportPermit for the job is also
 * saved to allow the log server to match a job finish with a start.
 */
typedef struct {
    int	    	  	pid;	    	/* ID of child */
    int	    	  	pgrp;	    	/* Process group of job */
    int			key;		/* Random key for communication with
					   child process */
    char 	    	command[MAX_CMD_LEN];    	
					/* Command executed */
    time_t		start;		/* Start time of child */
    ImportPermit  	*permit;  	/* Import permit for job */
    struct sockaddr_in 	retAddr;  	/* Address of socket on which to
					 * return the exit status */
} Process;

static Lst  	  	permits;    /* List of permits awaiting processes */
static Lst  	  	imports;    /* List of active processes */

/*
 * Bookkeeping for jobs statistics
 */
Job_Stats		stats;
#ifndef RUSAGE_SELF
static clock_t		userTimeMark;	/* zero mark for userTime */
static clock_t		sysTimeMark;	/* zero mark for sysTime */
#else /* RUSAGE_SELF */
#define USPS		1000000		/* # of microseconds in a second */
static struct timeval	userTimeMark;	/* zero mark for userTime */
static struct timeval	sysTimeMark;	/* zero mark for sysTime */
#endif /* !RUSAGE_SELF */

/*
 * This is the time for which an ImportPermit may remain active without a
 * process.
 */
static struct timeval 	expirationDate = {
    30, 0,
};
static struct timeval 	expirationNow = {
    0, 0,
};

/*
 * Private call number for import-internal Rpc
 */
#define CUSTOMS_PGRP	((int)CUSTOMS_PING + 1000)
/*
 * This structure is passed from the child daemon to the main daemon to
 * notify of a new process group created for the imported job.  The extra
 * overhead will allow the main daemon to send SIGSTOP and SIGKILL to the
 * import while leaving the the child daemon alone.
 */
typedef struct {
    int			pid;		/* Child pid */
    int			pgrp;		/* Job process group */
    int			key;		/* Random key from Process structure */
} Pgrp_Data;

static Boolean ImportCheckAll();


/*-
 *-----------------------------------------------------------------------
 * ImportCmpPid --
 *	Callback function for ImportCheckAll to find a Process record
 *	with the given pid.
 *
 * Results:
 *	0 if it matches, non-0 if it doesn't.
 *
 * Side Effects:
 *	None.
 *
 *-----------------------------------------------------------------------
 */
static int
ImportCmpPid(procPtr, pid)
    Process 	  	*procPtr;   	/* Process to examine */
    int	    	  	pid;	    	/* PID desired. */
{
    return (procPtr->pid - pid);
}
	
/*-
 *-----------------------------------------------------------------------
 * Import_Kill --
 *	Kill imported jobs.
 *
 * Results:
 *	0 if all went well, a positive errno value if kill failed,
 *	-1 if no jobs were found.
 *
 * Side Effects:
 *	The signal is sent to child process associated with job id.
 *	Job id = 0 means kill all imported jobs.
 *
 *-----------------------------------------------------------------------
 */
int
Import_Kill(jobid, signo, from)
    int			    jobid;	/* Job to kill */
    int			    signo;	/* Signal to send */
    struct sockaddr_in	    *from;	/* Caller id */
{
    register Process   	    *procPtr;
    LstNode 	  	    ln;
    Boolean		    found = False;
    int		    	    error = 0;

#ifdef SYSV
    /*
     * Some system don't seem to like terminal-related stop signals sent
     * to jobs -- aborts them with SIGHUP.  So turn them into SIGSTOP's.
     * This has some drawbacks, like sub-makes cannot catch them and pass
     * them on, but it is better than nothing.
     */
    if ((signo == SIGTSTP) || (signo == SIGTTIN) || (signo == SIGTTOU)) {
	signo = SIGSTOP;
    }
#endif /* SYSV */
    /*
     * Find the Process structure corresponding to the id number
     * given, then kill the process.
     */
    if (Lst_Open(imports) == SUCCESS) {
	while ((ln = Lst_Next(imports)) != NILLNODE) {
	    procPtr = (Process *)Lst_Datum(ln);
	    if (jobid == 0 ||
		procPtr->permit->permit.id == jobid)
	    {
		found = True;

		/*
		 * Killing a job is allowed if
		 * - the call is internal (from = 0), or
		 * - it comes from a priviledged port (i.e. root), or
		 * - we allow evictions by users, and this is one,
		 * - it comes from the return socket for that job.
		 */
		if (from &&
		    (ntohs(from->sin_port) >= IPPORT_RESERVED) &&
#ifdef ALLOW_EVICT_BY_USER
		    (signo != EVICT_NOTIFY && signo != EVICT_SIGNAL) &&
#endif
		    (from->sin_addr.s_addr != procPtr->retAddr.sin_addr.s_addr
		     || from->sin_port != procPtr->retAddr.sin_port))
		{
		    xlog (XLOG_WARNING,
			    "Import_Kill: killpg(%d, %d) from %d@%s denied",
			    procPtr->pgrp, signo,
			    ntohs((from)->sin_port),
			    InetNtoA((from)->sin_addr));

		    Log_Send(LOG_ACCESS, 1, xdr_sockaddr_in, from);
		    error = EPERM;
		    break;
		}

		if (verbose) {
		    xlog (XLOG_DEBUG, "Import_Kill: killpg(%d, %d) from %d@%s",
			    procPtr->pgrp, signo,
			    from ? ntohs((from)->sin_port) : 0,
			    from ? InetNtoA((from)->sin_addr) : "(internal)");
		}

		if (KILLPG(procPtr->pgrp, signo) < 0) {
		    error = errno;
		    xlog (XLOG_WARNING, "Couldn't send signal: %s",
			    strerror(errno));
		} else {
		    if (from) {
			/*
			 * Signal 0 is used to check on existence of jobs,
			 * so don't count them in the stats total.
			 */
			if (signo != 0) {
			    stats.signals++;
			}
		    } else if (signo == EVICT_SIGNAL) {
			stats.evictions++;
		    }
		}

		Log_Send(LOG_KILL, 2,
			 xdr_exportpermit, &procPtr->permit->permit,
			 xdr_int, &signo);

		if (jobid != 0) {
		    break;	/* jobs ids are unique */
		}
	    }
	}
	Lst_Close(imports);
    }

    if (!found)
	return (-1);
    else
	return (error);
}
	
/*-
 *-----------------------------------------------------------------------
 * ImportHandleKill --
 *	Handle the killing of an imported process.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	The signal number in the message given to the child.
 *	Job id = 0 means kill all imported jobs.
 *
 *-----------------------------------------------------------------------
 */
/*ARGSUSED*/
static void
ImportHandleKill (from, msg, len, data)
    struct sockaddr_in	    *from;
    Rpc_Message	  	    msg;
    int	    	  	    len;
    Rpc_Opaque 	  	    data;
{
    Kill_Data	  	    *packet = (Kill_Data *)data;
    
    switch (Import_Kill(packet->id, Signal_ToHost(packet->signal), from)) {
    case 0:
	Rpc_Return(msg, 0, (Rpc_Opaque)0);
	break;
    case -1:
	Rpc_Error(msg, RPC_BADARGS);	/* job not found */
	break;
    /*
     * We map errno values one-to-one to rpc error codes to be
     * able to give more informative error message at the other end.
     * This matters for cctrl -kill and rmt.c.
     */
    case ESRCH:	/* process not there */
	/*
	 * Most likely just a child that exited but hasn't been noticed
	 * by ImportCheckAll yet.  Ignore.
	 */
	Rpc_Return(msg, 0, (Rpc_Opaque)0);
	break;
    case EPERM:	/* permission denied */
	Rpc_Error(msg, RPC_ACCESS);
	break;
    case EINVAL:	/* bad signal number */
    default:	
	Rpc_Error(msg, RPC_SYSTEMERR);
	break;
    }
}

/*-
 *-----------------------------------------------------------------------
 * ImportPgrp --
 *	Receive info from child about a new process group allocated for
 *	imported job.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	The pgrp info in the process table is updated.
 *
 *-----------------------------------------------------------------------
 */
/*ARGSUSED*/
static void
ImportPgrp (from, msg, len, data)
    struct sockaddr_in	    *from;
    Rpc_Message	  	    msg;
    int	    	  	    len;
    Rpc_Opaque 	  	    data;
{
    Pgrp_Data	  	    *packet = (Pgrp_Data *)data;
    LstNode 	  	    ln;
    Process 	  	    *procPtr;

    ln = Lst_Find (imports, (ClientData)packet->pid, ImportCmpPid);
    if (ln == NILLNODE) {
	xlog (XLOG_WARNING,
		"Process group for unknown child received (pid %d)",
		packet->pid);
	Rpc_Error(msg, RPC_BADARGS);
	return;
    }

    procPtr = (Process *)Lst_Datum(ln);
    if (packet->key != procPtr->key) {
	xlog (XLOG_WARNING,
		"Key mismatch in process group change for pid %d",
		packet->pid);
	Rpc_Error(msg, RPC_ACCESS);
	return;
    }

    Rpc_Return(msg, 0, (Rpc_Opaque)0);

    procPtr->pgrp = packet->pgrp;

    if (verbose) {
	xlog (XLOG_DEBUG, "ImportPgrp: pgrp for job %d set to %d",
		procPtr->permit->permit.id, procPtr->pgrp);
    }
}

/*-
 *-----------------------------------------------------------------------
 * ImportCatchChild --
 *	Catches SIGCHLD and schedules a call to ImportCheckAll to get
 *	its exit status.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	Rpc event is created.
 *
 *-----------------------------------------------------------------------
 */
/*ARGSUSED*/
static SIGRET
ImportCatchChild(signo)
    int		signo;		/* Always SIGCHLD */
{
    struct timeval timeout;

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

    (void)Rpc_EventOnce(&timeout, ImportCheckAll, (Rpc_Opaque)0);
#ifndef SYSV
    /*
     * Don't bother with any more SIGCHLD's until the event goes off.
     * SYSV disarms signals by default, so this is redundant.
     */
    (void)SIGNAL(SIGCHLD, SIG_DFL);
#endif /* SYSV */
}

/*-
 *-----------------------------------------------------------------------
 * ImportCheckAll --
 *	Check on all the jobs. This is kinda gross. It just does a wait3
 *	to see if anyone wants to say anything and finishes the job out
 *	if it does.
 *
 * Results:
 *	FALSE.
 *
 * Side Effects:
 *	Jobs will be removed from the imports list, if they actually
 *	finish. If there are no imported jobs left, the event that caused
 *	the invocation of this function is deleted.
 *
 *-----------------------------------------------------------------------
 */
static Boolean
ImportCheckAll()
{
    WAIT_STATUS  status;   	/* Status of child */
    int	    	  pid;	    	/* ID of reporting child */
    LstNode 	  ln;
    Process 	  *procPtr;

    while ((pid = WAIT3(&status,WNOHANG,NULL)) > 0) {
	ln = Lst_Find (imports, (ClientData)pid, ImportCmpPid);
	if (ln == NILLNODE) {
	    xlog (XLOG_WARNING,
		    "Wait returned status from unknown child (pid %d)", pid);
	}
	else {
	    procPtr = (Process *)Lst_Datum(ln);

	    /*
	     * XXX: Crude real time statistics for jobs
	     */
	    stats.realTime += time((time_t *)0) - procPtr->start;

	    if (verbose) {
		int job = procPtr->permit->permit.id;

		if (WIFSIGNALED(status)) {
		    xlog (XLOG_DEBUG,
			    "ImportCheckAll: Job %d (pid %d): killed by %d",
			    job, pid, WTERMSIG(status));
		} else {
		    xlog (XLOG_DEBUG,
			    "ImportCheckAll: Job %d (pid %d): exited with %d",
		            job, pid, WEXITSTATUS(status));
		}
	    }

	    /*
	     * If process was exclusive user we are now again free for
	     * non-exclusive use.
	     */
	    if (procPtr->permit->flags & EXPORT_EXCLUSIVE &&
		avail_Exclusive > 0)
	    {
		avail_Exclusive --;
	    }

	    (void)Lst_Remove(imports, ln);
	    avail_LoadBias -= LOADBIAS;
	    Avail_Send();
	    free((char *)procPtr->permit);
	    free((char *)procPtr);
	}
    }

    (void)SIGNAL(SIGCHLD, ImportCatchChild);

    return(FALSE);
}

/*-
 *-----------------------------------------------------------------------
 * ImportExpire --
 *	Delete an expired ImportPermit.
 *
 * Results:
 *	False.
 *
 * Side Effects:
 *	The permit is removed from the permits list and the event that
 *	caused this call back is deleted. An availability packet is
 *	sent to the mca, too, since this expiration could have made the
 *	host available again.
 *
 *-----------------------------------------------------------------------
 */
static Boolean
ImportExpire(ln)
    LstNode 	  	    ln;	    	/* Node of permit to nuke */
{
    ImportPermit  	    *permit;	/* The actual permit */

    permit = (ImportPermit *)Lst_Datum(ln);
    Rpc_EventDelete(permit->expire);

    /*
     * If process was exclusive user we are now again free for
     * non-exclusive use.
     */
    if (permit->flags & EXPORT_EXCLUSIVE && avail_Exclusive > 0) {
	avail_Exclusive --;
    }

    Lst_Remove(permits, ln);
    free((char *)permit);
    avail_LoadBias -= LOADBIAS;
    Avail_Send();
    return False;
}

/*-
 *-----------------------------------------------------------------------
 * ImportCheckUser --
 *	Check import permission for a user.
 *
 * Results:
 *	True iff access allowed.
 *
 * Side Effects:
 *	None.
 *
 *-----------------------------------------------------------------------
 */
static Boolean
ImportCheckUser(uid)
    int 	  	    uid;	 /* User to check */
{
    struct passwd *pwd;
    
    /*
     * XXX: All we have is the user id.  With more information in the
     * export permit we could check that user names are the same,
     * do password authentication, etc.
     */
    if (checkUser >= PWD_CHECK) {
        if (!(pwd = getpwuid(uid)) ||
	    strcmp(pwd->pw_passwd, "*") == 0)
	    return False;
    }

    if (checkUser >= SHELL_CHECK) {
	char *ashell;

	setusershell();
	while (ashell = getusershell()) {
	    if (strcmp(pwd->pw_shell, ashell) == 0)
		break;
	}
	endusershell();

	if (!ashell)
	    return False;
    }

    return True;
}

/*-
 *-----------------------------------------------------------------------
 * ImportAllocated --
 *	Notice that we've been allocated. This call is only allowed to
 *	come from the udp customs port on the master machine (or 127.1
 * 	[localhost] if we are the master).
 *	If user access control is enabled we also check that the user
 *	is allowed to use this machine.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	None.
 *
 *-----------------------------------------------------------------------
 */
/*ARGSUSED*/
static void
ImportAllocated(from, msg, len, data)
    struct sockaddr_in	*from;
    Rpc_Message	  	msg;
    int	    	  	len;
    AllocData  		*data;
{
    ImportPermit  	*newPermit;
    AllocReply	    	reply;

    if (len != sizeof(AllocData)) {
	Rpc_Error (msg, RPC_BADARGS);
    } else if ((from->sin_port == htons(udpPort)) &&
	       ((from->sin_addr.s_addr == masterAddr.sin_addr.s_addr) ||
		(amMaster && Local(from))))
    {
	ExportPermit *permit = &data->permit;
	int uid = (short)((permit->id >> 16) & 0xffff);	/* user id recovered */

	if ((uid != -1) && !ImportCheckUser(uid)) {
	    if (verbose || checkUser >= LOG_CHECK) {
		xlog (XLOG_WARNING, "Allocation for user %d from %d@%s denied",
			uid, ntohs(from->sin_port), InetNtoA(from->sin_addr));
	    }
	    if (checkUser >= LOG_CHECK) {
		Log_Send(LOG_ACCESS, 1, xdr_sockaddr_in, from);
	    }
	    Rpc_Error(msg, RPC_ACCESS);
	    return;
	}

	/*
	 * Check that we're not accepting exclusive jobs while machine is 
	 * already in use.  This happens when the current jobs are not
	 * (all) exclusive (so the machine appears available to the master),
	 * but the incoming job has requested exclusive use.
	 */
	if (data->flags & EXPORT_EXCLUSIVE && Import_NJobs() >= maxExclusives) {
	    if (verbose) {
		xlog (XLOG_WARNING, "Exclusive allocation denied");
	    }
	    Rpc_Error(msg, RPC_ACCESS);
	    return;
	}

	if (permit->addr.s_addr == CUSTOMS_FROMADDR) {
	    permit->addr = from->sin_addr;
	}

	if (verbose) {
	    xlog (XLOG_DEBUG,
		    "ImportAllocated: Incoming process from %s (id %u)",
		    InetNtoA(permit->addr), permit->id);
	}
	
	/*
	 * Uid -1 is used to test the host allocation scheme (using the
	 * "host" test client), so take care to remove the permit immediately
	 * after issue.
	 */
	newPermit = (ImportPermit *)emalloc (sizeof (ImportPermit));
	newPermit->permit = *permit;
	newPermit->flags = data->flags;
	Lst_AtEnd (permits, (ClientData)newPermit);
	newPermit->expire =
	    Rpc_EventCreate((uid == -1 ? &expirationNow : &expirationDate),
			    ImportExpire,
			    (Rpc_Opaque)Lst_Last(permits));

	/*
	 * Adjust the load now so it gets reflected in the value returned
	 * here.
	 */
	avail_LoadBias += LOADBIAS;

	/*
	 * Note exclusive use has started if requested by client
	 */
	if (data->flags & EXPORT_EXCLUSIVE) {
	    avail_Exclusive ++;
	}

	reply.avail = Avail_Local(AVAIL_EVERYTHING, &reply.rating);
	Rpc_Return(msg, sizeof(reply), (Rpc_Opaque)&reply);
    } else {
	xlog (XLOG_WARNING,
		"Attempted Allocation from non-master %d@%s",
		ntohs(from->sin_port), InetNtoA(from->sin_addr));
	Rpc_Error(msg, RPC_ACCESS);
	Log_Send(LOG_ACCESS, 1, xdr_sockaddr_in, from);
    }
}

/*-
 *-----------------------------------------------------------------------
 * ImportFindID --
 *	Look for a permit with the given id number. Callback procedure
 *	for ImportProcess.
 *
 * Results:
 *	0 if the current permit matches. non-zero otherwise.
 *
 * Side Effects:
 *	None
 *
 *-----------------------------------------------------------------------
 */
static int
ImportFindID (permit, id)
    ImportPermit  *permit;
    Rpc_ULong  	  id;
{
    return (permit->permit.id - id);
}

/*-
 *-----------------------------------------------------------------------
 * ImportPrintPermit --
 *	Print out a permit...Used by ImportProcess in verbose mode.
 *
 * Results:
 *	Always returns 0.
 *
 * Side Effects:
 *	None.
 *
 *-----------------------------------------------------------------------
 */
static int
ImportPrintPermit (permitPtr)
    ImportPermit  *permitPtr;
{
    xlog (XLOG_DEBUG, "   #%u to %s,", permitPtr->permit.id,
	    InetNtoA(permitPtr->permit.addr));
    return (0);
}

/*-
 *-----------------------------------------------------------------------
 * ImportExtractVector --
 *	Extract an array of strings from a buffer and return a vector of
 *	char pointers. Used by ImportProcess to get the argv and envp for
 *	the new process.
 *
 * Results:
 *	A dynamically-allocated vector of char *'s, or NULL if allocation
 *	fails.
 *
 * Side Effects:
 *	*strPtrPtr is set to point beyond the extracted strings.
 *
 *-----------------------------------------------------------------------
 */
static char **
ImportExtractVector(strPtrPtr)
    char    	  **strPtrPtr;
{
    register char *cp;
    register char **vec;
    register int  numStrings;
    char    	  **vecPtr;

    cp = *strPtrPtr;
    numStrings = *(Rpc_Long *)cp;
    vecPtr = (char **)malloc((numStrings + 1) * sizeof(char *));
    if (vecPtr) {
	cp += sizeof(Rpc_Long);
	for (vec = vecPtr; numStrings != 0; vec++, numStrings--) {
	    *vec = cp;
	    cp += strlen(cp) + 1;
	}
	*vec = (char *)0;
	cp = Customs_Align(cp, char *);
	*strPtrPtr = cp;
    }
    return(vecPtr);
}

/*-
 *-----------------------------------------------------------------------
 * ImportProcess --
 *	Import a process from another machine. Requires an unique ID
 *	which is communicated by the MCA in the CustomsAlloc call.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *
 *-----------------------------------------------------------------------
 */
/*ARGSUSED*/
static void
ImportProcess (from, msg, len, wbPtr)
    struct sockaddr_in	*from;
    Rpc_Message	  	msg;
    int	    	  	len;
    WayBill 	  	*wbPtr;
{
    LstNode 	  	ln; 	    	/* Node of published permit */
    ImportPermit  	*permit;    	/* The permit itself */
    Process 	  	*proc;	    	/* Structure to track job */
    int	    	  	sock;	    	/* I/O socket for job */
    char    	  	*cp;	    	/* General char pointer */
    char    	  	*cwd;	    	/* Directory for job */
    char    	  	*file;	    	/* File to exec */
    char    	  	**argv;	    	/* Arguments for it */
    char    	  	**envp;	    	/* Environment for job */
    extern char	  	**environ;  	/* Current environment */
    int	    	  	permituid;  	/* UID in permit's ID */

#define ERROR(str) Rpc_Return(msg, sizeof(str), (Rpc_Opaque)str); \
                   Rpc_Ignore(sock);\
                   (void)close(sock);\
		   if (permit) free((char *)permit);\
		   return;

    sock = Rpc_MessageSocket(msg);
    
    ln = Lst_Find (permits, (ClientData)wbPtr->id, ImportFindID);
    if (ln == NILLNODE) {
	if (verbose) {
	    if (Lst_IsEmpty (permits)) {
		xlog (XLOG_DEBUG, "ImportProcess: No permits issued");
	    } else {
		xlog (XLOG_DEBUG, "ImportProcess: No permit for %u:",
			wbPtr->id);
		Lst_ForEach (permits, ImportPrintPermit, (ClientData)0);
	    }
	}
	permit = (ImportPermit *)0;
	ERROR("No permit issued to you");
    } else {
	permit = (ImportPermit *)Lst_Datum (ln);
	Rpc_EventDelete(permit->expire);
	(void) Lst_Remove (permits, ln);

#ifdef CHECK_ADDRESS
	/*
	 * Checking the client address against the one in the export permit
	 * gives some extra security, but it can do the wrong thing if 
	 * the client talks to the MCA using a different address than to us.
	 */
	if (permit->permit.addr.s_addr != from->sin_addr.s_addr) {
	    ERROR("Invalid address");
	}
#endif /* CHECK_ADDRESS */
	if (verbose) {
	    xlog (XLOG_DEBUG, "ImportProcess: Received IMPORT from %s",
		    InetNtoA(permit->permit.addr));
	}
	
#ifndef INSECURE
	/*
	 * Make sure the person's not trying to execute as root...
	 */
	if (wbPtr->ruid == 0 || wbPtr->euid == 0) {
	    xlog (XLOG_WARNING, "Attempted execution as ROOT from %d@%s",
		    ntohs(from->sin_port), InetNtoA(from->sin_addr));
	    /*
	     * We don't care if this RPC times out...
	     */
	    Log_Send(LOG_ACCESS, 1, xdr_sockaddr_in, from);
	    ERROR("Root execution not allowed");
	}
#endif /* INSECURE */

	/*
	 * The effective uid of the caller is encoded in the high word of
	 * permit id. We make sure it matches the id in the WayBill
	 * to prevent one source of fraud
	 */
	permituid = (wbPtr->id >> 16) & 0xffff;
	
	if (wbPtr->euid != permituid) {
	    xlog (XLOG_WARNING,
		    "Mismatched uid's (permit = %d, waybill=%d) from %d@%s",
		    permituid, wbPtr->euid,
		    ntohs(from->sin_port), InetNtoA(from->sin_addr));
	    Log_Send(LOG_ACCESS, 1, xdr_sockaddr_in, from);
	    ERROR("Mismatched user IDs");
	}

	cp = (char *)&wbPtr[1];
	cwd = cp;
	cp += strlen(cwd) + 1;
	file = cp;
	cp += strlen(file) + 1;
	cp = Customs_Align(cp, char *);
	argv = ImportExtractVector(&cp);
	if (argv == (char **)0) {
	    ERROR("Couldn't allocate argument vector");
	}
	envp = ImportExtractVector(&cp);
	if (envp == (char **)0) {
	    ERROR("Couldn't allocate environment vector");
	}
	
	proc = (Process *) emalloc (sizeof (Process));
	proc->permit = permit;
	proc->retAddr = *from;
	proc->retAddr.sin_port = wbPtr->port;
	proc->key = (random() & 0xffff) | (permituid << 16);

	/*
	 * Record start time and command file of this job for later reference.
	 * Use argv[0] rather than file for the command name, since
	 * pmake sets it to something distinctive for identification purposes.
	 */
	strncpy(proc->command, argv[0], sizeof(proc->command));
	time(&proc->start);

	/*
	 * Let the child process generate the reply once it is ready to go.
	 * So we don't generate a reply outselves.
	 */
	Rpc_Error(msg, RPC_NOREPLY);

	fflush(stdout);

	stats.jobs++;
	proc->pid = fork();
	proc->pgrp = proc->pid;
	if (proc->pid == 0) {
	    /*
	     * Child process:
	     * Set std{in,out,err} to send things to and receive things from
	     * the remote machine. Files opened for other jobs will close when
	     * we exec... Once that is done, attempt to set up the running
	     * environment:
	     *	  1) set both gids
	     *	  2) install all the groups the caller is in
	     *	  3) set both uids
	     *	  4) chdir to the working directory
	     *	  5) set the umask correctly.
	     * Then fork and execute the given command using the passed
	     * environment instead of our own. If any of these steps fails,
	     * we print an error message for pen pal to read and return a
	     * non-zero exit status.
	     */
	    char	errmsg[MAXPATHLEN + 40];
	    WAIT_STATUS	status;
	    int	  	cpid;
	    int	  	oldstdout;
	    Boolean	jobbyowner = False;
	    char	procTitle[12];
	    int		on = 1;
	    struct rlimit rlim;
	    Pgrp_Data	pgrp;
	    SIGSET_T	mask, oldmask;
#ifndef RLIMIT_FSIZE
	    extern long ulimit();
#endif

	    /*
	     * Clear signal handlers, which work only in the main process
	     */
	    SIGNAL(SIGHUP, SIG_DFL);
	    SIGNAL(SIGQUIT, SIG_DFL);
	    SIGNAL(SIGINT, SIG_DFL);
	    SIGNAL(SIGTERM, SIG_DFL);
	    SIGNAL(SIGTSTP, SIG_DFL);
	    SIGNAL(SIGCONT, SIG_DFL);
	    SIGNAL(SIGTTOU, SIG_DFL);
	    SIGNAL(SIGTTIN, SIG_DFL);

#ifdef SIGWINCH
	    SIGNAL(SIGWINCH, SIG_DFL);
#endif
#ifdef SIGWINDOW
	    SIGNAL(SIGWINDOW, SIG_DFL);
#endif
	    SIGNAL(SIGUSR1, SIG_DFL);
	    SIGNAL(SIGUSR2, SIG_DFL);

	    SIGNAL(SIGBUS, SIG_DFL);
	    SIGNAL(SIGSEGV, SIG_DFL);

#define CHILD_ERROR(str) \
		sprintf(errmsg, "%s: %s", str, strerror(errno)); \
		Rpc_Return(msg, strlen(errmsg)+1, (Rpc_Opaque)errmsg); \
		exit(1);

	    /*
	     * For easy identification in ps(1) output, set our argv to
	     * the job id.
	     */
	    sprintf(procTitle, "%-11lu", wbPtr->id);
	    Customs_SetProcTitle(procTitle);

	    /*
	     * Check whether we are running as non-root and the customs
	     * owner is just testing his/her own network.
	     */
	    if (getuid() != 0 &&
		wbPtr->ruid == getuid() &&
	        wbPtr->euid == geteuid()) {
		jobbyowner = True;
	        if (verbose) {
		    xlog (XLOG_DEBUG,
			    "ImportProcess: assuming job is from customs owner (%d/%d)",
			    wbPtr->ruid, wbPtr->euid);
		}
	    }

	    /*
	     * Reset our priority to 0 since we're just another job now.
	     */
	    if (!jobbyowner) {
#ifndef PRIO_PROCESS
		nice(-40); nice(20); nice(0);
#else
		setpriority (PRIO_PROCESS, 0, 0);
#endif /* PRIO_PROCESS */
	    }

	    /*
	     * Ignore eviction notification by default.  Allows regular
	     * jobs to terminated withing grace period.
	     */
	    SIGNAL(EVICT_NOTIFY, SIG_IGN);
	    SIGNAL(EVICT_SIGNAL, SIG_DFL);

	    /*
	     * Make sure user process doesn't inherit files opened by OS
	     * module.
	     */
	    OS_Exit();

	    /*
	     * Redirect error messages to user.
	     */
	    xlog_set(NULL, stdout);

	    /*
	     * Allow re-use of this address so if agent restarts, it won't
	     * die (maybe)
	     */
	    (void)setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
			     (char *)&on, sizeof(on));

	    oldstdout = dup(1);
	    
	    if (sock != 1) {
		dup2 (sock, 1);
	    }

	    WSTATUS(status) = 0;
	    
	    environ = envp;
	    
	    /*
	     * Restore priority and resource limits of exported job.
	     * If we are running our own jobs, at least try but don't
	     * worry about permission denied errors.
	     */
#define max(a,b) ((a)>(b)?(a):(b))
	    /*
	     * Make imports nicer ...
	     */
	    if (niceLevel) {
		wbPtr->priority = max(wbPtr->priority, niceLevel);
	    }
#ifndef PRIO_PROCESS
	    /*
	     * We are at nice level 0 after the normalization above.
	     */
	    if (nice(wbPtr->priority) == -1)
#else /* PRIO_PROCESS */
	    if (setpriority(PRIO_PROCESS, 0, wbPtr->priority) < 0)
#endif /* !PRIO_PROCESS */
	    {
		if (!jobbyowner) {
		    CHILD_ERROR("Couldn't set priority");
		}
	    }

#ifdef sgi
	    /*
	     * Remove or set non-degrading priority.
	     * XXX: We are exploiting the fact that a 0 value means
	     * 'none' to both customs and schedctl().
	     */
	    if (schedctl(NDPRI, 0, npriLevel) < 0) {
		if (!jobbyowner) {
		    CHILD_ERROR("Couldn't set non-degrading priority");
		}
	    }
#endif /* sgi */

#define min(a,b) ((a) == CUSTOMS_NORLIM ? (b) : \
		  ((a) < (b) ? (a) : (b)))
	    /*
	     * Impose global cpu time limit for imports
	     */
	    if (cpuLimit) {
		Customs_RlimCur(*wbPtr, CUSTOMS_RLIMCPU) =
		    min(Customs_RlimCur(*wbPtr, CUSTOMS_RLIMCPU),
			cpuLimit);
		Customs_RlimMax(*wbPtr, CUSTOMS_RLIMCPU) =
		    min(Customs_RlimMax(*wbPtr, CUSTOMS_RLIMCPU),
			cpuLimit + GRACE_CPU);
	    }

	    /*
	     * Impose global memory use limit for imports
	     */
	    if (memLimit) {
		Customs_RlimCur(*wbPtr, CUSTOMS_RLIMRSS) =
		    min(Customs_RlimCur(*wbPtr, CUSTOMS_RLIMRSS),
			memLimit * 1024);
		Customs_RlimMax(*wbPtr, CUSTOMS_RLIMCPU) =
		    min(Customs_RlimMax(*wbPtr, CUSTOMS_RLIMCPU),
			memLimit * 1024);
	    }

#ifdef RLIMIT_FSIZE
#ifdef FSIZE_SCALE
	    /*
	     * On HP-UX RLIMIT_FSIZE is scaled by the same blocksize as
	     * arguments to ulimit(2).
	     */
	    if (Customs_RlimCur(*wbPtr, CUSTOMS_RLIMFSIZE) != CUSTOMS_NORLIM)
		Customs_RlimCur(*wbPtr, CUSTOMS_RLIMFSIZE) /= FSIZE_SCALE;
	    if (Customs_RlimMax(*wbPtr, CUSTOMS_RLIMFSIZE) != CUSTOMS_NORLIM)
		Customs_RlimMax(*wbPtr, CUSTOMS_RLIMFSIZE) /= FSIZE_SCALE;
#endif /* FSIZE_SCALE */
#endif /* RLIMIT_FSIZE */

	    /*
	     * The order in which rlimits are set here must match the
	     * one used for getrlimit() calls in customslib.c.
	     *
	     * Note: EPERM signals insufficient permissions (as when we're
	     * running as non-root in test mode), or trying to raise
	     * limits beyond kernel limits (at least in Linux).
	     */
#define SET_RLIMIT(index,resource) \
    (rlim.rlim_cur = (Customs_RlimCur(*wbPtr,index) == CUSTOMS_NORLIM ? \
			RLIM_INFINITY : Customs_RlimCur(*wbPtr,index)), \
     rlim.rlim_max = (Customs_RlimMax(*wbPtr,index) == CUSTOMS_NORLIM ? \
			RLIM_INFINITY : Customs_RlimMax(*wbPtr,index)), \
     setrlimit(resource, &rlim) < 0 && errno != EPERM)

	    if (0
#ifdef RLIMIT_CPU
		|| SET_RLIMIT(CUSTOMS_RLIMCPU, RLIMIT_CPU)
#endif
#ifdef RLIMIT_FSIZE
		&& SET_RLIMIT(CUSTOMS_RLIMFSIZE, RLIMIT_FSIZE)
#else /* !RLIMIT_FSIZE */
	        && (Customs_RlimMax(*wbPtr, CUSTOMS_RLIMFSIZE) !=
					CUSTOMS_NORLIM &&
		    ulimit(2, (Customs_RlimMax(*wbPtr, CUSTOMS_RLIMFSIZE) - 1)
					/ 512 + 1) < 0 &&
		    errno != EPERM)
#endif
#ifdef RLIMIT_DATA
		|| SET_RLIMIT(CUSTOMS_RLIMDATA, RLIMIT_DATA)
#endif
#ifdef RLIMIT_STACK
		|| SET_RLIMIT(CUSTOMS_RLIMSTACK, RLIMIT_STACK)
#endif
#ifdef RLIMIT_CORE
		|| SET_RLIMIT(CUSTOMS_RLIMCORE, RLIMIT_CORE)
#endif
#ifdef RLIMIT_RSS
		|| SET_RLIMIT(CUSTOMS_RLIMRSS, RLIMIT_RSS)
#endif
#ifdef RLIMIT_NOFILE
		|| SET_RLIMIT(CUSTOMS_RLIMNOFILE, RLIMIT_NOFILE)
#endif
#ifdef RLIMIT_VMEM
		|| SET_RLIMIT(CUSTOMS_RLIMVMEM, RLIMIT_VMEM)
#endif
	       )
	    {
	        CHILD_ERROR("Couldn't set resource limits");
	    }
	
	    /* 
	     * Now change our identity, but only if running as root
	     * on behalf of another user.
	     */
	    if (!jobbyowner) {
		GID_T    groups[MAX_NUM_GROUPS];
		int	 i;

#ifndef SETREGID
		if (setgid (wbPtr->rgid) < 0 ||
		    setegid (wbPtr->rgid) < 0)
#else /* SETREGID */
		if (SETREGID (wbPtr->rgid, wbPtr->egid) < 0)
#endif /* !SETREGID */
		{
		    CHILD_ERROR("Couldn't set real/effective group ids");
		}
		for (i = 0; i < wbPtr->ngroups; i++) {
		    groups[i] = wbPtr->groups[i];
		}
		if (setgroups (min(wbPtr->ngroups, NGROUPS), groups) < 0) {
		    CHILD_ERROR("Couldn't set groups");
		}
#ifndef SETREUID
		/*
		 * No way to set uid and euid to different values --
		 * just use the effective uid.
		 */
		if (setuid (wbPtr->euid) < 0)
#else /* SETREUID */
		if (SETREUID (wbPtr->ruid, wbPtr->euid) < 0)
#endif /* !SETREUID */
		{
		    CHILD_ERROR("Couldn't set real/effective user ids");
		}
	    }

	    if (chdir (cwd) < 0) {
		CHILD_ERROR(cwd);
	    }

	    umask (wbPtr->umask);
	    SIGNAL(SIGPIPE, SIG_DFL);

#ifdef DOUBLECHECK_TIMEOUT
	    /*
	     * Check for export deadline expiration.  If we weren't able to get
	     * to this point in the time alotted, we give up.
	     */
	    if (wbPtr->deadline && time((time_t)0) > wbPtr->deadline) {
		xlog (XLOG_WARNING,
			"Import from %d@%s took %ld secs too long",
			ntohs(from->sin_port), InetNtoA(from->sin_addr),
			time((time_t)0) - wbPtr->deadline);
		CHILD_ERROR("Import took too long");
	    }
#endif /* DOUBLECHECK_TIMEOUT */

	    /*
	     * The user's process is ready to go
	     */
	    Rpc_Return(msg, sizeof("Ok"), (Rpc_Opaque)"Ok");
	
	    /*
	     * Don't want to do anything our parent is doing, so reset
	     * the RPC system and close the service sockets.
	     */
	    Rpc_Reset();
	    (void)close(tcpSocket);
	    (void)close(udpSocket);

	    SETPGRP();
	    pgrp.pid = getpid();

	    /*
	     * If we're still ok, fork and exec the program to
	     * be run, then wait for it to finish. We do a bare
	     * wait since we will suspend when it suspends, etc.
	     * We be a dedicated process...
	     */
	    cpid = vfork();
	    if (cpid == 0) {
		Rpc_Stat    rstat;
		int	    udpSocket;

		/*
		 * Tell our master about the new process group the
		 * job will be running under.  This will allow
		 * SIGSTOP and SIGKILL to be sent without affecting
		 * the parent.
		 * We copy the random key generated for the process
		 * by the parent to authenticate ourselves.
		 */
		pgrp.pgrp = getpid();
		pgrp.key = proc->key;

		udpSocket = Rpc_UdpCreate(FALSE, 0);
		rstat = Rpc_Call(udpSocket, &localAddr,
				 (Rpc_Proc)CUSTOMS_PGRP,
				 sizeof(pgrp), (Rpc_Opaque)&pgrp,
				 0, (Rpc_Opaque)0,
				 CUSTOMSINT_NRETRY, &retryTimeOut);
		if (rstat != RPC_SUCCESS) {
		    xlog_set(NULL, XLOG_PREVIOUS);
		    xlog (XLOG_ERROR, "PGRP call failed: %s",
			    Rpc_ErrorMessage(rstat));
		    xlog_set(NULL, XLOG_PREVIOUS);
		}
		else {
		    SETPGRP();
		}
		close(udpSocket);

		close(oldstdout);

		if (sock > 2) {
		    (void)close(sock);
		}
		/*
		 * Hook all stdio streams up to the remote socket.
		 * stdout has already been dealt with above.
		 * The only way to not lose stdin and stderr seems
		 * to be to do the dup-ing here rather than together
		 * with stdout above.
		 */
		dup2(1, 0);
		dup2(1, 2);

		execvp (file, argv);

		perror(file);
		_exit(5);
	    } else if (cpid < 0) {
		perror("Couldn't fork");
		/*
		 * Fake termination by signal
		 */
		WSTATUS(status) = SIGTERM;	/* status.w_termsig = SIGTERM */
	    }

	    /*
	     * Block all signals we can. Anything we get, our
	     * child will get too, and we will exit when it does,
	     * so there's no point in our dying without sending
	     * a status back, is there?
	     */
	    SIGEMPTYSET(mask);
	    SIGSETMASK(mask, oldmask);

	    /*
	     * Substitute new socket for sending log messages and exit
	     * statuses.
	     */
	    udpSocket = Rpc_UdpCreate(FALSE, 0);

	    /*
	     * No need for us to keep the socket open. Also want to print
	     * our messages to the log file, so redup the old stdout back to
	     * stream 1.
	     */
	    if (sock != 1) {
		close(1);
	    }
	    dup2(oldstdout, 1);
	    close(oldstdout);
	    
	    /*
	     * Redirect errors back to system log.
	     */
	    xlog_set(NULL, XLOG_PREVIOUS);

	    while (1) {
		Rpc_Stat    rstat;
		Exit_Data   retVal;
		
		if (WSTATUS(status) == 0) {
		    int pid;

		    /*
		     * Haven't got an exit status yet, so wait for one.
		     * We block on the wait since we've got nothing better
		     * to do.
		     */
		    do {
			pid = WAIT3(&status, WUNTRACED, NULL);
		    } while ((pid >= 0) && (pid != cpid) ||
			     (pid < 0) && (errno == EINTR));
		    
		    if (pid < 0) {
			perror("Unexpected error from wait");
		        WSTATUS(status) = 8<<8;	/* status.w_retcode = 8; */
		    }
		}

		/*
		 * Return exit status. We don't really care if the
		 * other side receives it. We're very optimistic.
		 * They'll find out either by the RPC or by the socket
		 * going away...
		 */
		if (verbose) {
		    xlog (XLOG_DEBUG,
			    "ImportProcess: Returning exit status to %d@%s",
			    ntohs(proc->retAddr.sin_port),
			    InetNtoA(proc->retAddr.sin_addr));
		}

		retVal.id = permit->permit.id;
		retVal.status = WSTATUS(status);
		retVal.signal = (WIFSTOPPED(status) ?
				  Signal_FromHost(WSTOPSIG(status)) :
		                (WIFSIGNALED(status) ?
				  Signal_FromHost(WTERMSIG(status)) : 
				SIGNAL_NONE));
		
		rstat = Rpc_Call(udpSocket, &proc->retAddr,
				 (Rpc_Proc)CUSTOMS_EXIT,
				 sizeof(retVal), (Rpc_Opaque)&retVal,
				 0, (Rpc_Opaque)0,
				 CUSTOMSINT_NRETRY, &retryTimeOut);
		if (rstat != RPC_SUCCESS) {
		    Log_Send(LOG_EXITFAIL, 1,
			     xdr_exportpermit, &permit->permit);
		    xlog (XLOG_DEBUG, "Couldn't return exit status: %s",
			   Rpc_ErrorMessage(rstat));
		}
		
		if (verbose) {
		    if (WIFSIGNALED(status)) {
			xlog (XLOG_DEBUG, "ImportProcess: Pid %d: signal %d",
				cpid, WTERMSIG(status));
		    } else if (WIFSTOPPED(status)) {
			xlog (XLOG_DEBUG, "ImportProcess: Pid %d: stopped(%d)",
				cpid, WSTOPSIG(status));
		    } else {
			xlog (XLOG_DEBUG, "ImportProcess: Pid %d: exit(%d)",
				cpid, WEXITSTATUS(status));
		    }
		}
		if (!WIFSTOPPED(status)) {
		    /*
		     * Force an EOF-equivalent on the socket on the remote side
		     */
		    close(sock);
		
		    /*
		     * The process is actually done, so break out of this
		     * loop after telling the logger that the job is
		     * finished.
		     */
		    Log_Send(LOG_FINISH, 2,
			     xdr_exportpermit, &permit->permit,
			     xdr_int, &status);

		    /*
		     * We don't allow detached children of the imported job
		     * to continue running.  Kill them, but be nice doing so.
		     * Also, the event is logged since this could mean
		     * someone is trying to evade the eviction mechanism.
		     */
		    if (KILLPG(cpid, SIGHUP) < 0) {
			if (errno != ESRCH) {
			    xlog (XLOG_WARNING,
				    "Error killing orphans of %d: %s",
				    cpid, strerror(errno));
			}
		    } else {
			xlog (XLOG_WARNING,
				"Killing orphans of process %d, uid %d",
				cpid, getuid());
			sleep(1);
			(void)KILLPG(cpid, SIGKILL);
		    }

		    break;
		} else {
		    /*
		     * Tell logger the job is just stopped and loop.
		     */
		    Log_Send(LOG_STOPPED, 1,
			     xdr_exportpermit, &permit->permit);
		    WSTATUS(status) = 0;
		}
	    }
	    exit(WEXITSTATUS(status));
	} else if (proc->pid == -1) {
	    /*
	     * Couldn't fork:
	     * close everything we just opened and return an error.
	     */
	    free((char *)proc);
	    free((char *)argv);
	    free((char *)envp);
	    ERROR("Could not fork");
	} else {
	    /*
	     * Parent process:
	     * Close the socket and start up the child reaper to catch dead
	     * children if it isn't going already (it won't be if there were
	     * no jobs running before this one).
	     */

	    Rpc_Ignore(sock);
	    (void)close(sock);
	    Lst_AtEnd (imports, (ClientData)proc);

	    Log_Send(LOG_START, 4,
		       xdr_exportpermit, &permit->permit,
		       xdr_short, &wbPtr->euid,
		       xdr_short, &wbPtr->ruid,
		       xdr_strvec, &argv);
	    free((char *)argv);
	    free((char *)envp);
	}
    }
}

/*-
 *-----------------------------------------------------------------------
 * Import_NJobs --
 *	Return the number of imported jobs. This includes not only
 *	currently-running jobs, but potential jobs as well. This is to
 *	keep the quota from being overflowed by requesting enough hosts
 *	to cause this machine to be overallocated before it can send
 *	of an availability packet...Better to overestimate the number
 *	of jobs and have this machine unavailable than to overload this
 *	machine...
 *
 * Results:
 *	The number of jobs.
 *
 * Side Effects:
 *	None.
 *
 *-----------------------------------------------------------------------
 */
Import_NJobs()
{
    return (Lst_Length (imports) + Lst_Length(permits));
}
/*-
 *-----------------------------------------------------------------------
 * ImportInfo --
 *	Provide information about all currently imported jobs.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	The info is sent as a reply.
 *
 *-----------------------------------------------------------------------
 */
/*ARGSUSED*/
static void
ImportInfo (from, msg, len, data)
    struct sockaddr_in	*from;
    Rpc_Message	  	msg;
    int	    	  	len;
    Rpc_Opaque 	  	data;
{
    LstNode 	     	ln;
    register Process	*procPtr;
    register ImportPermit	*permitPtr;
    Job_Info    	info[MAX_INFO_SIZE/sizeof(Job_Info)];
    register int	j = 0;
    time_t		now;

    CustomsCheckToken(msg, len, data);

    /*
     * The information about all the jobs is stored in the 'info' buffer
     * as an array of Job_Info structures.  The list is terminated by a
     * structure with pid = 0.
     */
    if (Import_NJobs() > sizeof(info)/sizeof(Job_Info)) {
	Rpc_Error(msg, RPC_TOOBIG);
	return;
    }

    time(&now);

    if (Lst_Open(imports) == FAILURE) {
	Rpc_Error(msg, RPC_SYSTEMERR);
	return;
    }
    for (ln=Lst_Next(imports);
	 !Lst_IsAtEnd(imports);
	 j++, ln=Lst_Next(imports))
    {
	procPtr = (Process *)Lst_Datum(ln);
	info[j].pid = procPtr->pgrp;
	info[j].id = procPtr->permit->permit.id;
	info[j].time = now - procPtr->start;
	info[j].uid = (info[j].id >> 16) & 0xffff;
	info[j].from_port = procPtr->retAddr.sin_port;
	info[j].from_addr = procPtr->retAddr.sin_addr.s_addr;
	strncpy(info[j].command, procPtr->command, sizeof(info[j].command));
	info[j].command[sizeof(info[j].command)-1] = '\0';
    }
    Lst_Close(imports);

    if (Lst_Open(permits) == FAILURE) {
	Rpc_Error(msg, RPC_SYSTEMERR);
	return;
    }
    for (ln=Lst_Next(permits);
	 !Lst_IsAtEnd(permits);
	 j++, ln=Lst_Next(permits))
    {
	permitPtr = (ImportPermit *)Lst_Datum(ln);
	info[j].pid = -1;
	info[j].id = permitPtr->permit.id;
	info[j].time = 0;
	info[j].uid = (info[j].id >> 16) & 0xffff;
	info[j].from_port = 0;
	info[j].from_addr = permitPtr->permit.addr.s_addr;
	strcpy(info[j].command, "<<<ALLOCATED>>>");
    }
    Lst_Close(permits);

    if (j < sizeof(info)/sizeof(Job_Info))
	info[j++].pid = 0;

    Rpc_Return(msg, ((char *)&info[j])-((char *)info), (Rpc_Opaque)info);
}

/*-
 *-----------------------------------------------------------------------
 * ImportSetStats --
 *	Reset the jobs statistics.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	The stats counters are reset to zero.
 *
 *-----------------------------------------------------------------------
 */
/*ARGSUSED*/
static void
ImportSetStats (from, msg, len, data)
    struct sockaddr_in	*from;
    Rpc_Message	  	msg;
    int	    	  	len;
    Rpc_Opaque 	  	data;
{
#ifndef RUSAGE_SELF
    struct tms tm;
#else
    struct rusage ru;
#endif

    if (from) {
	CustomsCheckToken(msg, len, data);
	CustomsReserved("ImportSetStats", from, msg);

	Rpc_Return(msg, 0, (Rpc_Opaque)0);
    }

    /*
     * Timestamp
     */
    stats.start = time((time_t *)0);

    /*
     * Initialize the fields that are used as accumulators
     */
    stats.jobs = 0;
    stats.evictions = 0;
    stats.signals = 0;
    stats.realTime = 0;
    stats.userTime = 0;
    stats.sysTime = 0;
    stats.avail = 0;
    stats.availChecks = 0;

    /*
     * Get checkpoint for user and system times
     */
#ifndef RUSAGE_SELF
    if (times(&tm) < 0) {
	xlog (XLOG_WARNING, "times: %s", strerror(errno));
    } else {
	userTimeMark = tm.tms_cutime;
	sysTimeMark = tm.tms_cstime;
    }
#else /* RUSAGE_SELF */
    if (getrusage(RUSAGE_CHILDREN, &ru) < 0) {
	xlog (XLOG_WARNING, "getrusage: %s", strerror(errno));
    } else {
	userTimeMark = ru.ru_utime;
	sysTimeMark = ru.ru_stime;
    }
#endif /* !RUSAGE_SELF */
}

/*-
 *-----------------------------------------------------------------------
 * ImportStats --
 *	Return job statistics.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	The stats are sent as a reply.
 *
 *-----------------------------------------------------------------------
 */
/*ARGSUSED*/
static void
ImportStats (from, msg, len, data)
    struct sockaddr_in	*from;
    Rpc_Message	  	msg;
    int	    	  	len;
    Rpc_Opaque 	  	data;
{
#ifndef RUSAGE_SELF
    struct tms tm;
#else
    struct rusage ru;
#endif

    CustomsCheckToken(msg, len, data);

    /*
     * Obtain the user and system times by subtracting the last checkpoint
     * values.
     */
#ifndef RUSAGE_SELF
    if (times(&tm) < 0) {
	xlog (XLOG_WARNING, "times: %s", strerror(errno));
    } else {
	stats.userTime = (tm.tms_cutime - userTimeMark)/HZ;
	stats.sysTime = (tm.tms_cstime - sysTimeMark)/HZ;
    }
#else /* RUSAGE_SELF */
    if (getrusage(RUSAGE_CHILDREN, &ru) < 0) {
	xlog (XLOG_WARNING, "getrusage: %s", strerror(errno));
    } else {
	stats.userTime = (ru.ru_utime.tv_sec - userTimeMark.tv_sec) +
		         (ru.ru_utime.tv_usec - userTimeMark.tv_usec +
			  USPS)/USPS - 1;
	stats.sysTime = (ru.ru_stime.tv_sec - sysTimeMark.tv_sec) +
		        (ru.ru_stime.tv_usec - sysTimeMark.tv_usec +
			 USPS)/USPS - 1;
    }
#endif /* !RUSAGE_SELF */

    Rpc_Return(msg, sizeof(stats), (Rpc_Opaque)&stats);
}

/*-
 *-----------------------------------------------------------------------
 * Import_Init --
 *	Initialize this module.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	The imports list is initialized.
 *
 *-----------------------------------------------------------------------
 */
void
Import_Init()
{
    imports = Lst_Init (FALSE);
    permits = Lst_Init (FALSE);
    (void)SIGNAL(SIGPIPE, SIG_IGN);
    (void)SIGNAL(SIGCHLD, ImportCatchChild);

    Rpc_ServerCreate(tcpSocket, (Rpc_Proc)CUSTOMS_IMPORT, ImportProcess,
		     Swap_WayBill, Rpc_SwapNull, (Rpc_Opaque)0);
    Rpc_ServerCreate(udpSocket, (Rpc_Proc)CUSTOMS_ALLOC, ImportAllocated,
		     Swap_AllocData, Swap_AllocReply, (Rpc_Opaque)0);
    Rpc_ServerCreate(udpSocket, (Rpc_Proc)CUSTOMS_KILL, ImportHandleKill,
		     Swap_Kill, Rpc_SwapNull, (Rpc_Opaque)0);
    Rpc_ServerCreate(udpSocket, (Rpc_Proc)CUSTOMS_JOBS, ImportInfo,
		     Rpc_SwapLong, Swap_Jobs, (Rpc_Opaque)0);
    Rpc_ServerCreate(udpSocket, (Rpc_Proc)CUSTOMS_STATS, ImportStats,
		     Rpc_SwapLong, Swap_Stats, (Rpc_Opaque)0);
    Rpc_ServerCreate(udpSocket, (Rpc_Proc)CUSTOMS_SETSTATS, ImportSetStats,
		     Rpc_SwapLong, Rpc_SwapNull, (Rpc_Opaque)0);

    /*
     * Initialize stats
     */
    ImportSetStats((struct sockaddr_in *)0, (Rpc_Message)0, 0, (Rpc_Opaque)0);

    /*
     * Import-internal RPC for communicating new process groups.
     * No need for byte-swapping since always local.
     */
    Rpc_ServerCreate(udpSocket, (Rpc_Proc)CUSTOMS_PGRP, ImportPgrp,
		     Rpc_SwapNull, Rpc_SwapNull, (Rpc_Opaque)0);
}
