yppush_main.c revision 13799
1/* 2 * Copyright (c) 1995 3 * Bill Paul <wpaul@ctr.columbia.edu>. All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 3. All advertising materials mentioning features or use of this software 14 * must display the following acknowledgement: 15 * This product includes software developed by Bill Paul. 16 * 4. Neither the name of the author nor the names of any co-contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND 21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 * ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul OR CONTRIBUTORS BE LIABLE 24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 * SUCH DAMAGE. 31 * 32 * $Id: yppush_main.c,v 1.25 1996/01/27 19:44:48 wpaul Exp $ 33 */ 34 35#include <stdio.h> 36#include <stdlib.h> 37#include <unistd.h> 38#include <string.h> 39#include <signal.h> 40#ifdef LONGJMP 41#include <setjmp.h> 42#endif 43#include <time.h> 44#include <errno.h> 45#include <sys/socket.h> 46#include <sys/fcntl.h> 47#include <sys/wait.h> 48#include <sys/param.h> 49#include <rpc/rpc.h> 50#include <rpc/clnt.h> 51#include <rpc/pmap_clnt.h> 52#include <rpcsvc/yp.h> 53struct dom_binding {}; 54#include <rpcsvc/ypclnt.h> 55#include "ypxfr_extern.h" 56#include "yppush_extern.h" 57 58#ifndef lint 59static const char rcsid[] = "$Id: yppush_main.c,v 1.25 1996/01/27 19:44:48 wpaul Exp $"; 60#endif 61 62char *progname = "yppush"; 63int debug = 1; 64int _rpcpmstart = 0; 65char *yp_dir = _PATH_YP; 66 67char *yppush_mapname = NULL; /* Map to transfer. */ 68char *yppush_domain = NULL; /* Domain in which map resides. */ 69char *yppush_master = NULL; /* Master NIS server for said domain. */ 70int verbose = 0; /* Toggle verbose mode. */ 71unsigned long yppush_transid = 0; 72int yppush_timeout = 80; /* Default timeout. */ 73int yppush_jobs = 0; /* Number of allowed concurrent jobs. */ 74int yppush_running_jobs = 0; /* Number of currently running jobs. */ 75#ifdef LONGJMP 76int yppush_pausing = 0; /* Flag set when longjmp()s are allowed. */ 77jmp_buf env; 78#endif 79int yppush_alarm_tripped = 0; 80 81/* Structure for holding information about a running job. */ 82struct jobs { 83 unsigned long tid; 84 int pid; 85 int sock; 86 int port; 87 ypxfrstat stat; 88 unsigned long prognum; 89 char *server; 90 char *map; 91 int polled; 92 struct jobs *next; 93}; 94 95struct jobs *yppush_joblist; /* Linked list of running jobs. */ 96 97/* 98 * Local error messages. 99 */ 100static char *yppusherr_string(err) 101 int err; 102{ 103 switch(err) { 104 case YPPUSH_TIMEDOUT: return("transfer or callback timed out"); 105 case YPPUSH_YPSERV: return("failed to contact ypserv"); 106 case YPPUSH_NOHOST: return("no such host"); 107 case YPPUSH_PMAP: return("portmapper failure"); 108 default: return("unknown error code"); 109 } 110} 111 112/* 113 * Report state of a job. 114 */ 115static int yppush_show_status(status, tid) 116 ypxfrstat status; 117 unsigned long tid; 118{ 119 struct jobs *job; 120 121 job = yppush_joblist; 122 123 while(job) { 124 if (job->tid == tid) 125 break; 126 job = job->next; 127 } 128 129 if (job->polled) { 130 return(0); 131 } 132 133 if (verbose > 1) 134 yp_error("Checking return status: Transaction ID: %lu", 135 job->tid); 136 if (status != YPPUSH_SUCC || verbose) { 137 yp_error("Transfer of map %s to server %s %s.", 138 job->map, job->server, status == YPPUSH_SUCC ? 139 "succeeded" : "failed"); 140 yp_error("status returned by ypxfr: %s", status > YPPUSH_AGE ? 141 yppusherr_string(status) : 142 ypxfrerr_string(status)); 143 } 144 145 job->polled = 1; 146 147 svc_unregister(job->prognum, 1); 148 149 yppush_running_jobs--; 150 return(0); 151} 152 153/* Exit routine. */ 154static void yppush_exit(now) 155 int now; 156{ 157 struct jobs *jptr; 158 int still_pending = 1; 159 160 /* Let all the information trickle in. */ 161 while(!now && still_pending) { 162#ifdef LONGJMP 163 yppush_pausing++; 164 setjmp(env); /* more magic */ 165#endif 166 jptr = yppush_joblist; 167 still_pending = 0; 168 while (jptr) { 169 if (jptr->polled == 0) { 170 still_pending++; 171 if (verbose > 1) 172 yp_error("%s has not responded", 173 jptr->server); 174 } else { 175 if (verbose > 1) 176 yp_error("%s has responded", 177 jptr->server); 178 } 179 jptr = jptr->next; 180 } 181 if (still_pending) { 182 if (verbose > 1) 183 yp_error("%d transfer%sstill pending", 184 still_pending, 185 still_pending > 1 ? "s " : " "); 186 yppush_alarm_tripped = 0; 187 alarm(YPPUSH_RESPONSE_TIMEOUT); 188 pause(); 189#ifdef LONGJMP 190 yppush_pausing = 0; 191#endif 192 alarm(0); 193 if (yppush_alarm_tripped == 1) { 194 yp_error("timed out"); 195 now = 1; 196 } 197 } else { 198 if (verbose) 199 yp_error("all transfers complete"); 200 break; 201 } 202 } 203 204 205 /* All stats collected and reported -- kill all the stragglers. */ 206 jptr = yppush_joblist; 207 while(jptr) { 208 if (!jptr->polled) 209 yp_error("warning: exiting with transfer \ 210to %s (transid = %lu) still pending.", jptr->server, jptr->tid); 211 svc_unregister(jptr->prognum, 1); 212 jptr = jptr->next; 213 } 214 215 exit(0); 216} 217 218/* 219 * Handler for 'normal' signals. 220 */ 221 222static void handler(sig) 223 int sig; 224{ 225 if (sig == SIGTERM || sig == SIGINT || sig == SIGABRT) { 226 yppush_jobs = 0; 227 yppush_exit(1); 228 } 229 230 if (sig == SIGALRM) { 231 alarm(0); 232 yppush_alarm_tripped++; 233 } 234 235 return; 236} 237 238/* 239 * Dispatch loop for callback RPC services. 240 */ 241static void yppush_svc_run() 242{ 243#ifdef FD_SETSIZE 244 fd_set readfds; 245#else 246 int readfds; 247#endif /* def FD_SETSIZE */ 248 struct timeval timeout; 249 250 timeout.tv_usec = 0; 251 timeout.tv_sec = 5; 252 253retry: 254#ifdef FD_SETSIZE 255 readfds = svc_fdset; 256#else 257 readfds = svc_fds; 258#endif /* def FD_SETSIZE */ 259 switch (select(_rpc_dtablesize(), &readfds, NULL, NULL, &timeout)) { 260 case -1: 261 if (errno == EINTR) 262 goto retry; 263 yp_error("select failed: %s", strerror(errno)); 264 break; 265 case 0: 266 yp_error("select() timed out"); 267 break; 268 default: 269 svc_getreqset(&readfds); 270 break; 271 } 272 return; 273} 274 275/* 276 * Special handler for asynchronous socket I/O. We mark the 277 * sockets of the callback handlers as O_ASYNC and handle SIGIO 278 * events here, which will occur when the callback handler has 279 * something interesting to tell us. 280 */ 281static void async_handler(sig) 282 int sig; 283{ 284 yppush_svc_run(); 285 286 /* reset any pending alarms. */ 287 alarm(0); 288 yppush_alarm_tripped++; 289 kill(getpid(), SIGALRM); 290#ifdef LONGJMP 291 if (yppush_pausing) 292 longjmp(env, 1); 293#endif 294 return; 295} 296 297/* 298 * RPC service routines for callbacks. 299 */ 300void * 301yppushproc_null_1_svc(void *argp, struct svc_req *rqstp) 302{ 303 static char * result; 304 /* Do nothing -- RPC conventions call for all a null proc. */ 305 return((void *) &result); 306} 307 308void * 309yppushproc_xfrresp_1_svc(yppushresp_xfr *argp, struct svc_req *rqstp) 310{ 311 static char * result; 312 yppush_show_status(argp->status, argp->transid); 313 return((void *) &result); 314} 315 316/* 317 * Transmit a YPPROC_XFR request to ypserv. 318 */ 319static int yppush_send_xfr(job) 320 struct jobs *job; 321{ 322 ypreq_xfr req; 323/* ypresp_xfr *resp; */ 324 DBT key, data; 325 CLIENT *clnt; 326 struct rpc_err err; 327 struct timeval timeout; 328 329 timeout.tv_usec = 0; 330 timeout.tv_sec = 0; 331 332 /* 333 * The ypreq_xfr structure has a member of type map_parms, 334 * which seems to require the order number of the map. 335 * It isn't actually used at the other end (at least the 336 * FreeBSD ypserv doesn't use it) but we fill it in here 337 * for the sake of completeness. 338 */ 339 key.data = "YP_LAST_MODIFIED"; 340 key.size = sizeof ("YP_LAST_MODIFIED") - 1; 341 342 if (yp_get_record(yppush_domain, yppush_mapname, &key, &data, 343 1) != YP_TRUE) { 344 yp_error("failed to read order number from %s: %s: %s", 345 yppush_mapname, yperr_string(yp_errno), 346 strerror(errno)); 347 return(1); 348 } 349 350 /* Fill in the request arguments */ 351 req.map_parms.ordernum = atoi(data.data); 352 req.map_parms.domain = yppush_domain; 353 req.map_parms.peer = yppush_master; 354 req.map_parms.map = job->map; 355 req.transid = job->tid; 356 req.prog = job->prognum; 357 req.port = job->port; 358 359 /* Get a handle to the remote ypserv. */ 360 if ((clnt = clnt_create(job->server, YPPROG, YPVERS, "udp")) == NULL) { 361 yp_error("%s: %s",job->server,clnt_spcreateerror("couldn't \ 362create udp handle to NIS server")); 363 switch(rpc_createerr.cf_stat) { 364 case RPC_UNKNOWNHOST: 365 job->stat = YPPUSH_NOHOST; 366 break; 367 case RPC_PMAPFAILURE: 368 job->stat = YPPUSH_PMAP; 369 break; 370 default: 371 job->stat = YPPUSH_RPC; 372 break; 373 } 374 return(1); 375 } 376 377 /* 378 * Reduce timeout to nothing since we may not 379 * get a response from ypserv and we don't want to block. 380 */ 381 if (clnt_control(clnt, CLSET_TIMEOUT, (char *)&timeout) == FALSE) 382 yp_error("failed to set timeout on ypproc_xfr call"); 383 384 /* Invoke the ypproc_xfr service. */ 385 if (ypproc_xfr_2(&req, clnt) == NULL) { 386 clnt_geterr(clnt, &err); 387 if (err.re_status != RPC_SUCCESS && 388 err.re_status != RPC_TIMEDOUT) { 389 yp_error("%s: %s", job->server, clnt_sperror(clnt, 390 "yp_xfr failed")); 391 job->stat = YPPUSH_YPSERV; 392 clnt_destroy(clnt); 393 return(1); 394 } 395 } 396 397 clnt_destroy(clnt); 398 399 return(0); 400} 401 402/* 403 * Main driver function. Register the callback service, add the transfer 404 * request to the internal list, send the YPPROC_XFR request to ypserv 405 * do other magic things. 406 */ 407int yp_push(server, map, tid) 408 char *server; 409 char *map; 410 unsigned long tid; 411{ 412 unsigned long prognum; 413 int sock = RPC_ANYSOCK; 414 SVCXPRT *xprt; 415 struct jobs *job; 416 417 /* 418 * Register the callback service on the first free 419 * transient program number. 420 */ 421 xprt = svcudp_create(sock); 422 for (prognum = 0x4000000; prognum < 0x5FFFFFF; prognum++) { 423 if (svc_register(xprt, prognum, 1, 424 yppush_xfrrespprog_1, IPPROTO_UDP) == TRUE) 425 break; 426 } 427 428 /* Register the job in our linked list of jobs. */ 429 if ((job = (struct jobs *)malloc(sizeof (struct jobs))) == NULL) { 430 yp_error("malloc failed: %s", strerror(errno)); 431 yppush_exit(1); 432 } 433 434 /* Initialize the info for this job. */ 435 job->stat = 0; 436 job->tid = tid; 437 job->port = xprt->xp_port; 438 job->sock = xprt->xp_sock; /*XXX: Evil!! EEEEEEEVIL!!! */ 439 job->server = strdup(server); 440 job->map = strdup(map); 441 job->prognum = prognum; 442 job->polled = 0; 443 job->next = yppush_joblist; 444 yppush_joblist = job; 445 446 /* 447 * Set the RPC sockets to asynchronous mode. This will 448 * cause the system to smack us with a SIGIO when an RPC 449 * callback is delivered. This in turn allows us to handle 450 * the callback even though we may be in the middle of doing 451 * something else at the time. 452 * 453 * XXX This is a horrible thing to do for two reasons, 454 * both of which have to do with portability: 455 * 1) We really ought not to be sticking our grubby mits 456 * into the RPC service transport handle like this. 457 * 2) Even in this day and age, there are still some *NIXes 458 * that don't support async socket I/O. 459 */ 460 if (fcntl(xprt->xp_sock, F_SETOWN, getpid()) == -1 || 461 fcntl(xprt->xp_sock, F_SETFL, O_ASYNC) == -1) { 462 yp_error("failed to set async I/O mode: %s", 463 strerror(errno)); 464 yppush_exit(1); 465 } 466 467 if (verbose) { 468 yp_error("initiating transfer: %s -> %s (transid = %lu)", 469 yppush_mapname, server, tid); 470 } 471 472 /* 473 * Send the XFR request to ypserv. We don't have to wait for 474 * a response here since we can handle them asynchronously. 475 */ 476 477 if (yppush_send_xfr(job)){ 478 /* Transfer request blew up. */ 479 yppush_show_status(job->stat ? job->stat : 480 YPPUSH_YPSERV,job->tid); 481 } else { 482 if (verbose > 1) 483 yp_error("%s has been called", server); 484 } 485 486 return(0); 487} 488 489/* 490 * Called for each entry in the ypservers map from yp_get_map(), which 491 * is our private yp_all() routine. 492 */ 493int yppush_foreach(status, key, keylen, val, vallen, data) 494 int status; 495 char *key; 496 int keylen; 497 char *val; 498 int vallen; 499 char *data; 500{ 501 char server[YPMAXRECORD + 2]; 502 503 if (status != YP_TRUE) 504 return (status); 505 506 snprintf(server, sizeof(server), "%.*s", vallen, val); 507 508 /* 509 * Restrict the number of concurrent jobs. If yppush_jobs number 510 * of jobs have already been dispatched and are still pending, 511 * wait for one of them to finish so we can reuse its slot. 512 */ 513 if (yppush_jobs <= 1) { 514#ifdef LONGJMP 515 yppush_pausing++; 516 while (!setjmp(env) && yppush_running_jobs) { 517#else 518 yppush_alarm_tripped = 0; 519 while (!yppush_alarm_tripped && yppush_running_jobs) { 520#endif 521 alarm(yppush_timeout); 522 yppush_alarm_tripped = 0; 523 pause(); 524 alarm(0); 525 } 526#ifdef LONGJMP 527 yppush_pausing = 0; 528#endif 529 } else { 530#ifdef LONGJMP 531 yppush_pausing++; 532 while (!setjmp(env) && yppush_running_jobs >= yppush_jobs) { 533#else 534 yppush_alarm_tripped = 0; 535 while (!yppush_alarm_tripped && yppush_running_jobs >= yppush_jobs) { 536#endif 537 alarm(yppush_timeout); 538 yppush_alarm_tripped = 0; 539 pause(); 540 alarm(0); 541 } 542#ifdef LONGJMP 543 yppush_pausing = 0; 544#endif 545 } 546 547 /* Cleared for takeoff: set everything in motion. */ 548 if (yp_push(&server, yppush_mapname, yppush_transid)) 549 return(yp_errno); 550 551 /* Bump the job counter and transaction ID. */ 552 yppush_running_jobs++; 553 yppush_transid++; 554 return (0); 555} 556 557static void usage() 558{ 559 fprintf (stderr, "%s: [-d domain] [-t timeout] [-j #parallel jobs] \ 560[-h host] [-p path] mapname\n", progname); 561 exit(1); 562} 563 564/* 565 * Entry point. (About time!) 566 */ 567main(argc,argv) 568 int argc; 569 char *argv[]; 570{ 571 int ch; 572 DBT key, data; 573 char myname[MAXHOSTNAMELEN]; 574 struct hostlist { 575 char *name; 576 struct hostlist *next; 577 }; 578 struct hostlist *yppush_hostlist = NULL; 579 struct hostlist *tmp; 580 struct sigaction sa; 581 582 while ((ch = getopt(argc, argv, "d:j:p:h:t:v")) != EOF) { 583 switch(ch) { 584 case 'd': 585 yppush_domain = optarg; 586 break; 587 case 'j': 588 yppush_jobs = atoi(optarg); 589 if (yppush_jobs <= 0) 590 yppush_jobs = 1; 591 break; 592 case 'p': 593 yp_dir = optarg; 594 break; 595 case 'h': /* we can handle multiple hosts */ 596 if ((tmp = (struct hostlist *)malloc(sizeof(struct hostlist))) == NULL) { 597 yp_error("malloc() failed: %s", strerror(errno)); 598 yppush_exit(1); 599 } 600 tmp->name = strdup(optarg); 601 tmp->next = yppush_hostlist; 602 yppush_hostlist = tmp; 603 break; 604 case 't': 605 yppush_timeout = atoi(optarg); 606 break; 607 case 'v': 608 verbose++; 609 break; 610 default: 611 usage(); 612 break; 613 } 614 } 615 616 argc -= optind; 617 argv += optind; 618 619 yppush_mapname = argv[0]; 620 621 if (yppush_mapname == NULL) { 622 /* "No guts, no glory." */ 623 usage(); 624 } 625 626 /* 627 * If no domain was specified, try to find the default 628 * domain. If we can't find that, we're doomed and must bail. 629 */ 630 if (yppush_domain == NULL) { 631 char *yppush_check_domain; 632 if (!yp_get_default_domain(&yppush_check_domain) && 633 !_yp_check(&yppush_check_domain)) { 634 yp_error("no domain specified and NIS not running"); 635 usage(); 636 } else 637 yp_get_default_domain(&yppush_domain); 638 } 639 640 /* Check to see that we are the master for this map. */ 641 642 if (gethostname ((char *)&myname, sizeof(myname))) { 643 yp_error("failed to get name of local host: %s", 644 strerror(errno)); 645 yppush_exit(1); 646 } 647 648 key.data = "YP_MASTER_NAME"; 649 key.size = sizeof("YP_MASTER_NAME") - 1; 650 651 if (yp_get_record(yppush_domain, yppush_mapname, 652 &key, &data, 1) != YP_TRUE) { 653 yp_error("couldn't open %s map: %s", yppush_mapname, 654 strerror(errno)); 655 yppush_exit(1); 656 } 657 658 if (strncmp(myname, data.data, data.size)) { 659 yp_error("this host is not the master for %s",yppush_mapname); 660 yppush_exit(1); 661 } 662 663 yppush_master = strdup(data.data); 664 yppush_master[data.size] = '\0'; 665 666 /* Install some handy handlers. */ 667 signal(SIGALRM, handler); 668 signal(SIGTERM, handler); 669 signal(SIGINT, handler); 670 signal(SIGABRT, handler); 671 672 /* 673 * Set up the SIGIO handler. Make sure that some of the 674 * other signals are blocked while the handler is running so 675 * select() doesn't get interrupted. 676 */ 677 sigemptyset(&sa.sa_mask); 678 sigaddset(&sa.sa_mask, SIGIO); /* Goes without saying. */ 679 sigaddset(&sa.sa_mask, SIGPIPE); 680 sigaddset(&sa.sa_mask, SIGCHLD); 681 sigaddset(&sa.sa_mask, SIGALRM); 682 sigaddset(&sa.sa_mask, SIGINT); 683 sa.sa_handler = async_handler; 684 685 sigaction(SIGIO, &sa, NULL); 686 687 /* set initial transaction ID */ 688 time(&yppush_transid); 689 690 if (yppush_hostlist) { 691 /* 692 * Host list was specified on the command line: 693 * kick off the transfers by hand. 694 */ 695 tmp = yppush_hostlist; 696 while(tmp) { 697 yppush_foreach(YP_TRUE, NULL, 0, tmp->name, 698 strlen(tmp->name)); 699 tmp = tmp->next; 700 } 701 } else { 702 /* 703 * Do a yp_all() on the ypservers map and initiate a ypxfr 704 * for each one. 705 */ 706 ypxfr_get_map("ypservers", yppush_domain, 707 "localhost", yppush_foreach); 708 } 709 710 if (verbose > 1) 711 yp_error("all jobs dispatched"); 712 713 /* All done -- normal exit. */ 714 yppush_exit(0); 715 716 /* Just in case. */ 717 exit(0); 718} 719