xref: /NextBSD/usr.sbin/notifyd/timer.c (revision 33da5adc555b3bc29986eeadca03829e4ad06b1e)
1 /*
2  * Copyright (c) 2009-2010 Apple Inc. All rights reserved.
3  *
4  * @APPLE_LICENSE_HEADER_START@
5  *
6  * This file contains Original Code and/or Modifications of Original Code
7  * as defined in and that are subject to the Apple Public Source License
8  * Version 2.0 (the 'License'). You may not use this file except in
9  * compliance with the License. Please obtain a copy of the License at
10  * http://www.opensource.apple.com/apsl/ and read it before using this
11  * file.
12  *
13  * The Original Code and all software distributed under the License are
14  * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15  * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16  * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18  * Please see the License for the specific language governing rights and
19  * limitations under the License.
20  *
21  * @APPLE_LICENSE_HEADER_END@
22  */
23 
24 #include <stdlib.h>
25 #include <string.h>
26 #include <Block.h>
27 #include "timer.h"
28 
29 #define MINUTE 60
30 #define HOUR 3600
31 #define DAY 86400
32 
33 static const uint8_t mlen[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
34 
35 /*
36  * Timed events
37  *
38  * Supported event types:
39  *
40  * Oneshot
41  * Every n seconds/minutes/hours/days/weeks
42  * Specific day of the month, every n months
43  * Specific weekday following specific day of the month, every n months
44  *
45  */
46 static time_t
timer_next(timer_event_t * t,time_t now)47 timer_next(timer_event_t *t, time_t now)
48 {
49 	uint32_t y, m;
50 	int32_t d, x, a, b, dd, wd;
51 	struct tm tmp;
52 	time_t next, tt, tod;
53 
54 	if (t == NULL) return 0;
55 
56 	switch (t->type)
57 	{
58 		case TIME_EVENT_ONESHOT:
59 		{
60 			/*
61 			 * oneshot time event
62 			 */
63 			if (t->start < now) return 0;
64 			return t->start;
65 		}
66 		case TIME_EVENT_CLOCK:
67 		{
68 			/*
69 			 * event recurs every t->freq seconds
70 			 */
71 
72 			/* t->end is the cut-off.  If it's in the past, return 0 */
73 			if ((t->end != 0) && (t->end < now)) return 0;
74 
75 			/* If the start time is in the future, that's the next occurrence */
76 			if (t->start >= now) return t->start;
77 
78 			/* shouldn't happen, as TIME_EVENT_CLOCK should always recur */
79 			if (t->freq == 0) return 0;
80 
81 			x = ((t->freq - 1) + now - t->start) / t->freq;
82 			next = t->start + (x * t->freq);
83 			return next;
84 		}
85 		case TIME_EVENT_CAL:
86 		{
87 			/*
88 			 * event recurs every t->freq months
89 			 * t->base gives us the starting month and year, and the time of day
90 			 * t->day specifies the day of the month (negative means relative to last day of the month)
91 			 * t->day is > 100 or < 100, then it means a weekday
92 			 * 101 = first monday, 102 = first tuesday, ..., 108 = second monday, and so on
93 			 * -101 = last monday, -102 = last tuesday, ..., -108 = second last monday, and so on
94 			 */
95 
96 			/* t->end is the cut-off.  If it's in the past, return 0 */
97 			if ((t->end != 0) && (t->end < now)) return 0;
98 
99 			/* If the start time is in the future, that's the next occurrence */
100 			if (t->start >= now) return t->start;
101 
102 			next = t->start;
103 
104 			/* If t->next is set from the last time we ran, and it is in the past, we can start there. */
105 			if ((t->next > 0) && (t->next < now)) next = t->next;
106 
107 			while (next < now)
108 			{
109 				/* determine year, month, and time-of-day (clock time) of the next occurance */
110 				memset(&tmp, 0, sizeof(struct tm));
111 				localtime_r((const time_t *)&(next), &tmp);
112 				y = tmp.tm_year;
113 				m = tmp.tm_mon;
114 				tod = tmp.tm_sec + (MINUTE * tmp.tm_min) + (HOUR * tmp.tm_hour);
115 
116 				m += t->freq;
117 				if (m > 11)
118 				{
119 					y += (m / 12);
120 					m %= 12;
121 				}
122 
123 				/* we now have a year (y), a month (m), and a time of day (tod) */
124 
125 				if (t->day > 0)
126 				{
127 					if (t->day < 100)
128 					{
129 						/* easy case: day is the date of the month */
130 
131 						memset(&tmp, 0, sizeof(struct tm));
132 						tmp.tm_year = y;
133 						tmp.tm_mon = m;
134 						tmp.tm_mday = t->day;
135 						tmp.tm_isdst = -1;
136 						next = mktime(&tmp) + tod;
137 						continue;
138 					}
139 					else
140 					{
141 						/* t->day is a weekday */
142 
143 						wd = t->day - 100;
144 
145 						/* start by finding out the weekday of the first of the month */
146 						memset(&tmp, 0, sizeof(struct tm));
147 						tmp.tm_year = y;
148 						tmp.tm_mon = m;
149 						tmp.tm_mday = 1;
150 						tmp.tm_isdst = -1;
151 						tt = mktime(&tmp);
152 						localtime_r((const time_t *)&tt, &tmp);
153 
154 						if (tmp.tm_wday == 0) tmp.tm_wday = 7;
155 
156 						x = 0;
157 						if (tmp.tm_wday > (wd % 7)) x = (wd + 7) - tmp.tm_wday;
158 						else x = wd - tmp.tm_wday;
159 
160 						tmp.tm_mday += x;
161 						tmp.tm_isdst = -1;
162 						next = mktime(&tmp) + tod;
163 						continue;
164 					}
165 				}
166 
167 				if (t->day > -100)
168 				{
169 					/* nth day from the end of the month */
170 					if (m == 1)
171 					{
172 						/* determine weekday of last day of February (== March 0) */
173 						memset(&tmp, 0, sizeof(struct tm));
174 						tmp.tm_year = y;
175 						tmp.tm_mon = 2;
176 						tmp.tm_mday = 0;
177 						tmp.tm_isdst = -1;
178 						tt = mktime(&tmp);
179 						memset(&tmp, 0, sizeof(struct tm));
180 						localtime_r((const time_t *)&(tt), &tmp);
181 						d = tmp.tm_mday + t->day;
182 					}
183 					else
184 					{
185 						d = mlen[m] + t->day;
186 					}
187 
188 					memset(&tmp, 0, sizeof(struct tm));
189 					tmp.tm_year = y;
190 					tmp.tm_mon = m;
191 					tmp.tm_mday = d;
192 					tmp.tm_isdst = -1;
193 					next = mktime(&tmp) + tod;
194 					continue;
195 				}
196 
197 				/* t->day is a weekday relative to the end of the month */
198 				if (m == 1)
199 				{
200 					/* determine weekday of last day of February (== March 0) */
201 					memset(&tmp, 0, sizeof(struct tm));
202 					tmp.tm_year = y;
203 					tmp.tm_mon = 2;
204 					tmp.tm_mday = 0;
205 					tmp.tm_isdst = -1;
206 					tt = mktime(&tmp);
207 					memset(&tmp, 0, sizeof(struct tm));
208 					localtime_r((const time_t *)&(tt), &tmp);
209 					d = tmp.tm_mday;
210 				}
211 				else
212 				{
213 					d = mlen[m];
214 				}
215 
216 				memset(&tmp, 0, sizeof(struct tm));
217 				tmp.tm_year = y;
218 				tmp.tm_mon = m;
219 				tmp.tm_mday = d;
220 				tmp.tm_isdst = -1;
221 
222 				dd = -1 * (t->day + 100);
223 				a = dd % 7;
224 				b = (dd + 6) / 7;
225 				if (a <= tmp.tm_wday) b--;
226 				tmp.tm_mday = ((a - tmp.tm_wday) + d) - (b * 7);
227 				next = mktime(&tmp) + tod;
228 			}
229 
230 			t->next = next;
231 			return next;
232 		}
233 		default:
234 		{
235 			return 0;
236 		}
237 	}
238 
239 	return 0;
240 }
241 
242 /*
243  * This does the actual free.
244  * It is dispatched on the timer's dispatch source queue to make it safe.
245  */
246 static void
timer_free(timer_event_t * t)247 timer_free(timer_event_t *t)
248 {
249 	if (t == NULL) return;
250 	if (t->deactivation_handler != NULL) Block_release(t->deactivation_handler);
251 	if (t->contextp != NULL) free(t->contextp);
252 
253 	dispatch_release(t->t_src);
254 	dispatch_release(t->t_queue);
255 
256 	memset(t, 0, sizeof(timer_event_t));
257 	free(t);
258 }
259 
260 void
timer_close(timer_event_t * t)261 timer_close(timer_event_t *t)
262 {
263 	if (t == NULL) return;
264 
265 	if (t->t_src != NULL) dispatch_source_cancel(t->t_src);
266 
267 	/*
268 	 * We need to make sure that the source's event handler isn't currently running
269 	 * before we free the timer.  We let the source's queue do the actual free.
270 	 */
271 	dispatch_async(t->t_queue, ^{ timer_free(t); });
272 }
273 
274 timer_event_t *
timer_oneshot(time_t when,dispatch_queue_t queue)275 timer_oneshot(time_t when, dispatch_queue_t queue)
276 {
277 	timer_event_t *t;
278 	time_t now;
279 	dispatch_time_t trigger;
280 
281 	/* refuse a trigger time in the past */
282 	now = time(0);
283 	if (when <= now) return NULL;
284 
285 	t = calloc(1, sizeof(timer_event_t));
286 	if (t == NULL) return NULL;
287 
288 	dispatch_retain(queue);
289 
290 	t->type = TIME_EVENT_ONESHOT;
291 	t->start = when;
292 	t->t_queue = queue;
293 
294 	t->t_src = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
295 	t->src = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, queue);
296 
297 	trigger = dispatch_walltime(NULL, (t->start - now) * NSEC_PER_SEC);
298 	dispatch_source_set_timer(t->t_src, trigger, NSEC_PER_SEC, 0);
299 
300 	dispatch_source_set_event_handler(t->t_src, ^{
301 		dispatch_source_merge_data(t->src, 1);
302 		dispatch_source_cancel(t->t_src);
303 		if (t->deactivation_handler != NULL)
304 		{
305 			dispatch_async(t->t_queue, ^{ t->deactivation_handler(); });
306 		}
307 	});
308 
309 	dispatch_resume(t->t_src);
310 	return t;
311 }
312 
313 void
314 timer_set_deactivation_handler(timer_event_t *t, void(^handler)())
315 {
316 	if (t == NULL) return;
317 
318 	if (t->deactivation_handler != NULL) Block_release(t->deactivation_handler);
319 	t->deactivation_handler = Block_copy(handler);
320 }
321 
322 timer_event_t *
timer_clock(time_t first,time_t freq_sec,time_t end,dispatch_queue_t queue)323 timer_clock(time_t first, time_t freq_sec, time_t end, dispatch_queue_t queue)
324 {
325 	timer_event_t *t;
326 	time_t now;
327 	dispatch_time_t trigger;
328 	int64_t x;
329 
330 	if (freq_sec == 0) return timer_oneshot(first, queue);
331 
332 	now = time(0);
333 
334 	t = calloc(1, sizeof(timer_event_t));
335 	if (t == NULL) return NULL;
336 
337 	t->type = TIME_EVENT_CLOCK;
338 
339 	if (first < now)
340 	{
341 		x = ((freq_sec - 1) + now - first) / freq_sec;
342 		t->start = first + (x * freq_sec);
343 	}
344 	else
345 	{
346 		t->start = first;
347 	}
348 
349 	t->end = end;
350 	t->freq = freq_sec;
351 	t->t_queue = queue;
352 
353 	t->t_src = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
354 	t->src = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, queue);
355 
356 	trigger = dispatch_walltime(NULL, (t->start - now) * NSEC_PER_SEC);
357 	dispatch_source_set_timer(t->t_src, trigger, freq_sec * NSEC_PER_SEC, 0);
358 
359 	dispatch_source_set_event_handler(t->t_src, ^{
360 		unsigned long n = dispatch_source_get_data(t->t_src);
361 		dispatch_source_merge_data(t->src, n);
362 
363 		/* deactivate if this is the last time we want to trigger the client source */
364 		if ((t->end > 0) && (t->end < (time(0) + freq_sec)))
365 		{
366 			dispatch_source_cancel(t->t_src);
367 			if (t->deactivation_handler != NULL)
368 			{
369 				dispatch_async(t->t_queue, ^{ t->deactivation_handler(); });
370 			}
371 		}
372 	});
373 
374 	dispatch_resume(t->t_src);
375 
376 	return t;
377 }
378 
379 timer_event_t *
timer_calendar(time_t first,time_t freq_mth,time_t end,int day,dispatch_queue_t queue)380 timer_calendar(time_t first, time_t freq_mth, time_t end, int day, dispatch_queue_t queue)
381 {
382 	timer_event_t *t;
383 	time_t next, now;
384 	dispatch_time_t trigger;
385 
386 	if (freq_mth == 0) return timer_oneshot(first, queue);
387 
388 	now = time(0);
389 
390 	t = calloc(1, sizeof(timer_event_t));
391 	if (t == NULL) return NULL;
392 
393 	t->type = TIME_EVENT_CAL;
394 	t->start = first;
395 	t->day = day;
396 	t->end = end;
397 	t->freq = freq_mth;
398 	t->t_queue = queue;
399 
400 	t->t_src = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
401 	t->src = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, queue);
402 
403 	next = timer_next(t, now);
404 	trigger = dispatch_walltime(NULL, (next - now) * NSEC_PER_SEC);
405 	dispatch_source_set_timer(t->t_src, trigger, NSEC_PER_SEC, 0);
406 
407 	dispatch_source_set_event_handler(t->t_src, ^{
408 		unsigned long n = dispatch_source_get_data(t->t_src);
409 		dispatch_source_merge_data(t->src, n);
410 
411 		time_t _now = time(0);
412 		time_t x = timer_next(t, now);
413 
414 		/* deactivate when there is no next time */
415 		if (x == 0)
416 		{
417 			dispatch_source_cancel(t->t_src);
418 			if (t->deactivation_handler != NULL)
419 			{
420 				dispatch_async(t->t_queue, ^{ t->deactivation_handler(); });
421 			}
422 		}
423 		else
424 		{
425 			dispatch_source_set_timer(t->t_src, dispatch_walltime(NULL, (x - _now) * NSEC_PER_SEC), NSEC_PER_SEC, 0);
426 		}
427 	});
428 
429 	dispatch_resume(t->t_src);
430 
431 	return t;
432 }
433 
434 timer_event_t *
timer_calendar_long(uint32_t start_year,uint32_t start_month,uint32_t start_day,uint32_t start_hour,uint32_t start_min,uint32_t start_sec,time_t freq,int day,uint32_t end_year,uint32_t end_month,uint32_t end_day,uint32_t end_hour,uint32_t end_min,uint32_t end_sec,dispatch_queue_t queue)435 timer_calendar_long(uint32_t start_year, uint32_t start_month, uint32_t start_day, uint32_t start_hour, uint32_t start_min, uint32_t start_sec, time_t freq, int day, uint32_t end_year, uint32_t end_month, uint32_t end_day, uint32_t end_hour, uint32_t end_min, uint32_t end_sec, dispatch_queue_t queue)
436 {
437 	struct tm tmp;
438 	time_t first, last;
439 
440 	memset(&tmp, 0, sizeof(struct tm));
441 	tmp.tm_year = start_year - 1900;
442 	tmp.tm_mon = start_month;
443 	tmp.tm_mday = start_day;
444 	tmp.tm_isdst = -1;
445 	tmp.tm_hour = start_hour;
446 	tmp.tm_min = start_min;
447 	tmp.tm_sec = start_sec;
448 
449 	first = mktime(&tmp);
450 
451 	if (freq == 0) return timer_oneshot(first, queue);
452 
453 	memset(&tmp, 0, sizeof(struct tm));
454 	tmp.tm_year = end_year;
455 	tmp.tm_mon = end_month;
456 	tmp.tm_mday = end_day;
457 	tmp.tm_isdst = -1;
458 	tmp.tm_hour = end_hour;
459 	tmp.tm_min = end_min;
460 	tmp.tm_sec = end_sec;
461 
462 	last = mktime(&tmp);
463 
464 	return timer_calendar(first, freq, day, last, queue);
465 }
466