/* This file is part of the Project Athena Zephyr Notification System.
 * It contains functions for communicating with the HostManager.
 *
 *	Created by:	John T. Kohl
 *
 *	$Source: /afs/sipb.mit.edu/project/sipb-athena/repository/src/zephyr/server/hostm.c,v $
 *	$Author: ghudson $
 *
 *	Copyright (c) 1987,1988,1991 by the Massachusetts Institute of Technology.
 *	For copying and distribution information, see the file
 *	"mit-copyright.h". 
 */

#include <zephyr/mit-copyright.h>

#ifndef lint
#ifndef SABER
static char rcsid_hostm_c[] = "$Id: hostm.c,v 1.43 1996/06/02 08:05:18 ghudson Exp $";
#endif
#endif

#include "zserver.h"
#include <sys/socket.h>			/* for AF_INET */

/*
 *
 * External functions:
 *
 * void hostm_dispatch(notice, auth, who, server)
 *	ZNotice_t *notice;
 *	int auth;
 *	struct sockaddr_in *who;
 *	ZServerDesc_t *server;
 *
 * void hostm_flush(host, server)
 *	ZHostList_t *host;
 *	ZServerDesc_t *server;
 *
 * void hostm_transfer(host, server)
 *	ZHostList_t *host;
 *	ZServerDesc_t *server;
 *
 * ZHostList_t *hostm_find_host(addr)
 *	struct in_addr *addr;
 *
 * ZServerDesc_t *hostm_find_server(addr)
 *	struct in_addr *addr;
 *
 * void hostm_shutdown()
 *
 * void hostm_losing(client, host)
 *	ZClient_t *client;
 *	ZHostList_t *host;
 *
 * void hostm_deathgram(sin, server)
 *	struct sockaddr_in *sin;
 * 	ZServerDesc_t *server;
 *
 * void hostm_dump_hosts(fp)
 *	FILE *fp;
 */

/*
 * This module maintains two important structures.
 * all_hosts is an array of all currently known hosts, and which server
 * is responsible for that host.  This list is kept sorted by IP address
 * so that lookups can be fast (binary search).  num_hosts contains the
 * number of hosts to be found in the array.
 *
 * The losing hosts list is a linked list of all the clients (and their hosts)
 * whose existence is in doubt.  Any host on this list has already been sent
 * a ping and is expected to reply immediately.
 * As usual, the first element of the list is a placeholder header so we
 * know when the list has been completely scanned.
 */

struct hostlist {
	ZHostList_t *host;		/* ptr to host struct */
	int server_index;		/* index of server in the table */
};

typedef struct _losinghost {
	struct _losinghost *q_forw;
	struct _losinghost *q_back;
	struct _ZHostList_t *lh_host;
	timer lh_timer;
	struct _ZClient_t *lh_client;
} losinghost;

#define	NULLHLT		((struct hostlist *) 0)

static struct hostlist *all_hosts;

static int num_hosts = 0;		/* number of hosts in all_hosts */
static long lose_timo = LOSE_TIMO;

static losinghost *losing_hosts; /* queue of pings for hosts we
					     doubt are really there */

#ifdef __STDC__
# define        P(s) s
#else
# define P(s) ()
#endif

static void host_detach P((register ZHostList_t *host, ZServerDesc_t *server)),
    insert_host P((ZHostList_t *host, ZServerDesc_t *server)),
    remove_host P((ZHostList_t *host));
static void host_not_losing P((struct sockaddr_in *who)),
    host_lost P((void *which)),
    ping P((struct sockaddr_in *sin));
static Code_t host_attach P((struct sockaddr_in *who, ZServerDesc_t *server));

#undef P

/*
 * We received a HostManager packet.  process accordingly.
 */

