summaryrefslogtreecommitdiffstats
path: root/lib/clplumbing/longclock.c
blob: 594c9c5e1e16f690211b618bbda28bc89aaf68ae (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
/*
 * Longclock operations
 *
 * Copyright (c) 2002 International Business Machines
 * Author:	Alan Robertson <alanr@unix.sh>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

#include <lha_internal.h>
#include <unistd.h>
#include <sys/times.h>
#include <errno.h>
#include <clplumbing/longclock.h>
#include <clplumbing/cl_log.h>

static	unsigned 	Hz = 0;
static	longclock_t 	Lc_Hz;
static	double		d_Hz;


const longclock_t	zero_longclock = 0UL;

#ifndef CLOCK_T_IS_LONG_ENOUGH
#	undef time_longclock
#endif

#ifdef HAVE_LONGCLOCK_ARITHMETIC
#	undef	msto_longclock
#	undef	longclockto_ms
#	undef	secsto_longclock
#	undef	add_longclock
#	undef	sub_longclock
#	undef	cmp_longclock
#endif


unsigned
hz_longclock(void)
{
	if (Hz == 0) {
		/* Compute various hz-related constants */

		Hz = sysconf(_SC_CLK_TCK);
		Lc_Hz = (longclock_t)Hz;
		d_Hz = (double) Hz;
	}
	return Hz;
}

#ifdef	TIMES_ALLOWS_NULL_PARAM
#	define	TIMES_PARAM	NULL
#else
	static struct tms	dummy_longclock_tms_struct;
#	define	TIMES_PARAM	&dummy_longclock_tms_struct
#endif

unsigned long
cl_times(void)	/* Make times(2) behave rationally on Linux */
{
	clock_t		ret;
#ifndef DISABLE_TIMES_KLUDGE
	int		save_errno = errno;

	/*
	 * times(2) really returns an unsigned value ...
	 *
	 * We don't check to see if we got back the error value (-1), because
	 * the only possibility for an error would be if the address of 
	 * longclock_dummy_tms_struct was invalid.  Since it's a
	 * compiler-generated address, we assume that errors are impossible.
	 * And, unfortunately, it is quite possible for the correct return
	 * from times(2) to be exactly (clock_t)-1.  Sigh...
	 *
	 */
	errno	= 0;
#endif /* DISABLE_TIMES_KLUDGE */
	ret	= times(TIMES_PARAM);

#ifndef DISABLE_TIMES_KLUDGE
/*
 *	This is to work around a bug in the system call interface
 *	for times(2) found in glibc on Linux (and maybe elsewhere)
 *	It changes the return values from -1 to -4096 all into
 *	-1 and then dumps the -(return value) into errno.
 *
 *	This totally bizarre behavior seems to be widespread in
 *	versions of Linux and glibc.
 *
 *	Many thanks to Wolfgang Dumhs <wolfgang.dumhs (at) gmx.at>
 *	for finding and documenting this bizarre behavior.
 */
	if (errno != 0) {
		ret = (clock_t) (-errno);
	}
	errno = save_errno;
#endif /* DISABLE_TIMES_KLUDGE */

	/* sizeof(long) may be larger than sizeof(clock_t).
	 * Don't jump from 0x7fffffff to 0xffffffff80000000
	 * because of sign extension.
	 * We do expect sizeof(clock_t) <= sizeof(long), however.
	 */
	BUILD_BUG_ON(sizeof(clock_t) > sizeof(unsigned long));
#define CLOCK_T_MAX	(~0UL >> (8*(sizeof(unsigned long) - sizeof(clock_t))))
	return (unsigned long)ret & CLOCK_T_MAX;
}

#ifdef CLOCK_T_IS_LONG_ENOUGH
longclock_t
time_longclock(void)
{
	/* See note below about deliberately ignoring errors... */
	return (longclock_t)cl_times();
}

#else	/* clock_t is shorter than 64 bits */

