/*-
 * mca.c --
 *	Functions to act as the Master Customs Agent.
 *
 * 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: mca.c,v 1.42 2001/05/08 00:02:42 stolcke Exp $ ICSI (Berkeley)";
#endif /* not lint */

#include    <netdb.h>
#include    <string.h>

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

/*
 * Avail flags used only internally by MCA.
 * Make sure there is no conflict with the public ones in customs.h.
 */
#define AVAIL_ALLOC	0x00010000	/* CUSTOMS_ALLOC in progress */

/*
 * Mask out the avail bits that don't actually mean the machine is 
 * unavailable.
 */
#define AVAIL(avail)	((avail) & ~AVAIL_TOLOCAL)

typedef struct {
    char    	  	*name;    	/* The name of the host */
    Lst	    	  	clients;  	/* A list of machines it will serve */
    Lst	    	  	attributes;  	/* A list of attribute strings */
    Rpc_Long 	  	avail;    	/* 0 if the machine is available */
    Rpc_Long    	rating;	    	/* Availability index (high => more
					 * available. If not available, records
					 * violated criteron value.  If down,
					 * time since last up. If time out-of-
					 * sync, the clock differential. */
    Rpc_Long 	  	nJobs;    	/* Number of imported jobs */
    struct in_addr	addr;	    	/* Address of the server. */
    Rpc_ULong 		arch;	    	/* Architecture code */
    Rpc_Event  	  	downEvent;  	/* If this event ever gets taken,
					 * the host is down... */
} Server, *ServerPtr;

static Lst  	    allHosts; 	    /* All hosts we know of */
static ServerPtr    lastAlloc;	    /* Last server allocated */
static ServerPtr    us;	    	    /* Our record */
static Rpc_Event    boastEvent;	    /* Event that causes us to boast of our
				     * mastery at random intervals */

static int MCACmpServerName();
static void MCAFreeServer();

/*-
 *-----------------------------------------------------------------------
 * MCACmpAddr --
 *	Compare the address of a Server record to the desired address.
 *
 * Results:
 *	0 or non-0 depending on match or non-match, resp.
 *
 * Side Effects:
 *
 *-----------------------------------------------------------------------
 */
static int
MCACmpAddr (servPtr, addrPtr)
    ServerPtr	  	servPtr;
    struct in_addr	*addrPtr;
{
    return (servPtr->addr.s_addr - addrPtr->s_addr);
}

/*-
 *-----------------------------------------------------------------------
 * MCAFindHostAddr --
 *	Find a host in the list of allHosts using its address as a key.
 *	Create it if it isn't there and create is TRUE.
 *
 * Results:
 *	The ServerPtr for the host.
 *
 * Side Effects:
 *	A Server structure may be allocated and filled in.
 *
 *-----------------------------------------------------------------------
 */
static ServerPtr
MCAFindHostAddr (addr, name, create)
    struct sockaddr_in	*addr;	    /* Address of host */
    char    	  	*name;	    /* Optional name (must exist if create is
				     * TRUE) */
    Boolean 	  	create;	    /* True if should create a record if we
				     * cannot find one */
{
    LstNode 	  	ln;
    ServerPtr	  	servPtr;

    if (addr->sin_addr.s_addr == htonl(INADDR_LOOPBACK) && us != NULL) {
	/*
	 * 'localhost' address (127.1) means us.
	 */
	servPtr = us;
    } else {
	ln = Lst_Find (allHosts, &addr->sin_addr, MCACmpAddr);
	if (ln != NILLNODE) {
	    servPtr = (ServerPtr) Lst_Datum (ln);
	    /*
	     * Update the host name if is has changed
	     */
	    if (create &&
		(name != (char *)0) && 
		(strcmp(name, servPtr->name) != 0))
	    {
		free(servPtr->name);
		servPtr->name = (char *) emalloc ((unsigned)(strlen(name) + 1));
		strcpy (servPtr->name, name);
	    }
	} else if (create) {
	    servPtr = (ServerPtr) emalloc (sizeof (Server));
	    servPtr->avail = AVAIL_DOWN;
	    servPtr->rating = time((time_t *)0);
	    servPtr->nJobs = 0;
	    if (name == (char *)0) {
		name = Customs_Hostname(&addr->sin_addr);
	    }
	    servPtr->name = (char *) emalloc ((unsigned)(strlen(name) + 1));
	    strcpy (servPtr->name, name);
	    servPtr->clients = NILLST;
	    servPtr->attributes = NILLST;
	    servPtr->addr = addr->sin_addr;
	    servPtr->downEvent = (Rpc_Event)0;
	    servPtr->arch = 0;  /* Unknown architecture */

	    /*
	     * Look for, and replace, entries with same hostname.  This
	     * can happen either because a host changed it address,
	     * or because it sits on multiple networks.  We don't care,
	     * just record whatever is the "current" address that corresponds
	     * to its route to us.
	     */
	    ln = Lst_Find (allHosts, (ClientData)name, MCACmpServerName);
	    if (ln != NILLNODE) {
		ServerPtr oldServPtr = (ServerPtr)Lst_Datum(ln);

		/*
		 * If the other host by the same name is considered down
		 * assume this is it's new incarnation.  
		 * Otherwise fail, to avoid duplicate entries.
		 */
		if (oldServPtr->avail|AVAIL_DOWN) {
		    (void)Lst_Replace(ln, (ClientData)servPtr);
		    MCAFreeServer(oldServPtr);
		} else {
		    servPtr = (ServerPtr) NULL;
		}
	    } else {
		(void)Lst_AtEnd (allHosts, (ClientData)servPtr);
	    }
	} else {
	    servPtr = (ServerPtr) NULL;
	}
    }

    return servPtr;
}