/*ARGSUSED*/
Code_t
hostm_dispatch(notice, auth, who, server)
     ZNotice_t *notice;
     int auth;
     struct sockaddr_in *who;
     ZServerDesc_t *server;
{
	ZServerDesc_t *owner;
	ZHostList_t *host = NULLZHLT;
	char *opcode = notice->z_opcode;
	Code_t retval;

#if 0
	zdbug((LOG_DEBUG,"hm_disp"));
#endif

	host = hostm_find_host(&who->sin_addr);
	if (host && host->zh_locked)
		return(ZSRV_REQUEUE);

	if (notice->z_kind == HMACK) {
		host_not_losing(who);
		return(ZERR_NONE);
	} else if (notice->z_kind != HMCTL) {
#if 0
		zdbug((LOG_DEBUG, "bogus HM packet"));
#endif
		clt_ack(notice, who, AUTH_FAILED);
		return(ZERR_NONE);
	}
	owner = hostm_find_server(&who->sin_addr);
	if (!strcmp(opcode, HM_ATTACH)) {
#if 0
		zdbug((LOG_DEBUG,"attach %s",inet_ntoa(who)));
#endif
		if (owner == server) {
#if 0
			zdbug((LOG_DEBUG,"no change"));
#endif
			/* Same server owns him.  do nothing */
		} else if (owner) {
			/* He has switched servers.
			   he was lost but has asked server to work for him.
			   We need to transfer him to server */
#if 0
			zdbug((LOG_DEBUG,"hm_disp transfer"));
#endif
			hostm_transfer(host, server);
		} else {
			/* no owner.  attach him to server. */
			if ((retval = host_attach(who, server))
			    != ZERR_NONE) {
				syslog(LOG_WARNING, "hattach failed: %s",
				       error_message(retval));
				return(retval);
			}

		}
		if (server == me_server) {
			server_forward(notice, auth, who);
			ack(notice, who);
		}
	} else if (!strcmp(opcode, HM_BOOT)) {
#if 0
		zdbug((LOG_DEBUG, "boot %s (server %s)",
		       inet_ntoa(who->sin_addr),
		       server->addr));
#endif
		/* Booting is just like flushing and attaching */
		if (owner)		/* if owned, flush */
			hostm_flush(host, owner);
		if ((retval = host_attach(who, server)) != ZERR_NONE) {
			syslog(LOG_WARNING, "hattach failed: %s",
			       error_message(retval));
			return(retval);
		}
		if (server == me_server) {
			server_forward(notice, auth, who);
			ack(notice, who);
		}
	} else if (!strcmp(opcode, HM_FLUSH)) {
#if 0
	        zdbug((LOG_DEBUG, "hm_flush %s (server %s)",
		       inet_ntoa(who->sin_addr),
		       server->addr));
#endif
		if (!owner)
			return(ZERR_NONE);
		/* flush him */
		hostm_flush(host, owner);
		if (server == me_server)
			server_forward(notice, auth, who);
	} else if (!strcmp(opcode, HM_DETACH)) {
#if 0
		zdbug((LOG_DEBUG, "hm_detach %s",inet_ntoa(who_sin_addr)));
#endif
		/* ignore it */
	} else {
		syslog(LOG_WARNING, "hm_disp: unknown opcode %s",opcode);
		return(ZERR_NONE);
	}
	return(ZERR_NONE);
}

/*
 * Flush all information about this host.  Remove any losing host entries,
 * deregister all the clients, flush any user locations, and remove the host
 * from its server.
 * The caller is responsible for informing other servers of this flush
 * (if appropriate).
 */

void
hostm_flush(host, server)
     ZHostList_t *host;
     ZServerDesc_t *server;
{
	register ZClientList_t *clist = NULLZCLT, *clt;
	losinghost *lhp, *lhp2;

	START_CRITICAL_CODE;

	if (!host) {
	    syslog(LOG_WARNING, "null host flush");
	    return;
	}

#if 0
	zdbug ((LOG_DEBUG,"hostm_flush %s", inet_ntoa (host->zh_addr.sin_addr)));
#endif

	if (losing_hosts)
		for (lhp = losing_hosts->q_forw;
		     lhp != losing_hosts;)
			if (lhp->lh_host == host) {
				lhp2 = lhp->q_back;
				timer_reset(lhp->lh_timer);
				xremque(lhp);
				xfree(lhp);
				lhp = lhp2->q_forw;
			} else
				lhp = lhp->q_forw;

	if ((clist = host->zh_clients) != NULLZCLT) {
	  for (clt = clist->q_forw; clt != clist; clt = clist->q_forw) {
	    /* client_deregister frees this client & subscriptions
	       & locations and remque()s the client */
#if 0
	    if (zdebug)
	      syslog (LOG_DEBUG, "hostm_flush clt_dereg %s/%d",
		      inet_ntoa(host->zh_addr.sin_addr),
		      ntohs (clt->zclt_client->zct_sin.sin_port));
#endif
	    client_deregister(clt->zclt_client, host, 1);
	  }
	}

	uloc_hflush(&host->zh_addr.sin_addr);
	host_detach(host, server);

	END_CRITICAL_CODE;

	return;
}

