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