yppush_main.c revision 94887
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
34static const char rcsid[] =
35  "$FreeBSD: head/usr.sbin/yppush/yppush_main.c 94887 2002-04-16 22:08:35Z des $";
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>
53struct dom_binding {};
54#include <rpcsvc/ypclnt.h>
55#include "ypxfr_extern.h"
56#include "yppush_extern.h"
57
58char *progname = "yppush";
59int debug = 1;
60int _rpcpmstart = 0;
61char *yp_dir = _PATH_YP;
62
63char *yppush_mapname = NULL;	/* Map to transfer. */
64char *yppush_domain = NULL;	/* Domain in which map resides. */
65char *yppush_master = NULL;	/* Master NIS server for said domain. */
66int verbose = 0;		/* Toggle verbose mode. */
67unsigned long yppush_transid = 0;
68int yppush_timeout = 80;	/* Default timeout. */
69int yppush_jobs = 0;		/* Number of allowed concurrent jobs. */
70int yppush_running_jobs = 0;	/* Number of currently running jobs. */
71int yppush_alarm_tripped = 0;
72
73/* Structure for holding information about a running job. */
74struct 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
86struct jobs *yppush_joblist;	/* Linked list of running jobs. */
87
88/*
89 * Local error messages.
90 */
91static const char *
92yppusherr_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 */
111static int
112yppush_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. */
149static void
150yppush_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 \
198to %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
210static void
211handler(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 */
229static void
230yppush_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
242retry:
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 */
270static void
271async_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 */
285void *
286yppushproc_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
293void *
294yppushproc_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 */
304static int
305yppush_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 \
347create 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 */
392int
393yp_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 */
476int
477yppush_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
520static 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 */
531int
532main(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}
687