#define	BITSPERBYTE	8
#define	WRAPSHIFT	(BITSPERBYTE*sizeof(clock_t))
#define	WRAPAMOUNT	(((longclock_t) 1) << WRAPSHIFT)
#define	MINJUMP		((CLOCK_T_MAX/100UL)*99UL)

longclock_t
time_longclock(void)
{
	/* Internal note: This updates the static fields; care should be
	 * taken to not call a function like cl_log (which internally
	 * calls time_longclock() as well) just before this happens,
	 * because then this can recurse infinitely; that is why the
	 * cl_log call is where it is; found by Simon Graham. */
	static	gboolean	calledbefore	= FALSE;
	static	unsigned long	lasttimes	= 0L;
	static	unsigned long	callcount	= 0L;
	static	longclock_t	lc_wrapcount	= 0L;
	unsigned long		timesval;

	++callcount;

	timesval = cl_times();

	if (calledbefore && timesval < lasttimes)  {
		unsigned long jumpbackby = lasttimes - timesval;

		if (jumpbackby < MINJUMP) {
			/* Kernel weirdness */
			cl_log(LOG_CRIT
			,	"%s: clock_t from times(2) appears to"
			" have jumped backwards (in error)!"
			,	__FUNCTION__);
			cl_log(LOG_CRIT
			,	"%s: old value was %lu"
			", new value is %lu, diff is %lu, callcount %lu"
			,	__FUNCTION__
			,	(unsigned long)lasttimes
			,	(unsigned long)timesval
			,	(unsigned long)jumpbackby
			,	callcount);
			/* Assume jump back was the error and ignore it */
			/* (i.e., hope it goes away) */
		}else{
			/* Normal looking wraparound */
			/* update last time BEFORE loging as log call
			   can call this routine recursively leading
			   to double update of wrapcount! */

			lasttimes = timesval;
			lc_wrapcount += WRAPAMOUNT;

			cl_log(LOG_INFO
			,	"%s: clock_t wrapped around (uptime)."
			,	__FUNCTION__);
		}
	}
	else {
		lasttimes = timesval;
		calledbefore = TRUE;
	}
	return (lc_wrapcount | timesval);
}
#endif	/* ! CLOCK_T_IS_LONG_ENOUGH */

longclock_t
msto_longclock(unsigned long ms)
{
	unsigned long	secs =	ms / 1000UL;
	unsigned long	msec = ms % 1000;
	longclock_t	result;

	(void)(Hz == 0 && hz_longclock());

	if (ms == 0) {
		return (longclock_t)0UL;
	}
	result = secs * Lc_Hz + (msec * Lc_Hz)/1000;

	if (result == 0) {
		result = 1;
	}
	return result;
}

longclock_t
secsto_longclock(unsigned long Secs)
{
	longclock_t	secs = Secs;

	(void)(Hz == 0 && hz_longclock());

	return secs * Lc_Hz;
}

longclock_t
dsecsto_longclock(double v)
{
	(void)(Hz == 0 && hz_longclock());

	return (longclock_t) ((v * d_Hz)+0.5);
	
}

unsigned long
longclockto_ms(longclock_t t)
{
	(void)(Hz == 0 && hz_longclock());

	if (t == 0) {
		return 0UL;
	}
	return (unsigned long) ((t*1000UL)/Lc_Hz);
}
#ifndef CLOCK_T_IS_LONG_ENOUGH
long
longclockto_long(longclock_t t)
{
	return	((long)(t));
}

longclock_t
add_longclock(longclock_t l, longclock_t r)
{
	return l + r;
}

longclock_t
sub_longclock(longclock_t l, longclock_t r)
{
	return l - r;
}

int
cmp_longclock(longclock_t l, longclock_t r)
{
	if (l < r) {
		return -1;
	}
	if (l > r) {
		return 1;
	}
	return 0;
}
#endif /* CLOCK_T_IS_LONG_ENOUGH */