NFSv2/usr/src/etc/ypserv/ypserv_ancil.c

Compare this file to the similar file:
Show the results in this format:

#ifndef lint
static	char sccsid[] = "@(#)ypserv_ancil.c 1.1 85/05/30 Copyr 1984 Sun Micro";
#endif

/*
 * This contains ancillary functions used by the yellow pages server process.
 */

#include "ypsym.h"
struct peer_list_item *set_random_alternate();

static struct dom_binding ping_peer;
static struct peer_list_item *pingee = (struct peer_list_item *) NULL;
static struct timeval pingintertry = {	/* udp secs between tries on ping */
	YPINTERTRY_TIME,		/* Seconds */
	0				/* uSecs */
};
static struct timeval pingtimeout = {	/* udp total timeout for ping */
	YPPING_TIME,			/* Seconds */
	0				/* uSecs */
};
static unsigned short int xfr_number = 0;


/*
 * This constructs a file name from a passed domain name, a passed map name,
 * and a globally known yellow pages data base path prefix.
 *
 * Note:  No pathname separators are assumed in either the map name or
 * the domain name.
 */
void
ypmkfilename(pdomain, pmap, ppath)
	char *pdomain;
	char *pmap;
	char *ppath;
{

	if ( (strlen(pdomain) + strlen(pmap) + strlen(ypdbpath) + 3) 
	    > (MAXNAMLEN + 1) ) {
		fprintf(stderr, "ypserv:  Map name string too long.\n");
	}

	strcpy(ppath, ypdbpath);
	strcat(ppath, "/");
	strcat(ppath, pdomain);
	strcat(ppath, "/");
	strcat(ppath, pmap);
}

/*
 * This sets up an rpc channel to a named server.  The
 * underlying transport mechanism (tcp or udp) is specified as an input.
 *	char *transport;		* Either "tcp" or "udp" (a null-
 *					*   terminated string)
 *	struct dom_binding *pdomb;	* Ptr to a domain binding.  The internet
 *					*   address should be filled out.
 *	struct timeval *ptimeval;	* The time to be used as an intertry
 *					*   time on udp connections.
 * This assumes that both the internet address and the port
 * have been set up in the sockaddr_in which is in the dom_binding.  It
 * is OK for the port to be 0 (this will generate an extra transaction at
 * the rpc level, asking the port mapper on the remote node for the port
 * of the remote yp server), but a garbage port or address value will
 * not work.  Also, since the dom_binding is a passed parameter, there
 * is no socket or client management done at this level.
 * 
 * Returns TRUE if client channel could be set up and the server is alive,
 * else FALSE.  All resources allocated by the call will be freed in the
 * failure case.
 */
bool
ypbind_to_named_server(transport, pdomb, ptimeval)
	char *transport;
	struct dom_binding *pdomb;
	struct timeval *ptimeval;
{
	enum clnt_stat clnt_stat;

	if (!transport ||
	    (strcmp(transport, "tcp") && strcmp(transport, "udp") ) ||
	    !pdomb || !ptimeval) {
		return(FALSE);
	}

	pdomb->dom_server_addr.sin_family = AF_INET;
	pdomb->dom_socket = RPC_ANYSOCK;

	if (!strcmp (transport, "tcp")) {

		if ((pdomb->dom_client =
		    clnttcp_create (&(pdomb->dom_server_addr), YPPROG, YPVERS,
	    	    &(pdomb->dom_socket), 1024, 1024)) == NULL) {
			return(FALSE);
		}
		
	} else {

		if ((pdomb->dom_client =
		    clntudp_create (&(pdomb->dom_server_addr), YPPROG, YPVERS,
		    *ptimeval, &(pdomb->dom_socket))) == NULL) {
			return(FALSE);
		}
	}

	pdomb->dom_server_port = pdomb->dom_server_addr.sin_port;

	if ( (clnt_stat =
	    (enum clnt_stat) clnt_call(pdomb->dom_client, YPPROC_NULL, xdr_void,
	    0, xdr_void, 0, pingtimeout) ) != RPC_SUCCESS) {
		clnt_destroy(pdomb->dom_client);
		close(pdomb->dom_socket);
		return (FALSE);
	}
	
	return (TRUE);			/* This is the go path */
}

