4.3BSD-UWisc/src/usr.etc/ypserv/ypserv_xfr.c
#ifndef lint
/* @(#)ypserv_xfr.c 2.1 86/04/16 NFSSRC */
static char sccsid[] = "@(#)ypserv_xfr.c 1.1 86/02/05 Copyr 1984 Sun Micro";
#endif
/*
* ypserv_xfr.c
* This contains functions which manipulate the data structures associated with
* the transfer of map databases from a peer yp server to this yp server.
*/
#include "ypsym.h"
#include <sys/file.h>
extern void clnt_perror();
extern bool silent;
extern bool log_transfers; /* Defined and documented in ypserv.c */
int ypclnterr2exit();
char *ypclnterr2string();
void output_rpc_error();
static char logmsg_template[] = " Map = %s, domain = %s, peer = %s. Transfer ";
static char log_filename[MAXNAMLEN + 1];
static FILE *log_file = (FILE *) NULL;
/*
* This allocates memory for a map_xfr_entry, initializes its fields to default
* values, points it at its associated map, and calls ypenqueue_xfr to stick it
* on the list of maps to be transferred. The fields within the new
* map_xfr_entry are set as follows:
* mx_xfr_pid is set to NULL
* mx_xfr_succeeded is set to FALSE
* mx_map is set to pmap
* mx_temp_name will contain the null string
* The map_xfr_entry will be queued to the map transfer list by lower levels.
*
* Note: This will not add a second map transfer element to the list if one is
* already there referring to the same map.
*/
bool
ypadd_xfr(pmap)
struct map_list_item *pmap;
{
struct map_xfr_entry *pxfr;
if (!pmap) {
return(FALSE);
}
if (yppoint_at_xfr(pmap)) {
return(TRUE);
}
if ( (pxfr = (struct map_xfr_entry *) malloc((sizeof
(struct map_xfr_entry) ) ) ) == (struct map_xfr_entry *) NULL) {
return(FALSE);
}
pxfr->mx_xfr_pid = NULL;
pxfr->mx_map = pmap;
pxfr->mx_temp_name[0] = '\0';
pxfr->mx_temp_path[0] = '\0';
ypenqueue_xfr(pxfr);
return(TRUE);
}
/*
* This sticks a map_xfr_entry onto the list of maps to be transferred, making
* it the tail entry.
*/
void
ypenqueue_xfr(pxfr)
struct map_xfr_entry *pxfr;
{
struct map_xfr_entry *scan;
if (!pxfr) {
return;
}
if (map_xfr_list != (struct map_xfr_entry *) NULL) {
for (scan = map_xfr_list;
scan->mx_pnext != (struct map_xfr_entry *) NULL;
scan = scan->mx_pnext) { /* Null loop */
}
scan->mx_pnext = pxfr;
} else {
map_xfr_list = pxfr;
}
pxfr->mx_pnext = (struct map_xfr_entry *) NULL;
}
/*
* This returns a pointer to a map transfer list entry associated with a given
* map, or NULL.
*/
struct map_xfr_entry *
yppoint_at_xfr(pmap)
struct map_list_item *pmap;
{
struct map_xfr_entry *scan;
if (!pmap) {
return( (struct map_xfr_entry *) NULL);
}
for (scan = map_xfr_list;
( (scan != (struct map_xfr_entry *) NULL) &&
(scan->mx_map != pmap) );
scan = scan->mx_pnext) { /* Null loop */
}
return(scan);
}
/*
* This deletes a map transfer element from the transfer list.
*
* Note: If the map is currently getting transferred, this will null out the
* mx_map field, but not free the map transfer element. The transfer-done
* routine should check for a null map pointer, and should call
* yprelease_current_xfr to get rid of the map transfer element, and do
* whatever it needs to do with the transferred map (or the error).
*/
void
ypdel_xfr(pmap)
struct map_list_item *pmap;
{
struct map_xfr_entry *pxfr;
struct map_xfr_entry *scan;
if (!pmap) {
return;
}
if (current_transfer && (current_transfer->mx_map == pmap) ) {
current_transfer->mx_map = (struct map_list_item *) NULL;
}
/*
* The map may have been queued to the transfer list while it was
* the current entry, so keep trying to remove it.
*/
if ( (pxfr = yppoint_at_xfr(pmap) ) == (struct map_xfr_entry *) NULL) {
return; /* It's not on the list */
}
/* At this point we know that it is on the list */
if (map_xfr_list == pxfr) {
map_xfr_list = pxfr->mx_pnext; /* It's the head entry */
} else {
for (scan = map_xfr_list;
( (scan != (struct map_xfr_entry *) NULL) &&
(scan->mx_pnext != pxfr) );
scan = scan->mx_pnext) { /* Null loop */
}
scan->mx_pnext = pxfr->mx_pnext;
}
free(pxfr);
}
/*
* This dequeues the head entry from the list of maps to be transfered, and
* points current_transfer at it. The state of the map transfer queue will be
* changed, and current_transfer will point to the old head entry.
*
* Note: The only case in which this returns FALSE is that in which
* current_transfer is not NULL when this function is called. That's bad....
*/
bool
ypset_current_xfr()
{
if (current_transfer != (struct map_xfr_entry *) NULL)
return(FALSE);
current_transfer = map_xfr_list;
map_xfr_list = map_xfr_list->mx_pnext;
current_transfer->mx_pnext = (struct map_xfr_entry *) NULL;
return(TRUE);
}
/*
* This sets current_transfer to NULL, and returns any memory pointed to (that
* is, a map_xfr_entry) to the system.
*/
void
yprelease_current_xfr()
{
if (current_transfer) {
free(current_transfer);
current_transfer = (struct map_xfr_entry *) NULL;
}
}
/*
* This enqueues the element pointed to by current_transfer to the list of maps
* to be transferred, and sets current_transfer to NULL .
*/
void
yprequeue_current_xfr()
{
if (current_transfer) {
ypenqueue_xfr(current_transfer);
current_transfer = (struct map_xfr_entry *) NULL;
}
}
/*
* This shoves the transfer process' pid into the mx_xfr_pid field of the
* current map transfer entry.
*
* Note: This returns FALSE only if there is no current transfer map.
*/
bool
ypset_current_pid(pid)
int pid;
{
if (current_transfer == (struct map_xfr_entry *) NULL)
return(FALSE);
current_transfer->mx_xfr_pid = pid;
return(TRUE);
}
/*
* This generates a temp map name, and shoves it into the mx_temp_name field of
* the current map transfer entry. It also generates a full path name within
* the defined temporary transfer directory, and puts that name into field
* mx_temp_path.
*
* Note: This returns FALSE only if there is no current transfer map.
*/
bool
ypset_current_tmpname()
{
if (current_transfer == (struct map_xfr_entry *) NULL)
return(FALSE);
ypmk_tmpname(current_transfer->mx_temp_name);
strcpy(current_transfer->mx_temp_path, YPTEMPDIRECTORY);
strcat(current_transfer->mx_temp_path, "/");
strcat(current_transfer->mx_temp_path, current_transfer->mx_temp_name);
return(TRUE);
}
/*
* This does the special processing needed when a new copy of the special map
* ypdomains in domain yp_private is successfully transferred. The state of
* the yp internal data base may be changed.
*
* Note: The initial string comparison on the domain name is to filter for
* yp_private domain. Any other domain is not a special case.
*/
void
ypnew_ypdomains(pmap)
struct map_list_item *pmap;
{
struct domain_list_item *pdom;
bool out_of_date_in_list = TRUE;
if (!pmap || strcmp(pmap->map_domain->dom_name, YPPRIVATE_DOMAIN_NAME) )
return;
/*
* Mark all domains as "not in the new map"
*/
for (pdom = yppoint_at_first_domain(FALSE);
pdom != (struct domain_list_item *) NULL;
pdom = yppoint_at_next_domain(pdom, FALSE) ) {
pdom->dom_in_new_map = FALSE;
}
/*
* Pick up new domains, mark existing domains which are in the new
* map so they will be seen.
*/
ypget_all_domains ();
/*
* Throw away domains which were in the old world, but are not in the
* new one.
*/
while (out_of_date_in_list) {
out_of_date_in_list = FALSE;
for (pdom = yppoint_at_first_domain(FALSE);
pdom != (struct domain_list_item *) NULL;
pdom = yppoint_at_next_domain(pdom, FALSE) ) {
if (!pdom->dom_in_new_map) {
ypdel_one_domain(pdom->dom_name);
out_of_date_in_list = TRUE;
break;
}
}
}
/*
* Find out which of the new domains are supportable. This will leave
* already supported domains unchanged.
*/
ypget_supported_domains();
for (pdom = yppoint_at_first_domain(TRUE);
pdom != (struct domain_list_item *) NULL;
pdom = yppoint_at_next_domain(pdom, TRUE) ) {
ypbld_dom_peerlist(pdom);
ypget_dom_all_maps(pdom);
ypget_dom_supported_maps(pdom);
}
}
/*
* This does the special processing needed when a new
* copy of the special map is ypservers successfully transferred.
* The state of the yp internal data base may be changed.
*/
void
ypnew_ypservers(pmap)
struct map_list_item *pmap;
{
struct peer_list_item *ppeer;
bool out_of_date_in_list = TRUE;
if (!pmap) {
return;
}
/*
* Mark all peers as "not in the new map"
*/
for (ppeer = yppoint_at_peerlist(pmap->map_domain);
ppeer != (struct peer_list_item *) NULL;
ppeer = ypnext_peer(ppeer) ) {
ppeer->peer_in_new_map = FALSE;
}
/*
* Pick up any new peers from the map, recheck all the peer addresses.
*/
ypbld_dom_peerlist(pmap->map_domain);
/*
* Knock out peers which still show up as "not in the new map", and
* NULL-out any references to the peer by the maps within the domain.
*/
while (out_of_date_in_list) {
out_of_date_in_list = FALSE;
for (ppeer = yppoint_at_peerlist(pmap->map_domain);
ppeer != (struct peer_list_item *) NULL;
ppeer = ypnext_peer(ppeer) ) {
if (!ppeer->peer_in_new_map) {
ypdel_one_peer(ppeer, pmap->map_domain);
out_of_date_in_list = TRUE;
break;
}
}
}
}
/*
* This does the special processing needed when a new copy of the special map
* ypmaps is successfully transferred. The state of the yp internal data base
* may be changed.
*/
void
ypnew_ypmaps(pmap)
struct map_list_item *pmap;
{
struct map_list_item *scan;
bool out_of_date_in_list = TRUE;
if (!pmap) {
return;
}
/*
* Mark all maps as "not in the new map"
*/
for (scan = yppoint_at_maplist(pmap->map_domain);
scan != (struct map_list_item *) NULL;
scan = yppoint_at_next_map(scan) ) {
scan->map_in_new_map = FALSE;
}
/*
* Pick up any new maps, recheck all the map masters
*/
ypget_dom_all_maps(pmap->map_domain);
/*
* KO any maps which still show up as not in the new map
*/
while (out_of_date_in_list) {
out_of_date_in_list = FALSE;
for (scan = yppoint_at_maplist(pmap->map_domain);
scan != (struct map_list_item *) NULL;
scan = yppoint_at_next_map(scan) ) {
if (!scan->map_in_new_map) {
ypdel_one_map(scan);
out_of_date_in_list = TRUE;
break;
}
}
}
/*
* Check to see if the newly-added maps are supportable. Maps which
* are already supported won't be altered.
*/
ypget_dom_supported_maps(pmap->map_domain);
}
/*
* This does the special processing needed when a new copy of the special map
* hosts.byname is successfully transferred. This consists of looking at every
* peer on the peerlist of the domain, and resetting the internet address. Just
* for safety's sake, the port will also be reset to an out-of-range value, to
* force them to be rechecked at their next use. The state of the yp internal
* data base may be changed.
*/
void
ypnew_hosts(pmap)
struct map_list_item *pmap;
{
struct peer_list_item *ppeer;
if (!pmap) {
return;
}
for (ppeer = yppoint_at_peerlist(pmap->map_domain);
ppeer != (struct peer_list_item *) NULL;
ppeer = ypnext_peer(ppeer) ) {
ypget_peer_addr(ppeer->peer_pname, pmap->map_domain->dom_name,
&(ppeer->peer_addr) );
ppeer->peer_port = htons( (unsigned short) YPNOPORT);
}
}
/*
* This is the body of the map transfer child process, forked off in ypxfr_map.
* It uses the data structures pointed to through global cell current_xfr.
* The main "functional value" of this is a process exit code: YPXFR_EXIT_SUCC
* (0) if the transfer was completed successfully, otherwise one of the transfer
* error codes defined in ypsym.h. In addition, in the success case, the new
* map will be created under the temporary name which has been set up in the
* current transfer entry.
*/
void
ypdo_xfr()
{
struct peer_list_item *ppeer;
struct map_list_item *pmap;
unsigned long bin_ordernum_before;
unsigned long bin_ordernum_after;
unsigned long bin_ordernum_map;
unsigned int ypclnt_status;
bool order_found = FALSE;
datum key;
char *inkey;
int inkeylen;
datum val;
char dbm_filename[MAXNAMLEN + 1];
int dbm_file;
struct timeval time;
char *asctime;
char *tmpptr;
char logmsg[YPMAXMAP + YPMAXDOMAIN + sizeof(logmsg_template) + 256];
/*
* Figure out whether we should be talking to the master, or we should
* try to assign an alternate. If we can't find a transfer peer,
* ppeer will remain NULL.
*/
ppeer = (struct peer_list_item *) NULL;
pmap = current_transfer->mx_map;
if (pmap->map_master) {
ypping_peer(pmap->map_master);
if (pmap->map_master->peer_reachable == TRUE) {
ppeer = pmap->map_master;
}
}
if (!ppeer) {
if (ypfind_alternate(pmap) ) {
ppeer = yppoint_at_map_alternate(pmap);
}
}
/*
* If we're supposed to be logging transfers, open the log file.
* A failure of the fopen/freopen is equivalent to no logging.
*/
if (log_transfers) {
strcpy(log_filename, ypdbpath);
strcat(log_filename, "/YP_MAP_TRANSFER.LOG");
if (silent) {
log_file = fopen(log_filename, "a+");
} else {
log_file = freopen(log_filename, "a+", stderr);
}
}
/*
* If we are logging, build a timestamp, then write the timestamp and
* the first part of the message to the log file. The message will
* be finished at any point at which the process decides to exit.
*/
if (log_file) {
gettimeofday(&time, NULL);
if (asctime = ctime(&(time.tv_sec) ) ) {
strcpy(logmsg, asctime);
for (tmpptr = logmsg;
(*tmpptr != '\n') && (*tmpptr != '\0');
tmpptr++) {
}
*tmpptr = '\0'; /* Replace newline with null */
} else {
strcpy(logmsg, "<NO TIMESTAMP>");
}
strcat(logmsg, logmsg_template);
fprintf(log_file, logmsg,
current_transfer->mx_map->map_name,
current_transfer->mx_map->map_domain->dom_name,
(ppeer ? ppeer->peer_pname : "<NO PEERNAME>"));
}
if (!ppeer) {
if (log_file) {
fprintf(log_file,
"failed: Can't find transfer partner.\n");
}
exit(YPXFR_EXIT_PEER);
}
/*
* Bind to the transfering peer. If we can't do it, exit. Before we
* bind, free any resources allocated in us because of our parent's
* current binding.
*/
ypclr_xfr_peer();
if (!ypset_xfr_peer(ppeer) ) {
if (log_file) {
fprintf(log_file,
"failed: Can't set (or bind to) partner.\n");
}
exit(YPXFR_EXIT_PEER);
}
/*
* Get the map's order number. If there are problems, bag the rest of
* the work, and exit. If the peer's version of the map is <= the one
* we have already, let's not go through all the work.
*/
if (ypclnt_status = yppoll_for_order_number(current_transfer->mx_map,
&bin_ordernum_before) ) {
if (log_file) {
fprintf(log_file,
"failed: First poll for order failed; /n/treason %s.\n",
ypclnterr2string(ypclnt_status));
}
output_rpc_error(ypclnt_status);
exit(ypclnterr2exit(ypclnt_status) );
}
if (bin_ordernum_before <= current_transfer->mx_map->map_order) {
if (log_file) {
fprintf(log_file,
"not attempted - peer's copy not newer.\n");
}
exit(YPXFR_EXIT_OLD);
}
/* Make a new pair of empty dbm files for the temp map. */
strcpy(dbm_filename, current_transfer->mx_temp_path);
strcat(dbm_filename, ".pag");
dbm_file = open(dbm_filename, (O_RDWR | O_CREAT | O_TRUNC), 0666);
if (dbm_file == -1 ) {
if (log_file) {
fprintf(log_file, "failed: Can't create dbm file %s.\n",
dbm_filename);
}
exit (YPXFR_EXIT_FILE);
} else {
close(dbm_file);
}
strcpy(dbm_filename, current_transfer->mx_temp_path);
strcat(dbm_filename, ".dir");
dbm_file = open(dbm_filename, (O_RDWR | O_CREAT | O_TRUNC), 0666);
if (dbm_file == -1 ) {
if (log_file) {
fprintf(log_file, "failed: Can't create dbm file %s.\n",
dbm_filename);
}
exit (YPXFR_EXIT_FILE);
} else {
close(dbm_file);
}
/*
* Initialize the dbm data base. Notice that we aren't going through
* ypset_current_map here. We have to check to see if there is a
* current map (which was set up by our parent), then reinitialize the
* dbm private data base to point to our temporary map.
*/
if (current_map[0] != '\0') {
dbmclose(current_map);
};
if (dbminit(current_transfer->mx_temp_path) < 0) {
if (log_file) {
fprintf(log_file, "failed: Can't dbminit temp map %s\n",
current_transfer->mx_temp_path);
}
exit(YPXFR_EXIT_DBM);
}
/*
* Get the map from the peer, while there are no errors, and there are
* more key-value pairs. Any error condition will make us close up
* shop.
*/
ypclnt_status =
_ypclnt_dofirst (current_transfer->mx_map->map_domain->dom_name,
current_transfer->mx_map->map_name, &xfr_binding, yptimeout,
&(key.dptr), &(key.dsize), &(val.dptr), &(val.dsize));
if (ypclnt_status) {
if (log_file) {
fprintf(log_file,
"failed: Can't get first key-value pair from peer; /n/treason %s.\n",
ypclnterr2string(ypclnt_status));
}
output_rpc_error(ypclnt_status);
exit(ypclnterr2exit(ypclnt_status) );
}
while (TRUE) {
if (store(key, val) < 0) {
if (log_file) {
fprintf(log_file,
"failed: dbm store operation failed.\n");
}
exit(YPXFR_EXIT_DBM);
}
free(val.dptr);
inkey = key.dptr;
inkeylen = key.dsize;
ypclnt_status =
_ypclnt_donext (
current_transfer->mx_map->map_domain->dom_name,
current_transfer->mx_map->map_name, inkey, inkeylen,
&xfr_binding, yptimeout, &(key.dptr), &(key.dsize),
&(val.dptr), &(val.dsize));
if (ypclnt_status) {
/*
* If the error code is "no more", we have completed
* successfully; otherwise, this is a hard error.
*/
if (ypclnt_status == YPERR_NOMORE) {
if (store(key, val) < 0) {
if (log_file) {
fprintf(log_file,
"failed: dbm store operation failed.\n");
}
exit(YPXFR_EXIT_DBM);
}
free(inkey);
free(val.dptr);
free(key.dptr);
break;
} else {
if (log_file) {
fprintf(log_file,
"failed: Can't get key-value pair from peer; /n/treason %s.\n",
ypclnterr2string(ypclnt_status));
}
output_rpc_error(ypclnt_status);
exit(ypclnterr2exit(ypclnt_status) );
}
}
free(inkey);
}
/*
* Get the map's order number again.
*/
if (ypclnt_status= yppoll_for_order_number(current_transfer->mx_map,
&bin_ordernum_after) ) {
if (log_file) {
fprintf(log_file,
"failed: Second poll for order failed; /n/treason %s.\n",
ypclnterr2string(ypclnt_status));
}
output_rpc_error(ypclnt_status);
exit(ypclnterr2exit(ypclnt_status) );
}
if (bin_ordernum_after != bin_ordernum_before) {
if (log_file) {
fprintf(log_file,
"failed: Version changed at peer during transfer.\n");
}
exit(YPXFR_EXIT_SKEW);
}
/*
* Sanity-check the map by retrieving the order number directly from
* the newly transferred map. If it's not there, exit indicating a
* badly formed map. If it is there, but isn't the same as the value
* the peer has told us of, exit indicating a skew error. If everything
* is OK, exit with a success code.
*/
key.dptr = order_key;
key.dsize = ORDER_KEY_LENGTH;
val = fetch(key);
if (val.dptr == NULL) {
if (log_file) {
fprintf(log_file,
"failed: Can't retrieve order number from map.\n");
}
exit(YPXFR_EXIT_FORM);
}
/*
* Recopy the value from dbm into some local memory, so we can
* correctly terminate it. (It's not null-terminated, and we don't
* know what garbage characters follow the value characters in
* dbm's private memory. If the garbage characters are numeric,
* atol will return a garbage value.) We'll reuse the char array
* dbm_filename, which we no longer need.
*/
bcopy(val.dptr, dbm_filename, val.dsize);
dbm_filename[val.dsize] = '\0';
bin_ordernum_map = (unsigned long) atol(dbm_filename);
if (bin_ordernum_map == bin_ordernum_before) {
if (log_file) {
fprintf(log_file, "succeeded.\n");
}
exit(YPXFR_EXIT_SUCC);
} else {
if (log_file) {
fprintf(log_file,
"failed: Order number in map differs from that claimed by peer.\n");
}
exit(YPXFR_EXIT_SKEW);
}
}
/*
* This maps an error code coming back from the ypcommon client layer into a
* map transfer process exit code.
*/
static int
ypclnterr2exit(client_error)
int client_error;
{
int exit_code;
switch (client_error) {
case YPERR_BADARGS: {
exit_code = YPXFR_EXIT_ERR;
break;
}
case YPERR_RPC: {
exit_code = YPXFR_EXIT_RPC;
break;
}
case YPERR_DOMAIN: {
exit_code = YPXFR_EXIT_DOMAIN;
break;
}
case YPERR_MAP: {
exit_code = YPXFR_EXIT_MAP;
break;
}
case YPERR_KEY: {
exit_code = YPXFR_EXIT_FORM;
break;
}
case YPERR_YPERR: {
exit_code = YPXFR_EXIT_ERR;
break;
}
case YPERR_RESRC: {
exit_code = YPXFR_EXIT_RSRC;
break;
}
case YPERR_NOMORE: {
exit_code = YPXFR_EXIT_SUCC;
break;
}
default: {
exit_code = YPXFR_EXIT_ERR;
break;
}
}
return(exit_code);
}
/*
* This maps a ypclnt error code into a printable string for inclusion in
* a logging error message.
*/
static char *
ypclnterr2string(err)
unsigned int err;
{
char *p;
switch (err) {
case YPERR_BADARGS: {
p = "args to function are bad";
break;
}
case YPERR_RPC: {
p = "RPC failure";
break;
}
case YPERR_DOMAIN: {
p = "can't bind to a server which serves this domain.";
break;
}
case YPERR_MAP: {
p = "no such map in server's domain";
break;
}
case YPERR_KEY: {
p = "no such key in map";
break;
}
case YPERR_YPERR: {
p = "internal yp server or client interface error";
break;
}
case YPERR_RESRC: {
p = "local resource allocation failure";
break;
}
case YPERR_NOMORE: {
p = "no more records in map database";
break;
}
default: {
p = "unknown error code";
break;
}
}
return(p);
}
/*
* This checks to see if we are logging and we still have stderr open, then
* calls clnt_perror to dump the RPC error message into the log file.
* Because clnt_perror uses stderr, this error message is only available if the
* global boolean "silent" is FALSE.
*/
void
output_rpc_error(err)
unsigned int err;
{
if (log_file && !silent && (err == YPERR_RPC) ) {
clnt_perror(xfr_binding.dom_client, "ypserv: output_rpc_error");
}
}