/*-
 *-----------------------------------------------------------------------
 * MCACmpServerName --
 *	See if the given Server record has the required name. Callback
 *	procedure for MCAFindHost.
 *
 * Results:
 *	0 if the names match. non-zero otherwise.
 *
 * Side Effects:
 *	None.
 *
 *-----------------------------------------------------------------------
 */
static int
MCACmpServerName(servPtr, name)
    ServerPtr	  servPtr;
    char    	  *name;
{
    return (strcmp (servPtr->name, name));
}

/*-
 *-----------------------------------------------------------------------
 * MCAFindHost --
 *	Find a host in the list of allHosts and create it if it isn't
 *	there.
 *
 * Results:
 *	The ServerPtr for the host.
 *
 * Side Effects:
 *	A Server structure may be allocated and filled in.
 *
 *-----------------------------------------------------------------------
 */
static ServerPtr
MCAFindHost (name, create)
    char    	  	*name;
    Boolean 	  	create;
{
    LstNode 	  	ln;
    ServerPtr	  	servPtr;
    struct hostent 	*he;
    struct sockaddr_in 	them;

    ln = Lst_Find (allHosts, (ClientData)name, MCACmpServerName);
    if (ln != NILLNODE) {
	servPtr = (ServerPtr) Lst_Datum (ln);
    } else {
	he = gethostbyname(name);
	if (he == (struct hostent *)NULL) {
	    xlog (XLOG_ERROR, "Host %s unknown", name);
	    return (ServerPtr)NULL;
	}
	them.sin_family = AF_INET;
	them.sin_port = 0;
	them.sin_addr = *(struct in_addr *)he->h_addr;
	servPtr = MCAFindHostAddr(&them, name, create);
    }

    return servPtr;
}

/*-
 *-----------------------------------------------------------------------
 * MCAMakeClients --
 *	Build a list of clients structures from a list of hostnames.
 *
 * Results:
 *	The clients list.
 *
 * Side Effects:
 *	Hostnames are looked up by MCAFindHost.
 *
 *-----------------------------------------------------------------------
 */
static Lst
MCAMakeClients (lst)
    Lst	  	lst;
{
    Lst		    clients;
    LstNode	    ln;

    if (Lst_Length (lst) == 1 &&
	strcmp ((char *)Lst_Datum(Lst_First(lst)), "ALL") == 0)
    {
	/*
	 * If there's only one client given and it is ALL, then guess what?
	 * the server will accept connections from all hosts.
	 */
	return (NILLST);
    }

    if (Lst_Open (lst) == FAILURE) {
	return (NILLST);
    }
	
    clients = Lst_Init (TRUE);

    for (ln = Lst_Next (lst); !Lst_IsAtEnd (lst); ln = Lst_Next (lst)) {
	ServerPtr clntPtr;

	clntPtr = MCAFindHost ((char *)Lst_Datum (ln), TRUE);
	if (clntPtr != (ServerPtr)NULL) {
	    Lst_AtEnd (clients, (ClientData)clntPtr);
	}
    }
    return (clients);
}

/*-
 *-----------------------------------------------------------------------
 * MCADown --
 *	The given server hasn't sent an availability packet in the
 *	required amount of time, so we mark it down...
 *
 * Results:
 *	FALSE.
 *
 * Side Effects:
 *	The given server is marked unavailable and its downEvent
 *	field is zeroed.  The current time is recorded so down time
 *	can be computed.
 *
 *-----------------------------------------------------------------------
 */
static Boolean
MCADown (servPtr)
    ServerPtr	  servPtr;
{
    servPtr->avail |= AVAIL_DOWN;
    servPtr->rating = time((time_t *)0);
    servPtr->nJobs = 0;
    Rpc_EventDelete(servPtr->downEvent);
    servPtr->downEvent = (Rpc_Event)0;
    return(FALSE);
}