/*
 * This returns for a name the integer index of that name within an array of
 * names.  If the passed name is not in the array, the function will return -1.
 * This is used both for checking to see if a name is on the list, and to use the
 * returned value as an index into an array parallel to the name array.  Returns
 * an index if match found, or -1 otherwise.
 * 
 * Note:  The list is assumed to be NULL-terminated.
 */
int
ypspecial_casep(name, list)
	char *name;
	char *list[];
{
	int index = 0;

	if (!name || (strlen(name) == 0) || !list) {
		return(-1);
	}
	
	while (*list) {
		
		if (!strcmp(name, *list)) {
			break;
		}

		index++;
		list++;
	}
		
	if (*list) {
		return(index);
	} else {
		return(-1);
	}
}

/*
 * This sends an "Are you alive" message to the yp server at some node, setting
 * the peer_port field at the same time.  It filters peers, and won't send the
 * message to itself, but will rather mark the "local peer" as reachable.  The
 * peer list item fields peer_port, peer_reachable, and peer_last_polled may be
 * set.
 */
void
ypping_peer(ppeer)
	struct peer_list_item *ppeer;
{
	enum clnt_stat clnt_stat;
	
	if (!ppeer) {
		return;
	}
		
	/*
	 * We are going to say that we are polling now, success or failure.
	 * We are going to assume failure.
	 */

	ppeer->peer_last_polled = tick_counter;
	ppeer->peer_reachable = FALSE;

	/*
	 * If the peer in question is me, mark me as reachable and return.
	 */

	if (!strcmp(ppeer->peer_pname, myhostname) ) {
		ppeer->peer_reachable = TRUE;
		return;
	}
	 
	/*
	 * If we are already bound to this peer, we'll try to use the
	 * binding.  If we use the binding, and are successful, we will
	 * mark the peer as reachable and return.  If the ping fails, we will
	 * clear the peer's port, in case he or his machine crashed, and
	 * his port has changed.
	 */
	 
	if (ppeer == pingee) {

		if ( (clnt_stat = (enum clnt_stat) clnt_call(
		    ping_peer.dom_client, YPPROC_NULL, xdr_void, 0, xdr_void,
		    0, pingtimeout) ) == RPC_SUCCESS) {
			ppeer->peer_reachable = TRUE;
			return;
		} else {
			ppeer->peer_port = htons( (unsigned short) YPNOPORT);
		}
	}

	/*
	 * Either we need to ping a server who was not the previous pingee, or
	 * our use of the old binding failed.  If we have a binding active
	 * we need to pull it down and free the associated resources.  
	 */

	if (pingee) {
		clnt_destroy(ping_peer.dom_client);
		close(ping_peer.dom_socket);
		pingee = (struct peer_list_item *) NULL;
	}

	/*
	 * Try to make a binding to the named peer.  (It's not me.)  If the
	 * binding succeeds, the server has already been successfully pinged
	 * by ypbind_to_named_server.  We will suck up the server's port, (in 
	 * case it was in its initial state) and remember the current binding.
	 */

	ping_peer.dom_server_addr.sin_addr.s_addr = ppeer->peer_addr.s_addr;
	ping_peer.dom_server_addr.sin_port = ppeer->peer_port;

	if (ypbind_to_named_server(peer_transport, &ping_peer,
	    &pingintertry) ) {
		pingee = ppeer;
		ppeer->peer_port = ping_peer.dom_server_port;
		ppeer->peer_reachable = TRUE;
	} else {
		ppeer->peer_port = htons( (unsigned short) YPNOPORT);
	}
}

/*
 * Runs though all known peers in all supported domains and pings them.  This
 * is called only at initialization time.  The peer_reachable and the
 * peer_last_polled fields on the peers may change state.  All work is done at
 * lower levels.
 */
void
ypping_all_peers()
{
	struct domain_list_item *pdom;
	struct peer_list_item *ppeer;

	for (pdom = yppoint_at_first_domain(TRUE);
	    pdom != (struct domain_list_item *) NULL;
	    pdom = yppoint_at_next_domain(pdom, TRUE) ) {

		for (ppeer = yppoint_at_peerlist(pdom);
		    ppeer != (struct peer_list_item *) NULL;
		    ppeer = ypnext_peer(ppeer) ) {
			ypping_peer(ppeer);
		}
	}
}


