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