/*
 * send a shutdown to each of our hosts
 */

void
hostm_shutdown()
{
    register ZHostList_t *hosts = otherservers[me_server_idx].zs_hosts;
    register ZHostList_t *host;
    int newserver, i;

#if 0
    zdbug((LOG_DEBUG,"hostm_shutdown"));
#endif
    if (!hosts)
	return;

    for (i = 0; i < nservers; i++){
	if (i == me_server_idx) continue;
	if (otherservers[i].zs_state == SERV_UP)
	    break;
    }
    if (i == nservers)				/* no other servers are up */
	newserver = 0;
    else
	newserver = 1;

    /* kill them all */
    for (host = hosts->q_forw;
	 host != hosts;
	 host = host->q_forw) {
	/* recommend a random, known up server */
	if (newserver) {
	    do
		newserver = (int) (random() % (nservers - 1)) + 1;
	    while (newserver == limbo_server_idx() ||
		   (otherservers[newserver].zs_state != SERV_UP &&
		    otherservers[newserver].zs_state != SERV_TARDY) ||
		   newserver == me_server_idx);
	    hostm_deathgram(&host->zh_addr, &otherservers[newserver]);
	} else
	    hostm_deathgram(&host->zh_addr, NULLZSDT);
    }
    return;
}


/*
 * The client on the host is not acknowledging any packets.  Ping the
 * host and set a timeout.
 */

void
hostm_losing(client, host)
     ZClient_t *client;
     ZHostList_t *host;
{
	losinghost *newhost;

#if 0
	zdbug((LOG_DEBUG,"losing host"));
#endif
	if (!losing_hosts) {
		if (!(losing_hosts = (losinghost *)
		      xmalloc(sizeof(losinghost)))) {
			syslog(LOG_ERR, "no mem losing host");
			return;
		}
		losing_hosts->q_forw = losing_hosts->q_back = losing_hosts;
	}
	for (newhost = losing_hosts->q_forw;
	     newhost != losing_hosts;
	     newhost = newhost->q_forw)
		if (newhost->lh_client == client) {
#if 0
			zdbug((LOG_DEBUG,"clt already losing"));
#endif
			return;
		}
	if (!(newhost = (losinghost *) xmalloc(sizeof(losinghost)))) {
		syslog(LOG_ERR, "no mem losing host 2");
		return;
	}

	/* send a ping */
	ping(&host->zh_addr);
	newhost->lh_host = host;
	newhost->lh_client = client;
	newhost->lh_timer = timer_set_rel(lose_timo, host_lost, (void *) newhost);
	xinsque(newhost, losing_hosts);
	return;
}

/*
 * The host did not respond to the ping, so we punt him
 */

static void
host_lost(arg)
     void* arg;
{
	losinghost *which = (losinghost *) arg;
	ZServerDesc_t *server;
	ZNotice_t notice;
	struct sockaddr_in who;
	Code_t retval;
	char *buffer;
	int len;

	START_CRITICAL_CODE;
	
	server = hostm_find_server(&which->lh_host->zh_addr.sin_addr);
#if 1
	zdbug ((LOG_DEBUG,"lost host %s (server %s)",
		inet_ntoa(which->lh_host->zh_addr.sin_addr),
		server ? server->addr : "<NONE>"));
#endif

	if (!server) {
#if 1
		zdbug((LOG_DEBUG,"no server"));
#endif
		xremque(which);
		xfree(which);
		END_CRITICAL_CODE;
		return;
	}
	xremque(which);
	hostm_flush(which->lh_host, server);

	(void) memset((caddr_t)&notice, 0, sizeof(notice));
	
	/* tell other servers to flush this host */
	notice.z_kind = HMCTL;
	notice.z_auth = 0;
	notice.z_port = hm_port;
	notice.z_class = ZEPHYR_CTL_CLASS;
	notice.z_class_inst = ZEPHYR_CTL_HM;
	notice.z_opcode = HM_FLUSH;
	notice.z_sender = "HM";
	notice.z_recipient = "";
	notice.z_default_format = "";
	notice.z_num_other_fields = 0;
	notice.z_message_len = 0;

	/* generate the other fields */
	retval = ZFormatNotice(&notice, &buffer, &len, ZNOAUTH);
	if (retval != ZERR_NONE)
	    return;
	xfree(buffer);

	/* forge a from address */
	(void) memset((char *) &who, 0, sizeof(who));
	who.sin_addr.s_addr = which->lh_host->zh_addr.sin_addr.s_addr;
	who.sin_port = hm_port;
	who.sin_family = AF_INET;

	server_forward(&notice, 0, &who); /* unauthentic */

	xfree(which);

	END_CRITICAL_CODE;

	return;
}

