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