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