source: ogServer-Git/src/schedule.c @ 79e7e2b

Last change on this file since 79e7e2b was 42c2253, checked in by OpenGnSys Support Team <soporte-og@…>, 3 years ago

schedule: fix daylight saving problem with mktime()

mktime modifies the struct tm it receives and takes into account whether DST is
active or not (tm_isdst). tm_isdst == 0 adjusts the time, which causes the time
mismatch error.

All fields are being initialized to 0 and therefore it is assumed that the time
that has been passed is not in daylight saving time.

When the value is negative in tm.tm_isdst it delegates to mktime to guess if it
is in daylight saving time or not, this works 99% of the time.

Best way would be that ogserver knows what is its timezone and when daylight
saving applies, so tm_isdst is set to 0 or 1 accordingly.

Meanwhile, "tm_isdst = -1" provides the hotfix.

  • Property mode set to 100644
File size: 10.2 KB
Line 
1/*
2 * Copyright (C) 2020 Soleta Networks <info@soleta.eu>
3 *
4 * This program is free software: you can redistribute it and/or modify it under
5 * the terms of the GNU Affero General Public License as published by the
6 * Free Software Foundation, version 3.
7 */
8
9#include "schedule.h"
10#include "list.h"
11#include <sys/types.h>
12#include <stdbool.h>
13#include <stdint.h>
14#include <stdlib.h>
15#include <syslog.h>
16#include <time.h>
17#include <ev.h>
18
19struct og_schedule *current_schedule = NULL;
20static LIST_HEAD(schedule_list);
21
22static void og_schedule_add(struct og_schedule *new)
23{
24        struct og_schedule *schedule, *next;
25
26        list_for_each_entry_safe(schedule, next, &schedule_list, list) {
27                if (new->seconds < schedule->seconds) {
28                        list_add_tail(&new->list, &schedule->list);
29                        return;
30                }
31        }
32        list_add_tail(&new->list, &schedule_list);
33}
34
35/* Returns the days in a month from the weekday. */
36static void get_days_from_weekday(struct tm *tm, int wday, int *days, int *j)
37{
38        int i, mday = 0;
39
40        tm->tm_mday = 1;
41
42        //Shift week to start on Sunday instead of Monday
43        if (wday == 6)
44                wday = 0;
45        else
46                wday++;
47
48        /* A bit bruteforce, but simple. */
49        for (i = 0; i <= 30; i++) {
50                mktime(tm);
51                /* Not this weekday, skip. */
52                if (tm->tm_wday != wday) {
53                        tm->tm_mday++;
54                        continue;
55                }
56                /* Not interested in next month. */
57                if (tm->tm_mday < mday)
58                        break;
59
60                /* Found a matching. */
61                mday = tm->tm_mday;
62                days[(*j)++] = tm->tm_mday;
63                tm->tm_mday++;
64        }
65}
66
67/* Returns the days in the given week. */
68static void get_days_from_week(struct tm *tm, int week, int *days, int *k)
69{
70        int i, j, week_counter = 0;
71        bool week_over = false;
72
73        tm->tm_mday = 1;
74
75        /* Remaining days of this month. */
76        for (i = 0; i <= 30; i++) {
77                mktime(tm);
78
79                /* Last day of this week? */
80                if (tm->tm_wday == 6)
81                        week_over = true;
82
83                /* Not the week we are searching for. */
84                if (week != week_counter) {
85                        tm->tm_mday++;
86                        if (week_over) {
87                                week_counter++;
88                                week_over = false;
89                        }
90                        continue;
91                }
92
93                /* Found matching. */
94                for (j = tm->tm_wday; j <= 6; j++) {
95                        days[(*k)++] = tm->tm_mday++;
96                        mktime(tm);
97                }
98                break;
99        }
100}
101
102static int monthdays[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
103
104static int last_month_day(struct tm *tm)
105{
106        /* Leap year? Adjust it. */
107        if (tm->tm_mon == 1) {
108                tm->tm_mday = 29;
109                mktime(tm);
110                if (tm->tm_mday == 29)
111                        return 29;
112
113                tm->tm_mon = 1;
114        }
115
116        return monthdays[tm->tm_mon];
117}
118
119/* Returns the days in the given week. */
120static void get_last_week(struct tm *tm, int *days, int *j)
121{
122        int i, last_day;
123
124        last_day = last_month_day(tm);
125        tm->tm_mday = last_day;
126
127        for (i = last_day; i >= last_day - 6; i--) {
128                mktime(tm);
129
130                days[(*j)++] = tm->tm_mday;
131
132
133                /* Last day of this week? */
134                if (tm->tm_wday == 1)
135                        break;
136
137                tm->tm_mday--;
138        }
139}
140
141static void og_parse_years(uint16_t years_mask, int years[])
142{
143        int i, j = 0;
144
145        for (i = 0; i < 16; i++) {
146                if ((1 << i) & years_mask)
147                        years[j++] = 2010 + i - 1900;
148        }
149}
150
151static void og_parse_months(uint16_t months_mask, int months[])
152{
153        int i, j = 0;
154
155        for (i = 0; i < 12; i++) {
156                if ((1 << i) & months_mask)
157                        months[j++] = i + 1;
158        }
159}
160
161static void og_parse_days(uint32_t days_mask, int *days)
162{
163        int i, j = 0;
164
165        for (i = 0; i < 31; i++) {
166                if ((1 << i) & days_mask)
167                        days[j++] = i + 1;
168        }
169}
170
171static void og_parse_hours(uint16_t hours_mask, uint8_t am_pm, int hours[])
172{
173        int pm = 12 * am_pm;
174        int i, j = 0;
175
176        for (i = 0; i < 12; i++) {
177                if ((1 << i) & hours_mask)
178                        hours[j++] = i + pm + 1;
179        }
180}
181
182static void og_schedule_remove_duplicates()
183{
184        struct og_schedule *schedule, *next, *prev = NULL;
185
186        list_for_each_entry_safe(schedule, next, &schedule_list, list) {
187                if (!prev) {
188                        prev = schedule;
189                        continue;
190                }
191                if (prev->seconds == schedule->seconds &&
192                    prev->task_id == schedule->task_id) {
193                        list_del(&prev->list);
194                        free(prev);
195                }
196                prev = schedule;
197        }
198}
199
200static bool og_schedule_stale(time_t seconds)
201{
202        time_t now;
203
204        now = time(NULL);
205        if (seconds < now)
206                return true;
207
208        return false;
209}
210
211static void og_schedule_create_weekdays(int month, int year,
212                                        int *hours, int minutes, int week_days,
213                                        uint32_t task_id, uint32_t schedule_id,
214                                        enum og_schedule_type type,
215                                        bool check_stale)
216{
217        struct og_schedule *schedule;
218        int month_days[5];
219        int n_month_days;
220        time_t seconds;
221        uint32_t wday;
222        struct tm tm;
223        int k, l;
224
225        for (wday = 0; wday < 7; wday++) {
226                if (!((1 << wday) & week_days))
227                        continue;
228
229                memset(&tm, 0, sizeof(tm));
230                tm.tm_mon = month;
231                tm.tm_year = year;
232
233                n_month_days = 0;
234                memset(month_days, 0, sizeof(month_days));
235                get_days_from_weekday(&tm, wday, month_days, &n_month_days);
236
237                for (k = 0; month_days[k] != 0 && k < n_month_days; k++) {
238                        for (l = 0; hours[l] != 0 && l < 31; l++) {
239                                memset(&tm, 0, sizeof(tm));
240                                tm.tm_year = year;
241                                tm.tm_mon = month;
242                                tm.tm_mday = month_days[k];
243                                tm.tm_hour = hours[l] - 1;
244                                tm.tm_min = minutes;
245                                tm.tm_isdst = -1;
246                                seconds = mktime(&tm);
247
248                                if (check_stale && og_schedule_stale(seconds))
249                                        continue;
250
251                                schedule = (struct og_schedule *)
252                                        calloc(1, sizeof(struct og_schedule));
253                                if (!schedule)
254                                        return;
255
256                                schedule->seconds = seconds;
257                                schedule->task_id = task_id;
258                                schedule->schedule_id = schedule_id;
259                                schedule->type = type;
260                                og_schedule_add(schedule);
261                        }
262                }
263        }
264}
265
266static void og_schedule_create_weeks(int month, int year,
267                                     int *hours, int minutes, int weeks,
268                                     uint32_t task_id, uint32_t schedule_id,
269                                     enum og_schedule_type type, bool check_stale)
270{
271        struct og_schedule *schedule;
272        int month_days[7];
273        int n_month_days;
274        time_t seconds;
275        struct tm tm;
276        int week;
277        int k, l;
278
279        for (week = 0; week < 5; week++) {
280                if (!((1 << week) & weeks))
281                        continue;
282
283                memset(&tm, 0, sizeof(tm));
284                tm.tm_mon = month;
285                tm.tm_year = year;
286
287                n_month_days = 0;
288                memset(month_days, 0, sizeof(month_days));
289                if (week == 5)
290                        get_last_week(&tm, month_days, &n_month_days);
291                else
292                        get_days_from_week(&tm,  week, month_days, &n_month_days);
293
294                for (k = 0; month_days[k] != 0 && k < n_month_days; k++) {
295                        for (l = 0; hours[l] != 0 && l < 31; l++) {
296                                memset(&tm, 0, sizeof(tm));
297                                tm.tm_year = year;
298                                tm.tm_mon = month;
299                                tm.tm_mday = month_days[k];
300                                tm.tm_hour = hours[l] - 1;
301                                tm.tm_min = minutes;
302                                tm.tm_isdst = -1;
303                                seconds = mktime(&tm);
304
305                                if (check_stale && og_schedule_stale(seconds))
306                                        continue;
307
308                                schedule = (struct og_schedule *)
309                                        calloc(1, sizeof(struct og_schedule));
310                                if (!schedule)
311                                        return;
312
313                                schedule->seconds = seconds;
314                                schedule->task_id = task_id;
315                                schedule->schedule_id = schedule_id;
316                                schedule->type = type;
317                                og_schedule_add(schedule);
318                        }
319                }
320        }
321}
322
323static void og_schedule_create_days(int month, int year,
324                                    int *hours, int minutes, int *days,
325                                    uint32_t task_id, uint32_t schedule_id,
326                                    enum og_schedule_type type, bool check_stale)
327{
328        struct og_schedule *schedule;
329        time_t seconds;
330        struct tm tm;
331        int k, l;
332
333        for (k = 0; days[k] != 0 && k < 31; k++) {
334                for (l = 0; hours[l] != 0 && l < 31; l++) {
335
336                        memset(&tm, 0, sizeof(tm));
337                        tm.tm_year = year;
338                        tm.tm_mon = month;
339                        tm.tm_mday = days[k];
340                        tm.tm_hour = hours[l] - 1;
341                        tm.tm_min = minutes;
342                        tm.tm_isdst = -1;
343                        seconds = mktime(&tm);
344
345                        if (check_stale && og_schedule_stale(seconds))
346                                continue;
347
348                        schedule = (struct og_schedule *)
349                                calloc(1, sizeof(struct og_schedule));
350                        if (!schedule)
351                                return;
352
353                        schedule->seconds = seconds;
354                        schedule->task_id = task_id;
355                        schedule->schedule_id = schedule_id;
356                        schedule->type = type;
357                        og_schedule_add(schedule);
358                }
359        }
360}
361
362void og_schedule_create(unsigned int schedule_id, unsigned int task_id,
363                        enum og_schedule_type type,
364                        struct og_schedule_time *time)
365{
366        int year, month, minutes;
367        int months[12] = {};
368        int years[12] = {};
369        int hours[12] = {};
370        int days[31] = {};
371        int i, j;
372
373        og_parse_years(time->years, years);
374        og_parse_months(time->months, months);
375        og_parse_days(time->days, days);
376        og_parse_hours(time->hours, time->am_pm, hours);
377        minutes = time->minutes;
378
379        for (i = 0; years[i] != 0 && i < 12; i++) {
380                for (j = 0; months[j] != 0 && j < 12; j++) {
381                        month = months[j] - 1;
382                        year = years[i];
383
384                        if (time->week_days)
385                                og_schedule_create_weekdays(month, year,
386                                                            hours, minutes,
387                                                            time->week_days,
388                                                            task_id,
389                                                            schedule_id,
390                                                            type,
391                                                            time->check_stale);
392
393                        if (time->weeks)
394                                og_schedule_create_weeks(month, year,
395                                                         hours, minutes,
396                                                         time->weeks,
397                                                         task_id,
398                                                         schedule_id,
399                                                         type, time->check_stale);
400
401                        if (time->days)
402                                og_schedule_create_days(month, year,
403                                                        hours, minutes,
404                                                        days,
405                                                        task_id,
406                                                        schedule_id,
407                                                        type, time->check_stale);
408                }
409        }
410
411        og_schedule_remove_duplicates();
412}
413
414void og_schedule_delete(struct ev_loop *loop, uint32_t schedule_id)
415{
416        struct og_schedule *schedule, *next;
417
418        list_for_each_entry_safe(schedule, next, &schedule_list, list) {
419                if (schedule->schedule_id != schedule_id)
420                        continue;
421
422                list_del(&schedule->list);
423                if (current_schedule == schedule) {
424                        ev_timer_stop(loop, &schedule->timer);
425                        current_schedule = NULL;
426                        og_schedule_refresh(loop);
427                }
428                free(schedule);
429        }
430}
431
432void og_schedule_update(struct ev_loop *loop, unsigned int schedule_id,
433                        unsigned int task_id, struct og_schedule_time *time)
434{
435        og_schedule_delete(loop, schedule_id);
436        og_schedule_create(schedule_id, task_id, OG_SCHEDULE_TASK, time);
437}
438
439static void og_agent_timer_cb(struct ev_loop *loop, ev_timer *timer, int events)
440{
441        struct og_schedule *current;
442
443        current = container_of(timer, struct og_schedule, timer);
444        og_schedule_run(current->task_id, current->schedule_id, current->type);
445
446        ev_timer_stop(loop, timer);
447        list_del(&current->list);
448        free(current);
449
450        og_schedule_next(loop);
451}
452
453void og_schedule_next(struct ev_loop *loop)
454{
455        struct og_schedule *schedule;
456        time_t now, seconds;
457
458        if (list_empty(&schedule_list)) {
459                current_schedule = NULL;
460                return;
461        }
462
463        schedule = list_first_entry(&schedule_list, struct og_schedule, list);
464        now = time(NULL);
465        if (schedule->seconds <= now)
466                seconds = 0;
467        else
468                seconds = schedule->seconds - now;
469
470        ev_timer_init(&schedule->timer, og_agent_timer_cb, seconds, 0.);
471        ev_timer_start(loop, &schedule->timer);
472        current_schedule = schedule;
473}
474
475void og_schedule_refresh(struct ev_loop *loop)
476{
477        if (current_schedule)
478                ev_timer_stop(loop, &current_schedule->timer);
479
480        og_schedule_next(loop);
481}
Note: See TracBrowser for help on using the repository browser.