/*
 * The host responded to the ping, so we flush the losing clients on this host.
 */

static void
host_not_losing(who)
     struct sockaddr_in *who;
{
	losinghost *lhp, *lhp2;

	if (!losing_hosts)
		return;

	START_CRITICAL_CODE;
	
	for (lhp = losing_hosts->q_forw;
	     lhp != losing_hosts;)
		if (lhp->lh_host->zh_addr.sin_addr.s_addr == who->sin_addr.s_addr) {
			/* go back, since remque will change things */
			lhp2 = lhp->q_back;
			timer_reset(lhp->lh_timer);
#if 1
			if (zdebug || 1)
			    syslog (LOG_DEBUG,"lost client %s/%d",
				    inet_ntoa(lhp->lh_client->zct_sin.sin_addr),
				    ntohs(lhp->lh_client->zct_sin.sin_port));
#endif
			/* deregister all subscriptions, and flush locations
			   associated with the client. */
#if 0
			if (zdebug)
				syslog(LOG_DEBUG,"h_not_lose clt_dereg");
#endif
			server_kill_clt(lhp->lh_client);
			client_deregister(lhp->lh_client, lhp->lh_host, 1);
			xremque(lhp);
			xfree(lhp);
			/* now that the remque adjusted the linked list,
			   we go forward again */
			lhp = lhp2->q_forw;
		} else
			lhp = lhp->q_forw;

	END_CRITICAL_CODE;

	return;
}

/*
 * A client is being de-registered, so remove it from the losing_host list,
 * if it is there.
 */

void
hostm_lose_ignore(client)
     ZClient_t *client;
{
	losinghost *lhp, *lhp2;
	
	if (!losing_hosts)
		return;

	START_CRITICAL_CODE;
	
	for (lhp = losing_hosts->q_forw;
	     lhp != losing_hosts;)
		/* if client matches, remove it */
		if (lhp->lh_client == client) {
			/* go back, since remque will change things */
			lhp2 = lhp->q_back;
			timer_reset(lhp->lh_timer);
#if 0
			zdbug((LOG_DEBUG,"hm_l_ign client %s/%d",
			       inet_ntoa(client->zct_sin),
			       ntohs(client->zct_sin.sin_port)));
#endif
			xremque(lhp);
			xfree(lhp);
			/* now that the remque adjusted the linked list,
			   we go forward again */
			lhp = lhp2->q_forw;
		} else
			lhp = lhp->q_forw;

	END_CRITICAL_CODE;
	
	return;
}

/*
 * transfer this host to server's ownership.  The caller must update the
 * other servers.
 */

void
hostm_transfer(host, server)
     ZHostList_t *host;
     ZServerDesc_t *server;
{
	/* we need to unlink and relink him, and change the table entry */
#if 1
	if (zdebug)
	    syslog (LOG_DEBUG, "hostm_transfer %s to %s",
		    inet_ntoa (host->zh_addr.sin_addr), server->addr);
#endif

	/* is this the same server? */
	if (hostm_find_server(&host->zh_addr.sin_addr) == server)
		return;

	START_CRITICAL_CODE;
	
	/* remove from old server's queue */
	xremque(host);

	/* switch servers in the table */
	remove_host(host);
	insert_host(host, server);

	/* insert in our queue */
	xinsque(host, server->zs_hosts);

	END_CRITICAL_CODE;

	return;
}


/*
 * attach the host with return address in who to the server.
 */

