yppush_main.c revision 13395
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.24 1996/01/12 06:21:52 wpaul Exp wpaul $
33 */
34
35#include <stdio.h>
36#include <stdlib.h>
37#include <unistd.h>
38#include <string.h>
39#include <signal.h>
40#include <setjmp.h>
41#include <time.h>
42#include <errno.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
56#ifndef lint
57static const char rcsid[] = "$Id: yppush_main.c,v 1.24 1996/01/12 06:21:52 wpaul Exp wpaul $";
58#endif
59
60char *progname = "yppush";
61int debug = 1;
62int _rpcpmstart = 0;
63char *yp_dir = _PATH_YP;
64
65char *yppush_mapname = NULL;	/* Map to transfer. */
66char *yppush_domain = NULL;	/* Domain in which map resides. */
67char *yppush_master = NULL;	/* Master NIS server for said domain. */
68int verbose = 0;		/* Toggle verbose mode. */
69unsigned long yppush_transid = 0;
70int yppush_timeout = 80;	/* Default timeout. */
71int yppush_jobs = 0;		/* Number of allowed concurrent jobs. */
72int yppush_running_jobs = 0;	/* Number of currently running jobs. */
73int yppush_pausing = 0;		/* Flag set when longjmp()s are allowed. */
74jmp_buf env;
75
76/* Structure for holding information about a running job. */
77struct jobs {
78	unsigned long tid;
79	int pid;
80	int sock;
81	int port;
82	ypxfrstat stat;
83	unsigned long prognum;
84	char *server;
85	char *map;
86	int polled;
87	struct jobs *next;
88};
89
90struct jobs *yppush_joblist;	/* Linked list of running jobs. */
91
92/*
93 * Local error messages.
94 */
95static char *yppusherr_string(err)
96	int err;
97{
98	switch(err) {
99	case YPPUSH_TIMEDOUT: return("transfer or callback timed out");
100	case YPPUSH_YPSERV:   return("failed to contact ypserv");
101	case YPPUSH_NOHOST:   return("no such host");
102	case YPPUSH_PMAP:     return("portmapper failure");
103	default:              return("unknown error code");
104	}
105}
106
107/*
108 * Report state of a job.
109 */
110static int yppush_show_status(status, tid)
111	ypxfrstat status;
112	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 yppush_exit(now)
150	int now;
151{
152	struct jobs *jptr;
153	int still_pending = 1234;
154
155	/* Let all the information trickle in. */
156	while(!now && still_pending) {
157		yppush_pausing++;
158		setjmp(env);	/* more magic */
159		jptr = yppush_joblist;
160		still_pending = 0;
161		while (jptr) {
162			if (jptr->polled == 0) {
163				still_pending++;
164				if (verbose > 1)
165					yp_error("%s has not responded",
166						  jptr->server);
167			} else {
168				if (verbose > 1)
169					yp_error("%s has responded",
170						  jptr->server);
171			}
172			jptr = jptr->next;
173		}
174		if (still_pending) {
175			if (verbose > 1)
176				yp_error("%d transfer%sstill pending",
177					still_pending,
178					still_pending > 1 ? "s " : " ");
179			alarm(YPPUSH_RESPONSE_TIMEOUT);
180			pause();
181			yppush_pausing = 0;
182			alarm(0);
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 handler(sig)
209	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	}
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	kill(getpid(), SIGALRM);
274	if (yppush_pausing)
275		longjmp(env, 1);
276	return;
277}
278
279/*
280 * RPC service routines for callback listener process
281 */
282void *
283yppushproc_null_1_svc(void *argp, struct svc_req *rqstp)
284{
285	static char * result;
286	/* Do nothing -- RPC conventions call for all a null proc. */
287	return((void *) &result);
288}
289
290void *
291yppushproc_xfrresp_1_svc(yppushresp_xfr *argp, struct svc_req *rqstp)
292{
293	static char * result;
294	yppush_show_status(argp->status, argp->transid);
295	return((void *) &result);
296}
297
298/*
299 * Transmit a YPPROC_XFR request to ypserv.
300 */
301static int yppush_send_xfr(job)
302	struct jobs *job;
303{
304	ypreq_xfr req;
305/*	ypresp_xfr *resp; */
306	DBT key, data;
307	CLIENT *clnt;
308	struct rpc_err err;
309	struct timeval timeout;
310
311	timeout.tv_usec = 0;
312	timeout.tv_sec = 0;
313
314	/*
315	 * The ypreq_xfr structure has a member of type map_parms,
316	 * which seems to require the order number of the map.
317	 * It isn't actually used at the other end (at least the
318	 * FreeBSD ypserv doesn't use it) but we fill it in here
319	 * for the sake of completeness.
320	 */
321	key.data = "YP_LAST_MODIFIED";
322	key.size = sizeof ("YP_LAST_MODIFIED") - 1;
323
324	if (yp_get_record(yppush_domain, yppush_mapname, &key, &data,
325			  1) != YP_TRUE) {
326		yp_error("failed to read order number from %s: %s: %s",
327			  yppush_mapname, yperr_string(yp_errno),
328			  strerror(errno));
329		return(1);
330	}
331
332	/* Fill in the request arguments */
333	req.map_parms.ordernum = atoi(data.data);
334	req.map_parms.domain = yppush_domain;
335	req.map_parms.peer = yppush_master;
336	req.map_parms.map = job->map;
337	req.transid = job->tid;
338	req.prog = job->prognum;
339	req.port = job->port;
340
341	/* Get a handle to the remote ypserv. */
342	if ((clnt = clnt_create(job->server, YPPROG, YPVERS, "udp")) == NULL) {
343		yp_error("%s: %s",job->server,clnt_spcreateerror("couldn't \
344create udp handle to NIS server"));
345		switch(rpc_createerr.cf_stat) {
346			case RPC_UNKNOWNHOST:
347				job->stat = YPPUSH_NOHOST;
348				break;
349			case RPC_PMAPFAILURE:
350				job->stat = YPPUSH_PMAP;
351				break;
352			default:
353				job->stat = YPPUSH_RPC;
354				break;
355			}
356		return(1);
357	}
358
359	/*
360	 * Reduce timeout to nothing since we may not
361	 * get a response from ypserv and we don't want to block.
362	 */
363	if (clnt_control(clnt, CLSET_TIMEOUT, (char *)&timeout) == FALSE)
364		yp_error("failed to set timeout on ypproc_xfr call");
365
366	/* Invoke the ypproc_xfr service. */
367	if (ypproc_xfr_2(&req, clnt) == NULL) {
368		clnt_geterr(clnt, &err);
369		if (err.re_status != RPC_SUCCESS &&
370		    err.re_status != RPC_TIMEDOUT) {
371			yp_error("%s: %s", job->server, clnt_sperror(clnt,
372							"yp_xfr failed"));
373			job->stat = YPPUSH_YPSERV;
374			clnt_destroy(clnt);
375			return(1);
376		}
377	}
378
379	clnt_destroy(clnt);
380
381	return(0);
382}
383
384/*
385 * Main driver function. Register the callback service, add the transfer
386 * request to the internal list, send the YPPROC_XFR request to ypserv
387 * do other magic things.
388 */
389int yp_push(server, map, tid)
390	char *server;
391	char *map;
392	unsigned long tid;
393{
394	unsigned long prognum;
395	int sock = RPC_ANYSOCK;
396	SVCXPRT *xprt;
397	struct jobs *job;
398
399	/*
400	 * Register the callback service on the first free
401	 * transient program number.
402	 */
403	xprt = svcudp_create(sock);
404	for (prognum = 0x4000000; prognum < 0x5FFFFFF; prognum++) {
405		if (svc_register(xprt, prognum, 1,
406		    yppush_xfrrespprog_1, IPPROTO_UDP) == TRUE)
407			break;
408	}
409
410	/* Register the job in our linked list of jobs. */
411	if ((job = (struct jobs *)malloc(sizeof (struct jobs))) == NULL) {
412		yp_error("malloc failed: %s", strerror(errno));
413		yppush_exit(1);
414	}
415
416	/* Initialize the info for this job. */
417	job->stat = 0;
418	job->tid = tid;
419	job->port = xprt->xp_port;
420	job->sock = xprt->xp_sock; /*XXX: Evil!! EEEEEEEVIL!!! */
421	job->server = strdup(server);
422	job->map = strdup(map);
423	job->prognum = prognum;
424	job->polled = 0;
425	job->next = yppush_joblist;
426	yppush_joblist = job;
427
428	/*
429	 * Set the RPC sockets to asynchronous mode. This will
430	 * cause the system to smack us with a SIGIO when an RPC
431	 * callback is delivered. This in turn allows us to handle
432	 * the callback even though we may be in the middle of doing
433	 * something else at the time.
434	 *
435	 * XXX This is a horrible thing to do for two reasons,
436	 * both of which have to do with portability:
437	 * 1) We really ought not to be sticking our grubby mits
438	 *    into the RPC service transport handle like this.
439	 * 2) Even in this day and age, there are still some *NIXes
440	 *    that don't support async socket I/O.
441	 */
442	if (fcntl(xprt->xp_sock, F_SETOWN, getpid()) == -1 ||
443	    fcntl(xprt->xp_sock, F_SETFL, O_ASYNC) == -1) {
444		yp_error("failed to set async I/O mode: %s",
445			 strerror(errno));
446		yppush_exit(1);
447	}
448
449	if (verbose) {
450		yp_error("initiating transfer: %s -> %s (transid = %lu)",
451			yppush_mapname, server, tid);
452	}
453
454	/*
455	 * Send the XFR request to ypserv. We don't have to wait for
456	 * a response here since we can handle them asynchronously.
457	 */
458
459	if (yppush_send_xfr(job)){
460		/* Transfer request blew up. */
461		yppush_show_status(job->stat ? job->stat :
462			YPPUSH_YPSERV,job->tid);
463	} else {
464		if (verbose > 1)
465			yp_error("%s has been called", server);
466	}
467
468	return(0);
469}
470
471/*
472 * Called for each entry in the ypservers map from yp_get_map(), which
473 * is our private yp_all() routine.
474 */
475int yppush_foreach(status, key, keylen, val, vallen, data)
476	int status;
477	char *key;
478	int keylen;
479	char *val;
480	int vallen;
481	char *data;
482{
483	char server[YPMAXRECORD + 2];
484
485	if (status != YP_TRUE)
486		return (status);
487
488	snprintf(server, sizeof(server), "%.*s", vallen, val);
489
490	/*
491	 * Restrict the number of concurrent jobs. If yppush_jobs number
492	 * of jobs have already been dispatched and are still pending,
493	 * wait for one of them to finish so we can reuse its slot.
494	 */
495	if (yppush_jobs <= 1) {
496		yppush_pausing++;
497		while (!setjmp(env) && yppush_running_jobs) {
498			alarm(yppush_timeout);
499			pause();
500			alarm(0);
501		}
502		yppush_pausing = 0;
503	} else {
504		yppush_pausing++;
505		while (!setjmp(env) && yppush_running_jobs >= yppush_jobs) {
506			alarm(yppush_timeout);
507			pause();
508			alarm(0);
509		}
510		yppush_pausing = 0;
511	}
512
513	/* Cleared for takeoff: set everything in motion. */
514	if (yp_push(&server, yppush_mapname, yppush_transid))
515		return(yp_errno);
516
517	/* Bump the job counter and transaction ID. */
518	yppush_running_jobs++;
519	yppush_transid++;
520	return (0);
521}
522
523static void usage()
524{
525	fprintf (stderr, "%s: [-d domain] [-t timeout] [-j #parallel jobs] \
526[-h host] [-p path] mapname\n", progname);
527	exit(1);
528}
529
530/*
531 * Entry point. (About time!)
532 */
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")) != EOF) {
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: %s", strerror(errno));
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("this host is not the master for %s",yppush_mapname);
626		yppush_exit(1);
627	}
628
629	yppush_master = strdup(data.data);
630	yppush_master[data.size] = '\0';
631
632	/* Install some handy handlers. */
633	signal(SIGALRM, handler);
634	signal(SIGTERM, handler);
635	signal(SIGINT, handler);
636	signal(SIGABRT, handler);
637
638	/*
639	 * Set up the SIGIO handler. Make sure that some of the
640	 * other signals are blocked while the handler is running so
641	 * select() doesn't get interrupted.
642	 */
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
650	sigaction(SIGIO, &sa, NULL);
651
652	/* set initial transaction ID */
653	time(&yppush_transid);
654
655	if (yppush_hostlist) {
656	/*
657	 * Host list was specified on the command line:
658	 * kick off the transfers by hand.
659	 */
660		tmp = yppush_hostlist;
661		while(tmp) {
662			yppush_foreach(YP_TRUE, NULL, 0, tmp->name,
663							strlen(tmp->name));
664			tmp = tmp->next;
665		}
666	} else {
667	/*
668	 * Do a yp_all() on the ypservers map and initiate a ypxfr
669	 * for each one.
670	 */
671		ypxfr_get_map("ypservers", yppush_domain,
672			      "localhost", yppush_foreach);
673	}
674
675	if (verbose > 1)
676		yp_error("all jobs dispatched");
677
678	/* All done -- normal exit. */
679	yppush_exit(0);
680
681	/* Just in case. */
682	exit(0);
683}
684