slave.c revision 86644
1/*-
2 * Copyright (c) 1985, 1993
3 *	The Regents of the University of California.  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 the University of
16 *	California, Berkeley and its contributors.
17 * 4. Neither the name of the University nor the names of its contributors
18 *    may be used to endorse or promote products derived from this software
19 *    without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33
34#ifndef lint
35#if 0
36static char sccsid[] = "@(#)slave.c	8.1 (Berkeley) 6/6/93";
37#endif
38static const char rcsid[] =
39  "$FreeBSD: head/usr.sbin/timed/timed/slave.c 86644 2001-11-20 06:36:09Z jhb $";
40#endif /* not lint */
41
42#include "globals.h"
43#include <setjmp.h>
44#include "pathnames.h"
45
46extern jmp_buf jmpenv;
47extern int Mflag;
48extern int justquit;
49
50extern u_short sequence;
51
52static char master_name[MAXHOSTNAMELEN];
53static struct netinfo *old_slavenet;
54static int old_status;
55
56static void schgdate __P((struct tsp *, char *));
57static void setmaster __P((struct tsp *));
58static void answerdelay __P((void));
59
60#ifdef sgi
61extern void logwtmp __P((struct timeval *, struct timeval *));
62#else
63extern void logwtmp __P((char *, char *, char *));
64#endif /* sgi */
65
66int
67slave()
68{
69	int tries;
70	long electiontime, refusetime, looktime, looptime, adjtime;
71	u_short seq;
72	long fastelection;
73#define FASTTOUT 3
74	struct in_addr cadr;
75	struct timeval otime;
76	struct sockaddr_in taddr;
77	char tname[MAXHOSTNAMELEN];
78	struct tsp *msg, to;
79	struct timeval ntime, wait, tmptv;
80	time_t tsp_time_sec;
81	struct tsp *answer;
82	int timeout();
83	char olddate[32];
84	char newdate[32];
85	struct netinfo *ntp;
86	struct hosttbl *htp;
87
88
89	old_slavenet = 0;
90	seq = 0;
91	refusetime = 0;
92	adjtime = 0;
93
94	(void)gettimeofday(&ntime, 0);
95	electiontime = ntime.tv_sec + delay2;
96	fastelection = ntime.tv_sec + FASTTOUT;
97	if (justquit)
98		looktime = electiontime;
99	else
100		looktime = fastelection;
101	looptime = fastelection;
102
103	if (slavenet)
104		xmit(TSP_SLAVEUP, 0, &slavenet->dest_addr);
105	if (status & MASTER) {
106		for (ntp = nettab; ntp != NULL; ntp = ntp->next) {
107			if (ntp->status == MASTER)
108				masterup(ntp);
109		}
110	}
111
112loop:
113	get_goodgroup(0);
114	(void)gettimeofday(&ntime, (struct timezone *)0);
115	if (ntime.tv_sec > electiontime) {
116		if (trace)
117			fprintf(fd, "election timer expired\n");
118		longjmp(jmpenv, 1);
119	}
120
121	if (ntime.tv_sec >= looktime) {
122		if (trace)
123			fprintf(fd, "Looking for nets to master\n");
124
125		if (Mflag && nignorednets > 0) {
126			for (ntp = nettab; ntp != NULL; ntp = ntp->next) {
127				if (ntp->status == IGNORE
128				    || ntp->status == NOMASTER) {
129					lookformaster(ntp);
130					if (ntp->status == MASTER) {
131						masterup(ntp);
132					} else if (ntp->status == MASTER) {
133						ntp->status = NOMASTER;
134					}
135				}
136				if (ntp->status == MASTER
137				    && --ntp->quit_count < 0)
138					ntp->quit_count = 0;
139			}
140			makeslave(slavenet);	/* prune extras */
141			setstatus();
142		}
143		(void)gettimeofday(&ntime, 0);
144		looktime = ntime.tv_sec + delay2;
145	}
146	if (ntime.tv_sec >= looptime) {
147		if (trace)
148			fprintf(fd, "Looking for loops\n");
149		for (ntp = nettab; ntp != NULL; ntp = ntp->next) {
150		    if (ntp->status == MASTER) {
151			to.tsp_type = TSP_LOOP;
152			to.tsp_vers = TSPVERSION;
153			to.tsp_seq = sequence++;
154			to.tsp_hopcnt = MAX_HOPCNT;
155			(void)strcpy(to.tsp_name, hostname);
156			bytenetorder(&to);
157			if (sendto(sock, (char *)&to, sizeof(struct tsp), 0,
158				   (struct sockaddr*)&ntp->dest_addr,
159				   sizeof(ntp->dest_addr)) < 0) {
160				trace_sendto_err(ntp->dest_addr.sin_addr);
161			}
162		    }
163		}
164		(void)gettimeofday(&ntime, 0);
165		looptime = ntime.tv_sec + delay2;
166	}
167
168	wait.tv_sec = min(electiontime,min(looktime,looptime)) - ntime.tv_sec;
169	if (wait.tv_sec < 0)
170		wait.tv_sec = 0;
171	wait.tv_sec += FASTTOUT;
172	wait.tv_usec = 0;
173	msg = readmsg(TSP_ANY, ANYADDR, &wait, 0);
174
175	if (msg != NULL) {
176		/*
177		 * filter stuff not for us
178		 */
179		switch (msg->tsp_type) {
180		case TSP_SETDATE:
181		case TSP_TRACEOFF:
182		case TSP_TRACEON:
183			/*
184			 * XXX check to see they are from ourself
185			 */
186			break;
187
188		case TSP_TEST:
189		case TSP_MSITE:
190			break;
191
192		case TSP_MASTERUP:
193			if (!fromnet) {
194				if (trace) {
195					fprintf(fd, "slave ignored: ");
196					print(msg, &from);
197				}
198				goto loop;
199			}
200			break;
201
202		default:
203			if (!fromnet
204			    || fromnet->status == IGNORE
205			    || fromnet->status == NOMASTER) {
206				if (trace) {
207					fprintf(fd, "slave ignored: ");
208					print(msg, &from);
209				}
210				goto loop;
211			}
212			break;
213		}
214
215
216		/*
217		 * now process the message
218		 */
219		switch (msg->tsp_type) {
220
221		case TSP_ADJTIME:
222			if (fromnet != slavenet)
223				break;
224			if (!good_host_name(msg->tsp_name)) {
225				syslog(LOG_NOTICE,
226				   "attempted time adjustment by %s",
227				       msg->tsp_name);
228				suppress(&from, msg->tsp_name, fromnet);
229				break;
230			}
231			/*
232			 * Speed up loop detection in case we have a loop.
233			 * Otherwise the clocks can race until the loop
234			 * is found.
235			 */
236			(void)gettimeofday(&otime, 0);
237			if (adjtime < otime.tv_sec)
238				looptime -= (looptime-otime.tv_sec)/2 + 1;
239
240			setmaster(msg);
241			if (seq != msg->tsp_seq) {
242				seq = msg->tsp_seq;
243				synch(tvtomsround(msg->tsp_time));
244			}
245			(void)gettimeofday(&ntime, 0);
246			electiontime = ntime.tv_sec + delay2;
247			fastelection = ntime.tv_sec + FASTTOUT;
248			adjtime = ntime.tv_sec + SAMPLEINTVL*2;
249			break;
250
251		case TSP_SETTIME:
252			if (fromnet != slavenet)
253				break;
254			if (seq == msg->tsp_seq)
255				break;
256			seq = msg->tsp_seq;
257
258			/* adjust time for residence on the queue */
259			(void)gettimeofday(&otime, 0);
260			adj_msg_time(msg,&otime);
261#ifdef sgi
262			(void)cftime(newdate, "%D %T", &msg->tsp_time.tv_sec);
263			(void)cftime(olddate, "%D %T", &otime.tv_sec);
264#else
265			/*
266			 * the following line is necessary due to syslog
267			 * calling ctime() which clobbers the static buffer
268			 */
269			(void)strcpy(olddate, date());
270			tsp_time_sec = msg->tsp_time.tv_sec;
271			(void)strcpy(newdate, ctime(&tsp_time_sec));
272#endif /* sgi */
273
274			if (!good_host_name(msg->tsp_name)) {
275				syslog(LOG_NOTICE,
276			    "attempted time setting by untrusted %s to %s",
277				       msg->tsp_name, newdate);
278				suppress(&from, msg->tsp_name, fromnet);
279				break;
280			}
281
282			setmaster(msg);
283 			tmptv.tv_sec = msg->tsp_time.tv_sec;
284 			tmptv.tv_usec = msg->tsp_time.tv_usec;
285			timevalsub(&ntime, &tmptv, &otime);
286			if (ntime.tv_sec < MAXADJ && ntime.tv_sec > -MAXADJ) {
287				/*
288				 * do not change the clock if we can adjust it
289				 */
290				synch(tvtomsround(ntime));
291			} else {
292#ifdef sgi
293				if (0 > settimeofday(&msg->tsp_time, 0)) {
294					syslog(LOG_ERR,"settimeofdate(): %m");
295					break;
296				}
297				logwtmp(&otime, &msg->tsp_time);
298#else
299				logwtmp("|", "date", "");
300 				(void)settimeofday(&tmptv, 0);
301				logwtmp("{", "date", "");
302#endif /* sgi */
303				syslog(LOG_NOTICE,
304				       "date changed by %s from %s",
305					msg->tsp_name, olddate);
306				if (status & MASTER)
307					spreadtime();
308			}
309			(void)gettimeofday(&ntime, 0);
310			electiontime = ntime.tv_sec + delay2;
311			fastelection = ntime.tv_sec + FASTTOUT;
312
313/* This patches a bad protocol bug.  Imagine a system with several networks,
314 * where there are a pair of redundant gateways between a pair of networks,
315 * each running timed.  Assume that we start with a third machine mastering
316 * one of the networks, and one of the gateways mastering the other.
317 * Imagine that the third machine goes away and the non-master gateway
318 * decides to replace it.  If things are timed just 'right,' we will have
319 * each gateway mastering one network for a little while.  If a SETTIME
320 * message gets into the network at that time, perhaps from the newly
321 * masterful gateway as it was taking control, the SETTIME will loop
322 * forever.  Each time a gateway receives it on its slave side, it will
323 * call spreadtime to forward it on its mastered network.  We are now in
324 * a permanent loop, since the SETTIME msgs will keep any clock
325 * in the network from advancing.  Normally, the 'LOOP' stuff will detect
326 * and correct the situation.  However, with the clocks stopped, the
327 * 'looptime' timer cannot expire.  While they are in this state, the
328 * masters will try to saturate the network with SETTIME packets.
329 */
330			looptime = ntime.tv_sec + (looptime-otime.tv_sec)/2-1;
331			break;
332
333		case TSP_MASTERUP:
334			if (slavenet && fromnet != slavenet)
335				break;
336			if (!good_host_name(msg->tsp_name)) {
337				suppress(&from, msg->tsp_name, fromnet);
338				if (electiontime > fastelection)
339					electiontime = fastelection;
340				break;
341			}
342			makeslave(fromnet);
343			setmaster(msg);
344			setstatus();
345			answerdelay();
346			xmit(TSP_SLAVEUP, 0, &from);
347			(void)gettimeofday(&ntime, 0);
348			electiontime = ntime.tv_sec + delay2;
349			fastelection = ntime.tv_sec + FASTTOUT;
350			refusetime = 0;
351			break;
352
353		case TSP_MASTERREQ:
354			if (fromnet->status != SLAVE)
355				break;
356			(void)gettimeofday(&ntime, 0);
357			electiontime = ntime.tv_sec + delay2;
358			break;
359
360		case TSP_SETDATE:
361#ifdef sgi
362			(void)cftime(newdate, "%D %T", &msg->tsp_time.tv_sec);
363#else
364			tsp_time_sec = msg->tsp_time.tv_sec;
365			(void)strcpy(newdate, ctime(&tsp_time_sec));
366#endif /* sgi */
367			schgdate(msg, newdate);
368			break;
369
370		case TSP_SETDATEREQ:
371			if (fromnet->status != MASTER)
372				break;
373#ifdef sgi
374			(void)cftime(newdate, "%D %T", &msg->tsp_time.tv_sec);
375#else
376			tsp_time_sec = msg->tsp_time.tv_sec;
377			(void)strcpy(newdate, ctime(&tsp_time_sec));
378#endif /* sgi */
379			htp = findhost(msg->tsp_name);
380			if (0 == htp) {
381				syslog(LOG_WARNING,
382				       "DATEREQ from uncontrolled machine");
383				break;
384			}
385			if (!htp->good) {
386				syslog(LOG_WARNING,
387				"attempted date change by untrusted %s to %s",
388				       htp->name, newdate);
389				spreadtime();
390				break;
391			}
392			schgdate(msg, newdate);
393			break;
394
395		case TSP_TRACEON:
396			traceon();
397			break;
398
399		case TSP_TRACEOFF:
400			traceoff("Tracing ended at %s\n");
401			break;
402
403		case TSP_SLAVEUP:
404			newslave(msg);
405			break;
406
407		case TSP_ELECTION:
408			if (fromnet->status == SLAVE) {
409				(void)gettimeofday(&ntime, 0);
410				electiontime = ntime.tv_sec + delay2;
411				fastelection = ntime.tv_sec + FASTTOUT;
412				seq = 0;
413				if (!good_host_name(msg->tsp_name)) {
414					syslog(LOG_NOTICE,
415					       "suppress election of %s",
416					       msg->tsp_name);
417					to.tsp_type = TSP_QUIT;
418					electiontime = fastelection;
419				} else if (cadr.s_addr != from.sin_addr.s_addr
420					   && ntime.tv_sec < refusetime) {
421/* if the candidate has to repeat itself, the old code would refuse it
422 * the second time.  That would prevent elections.
423 */
424					to.tsp_type = TSP_REFUSE;
425				} else {
426					cadr.s_addr = from.sin_addr.s_addr;
427					to.tsp_type = TSP_ACCEPT;
428					refusetime = ntime.tv_sec + 30;
429				}
430				taddr = from;
431				(void)strcpy(tname, msg->tsp_name);
432				(void)strcpy(to.tsp_name, hostname);
433				answerdelay();
434				if (!acksend(&to, &taddr, tname,
435					     TSP_ACK, 0, 0))
436					syslog(LOG_WARNING,
437					     "no answer from candidate %s\n",
438					       tname);
439
440			} else {	/* fromnet->status == MASTER */
441				htp = addmach(msg->tsp_name, &from,fromnet);
442				to.tsp_type = TSP_QUIT;
443				(void)strcpy(to.tsp_name, hostname);
444				if (!acksend(&to, &htp->addr, htp->name,
445					     TSP_ACK, 0, htp->noanswer)) {
446					syslog(LOG_ERR,
447					  "no reply from %s to ELECTION-QUIT",
448					       htp->name);
449					(void)remmach(htp);
450				}
451			}
452			break;
453
454		case TSP_CONFLICT:
455			if (fromnet->status != MASTER)
456				break;
457			/*
458			 * After a network partition, there can be
459			 * more than one master: the first slave to
460			 * come up will notify here the situation.
461			 */
462			(void)strcpy(to.tsp_name, hostname);
463
464			/* The other master often gets into the same state,
465			 * with boring results.
466			 */
467			ntp = fromnet;	/* (acksend() can leave fromnet=0 */
468			for (tries = 0; tries < 3; tries++) {
469				to.tsp_type = TSP_RESOLVE;
470				answer = acksend(&to, &ntp->dest_addr,
471						 ANYADDR, TSP_MASTERACK,
472						 ntp, 0);
473				if (answer == NULL)
474					break;
475				htp = addmach(answer->tsp_name,&from,ntp);
476				to.tsp_type = TSP_QUIT;
477				answer = acksend(&to, &htp->addr, htp->name,
478						 TSP_ACK, 0, htp->noanswer);
479				if (!answer) {
480					syslog(LOG_WARNING,
481				  "conflict error: no reply from %s to QUIT",
482						htp->name);
483					(void)remmach(htp);
484				}
485			}
486			masterup(ntp);
487			break;
488
489		case TSP_MSITE:
490			if (!slavenet)
491				break;
492			taddr = from;
493			to.tsp_type = TSP_MSITEREQ;
494			to.tsp_vers = TSPVERSION;
495			to.tsp_seq = 0;
496			(void)strcpy(to.tsp_name, hostname);
497			answer = acksend(&to, &slavenet->dest_addr,
498					 ANYADDR, TSP_ACK,
499					 slavenet, 0);
500			if (answer != NULL
501			    && good_host_name(answer->tsp_name)) {
502				setmaster(answer);
503				to.tsp_type = TSP_ACK;
504				(void)strcpy(to.tsp_name, answer->tsp_name);
505				bytenetorder(&to);
506				if (sendto(sock, (char *)&to,
507					   sizeof(struct tsp), 0,
508					   (struct sockaddr*)&taddr,
509					   sizeof(taddr)) < 0) {
510					trace_sendto_err(taddr.sin_addr);
511				}
512			}
513			break;
514
515		case TSP_MSITEREQ:
516			break;
517
518		case TSP_ACCEPT:
519		case TSP_REFUSE:
520		case TSP_RESOLVE:
521			break;
522
523		case TSP_QUIT:
524			doquit(msg);		/* become a slave */
525			break;
526
527		case TSP_TEST:
528			electiontime = 0;
529			break;
530
531		case TSP_LOOP:
532			/* looking for loops of masters */
533			if (!(status & MASTER))
534				break;
535			if (fromnet->status == SLAVE) {
536			    if (!strcmp(msg->tsp_name, hostname)) {
537				/*
538				 * Someone forwarded our message back to
539				 * us.  There must be a loop.  Tell the
540				 * master of this network to quit.
541				 *
542				 * The other master often gets into
543				 * the same state, with boring results.
544				 */
545				ntp = fromnet;
546				for (tries = 0; tries < 3; tries++) {
547				    to.tsp_type = TSP_RESOLVE;
548				    answer = acksend(&to, &ntp->dest_addr,
549						     ANYADDR, TSP_MASTERACK,
550						     ntp,0);
551				    if (answer == NULL)
552					break;
553				    taddr = from;
554				    (void)strcpy(tname, answer->tsp_name);
555				    to.tsp_type = TSP_QUIT;
556				    (void)strcpy(to.tsp_name, hostname);
557				    if (!acksend(&to, &taddr, tname,
558						 TSP_ACK, 0, 1)) {
559					syslog(LOG_ERR,
560					"no reply from %s to slave LOOP-QUIT",
561						 tname);
562				    } else {
563					electiontime = 0;
564				    }
565				}
566				(void)gettimeofday(&ntime, 0);
567				looptime = ntime.tv_sec + FASTTOUT;
568			    } else {
569				if (msg->tsp_hopcnt-- < 1)
570				    break;
571				bytenetorder(msg);
572				for (ntp = nettab; ntp != 0; ntp = ntp->next) {
573				    if (ntp->status == MASTER
574					&& 0 > sendto(sock, (char *)msg,
575						      sizeof(struct tsp), 0,
576					      (struct sockaddr*)&ntp->dest_addr,
577						      sizeof(ntp->dest_addr)))
578				    trace_sendto_err(ntp->dest_addr.sin_addr);
579				}
580			    }
581			} else {	/* fromnet->status == MASTER */
582			    /*
583			     * We should not have received this from a net
584			     * we are master on.  There must be two masters,
585			     * unless the packet was really from us.
586			     */
587			    if (from.sin_addr.s_addr
588				== fromnet->my_addr.s_addr) {
589				if (trace)
590				    fprintf(fd,"discarding forwarded LOOP\n");
591				break;
592			    }
593
594			    /*
595			     * The other master often gets into the same
596			     * state, with boring results.
597			     */
598			    ntp = fromnet;
599			    for (tries = 0; tries < 3; tries++) {
600				to.tsp_type = TSP_RESOLVE;
601				answer = acksend(&to, &ntp->dest_addr,
602						 ANYADDR, TSP_MASTERACK,
603						ntp,0);
604				if (!answer)
605					break;
606				htp = addmach(answer->tsp_name,
607					      &from,ntp);
608				to.tsp_type = TSP_QUIT;
609				(void)strcpy(to.tsp_name, hostname);
610				if (!acksend(&to,&htp->addr,htp->name,
611					     TSP_ACK, 0, htp->noanswer)) {
612					syslog(LOG_ERR,
613				    "no reply from %s to master LOOP-QUIT",
614					       htp->name);
615					(void)remmach(htp);
616				}
617			    }
618			    (void)gettimeofday(&ntime, 0);
619			    looptime = ntime.tv_sec + FASTTOUT;
620			}
621			break;
622		default:
623			if (trace) {
624				fprintf(fd, "garbage message: ");
625				print(msg, &from);
626			}
627			break;
628		}
629	}
630	goto loop;
631}
632
633
634/*
635 * tell the world who our master is
636 */
637static void
638setmaster(msg)
639	struct tsp *msg;
640{
641	if (slavenet
642	    && (slavenet != old_slavenet
643		|| strcmp(msg->tsp_name, master_name)
644		|| old_status != status)) {
645		(void)strcpy(master_name, msg->tsp_name);
646		old_slavenet = slavenet;
647		old_status = status;
648
649		if (status & MASTER) {
650			syslog(LOG_NOTICE, "submaster to %s", master_name);
651			if (trace)
652				fprintf(fd, "submaster to %s\n", master_name);
653
654		} else {
655			syslog(LOG_NOTICE, "slave to %s", master_name);
656			if (trace)
657				fprintf(fd, "slave to %s\n", master_name);
658		}
659	}
660}
661
662
663
664/*
665 * handle date change request on a slave
666 */
667static void
668schgdate(msg, newdate)
669	struct tsp *msg;
670	char *newdate;
671{
672	struct tsp to;
673	u_short seq;
674	struct sockaddr_in taddr;
675	struct timeval otime;
676
677	if (!slavenet)
678		return;			/* no where to forward */
679
680	taddr = from;
681	seq = msg->tsp_seq;
682
683	syslog(LOG_INFO,
684	       "forwarding date change by %s to %s",
685	       msg->tsp_name, newdate);
686
687	/* adjust time for residence on the queue */
688	(void)gettimeofday(&otime, 0);
689	adj_msg_time(msg, &otime);
690
691	to.tsp_type = TSP_SETDATEREQ;
692	to.tsp_time = msg->tsp_time;
693	(void)strcpy(to.tsp_name, hostname);
694	if (!acksend(&to, &slavenet->dest_addr,
695		     ANYADDR, TSP_DATEACK,
696		     slavenet, 0))
697		return;			/* no answer */
698
699	xmit(TSP_DATEACK, seq, &taddr);
700}
701
702
703/*
704 * Used before answering a broadcast message to avoid network
705 * contention and likely collisions.
706 */
707static void
708answerdelay()
709{
710#ifdef sgi
711	sginap(delay1);
712#else
713	struct timeval timeout;
714
715	timeout.tv_sec = 0;
716	timeout.tv_usec = delay1;
717
718	(void)select(0, (fd_set *)NULL, (fd_set *)NULL, (fd_set *)NULL,
719	    &timeout);
720	return;
721#endif /* sgi */
722}
723