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