1/*
2 *   $Id: timer.c,v 1.9 2005/10/18 19:17:29 lutchann Exp $
3 *
4 *   Authors:
5 *    Pedro Roque		<roque@di.fc.ul.pt>
6 *    Lars Fenneberg		<lf@elemental.net>
7 *
8 *   This software is Copyright 1996-2000 by the above mentioned author(s),
9 *   All Rights Reserved.
10 *
11 *   The license which is distributed with this software in the file COPYRIGHT
12 *   applies to this software. If your distribution is missing this file, you
13 *   may request it from <pekkas@netcore.fi>.
14 *
15 */
16
17#include <config.h>
18#include <includes.h>
19#include <radvd.h>
20
21static struct timer_lst timers_head = {
22	{LONG_MAX, LONG_MAX},
23	NULL, NULL,
24	&timers_head, &timers_head
25};
26
27static void alarm_handler(int sig);
28int inline check_time_diff(struct timer_lst *tm, struct timeval tv);
29
30static void
31schedule_timer(void)
32{
33	struct timer_lst *tm = timers_head.next;
34	struct timeval tv;
35
36	gettimeofday(&tv, NULL);
37
38	if (tm != &timers_head)
39	{
40		struct itimerval next;
41
42	        memset(&next, 0, sizeof(next));
43
44	        timersub(&tm->expires, &tv, &next.it_value);
45
46		signal(SIGALRM, alarm_handler);
47
48		if ((next.it_value.tv_sec > 0) ||
49				((next.it_value.tv_sec == 0) && (next.it_value.tv_usec > 0)))
50		{
51			dlog(LOG_DEBUG, 4, "calling alarm: %ld secs, %ld usecs",
52					next.it_value.tv_sec, next.it_value.tv_usec);
53
54			if(setitimer(ITIMER_REAL, &next,  NULL))
55				flog(LOG_WARNING, "schedule_timer setitimer for %ld.%ld failed: %s",
56					next.it_value.tv_sec, next.it_value.tv_usec, strerror(errno));
57		}
58		else
59		{
60			dlog(LOG_DEBUG, 4, "next timer has already expired, queueing signal");
61			kill(getpid(), SIGALRM);
62		}
63	}
64}
65
66void
67set_timer(struct timer_lst *tm, double secs)
68{
69	struct timeval tv;
70	struct timer_lst *lst;
71	sigset_t bmask, oldmask;
72	struct timeval firein;
73
74	dlog(LOG_DEBUG, 3, "setting timer: %.2f secs", secs);
75
76	firein.tv_sec = (long)secs;
77	firein.tv_usec = (long)((secs - (double)firein.tv_sec) * 1000000);
78
79	dlog(LOG_DEBUG, 5, "setting timer: %ld secs %ld usecs", firein.tv_sec, firein.tv_usec);
80
81	gettimeofday(&tv, NULL);
82	timeradd(&tv, &firein, &tm->expires);
83
84	sigemptyset(&bmask);
85	sigaddset(&bmask, SIGALRM);
86	sigprocmask(SIG_BLOCK, &bmask, &oldmask);
87
88	lst = &timers_head;
89
90	/* the timers are in the list in the order they expire, the soonest first */
91	do {
92		lst = lst->next;
93	} while ((tm->expires.tv_sec > lst->expires.tv_sec) ||
94		 ((tm->expires.tv_sec == lst->expires.tv_sec) &&
95		  (tm->expires.tv_usec > lst->expires.tv_usec)));
96
97	tm->next = lst;
98	tm->prev = lst->prev;
99	lst->prev = tm;
100	tm->prev->next = tm;
101
102	dlog(LOG_DEBUG, 5, "calling schedule_timer from set_timer context");
103	schedule_timer();
104
105	sigprocmask(SIG_SETMASK, &oldmask, NULL);
106}
107
108void
109clear_timer(struct timer_lst *tm)
110{
111	sigset_t bmask, oldmask;
112
113	sigemptyset(&bmask);
114	sigaddset(&bmask, SIGALRM);
115	sigprocmask(SIG_BLOCK, &bmask, &oldmask);
116
117	tm->prev->next = tm->next;
118	tm->next->prev = tm->prev;
119
120	tm->prev = tm->next = NULL;
121
122	dlog(LOG_DEBUG, 5, "calling schedule_timer from clear_timer context");
123	schedule_timer();
124
125	sigprocmask(SIG_SETMASK, &oldmask, NULL);
126}
127
128static void
129alarm_handler(int sig)
130{
131	struct timer_lst *tm, *back;
132	struct timeval tv;
133	gettimeofday(&tv, NULL);
134	tm = timers_head.next;
135
136	/*
137	 * This handler is called when the alarm goes off, so at least one of
138	 * the interfaces' timers should satisfy the while condition.
139	 *
140	 * Sadly, this is not always the case, at least on Linux kernels:
141	 * see http://lkml.org/lkml/2005/4/29/163. :-(.  It seems some
142	 * versions of timers are not accurate and get called up to a couple of
143	 * hundred microseconds before they expire.
144	 *
145	 * Therefore we allow some inaccuracy here; it's sufficient for us
146	 * that a timer should go off in a millisecond.
147	 */
148
149	/* unused timers are initialized to LONG_MAX so we skip them */
150	while (tm->expires.tv_sec != LONG_MAX && check_time_diff(tm, tv))
151	{
152		tm->prev->next = tm->next;
153		tm->next->prev = tm->prev;
154
155		back = tm;
156		tm = tm->next;
157		back->prev = back->next = NULL;
158
159		(*back->handler)(back->data);
160	}
161
162	dlog(LOG_DEBUG, 5, "calling schedule_timer from alarm_handler context");
163	schedule_timer();
164}
165
166
167void
168init_timer(struct timer_lst *tm, void (*handler)(void *), void *data)
169{
170	memset(tm, 0, sizeof(struct timer_lst));
171	tm->handler = handler;
172	tm->data = data;
173}
174
175int inline
176check_time_diff(struct timer_lst *tm, struct timeval tv)
177{
178	struct itimerval diff;
179	memset(&diff, 0, sizeof(diff));
180
181	#define ALLOW_CLOCK_USEC 1000
182
183	timersub(&tm->expires, &tv, &diff.it_value);
184	dlog(LOG_DEBUG, 5, "check_time_diff, difference: %ld sec + %ld usec",
185		diff.it_value.tv_sec, diff.it_value.tv_usec);
186
187	if (diff.it_value.tv_sec <= 0) {
188		/* already gone, this is the "good" case */
189		if (diff.it_value.tv_sec < 0)
190			return 1;
191#ifdef __linux__ /* we haven't seen this on other OSes */
192		/* still OK if the expiry time is not too much in the future */
193		else if (diff.it_value.tv_usec > 0 &&
194		            diff.it_value.tv_usec <= ALLOW_CLOCK_USEC) {
195			dlog(LOG_DEBUG, 4, "alarm_handler clock was probably off by %ld usec, allowing %u",
196			     tm->expires.tv_usec-tv.tv_usec, ALLOW_CLOCK_USEC);
197			return 2;
198		}
199#endif /* __linux__ */
200		else /* scheduled intentionally in the future? */
201			return 0;
202	}
203	return 0;
204}
205