blob: 77304ccf3370424fce46ec72fdb125941dca9850 [file] [log] [blame]
sin8d2c5dd2014-12-09 16:04:14 +00001/* See LICENSE file for copyright and license details. */
sina1c04e92015-11-06 10:58:27 +00002#include <sys/types.h>
sin8d2c5dd2014-12-09 16:04:14 +00003#include <sys/wait.h>
4
5#include <errno.h>
sina1c04e92015-11-06 10:58:27 +00006#include <limits.h>
sin3e1e5402015-02-14 20:15:01 +00007#include <signal.h>
sin8d2c5dd2014-12-09 16:04:14 +00008#include <stdarg.h>
9#include <stdlib.h>
10#include <stdio.h>
sina1c04e92015-11-06 10:58:27 +000011#include <ctype.h>
sin8d2c5dd2014-12-09 16:04:14 +000012#include <string.h>
13#include <syslog.h>
14#include <time.h>
15#include <unistd.h>
16
sin8d2c5dd2014-12-09 16:04:14 +000017#include "queue.h"
sinab8a11e2014-12-09 16:19:27 +000018#include "util.h"
sin8d2c5dd2014-12-09 16:04:14 +000019
20struct field {
sina1c04e92015-11-06 10:58:27 +000021 enum {
22 ERROR,
23 WILDCARD,
24 NUMBER,
25 RANGE,
26 REPEAT,
27 LIST
28 } type;
29 long *val;
30 int len;
sin8d2c5dd2014-12-09 16:04:14 +000031};
32
33struct ctabentry {
34 struct field min;
35 struct field hour;
36 struct field mday;
37 struct field mon;
38 struct field wday;
39 char *cmd;
40 TAILQ_ENTRY(ctabentry) entry;
41};
42
43struct jobentry {
44 char *cmd;
45 pid_t pid;
46 TAILQ_ENTRY(jobentry) entry;
47};
48
sin8d2c5dd2014-12-09 16:04:14 +000049static sig_atomic_t chldreap;
50static sig_atomic_t reload;
51static sig_atomic_t quit;
52static TAILQ_HEAD(, ctabentry) ctabhead = TAILQ_HEAD_INITIALIZER(ctabhead);
53static TAILQ_HEAD(, jobentry) jobhead = TAILQ_HEAD_INITIALIZER(jobhead);
54static char *config = "/etc/crontab";
55static char *pidfile = "/var/run/crond.pid";
56static int nflag;
57
58static void
59loginfo(const char *fmt, ...)
60{
61 va_list ap;
62 va_start(ap, fmt);
63 if (nflag == 0)
64 vsyslog(LOG_INFO, fmt, ap);
65 else
66 vfprintf(stdout, fmt, ap);
67 fflush(stdout);
68 va_end(ap);
69}
70
71static void
72logwarn(const char *fmt, ...)
73{
74 va_list ap;
75 va_start(ap, fmt);
76 if (nflag == 0)
77 vsyslog(LOG_WARNING, fmt, ap);
78 else
79 vfprintf(stderr, fmt, ap);
80 va_end(ap);
81}
82
83static void
84logerr(const char *fmt, ...)
85{
86 va_list ap;
87 va_start(ap, fmt);
88 if (nflag == 0)
89 vsyslog(LOG_ERR, fmt, ap);
90 else
91 vfprintf(stderr, fmt, ap);
92 va_end(ap);
93}
94
sin8d2c5dd2014-12-09 16:04:14 +000095static void
96runjob(char *cmd)
97{
98 struct jobentry *je;
99 time_t t;
100 pid_t pid;
101
102 t = time(NULL);
103
104 /* If command is already running, skip it */
105 TAILQ_FOREACH(je, &jobhead, entry) {
106 if (strcmp(je->cmd, cmd) == 0) {
107 loginfo("already running %s pid: %d at %s",
108 je->cmd, je->pid, ctime(&t));
109 return;
110 }
111 }
112
FRIGNa8bd21c2015-03-09 15:01:29 +0100113 switch ((pid = fork())) {
114 case -1:
sin8d2c5dd2014-12-09 16:04:14 +0000115 logerr("error: failed to fork job: %s time: %s",
116 cmd, ctime(&t));
117 return;
FRIGNa8bd21c2015-03-09 15:01:29 +0100118 case 0:
sin8d2c5dd2014-12-09 16:04:14 +0000119 setsid();
120 loginfo("run: %s pid: %d at %s",
121 cmd, getpid(), ctime(&t));
122 execl("/bin/sh", "/bin/sh", "-c", cmd, (char *)NULL);
sina1c04e92015-11-06 10:58:27 +0000123 logerr("error: failed to execute job: %s time: %s",
sin8d2c5dd2014-12-09 16:04:14 +0000124 cmd, ctime(&t));
sinab8a11e2014-12-09 16:19:27 +0000125 _exit(1);
FRIGNa8bd21c2015-03-09 15:01:29 +0100126 default:
sin8d2c5dd2014-12-09 16:04:14 +0000127 je = emalloc(sizeof(*je));
128 je->cmd = estrdup(cmd);
129 je->pid = pid;
130 TAILQ_INSERT_TAIL(&jobhead, je, entry);
131 }
132}
133
134static void
135waitjob(void)
136{
137 struct jobentry *je, *tmp;
138 int status;
139 time_t t;
140 pid_t pid;
141
142 t = time(NULL);
143
144 while ((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0) {
145 je = NULL;
146 TAILQ_FOREACH(tmp, &jobhead, entry) {
147 if (tmp->pid == pid) {
148 je = tmp;
149 break;
150 }
151 }
152 if (je) {
153 TAILQ_REMOVE(&jobhead, je, entry);
154 free(je->cmd);
155 free(je);
156 }
157 if (WIFEXITED(status) == 1)
158 loginfo("complete: pid: %d returned: %d time: %s",
159 pid, WEXITSTATUS(status), ctime(&t));
160 else if (WIFSIGNALED(status) == 1)
161 loginfo("complete: pid: %d terminated by signal: %s time: %s",
162 pid, strsignal(WTERMSIG(status)), ctime(&t));
163 else if (WIFSTOPPED(status) == 1)
164 loginfo("complete: pid: %d stopped by signal: %s time: %s",
165 pid, strsignal(WSTOPSIG(status)), ctime(&t));
166 }
167}
168
169static int
170isleap(int year)
171{
172 if (year % 400 == 0)
173 return 1;
174 if (year % 100 == 0)
175 return 0;
176 return (year % 4 == 0);
177}
178
179static int
180daysinmon(int mon, int year)
181{
182 int days[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
183 if (year < 1900)
184 year += 1900;
185 if (isleap(year))
186 days[1] = 29;
187 return days[mon];
188}
189
190static int
191matchentry(struct ctabentry *cte, struct tm *tm)
192{
193 struct {
194 struct field *f;
195 int tm;
196 int len;
197 } matchtbl[] = {
198 { .f = &cte->min, .tm = tm->tm_min, .len = 60 },
199 { .f = &cte->hour, .tm = tm->tm_hour, .len = 24 },
200 { .f = &cte->mday, .tm = tm->tm_mday, .len = daysinmon(tm->tm_mon, tm->tm_year) },
201 { .f = &cte->mon, .tm = tm->tm_mon, .len = 12 },
202 { .f = &cte->wday, .tm = tm->tm_wday, .len = 7 },
203 };
204 size_t i;
sina1c04e92015-11-06 10:58:27 +0000205 int j;
sin8d2c5dd2014-12-09 16:04:14 +0000206
207 for (i = 0; i < LEN(matchtbl); i++) {
sina1c04e92015-11-06 10:58:27 +0000208 switch (matchtbl[i].f->type) {
209 case WILDCARD:
sin8d2c5dd2014-12-09 16:04:14 +0000210 continue;
sina1c04e92015-11-06 10:58:27 +0000211 case NUMBER:
212 if (matchtbl[i].f->val[0] == matchtbl[i].tm)
213 continue;
214 break;
215 case RANGE:
216 if (matchtbl[i].f->val[0] <= matchtbl[i].tm)
217 if (matchtbl[i].f->val[1] >= matchtbl[i].tm)
218 continue;
219 break;
220 case REPEAT:
221 if (matchtbl[i].tm > 0) {
222 if (matchtbl[i].tm % matchtbl[i].f->val[0] == 0)
223 continue;
224 } else {
225 if (matchtbl[i].len % matchtbl[i].f->val[0] == 0)
226 continue;
227 }
228 break;
229 case LIST:
230 for (j = 0; j < matchtbl[i].f->len; j++)
231 if (matchtbl[i].f->val[j] == matchtbl[i].tm)
232 break;
233 if (j < matchtbl[i].f->len)
234 continue;
235 break;
236 default:
237 break;
sin8d2c5dd2014-12-09 16:04:14 +0000238 }
239 break;
240 }
241 if (i != LEN(matchtbl))
242 return 0;
243 return 1;
244}
245
246static int
247parsefield(const char *field, long low, long high, struct field *f)
248{
sina1c04e92015-11-06 10:58:27 +0000249 int i;
sin8d2c5dd2014-12-09 16:04:14 +0000250 char *e1, *e2;
sina1c04e92015-11-06 10:58:27 +0000251 const char *p;
sin8d2c5dd2014-12-09 16:04:14 +0000252
sina1c04e92015-11-06 10:58:27 +0000253 p = field;
254 while (isdigit(*p))
255 p++;
sin8d2c5dd2014-12-09 16:04:14 +0000256
sina1c04e92015-11-06 10:58:27 +0000257 f->type = ERROR;
sin8d2c5dd2014-12-09 16:04:14 +0000258
sina1c04e92015-11-06 10:58:27 +0000259 switch (*p) {
sin8d2c5dd2014-12-09 16:04:14 +0000260 case '*':
sina1c04e92015-11-06 10:58:27 +0000261 if (strcmp(field, "*") == 0) {
262 f->val = NULL;
263 f->len = 0;
264 f->type = WILDCARD;
265 } else if (strncmp(field, "*/", 2) == 0) {
266 f->val = emalloc(sizeof(*f->val));
267 f->len = 1;
268
269 errno = 0;
270 f->val[0] = strtol(field + 2, &e1, 10);
271 if (e1[0] != '\0' || errno != 0 || f->val[0] == 0)
272 break;
273
274 f->type = REPEAT;
275 }
sin8d2c5dd2014-12-09 16:04:14 +0000276 break;
277 case '\0':
sina1c04e92015-11-06 10:58:27 +0000278 f->val = emalloc(sizeof(*f->val));
279 f->len = 1;
280
281 errno = 0;
282 f->val[0] = strtol(field, &e1, 10);
283 if (e1[0] != '\0' || errno != 0)
284 break;
285
286 f->type = NUMBER;
287 break;
288 case '-':
289 f->val = emalloc(2 * sizeof(*f->val));
290 f->len = 2;
291
292 errno = 0;
293 f->val[0] = strtol(field, &e1, 10);
294 if (e1[0] != '-' || errno != 0)
295 break;
296
297 errno = 0;
298 f->val[1] = strtol(e1 + 1, &e2, 10);
299 if (e2[0] != '\0' || errno != 0)
300 break;
301
302 f->type = RANGE;
303 break;
304 case ',':
305 for (i = 1; isdigit(*p) || *p == ','; p++)
306 if (*p == ',')
307 i++;
308 f->val = emalloc(i * sizeof(*f->val));
309 f->len = i;
310
311 errno = 0;
312 f->val[0] = strtol(field, &e1, 10);
313 if (f->val[0] < low || f->val[0] > high)
314 break;
315
316 for (i = 1; *e1 == ',' && errno == 0; i++) {
317 errno = 0;
318 f->val[i] = strtol(e1 + 1, &e2, 10);
319 e1 = e2;
320 }
321 if (e1[0] != '\0' || errno != 0)
322 break;
323
324 f->type = LIST;
sin8d2c5dd2014-12-09 16:04:14 +0000325 break;
326 default:
327 return -1;
328 }
329
sina1c04e92015-11-06 10:58:27 +0000330 for (i = 0; i < f->len; i++)
331 if (f->val[i] < low || f->val[i] > high)
332 f->type = ERROR;
sin8d2c5dd2014-12-09 16:04:14 +0000333
sina1c04e92015-11-06 10:58:27 +0000334 if (f->type == ERROR) {
335 free(f->val);
336 return -1;
337 }
338
sin8d2c5dd2014-12-09 16:04:14 +0000339 return 0;
340}
341
342static void
sina1c04e92015-11-06 10:58:27 +0000343freecte(struct ctabentry *cte, int nfields)
344{
345 switch (nfields) {
346 case 6:
347 free(cte->cmd);
348 case 5:
349 free(cte->wday.val);
350 case 4:
351 free(cte->mon.val);
352 case 3:
353 free(cte->mday.val);
354 case 2:
355 free(cte->hour.val);
356 case 1:
357 free(cte->min.val);
358 }
359 free(cte);
360}
361
362static void
sin8d2c5dd2014-12-09 16:04:14 +0000363unloadentries(void)
364{
365 struct ctabentry *cte, *tmp;
366
367 for (cte = TAILQ_FIRST(&ctabhead); cte; cte = tmp) {
368 tmp = TAILQ_NEXT(cte, entry);
369 TAILQ_REMOVE(&ctabhead, cte, entry);
sina1c04e92015-11-06 10:58:27 +0000370 freecte(cte, 6);
sin8d2c5dd2014-12-09 16:04:14 +0000371 }
372}
373
374static int
375loadentries(void)
376{
377 struct ctabentry *cte;
378 FILE *fp;
379 char *line = NULL, *p, *col;
380 int r = 0, y;
381 size_t size = 0;
382 ssize_t len;
sina1c04e92015-11-06 10:58:27 +0000383 struct fieldlimits {
384 char *name;
385 long min;
386 long max;
387 struct field *f;
388 } flim[] = {
389 { "min", 0, 59, NULL },
390 { "hour", 0, 23, NULL },
391 { "mday", 1, 31, NULL },
392 { "mon", 1, 12, NULL },
393 { "wday", 0, 6, NULL }
394 };
395 size_t x;
sin8d2c5dd2014-12-09 16:04:14 +0000396
397 if ((fp = fopen(config, "r")) == NULL) {
398 logerr("error: can't open %s: %s\n", config, strerror(errno));
399 return -1;
400 }
401
sina1c04e92015-11-06 10:58:27 +0000402 for (y = 0; (len = getline(&line, &size, fp)) != -1; y++) {
sin8d2c5dd2014-12-09 16:04:14 +0000403 p = line;
404 if (line[0] == '#' || line[0] == '\n' || line[0] == '\0')
405 continue;
406
407 cte = emalloc(sizeof(*cte));
sina1c04e92015-11-06 10:58:27 +0000408 flim[0].f = &cte->min;
409 flim[1].f = &cte->hour;
410 flim[2].f = &cte->mday;
411 flim[3].f = &cte->mon;
412 flim[4].f = &cte->wday;
sin8d2c5dd2014-12-09 16:04:14 +0000413
sina1c04e92015-11-06 10:58:27 +0000414 for (x = 0; x < LEN(flim); x++) {
415 do
416 col = strsep(&p, "\t\n ");
417 while (col && col[0] == '\0');
418
419 if (!col || parsefield(col, flim[x].min, flim[x].max, flim[x].f) < 0) {
420 logerr("error: failed to parse `%s' field on line %d\n",
421 flim[x].name, y + 1);
422 freecte(cte, x);
423 r = -1;
424 break;
425 }
sin8d2c5dd2014-12-09 16:04:14 +0000426 }
427
sina1c04e92015-11-06 10:58:27 +0000428 if (r == -1)
sin8d2c5dd2014-12-09 16:04:14 +0000429 break;
sin8d2c5dd2014-12-09 16:04:14 +0000430
431 col = strsep(&p, "\n");
sina1c04e92015-11-06 10:58:27 +0000432 if (col)
433 while (col[0] == '\t' || col[0] == ' ')
434 col++;
435 if (!col || col[0] == '\0') {
sin8d2c5dd2014-12-09 16:04:14 +0000436 logerr("error: missing `cmd' field on line %d\n",
437 y + 1);
sina1c04e92015-11-06 10:58:27 +0000438 freecte(cte, 5);
sin8d2c5dd2014-12-09 16:04:14 +0000439 r = -1;
440 break;
441 }
442 cte->cmd = estrdup(col);
443
444 TAILQ_INSERT_TAIL(&ctabhead, cte, entry);
445 }
446
447 if (r < 0)
448 unloadentries();
449
450 free(line);
451 fclose(fp);
452
453 return r;
454}
455
456static void
457reloadentries(void)
458{
459 unloadentries();
460 if (loadentries() < 0)
461 logwarn("warning: discarding old crontab entries\n");
462}
463
464static void
465sighandler(int sig)
466{
467 switch (sig) {
468 case SIGCHLD:
469 chldreap = 1;
470 break;
471 case SIGHUP:
472 reload = 1;
473 break;
474 case SIGTERM:
475 quit = 1;
476 break;
477 }
478}
479
480static void
481usage(void)
482{
sind30ac8d2014-12-09 16:32:40 +0000483 eprintf("usage: %s [-f file] [-n]\n", argv0);
sin8d2c5dd2014-12-09 16:04:14 +0000484}
485
486int
487main(int argc, char *argv[])
488{
489 FILE *fp;
490 struct ctabentry *cte;
491 time_t t;
492 struct tm *tm;
493 struct sigaction sa;
494
495 ARGBEGIN {
496 case 'n':
497 nflag = 1;
498 break;
499 case 'f':
500 config = EARGF(usage());
501 break;
502 default:
503 usage();
sin23661642015-11-01 10:16:49 +0000504 } ARGEND
sin8d2c5dd2014-12-09 16:04:14 +0000505
506 if (argc > 0)
507 usage();
508
509 if (nflag == 0) {
510 openlog(argv[0], LOG_CONS | LOG_PID, LOG_CRON);
511 if (daemon(1, 0) < 0) {
512 logerr("error: failed to daemonize %s\n", strerror(errno));
sinab8a11e2014-12-09 16:19:27 +0000513 return 1;
sin8d2c5dd2014-12-09 16:04:14 +0000514 }
515 if ((fp = fopen(pidfile, "w"))) {
516 fprintf(fp, "%d\n", getpid());
517 fclose(fp);
518 }
519 }
520
521 sa.sa_handler = sighandler;
522 sigfillset(&sa.sa_mask);
523 sa.sa_flags = SA_RESTART;
524 sigaction(SIGCHLD, &sa, NULL);
525 sigaction(SIGHUP, &sa, NULL);
526 sigaction(SIGTERM, &sa, NULL);
527
528 loadentries();
529
530 while (1) {
531 t = time(NULL);
532 sleep(60 - t % 60);
533
534 if (quit == 1) {
535 if (nflag == 0)
536 unlink(pidfile);
537 unloadentries();
538 /* Don't wait or kill forked processes, just exit */
539 break;
540 }
541
542 if (reload == 1 || chldreap == 1) {
543 if (reload == 1) {
544 reloadentries();
545 reload = 0;
546 }
547 if (chldreap == 1) {
548 waitjob();
549 chldreap = 0;
550 }
551 continue;
552 }
553
554 TAILQ_FOREACH(cte, &ctabhead, entry) {
555 t = time(NULL);
556 tm = localtime(&t);
557 if (matchentry(cte, tm) == 1)
558 runjob(cte->cmd);
559 }
560 }
561
562 if (nflag == 0)
563 closelog();
564
sinab8a11e2014-12-09 16:19:27 +0000565 return 0;
sin8d2c5dd2014-12-09 16:04:14 +0000566}
OSZAR »