static Code_t
host_attach(who, server)
     struct sockaddr_in *who;
     ZServerDesc_t *server;
{
	register ZHostList_t *hlist;
	register ZClientList_t *clist;

	START_CRITICAL_CODE;

#if 0
	if (zdebug)
	    syslog (LOG_DEBUG, "host_attach %s to %s",
		    inet_ntoa (who->sin_addr), server->addr);
#endif
	/* allocate a header */
	if (!(hlist = (ZHostList_t *) xmalloc(sizeof(ZHostList_t)))) {
		syslog(LOG_WARNING, "hm_attach alloc");
		END_CRITICAL_CODE;
		return(ENOMEM);
	}
	/* set up */
	if (!(clist = (ZClientList_t *)xmalloc(sizeof(ZClientList_t)))) {
	        xfree(hlist);
		END_CRITICAL_CODE;
		return(ENOMEM);
	}
	clist->zclt_client = NULLZCNT;
	clist->q_forw = clist->q_back = clist;

	hlist->zh_clients = clist;
	hlist->zh_addr = *who;
	hlist->q_forw = hlist->q_back = hlist;
	hlist->zh_locked = 0;

	/* add to table */
	insert_host(hlist, server);

	/* chain in to the end of the list */
	xinsque(hlist, server->zs_hosts->q_back);

	END_CRITICAL_CODE;

	return(ZERR_NONE);
}

/*
 * detach the host at addr from the server
 * Warning: this routine assumes all the clients have already been removed
 * from this host.
 */

static void
host_detach(host, server)
     register ZHostList_t *host;
     ZServerDesc_t *server;
{
	ZServerDesc_t *server2;

	START_CRITICAL_CODE;

	/* undo what we did in host_attach */
	server2 = hostm_find_server (&host->zh_addr.sin_addr);

	if (server2 != server) {
		syslog(LOG_WARNING,
		       "host_detach: wrong server: %s from %s, found %s",
		       inet_ntoa (host->zh_addr.sin_addr),
		       server->addr,
		       server2->addr);
		END_CRITICAL_CODE;
		return;
	}


	/* all the clients have already been freed */
	xfree(host->zh_clients);

	/* unchain */
	xremque(host);

	/* remove from table */
	remove_host(host);

	xfree(host);

	END_CRITICAL_CODE;
	
	return;
}

/*
 * Build hostmanager recipient name.
 */
static char *
hm_recipient ()
{
    static char *recipient;
    char *realm;

    if (recipient)
	return recipient;

    realm = ZGetRealm ();
    if (!realm)
	realm = "???";
    recipient = (char *) xmalloc (strlen (realm) + 4);
    strcpy (recipient, "hm@");
    strcat (recipient, realm);
    return recipient;
}

/*
 * Send a shutdown message to the HostManager at sin, recommending him to
 * use server
 */

void
hostm_deathgram(sin, server)
     struct sockaddr_in *sin;
     ZServerDesc_t *server;
{
	Code_t retval;
	int shutlen;
	ZNotice_t shutnotice;
	char *shutpack;

#if 0
	zdbug((LOG_DEBUG,"deathgram %s",inet_ntoa(*sin)));
#endif

	/* fill in the shutdown notice */

	shutnotice.z_kind = HMCTL;
	shutnotice.z_port = sock_sin.sin_port; /* we are sending it */
	shutnotice.z_class = HM_CTL_CLASS;
	shutnotice.z_class_inst = HM_CTL_SERVER;
	shutnotice.z_opcode = SERVER_SHUTDOWN;
	shutnotice.z_sender = HM_CTL_SERVER;
	shutnotice.z_recipient = hm_recipient ();
	shutnotice.z_default_format = "";
	shutnotice.z_num_other_fields = 0;

	if (server) {
		shutnotice.z_message = server->addr;
		shutnotice.z_message_len = strlen(shutnotice.z_message) + 1;
#if 0
		zdbug((LOG_DEBUG, "suggesting %s",shutnotice.z_message));
#endif
	} else {
		shutnotice.z_message = NULL;
		shutnotice.z_message_len = 0;
	}

	if ((retval = ZFormatNotice(&shutnotice,
				    &shutpack,
				    &shutlen,
				    ZNOAUTH)) != ZERR_NONE) {
		syslog(LOG_ERR, "hm_shut format: %s",error_message(retval));
		return;
	}
	if ((retval = ZSetDestAddr(sin)) != ZERR_NONE) {
		syslog(LOG_WARNING, "hm_shut set addr: %s",
		       error_message(retval));
		xfree(shutpack);	/* free allocated storage */
		return;
	}
	/* don't wait for ack! */
	if ((retval = ZSendPacket(shutpack, shutlen, 0)) != ZERR_NONE) {
		syslog(LOG_WARNING, "hm_shut xmit: %s", error_message(retval));
		xfree(shutpack);	/* free allocated storage */
		return;
	}
	xfree(shutpack);		/* free allocated storage */
	return;
}