/*-
 *-----------------------------------------------------------------------
 * MCAAvail --
 *	Register the availability of a host. We do not return errors
 *	to avoid complicating the Avail module.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	the avail field of the host is altered. An event to mark the host
 *	down is registered.
 *
 *-----------------------------------------------------------------------
 */
/*ARGSUSED*/
static void
MCAAvail (from, msg, len, avail)
    struct sockaddr_in	*from;
    Rpc_Message	  	msg;
    int	    	  	len;
    Avail	    	*avail;	    /* New availability */
{
    ServerPtr	  hostPtr;

    if (avail->addr.s_addr != from->sin_addr.s_addr) {
	/*
	 * This is legitimate if a host is on multiple networks and
	 * we are connected to it through one of its secondary interfaces.
	 */
	if (verbose) {
	    char availhost[16];
	    strcpy(availhost, InetNtoA(avail->addr));
	    xlog (XLOG_DEBUG, "MCAAvail: packet for %s from %s?!",
		    availhost, InetNtoA(from->sin_addr));
	}
    }

    hostPtr = MCAFindHostAddr(from, (char *)0, FALSE);

    if (hostPtr == (ServerPtr)NULL) {
	if (verbose) {
	    xlog (XLOG_DEBUG,
		    "MCAAvail: packet received from unregistered host %s?!",
		    InetNtoA(from->sin_addr));
	}
	/*
	 * Return an error to cause host to re-register with the
	 * right address
	 */
	Rpc_Error(msg, RPC_BADARGS);
	return;
    }

    if (len == sizeof(Avail)) {
	time_t clockdiff;
	struct timeval interval;

	if (avail->clock != 0) {
	    clockdiff = (time_t)avail->clock - time((time_t *)0);
	} else {
	    clockdiff = 0;
	}
	
	hostPtr->nJobs = avail->nJobs;

	/*
	 * Check clock difference between slave and master.  If it exceeds
	 * the RPC timeout, mark the slave as having an out-of-sync clock
	 * and avoid exporting to it.
	 */
	if (abs(clockdiff) > CUSTOMSINT_RETRY + 1) {
	    if (verbose) {
		xlog (XLOG_DEBUG, "MCAAvail: %s clock off by %ld secs",
			hostPtr->name,
			clockdiff);
	    }
	    /*
	     * Set the reason for unavailability to clock, but preserve
	     * the ALLOC and TOLOCAL flags.
	     */
	    hostPtr->avail = (hostPtr->avail & (AVAIL_ALLOC | AVAIL_TOLOCAL))
				| AVAIL_CLOCK;
	    hostPtr->rating = clockdiff;
	} else {
	    if (verbose) {
		xlog (XLOG_DEBUG, "MCAAvail: %s %s available (%d)",
			hostPtr->name,
			AVAIL(avail->avail) ? "not" : "is",
			avail->rating);
	    }
	    /*
	     * Record new availability, preserving the ALLOC flag
	     */
	    hostPtr->avail = (hostPtr->avail & AVAIL_ALLOC) | avail->avail;
	    hostPtr->rating = avail->rating;
	}

	interval.tv_sec = avail->interval.tv_sec;
	interval.tv_usec = avail->interval.tv_usec;

	if (hostPtr->downEvent) {
	    Rpc_EventReset(hostPtr->downEvent, &interval);
	} else {
	    hostPtr->downEvent = Rpc_EventCreate(&interval, MCADown,
						 (Rpc_Opaque)hostPtr);
	}
    } else {
	/*
	 * Must be an incompatible agent
	 */
	hostPtr->avail = AVAIL_VERSION;
    }

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

/*
 * Structure passed as the data for MCAAvailHost since we need to pass two
 * pieces of data and only have one ClientData for passing it.
 */
struct sb {
    ServerPtr	    clntPtr;
    int	    	    flags;
    Lst		    attributes;
    Lst		    denied;
    Rpc_Long	    rating; /* Rating of currently chosen server */
    ServerPtr	    server; /* Currently chosen server */
};
/*-
 *-----------------------------------------------------------------------
 * MCAAvailHost --
 *	Callback procedure for MCA_HostInt to find a server for a given
 *	client.
 *
 * Results:
 *	Always 1 (so we scan the entire list).
 *
 * Side Effects:
 *	The sb struct is updated with results of our search.
 *
 *-----------------------------------------------------------------------
 */
static int
MCAAvailHost (servPtr, data)
    ServerPtr	  servPtr;
    struct sb	  *data;
{
    int		rating;

    if (AVAIL(servPtr->avail) == 0) {
	rating = servPtr->rating;
    } else {
	rating = 0;
    }

    if (verbose) {
	xlog (XLOG_DEBUG,
		"MCAAvailHost: checking %s: avail = %d, arch = %d, rating = %d",
		servPtr->name, servPtr->avail,
		servPtr->arch, rating);
    }

    if ((rating >= data->rating) &&	    /* best rating so far */
        ((data->flags & EXPORT_FORCE) ||    /* user is forcing it ... */
	 (AVAIL(servPtr->avail) == 0)) &&   /* ... or host is available */
	!(servPtr->avail & AVAIL_DOWN) &&   /* ... but not down */
	!(servPtr->avail & AVAIL_ALLOC) &&  /* ... or being allocated */
	((servPtr->avail & AVAIL_TOLOCAL) ||/* host allows local jobs ... */
	 (servPtr != data->clntPtr)) &&	    /* ... or the client is not local */
	(!(data->flags & EXPORT_SAME) ||    /* arch need not be same */
	 (servPtr->arch == data->clntPtr->arch)) &&	/* ... or it is */
	(!(data->flags & EXPORT_68020) || (servPtr->arch <= 2)) && /*XXX*/
	((servPtr->clients == NILLST) ||    /* host accepts all comers */
	 (Lst_Member (servPtr->clients,	    /* ... or at least this one */
		      (ClientData)(data->clntPtr)) != NILLNODE)) &&
	Customs_CheckAttr (data->attributes, /* matching attributes */
			   servPtr->attributes) &&
	((data->denied == NILLST) ||	    /* has not denied us before */
	 (Lst_Member (data->denied, (ClientData)servPtr) == NILLNODE)))
    {
	data->rating = rating;
	data->server = servPtr;
    }

    return (1);
}

/*-
 *-----------------------------------------------------------------------
 * MCA_HostInt --
 *	Allocate a host for the given machine.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	An ExportPermit containing either the address of a machine,
 *	or INADDR_ANY in the case of failure, is sent to the requesting
 *	host and an allocation packet is sent to the server which
 *	was allocated...
 *
 *-----------------------------------------------------------------------
 */
void
MCA_HostInt (from, msg, len, data)
    struct sockaddr_in	*from;
    Rpc_Message	  	msg;
    int	    	  	len;
    Host_Data  	  	*data;	    /* UID and flags for client process */
{
    ServerPtr	  	clntPtr;    /* Client requesting Host */
    ServerPtr	  	servPtr;    /* Agent to serve client */
    ExportPermit	permit;	    /* Permit to send to client */
    AllocData  		allocData;  /* Data to send to server */
    int	    	  	uid;

    if (len < sizeof(Host_Data)) {
	Rpc_Error(msg, RPC_BADARGS);
	return;
    }
    uid = data->uid;
    
    clntPtr = MCAFindHostAddr (from, (char *)0, FALSE);

    if ((clntPtr == (ServerPtr) NULL) ||
	(ntohs(from->sin_port) != udpPort))
    {
	xlog (XLOG_WARNING, "Host allocation attempted from %d@%s",
		ntohs(from->sin_port), InetNtoA(from->sin_addr));

	permit.addr.s_addr = htonl(INADDR_ANY);
    } else {
	struct sb sb;

	if (verbose) {
	    xlog (XLOG_DEBUG,
		    "MCA_HostInt: client %s, UID %d, flags %x, arch = %d",
		    clntPtr->name, data->uid, data->flags, clntPtr->arch);
	}

	sb.clntPtr = clntPtr;
	sb.flags = data->flags;
	sb.attributes = Customs_ArgvToLst (data->attrs, (int *)NULL);
	sb.denied = NILLST;

	while(1) {
	    /*
	     * Search through the list of hosts for the one that the calling
	     * host may use with the best rating.  If successful, sb.server
	     * will point to the chosen host. We do this until we can actually
	     * contact the host we've allocated. If this takes a while,
	     * the requesting agent and the client will both hang.
	     */
	    sb.rating = 0;
	    sb.server = NULL;

	    /*
	     * MCAAvailHost just never "finds" the node for which we're
	     * "looking"
	     */
	    (void)Lst_Find (allHosts, (ClientData)&sb, MCAAvailHost);

	    if (sb.server == NULL) {
		if (verbose) {
		    xlog(XLOG_DEBUG, "MCA_HostInt: no host available");
		}
		permit.addr.s_addr = htonl(INADDR_ANY);
		permit.id = 0;
		break;
	    } else {
		struct sockaddr_in  victim; /* Address of victim */
		Rpc_Stat  	    stat;   /* Result of call */
		AllocReply  	    reply;
		
		servPtr = sb.server;
		if (AVAIL(servPtr->avail) != 0) {
		    xlog (XLOG_INFO,
			    "User %d on %s forcing export to %s",
			    uid, clntPtr->name, servPtr->name);
		} else if (verbose) {
		    xlog (XLOG_DEBUG,
			    "MCA_HostInt: %s given to %s for uid %d",
			    servPtr->name, clntPtr->name, uid);
		}
		if (servPtr == us) {
		    permit.addr.s_addr = CUSTOMS_FROMADDR;
		} else {
		    permit.addr = servPtr->addr;
		}
		/*
		 * Generate a random ID for this permit.  This provides
		 * extra security since it authenticates the client to
		 * the server later.
		 */
		permit.id = ((time((time_t *)0) + random()) & 0xffff) | 
								(uid << 16);

		/*
		 * Before we reply to the requesting server, we must tell the
		 * victim what a lucky machine it is...
		 * Pass the EXPORT_* flags from the client to help the
		 * victim decide whether to accept the challenge.
		 */
		if (clntPtr == us) {
		    allocData.permit.addr.s_addr = CUSTOMS_FROMADDR;
		} else {
		    allocData.permit.addr = clntPtr->addr;
		}
		allocData.permit.id = permit.id;
		allocData.flags = data->flags;

		victim.sin_family = AF_INET;
		victim.sin_port = htons(udpPort);
		victim.sin_addr = servPtr->addr;
		
		/*
		 * Prevent concurrent allocation while we're at it ...
		 */
		servPtr->avail |= AVAIL_ALLOC;
		stat = Rpc_Call(udpSocket, &victim, (Rpc_Proc)CUSTOMS_ALLOC,
				sizeof(allocData),
				(Rpc_Opaque)&allocData,
				sizeof(reply),
				(Rpc_Opaque)&reply,
				CUSTOMSINT_NRETRY, &retryTimeOut);
		if (stat == RPC_SUCCESS) {
		    servPtr->avail = reply.avail;
		    servPtr->rating = reply.rating;
		    lastAlloc = servPtr;
		    break;
		} else if (stat == RPC_TIMEDOUT) {
		    /*
		     * OOPS. Mark the host down and try again...
		     */
		    if (verbose) {
			xlog (XLOG_DEBUG,
				"MCA_HostInt: %s timed out -- looping",
				servPtr->name);
		    }
		    servPtr->avail = AVAIL_DOWN;
		    servPtr->rating = time((time_t *)0);
	    	    servPtr->nJobs = 0;
		} else {
		    /*
		     * Outright rejection.  Maybe due to access restrictions
		     * on the user doing the exporting.
		     */
		    if (verbose) {
			xlog (XLOG_DEBUG,
				"MCA_HostInt: %s refused allocation (%s) -- looping",
				servPtr->name, Rpc_ErrorMessage(stat));
		    }
		    servPtr->avail &= ~AVAIL_ALLOC;
		    /*
		     * To prevent endless loops we remember the ones that have
		     * rejected us and do not dare to try them again.
		     */
		    if (sb.denied == NILLST) {
			sb.denied = Lst_Init(FALSE);
		    }
		    (void)Lst_AtEnd(sb.denied, (ClientData)servPtr);
		}
	    }
	}

	Lst_Destroy (sb.attributes, free);
	Lst_Destroy (sb.denied, NOFREE);
    }

    Rpc_Return(msg, sizeof(permit), (Rpc_Opaque)&permit);
}

/*-
 *-----------------------------------------------------------------------
 * MCARegister --
 *	Register a machine as serving a set of hosts. The data buffer
 *	looks like this:
 *		<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:
 *	A record is created for the host, if one doesn't exist. Its
 *	list of clients is set to contain the records for the hosts it
 *	will serve, unless it will serve the single host ALL, in which
 *	case the clients list is set to NILLST to signal its availability
 *	to all hosts.
 *
 *-----------------------------------------------------------------------
 */
/*ARGSUSED*/
static void
MCARegister (from, msg, len, data)
    struct sockaddr_in	*from;
    Rpc_Message	  	msg;
    int	    	  	len;
    char    	  	*data;	    /* Registration buffer */
{
    ServerPtr	  servPtr;  	    /* Record for registering agent */
    Lst		  clntLst;	    /* List of client names */
    int		  clntLen;	    /* Length of client names */
    char	  *buf;
    
    /*
     * Make sure the call comes from another agent -- if not, ignore it.
     *
     * XXX: Should also allow limiting to a group of addresses or networks.
     */
    if (ntohs(from->sin_port) != udpPort) {
	xlog (XLOG_WARNING, "Illegal registration from %d@%s",
		ntohs(from->sin_port), InetNtoA(from->sin_addr));
	Rpc_Error(msg, RPC_BADARGS);
	return;
    }
    
    /*
     * Find and create a record for the agent, leaving it in servPtr
     * This may fail if the host claims a name that is already taken.
     */
    servPtr = MCAFindHostAddr(from, data, TRUE);
    if (servPtr == (ServerPtr) NULL) {
	Rpc_Error(msg, RPC_BADARGS);
	return;
    }
    
    Rpc_Return(msg, 0, (Rpc_Opaque)0);

    buf = data;
	
    /*
     * If the agent was registered before, nuke any previous list of clients
     * and attributes before creating a new one
     */
    Lst_Destroy (servPtr->clients, NOFREE);
    if (servPtr->attributes != attributes) {
	Lst_Destroy (servPtr->attributes, free);
    }

    Log_Send(LOG_NEWAGENT, 1, xdr_sockaddr_in, from);

    /*
     * The architecture code is stored on a 32-bit boundary, so put buf at the
     * next one beyond the machine name string (being sure to include at least
     * one null in our calculations).
     */
    buf += strlen(buf)+1;
    buf = Customs_Align(buf, char *);
    servPtr->arch = *(Rpc_Long *)buf;
    buf += sizeof(Rpc_Long);

    /*
     * Extract the list of clients, given in argv format
     */
    clntLst = Customs_ArgvToLst (buf, &clntLen);
    servPtr->clients = MCAMakeClients (clntLst);

    if (verbose) {
	if (servPtr->clients == NILLST) {
	    xlog (XLOG_DEBUG, "MCARegister: registering %s: ALL clients",
		    servPtr->name);
	} else {
	    xlog (XLOG_DEBUG, "MCARegister: registering %s: %d clients",
		    servPtr->name, Lst_Length(clntLst));
	}
    }

    Lst_Destroy (clntLst, free);
    buf += clntLen;

    /*
     * Extract the list of attributes, given in argv format
     */
    servPtr->attributes = Customs_ArgvToLst (buf, (int *)NULL);
}

/*-
 *-----------------------------------------------------------------------
 * MCAInfo --
 *	Provide allocation and registration information...
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	The info is sent as a reply.
 *
 *-----------------------------------------------------------------------
 */
/*ARGSUSED*/
static void
MCAInfo (from, msg, len, data)
    struct sockaddr_in	*from;
    Rpc_Message	  	msg;
    int	    	  	len;
    Rpc_Opaque 	  	data;
{
    LstNode 	     	ln;
    register ServerPtr 	servPtr;
    ServerPtr	  	clntPtr;
    char    	  	info[MAX_INFO_SIZE];
    register char 	*cp;
    time_t		now;

    time(&now);

    /*
     * The information about all the hosts is stored in the 'info' buffer
     * in the following format:
     *	<net number>   		integer
     *	<number-of-hosts>   	integer
     *	<host1>	  	    	{
     *	    	  	    	    name (rounded to next 32-bit boundary)
     *				    attributes (in argv format)
     *	    	  	    	    availability
     *	    	    	    	    availability index
     *				    architecture
     *	    	  	    	    number of clients (0 if ALL)
     *	    	  	    	    indices of clients (0..numClients)
     *	    	  	    	}
     *     .
     *     .
     *     .
     *	<host-n>  	    	ditto
     *	<last-allocated>    	integer (index of host last allocated)
     *
     * Due to the variable size of the host names and client lists, this
     * is not really amenable to a one-dimensional C structure, so...
     */
    cp = info;
    *(Rpc_Long *)cp = elect_Token;
    cp += sizeof(Rpc_Long);
    *(Rpc_Long *)cp = Lst_Length(allHosts);
    cp += sizeof(Rpc_Long);
    if (Lst_Open(allHosts) == FAILURE) {
	Rpc_Error(msg, RPC_SYSTEMERR);
	return;
    }
    for (ln=Lst_Next(allHosts); !Lst_IsAtEnd(allHosts); ln=Lst_Next(allHosts)){
	char *attr;
	int attrLen;

	/*
	 * First copy the name (the size of which is rounded up to the
	 * next 32-bit boundary)
	 */
	servPtr = (ServerPtr)Lst_Datum(ln);

	/*
	 * Convert attributes to argv string format
	 */
	attr = Customs_LstToArgv (servPtr->attributes, &attrLen);

	/*
	 * Check for buffer overflow
	 */
	if (sizeof(info) < cp - info
		+ strlen(servPtr->name) + sizeof(Rpc_Long) /* name + align */
		+ 3 * sizeof(Rpc_Long)	/* avail + rating + arch */
		+ sizeof(Rpc_Long)		/* numclients */
		+ (servPtr->clients == NILLST ? 0
			: sizeof(Rpc_Long) * Lst_Length(servPtr->clients))
					/* clients indices */
		+ attrLen)
	{
	    free (attr);
	    Lst_Close(allHosts);
	    xlog (XLOG_WARNING, "Reginfo exceeds max RPC data size");
	    Rpc_Error(msg, RPC_TOOBIG);
	    return;
	}

	strcpy(cp, servPtr->name);
	cp += strlen(servPtr->name) + 1;

	memcpy(cp, attr, attrLen);
	free (attr);
	cp += attrLen;

	cp = Customs_Align(cp, char *);
	
	*(Rpc_Long *)cp = AVAIL(servPtr->avail);
	cp += sizeof(Rpc_Long);
	if (servPtr->avail & AVAIL_DOWN) {
	    *(Rpc_Long *)cp = now - servPtr->rating;
	} else {
	    *(Rpc_Long *)cp = servPtr->rating;
	}
	cp += sizeof(Rpc_Long);
	
	*(Rpc_Long *)cp = servPtr->arch;
	cp += sizeof(Rpc_Long);

	if (servPtr->clients != NILLST) {
	    /*
	     * Stuff the number of clients served into the buffer and
	     * pass down the list, storing the index of each client in
	     * turn.
	     */
	    *(Rpc_Long *)cp = Lst_Length(servPtr->clients);
	    cp += sizeof(Rpc_Long);
	    if (Lst_Open(servPtr->clients) == FAILURE) {
		Rpc_Error(msg, RPC_SYSTEMERR);
		return;
	    }
	    for (ln = Lst_Next(servPtr->clients);
		 !Lst_IsAtEnd(servPtr->clients);
		 ln = Lst_Next(servPtr->clients)) {
		     clntPtr = (ServerPtr)Lst_Datum(ln);
		     *(Rpc_Long *)cp = Lst_Index(allHosts, (ClientData)clntPtr);
		     cp += sizeof(Rpc_Long);
	    }
	    Lst_Close(servPtr->clients);
	} else {
	    /*
	     * 0 clients served => all clients served
	     */
	    *(Rpc_Long *)cp = 0;
	    cp += sizeof(Rpc_Long);
	}
    }
    Lst_Close(allHosts);

    /*
     * Find last-allocated host and store its index
     */
    *(Rpc_Long *)cp = Lst_Index(allHosts, (ClientData)lastAlloc);
    cp += sizeof(Rpc_Long);
    Rpc_Return(msg, cp - info, (Rpc_Opaque)info);
}


/*-
 *-----------------------------------------------------------------------
 * MCANewMasterResponse --
 *	Handle a response to a NEWMASTER broadcast. Doesn't do anything
 *	except return True.
 *
 * Results:
 *	True.
 *
 * Side Effects:
 *	None.
 *
 *-----------------------------------------------------------------------
 */
/*ARGSUSED*/
static Boolean
MCANewMasterResponse(from, len, data)
    struct sockaddr_in	*from;
    int	    	  	len;
    Rpc_Opaque 	  	data;
{
    if (ntohs(from->sin_port) != udpPort) {
	/*
	 * Ignore response if not from offical port
	 */
	return(False);
    } else {
	return (True);
    }
}
/*-
 *-----------------------------------------------------------------------
 * MCABoast --
 *	Tell the world we're the master agent. The call is sent out at
 *	random times between five and ten minutes apart to deal with
 *	network partitions. Registration is a fairly lightweight
 *	operation, so five-to-ten minutes seems like a reasonable
 *	interval.
 *
 * Results:
 *	False (don't stay awake)
 *
 * Side Effects:
 *	The event we're given is reset for a random time between five and
 *	ten minutes away.
 *	
 *-----------------------------------------------------------------------
 */
/*ARGSUSED*/
static Boolean
MCABoast(data, ev)
    Rpc_Opaque	    data;   	/* Data stored (Nothing) */
    Rpc_Event	    ev;	    	/* Event that called us */
{
    struct timeval  again;  	/* Time at which to broadcast again */
    struct sockaddr_in broadcast;  /* Address to which to broadcast */

    broadcast.sin_family = AF_INET;
    broadcast.sin_port = htons(udpPort);
    broadcast.sin_addr.s_addr = htonl(INADDR_ANY);
    
    if (!amMaster) {
	/*
	 * XXX: This should never happen, but it is the only explanation
	 * for the recurring "registering: No such procedure" messages ...
	 */
	xlog (XLOG_ERROR, "MCABoast: boasting while not master");

	Rpc_EventDelete(ev);
	boastEvent = (Rpc_Event)NULL;

	return(False);
    }

    if (verbose) {
	xlog (XLOG_DEBUG, "MCABoast: affirming mastery");
    }

    /*
     * Let the world know we consider ourselves the master.
     */
    (void)Rpc_Broadcast(udpSocket, &broadcast,
			(Rpc_Proc)CUSTOMS_NEWMASTER,
			sizeof(elect_Token), (Rpc_Opaque)&elect_Token,
			0, (Rpc_Opaque)0,
			CUSTOMSINT_NRETRY, &retryTimeOut,
			MCANewMasterResponse, (Rpc_Opaque)0);

    if (boastEvent != NULL) {
	/*
	 * Pick a random time between five and ten minutes from now at which
	 * to broadcast again. Note the conditional on boastEvent still
	 * being non-null. This is to handle getting a NEWMASTER call ourselves
	 * during the broadcast.
	 */
	again.tv_sec = (random() % 300) + 300;
	again.tv_usec = (random() % 1000000);

	Rpc_EventReset(ev, &again);
    }

    return(False);
}
/*-
 *-----------------------------------------------------------------------
 * MCA_Init --
 *	Initialize things as the master agent.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	Loads.
 *
 *-----------------------------------------------------------------------
 */
void
MCA_Init ()
{
    struct timeval  junk;   /* Timeval needed for creating the boastEvent. */
    
    allHosts = Lst_Init (TRUE);

    us = MCAFindHost (localhost, TRUE);

    if (us == (ServerPtr)NULL) {
	xlog (XLOG_FATAL, "Failed to look up own hostname: %s", localhost);
	exit(1);
    }

    us->addr = localAddr.sin_addr;
    us->arch = arch;
    us->clients = MCAMakeClients (clients);
    us->attributes = attributes;

    lastAlloc = us;

    xlog (XLOG_INFO, "becoming master");

    amMaster = TRUE;

    /*
     * Register the services we as the master will perform.
     */
    Rpc_ServerCreate(udpSocket, (Rpc_Proc)CUSTOMS_AVAIL, MCAAvail,
		     Swap_AvailInt, Rpc_SwapNull, (Rpc_Opaque)TRUE);
    Rpc_ServerCreate(udpSocket, (Rpc_Proc)CUSTOMS_HOSTINT, MCA_HostInt,
		     Swap_Host, Swap_ExportPermit, (Rpc_Opaque)0);
    Rpc_ServerCreate(udpSocket, (Rpc_Proc)CUSTOMS_REG, MCARegister,
		     Swap_RegPacket, Rpc_SwapNull, (Rpc_Opaque)0);
    Rpc_ServerCreate(udpSocket, (Rpc_Proc)CUSTOMS_INFO, MCAInfo,
		     Rpc_SwapNull, Swap_Info, (Rpc_Opaque)0);
    /*
     * CUSTOMS_INFO is served on both UDP and TCP, since UDP may not be
     * able to handle the large return messages.
     */
    Rpc_ServerCreate(tcpSocket, (Rpc_Proc)CUSTOMS_INFO, MCAInfo,
		     Rpc_SwapNull, Swap_Info, (Rpc_Opaque)0);
    /*
     * Create an event for sending out a period NEWMASTER call (see below).
     * The time doesn't matter since MCABoast will reset the thing right
     * away.
     */
    junk.tv_sec = junk.tv_usec = 1;
    boastEvent = Rpc_EventCreate(&junk, MCABoast, (Rpc_Opaque)NULL);

    /*
     * Dispatch initial NEWMASTER call. The idea is to send out NEWMASTER
     * broadcasts at regular, though random, intervals to handle the
     * resolution of a network partition -- if you have two masters on
     * the net, when the first one sends out the NEWMASTER call, the second
     * will cancel its master automatically, while all the agents that
     * had the second agent listed as the master will register with the first.
     */
    MCABoast(NULL, boastEvent);
}

/*-
 *-----------------------------------------------------------------------
 * MCAFreeServer --
 *	Free a server description.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	The data for the given server is freed.
 *
 *-----------------------------------------------------------------------
 */
static void
MCAFreeServer(servPtr)
    ServerPtr	  servPtr;
{
    free(servPtr->name);
    if (servPtr->downEvent) {
	Rpc_EventDelete(servPtr->downEvent);
    }
    Lst_Destroy(servPtr->clients, NOFREE);
    if (servPtr->attributes != attributes) {
	Lst_Destroy(servPtr->attributes, free);
    }
    free((char *)servPtr);
}

/*-
 *-----------------------------------------------------------------------
 * MCA_Cancel --
 *	Stop acting as the MCA. Involves destroying our records and
 *	unregistering our master services.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	All the data on the allHosts list are freed.
 *
 *-----------------------------------------------------------------------
 */
void
MCA_Cancel()
{
    xlog (XLOG_INFO, "giving up mastery");

    amMaster = FALSE;
    us = (ServerPtr)NULL;

    Lst_Destroy(allHosts, MCAFreeServer);
    
    Rpc_ServerDelete(udpSocket, (Rpc_Proc)CUSTOMS_AVAIL);
    Rpc_ServerDelete(udpSocket, (Rpc_Proc)CUSTOMS_HOSTINT);
    Rpc_ServerDelete(udpSocket, (Rpc_Proc)CUSTOMS_REG);
    Rpc_ServerDelete(udpSocket, (Rpc_Proc)CUSTOMS_INFO);
    Rpc_ServerDelete(tcpSocket, (Rpc_Proc)CUSTOMS_INFO);

    Rpc_EventDelete(boastEvent);

    boastEvent = (Rpc_Event)NULL;
}
