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