yppush_main.c revision 114601
1/*
2 * Copyright (c) 1995
3 *	Bill Paul <wpaul@ctr.columbia.edu>.  All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 * 3. All advertising materials mentioning features or use of this software
14 *    must display the following acknowledgement:
15 *	This product includes software developed by Bill Paul.
16 * 4. Neither the name of the author nor the names of any co-contributors
17 *    may be used to endorse or promote products derived from this software
18 *    without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND
21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED.  IN NO EVENT SHALL Bill Paul OR CONTRIBUTORS BE LIABLE
24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 * SUCH DAMAGE.
31 */
32
33#include <sys/cdefs.h>
34__FBSDID("$FreeBSD: head/usr.sbin/yppush/yppush_main.c 114601 2003-05-03 21:06:42Z obrien $");
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>
51struct dom_binding {};
52#include <rpcsvc/ypclnt.h>
53#include "ypxfr_extern.h"
54#include "yppush_extern.h"
55
56char *progname = "yppush";
57int debug = 1;
58int _rpcpmstart = 0;
59char *yp_dir = _PATH_YP;
60
61char *yppush_mapname = NULL;	/* Map to transfer. */
62char *yppush_domain = NULL;	/* Domain in which map resides. */
63char *yppush_master = NULL;	/* Master NIS server for said domain. */
64int verbose = 0;		/* Toggle verbose mode. */
65unsigned long yppush_transid = 0;
66int yppush_timeout = 80;	/* Default timeout. */
67int yppush_jobs = 0;		/* Number of allowed concurrent jobs. */
68int yppush_running_jobs = 0;	/* Number of currently running jobs. */
69int yppush_alarm_tripped = 0;
70
71/* Structure for holding information about a running job. */
72struct 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
84struct jobs *yppush_joblist;	/* Linked list of running jobs. */
85
86/*
87 * Local error messages.
88 */
89static const char *
90yppusherr_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 */
109static int
110yppush_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. */
147static void
148yppush_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 \
196to %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
208static void
209handler(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 */
227static void
228yppush_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
240retry:
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 */
268static void
269async_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 */
283void *
284yppushproc_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
291void *
292yppushproc_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 */
302static int
303yppush_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 \
345create 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 */
390int
391yp_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 */
474int
475yppush_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
518static 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 */
529int
530main(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}
685