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