1/*
2 * systime -- routines to fiddle a UNIX clock.
3 *
4 * ATTENTION: Get approval from Dave Mills on all changes to this file!
5 *
6 */
7#include <config.h>
8#include <math.h>
9
10#include "ntp.h"
11#include "ntpd.h"
12#include "ntp_syslog.h"
13#include "ntp_stdlib.h"
14#include "ntp_random.h"
15#include "iosignal.h"
16#include "timevalops.h"
17#include "timespecops.h"
18#include "ntp_calendar.h"
19#include "lib_strbuf.h"
20
21#ifdef HAVE_SYS_PARAM_H
22# include <sys/param.h>
23#endif
24#ifdef HAVE_UTMP_H
25# include <utmp.h>
26#endif /* HAVE_UTMP_H */
27#ifdef HAVE_UTMPX_H
28# include <utmpx.h>
29#endif /* HAVE_UTMPX_H */
30
31int	allow_panic = FALSE;		/* allow panic correction (-g) */
32int	enable_panic_check = TRUE;	/* Can we check allow_panic's state? */
33
34u_long	sys_lamport;			/* Lamport violation */
35u_long	sys_tsrounding;			/* timestamp rounding errors */
36
37#ifndef USE_COMPILETIME_PIVOT
38# define USE_COMPILETIME_PIVOT 1
39#endif
40
41/*
42 * These routines (get_systime, step_systime, adj_systime) implement an
43 * interface between the system independent NTP clock and the Unix
44 * system clock in various architectures and operating systems. Time is
45 * a precious quantity in these routines and every effort is made to
46 * minimize errors by unbiased rounding and amortizing adjustment
47 * residues.
48 *
49 * In order to improve the apparent resolution, provide unbiased
50 * rounding and most importantly ensure that the readings cannot be
51 * predicted, the low-order unused portion of the time below the minimum
52 * time to read the clock is filled with an unbiased random fuzz.
53 *
54 * The sys_tick variable specifies the system clock tick interval in
55 * seconds, for stepping clocks, defined as those which return times
56 * less than MINSTEP greater than the previous reading. For systems that
57 * use a high-resolution counter such that each clock reading is always
58 * at least MINSTEP greater than the prior, sys_tick is the time to read
59 * the system clock.
60 *
61 * The sys_fuzz variable measures the minimum time to read the system
62 * clock, regardless of its precision.  When reading the system clock
63 * using get_systime() after sys_tick and sys_fuzz have been determined,
64 * ntpd ensures each unprocessed clock reading is no less than sys_fuzz
65 * later than the prior unprocessed reading, and then fuzzes the bits
66 * below sys_fuzz in the timestamp returned, ensuring each of its
67 * resulting readings is strictly later than the previous.
68 *
69 * When slewing the system clock using adj_systime() (with the kernel
70 * loop discipline unavailable or disabled), adjtime() offsets are
71 * quantized to sys_tick, if sys_tick is greater than sys_fuzz, which
72 * is to say if the OS presents a stepping clock.  Otherwise, offsets
73 * are quantized to the microsecond resolution of adjtime()'s timeval
74 * input.  The remaining correction sys_residual is carried into the
75 * next adjtime() and meanwhile is also factored into get_systime()
76 * readings.
77 */
78double	sys_tick = 0;		/* tick size or time to read (s) */
79double	sys_fuzz = 0;		/* min. time to read the clock (s) */
80long	sys_fuzz_nsec = 0;	/* min. time to read the clock (ns) */
81double	measured_tick;		/* non-overridable sys_tick (s) */
82double	sys_residual = 0;	/* adjustment residue (s) */
83int	trunc_os_clock;		/* sys_tick > measured_tick */
84time_stepped_callback	step_callback;
85
86#ifndef SIM
87/* perlinger@ntp.org: As 'get_sysime()' does it's own check for clock
88 * backstepping, this could probably become a local variable in
89 * 'get_systime()' and the cruft associated with communicating via a
90 * static value could be removed after the v4.2.8 release.
91 */
92static int lamport_violated;	/* clock was stepped back */
93#endif	/* !SIM */
94
95#ifdef DEBUG
96static int systime_init_done;
97# define DONE_SYSTIME_INIT()	systime_init_done = TRUE
98#else
99# define DONE_SYSTIME_INIT()	do {} while (FALSE)
100#endif
101
102#ifdef HAVE_SIGNALED_IO
103int using_sigio;
104#endif
105
106#ifdef SYS_WINNT
107CRITICAL_SECTION get_systime_cs;
108#endif
109
110
111void
112set_sys_fuzz(
113	double	fuzz_val
114	)
115{
116	sys_fuzz = fuzz_val;
117	INSIST(sys_fuzz >= 0);
118	INSIST(sys_fuzz <= 1.0);
119	/* [Bug 3450] ensure nsec fuzz >= sys_fuzz to reduce chance of
120	 * short-falling fuzz advance
121	 */
122	sys_fuzz_nsec = (long)ceil(sys_fuzz * 1e9);
123}
124
125
126void
127init_systime(void)
128{
129	INIT_GET_SYSTIME_CRITSEC();
130	INIT_WIN_PRECISE_TIME();
131	DONE_SYSTIME_INIT();
132}
133
134
135#ifndef SIM	/* ntpsim.c has get_systime() and friends for sim */
136
137static inline void
138get_ostime(
139	struct timespec *	tsp
140	)
141{
142	int	rc;
143	long	ticks;
144
145#if defined(HAVE_CLOCK_GETTIME)
146	rc = clock_gettime(CLOCK_REALTIME, tsp);
147#elif defined(HAVE_GETCLOCK)
148	rc = getclock(TIMEOFDAY, tsp);
149#else
150	struct timeval		tv;
151
152	rc = GETTIMEOFDAY(&tv, NULL);
153	tsp->tv_sec = tv.tv_sec;
154	tsp->tv_nsec = tv.tv_usec * 1000;
155#endif
156	if (rc < 0) {
157		msyslog(LOG_ERR, "read system clock failed: %m (%d)",
158			errno);
159		exit(1);
160	}
161
162	if (trunc_os_clock) {
163		ticks = (long)((tsp->tv_nsec * 1e-9) / sys_tick);
164		tsp->tv_nsec = (long)(ticks * 1e9 * sys_tick);
165	}
166}
167
168
169/*
170 * get_systime - return system time in NTP timestamp format.
171 */
172void
173get_systime(
174	l_fp *now		/* system time */
175	)
176{
177        static struct timespec  ts_last;        /* last sampled os time */
178	static struct timespec	ts_prev;	/* prior os time */
179	static l_fp		lfp_prev;	/* prior result */
180	struct timespec ts;	/* seconds and nanoseconds */
181	struct timespec ts_min;	/* earliest permissible */
182	struct timespec ts_lam;	/* lamport fictional increment */
183	double	dfuzz;
184	l_fp	result;
185	l_fp	lfpfuzz;
186	l_fp	lfpdelta;
187
188	get_ostime(&ts);
189	DEBUG_REQUIRE(systime_init_done);
190	ENTER_GET_SYSTIME_CRITSEC();
191
192        /* First check if here was a Lamport violation, that is, two
193         * successive calls to 'get_ostime()' resulted in negative
194         * time difference. Use a few milliseconds of permissible
195         * tolerance -- being too sharp can hurt here. (This is intented
196         * for the Win32 target, where the HPC interpolation might
197         * introduce small steps backward. It should not be an issue on
198         * systems where get_ostime() results in a true syscall.)
199         */
200        if (cmp_tspec(add_tspec_ns(ts, 50000000), ts_last) < 0) {
201                lamport_violated = 1;
202                sys_lamport++;
203	}
204        ts_last = ts;
205
206	/*
207	 * After default_get_precision() has set a nonzero sys_fuzz,
208	 * ensure every reading of the OS clock advances by at least
209	 * sys_fuzz over the prior reading, thereby assuring each
210	 * fuzzed result is strictly later than the prior.  Limit the
211	 * necessary fiction to 1 second.
212	 */
213	if (!USING_SIGIO()) {
214		ts_min = add_tspec_ns(ts_prev, sys_fuzz_nsec);
215		if (cmp_tspec(ts, ts_min) < 0) {
216			ts_lam = sub_tspec(ts_min, ts);
217			if (ts_lam.tv_sec > 0 && !lamport_violated) {
218				msyslog(LOG_ERR,
219					"get_systime Lamport advance exceeds one second (%.9f)",
220					ts_lam.tv_sec +
221					    1e-9 * ts_lam.tv_nsec);
222				exit(1);
223			}
224			if (!lamport_violated)
225				ts = ts_min;
226		}
227		ts_prev = ts;
228	}
229
230	/* convert from timespec to l_fp fixed-point */
231	result = tspec_stamp_to_lfp(ts);
232
233	/*
234	 * Add in the fuzz. 'ntp_random()' returns [0..2**31-1] so we
235	 * must scale up the result by 2.0 to cover the full fractional
236	 * range.
237	 */
238	dfuzz = ntp_random() * 2. / FRAC * sys_fuzz;
239	DTOLFP(dfuzz, &lfpfuzz);
240	L_ADD(&result, &lfpfuzz);
241
242	/*
243	 * Ensure result is strictly greater than prior result (ignoring
244	 * sys_residual's effect for now) once sys_fuzz has been
245	 * determined.
246	 *
247	 * [Bug 3450] Rounding errors and time slew can lead to a
248	 * violation of the expected postcondition. This is bound to
249	 * happen from time to time (depending on state of the random
250	 * generator, the current slew and the closeness of system time
251	 * stamps drawn) and does not warrant a syslog entry. Instead it
252	 * makes much more sense to ensure the postcondition and hop
253	 * along silently.
254	 */
255	if (!USING_SIGIO()) {
256		if (   !L_ISZERO(&lfp_prev)
257		    && !lamport_violated
258		    && (sys_fuzz > 0.0)
259		   ) {
260			lfpdelta = result;
261			L_SUB(&lfpdelta, &lfp_prev);
262			L_SUBUF(&lfpdelta, 1);
263			if (lfpdelta.l_i < 0)
264			{
265				L_NEG(&lfpdelta);
266				DPRINTF(1, ("get_systime: postcond failed by %s secs, fixed\n",
267					    lfptoa(&lfpdelta, 9)));
268				result = lfp_prev;
269				L_ADDUF(&result, 1);
270				sys_tsrounding++;
271			}
272		}
273		lfp_prev = result;
274		if (lamport_violated)
275			lamport_violated = FALSE;
276	}
277	LEAVE_GET_SYSTIME_CRITSEC();
278	*now = result;
279}
280
281
282/*
283 * adj_systime - adjust system time by the argument.
284 */
285#if !defined SYS_WINNT
286int				/* 0 okay, 1 error */
287adj_systime(
288	double now		/* adjustment (s) */
289	)
290{
291	struct timeval adjtv;	/* new adjustment */
292	struct timeval oadjtv;	/* residual adjustment */
293	double	quant;		/* quantize to multiples of */
294	double	dtemp;
295	long	ticks;
296	int	isneg = 0;
297
298	/*
299	 * The Windows port adj_systime() depends on being called each
300	 * second even when there's no additional correction, to allow
301	 * emulation of adjtime() behavior on top of an API that simply
302	 * sets the current rate.  This POSIX implementation needs to
303	 * ignore invocations with zero correction, otherwise ongoing
304	 * EVNT_NSET adjtime() can be aborted by a tiny adjtime()
305	 * triggered by sys_residual.
306	 */
307	if (0. == now) {
308		if (enable_panic_check && allow_panic) {
309			msyslog(LOG_ERR, "adj_systime: allow_panic is TRUE!");
310			INSIST(!allow_panic);
311		}
312		return TRUE;
313	}
314
315	/*
316	 * Most Unix adjtime() implementations adjust the system clock
317	 * in microsecond quanta, but some adjust in 10-ms quanta. We
318	 * carefully round the adjustment to the nearest quantum, then
319	 * adjust in quanta and keep the residue for later.
320	 */
321	dtemp = now + sys_residual;
322	if (dtemp < 0) {
323		isneg = 1;
324		dtemp = -dtemp;
325	}
326	adjtv.tv_sec = (long)dtemp;
327	dtemp -= adjtv.tv_sec;
328	if (sys_tick > sys_fuzz)
329		quant = sys_tick;
330	else
331		quant = 1e-6;
332	ticks = (long)(dtemp / quant + .5);
333	adjtv.tv_usec = (long)(ticks * quant * 1.e6 + .5);
334	/* The rounding in the conversions could us push over the
335	 * limits: make sure the result is properly normalised!
336	 * note: sign comes later, all numbers non-negative here.
337	 */
338	if (adjtv.tv_usec >= 1000000) {
339		adjtv.tv_sec  += 1;
340		adjtv.tv_usec -= 1000000;
341		dtemp         -= 1.;
342	}
343	/* set the new residual with leftover from correction */
344	sys_residual = dtemp - adjtv.tv_usec * 1.e-6;
345
346	/*
347	 * Convert to signed seconds and microseconds for the Unix
348	 * adjtime() system call. Note we purposely lose the adjtime()
349	 * leftover.
350	 */
351	if (isneg) {
352		adjtv.tv_sec = -adjtv.tv_sec;
353		adjtv.tv_usec = -adjtv.tv_usec;
354		sys_residual = -sys_residual;
355	}
356	if (adjtv.tv_sec != 0 || adjtv.tv_usec != 0) {
357		if (adjtime(&adjtv, &oadjtv) < 0) {
358			msyslog(LOG_ERR, "adj_systime: %m");
359			if (enable_panic_check && allow_panic) {
360				msyslog(LOG_ERR, "adj_systime: allow_panic is TRUE!");
361			}
362			return FALSE;
363		}
364	}
365	if (enable_panic_check && allow_panic) {
366		msyslog(LOG_ERR, "adj_systime: allow_panic is TRUE!");
367	}
368	return TRUE;
369}
370#endif
371
372/*
373 * helper to keep utmp/wtmp up to date
374 */
375static void
376update_uwtmp(
377	struct timeval timetv,
378	struct timeval tvlast
379	)
380{
381	struct timeval tvdiff;
382	/*
383	 * FreeBSD, for example, has:
384	 * struct utmp {
385	 *	   char    ut_line[UT_LINESIZE];
386	 *	   char    ut_name[UT_NAMESIZE];
387	 *	   char    ut_host[UT_HOSTSIZE];
388	 *	   long    ut_time;
389	 * };
390	 * and appends line="|", name="date", host="", time for the OLD
391	 * and appends line="{", name="date", host="", time for the NEW // }
392	 * to _PATH_WTMP .
393	 *
394	 * Some OSes have utmp, some have utmpx.
395	 */
396
397	/*
398	 * Write old and new time entries in utmp and wtmp if step
399	 * adjustment is greater than one second.
400	 *
401	 * This might become even Uglier...
402	 */
403	tvdiff = abs_tval(sub_tval(timetv, tvlast));
404	if (tvdiff.tv_sec > 0) {
405#ifdef HAVE_UTMP_H
406		struct utmp ut;
407#endif
408#ifdef HAVE_UTMPX_H
409		struct utmpx utx;
410#endif
411
412#ifdef HAVE_UTMP_H
413		ZERO(ut);
414#endif
415#ifdef HAVE_UTMPX_H
416		ZERO(utx);
417#endif
418
419		/* UTMP */
420
421#ifdef UPDATE_UTMP
422# ifdef HAVE_PUTUTLINE
423#  ifndef _PATH_UTMP
424#   define _PATH_UTMP UTMP_FILE
425#  endif
426		utmpname(_PATH_UTMP);
427		ut.ut_type = OLD_TIME;
428		strlcpy(ut.ut_line, OTIME_MSG, sizeof(ut.ut_line));
429		ut.ut_time = tvlast.tv_sec;
430		setutent();
431		pututline(&ut);
432		ut.ut_type = NEW_TIME;
433		strlcpy(ut.ut_line, NTIME_MSG, sizeof(ut.ut_line));
434		ut.ut_time = timetv.tv_sec;
435		setutent();
436		pututline(&ut);
437		endutent();
438# else /* not HAVE_PUTUTLINE */
439# endif /* not HAVE_PUTUTLINE */
440#endif /* UPDATE_UTMP */
441
442		/* UTMPX */
443
444#ifdef UPDATE_UTMPX
445# ifdef HAVE_PUTUTXLINE
446		utx.ut_type = OLD_TIME;
447		strlcpy(utx.ut_line, OTIME_MSG, sizeof(utx.ut_line));
448		utx.ut_tv = tvlast;
449		setutxent();
450		pututxline(&utx);
451		utx.ut_type = NEW_TIME;
452		strlcpy(utx.ut_line, NTIME_MSG, sizeof(utx.ut_line));
453		utx.ut_tv = timetv;
454		setutxent();
455		pututxline(&utx);
456		endutxent();
457# else /* not HAVE_PUTUTXLINE */
458# endif /* not HAVE_PUTUTXLINE */
459#endif /* UPDATE_UTMPX */
460
461		/* WTMP */
462
463#ifdef UPDATE_WTMP
464# ifdef HAVE_PUTUTLINE
465#  ifndef _PATH_WTMP
466#   define _PATH_WTMP WTMP_FILE
467#  endif
468		utmpname(_PATH_WTMP);
469		ut.ut_type = OLD_TIME;
470		strlcpy(ut.ut_line, OTIME_MSG, sizeof(ut.ut_line));
471		ut.ut_time = tvlast.tv_sec;
472		setutent();
473		pututline(&ut);
474		ut.ut_type = NEW_TIME;
475		strlcpy(ut.ut_line, NTIME_MSG, sizeof(ut.ut_line));
476		ut.ut_time = timetv.tv_sec;
477		setutent();
478		pututline(&ut);
479		endutent();
480# else /* not HAVE_PUTUTLINE */
481# endif /* not HAVE_PUTUTLINE */
482#endif /* UPDATE_WTMP */
483
484		/* WTMPX */
485
486#ifdef UPDATE_WTMPX
487# ifdef HAVE_PUTUTXLINE
488		utx.ut_type = OLD_TIME;
489		utx.ut_tv = tvlast;
490		strlcpy(utx.ut_line, OTIME_MSG, sizeof(utx.ut_line));
491#  ifdef HAVE_UPDWTMPX
492		updwtmpx(WTMPX_FILE, &utx);
493#  else /* not HAVE_UPDWTMPX */
494#  endif /* not HAVE_UPDWTMPX */
495# else /* not HAVE_PUTUTXLINE */
496# endif /* not HAVE_PUTUTXLINE */
497# ifdef HAVE_PUTUTXLINE
498		utx.ut_type = NEW_TIME;
499		utx.ut_tv = timetv;
500		strlcpy(utx.ut_line, NTIME_MSG, sizeof(utx.ut_line));
501#  ifdef HAVE_UPDWTMPX
502		updwtmpx(WTMPX_FILE, &utx);
503#  else /* not HAVE_UPDWTMPX */
504#  endif /* not HAVE_UPDWTMPX */
505# else /* not HAVE_PUTUTXLINE */
506# endif /* not HAVE_PUTUTXLINE */
507#endif /* UPDATE_WTMPX */
508
509	}
510}
511
512/*
513 * step_systime - step the system clock.
514 */
515
516int
517step_systime(
518	double step
519	)
520{
521	time_t pivot; /* for ntp era unfolding */
522	struct timeval timetv, tvlast;
523	struct timespec timets;
524	l_fp fp_ofs, fp_sys; /* offset and target system time in FP */
525
526	/*
527	 * Get pivot time for NTP era unfolding. Since we don't step
528	 * very often, we can afford to do the whole calculation from
529	 * scratch. And we're not in the time-critical path yet.
530	 */
531#if SIZEOF_TIME_T > 4
532	pivot = basedate_get_eracenter();
533#else
534	/* This makes sure the resulting time stamp is on or after
535	 * 1969-12-31/23:59:59 UTC and gives us additional two years,
536	 * from the change of NTP era in 2036 to the UNIX rollover in
537	 * 2038. (Minus one second, but that won't hurt.) We *really*
538	 * need a longer 'time_t' after that!  Or a different baseline,
539	 * but that would cause other serious trouble, too.
540	 */
541	pivot = 0x7FFFFFFF;
542#endif
543
544	/* get the complete jump distance as l_fp */
545	DTOLFP(sys_residual, &fp_sys);
546	DTOLFP(step,         &fp_ofs);
547	L_ADD(&fp_ofs, &fp_sys);
548
549	/* ---> time-critical path starts ---> */
550
551	/* get the current time as l_fp (without fuzz) and as struct timeval */
552	get_ostime(&timets);
553	fp_sys = tspec_stamp_to_lfp(timets);
554	tvlast.tv_sec = timets.tv_sec;
555	tvlast.tv_usec = (timets.tv_nsec + 500) / 1000;
556
557	/* get the target time as l_fp */
558	L_ADD(&fp_sys, &fp_ofs);
559
560	/* unfold the new system time */
561	timetv = lfp_stamp_to_tval(fp_sys, &pivot);
562
563	/* now set new system time */
564	if (ntp_set_tod(&timetv, NULL) != 0) {
565		msyslog(LOG_ERR, "step-systime: %m");
566		if (enable_panic_check && allow_panic) {
567			msyslog(LOG_ERR, "step_systime: allow_panic is TRUE!");
568		}
569		return FALSE;
570	}
571
572	/* <--- time-critical path ended with 'ntp_set_tod()' <--- */
573
574	sys_residual = 0;
575	lamport_violated = (step < 0);
576	if (step_callback)
577		(*step_callback)();
578
579#ifdef NEED_HPUX_ADJTIME
580	/*
581	 * CHECKME: is this correct when called by ntpdate?????
582	 */
583	_clear_adjtime();
584#endif
585
586	update_uwtmp(timetv, tvlast);
587	if (enable_panic_check && allow_panic) {
588		msyslog(LOG_ERR, "step_systime: allow_panic is TRUE!");
589		INSIST(!allow_panic);
590	}
591	return TRUE;
592}
593
594static const char *
595tv_fmt_libbuf(
596	const struct timeval * ptv
597	)
598{
599	char *		retv;
600	vint64		secs;
601	ntpcal_split	dds;
602	struct calendar	jd;
603
604	secs = time_to_vint64(&ptv->tv_sec);
605	dds  = ntpcal_daysplit(&secs);
606	ntpcal_daysplit_to_date(&jd, &dds, DAY_UNIX_STARTS);
607	LIB_GETBUF(retv);
608	snprintf(retv, LIB_BUFLENGTH,
609		 "%04hu-%02hu-%02hu/%02hu:%02hu:%02hu.%06u",
610		 jd.year, (u_short)jd.month, (u_short)jd.monthday,
611		 (u_short)jd.hour, (u_short)jd.minute, (u_short)jd.second,
612		 (u_int)ptv->tv_usec);
613	return retv;
614}
615
616
617int /*BOOL*/
618clamp_systime(void)
619{
620#if SIZEOF_TIME_T > 4
621
622	struct timeval timetv, tvlast;
623	struct timespec timets;
624	uint32_t	tdiff;
625
626
627	timetv.tv_sec = basedate_get_erabase();
628
629	/* ---> time-critical path starts ---> */
630
631	/* get the current time as l_fp (without fuzz) and as struct timeval */
632	get_ostime(&timets);
633	tvlast.tv_sec = timets.tv_sec;
634	tvlast.tv_usec = (timets.tv_nsec + 500) / 1000;
635	if (tvlast.tv_usec >= 1000000) {
636		tvlast.tv_usec -= 1000000;
637		tvlast.tv_sec  += 1;
638	}
639	timetv.tv_usec = tvlast.tv_usec;
640
641	tdiff = (uint32_t)(tvlast.tv_sec & UINT32_MAX) -
642	        (uint32_t)(timetv.tv_sec & UINT32_MAX);
643	timetv.tv_sec += tdiff;
644	if (timetv.tv_sec != tvlast.tv_sec) {
645		/* now set new system time */
646		if (ntp_set_tod(&timetv, NULL) != 0) {
647			msyslog(LOG_ERR, "clamp-systime: %m");
648			return FALSE;
649		}
650	} else {
651		msyslog(LOG_INFO,
652			"clamp-systime: clock (%s) in allowed range",
653			tv_fmt_libbuf(&timetv));
654		return FALSE;
655	}
656
657	/* <--- time-critical path ended with 'ntp_set_tod()' <--- */
658
659	sys_residual = 0;
660	lamport_violated = (timetv.tv_sec < tvlast.tv_sec);
661	if (step_callback)
662		(*step_callback)();
663
664#   ifdef NEED_HPUX_ADJTIME
665	/*
666	 * CHECKME: is this correct when called by ntpdate?????
667	 */
668	_clear_adjtime();
669#   endif
670
671	update_uwtmp(timetv, tvlast);
672	msyslog(LOG_WARNING,
673		"clamp-systime: clock stepped from %s to %s!",
674		tv_fmt_libbuf(&tvlast), tv_fmt_libbuf(&timetv));
675	return TRUE;
676
677#else
678
679	return 0;
680#endif
681}
682
683#endif	/* !SIM */
684