/*
 * Send a ping to the HostManager at sin
 */

static void
ping(sin)
     struct sockaddr_in *sin;
{
	Code_t retval;
	int shutlen;
	ZNotice_t shutnotice;
	char *shutpack;

#if 0
	zdbug((LOG_DEBUG,"ping %s",inet_ntoa(*sin)));
#endif

	/* fill in the shutdown notice */

	shutnotice.z_kind = HMCTL;
	shutnotice.z_port = sock_sin.sin_port;
	shutnotice.z_class = HM_CTL_CLASS;
	shutnotice.z_class_inst = HM_CTL_SERVER;
	shutnotice.z_opcode = SERVER_PING;
	shutnotice.z_sender = HM_CTL_SERVER;
	shutnotice.z_recipient = hm_recipient ();
	shutnotice.z_message = NULL;
	shutnotice.z_message_len = 0;
	shutnotice.z_default_format = "";
	shutnotice.z_num_other_fields = 0;

	if ((retval = ZFormatNotice(&shutnotice,
				    &shutpack,
				    &shutlen,
				    ZNOAUTH)) != ZERR_NONE) {
		syslog(LOG_ERR, "hm_ping format: %s",error_message(retval));
		return;
	}
	if ((retval = ZSetDestAddr(sin)) != ZERR_NONE) {
		syslog(LOG_WARNING, "hm_ping set addr: %s",
		       error_message(retval));
		xfree(shutpack);	/* free allocated storage */
		return;
	}
	/* don't wait for ack */
	if ((retval = ZSendPacket(shutpack, shutlen, 0)) != ZERR_NONE) {
		syslog(LOG_WARNING, "hm_ping xmit: %s", error_message(retval));
		xfree(shutpack);	/* free allocated storage */
		return;
	}
	xfree(shutpack);	/* free allocated storage */
	return;
}

/*
 * Routines for maintaining the host array.
 */

/*
 * Binary search on the host table to find this host.
 */

ZHostList_t *
hostm_find_host(addr)
     struct in_addr *addr;
{
	register int i, rlo, rhi;

	if (!all_hosts)
		return(NULLZHLT);

	/* i is the current host we are checking */
	/* rlo is the lowest we will still check, rhi is the highest we will
	   still check */

	i = num_hosts >> 1;		/* start in the middle */
	rlo = 0;
	rhi = num_hosts - 1;		/* first index is 0 */

	while ((all_hosts[i].host)->zh_addr.sin_addr.s_addr != addr->s_addr) {
		if ((all_hosts[i].host)->zh_addr.sin_addr.s_addr < addr->s_addr)
			rlo = i + 1;
		else
			rhi = i - 1;
		if (rhi - rlo < 0)
			return(NULLZHLT);
		i = (rhi + rlo) >> 1; /* split the diff */
	}
	return(all_hosts[i].host);
}

/*
 * Binary search on the host table to find this host's server.
 */

ZServerDesc_t *
hostm_find_server(addr)
     struct in_addr *addr;
{
	register int i, rlo, rhi;

	if (!all_hosts)
		return(NULLZSDT);

	/* i is the current host we are checking */
	/* rlo is the lowest we will still check, rhi is the highest we will
	   still check */

	i = num_hosts >> 1;		/* start in the middle */
	rlo = 0;
	rhi = num_hosts - 1;		/* first index is 0 */

	while ((all_hosts[i].host)->zh_addr.sin_addr.s_addr != addr->s_addr) {
		if ((all_hosts[i].host)->zh_addr.sin_addr.s_addr < addr->s_addr)
			rlo = i + 1;
		else
			rhi = i - 1;
		if (rhi - rlo < 0)
			return(NULLZSDT);
		i = (rhi + rlo) >> 1; /* split the diff */
	}
	return(&otherservers[all_hosts[i].server_index]);
}

/*
 * Insert the host and server into the sorted array of hosts.
 */

