/*-
 * customs.c --
 *	A server to inform clients, using RPC, where they can send
 *	processes.
 *
 *	Each machine that wishes to export processes must have
 *	a server running. The first server to start is the
 *	Master Customs Agent (MCA), while everything else is a
 *	Slave Customs Agent (SCA).
 *
 *	The job of the MCA is to track the availability of machines
 *	running SCA's and allocate them to other SCA's as necessary.
 *
 *	An SCA is responsible for sampling its host's status from
 *	time to time to see if it can accept processes from other
 *	machines. This information must be provided to the MCA
 *	at regular intervals.
 *
 *	An SCA can specify which hosts it is willing to serve for
 *	(there must be at least one) and the MCA will only allocate
 *	it to one of the hosts in that list. A host will not be allocated
 *	to an SCA again until all other available hosts which serve
 *	the requesting SCA have been used.
 *
 *	Several procedures are provided to clients of the customs agents.
 *	The CUSTOMSPROC_HOST will return a structure containing the address
 *	of a machine on which the exported process may be run. It also
 *	returns an identifying number which must be passed to the serving
 *	machine before it will execute the process. The MCA does not guarantee
 *	that the serving machine is actually up, though it tries hard to
 *	ensure this. If the machine has a long interval between availability
 *	packets, all bets are off.
 *
 * 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: customs.c,v 1.83 2001/02/17 01:09:58 stolcke Exp $ ICSI (Berkeley)";
#endif /* not lint */

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

#ifdef hpux
#include    <sys/pstat.h>
#endif

#include    "sprite.h"
#include    "customsInt.h"
#include    "xlog.h"

#include    "patchlevel.h"	/* for global version info */
#include    "version.h"		/* for build version info */

char	    	  	    localhost[MACHINE_NAME_SIZE];
Boolean	    	  	    amMaster;	/* TRUE if this process is MCA */
Boolean	    	    	    canBeMaster = FALSE;    /* TRUE if this agent may
						     * become the MCA */
struct sockaddr_in	    serverAddr;	/* Address of server host */

Boolean	    	  	    verbose = FALSE;

Rpc_ULong    	    	    arch;   	/* Machine architecture code */
char	    	  	    *regPacket;
int	    	  	    regPacketLen;
Lst	    	  	    clients;
Lst	    	  	    attributes;
int	    	  	    initARGC;
char    	  	    **initARGV;
Boolean			    initialized = FALSE;

struct timeval	retryTimeOut = {
    CUSTOMSINT_RETRY, CUSTOMSINT_URETRY
};

short	    	    	    tcpPort;
int	    	  	    tcpSocket;
short			    udpPort;
int	    	  	    udpSocket;	/* The actual socket to which clients
					 * connect */
struct sockaddr_in	    localAddr;	/* Address of local socket */

static Version_Data version = {
    MAJORVERSION,
    MINORVERSION,
    PATCHLEVEL,
    BUILDNO,
    BUILDDATE,
    BUILDER
};

/*-
 *-----------------------------------------------------------------------
 * MakeRegistrationPacket --
 *	Mangles the local host and argument vector into a packet to
 *	be sent to the master at registration time.  Format is
 *		<hostname>\0+<arch>\
 *		<client-1>\0<client-2>\0...<client-n>\0\0\
 *		<attr-1>\0<attr-2>\0...<attr-m>\0\0
 *	Notice <arch> is aligned as a 32bit integer, and the client and
 *	attribute vectors are encoded in argv format.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	regPacket is allocated and filled and regPacketLen is altered.
 *
 *-----------------------------------------------------------------------
 */
static void
MakeRegistrationPacket()
{
    char *cp, *clntBuf, *attrBuf;
    int localhostLen, clntLen, attrLen;

    localhostLen = Customs_Align(strlen(localhost) + 1, int);
    clntBuf = Customs_LstToArgv (clients, &clntLen);
    attrBuf = Customs_LstToArgv (attributes, &attrLen);
    regPacketLen = localhostLen + sizeof(Rpc_Long) + sizeof(Rpc_Long) +
		   clntLen + attrLen;

    regPacket = (char *)emalloc((unsigned)regPacketLen);
    strncpy (regPacket, localhost, localhostLen);
    cp = regPacket + localhostLen;
    *((Rpc_ULong *)cp) = arch;
    cp += sizeof(Rpc_ULong);
    memcpy (cp, clntBuf, clntLen);
    cp += clntLen;
    memcpy (cp, attrBuf, attrLen);
    free (clntBuf);
    free (attrBuf);
}

/*-
 *-----------------------------------------------------------------------
 * CustomsPing --
 *	Do-nothing function to handle the CUSTOMS_PING procedure.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	None.
 *
 *-----------------------------------------------------------------------
 */
/*ARGSUSED*/
static void
CustomsPing(from, msg, len, data)
    struct sockaddr_in	*from;
    Rpc_Message	  	msg;
    int	    	  	len;
    Rpc_Opaque 	  	data;
{
    CustomsCheckToken(msg, len, data);

    Rpc_Return(msg, 0, data);
}

/*-
 *-----------------------------------------------------------------------
 * CustomsHost --
 *	Stub for handling the CUSTOMS_HOST procedure. Issues
 *	CUSTOMS_HOSTINT call to master. While the master could conceivably
 *	call MCA_HostInt, we don't do that b/c the port number for return
 *	will be wrong. If the flags in the data contain EXPORT_USELOCAL,
 *	the availability of the local machine is checked before the call
 *	is issued.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	An ExportPermit is sent in reply.
 *
 *-----------------------------------------------------------------------
 */
