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