xref: /freebsd/contrib/sendmail/libsm/clock.c (revision ee2ea5ceafed78a5bd9810beb9e3ca927180c226)
1 /*
2  * Copyright (c) 1998-2001 Sendmail, Inc. and its suppliers.
3  *	All rights reserved.
4  * Copyright (c) 1983, 1995-1997 Eric P. Allman.  All rights reserved.
5  * Copyright (c) 1988, 1993
6  *	The Regents of the University of California.  All rights reserved.
7  *
8  * By using this file, you agree to the terms and conditions set
9  * forth in the LICENSE file which can be found at the top level of
10  * the sendmail distribution.
11  *
12  */
13 
14 #include <sm/gen.h>
15 SM_RCSID("@(#)$Id: clock.c,v 1.1.1.2 2002/04/10 03:04:55 gshapiro Exp $")
16 #include <unistd.h>
17 #include <time.h>
18 #include <errno.h>
19 #if SM_CONF_SETITIMER
20 # include <sys/time.h>
21 #endif /* SM_CONF_SETITIMER */
22 #include <sm/heap.h>
23 #include <sm/debug.h>
24 #include <sm/bitops.h>
25 #include <sm/clock.h>
26 #include "local.h"
27 
28 #ifndef sigmask
29 # define sigmask(s)	(1 << ((s) - 1))
30 #endif /* ! sigmask */
31 
32 static void	sm_endsleep __P((void));
33 
34 
35 /*
36 **  SM_SETEVENTM -- set an event to happen at a specific time in milliseconds.
37 **
38 **	Events are stored in a sorted list for fast processing.
39 **	An event only applies to the process that set it.
40 **	Source is #ifdef'd to work with older OS's that don't have setitimer()
41 **	(that is, don't have a timer granularity less than 1 second).
42 **
43 **	Parameters:
44 **		intvl -- interval until next event occurs (milliseconds).
45 **		func -- function to call on event.
46 **		arg -- argument to func on event.
47 **
48 **	Returns:
49 **		On success returns the SM_EVENT entry created.
50 **		On failure returns NULL.
51 **
52 **	Side Effects:
53 **		none.
54 */
55 
56 static SM_EVENT	*volatile SmEventQueue;		/* head of event queue */
57 static SM_EVENT	*volatile SmFreeEventList;	/* list of free events */
58 
59 SM_EVENT *
60 sm_seteventm(intvl, func, arg)
61 	int intvl;
62 	void (*func)();
63 	int arg;
64 {
65 	ENTER_CRITICAL();
66 	if (SmFreeEventList == NULL)
67 	{
68 		SmFreeEventList = (SM_EVENT *) sm_pmalloc_x(sizeof *SmFreeEventList);
69 		SmFreeEventList->ev_link = NULL;
70 	}
71 	LEAVE_CRITICAL();
72 
73 	return sm_sigsafe_seteventm(intvl, func, arg);
74 }
75 
76 /*
77 **	NOTE:	THIS CAN BE CALLED FROM A SIGNAL HANDLER.  DO NOT ADD
78 **		ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
79 **		DOING.
80 */
81 
82 SM_EVENT *
83 sm_sigsafe_seteventm(intvl, func, arg)
84 	int intvl;
85 	void (*func)();
86 	int arg;
87 {
88 	register SM_EVENT **evp;
89 	register SM_EVENT *ev;
90 #if SM_CONF_SETITIMER
91 	auto struct timeval now, nowi, ival;
92 	auto struct itimerval itime;
93 #else /*  SM_CONF_SETITIMER */
94 	auto time_t now, nowi;
95 #endif /*  SM_CONF_SETITIMER */
96 	int wasblocked;
97 
98 	/* negative times are not allowed */
99 	if (intvl <= 0)
100 		return NULL;
101 
102 	wasblocked = sm_blocksignal(SIGALRM);
103 #if SM_CONF_SETITIMER
104 	ival.tv_sec = intvl / 1000;
105 	ival.tv_usec = (intvl - ival.tv_sec * 1000) * 10;
106 	(void) gettimeofday(&now, NULL);
107 	nowi = now;
108 	timeradd(&now, &ival, &nowi);
109 #else /*  SM_CONF_SETITIMER */
110 	now = time(NULL);
111 	nowi = now + (time_t)(intvl / 1000);
112 #endif /*  SM_CONF_SETITIMER */
113 
114 	/* search event queue for correct position */
115 	for (evp = (SM_EVENT **) (&SmEventQueue);
116 	     (ev = *evp) != NULL;
117 	     evp = &ev->ev_link)
118 	{
119 #if SM_CONF_SETITIMER
120 		if (timercmp(&(ev->ev_time), &nowi, >=))
121 #else /* SM_CONF_SETITIMER */
122 		if (ev->ev_time >= nowi)
123 #endif /* SM_CONF_SETITIMER */
124 			break;
125 	}
126 
127 	ENTER_CRITICAL();
128 	if (SmFreeEventList == NULL)
129 	{
130 		/*
131 		**  This shouldn't happen.  If called from sm_seteventm(),
132 		**  we have just malloced a SmFreeEventList entry.  If
133 		**  called from a signal handler, it should have been
134 		**  from an existing event which sm_tick() just added to
135 		**  SmFreeEventList.
136 		*/
137 
138 		LEAVE_CRITICAL();
139 		return NULL;
140 	}
141 	else
142 	{
143 		ev = SmFreeEventList;
144 		SmFreeEventList = ev->ev_link;
145 	}
146 	LEAVE_CRITICAL();
147 
148 	/* insert new event */
149 	ev->ev_time = nowi;
150 	ev->ev_func = func;
151 	ev->ev_arg = arg;
152 	ev->ev_pid = getpid();
153 	ENTER_CRITICAL();
154 	ev->ev_link = *evp;
155 	*evp = ev;
156 	LEAVE_CRITICAL();
157 
158 	(void) sm_signal(SIGALRM, sm_tick);
159 # if SM_CONF_SETITIMER
160 	timersub(&SmEventQueue->ev_time, &now, &itime.it_value);
161 	itime.it_interval.tv_sec = 0;
162 	itime.it_interval.tv_usec = 0;
163 	if (itime.it_value.tv_sec < 0)
164 		itime.it_value.tv_sec = 0;
165 	if (itime.it_value.tv_sec == 0 && itime.it_value.tv_usec == 0)
166 		itime.it_value.tv_usec = 1000;
167 	(void) setitimer(ITIMER_REAL, &itime, NULL);
168 # else /* SM_CONF_SETITIMER */
169 	intvl = SmEventQueue->ev_time - now;
170 	(void) alarm((unsigned) intvl < 1 ? 1 : intvl);
171 # endif /* SM_CONF_SETITIMER */
172 	if (wasblocked == 0)
173 		(void) sm_releasesignal(SIGALRM);
174 	return ev;
175 }
176 /*
177 **  SM_CLREVENT -- remove an event from the event queue.
178 **
179 **	Parameters:
180 **		ev -- pointer to event to remove.
181 **
182 **	Returns:
183 **		none.
184 **
185 **	Side Effects:
186 **		arranges for event ev to not happen.
187 */
188 
189 void
190 sm_clrevent(ev)
191 	register SM_EVENT *ev;
192 {
193 	register SM_EVENT **evp;
194 	int wasblocked;
195 # if SM_CONF_SETITIMER
196 	struct itimerval clr;
197 # endif /* SM_CONF_SETITIMER */
198 
199 	if (ev == NULL)
200 		return;
201 
202 	/* find the parent event */
203 	wasblocked = sm_blocksignal(SIGALRM);
204 	for (evp = (SM_EVENT **) (&SmEventQueue);
205 	     *evp != NULL;
206 	     evp = &(*evp)->ev_link)
207 	{
208 		if (*evp == ev)
209 			break;
210 	}
211 
212 	/* now remove it */
213 	if (*evp != NULL)
214 	{
215 		ENTER_CRITICAL();
216 		*evp = ev->ev_link;
217 		ev->ev_link = SmFreeEventList;
218 		SmFreeEventList = ev;
219 		LEAVE_CRITICAL();
220 	}
221 
222 	/* restore clocks and pick up anything spare */
223 	if (wasblocked == 0)
224 		(void) sm_releasesignal(SIGALRM);
225 	if (SmEventQueue != NULL)
226 		(void) kill(getpid(), SIGALRM);
227 	else
228 	{
229 		/* nothing left in event queue, no need for an alarm */
230 # if SM_CONF_SETITIMER
231 		clr.it_interval.tv_sec = 0;
232 		clr.it_interval.tv_usec = 0;
233 		clr.it_value.tv_sec = 0;
234 		clr.it_value.tv_usec = 0;
235 		(void) setitimer(ITIMER_REAL, &clr, NULL);
236 # else /* SM_CONF_SETITIMER */
237 		(void) alarm(0);
238 # endif /* SM_CONF_SETITIMER */
239 	}
240 }
241 /*
242 **  SM_CLEAR_EVENTS -- remove all events from the event queue.
243 **
244 **	Parameters:
245 **		none.
246 **
247 **	Returns:
248 **		none.
249 */
250 
251 void
252 sm_clear_events()
253 {
254 	register SM_EVENT *ev;
255 #if SM_CONF_SETITIMER
256 	struct itimerval clr;
257 #endif /* SM_CONF_SETITIMER */
258 	int wasblocked;
259 
260 	if (SmEventQueue == NULL)
261 		return;
262 
263 	/* nothing will be left in event queue, no need for an alarm */
264 #if SM_CONF_SETITIMER
265 	clr.it_interval.tv_sec = 0;
266 	clr.it_interval.tv_usec = 0;
267 	clr.it_value.tv_sec = 0;
268 	clr.it_value.tv_usec = 0;
269 	(void) setitimer(ITIMER_REAL, &clr, NULL);
270 #else /* SM_CONF_SETITIMER */
271 	(void) alarm(0);
272 #endif /* SM_CONF_SETITIMER */
273 	wasblocked = sm_blocksignal(SIGALRM);
274 
275 	/* find the end of the EventQueue */
276 	for (ev = SmEventQueue; ev->ev_link != NULL; ev = ev->ev_link)
277 		continue;
278 
279 	ENTER_CRITICAL();
280 	ev->ev_link = SmFreeEventList;
281 	SmFreeEventList = SmEventQueue;
282 	SmEventQueue = NULL;
283 	LEAVE_CRITICAL();
284 
285 	/* restore clocks and pick up anything spare */
286 	if (wasblocked == 0)
287 		(void) sm_releasesignal(SIGALRM);
288 }
289 /*
290 **  SM_TICK -- take a clock tick
291 **
292 **	Called by the alarm clock.  This routine runs events as needed.
293 **	Always called as a signal handler, so we assume that SIGALRM
294 **	has been blocked.
295 **
296 **	Parameters:
297 **		One that is ignored; for compatibility with signal handlers.
298 **
299 **	Returns:
300 **		none.
301 **
302 **	Side Effects:
303 **		calls the next function in EventQueue.
304 **
305 **	NOTE:	THIS CAN BE CALLED FROM A SIGNAL HANDLER.  DO NOT ADD
306 **		ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
307 **		DOING.
308 */
309 
310 /* ARGSUSED */
311 SIGFUNC_DECL
312 sm_tick(sig)
313 	int sig;
314 {
315 	register SM_EVENT *ev;
316 	pid_t mypid;
317 	int save_errno = errno;
318 #if SM_CONF_SETITIMER
319 	struct itimerval clr;
320 	struct timeval now;
321 #else /* SM_CONF_SETITIMER */
322 	register time_t now;
323 #endif /* SM_CONF_SETITIMER */
324 
325 #if SM_CONF_SETITIMER
326 	clr.it_interval.tv_sec = 0;
327 	clr.it_interval.tv_usec = 0;
328 	clr.it_value.tv_sec = 0;
329 	clr.it_value.tv_usec = 0;
330 	(void) setitimer(ITIMER_REAL, &clr, NULL);
331 	gettimeofday(&now, NULL);
332 #else /* SM_CONF_SETITIMER */
333 	(void) alarm(0);
334 	now = time(NULL);
335 #endif /* SM_CONF_SETITIMER */
336 
337 	FIX_SYSV_SIGNAL(sig, sm_tick);
338 	errno = save_errno;
339 	CHECK_CRITICAL(sig);
340 
341 	mypid = getpid();
342 	while (PendingSignal != 0)
343 	{
344 		int sigbit = 0;
345 		int sig = 0;
346 
347 		if (bitset(PEND_SIGHUP, PendingSignal))
348 		{
349 			sigbit = PEND_SIGHUP;
350 			sig = SIGHUP;
351 		}
352 		else if (bitset(PEND_SIGINT, PendingSignal))
353 		{
354 			sigbit = PEND_SIGINT;
355 			sig = SIGINT;
356 		}
357 		else if (bitset(PEND_SIGTERM, PendingSignal))
358 		{
359 			sigbit = PEND_SIGTERM;
360 			sig = SIGTERM;
361 		}
362 		else if (bitset(PEND_SIGUSR1, PendingSignal))
363 		{
364 			sigbit = PEND_SIGUSR1;
365 			sig = SIGUSR1;
366 		}
367 		else
368 		{
369 			/* If we get here, we are in trouble */
370 			abort();
371 		}
372 		PendingSignal &= ~sigbit;
373 		kill(mypid, sig);
374 	}
375 
376 #if SM_CONF_SETITIMER
377 	gettimeofday(&now, NULL);
378 #else /* SM_CONF_SETITIMER */
379 	now = time(NULL);
380 #endif /* SM_CONF_SETITIMER */
381 	while ((ev = SmEventQueue) != NULL &&
382 	       (ev->ev_pid != mypid ||
383 #if SM_CONF_SETITIMER
384 		timercmp(&ev->ev_time, &now, <=)
385 #else /* SM_CONF_SETITIMER */
386 		ev->ev_time <= now
387 #endif /* SM_CONF_SETITIMER */
388 		))
389 	{
390 		void (*f)();
391 		int arg;
392 		pid_t pid;
393 
394 		/* process the event on the top of the queue */
395 		ev = SmEventQueue;
396 		SmEventQueue = SmEventQueue->ev_link;
397 
398 		/* we must be careful in here because ev_func may not return */
399 		f = ev->ev_func;
400 		arg = ev->ev_arg;
401 		pid = ev->ev_pid;
402 		ENTER_CRITICAL();
403 		ev->ev_link = SmFreeEventList;
404 		SmFreeEventList = ev;
405 		LEAVE_CRITICAL();
406 		if (pid != getpid())
407 			continue;
408 		if (SmEventQueue != NULL)
409 		{
410 #if SM_CONF_SETITIMER
411 			if (timercmp(&SmEventQueue->ev_time, &now, >))
412 			{
413 				timersub(&SmEventQueue->ev_time, &now,
414 					 &clr.it_value);
415 				clr.it_interval.tv_sec = 0;
416 				clr.it_interval.tv_usec = 0;
417 				if (clr.it_value.tv_sec < 0)
418 					clr.it_value.tv_sec = 0;
419 				if (clr.it_value.tv_sec == 0 &&
420 				    clr.it_value.tv_usec == 0)
421 					clr.it_value.tv_usec = 1000;
422 				(void) setitimer(ITIMER_REAL, &clr, NULL);
423 			}
424 			else
425 			{
426 				clr.it_interval.tv_sec = 0;
427 				clr.it_interval.tv_usec = 0;
428 				clr.it_value.tv_sec = 3;
429 				clr.it_value.tv_usec = 0;
430 				(void) setitimer(ITIMER_REAL, &clr, NULL);
431 			}
432 #else /* SM_CONF_SETITIMER */
433 			if (SmEventQueue->ev_time > now)
434 				(void) alarm((unsigned) (SmEventQueue->ev_time
435 							 - now));
436 			else
437 				(void) alarm(3);
438 #endif /* SM_CONF_SETITIMER */
439 		}
440 
441 		/* call ev_func */
442 		errno = save_errno;
443 		(*f)(arg);
444 #if SM_CONF_SETITIMER
445 		clr.it_interval.tv_sec = 0;
446 		clr.it_interval.tv_usec = 0;
447 		clr.it_value.tv_sec = 0;
448 		clr.it_value.tv_usec = 0;
449 		(void) setitimer(ITIMER_REAL, &clr, NULL);
450 		gettimeofday(&now, NULL);
451 #else /* SM_CONF_SETITIMER */
452 		(void) alarm(0);
453 		now = time(NULL);
454 #endif /* SM_CONF_SETITIMER */
455 	}
456 	if (SmEventQueue != NULL)
457 	{
458 #if SM_CONF_SETITIMER
459 		timersub(&SmEventQueue->ev_time, &now, &clr.it_value);
460 		clr.it_interval.tv_sec = 0;
461 		clr.it_interval.tv_usec = 0;
462 		if (clr.it_value.tv_sec < 0)
463 			clr.it_value.tv_sec = 0;
464 		if (clr.it_value.tv_sec == 0 && clr.it_value.tv_usec == 0)
465 			clr.it_value.tv_usec = 1000;
466 		(void) setitimer(ITIMER_REAL, &clr, NULL);
467 #else /* SM_CONF_SETITIMER */
468 		(void) alarm((unsigned) (SmEventQueue->ev_time - now));
469 #endif /* SM_CONF_SETITIMER */
470 	}
471 	errno = save_errno;
472 	return SIGFUNC_RETURN;
473 }
474 /*
475 **  SLEEP -- a version of sleep that works with this stuff
476 **
477 **	Because Unix sleep uses the alarm facility, I must reimplement
478 **	it here.
479 **
480 **	Parameters:
481 **		intvl -- time to sleep.
482 **
483 **	Returns:
484 **		zero.
485 **
486 **	Side Effects:
487 **		waits for intvl time.  However, other events can
488 **		be run during that interval.
489 */
490 
491 
492 static bool	volatile SmSleepDone;
493 
494 #ifndef SLEEP_T
495 # define SLEEP_T	unsigned int
496 #endif /* ! SLEEP_T */
497 
498 SLEEP_T
499 sleep(intvl)
500 	unsigned int intvl;
501 {
502 	int was_held;
503 
504 	if (intvl == 0)
505 		return (SLEEP_T) 0;
506 	SmSleepDone = false;
507 	(void) sm_setevent((time_t) intvl, sm_endsleep, 0);
508 	was_held = sm_releasesignal(SIGALRM);
509 	while (!SmSleepDone)
510 		(void) pause();
511 	if (was_held > 0)
512 		(void) sm_blocksignal(SIGALRM);
513 	return (SLEEP_T) 0;
514 }
515 
516 static void
517 sm_endsleep()
518 {
519 	/*
520 	**  NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER.  DO NOT ADD
521 	**	ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
522 	**	DOING.
523 	*/
524 
525 	SmSleepDone = true;
526 }
527 
528