static void
insert_host(host, server)
     ZHostList_t *host;
     ZServerDesc_t *server;
{
	struct hostlist *oldlist;
	register int i = 0;

#if 0
	zdbug ((LOG_DEBUG,"insert_host %s %s",
		inet_ntoa(host->zh_addr.sin_addr), server->addr));
#endif
	if (hostm_find_host(&host->zh_addr.sin_addr))
		return;

	START_CRITICAL_CODE;

	num_hosts++;
	oldlist = all_hosts;

	if (!(all_hosts = (struct hostlist *) xmalloc(num_hosts * sizeof(struct hostlist)))) {
		syslog(LOG_CRIT, "insert_host: nomem");
		abort();
	}

	if (!oldlist) {			/* this is the first */
		all_hosts[0].host = host;
		all_hosts[0].server_index = server - otherservers;
		END_CRITICAL_CODE;
		return;
	}

	/* copy old pointers */
	while ((i < (num_hosts - 1)) &&
	       ((oldlist[i].host)->zh_addr.sin_addr.s_addr < host->zh_addr.sin_addr.s_addr)) {
		all_hosts[i] = oldlist[i];
		i++;
	}
	/* add this one */
	all_hosts[i].host = host;
	all_hosts[i++].server_index = server - otherservers;

	/* copy the rest */
	while (i < num_hosts) {
		all_hosts[i] = oldlist[i - 1];
		i++;
	}
	xfree(oldlist);

	END_CRITICAL_CODE;

#if defined (DEBUG) && 0
        if (zdebug) {
                register int i = 0;
		for (i = 0; i < num_hosts; i++)
		    syslog(LOG_DEBUG, "%d: %s %s",i,
			   inet_ntoa ((all_hosts[i].host)->zh_addr.sin_addr),
			   otherservers[all_hosts[i].server_index]->addr);
        }
#endif
	return;
}

/*
 * remove the host from the array of known hosts.
 */

static void
remove_host(host)
     ZHostList_t *host;
{
	struct hostlist *oldlist;
	register int i = 0;

#if 0
	zdbug((LOG_DEBUG,"remove_host %s", inet_ntoa(host->zh_addr.sin_addr)));
#endif
	if (!hostm_find_host(&host->zh_addr.sin_addr))
		return;

	START_CRITICAL_CODE;
	
	if (--num_hosts == 0) {
#if 0
		zdbug((LOG_DEBUG,"last host"));
#endif
		xfree (all_hosts);
		all_hosts = NULLHLT;
		END_CRITICAL_CODE;
		return;
	}

	oldlist = all_hosts;

	if (!(all_hosts = (struct hostlist *) xmalloc(num_hosts * sizeof(struct hostlist)))) {
		syslog(LOG_CRIT, "remove_host: nomem");
		abort();
	}

	/* copy old pointers */
	while (i < num_hosts && (oldlist[i].host)->zh_addr.sin_addr.s_addr < host->zh_addr.sin_addr.s_addr) {
		all_hosts[i] = oldlist[i];
		i++;
	}

	i++;				/* skip over this one */

	/* copy the rest */
	while (i <= num_hosts) {
		all_hosts[i - 1] = oldlist[i];
		i++;
	}
	xfree (oldlist);

	END_CRITICAL_CODE;

	return;
}

/*
 * Assumes that SIGFPE is blocked when called; this is true if called from a
 * signal handler
 */

void
hostm_dump_hosts(fp)
     FILE *fp;
{
	register int i;
	for (i = 0; i < num_hosts; i++) {
		(void) fprintf(fp, "%s/%d:\n", 
			       inet_ntoa((all_hosts[i].host)->zh_addr.sin_addr),
			       all_hosts[i].server_index);
		client_dump_clients(fp,(all_hosts[i].host)->zh_clients);
	}
	return;
}

/*
 * Readjust server-array indices according to the supplied new vector.
 */

void
hostm_renumber_servers (srv)
     int *srv;
{
    int i;
    for (i = 0; i < num_hosts; i++) {
	int idx = srv[all_hosts[i].server_index];
	if (idx < 0) {
	    syslog (LOG_ERR, "hostm_renumber_servers error: [%d] = %d",
		    all_hosts[i].server_index, idx);
	    idx = 0;
	}
	all_hosts[i].server_index = idx;
    }
}
