1 /*
2 * Tvheadend - cron routines
3 *
4 * Copyright (C) 2014 Adam Sutton
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "build.h"
21 #include "cron.h"
22 #include "tvheadend.h"
23
24 #include <time.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28
29 #if !defined(PLATFORM_FREEBSD)
30 #include <alloca.h>
31 #endif
32
33 /*
34 * Parse value
35 */
36 static int
cron_parse_val(const char * str,const char ** key,int * v)37 cron_parse_val ( const char *str, const char **key, int *v )
38 {
39 int i = 0;
40 if (!str)
41 return 0;
42 if (key) {
43 while (key[i]) {
44 if (!strncasecmp(str, key[i], strlen(key[i]))) {
45 *v = i;
46 return 0;
47 }
48 i++;
49 }
50 }
51
52 return sscanf(str, "%d", v) == 1 ? 0 : 1;
53 }
54
55 /*
56 * Parse individual field in cron spec
57 */
58 static int
cron_parse_field(const char ** istr,uint64_t * field,uint64_t mask,int bits,int off,const char ** key)59 cron_parse_field
60 ( const char **istr, uint64_t *field, uint64_t mask, int bits, int off,
61 const char **key )
62 {
63 int sn = -1, en = -1, mn = -1;
64 const char *str = *istr;
65 const char *beg = str;
66 uint64_t val = 0;
67 while ( 1 ) {
68 if ( *str == '*' ) {
69 sn = off;
70 en = bits + off - 1;
71 beg = NULL;
72 } else if ( *str == ',' || *str == ' ' || *str == '\0' ) {
73 if (beg)
74 if (cron_parse_val(beg, key, en == -1 ? (sn == -1 ? &sn : &en) : &mn))
75 return 1;
76 if ((sn - off) >= bits || (en - off) >= bits || mn > bits)
77 return 1;
78 if (en < 0) en = sn;
79 if (mn <= 0) mn = 1;
80 while (sn <= en) {
81 if ( (sn % mn) == 0 )
82 val |= (0x1ULL << (sn - off));
83 sn++;
84 }
85 if (*str != ',') break;
86 sn = en = mn = -1;
87 beg = (str + 1);
88 } else if ( *str == '/' ) {
89 if (beg)
90 if (en == -1 || cron_parse_val(beg, key, sn == -1 ? &sn : &en))
91 return 1;
92 beg = (str + 1);
93 } else if ( *str == '-' ) {
94 if (sn != -1 || cron_parse_val(beg, key, &sn))
95 return 1;
96 beg = (str + 1);
97 }
98 str++;
99 }
100 if (*str == ' ') str++;
101 *istr = str;
102 *field = (val | ((val >> bits) & 0x1)) & mask;
103 return 0;
104 }
105
106 /*
107 * Set value
108 */
109 int
cron_set(cron_t * c,const char * str)110 cron_set ( cron_t *c, const char *str )
111 {
112 uint64_t ho, mi, mo, dm, dw;
113 static const char *days[] = {
114 "sun", "mon", "tue", "wed", "thu", "fri", "sat", NULL
115 };
116 static const char *months[] = {
117 "ignore",
118 "jan", "feb", "mar", "apr", "may", "jun",
119 "jul", "aug", "sep", "oct", "nov", "dec",
120 NULL
121 };
122
123 /* Daily (01:01) */
124 if ( !strcmp(str, "@daily") ) {
125 c->c_min = 1;
126 c->c_hour = 1;
127 c->c_mday = CRON_MDAY_MASK;
128 c->c_mon = CRON_MON_MASK;
129 c->c_wday = CRON_WDAY_MASK;
130
131 /* Hourly (XX:02) */
132 } else if ( !strcmp(str, "@hourly") ) {
133 c->c_min = 2;
134 c->c_hour = CRON_HOUR_MASK;
135 c->c_mday = CRON_MDAY_MASK;
136 c->c_mon = CRON_MON_MASK;
137 c->c_wday = CRON_WDAY_MASK;
138
139 /* Standard */
140 } else {
141 if (cron_parse_field(&str, &mi, CRON_MIN_MASK, 60, 0, NULL) || !mi)
142 return 1;
143 if (cron_parse_field(&str, &ho, CRON_HOUR_MASK, 24, 0, NULL) || !ho)
144 return 1;
145 if (cron_parse_field(&str, &dm, CRON_MDAY_MASK, 31, 1, NULL) || !dm)
146 return 1;
147 if (cron_parse_field(&str, &mo, CRON_MON_MASK, 12, 1, months) || !mo)
148 return 1;
149 if (cron_parse_field(&str, &dw, CRON_WDAY_MASK, 7, 0, days) || !dw)
150 return 1;
151 c->c_min = mi;
152 c->c_hour = ho;
153 c->c_mday = dm;
154 c->c_mon = mo;
155 c->c_wday = dw;
156 }
157
158 return 0;
159 }
160
161 /*
162 * Set value
163 */
164 cron_multi_t *
cron_multi_set(const char * str)165 cron_multi_set ( const char *str )
166 {
167 char *s = str ? alloca(strlen(str) + 1) : NULL;
168 char *line, *sptr = NULL;
169 cron_t cron;
170 cron_multi_t *cm = NULL, *cm2;
171 int count = 0;
172
173 if (s == NULL)
174 return NULL;
175 strcpy(s, str);
176 line = strtok_r(s, "\n", &sptr);
177 while (line) {
178 while (*line && *line <= ' ')
179 line++;
180 if (line[0] != '#')
181 if (!cron_set(&cron, line)) {
182 count++;
183 cm2 = realloc(cm, sizeof(*cm) + sizeof(cron) * count);
184 if (cm2 == NULL) {
185 free(cm);
186 return NULL;
187 }
188 cm = cm2;
189 cm->cm_crons[count - 1] = cron;
190 }
191 line = strtok_r(NULL, "\n", &sptr);
192 }
193 if (count)
194 cm->cm_count = count;
195 return cm;
196 }
197
198 /*
199 * Check for leap year
200 */
201 static int
is_leep_year(int year)202 is_leep_year ( int year )
203 {
204 if (!(year % 400))
205 return 1;
206 if (!(year % 100))
207 return 0;
208 return (year % 4) ? 0 : 1;
209 }
210
211 /*
212 * Check for days in month
213 */
214 static int
days_in_month(int year,int mon)215 days_in_month ( int year, int mon )
216 {
217 int d;
218 if (mon == 2)
219 d = 28 + is_leep_year(year);
220 else
221 d = 30 + ((0x15AA >> mon) & 0x1);
222 return d;
223 }
224
225 /*
226 * Find the next time (starting from now) that the cron should fire
227 */
228 int
cron_next(cron_t * c,const time_t now,time_t * ret)229 cron_next ( cron_t *c, const time_t now, time_t *ret )
230 {
231 struct tm nxt, tmp;
232 int endyear, loops = 1000;
233 localtime_r(&now, &nxt);
234 endyear = nxt.tm_year + 10;
235
236 /* Clear seconds */
237 nxt.tm_sec = 0;
238
239 /* Invalid day */
240 if (!(c->c_mday & (0x1LL << (nxt.tm_mday-1))) ||
241 !(c->c_wday & (0x1LL << (nxt.tm_wday))) ||
242 !(c->c_mon & (0x1LL << (nxt.tm_mon))) ) {
243 nxt.tm_min = 0;
244 nxt.tm_hour = 0;
245
246 /* Invalid hour */
247 } else if (!(c->c_hour & (0x1LL << nxt.tm_hour))) {
248 nxt.tm_min = 0;
249
250 /* Increment */
251 } else {
252 ++nxt.tm_min;
253 }
254
255 /* Minute */
256 while (!(c->c_min & (0x1LL << nxt.tm_min))) {
257 if (nxt.tm_min == 60) {
258 ++nxt.tm_hour;
259 nxt.tm_min = 0;
260 } else
261 nxt.tm_min++;
262 }
263
264 /* Hour */
265 while (!(c->c_hour & (0x1LL << nxt.tm_hour))) {
266 if (nxt.tm_hour == 24) {
267 ++nxt.tm_mday;
268 ++nxt.tm_wday;
269 nxt.tm_hour = 0;
270 } else
271 ++nxt.tm_hour;
272 }
273
274 /* Date */
275 if (nxt.tm_wday == 7)
276 nxt.tm_wday = 0;
277 if (nxt.tm_mday > days_in_month(nxt.tm_year+1900, nxt.tm_mon+1)) {
278 nxt.tm_mday = 1;
279 nxt.tm_mon++;
280 if (nxt.tm_mon == 12) {
281 nxt.tm_mon = 0;
282 ++nxt.tm_year;
283 }
284 }
285 while (!(c->c_mday & (0x1LL << (nxt.tm_mday-1))) ||
286 !(c->c_wday & (0x1LL << (nxt.tm_wday))) ||
287 !(c->c_mon & (0x1LL << (nxt.tm_mon))) ) {
288
289 /* Endless loop protection */
290 if (loops-- == 0)
291 return -1;
292
293 /* Stop possible infinite loop on invalid request */
294 if (nxt.tm_year >= endyear)
295 return -1;
296
297 /* Increment day of week */
298 if (++nxt.tm_wday == 7)
299 nxt.tm_wday = 0;
300
301 /* Increment day */
302 if (++nxt.tm_mday > days_in_month(nxt.tm_year+1900, nxt.tm_mon+1)) {
303 nxt.tm_mday = 1;
304 if (++nxt.tm_mon == 12) {
305 nxt.tm_mon = 0;
306 ++nxt.tm_year;
307 }
308 }
309
310 /* Shortcut the month */
311 while (!(c->c_mon & (0x1LL << nxt.tm_mon))) {
312 nxt.tm_wday
313 += 1 + (days_in_month(nxt.tm_year+1900, nxt.tm_mon+1) - nxt.tm_mday);
314 nxt.tm_mday = 1;
315 if (++nxt.tm_mon >= 12) {
316 nxt.tm_mon = 0;
317 ++nxt.tm_year;
318 }
319 }
320 nxt.tm_wday %= 7;
321 }
322
323 /* Create time */
324 memcpy(&tmp, &nxt, sizeof(tmp));
325 mktime(&tmp);
326 nxt.tm_isdst = tmp.tm_isdst;
327 *ret = mktime(&nxt);
328 if (*ret <= now)
329 *ret = mktime(&tmp);
330 if (*ret <= now) {
331 #ifndef CRON_TEST
332 tvherror(LS_CRON, "invalid time, now %"PRItime_t", result %"PRItime_t, now, *ret);
333 #else
334 printf("ERROR: invalid time, now %"PRItime_t", result %"PRItime_t"\n", now, *ret);
335 #endif
336 *ret = now + 600;
337 }
338 return 0;
339 }
340
341 /*
342 * Find the next time (starting from now) that the cron should fire
343 */
344 int
cron_multi_next(cron_multi_t * cm,const time_t now,time_t * ret)345 cron_multi_next ( cron_multi_t *cm, const time_t now, time_t *ret )
346 {
347 uint32_t i;
348 time_t r = (time_t)-1, t;
349
350 if (cm == NULL)
351 return -1;
352 for (i = 0; i < cm->cm_count; i++)
353 if (!cron_next(&cm->cm_crons[i], now, &t))
354 if (r == (time_t)-1 || t < r)
355 r = t;
356 if (r == (time_t)-1)
357 return -1;
358 *ret = r;
359 return 0;
360 }
361
362 /*
363 * Testing
364 *
365 * gcc -g -DCRON_TEST -I./build.linux src/cron.c
366 */
367 #ifdef CRON_TEST
368 static
print_bits(uint64_t b,int n)369 void print_bits ( uint64_t b, int n )
370 {
371 while (n) {
372 printf("%d", (int)(b & 0x1));
373 b >>= 1;
374 n--;
375 }
376 }
377
378 int
main(int argc,char ** argv)379 main ( int argc, char **argv )
380 {
381 cron_t c;
382 time_t n;
383 struct tm tm;
384 char buf[128];
385
386 if (argc < 2) {
387 printf("Specify: CRON [NOW]\n");
388 return 1;
389 }
390 if (argc > 2)
391 n = atol(argv[2]);
392 else
393 time(&n);
394 if (cron_set(&c, argv[1]))
395 printf("INVALID CRON: %s\n", argv[1]);
396 else {
397 printf("min = "); print_bits(c.c_min, 60); printf("\n");
398 printf("hour = "); print_bits(c.c_hour, 24); printf("\n");
399 printf("mday = "); print_bits(c.c_mday, 31); printf("\n");
400 printf("mon = "); print_bits(c.c_mon, 12); printf("\n");
401 printf("wday = "); print_bits(c.c_wday, 7); printf("\n");
402
403 localtime_r(&n, &tm);
404 strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M", &tm);
405 printf("NOW: %ld - %s (DST %d) (ZONE %s)\n", (long)n, buf, tm.tm_isdst, tm.tm_zone);
406
407 if (cron_next(&c, n, &n)) {
408 printf("FAILED to find NEXT\n");
409 return 1;
410 }
411 localtime_r(&n, &tm);
412 strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M", &tm);
413 printf("NXT: %ld - %s (DST %d) (ZONE %s)\n", (long)n, buf, tm.tm_isdst, tm.tm_zone);
414
415 }
416 return 0;
417 }
418 #endif
419
420 /******************************************************************************
421 * Editor Configuration
422 *
423 * vim:sts=2:ts=2:sw=2:et
424 *****************************************************************************/
425