#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); } }