/*
 * This is a boolean function which attempts to find a "suitable" alternate
 * peer to serve a passed map.  It searches through the list of peers in the
 * domain and tries to return a peer which is:
 * 	1.  Not the map master
 * 	2.  Not myself
 * 	3.  Reachable
 *
 * The field pmap->map_alternate will be set to the alternate
 * peer if the functional value is TRUE, otherwise the field will be set to
 * NULL.  Field peer_reachable for the alternate will be set true by lower
 * levels.
 */
bool
ypfind_alternate(pmap)
	struct map_list_item *pmap;
{
	struct peer_list_item *ppeer;
	struct peer_list_item *peerlist;
	int peercount = 0;

	if (!pmap) {
		return(FALSE);
	}

	pmap->map_alternate = (struct peer_list_item *) NULL;
	
	/*
	 * Traverse the list of peers, counting and  marking as reachable
	 * all except myself and the map's master.
	 */

	for (peerlist = ppeer = yppoint_at_peerlist(pmap->map_domain);
	    ppeer != (struct peer_list_item *) NULL;
	    ppeer = ypnext_peer(ppeer) ) {

		if (ppeer == pmap->map_master) {
			ppeer->peer_reachable = FALSE;
		} else if (!strcmp(ppeer->peer_pname, myhostname) ) {
			ppeer->peer_reachable = FALSE;
		} else {
			peercount++;
			ppeer->peer_reachable = TRUE;
		}
	}

	if (peercount == 0) {
		return(FALSE);
	}
	
	ppeer = (struct peer_list_item *) NULL;

	/*
	 * Choose an assumed reachable peer "at random", and test to see if
	 * he really is reachable. If he is, use him as the alternate; if he's
	 * not, knock him out of the list and try till we've got a reachable
	 * peer, or we've tried them all.
	 */

	while ( (ppeer == (struct peer_list_item *) NULL) &&
	    (peercount > 0) ) {
		ppeer = set_random_alternate(peerlist, peercount);

		if (ppeer) {
			ypping_peer(ppeer);

			if (ppeer->peer_reachable) {
				pmap->map_alternate = ppeer;
			} else {
				peercount--;
				ppeer = (struct peer_list_item *) NULL;
			}
			
		} else {
			break;
		}
	}

	return(ppeer ? TRUE: FALSE);
}

/*
 * This chooses one of the peers which is marked as reachable from the passed
 * peerlist.  The peer chosen is "at random".
 */
 
static struct peer_list_item *
set_random_alternate(peerlist, peercount)
	struct peer_list_item *peerlist;
	int peercount;
	
{
	struct timeval tod;
	int index;
	int match = 0;
	struct peer_list_item *ppeer;

	if (!peerlist || (peercount == 0) ) {
		return( (struct peer_list_item *) NULL);
	}
	 
	/*
	 * Generate a quick pseudorandom number between 0 and peercount - 1, and
	 * use it as an index into the list of (assumed) reachable peers.
	 */

	gettimeofday(&tod, NULL);
	index = tod.tv_usec % peercount;

	for (ppeer = peerlist;
	    ppeer != (struct peer_list_item *) NULL;
	    ppeer = ypnext_peer(ppeer)) {

		if (ppeer->peer_reachable) {

			if (match == index) {
				return(ppeer);
			} else {
				match++;
			}
		}
	}

	return( (struct peer_list_item *) NULL);
}

/*
 * This returns a pointer to a temporary name for a map transfer.
 */
void
ypmk_tmpname(xfr_name)
	char *xfr_name;
{
	int len;
	char xfr_anumber[10];

	if (!xfr_name) {
		return;
	}
	
	strcpy(xfr_name, YPTEMPNAME_PREFIX);
	sprintf(xfr_anumber, "%d", xfr_number++);
	strcat(xfr_name, xfr_anumber);
}

/*
 * This checks to see if the source map files exist, then renames them to the
 * target names.  This is a boolean function.  The file names from.pag and
 * from.dir will be changed to to.pag and to.dir in the success case.
 *
 * Note:  If the second of the two renames fails, yprename_map will try to
 * un-rename the first pair, and leave the world in the state it was on entry.
 * This might fail, too, though...
 */
