xref: /illumos-gate/usr/src/cmd/sendmail/libsm/clock.c (revision 7a6d80f1660abd4755c68cbd094d4a914681d26e)
1 /*
2  * Copyright (c) 1998-2004 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.47 2005/06/14 23:07:20 ca Exp $")
16 #include <unistd.h>
17 #include <time.h>
18 #include <errno.h>
19 #if SM_CONF_SETITIMER
20 # include <sm/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 #if _FFR_SLEEP_USE_SELECT > 0
28 # include <sys/types.h>
29 #endif /* _FFR_SLEEP_USE_SELECT > 0 */
30 #if defined(_FFR_MAX_SLEEP_TIME) && _FFR_MAX_SLEEP_TIME > 2
31 # include <syslog.h>
32 #endif /* defined(_FFR_MAX_SLEEP_TIME) && _FFR_MAX_SLEEP_TIME > 2 */
33 
34 #ifndef sigmask
35 # define sigmask(s)	(1 << ((s) - 1))
36 #endif /* ! sigmask */
37 
38 
39 /*
40 **  SM_SETEVENTM -- set an event to happen at a specific time in milliseconds.
41 **
42 **	Events are stored in a sorted list for fast processing.
43 **	An event only applies to the process that set it.
44 **	Source is #ifdef'd to work with older OS's that don't have setitimer()
45 **	(that is, don't have a timer granularity less than 1 second).
46 **
47 **	Parameters:
48 **		intvl -- interval until next event occurs (milliseconds).
49 **		func -- function to call on event.
50 **		arg -- argument to func on event.
51 **
52 **	Returns:
53 **		On success returns the SM_EVENT entry created.
54 **		On failure returns NULL.
55 **
56 **	Side Effects:
57 **		none.
58 */
59 
60 static SM_EVENT	*volatile SmEventQueue;		/* head of event queue */
61 static SM_EVENT	*volatile SmFreeEventList;	/* list of free events */
62 
63 SM_EVENT *
64 sm_seteventm(intvl, func, arg)
65 	int intvl;
66 	void (*func)__P((int));
67 	int arg;
68 {
69 	ENTER_CRITICAL();
70 	if (SmFreeEventList == NULL)
71 	{
72 		SmFreeEventList = (SM_EVENT *) sm_pmalloc_x(sizeof *SmFreeEventList);
73 		SmFreeEventList->ev_link = NULL;
74 	}
75 	LEAVE_CRITICAL();
76 
77 	return sm_sigsafe_seteventm(intvl, func, arg);
78 }
79 
80 /*
81 **	NOTE:	THIS CAN BE CALLED FROM A SIGNAL HANDLER.  DO NOT ADD
82 **		ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
83 **		DOING.
84 */
85 
86 SM_EVENT *
87 sm_sigsafe_seteventm(intvl, func, arg)
88 	int intvl;
89 	void (*func)__P((int));
90 	int arg;
91 {
92 	register SM_EVENT **evp;
93 	register SM_EVENT *ev;
94 #if SM_CONF_SETITIMER
95 	auto struct timeval now, nowi, ival;
96 	auto struct itimerval itime;
97 #else /*  SM_CONF_SETITIMER */
98 	auto time_t now, nowi;
99 #endif /*  SM_CONF_SETITIMER */
100 	int wasblocked;
101 
102 	/* negative times are not allowed */
103 	if (intvl <= 0)
104 		return NULL;
105 
106 	wasblocked = sm_blocksignal(SIGALRM);
107 #if SM_CONF_SETITIMER
108 	ival.tv_sec = intvl / 1000;
109 	ival.tv_usec = (intvl - ival.tv_sec * 1000) * 10;
110 	(void) gettimeofday(&now, NULL);
111 	nowi = now;
112 	timeradd(&now, &ival, &nowi);
113 #else /*  SM_CONF_SETITIMER */
114 	now = time(NULL);
115 	nowi = now + (time_t)(intvl / 1000);
116 #endif /*  SM_CONF_SETITIMER */
117 
118 	/* search event queue for correct position */
119 	for (evp = (SM_EVENT **) (&SmEventQueue);
120 	     (ev = *evp) != NULL;
121 	     evp = &ev->ev_link)
122 	{
123 #if SM_CONF_SETITIMER
124 		if (timercmp(&(ev->ev_time), &nowi, >=))
125 #else /* SM_CONF_SETITIMER */
126 		if (ev->ev_time >= nowi)
127 #endif /* SM_CONF_SETITIMER */
128 			break;
129 	}
130 
131 	ENTER_CRITICAL();
132 	if (SmFreeEventList == NULL)
133 	{
134 		/*
135 		**  This shouldn't happen.  If called from sm_seteventm(),
136 		**  we have just malloced a SmFreeEventList entry.  If
137 		**  called from a signal handler, it should have been
138 		**  from an existing event which sm_tick() just added to
139 		**  SmFreeEventList.
140 		*/
141 
142 		LEAVE_CRITICAL();
143 		if (wasblocked == 0)
144 			(void) sm_releasesignal(SIGALRM);
145 		return NULL;
146 	}
147 	else
148 	{
149 		ev = SmFreeEventList;
150 		SmFreeEventList = ev->ev_link;
151 	}
152 	LEAVE_CRITICAL();
153 
154 	/* insert new event */
155 	ev->ev_time = nowi;
156 	ev->ev_func = func;
157 	ev->ev_arg = arg;
158 	ev->ev_pid = getpid();
159 	ENTER_CRITICAL();
160 	ev->ev_link = *evp;
161 	*evp = ev;
162 	LEAVE_CRITICAL();
163 
164 	(void) sm_signal(SIGALRM, sm_tick);
165 # if SM_CONF_SETITIMER
166 	timersub(&SmEventQueue->ev_time, &now, &itime.it_value);
167 	itime.it_interval.tv_sec = 0;
168 	itime.it_interval.tv_usec = 0;
169 	if (itime.it_value.tv_sec < 0)
170 		itime.it_value.tv_sec = 0;
171 	if (itime.it_value.tv_sec == 0 && itime.it_value.tv_usec == 0)
172 		itime.it_value.tv_usec = 1000;
173 	(void) setitimer(ITIMER_REAL, &itime, NULL);
174 # else /* SM_CONF_SETITIMER */
175 	intvl = SmEventQueue->ev_time - now;
176 	(void) alarm((unsigned) (intvl < 1 ? 1 : intvl));
177 # endif /* SM_CONF_SETITIMER */
178 	if (wasblocked == 0)
179 		(void) sm_releasesignal(SIGALRM);
180 	return ev;
181 }
182 /*
183 **  SM_CLREVENT -- remove an event from the event queue.
184 **
185 **	Parameters:
186 **		ev -- pointer to event to remove.
187 **
188 **	Returns:
189 **		none.
190 **
191 **	Side Effects:
192 **		arranges for event ev to not happen.
193 */
194 
195 void
196 sm_clrevent(ev)
197 	register SM_EVENT *ev;
198 {
199 	register SM_EVENT **evp;
200 	int wasblocked;
201 # if SM_CONF_SETITIMER
202 	struct itimerval clr;
203 # endif /* SM_CONF_SETITIMER */
204 
205 	if (ev == NULL)
206 		return;
207 
208 	/* find the parent event */
209 	wasblocked = sm_blocksignal(SIGALRM);
210 	for (evp = (SM_EVENT **) (&SmEventQueue);
211 	     *evp != NULL;
212 	     evp = &(*evp)->ev_link)
213 	{
214 		if (*evp == ev)
215 			break;
216 	}
217 
218 	/* now remove it */
219 	if (*evp != NULL)
220 	{
221 		ENTER_CRITICAL();
222 		*evp = ev->ev_link;
223 		ev->ev_link = SmFreeEventList;
224 		SmFreeEventList = ev;
225 		LEAVE_CRITICAL();
226 	}
227 
228 	/* restore clocks and pick up anything spare */
229 	if (wasblocked == 0)
230 		(void) sm_releasesignal(SIGALRM);
231 	if (SmEventQueue != NULL)
232 		(void) kill(getpid(), SIGALRM);
233 	else
234 	{
235 		/* nothing left in event queue, no need for an alarm */
236 # if SM_CONF_SETITIMER
237 		clr.it_interval.tv_sec = 0;
238 		clr.it_interval.tv_usec = 0;
239 		clr.it_value.tv_sec = 0;
240 		clr.it_value.tv_usec = 0;
241 		(void) setitimer(ITIMER_REAL, &clr, NULL);
242 # else /* SM_CONF_SETITIMER */
243 		(void) alarm(0);
244 # endif /* SM_CONF_SETITIMER */
245 	}
246 }
247 /*
248 **  SM_CLEAR_EVENTS -- remove all events from the event queue.
249 **
250 **	Parameters:
251 **		none.
252 **
253 **	Returns:
254 **		none.
255 */
256 
257 void
258 sm_clear_events()
259 {
260 	register SM_EVENT *ev;
261 #if SM_CONF_SETITIMER
262 	struct itimerval clr;
263 #endif /* SM_CONF_SETITIMER */
264 	int wasblocked;
265 
266 	/* nothing will be left in event queue, no need for an alarm */
267 #if SM_CONF_SETITIMER
268 	clr.it_interval.tv_sec = 0;
269 	clr.it_interval.tv_usec = 0;
270 	clr.it_value.tv_sec = 0;
271 	clr.it_value.tv_usec = 0;
272 	(void) setitimer(ITIMER_REAL, &clr, NULL);
273 #else /* SM_CONF_SETITIMER */
274 	(void) alarm(0);
275 #endif /* SM_CONF_SETITIMER */
276 
277 	if (SmEventQueue == NULL)
278 		return;
279 
280 	wasblocked = sm_blocksignal(SIGALRM);
281 
282 	/* find the end of the EventQueue */
283 	for (ev = SmEventQueue; ev->ev_link != NULL; ev = ev->ev_link)
284 		continue;
285 
286 	ENTER_CRITICAL();
287 	ev->ev_link = SmFreeEventList;
288 	SmFreeEventList = SmEventQueue;
289 	SmEventQueue = NULL;
290 	LEAVE_CRITICAL();
291 
292 	/* restore clocks and pick up anything spare */
293 	if (wasblocked == 0)
294 		(void) sm_releasesignal(SIGALRM);
295 }
296 /*
297 **  SM_TICK -- take a clock tick
298 **
299 **	Called by the alarm clock.  This routine runs events as needed.
300 **	Always called as a signal handler, so we assume that SIGALRM
301 **	has been blocked.
302 **
303 **	Parameters:
304 **		One that is ignored; for compatibility with signal handlers.
305 **
306 **	Returns:
307 **		none.
308 **
309 **	Side Effects:
310 **		calls the next function in EventQueue.
311 **
312 **	NOTE:	THIS CAN BE CALLED FROM A SIGNAL HANDLER.  DO NOT ADD
313 **		ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
314 **		DOING.
315 */
316 
317 /* ARGSUSED */
318 SIGFUNC_DECL
319 sm_tick(sig)
320 	int sig;
321 {
322 	register SM_EVENT *ev;
323 	pid_t mypid;
324 	int save_errno = errno;
325 #if SM_CONF_SETITIMER
326 	struct itimerval clr;
327 	struct timeval now;
328 #else /* SM_CONF_SETITIMER */
329 	register time_t now;
330 #endif /* SM_CONF_SETITIMER */
331 
332 #if SM_CONF_SETITIMER
333 	clr.it_interval.tv_sec = 0;
334 	clr.it_interval.tv_usec = 0;
335 	clr.it_value.tv_sec = 0;
336 	clr.it_value.tv_usec = 0;
337 	(void) setitimer(ITIMER_REAL, &clr, NULL);
338 	gettimeofday(&now, NULL);
339 #else /* SM_CONF_SETITIMER */
340 	(void) alarm(0);
341 	now = time(NULL);
342 #endif /* SM_CONF_SETITIMER */
343 
344 	FIX_SYSV_SIGNAL(sig, sm_tick);
345 	errno = save_errno;
346 	CHECK_CRITICAL(sig);
347 
348 	mypid = getpid();
349 	while (PendingSignal != 0)
350 	{
351 		int sigbit = 0;
352 		int sig = 0;
353 
354 		if (bitset(PEND_SIGHUP, PendingSignal))
355 		{
356 			sigbit = PEND_SIGHUP;
357 			sig = SIGHUP;
358 		}
359 		else if (bitset(PEND_SIGINT, PendingSignal))
360 		{
361 			sigbit = PEND_SIGINT;
362 			sig = SIGINT;
363 		}
364 		else if (bitset(PEND_SIGTERM, PendingSignal))
365 		{
366 			sigbit = PEND_SIGTERM;
367 			sig = SIGTERM;
368 		}
369 		else if (bitset(PEND_SIGUSR1, PendingSignal))
370 		{
371 			sigbit = PEND_SIGUSR1;
372 			sig = SIGUSR1;
373 		}
374 		else
375 		{
376 			/* If we get here, we are in trouble */
377 			abort();
378 		}
379 		PendingSignal &= ~sigbit;
380 		kill(mypid, sig);
381 	}
382 
383 #if SM_CONF_SETITIMER
384 	gettimeofday(&now, NULL);
385 #else /* SM_CONF_SETITIMER */
386 	now = time(NULL);
387 #endif /* SM_CONF_SETITIMER */
388 	while ((ev = SmEventQueue) != NULL &&
389 	       (ev->ev_pid != mypid ||
390 #if SM_CONF_SETITIMER
391 		timercmp(&ev->ev_time, &now, <=)
392 #else /* SM_CONF_SETITIMER */
393 		ev->ev_time <= now
394 #endif /* SM_CONF_SETITIMER */
395 		))
396 	{
397 		void (*f)__P((int));
398 		int arg;
399 		pid_t pid;
400 
401 		/* process the event on the top of the queue */
402 		ev = SmEventQueue;
403 		SmEventQueue = SmEventQueue->ev_link;
404 
405 		/* we must be careful in here because ev_func may not return */
406 		f = ev->ev_func;
407 		arg = ev->ev_arg;
408 		pid = ev->ev_pid;
409 		ENTER_CRITICAL();
410 		ev->ev_link = SmFreeEventList;
411 		SmFreeEventList = ev;
412 		LEAVE_CRITICAL();
413 		if (pid != getpid())
414 			continue;
415 		if (SmEventQueue != NULL)
416 		{
417 #if SM_CONF_SETITIMER
418 			if (timercmp(&SmEventQueue->ev_time, &now, >))
419 			{
420 				timersub(&SmEventQueue->ev_time, &now,
421 					 &clr.it_value);
422 				clr.it_interval.tv_sec = 0;
423 				clr.it_interval.tv_usec = 0;
424 				if (clr.it_value.tv_sec < 0)
425 					clr.it_value.tv_sec = 0;
426 				if (clr.it_value.tv_sec == 0 &&
427 				    clr.it_value.tv_usec == 0)
428 					clr.it_value.tv_usec = 1000;
429 				(void) setitimer(ITIMER_REAL, &clr, NULL);
430 			}
431 			else
432 			{
433 				clr.it_interval.tv_sec = 0;
434 				clr.it_interval.tv_usec = 0;
435 				clr.it_value.tv_sec = 3;
436 				clr.it_value.tv_usec = 0;
437 				(void) setitimer(ITIMER_REAL, &clr, NULL);
438 			}
439 #else /* SM_CONF_SETITIMER */
440 			if (SmEventQueue->ev_time > now)
441 				(void) alarm((unsigned) (SmEventQueue->ev_time
442 							 - now));
443 			else
444 				(void) alarm(3);
445 #endif /* SM_CONF_SETITIMER */
446 		}
447 
448 		/* call ev_func */
449 		errno = save_errno;
450 		(*f)(arg);
451 #if SM_CONF_SETITIMER
452 		clr.it_interval.tv_sec = 0;
453 		clr.it_interval.tv_usec = 0;
454 		clr.it_value.tv_sec = 0;
455 		clr.it_value.tv_usec = 0;
456 		(void) setitimer(ITIMER_REAL, &clr, NULL);
457 		gettimeofday(&now, NULL);
458 #else /* SM_CONF_SETITIMER */
459 		(void) alarm(0);
460 		now = time(NULL);
461 #endif /* SM_CONF_SETITIMER */
462 	}
463 	if (SmEventQueue != NULL)
464 	{
465 #if SM_CONF_SETITIMER
466 		timersub(&SmEventQueue->ev_time, &now, &clr.it_value);
467 		clr.it_interval.tv_sec = 0;
468 		clr.it_interval.tv_usec = 0;
469 		if (clr.it_value.tv_sec < 0)
470 			clr.it_value.tv_sec = 0;
471 		if (clr.it_value.tv_sec == 0 && clr.it_value.tv_usec == 0)
472 			clr.it_value.tv_usec = 1000;
473 		(void) setitimer(ITIMER_REAL, &clr, NULL);
474 #else /* SM_CONF_SETITIMER */
475 		(void) alarm((unsigned) (SmEventQueue->ev_time - now));
476 #endif /* SM_CONF_SETITIMER */
477 	}
478 	errno = save_errno;
479 	return SIGFUNC_RETURN;
480 }
481 /*
482 **  SLEEP -- a version of sleep that works with this stuff
483 **
484 **	Because Unix sleep uses the alarm facility, I must reimplement
485 **	it here.
486 **
487 **	Parameters:
488 **		intvl -- time to sleep.
489 **
490 **	Returns:
491 **		zero.
492 **
493 **	Side Effects:
494 **		waits for intvl time.  However, other events can
495 **		be run during that interval.
496 */
497 
498 
499 # if !HAVE_NANOSLEEP
500 static void	sm_endsleep __P((int));
501 static bool	volatile SmSleepDone;
502 # endif /* !HAVE_NANOSLEEP */
503 
504 #ifndef SLEEP_T
505 # define SLEEP_T	unsigned int
506 #endif /* ! SLEEP_T */
507 
508 SLEEP_T
509 sleep(intvl)
510 	unsigned int intvl;
511 {
512 #if HAVE_NANOSLEEP
513 	struct timespec rqtp;
514 
515 	if (intvl == 0)
516 		return (SLEEP_T) 0;
517 	rqtp.tv_sec = intvl;
518 	rqtp.tv_nsec = 0;
519 	nanosleep(&rqtp, NULL);
520 	return (SLEEP_T) 0;
521 #else /* HAVE_NANOSLEEP */
522 	int was_held;
523 	SM_EVENT *ev;
524 #if _FFR_SLEEP_USE_SELECT > 0
525 	int r;
526 # if _FFR_SLEEP_USE_SELECT > 0
527 	struct timeval sm_io_to;
528 # endif /* _FFR_SLEEP_USE_SELECT > 0 */
529 #endif /* _FFR_SLEEP_USE_SELECT > 0 */
530 #if SM_CONF_SETITIMER
531 	struct timeval now, begin, diff;
532 # if _FFR_SLEEP_USE_SELECT > 0
533 	struct timeval slpv;
534 # endif /* _FFR_SLEEP_USE_SELECT > 0 */
535 #else /*  SM_CONF_SETITIMER */
536 	time_t begin, now;
537 #endif /*  SM_CONF_SETITIMER */
538 
539 	if (intvl == 0)
540 		return (SLEEP_T) 0;
541 #if defined(_FFR_MAX_SLEEP_TIME) && _FFR_MAX_SLEEP_TIME > 2
542 	if (intvl > _FFR_MAX_SLEEP_TIME)
543 	{
544 		syslog(LOG_ERR, "sleep: interval=%u exceeds max value %d",
545 			intvl, _FFR_MAX_SLEEP_TIME);
546 # if 0
547 		SM_ASSERT(intvl < (unsigned int) INT_MAX);
548 # endif /* 0 */
549 		intvl = _FFR_MAX_SLEEP_TIME;
550 	}
551 #endif /* defined(_FFR_MAX_SLEEP_TIME) && _FFR_MAX_SLEEP_TIME > 2 */
552 	SmSleepDone = false;
553 
554 #if SM_CONF_SETITIMER
555 # if _FFR_SLEEP_USE_SELECT > 0
556 	slpv.tv_sec = intvl;
557 	slpv.tv_usec = 0;
558 # endif /* _FFR_SLEEP_USE_SELECT > 0 */
559 	(void) gettimeofday(&now, NULL);
560 	begin = now;
561 #else /*  SM_CONF_SETITIMER */
562 	now = begin = time(NULL);
563 #endif /*  SM_CONF_SETITIMER */
564 
565 	ev = sm_setevent((time_t) intvl, sm_endsleep, 0);
566 	if (ev == NULL)
567 	{
568 		/* COMPLAIN */
569 #if 0
570 		syslog(LOG_ERR, "sleep: sm_setevent(%u) failed", intvl);
571 #endif /* 0 */
572 		SmSleepDone = true;
573 	}
574 	was_held = sm_releasesignal(SIGALRM);
575 
576 	while (!SmSleepDone)
577 	{
578 #if SM_CONF_SETITIMER
579 		(void) gettimeofday(&now, NULL);
580 		timersub(&now, &begin, &diff);
581 		if (diff.tv_sec < 0 ||
582 		    (diff.tv_sec == 0 && diff.tv_usec == 0))
583 			break;
584 # if _FFR_SLEEP_USE_SELECT > 0
585 		timersub(&slpv, &diff, &sm_io_to);
586 # endif /* _FFR_SLEEP_USE_SELECT > 0 */
587 #else /* SM_CONF_SETITIMER */
588 		now = time(NULL);
589 
590 		/*
591 		**  Check whether time expired before signal is released.
592 		**  Due to the granularity of time() add 1 to be on the
593 		**  safe side.
594 		*/
595 
596 		if (!(begin + (time_t) intvl + 1 > now))
597 			break;
598 # if _FFR_SLEEP_USE_SELECT > 0
599 		sm_io_to.tv_sec = intvl - (now - begin);
600 		if (sm_io_to.tv_sec <= 0)
601 			sm_io_to.tv_sec = 1;
602 		sm_io_to.tv_usec = 0;
603 # endif /* _FFR_SLEEP_USE_SELECT > 0 */
604 #endif /* SM_CONF_SETITIMER */
605 #if _FFR_SLEEP_USE_SELECT > 0
606 		if (intvl <= _FFR_SLEEP_USE_SELECT)
607 		{
608 			r = select(0, NULL, NULL, NULL, &sm_io_to);
609 			if (r == 0)
610 				break;
611 		}
612 		else
613 #endif /* _FFR_SLEEP_USE_SELECT > 0 */
614 		(void) pause();
615 	}
616 
617 	/* if out of the loop without the event being triggered remove it */
618 	if (!SmSleepDone)
619 		sm_clrevent(ev);
620 	if (was_held > 0)
621 		(void) sm_blocksignal(SIGALRM);
622 	return (SLEEP_T) 0;
623 #endif /* HAVE_NANOSLEEP */
624 }
625 
626 #if !HAVE_NANOSLEEP
627 static void
628 sm_endsleep(ignore)
629 	int ignore;
630 {
631 	/*
632 	**  NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER.  DO NOT ADD
633 	**	ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
634 	**	DOING.
635 	*/
636 
637 	SmSleepDone = true;
638 }
639 #endif /* !HAVE_NANOSLEEP */
640 
641