yppush_main.c revision 50479
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 50479 1999-08-28 01:35:59Z peter $";
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 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 */
106static 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. */
145static 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 \
194to %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
206static 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 */
225static 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
237retry:
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 */
265static 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 */
280void *
281yppushproc_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
288void *
289yppushproc_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 */
299static 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 \
342create 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 */
387int 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 */
473int 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
521static 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 */
532int
533main(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
655	sigaction(SIGIO, &sa, NULL);
656
657	/* set initial transaction ID */
658	yppush_transid = time((time_t *)NULL);
659
660	if (yppush_hostlist) {
661	/*
662	 * Host list was specified on the command line:
663	 * kick off the transfers by hand.
664	 */
665		tmp = yppush_hostlist;
666		while(tmp) {
667			yppush_foreach(YP_TRUE, NULL, 0, tmp->name,
668							strlen(tmp->name));
669			tmp = tmp->next;
670		}
671	} else {
672	/*
673	 * Do a yp_all() on the ypservers map and initiate a ypxfr
674	 * for each one.
675	 */
676		ypxfr_get_map("ypservers", yppush_domain,
677			      "localhost", yppush_foreach);
678	}
679
680	if (verbose > 1)
681		yp_error("all jobs dispatched");
682
683	/* All done -- normal exit. */
684	yppush_exit(0);
685
686	/* Just in case. */
687	exit(0);
688}
689