bool
yprename_map(from, to)
	char *from;
	char *to;
{
	char fromfile[MAXNAMLEN + 1];
	char tofile[MAXNAMLEN + 1];

	if (!from || !to) {
		return(FALSE);
	}
	
	if (!ypcheck_map_existence(from) ) {
		return(FALSE);
	}

	strcpy(fromfile, from);
	strcat(fromfile, ".pag");
	strcpy(tofile, to);
	strcat(tofile, ".pag");

	if (rename(fromfile, tofile) ) {
		fprintf(stderr, "ypserv:  yprename_map: can't mv %s to %s.\n",
		    fromfile, tofile);
		return(FALSE);
	}
	
	strcpy(fromfile, from);
	strcat(fromfile, ".dir");
	strcpy(tofile, to);
	strcat(tofile, ".dir");

	if (rename(fromfile, tofile) ) {
		fprintf(stderr, "ypserv:  yprename_map: can't mv %s to %s.\n",
		    fromfile, tofile);

		strcpy(fromfile, from);
		strcat(fromfile, ".pag");
		strcpy(tofile, to);
		strcat(tofile, ".pag");

		if (rename(tofile, fromfile) ) {
			fprintf(stderr,
			    "ypserv:  yprename_map: can't recover.\n");
			return(FALSE);
		}
	
		
		return(FALSE);
	}

	return(TRUE);
	
}

/*
 * This deletes the .pag and .dir files which implement a map.
 *
 * Note:  No error checking is done here for a garbage input file name or for
 * failed unlink operations.
 */
void
ypdel_mapfiles(basename)
	char *basename;
{
	char dbfilename[MAXNAMLEN + 1];

	if (!basename) {
		return;
	}
	
	strcpy(dbfilename, basename);
	strcat(dbfilename, ".pag");
	unlink(dbfilename);
	strcpy(dbfilename, basename);
	strcat(dbfilename, ".dir");
	unlink(dbfilename);
}

/*
 * This provides a layer above ypbind_to_named_peer which knows about the
 * dom_binding at xfr_binding and the associated peer pointer xfr_peer.  It
 * does the socket and client management required when changing from one peer
 * to another.  The dom_binding at xfr_binding will be set up to reflect
 * the rpc path to the peer, and xfr_peer will be set to point to the peer.
 * In the failure case, xfr_peer will be set to NULL, and no rpc path will
 * be valid.
 *
 * Note:  If the transfer peer binding is already set up to point to the input
 * peer, the function will return TRUE without testing the binding.  If the
 * binding is tested, however, an entire "ping" will be done, including
 * resetting the peer_last_polled, peer_reachable, and peer_port fields to
 * current values.
 */
bool
ypset_xfr_peer(ppeer)
	struct peer_list_item *ppeer;
{
	if (!ppeer) {
		return(FALSE);
	}
	
	if (xfr_peer == ppeer) {
		return(TRUE);
	}

	ypclr_xfr_peer();
	xfr_binding.dom_server_addr.sin_addr.s_addr = ppeer->peer_addr.s_addr;
	xfr_binding.dom_server_addr.sin_port = ppeer->peer_port;
	ppeer->peer_last_polled = tick_counter;
	ppeer->peer_reachable = FALSE;
		
	if (ypbind_to_named_server(peer_transport, &xfr_binding,
	    &ypintertry) ) {
		xfr_peer = ppeer;
		ppeer->peer_port = ping_peer.dom_server_port;
		ppeer->peer_reachable = TRUE;
		return(TRUE);
	} else {
		ppeer->peer_port = htons( (unsigned short) YPNOPORT);
		return(FALSE);
	}
}

/*
 * This closes the socket associated with the transfer peer data structures,
 * and tears down the rcp path, releasing the associated system port.
 */
void
ypclr_xfr_peer()
{
	if (xfr_peer) {
		clnt_destroy(xfr_binding.dom_client);
		close(xfr_binding.dom_socket);
		xfr_peer = (struct peer_list_item *) NULL;
	} else {
		return;
	}
}

/*
 * This asks the current transfer peer for the order number associated with a
 * passed map.  If it gets the order number value back, it converts the value
 * to an int and passes that back as an output value.
 *
 * Notes:   If this function is called when xfr_peer is set to NULL, it is an
 * error condition.  In that case, a functional value of YPERR_YPERR (internal
 * error) will be returned.
 */