static void
CustomsHost(from, msg, len, data)
    struct sockaddr_in	*from;
    Rpc_Message	  	msg;
    int	    	  	len;
    Rpc_Opaque	  	data;
{
    ExportPermit    	permit;
    Host_Data	  	*host = (Host_Data *)data;
    Lst			attrLst;
    Rpc_Long    	    	rating;	/* Junk variable */

    /*
     * We require this call to be issued from a reserved port to
     * ensure that the uid in the request is accurate.  This will
     * usually mean the client program has to be suid root, so that
     * users cannot write fake clients.
     */
    CustomsReserved("CustomsHost", from, msg);
	
    /*
     * A CustomsHost call must come from the local host to
     * keep people from requesting from a different machine, not
     * that it would work anyway, since the permit published
     * by the MCA wouldn't match...
     */
    if (!Local(from)) {
	Rpc_Error(msg, RPC_ACCESS);
	return;
    } else if (len < sizeof(Host_Data)) {
	Rpc_Error(msg, RPC_BADARGS);
	return;
    }

    attrLst = Customs_ArgvToLst (host->attrs, (int *)NULL);

    /*
     * EXPORT_TEST allocation uses uid -1 to tell the server
     * that no real permit should be create.
     */
    if (host->flags & EXPORT_TEST) {
	host->uid = -1;
    }

    if (Elect_InProgress() ||
	((host->flags & EXPORT_USELOCAL) &&
	Avail_Local (~(AVAIL_IDLE|AVAIL_LOAD), &rating) == 0) &&
	Customs_CheckAttr (attrLst, attributes))
    {
	/*
	 * If we're electing, or being nice and the local machine is
	 * available, barring keyboard idle time and load, of course, then
	 * send a failure message to the client -- set the id field to 1
	 * to indicate that the local host is available.
	 */
	permit.addr.s_addr = htonl(INADDR_ANY);
	permit.id = 1;
	if (verbose) {
	    xlog (XLOG_DEBUG, "CustomsHost: honoring USELOCAL");
	}
	Rpc_Return(msg, sizeof(permit), (Rpc_Opaque)&permit);
    } else {
	/*
	 * Ask the master where to go.
	 */
	Rpc_Stat  	rstat;

	rstat = Rpc_Call(udpSocket, &masterAddr, (Rpc_Proc)CUSTOMS_HOSTINT,
			 len, data, sizeof(permit), (Rpc_Opaque)&permit,
			 CUSTOMSINT_NRETRY, &retryTimeOut);
	if (rstat != RPC_SUCCESS) {
	    permit.addr.s_addr = htonl(INADDR_ANY);
	    if (verbose) {
		xlog (XLOG_DEBUG, "CustomsHost: %s",
			Rpc_ErrorMessage(rstat));
	    }
	} else if (verbose) {
	    xlog (XLOG_DEBUG, "CustomsHost: returns %s\n",
		    InetNtoA(permit.addr));
	}

	/*
	 * If no remote host was available, but the user wants a specific
	 * set of attributes, check the local attributes and return an
	 * indication of the result.  This allows the client to determine
	 * whether it can continue running the job locally or not.
	 * If the customs agent is configured to execute jobs from local
	 * clients then we tell the client that it is not okay to run the
	 * job locally (since the master would have told us if it were).
	 */
	if (CUSTOMS_FAIL(&permit.addr)) {
	    if (!localJobs && Customs_CheckAttr (attrLst, attributes)) {
		permit.id = 1;
	    } else {
		permit.id = 0;
	    }
	}
	    
	if (permit.addr.s_addr == CUSTOMS_FROMADDR) {
	    permit.addr = masterAddr.sin_addr;
	}

	Rpc_Return(msg, sizeof(permit), (Rpc_Opaque)&permit);
	if (rstat == RPC_TIMEDOUT) {
	    Elect_GetMaster();
	}
    }

    Lst_Destroy (attrLst, free);
}

/*-
 *-----------------------------------------------------------------------
 * CustomsMaster --
 *	Stub to handle CUSTOMS_MASTER procedure. Returns the address of
 *	the current master agent.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	The address of the master is sent to the client, or an RPC_TIMEDOUT
 *	error is returned if an election is going on.
 *
 *-----------------------------------------------------------------------
 */
/*ARGSUSED*/
static void
CustomsMaster(from, msg, len, data)
    struct sockaddr_in  *from;
    Rpc_Message		msg;
    int			len;
    Rpc_Opaque 	  	data;
{
    CustomsCheckToken(msg, len, data);

    if (Elect_InProgress()) {
	/*
	 * If the call wasn't local, it must be another lost agent looking
	 * for its master. Since we can't help it there, we don't reply at all.
	 * A zero-length argument means the inquiry is from a client program
	 * such as reginfo, to which we always reply.
	 */
	if (Local(from) || len == 0) {
	    Rpc_Error(msg, RPC_TIMEDOUT);
	}
    } else if (Local(from) || len == 0 || (*(Rpc_Long *)data == elect_Token)) {
	SockAddr	data;

	/*
	 * Copy sin_addr data into own structure for portability reasons
	 * If we are the master, let the other side know by setting the
	 * address to CUSTOMS_FROMADDR.  It can then fill in the address
	 * it used to reach us.  This ensures that we don't return an address
	 * that is not reachable from the other end (e.g., because we
	 * are using multiple network interfaces).
	 */
	data.family = masterAddr.sin_family;
	data.port = masterAddr.sin_port;
	if (amMaster) {
	    data.addr = CUSTOMS_FROMADDR;
	} else {
	    data.addr = masterAddr.sin_addr.s_addr;
	}

	/*
	 * Call is local, or it's remote and has the same election token
	 * as we, so send our idea of the current master in return.
	 */
	Rpc_Return(msg, sizeof(data), (Rpc_Opaque)&data);
    }
}

/*-
 *-----------------------------------------------------------------------
 * CustomsAbort --
 *	Abort this daemon. Returns nothing. Just exits.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	The process exits or aborts, depending on the data parameter.
 *
 *-----------------------------------------------------------------------
 */
