Initial import from FreeBSD RELENG_4:
[dragonfly.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: src/usr.sbin/yppush/yppush_main.c,v 1.11.2.2 2002/02/15 00:46:59 des Exp $";
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 char *yppusherr_string(err)
92         int err;
93 {
94         switch (err) {
95         case YPPUSH_TIMEDOUT: return("transfer or callback timed out");
96         case YPPUSH_YPSERV:   return("failed to contact ypserv");
97         case YPPUSH_NOHOST:   return("no such host");
98         case YPPUSH_PMAP:     return("portmapper failure");
99         default:              return("unknown error code");
100         }
101 }
102
103 /*
104  * Report state of a job.
105  */
106 static int yppush_show_status(status, tid)
107         ypxfrstat status;
108         unsigned long tid;
109 {
110         struct jobs *job;
111
112         job = yppush_joblist;
113
114         while (job) {
115                 if (job->tid == tid)
116                         break;
117                 job = job->next;
118         }
119
120         if (job->polled) {
121                 return(0);
122         }
123
124         if (verbose > 1)
125                 yp_error("checking return status: transaction ID: %lu",
126                                                                 job->tid);
127         if (status != YPPUSH_SUCC || verbose) {
128                 yp_error("transfer of map %s to server %s %s",
129                         job->map, job->server, status == YPPUSH_SUCC ?
130                         "succeeded" : "failed");
131                 yp_error("status returned by ypxfr: %s", status > YPPUSH_AGE ?
132                         yppusherr_string(status) :
133                         ypxfrerr_string(status));
134         }
135
136         job->polled = 1;
137
138         svc_unregister(job->prognum, 1);
139
140         yppush_running_jobs--;
141         return(0);
142 }
143
144 /* Exit routine. */
145 static void yppush_exit(now)
146         int now;
147 {
148         struct jobs *jptr;
149         int still_pending = 1;
150
151         /* Let all the information trickle in. */
152         while (!now && still_pending) {
153                 jptr = yppush_joblist;
154                 still_pending = 0;
155                 while (jptr) {
156                         if (jptr->polled == 0) {
157                                 still_pending++;
158                                 if (verbose > 1)
159                                         yp_error("%s has not responded",
160                                                   jptr->server);
161                         } else {
162                                 if (verbose > 1)
163                                         yp_error("%s has responded",
164                                                   jptr->server);
165                         }
166                         jptr = jptr->next;
167                 }
168                 if (still_pending) {
169                         if (verbose > 1)
170                                 yp_error("%d transfer%sstill pending",
171                                         still_pending,
172                                         still_pending > 1 ? "s " : " ");
173                         yppush_alarm_tripped = 0;
174                         alarm(YPPUSH_RESPONSE_TIMEOUT);
175                         pause();
176                         alarm(0);
177                         if (yppush_alarm_tripped == 1) {
178                                 yp_error("timed out");
179                                 now = 1;
180                         }
181                 } else {
182                         if (verbose)
183                                 yp_error("all transfers complete");
184                         break;
185                 }
186         }
187
188
189         /* All stats collected and reported -- kill all the stragglers. */
190         jptr = yppush_joblist;
191         while (jptr) {
192                 if (!jptr->polled)
193                         yp_error("warning: exiting with transfer \
194 to %s (transid = %lu) still pending", jptr->server, jptr->tid);
195                 svc_unregister(jptr->prognum, 1);
196                 jptr = jptr->next;
197         }
198
199         exit(0);
200 }
201
202 /*
203  * Handler for 'normal' signals.
204  */
205
206 static void handler(sig)
207         int sig;
208 {
209         if (sig == SIGTERM || sig == SIGINT || sig == SIGABRT) {
210                 yppush_jobs = 0;
211                 yppush_exit(1);
212         }
213
214         if (sig == SIGALRM) {
215                 alarm(0);
216                 yppush_alarm_tripped++;
217         }
218
219         return;
220 }
221
222 /*
223  * Dispatch loop for callback RPC services.
224  */
225 static void yppush_svc_run()
226 {
227 #ifdef FD_SETSIZE
228         fd_set readfds;
229 #else
230         int readfds;
231 #endif /* def FD_SETSIZE */
232         struct timeval timeout;
233
234         timeout.tv_usec = 0;
235         timeout.tv_sec = 5;
236
237 retry:
238 #ifdef FD_SETSIZE
239         readfds = svc_fdset;
240 #else
241         readfds = svc_fds;
242 #endif /* def FD_SETSIZE */
243         switch (select(_rpc_dtablesize(), &readfds, NULL, NULL, &timeout)) {
244         case -1:
245                 if (errno == EINTR)
246                         goto retry;
247                 yp_error("select failed: %s", strerror(errno));
248                 break;
249         case 0:
250                 yp_error("select() timed out");
251                 break;
252         default:
253                 svc_getreqset(&readfds);
254                 break;
255         }
256         return;
257 }
258
259 /*
260  * Special handler for asynchronous socket I/O. We mark the
261  * sockets of the callback handlers as O_ASYNC and handle SIGIO
262  * events here, which will occur when the callback handler has
263  * something interesting to tell us.
264  */
265 static void async_handler(sig)
266         int sig;
267 {
268         yppush_svc_run();
269
270         /* reset any pending alarms. */
271         alarm(0);
272         yppush_alarm_tripped++;
273         kill(getpid(), SIGALRM);
274         return;
275 }
276
277 /*
278  * RPC service routines for callbacks.
279  */
280 void *
281 yppushproc_null_1_svc(void *argp, struct svc_req *rqstp)
282 {
283         static char * result;
284         /* Do nothing -- RPC conventions call for all a null proc. */
285         return((void *) &result);
286 }
287
288 void *
289 yppushproc_xfrresp_1_svc(yppushresp_xfr *argp, struct svc_req *rqstp)
290 {
291         static char * result;
292         yppush_show_status(argp->status, argp->transid);
293         return((void *) &result);
294 }
295
296 /*
297  * Transmit a YPPROC_XFR request to ypserv.
298  */
299 static int yppush_send_xfr(job)
300         struct jobs *job;
301 {
302         ypreq_xfr req;
303 /*      ypresp_xfr *resp; */
304         DBT key, data;
305         CLIENT *clnt;
306         struct rpc_err err;
307         struct timeval timeout;
308
309         timeout.tv_usec = 0;
310         timeout.tv_sec = 0;
311
312         /*
313          * The ypreq_xfr structure has a member of type map_parms,
314          * which seems to require the order number of the map.
315          * It isn't actually used at the other end (at least the
316          * FreeBSD ypserv doesn't use it) but we fill it in here
317          * for the sake of completeness.
318          */
319         key.data = "YP_LAST_MODIFIED";
320         key.size = sizeof ("YP_LAST_MODIFIED") - 1;
321
322         if (yp_get_record(yppush_domain, yppush_mapname, &key, &data,
323                           1) != YP_TRUE) {
324                 yp_error("failed to read order number from %s: %s: %s",
325                           yppush_mapname, yperr_string(yp_errno),
326                           strerror(errno));
327                 return(1);
328         }
329
330         /* Fill in the request arguments */
331         req.map_parms.ordernum = atoi(data.data);
332         req.map_parms.domain = yppush_domain;
333         req.map_parms.peer = yppush_master;
334         req.map_parms.map = job->map;
335         req.transid = job->tid;
336         req.prog = job->prognum;
337         req.port = job->port;
338
339         /* Get a handle to the remote ypserv. */
340         if ((clnt = clnt_create(job->server, YPPROG, YPVERS, "udp")) == NULL) {
341                 yp_error("%s: %s",job->server,clnt_spcreateerror("couldn't \
342 create udp handle to NIS server"));
343                 switch (rpc_createerr.cf_stat) {
344                         case RPC_UNKNOWNHOST:
345                                 job->stat = YPPUSH_NOHOST;
346                                 break;
347                         case RPC_PMAPFAILURE:
348                                 job->stat = YPPUSH_PMAP;
349                                 break;
350                         default:
351                                 job->stat = YPPUSH_RPC;
352                                 break;
353                         }
354                 return(1);
355         }
356
357         /*
358          * Reduce timeout to nothing since we may not
359          * get a response from ypserv and we don't want to block.
360          */
361         if (clnt_control(clnt, CLSET_TIMEOUT, (char *)&timeout) == FALSE)
362                 yp_error("failed to set timeout on ypproc_xfr call");
363
364         /* Invoke the ypproc_xfr service. */
365         if (ypproc_xfr_2(&req, clnt) == NULL) {
366                 clnt_geterr(clnt, &err);
367                 if (err.re_status != RPC_SUCCESS &&
368                     err.re_status != RPC_TIMEDOUT) {
369                         yp_error("%s: %s", job->server, clnt_sperror(clnt,
370                                                         "yp_xfr failed"));
371                         job->stat = YPPUSH_YPSERV;
372                         clnt_destroy(clnt);
373                         return(1);
374                 }
375         }
376
377         clnt_destroy(clnt);
378
379         return(0);
380 }
381
382 /*
383  * Main driver function. Register the callback service, add the transfer
384  * request to the internal list, send the YPPROC_XFR request to ypserv
385  * do other magic things.
386  */
387 int yp_push(server, map, tid)
388         char *server;
389         char *map;
390         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_sock; /*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_sock, F_SETOWN, getpid()) == -1 ||
441             fcntl(xprt->xp_sock, 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 yppush_foreach(status, key, keylen, val, vallen, data)
474         int status;
475         char *key;
476         int keylen;
477         char *val;
478         int vallen;
479         char *data;
480 {
481         char server[YPMAXRECORD + 2];
482
483         if (status != YP_TRUE)
484                 return (status);
485
486         snprintf(server, sizeof(server), "%.*s", vallen, val);
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
521 static 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  */
532 int
533 main(argc,argv)
534         int argc;
535         char *argv[];
536 {
537         int ch;
538         DBT key, data;
539         char myname[MAXHOSTNAMELEN];
540         struct hostlist {
541                 char *name;
542                 struct hostlist *next;
543         };
544         struct hostlist *yppush_hostlist = NULL;
545         struct hostlist *tmp;
546         struct sigaction sa;
547
548         while ((ch = getopt(argc, argv, "d:j:p:h:t:v")) != -1) {
549                 switch (ch) {
550                 case 'd':
551                         yppush_domain = optarg;
552                         break;
553                 case 'j':
554                         yppush_jobs = atoi(optarg);
555                         if (yppush_jobs <= 0)
556                                 yppush_jobs = 1;
557                         break;
558                 case 'p':
559                         yp_dir = optarg;
560                         break;
561                 case 'h': /* we can handle multiple hosts */
562                         if ((tmp = (struct hostlist *)malloc(sizeof(struct hostlist))) == NULL) {
563                                 yp_error("malloc failed");
564                                 yppush_exit(1);
565                         }
566                         tmp->name = strdup(optarg);
567                         tmp->next = yppush_hostlist;
568                         yppush_hostlist = tmp;
569                         break;
570                 case 't':
571                         yppush_timeout = atoi(optarg);
572                         break;
573                 case 'v':
574                         verbose++;
575                         break;
576                 default:
577                         usage();
578                         break;
579                 }
580         }
581
582         argc -= optind;
583         argv += optind;
584
585         yppush_mapname = argv[0];
586
587         if (yppush_mapname == NULL) {
588         /* "No guts, no glory." */
589                 usage();
590         }
591
592         /*
593          * If no domain was specified, try to find the default
594          * domain. If we can't find that, we're doomed and must bail.
595          */
596         if (yppush_domain == NULL) {
597                 char *yppush_check_domain;
598                 if (!yp_get_default_domain(&yppush_check_domain) &&
599                         !_yp_check(&yppush_check_domain)) {
600                         yp_error("no domain specified and NIS not running");
601                         usage();
602                 } else
603                         yp_get_default_domain(&yppush_domain);
604         }
605
606         /* Check to see that we are the master for this map. */
607
608         if (gethostname ((char *)&myname, sizeof(myname))) {
609                 yp_error("failed to get name of local host: %s",
610                         strerror(errno));
611                 yppush_exit(1);
612         }
613
614         key.data = "YP_MASTER_NAME";
615         key.size = sizeof("YP_MASTER_NAME") - 1;
616
617         if (yp_get_record(yppush_domain, yppush_mapname,
618                           &key, &data, 1) != YP_TRUE) {
619                 yp_error("couldn't open %s map: %s", yppush_mapname,
620                          strerror(errno));
621                 yppush_exit(1);
622         }
623
624         if (strncmp(myname, data.data, data.size)) {
625                 yp_error("warning: this host is not the master for %s",
626                                                         yppush_mapname);
627 #ifdef NITPICKY
628                 yppush_exit(1);
629 #endif
630         }
631
632         yppush_master = malloc(data.size + 1);
633         strncpy(yppush_master, data.data, data.size);
634         yppush_master[data.size] = '\0';
635
636         /* Install some handy handlers. */
637         signal(SIGALRM, handler);
638         signal(SIGTERM, handler);
639         signal(SIGINT, handler);
640         signal(SIGABRT, handler);
641
642         /*
643          * Set up the SIGIO handler. Make sure that some of the
644          * other signals are blocked while the handler is running so
645          * select() doesn't get interrupted.
646          */
647         sigemptyset(&sa.sa_mask);
648         sigaddset(&sa.sa_mask, SIGIO); /* Goes without saying. */
649         sigaddset(&sa.sa_mask, SIGPIPE);
650         sigaddset(&sa.sa_mask, SIGCHLD);
651         sigaddset(&sa.sa_mask, SIGALRM);
652         sigaddset(&sa.sa_mask, SIGINT);
653         sa.sa_handler = async_handler;
654         sa.sa_flags = 0;
655
656         sigaction(SIGIO, &sa, NULL);
657
658         /* set initial transaction ID */
659         yppush_transid = time((time_t *)NULL);
660
661         if (yppush_hostlist) {
662         /*
663          * Host list was specified on the command line:
664          * kick off the transfers by hand.
665          */
666                 tmp = yppush_hostlist;
667                 while (tmp) {
668                         yppush_foreach(YP_TRUE, NULL, 0, tmp->name,
669                                                         strlen(tmp->name));
670                         tmp = tmp->next;
671                 }
672         } else {
673         /*
674          * Do a yp_all() on the ypservers map and initiate a ypxfr
675          * for each one.
676          */
677                 ypxfr_get_map("ypservers", yppush_domain,
678                               "localhost", yppush_foreach);
679         }
680
681         if (verbose > 1)
682                 yp_error("all jobs dispatched");
683
684         /* All done -- normal exit. */
685         yppush_exit(0);
686
687         /* Just in case. */
688         exit(0);
689 }