int
yppoll_for_order_number(pmap, order_number)
	struct map_list_item *pmap;
	int *order_number;
{
	struct yprequest req;
	struct ypresponse resp;
	unsigned int retval = 0;
	bool go_on = TRUE;
	enum clnt_stat clnt_stat;

	if (!pmap) {
		return(YPERR_YPERR);
	}
	
	pmap->map_last_polled = tick_counter;
	
	if (!order_number || !xfr_peer) {
		return(YPERR_YPERR);
	}

	req.yppoll_req_domain = pmap->map_domain->dom_name;
	req.yppoll_req_map = pmap->map_name;
	req.yp_reqtype = YPPOLL_REQTYPE;
	
	resp.yppoll_resp_domain = NULL;
	resp.yppoll_resp_map = NULL;
	resp.yppoll_resp_owner = NULL;
	resp.yppoll_resp_ordernum = 0;

	
	if( (clnt_stat = (enum clnt_stat) clnt_call(xfr_binding.dom_client,
	    YPPROC_POLL, xdr_yprequest, &req, xdr_ypresponse, &resp,
	    yptimeout) ) != RPC_SUCCESS) {
		return(YPERR_RPC);
	}

	if (resp.yp_resptype == YPPOLL_RESPTYPE) {

		if (strlen(resp.yppoll_resp_domain) == 0) {
			retval = YPERR_DOMAIN;
			go_on = FALSE;
		}
		
		if (go_on && strlen(resp.yppoll_resp_map) == 0) {
			retval = YPERR_MAP;
			go_on = FALSE;
		}

		if (go_on && resp.yppoll_resp_ordernum == 0) {
			retval = YPERR_KEY;
			go_on = FALSE;
		}

		if (go_on) {
			*order_number = resp.yppoll_resp_ordernum;
		}
		
	} else {
		retval = YPERR_YPERR;
	}

	CLNT_FREERES(xfr_binding.dom_client, xdr_ypresponse, &resp);
	return(retval);
}

/*
 * This tries to find out whether a map should be queued to the map transfer
 * list.  It should be if it doesn't exist, or if it does exist, but is not
 * supported, or if its master is not known.  Maps of which we are the master
 * will be checked locally to catch points at which the humans running around
 * the system have changed the world beneath us.  No RPC-based stuff is being
 * done here.
 */
void
ypping_map(pmap)
	struct map_list_item *pmap;
{
	struct peer_list_item *ppeer = (struct peer_list_item *) NULL;
	unsigned long order_at_peer;
	char map[MAXNAMLEN + 1];
	bool was_supported;
	unsigned long old_order;
	int case_index;
	int pid;

	if (!pmap) {
		return;
	}
	
	pmap->map_last_polled = tick_counter;
	ppeer = pmap->map_master;

	if (ppeer && (strcmp(ppeer->peer_pname, myhostname) == 0) ) {
		was_supported = pmap->map_supported;
		old_order = pmap->map_order;
		ypmkfilename(pmap->map_domain->dom_name, pmap->map_name, map);

		if (ypcheck_map_existence(map) ) {
			pmap->map_exists = TRUE;

			if(ypget_map_order(pmap) ) {
				pmap->map_supported = TRUE;
			} else {
				pmap->map_supported = FALSE;
			}
				
		} else {
			pmap->map_exists = FALSE;
			pmap->map_supported = FALSE;
		}

		if (pmap->map_supported && ( (!was_supported) ||
		    (was_supported && (old_order != pmap->map_order) ) ) ) {
			    
			/*
		 	 * If the map is in the list of special cases,
			 * call the special case handler.
		 	 */
		 
			if ( (case_index = ypspecial_casep(
			    pmap->map_name, map_special_cases) ) >= 0) {
				special_map_handlers[case_index](pmap);
			}

			pid = fork();

			if (pid == 0) {
				ypclr_xfr_peer();

				for (ppeer =
				    yppoint_at_peerlist(pmap->map_domain);
			    	    ppeer != (struct peer_list_item *) NULL;
			    	    ppeer = ypnext_peer(ppeer)) {

					if (strcmp(ppeer->peer_pname,
					    myhostname) == 0) {
						continue;
				 	}
						 
					ypsend_getreq(ppeer, pmap);
				}

				exit(0);
			}
		}

	} else {			/* Master unknown, or not me */
		(void) ypadd_xfr(pmap);
	}
}