/*ARGSUSED*/
static void
CustomsAbort(from, msg, len, data)
    struct sockaddr_in  *from;
    Rpc_Message		msg;
    int			len;
    Rpc_Opaque 	  	data;
{
    Boolean doAbort;
    int nJobs;

    /*
     * from == NULL when called from the SIGTERM handler
     */
    if (from) {
	CustomsCheckToken(msg, len, data);
	CustomsReserved("CustomsAbort", from, msg);

	if (len != 0 && len != sizeof(elect_Token)) {
	    Rpc_Error(msg, RPC_BADARGS);
	    return;
	} else if (len != 0 && *(Rpc_Long *)data != elect_Token) {
	    /*
	     * Not our type -- ignore
	     */
	    return;
	}

	xlog(XLOG_INFO, "Received ABORT message from %d@%s...",
	       ntohs(from->sin_port),
	       InetNtoA(from->sin_addr));

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

    /*
     * Check if this function was called internally to handle an
     * an exception interrupt etc.  The caller sets the 'data' argument
     * to TRUE in that case.
     */
    doAbort = (from == (Rpc_Message)0) && (Boolean)data;

    /* 
     * Prevent further allocation of imports and kick out any current ones.
     * Skip this step if we are 'aborting' to avoid raising more
     * SIGBUS's and SIGSEGV's and such.
     */
    if (initialized && !doAbort) {
	maxImports = 0;
	nJobs = Import_NJobs();
	if (verbose || nJobs > 0) {
	    xlog (verbose ? XLOG_DEBUG : XLOG_INFO,
		    "CustomsAbort: evicting %d import(s) with signal %d",
		    nJobs, EVICT_SIGNAL);
	}
	(void)Import_Kill(0, EVICT_SIGNAL, (struct sockaddr_in *)0);

	OS_Exit();
    }

    xlog(XLOG_INFO, "Customs EXITING");

    if (doAbort) {
	abort();
    } else {
	exit(0);
    }
}

/*-
 *-----------------------------------------------------------------------
 * CustomsRestart --
 *	Re-execute ourselves using the original argument vector.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	We reexecute.
 *
 *-----------------------------------------------------------------------
 */
/*ARGSUSED*/
static void
CustomsRestart(from, msg, len, data)
    struct sockaddr_in	*from;
    Rpc_Message	  	msg;
    int	    	  	len;
    Rpc_Opaque	  	data;
{
    int	    	  	i;
    int			fd;
    char		*cmdstr;
    int			cmdlen;

    /*
     * from == NULL when called from the SIGHUP handler
     */
    if (from) {
	CustomsCheckToken(msg, len, data);
	CustomsReserved("CustomsRestart", from, msg);

	xlog(XLOG_INFO, "Received RESTART from %d@%s ...",
		ntohs(from->sin_port), InetNtoA(from->sin_addr));
    }

    /*
     * Check that no jobs are running. If not, the user would lose control
     * over those jobs, and besides, the restart would fail because the
     * RPC sockets are still in use.
     * NOTE: We *could* evict running jobs, but we leave that to the more
     * drastic CUSTOMS_ABORT, and rather not kill someone's precious
     * jobs when all be need is a restart.
     */
    if (Import_NJobs () > 0) {
	if (from) {
	    Rpc_Error(msg, RPC_SYSTEMERR);
	}
	xlog(XLOG_WARNING, "Restart attempted while jobs are running");
	return;
    }

    /* 
     * Prevent further allocation of imports
     */
    maxImports = 0;

    /*
     * Re-assemble original command line for exec()
     */
    cmdlen = 0;
    for (i = 0; i < initARGC; i++) {
	cmdlen += strlen(initARGV[i]) + 1;
    }
    cmdstr = emalloc(cmdlen);
    cmdstr[0] = '\0';
    for (i = 0; i < initARGC; i++) {
	strcat(cmdstr, initARGV[i]);
	strcat(cmdstr, " ");
    }
    xlog(XLOG_INFO, "executing: %s", cmdstr);

    /*
     * We would like to send the ACK only after successful exec, but without
     * vfork we can't.
     */
    if (from) {
	Rpc_Return(msg, 0, (Rpc_Opaque)0);
    }

    /*
     * Close any files the OS module has opened.
     */
    OS_Exit();

    /*
     * Close log.
     */
    xlog_set(NULL, XLOG_NONE);

    /*
     * Make sure stdin and stderr are open to something so we don't close
     * the sockets in our next life.
     */
    fd = open("/dev/null", 0);
    if (fd != 0) {
	dup2(fd, 0);
    }
    if (fd != 2) {
	dup2(fd, 2);
    }
    if (fd > 2) {
	close(fd);
    }

    execvp(initARGV[0],initARGV);

    xlog_set(NULL, XLOG_PREVIOUS);

    perror(initARGV[0]);
    xlog(XLOG_FATAL, "Customs ABORTING");

    exit(1);
}

/*-
 *-----------------------------------------------------------------------
 * CustomsDebug --
 *	Turn on debugging for us and/or the rpc system
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	verbose may be set true, as may rpcDebug.
 *-----------------------------------------------------------------------
 */
/*ARGSUSED*/
static void
CustomsDebug(from, msg, len, data)
    struct sockaddr_in	*from;
    Rpc_Message	  	msg;
    int	    	  	len;
    int	  	  	*data;
{
    CustomsReserved("CustomsDebug", from, msg);

    if (len != sizeof(int)) {
	Rpc_Error(msg, RPC_BADARGS);
	return;
    }

    Rpc_Debug(*data & DEBUG_RPC);
    verbose = *data & DEBUG_CUSTOMS;

    Rpc_Return(msg, 0, (Rpc_Opaque)NULL);
}

/*-
 *-----------------------------------------------------------------------
 * CustomsAllow --
 *	Add address to access list.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	RPC access list is modified.
 *-----------------------------------------------------------------------
 */
/*ARGSUSED*/
static void
CustomsAllow(from, msg, len, data)
    struct sockaddr_in	*from;
    Rpc_Message	  	msg;
    int	    	  	len;
    Rpc_ULong		*data;
{
    CustomsReserved("CustomsAllow", from, msg);

    if (len != sizeof(Rpc_ULong)) {
	Rpc_Error(msg, RPC_BADARGS);
	return;
    }

    if (!Rpc_Allow(*data)) {
	Rpc_Error(msg, RPC_SYSTEMERR);
    }

    Rpc_Return(msg, 0, (Rpc_Opaque)NULL);
}

/*-
 *-----------------------------------------------------------------------
 * CustomsDeny --
 *	Remove address from access list.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	RPC access list is modified.
 *-----------------------------------------------------------------------
 */
/*ARGSUSED*/
static void
CustomsDeny(from, msg, len, data)
    struct sockaddr_in	*from;
    Rpc_Message	  	msg;
    int	    	  	len;
    Rpc_ULong		*data;
{
    CustomsReserved("CustomsDeny", from, msg);

    if (len != sizeof(Rpc_ULong)) {
	Rpc_Error(msg, RPC_BADARGS);
	return;
    }

    if (!Rpc_Deny(*data)) {
	Rpc_Error(msg, RPC_SYSTEMERR);
    }

    Rpc_Return(msg, 0, (Rpc_Opaque)NULL);
}

/*-
 *-----------------------------------------------------------------------
 * PrintVersion --
 *	Print version info on stdout
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	Ditto.
 *-----------------------------------------------------------------------
 */
/*ARGSUSED*/
static void
PrintVersion()
{
    xlog(XLOG_DEBUG, "Customs version %d.%d.%d #%d: built by %s on %s",
		version.majorversion,
		version.minorversion,
		version.patchlevel,
		version.buildno,
		version.builder,
		version.builddate);
}
/*-
 *-----------------------------------------------------------------------
 * CustomsVersion --
 *	Return version info for this daemon
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	None.
 *-----------------------------------------------------------------------
 */
/*ARGSUSED*/
static void
CustomsVersion(from, msg, len, data)
    struct sockaddr_in	*from;
    Rpc_Message	  	msg;
    int	    	  	len;
    int	  	  	*data;
{
    CustomsCheckToken(msg, len, data);

    if (verbose)
	PrintVersion();

    Rpc_Return(msg, sizeof(version), (Rpc_Opaque)&version);
}

/*-
 *-----------------------------------------------------------------------
 * Customs_ProcTitle --
 *	Set process title for ps (borrowed from sendmail).
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	Clobbers argv of our main procedure so ps(1) will
 *	display the title.
 *
 *-----------------------------------------------------------------------
 */
void
Customs_SetProcTitle(str)
    char *str;
{
#ifdef SETPROCTITLE
#ifdef PSTAT_SETCMD
    /*
     * Use pstat(2) system call on HP-UX
     */
    char *title;
    union pstun un;

    title = emalloc(strlen(str) + strlen(initARGV[0]) + 4);
    sprintf(title, "%s (%s)", str, initARGV[0]);
    un.pst_command = title;

    pstat(PSTAT_SETCMD, un, strlen(un.pst_command), 0, 0);
#else /* !PSTAT_SETCMD */
    /*
     * Write into argv string space.
     */
    char *p;
    int i;
    static char *lastArgv = NULL;

    if (!lastArgv) {
	lastArgv = initARGV[initARGC - 1] + strlen(initARGV[initARGC - 1]);
    }

    /*
     * Make ps print "(customs)"
     */
    p = initARGV[0];
    *p++ = '-';

    i = strlen(str);
    if (i > lastArgv - p - 2) {
	i = lastArgv - p - 2;
    }
    (void) strncpy(p, str, i);
    p += i;
    while (p < lastArgv)
	*p++ = ' ';
#endif /* PSTAT_SETCMD */
#endif /* SETPROCTITLE */
}

/*-
 *-----------------------------------------------------------------------
 * gettime --
 *	Get a time value from a string. It must be in the form
 *	    mm:ss
 *	where 'm' is a minute's digit and 's' is a second's digit.
 *	seconds may not be greater than 60, for obvious reasons.
 *
 * Results:
 *
 * Side Effects:
 *
 *-----------------------------------------------------------------------
 */
int
gettime (str)
    char    *str;
{
    int	    min,
	    sec;

    for (min = 0;
	 *str != '\0' && *str != ':' ;
	 min = 10 * min + *str++ - '0') {
	     continue;
    }
    if (*str == '\0') {
	sec = min;
	min = 0;
    } else {
	for (sec = 0, str++; *str != '\0'; sec = 10 * sec + *str++ - '0') {
	    continue;
	}
    }
    if (sec >= 60) {
	printf("malformed time (only 60 seconds in a minute)\n");
	exit(1);
    }
    return (min * 60 + sec);
}

/*ARGSUSED*/
static SIGRET
CustomsDebugOn(signo)
    int		signo;
{
    verbose = TRUE;
    Rpc_Debug(True);

    SIGRESTORE(signo, CustomsDebugOn);
}

/*ARGSUSED*/
static SIGRET
CustomsDebugOff(signo)
    int		signo;
{
    verbose = FALSE;
    Rpc_Debug(False);

    SIGRESTORE(signo, CustomsDebugOff);
}

static SIGRET
CustomsInit(signo)
    int		signo;
{
    xlog(XLOG_INFO, "Restarting on signal %d ...", signo);
    CustomsRestart((struct sockaddr_in *)0, (Rpc_Message)0, 0, (Rpc_Opaque)0);

    SIGRESTORE(signo, CustomsInit);
}

static SIGRET
CustomsDown(signo)
    int		signo;
{
    Boolean abort;

    xlog(XLOG_INFO, "Going down on signal %d ...", signo);

    abort = (signo == SIGQUIT) || (signo == SIGBUS) || (signo == SIGSEGV);

    CustomsAbort((struct sockaddr_in *)0, (Rpc_Message)0,
		 0, (Rpc_Opaque)abort);
}

/*-
 *-----------------------------------------------------------------------
 * Usage --
 *	Print out the flags we accept and die.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	The process exits.
 *
 *-----------------------------------------------------------------------
 */
void
Usage ()
{
    printf ("Usage: customs <options> {host [host ...] | ALL }\n");
    printf ("\t-verbose         Print verbose log messages\n");
    printf ("\t-check <time>    Set availability check interval\n");
    printf ("\t-localjobs <0|1> Allow jobs from local clients\n");
    printf ("\t-idle <time>     Set minimum idle time\n");
    printf ("\t-idlecrit <num>  Set idle criterion\n");
    printf ("\t-load <loadavg>  Set maximum load average\n");
    printf ("\t-swap <percent>  Set minimum free swap space\n");
    printf ("\t-jobs <num>      Set maximum imported jobs\n");
    printf ("\t-exclusives <num>Set maximum exclusive jobs\n");
    printf ("\t-proc <num>      Set minimum free processes\n");
    printf ("\t-cpu <time>      Set cpu time limit for imported jobs\n");
    printf ("\t-memory <kbytes> Set memory use limit for imported jobs\n");
    printf ("\t-nice <level>    Set nice increment for imported jobs\n");
    printf ("\t-npri <prio>     Set non-degrading priority for imported jobs\n");
    printf ("\t-access <level>  Set user access level\n");
    printf ("\t-evict <time>    Set eviction delay for imported jobs\n");
    printf ("\t-net <netnum>    Set customs network number\n");
    printf ("\t-arch <code>     Set customs architecture code\n");
    printf ("\t-attr <string>   Add attribute string\n");
    printf ("\t-server <host>   Set server address\n");
    printf ("\t-allow <addr>    Allow RPC access from address\n");
    printf ("\t-deny <addr>     Deny RPC access to address\n");
    printf ("\t-master          Allow agent to become the master, if needed\n");
    exit(1);
}

/*-
 *-----------------------------------------------------------------------
 * main --
 *	Check the correctness of the arguments, look for an MCA and become
 *	it if it doesn't exist, registering ourselves in the process.
 *	Else register with the MCA and go into SCA mode.
 *
 * Results:
 *	none.
 *
 * Side Effects:
 *	Billions and Billions.
 *
 *-----------------------------------------------------------------------
 */
main (argc, argv)
    int	    	  	argc;
    char    	  	**argv;
{
    Boolean 	  	doLog = TRUE;
    Boolean 	  	debug = FALSE;
    Boolean 	  	version = FALSE;
    Avail_Data	  	criteria;
    int	    	  	checkTime;
    struct servent  	*sep;
    char		*serverName = NULL;
    int			i;

    clients = Lst_Init (FALSE);
    attributes = Lst_Init (FALSE);

    initARGC = argc;
    initARGV = argv;

    argc--, argv++;
    criteria.changeMask = 0;
    checkTime = 0;

    while (argc > 0) {
	if (strcmp (*argv, "-verbose") == 0) {
	    verbose = TRUE;
	} else if (strcmp (*argv, "-nolog") == 0) {
	    doLog = FALSE; debug = TRUE;
	} else if (strcmp (*argv, "-debug") == 0) {
	    debug = TRUE;
	} else if (strcmp (*argv, "-v") == 0 ||
	         strcmp (*argv, "-version") == 0) {
	    version = TRUE;
	} else if (strcmp (*argv, "-check") == 0) {
	    if (argc > 1) {
		checkTime = gettime(argv[1]);
		argc--;
		argv++;
	    } else {
		printf("-check needs a time as an argument\n");
		Usage();
		/*NOTREACHED*/
	    }
	} else if (strcmp (*argv, "-localjobs") == 0) {
	    if (argc > 1) {
		criteria.localJobs = atoi(argv[1]);
		argc--;
		argv++;
		criteria.changeMask |= AVAIL_TOLOCAL;
	    } else {
		printf("-localJobs needs 0 or 1 as an argument\n");
		Usage();
		/*NOTREACHED*/
	    }
	} else if (strcmp (*argv, "-idle") == 0) {
	    if (argc > 1) {
		criteria.idleTime = gettime(argv[1]);
		argc--;
		argv++;
		criteria.changeMask |= AVAIL_IDLE;
	    } else {
		printf("-idle needs a time as an argument\n");
		Usage();
		/*NOTREACHED*/
	    }
	} else if (strcmp (*argv, "-idlecrit") == 0) {
	    if (argc > 1) {
		criteria.idleCrit = atoi(argv[1]);
		argc--;
		argv++;
		criteria.changeMask |= AVAIL_IDLECRIT;
	    } else {
		printf("-idlecrit needs a number as an argument\n");
		Usage();
		/*NOTREACHED*/
	    }
	} else if (strcmp (*argv, "-swap") == 0) {
	    if (argc > 1) {
		criteria.swapPct = atoi(argv[1]);
		argc--;
		argv++;
		criteria.changeMask |= AVAIL_SWAP;
	    } else {
		printf("-swap needs a percentage as an argument\n");
		Usage();
		/*NOTREACHED*/
	    }
	} else if (strcmp (*argv, "-load") == 0) {
	    double	maxLoad;

	    if (argc > 1) {
		maxLoad = atof(argv[1]);
		criteria.loadAvg = (int)(maxLoad * LOADSCALE);
		criteria.changeMask |= AVAIL_LOAD;
		argc--;
		argv++;
	    } else {
		printf("-load needs a load average as an argument\n");
		Usage();
		/*NOTREACHED*/
	    }
	} else if (strcmp (*argv, "-jobs") == 0) {
	    if (argc > 1) {
		criteria.imports = atoi(argv[1]);
		argc--;
		argv++;
		criteria.changeMask |= AVAIL_IMPORTS;
	    } else {
		printf("-jobs needs a number of jobs as an argument\n");
		Usage();
		/*NOTREACHED*/
	    }
	} else if (strcmp (*argv, "-exclusives") == 0) {
	    if (argc > 1) {
		maxExclusives = atoi(argv[1]);
		argc--;
		argv++;
	    } else {
		printf("-exclusives needs a number of jobs as an argument\n");
		Usage();
		/*NOTREACHED*/
	    }
	} else if (strcmp (*argv, "-proc") == 0) {
	    if (argc > 1) {
		criteria.procs = atoi(argv[1]);
		argc--;
		argv++;
		criteria.changeMask |= AVAIL_PROC;
	    } else {
		printf("-proc needs a number of processes as an argument\n");
		Usage();
		/*NOTREACHED*/
	    }
	} else if (strcmp (*argv, "-cpu") == 0) {
	    if (argc > 1) {
		criteria.cpuLimit = gettime(argv[1]);
		argc--;
		argv++;
		criteria.changeMask |= AVAIL_CPU;
	    } else {
		printf("-cpu needs a time as an argument\n");
		Usage();
		/*NOTREACHED*/
	    }
	} else if (strcmp (*argv, "-memory") == 0) {
	    if (argc > 1) {
		criteria.memLimit = atoi(argv[1]);
		argc--;
		argv++;
		criteria.changeMask |= AVAIL_MEMORY;
	    } else {
		printf("-memory needs an integer (K bytes) as an argument\n");
		Usage();
		/*NOTREACHED*/
	    }
	} else if (strcmp (*argv, "-nice") == 0) {
	    if (argc > 1) {
		criteria.niceLevel = atoi(argv[1]);
		argc--;
		argv++;
		criteria.changeMask |= AVAIL_NICE;
	    } else {
		printf("-nice needs a nice level number as an argument\n");
		Usage();
		/*NOTREACHED*/
	    }
	} else if (strcmp (*argv, "-npri") == 0) {
	    if (argc > 1) {
		criteria.npriLevel = atoi(argv[1]);
		argc--;
		argv++;
		criteria.changeMask |= AVAIL_NPRI;
	    } else {
		printf("-npri needs a priority as an argument\n");
		Usage();
		/*NOTREACHED*/
	    }
	} else if (strcmp (*argv, "-access") == 0) {
	    if (argc > 1) {
		criteria.checkUser = atoi(argv[1]);
		argc--;
		argv++;
		criteria.changeMask |= AVAIL_CHECK;
	    } else {
		printf("-access needs a level number as an argument\n");
		Usage();
		/*NOTREACHED*/
	    }
	} else if (strcmp (*argv, "-evict") == 0) {
	    if (argc > 1) {
		criteria.evictDelay = gettime(argv[1]);
		argc--;
		argv++;
		criteria.changeMask |= AVAIL_EVICT;
	    } else {
		printf("-evict needs a time as an argument\n");
		Usage();
		/*NOTREACHED*/
	    }
	} else if (strcmp (*argv, "-net") == 0) {
	    if (argc > 1) {
		elect_Token = atoi(argv[1]);
		argc--;
		argv++;
	    } else {
		printf("-net needs a network number for an argument\n");
		Usage();
		/*NOTREACHED*/
	    }
	} else if (strcmp (*argv, "-arch") == 0) {
	    if (argc > 1) {
		arch = atoi(argv[1]);
		argc--;
		argv++;
	    } else {
		printf("-arch needs an architecture code for an argument\n");
		Usage();
		/*NOTREACHED*/
	    }
	} else if (strcmp(*argv, "-bias") == 0) {
	    if (argc > 1) {
		avail_Bias = atoi(argv[1]);
		argc--;
		argv++;
	    } else {
		printf("-bias needs a bias value for an argument\n");
		Usage();
		/*NOTREACHED*/
	    }
	} else if (strcmp (*argv, "-attr") == 0) {
	    if (argc > 1) {
		(void)Lst_AtEnd (attributes, (ClientData)argv[1]);
		argc--;
		argv++;
	    } else {
		printf("-attr needs an attribute string for an argument\n");
		Usage();
		/*NOTREACHED*/
	    }
	} else if (strcmp (*argv, "-server") == 0) {
	    if (argc > 1) {
		serverName = argv[1];
		argc--;
		argv++;
	    } else {
		printf("-server needs a hostname for an argument\n");
		Usage();
		/*NOTREACHED*/
	    }
	} else if (strcmp (*argv, "-allow") == 0) {
	    if (argc > 1) {
		/* process later */
		argc--;
		argv++;
	    } else {
		printf("-allow needs a IP number for an argument\n");
		Usage();
		/*NOTREACHED*/
	    }
	} else if (strcmp (*argv, "-deny") == 0) {
	    if (argc > 1) {
		/* process later */
		argc--;
		argv++;
	    } else {
		printf("-deny needs a IP number for an argument\n");
		Usage();
		/*NOTREACHED*/
	    }
	} else if (strcmp(*argv, "-master") == 0) {
	    canBeMaster = TRUE;
	} else if (**argv == '-') {
	    printf ("Unknown option %s\n", *argv);
	    Usage();
	    /*NOTREACHED*/
	} else {
	    (void)Lst_AtEnd (clients, (ClientData)*argv);
	}
	argc--; argv++;
    }

    if (version) {
	PrintVersion();
	exit(0);
    }

    if (Lst_IsEmpty (clients)) {
	xlog (XLOG_FATAL, "You must serve at least one host!");
	exit(1);
    }

    if (!debug) {
	int t, pid;

	/*
	 * First detach from our parent, then from the tty...
	 */
	pid = fork();
	if (pid == -1) {
	    perror("fork");
	} else if (pid != 0) {
	    exit(0);
	}

#ifndef TIOCNOTTY
	SETPGRP();
#else /* TIOCNOTTY */
	t = open ("/dev/tty", O_RDWR, 0);
	if (t >= 0) {
	    ioctl (t, TIOCNOTTY, 0);
	    (void) close (t);
	}
#endif /* !TIOCNOTTY */

	if (getuid() || geteuid()) {
	    xlog (XLOG_FATAL,
		    "This program must be run as root to execute properly");
	    exit(1);
	}

#ifndef NO_PRIORITY
	/*
	 * Renice the main server so it can respond to calls even if four
	 * of its jobs are running...
	 */
#ifndef PRIO_PROCESS
	/*
	 * XXX: We used to check nice(-2) != -2 here, but SunOS 5.3 seems to
	 * always return 0 contrary to what the man page sez.
	 */
	if (nice(0) == 0 && nice(-2) == -1) {
	    perror("nice");
	}
#else /* PRIO_PROCESS */
	if (setpriority(PRIO_PROCESS, 0, -2) < 0) {
	    perror("setpriority");
	}
#endif /* !PRIO_PROCESS */
#endif /* NO_PRIORITY */
    }
    
    if (gethostname (localhost, MACHINE_NAME_SIZE)) {
	xlog (XLOG_FATAL, "The name of this machine is too long (%d chars max)",
		  MACHINE_NAME_SIZE);
	exit(1);
    }

    if (doLog) {
#ifdef LOG_FACILITY
	/*
	 * Detach from terminal but keep stdout open for compatibility
	 * with the remaining code.
	 */
	freopen ("/dev/null", "a", stdout);

	xlog_set("customs", XLOG_SYSLOG, LOG_FACILITY);
#else /* !LOG_FACILITY */
	char logName[256];

#ifdef LOG_BASE
	char *cp = strchr (localhost, '.');
	if (cp) {
	    *cp = '\0';
	}
	sprintf (logName, "%s.%s", LOG_BASE, localhost);
	if (cp) {
	    *cp = '.';
	}
#else /* !LOG_BASE */
	sprintf (logName, "%s", LOG_FILE);
#endif /* LOG_BASE */

	freopen (logName, "a", stdout);
	fcntl(fileno(stdout), F_SETFL, FAPPEND);

	xlog_set("customs", stdout);
#endif /* LOG_FACILITY */
    } else {
	xlog_set("customs", stdout);
    }

    /*
     * Handle some signals gracefully
     */
    SIGNAL(SIGUSR1, CustomsDebugOn);
    SIGNAL(SIGUSR2, CustomsDebugOff);

    SIGNAL(SIGHUP, CustomsInit);

    SIGNAL(SIGINT, CustomsDown);
    SIGNAL(SIGTERM, CustomsDown);

    SIGNAL(SIGQUIT, CustomsDown);
    SIGNAL(SIGBUS, CustomsDown);
    SIGNAL(SIGSEGV, CustomsDown);
    
    /*
     * Set up RPC sockets
     */
    for (i = 3; i > 0; i--) {
	sep = getservbyname(CUSTOMS_SERVICE_NAME, "udp");
	if (sep != NULL) {
	    sleep(2);
	} else {
	    break;
	}
    }
    if (sep == NULL) {
	xlog(XLOG_WARNING, "%s/udp (still) unknown", CUSTOMS_SERVICE_NAME);
	udpPort = DEF_CUSTOMS_UDP_PORT;
    } else {
	udpPort = ntohs(sep->s_port);
    }
tryUdpAgain:
    udpSocket = Rpc_UdpCreate(True, udpPort);
    if (udpSocket < 0) {
#ifdef WAIT_FOR_FREE_ADDRESS
	if (errno == EADDRINUSE) {
	    xlog(XLOG_WARNING, "%s/udp port in use, waiting for %d seconds",
		CUSTOMS_SERVICE_NAME, WAIT_FOR_FREE_ADDRESS);
	    sleep(WAIT_FOR_FREE_ADDRESS);
	    goto tryUdpAgain;
	}
#endif /* WAIT_FOR_FREE_ADDRESS */
	perror ("Rpc_UdpCreate");
	exit(1);
    }

    for (i = 3; i > 0; i--) {
	sep = getservbyname(CUSTOMS_SERVICE_NAME, "tcp");
	if (sep != NULL) {
	    sleep(2);
	} else {
	    break;
	}
    }
    if (sep == NULL) {
	xlog(XLOG_WARNING, "%s/tcp (still) unknown", CUSTOMS_SERVICE_NAME);
	tcpPort = DEF_CUSTOMS_TCP_PORT;
    } else {
	tcpPort = ntohs(sep->s_port);
    }
tryTcpAgain:
    tcpSocket = Rpc_TcpCreate(True, tcpPort);
    if (tcpSocket < 0) {
#ifdef WAIT_FOR_FREE_ADDRESS
	if (errno == EADDRINUSE) {
	    xlog(XLOG_WARNING, "%s/tcp port in use, waiting for %d seconds",
		CUSTOMS_SERVICE_NAME, WAIT_FOR_FREE_ADDRESS);
	    sleep(WAIT_FOR_FREE_ADDRESS);
	    goto tryTcpAgain;
	}
#endif /* WAIT_FOR_FREE_ADDRESS */
	perror("Rpc_TcpCreate");
	exit(1);
    }
    /*
     * Mark both service sockets as close on exec.
     */
    (void)fcntl (udpSocket, F_SETFD, 1);
    (void)fcntl (tcpSocket, F_SETFD, 1);

    /*
     * Reparse command line for -allow and -deny options
     * XXX: This is not really foolproof since arguments to other
     * options might be mistaken as -allow or -deny flags.
     */
    argc = initARGC - 1;
    argv = initARGV + 1;
    while (argc > 0) {
	if (strcmp (*argv, "-allow") == 0) {
	    unsigned int addr = inet_addr(argv[1]);

	    if ((int)addr == -1) {
		xlog (XLOG_ERROR, "malformed address: %s", argv[1]);
	    } else if (!Rpc_Allow(addr)) {
		xlog (XLOG_ERROR, "-allow %s failed", argv[1]);
	    }
	    argc--;
	    argv++;
	} else if (strcmp (*argv, "-deny") == 0) {
	    unsigned int addr = inet_addr(argv[1]);

	    if ((int)addr == -1) {
		xlog (XLOG_ERROR, "malformed address: %s", argv[1]);
	    } else if (!Rpc_Deny(addr)) {
		xlog (XLOG_ERROR, "-deny %s failed", argv[1]);
	    }
	    argc--;
	    argv++;
	}
	argc--; argv++;
    }

    /*
     * Register all the servers every agent must handle
     */
    Rpc_ServerCreate(udpSocket, (Rpc_Proc)CUSTOMS_PING, CustomsPing,
		     Rpc_SwapLong, Rpc_SwapNull, (Rpc_Opaque)0);
    Rpc_ServerCreate(udpSocket, (Rpc_Proc)CUSTOMS_HOST, CustomsHost,
		     Swap_Host, Swap_ExportPermit, (Rpc_Opaque)0);
    Rpc_ServerCreate(udpSocket, (Rpc_Proc)CUSTOMS_MASTER, CustomsMaster,
		     Rpc_SwapLong, Swap_SockAddr, (Rpc_Opaque)0);
    Rpc_ServerCreate(udpSocket, (Rpc_Proc)CUSTOMS_ABORT, CustomsAbort,
		     Rpc_SwapLong, Rpc_SwapNull, (Rpc_Opaque)0);
    Rpc_ServerCreate(tcpSocket, (Rpc_Proc)CUSTOMS_ABORT, CustomsAbort,
		     Rpc_SwapLong, Rpc_SwapNull, (Rpc_Opaque)0);
    Rpc_ServerCreate(udpSocket, (Rpc_Proc)CUSTOMS_RESTART, CustomsRestart,
		     Rpc_SwapLong, Rpc_SwapNull, (Rpc_Opaque)0);
    Rpc_ServerCreate(udpSocket, (Rpc_Proc)CUSTOMS_DEBUG, CustomsDebug,
		     Rpc_SwapLong, Rpc_SwapNull, (Rpc_Opaque)0);
    Rpc_ServerCreate(udpSocket, (Rpc_Proc)CUSTOMS_VERSION, CustomsVersion,
		     Rpc_SwapLong, Swap_Version, (Rpc_Opaque)0);
    Rpc_ServerCreate(udpSocket, (Rpc_Proc)CUSTOMS_ALLOW, CustomsAllow,
		     Rpc_SwapNull, Rpc_SwapNull, (Rpc_Opaque)0);
    Rpc_ServerCreate(udpSocket, (Rpc_Proc)CUSTOMS_DENY, CustomsDeny,
		     Rpc_SwapNull, Rpc_SwapNull, (Rpc_Opaque)0);
    
    /*
     * Close stdin and stderr so the descriptors are reused by the Avail
     * module. Note that if the Avail module changes to use fewer than two
     * descriptors, you should leave one of these things open so the Import
     * module doesn't have to worry about its socket already being in the
     * right place (dup2 (0, 0) would probably not be cool...)
     */
    fclose (stdin);
    fclose (stderr);

    /*
     * XXX: There should be some way to actually share these things so
     * all the perror-type functions would play with the same things.
     * unfortunately, stderr and stdout are macros, so we can only
     * get around things by making stdout be unbuffered...
     */
    setbuf (stdout, (char *)NULL);
    *stderr = *stdout;
	
    /*
     * Find the local address.
     */
    localAddr.sin_addr.s_addr = 0;
    if (!Rpc_IsLocal(&localAddr)) {
	xlog (XLOG_ERROR, "No local network address found");
	exit (1);
    }

    localAddr.sin_port = htons(udpPort);
    xlog (XLOG_INFO, "daemon started: local address = %d@%s",
	    ntohs(localAddr.sin_port),
	    InetNtoA(localAddr.sin_addr));

    /*
     * Find the server address, if any.
     */
    if (serverName) {
	struct hostent *server = gethostbyname(serverName);
	if (!server) {
	    xlog (XLOG_ERROR, "%s: unknown server host", serverName);
	} else {
	    serverAddr.sin_family = AF_INET;
	    serverAddr.sin_port = htons(udpPort);
	    serverAddr.sin_addr = *(struct in_addr *)server->h_addr;
	}
    } else {
	serverAddr.sin_addr.s_addr = 0;
    }
	
    MakeRegistrationPacket();

    Log_Init();
    Avail_Init(&criteria, checkTime);
    Import_Init();
    Elect_Init();
    Elect_GetMaster();

    initialized = TRUE;

    Rpc_Run();
}

/*
 * Replacement for generic handler.
 */
void
enomem ()
{
    static Boolean once = FALSE;

    xlog (XLOG_FATAL, "malloc: %s -- ABORTING", strerror(errno));

    /*
     * The first time we encounter this condition try to do an
     * orderly shutdown.
     */
    if (!once) {
	once = TRUE;
	CustomsAbort((struct sockaddr_in *)0, (Rpc_Message)0,
		     0, (Rpc_Opaque)TRUE);
    } else {
	exit (2);
    }
}
