Introduction
Introduction Statistics Contact Development Disclaimer Help
cron.c - sbase - suckless unix tools
git clone git://git.suckless.org/sbase
Log
Files
Refs
README
LICENSE
---
cron.c (10177B)
---
1 /* See LICENSE file for copyright and license details. */
2 #include <sys/types.h>
3 #include <sys/wait.h>
4
5 #include <errno.h>
6 #include <limits.h>
7 #include <signal.h>
8 #include <stdarg.h>
9 #include <stdlib.h>
10 #include <stdio.h>
11 #include <ctype.h>
12 #include <string.h>
13 #include <syslog.h>
14 #include <time.h>
15 #include <unistd.h>
16
17 #include "queue.h"
18 #include "util.h"
19
20 struct field {
21 enum {
22 ERROR,
23 WILDCARD,
24 NUMBER,
25 RANGE,
26 REPEAT,
27 LIST
28 } type;
29 long *val;
30 int len;
31 };
32
33 struct 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
43 struct jobentry {
44 char *cmd;
45 pid_t pid;
46 TAILQ_ENTRY(jobentry) entry;
47 };
48
49 static sig_atomic_t chldreap;
50 static sig_atomic_t reload;
51 static sig_atomic_t quit;
52 static TAILQ_HEAD(, ctabentry) ctabhead = TAILQ_HEAD_INITIALIZER(ctabhea…
53 static TAILQ_HEAD(, jobentry) jobhead = TAILQ_HEAD_INITIALIZER(jobhead);
54 static char *config = "/etc/crontab";
55 static char *pidfile = "/var/run/crond.pid";
56 static int nflag;
57
58 static void
59 loginfo(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
71 static void
72 logwarn(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
83 static void
84 logerr(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
95 static void
96 runjob(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
113 switch ((pid = fork())) {
114 case -1:
115 logerr("error: failed to fork job: %s time: %s",
116 cmd, ctime(&t));
117 return;
118 case 0:
119 setsid();
120 loginfo("run: %s pid: %d at %s",
121 cmd, getpid(), ctime(&t));
122 execl("/bin/sh", "/bin/sh", "-c", cmd, (char *)NULL);
123 logerr("error: failed to execute job: %s time: %s",
124 cmd, ctime(&t));
125 _exit(1);
126 default:
127 je = emalloc(sizeof(*je));
128 je->cmd = estrdup(cmd);
129 je->pid = pid;
130 TAILQ_INSERT_TAIL(&jobhead, je, entry);
131 }
132 }
133
134 static void
135 waitjob(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:…
162 pid, strsignal(WTERMSIG(status)), ctime(…
163 else if (WIFSTOPPED(status) == 1)
164 loginfo("complete: pid: %d stopped by signal: %s…
165 pid, strsignal(WSTOPSIG(status)), ctime(…
166 }
167 }
168
169 static int
170 isleap(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
179 static int
180 daysinmon(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
190 static int
191 matchentry(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(t…
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;
205 int j;
206
207 for (i = 0; i < LEN(matchtbl); i++) {
208 switch (matchtbl[i].f->type) {
209 case WILDCARD:
210 continue;
211 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]…
218 continue;
219 break;
220 case REPEAT:
221 if (matchtbl[i].tm > 0) {
222 if (matchtbl[i].tm % matchtbl[i].f->val[…
223 continue;
224 } else {
225 if (matchtbl[i].len % matchtbl[i].f->val…
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]…
232 break;
233 if (j < matchtbl[i].f->len)
234 continue;
235 break;
236 default:
237 break;
238 }
239 break;
240 }
241 if (i != LEN(matchtbl))
242 return 0;
243 return 1;
244 }
245
246 static int
247 parsefield(const char *field, long low, long high, struct field *f)
248 {
249 int i;
250 char *e1, *e2;
251 const char *p;
252
253 p = field;
254 while (isdigit(*p))
255 p++;
256
257 f->type = ERROR;
258
259 switch (*p) {
260 case '*':
261 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] == …
272 break;
273
274 f->type = REPEAT;
275 }
276 break;
277 case '\0':
278 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;
325 break;
326 default:
327 return -1;
328 }
329
330 for (i = 0; i < f->len; i++)
331 if (f->val[i] < low || f->val[i] > high)
332 f->type = ERROR;
333
334 if (f->type == ERROR) {
335 free(f->val);
336 return -1;
337 }
338
339 return 0;
340 }
341
342 static void
343 freecte(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
362 static void
363 unloadentries(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);
370 freecte(cte, 6);
371 }
372 }
373
374 static int
375 loadentries(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;
383 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;
396
397 if ((fp = fopen(config, "r")) == NULL) {
398 logerr("error: can't open %s: %s\n", config, strerror(er…
399 return -1;
400 }
401
402 for (y = 0; (len = getline(&line, &size, fp)) != -1; y++) {
403 p = line;
404 if (line[0] == '#' || line[0] == '\n' || line[0] == '\0')
405 continue;
406
407 cte = emalloc(sizeof(*cte));
408 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;
413
414 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]…
420 logerr("error: failed to parse `%s' fiel…
421 flim[x].name, y + 1);
422 freecte(cte, x);
423 r = -1;
424 break;
425 }
426 }
427
428 if (r == -1)
429 break;
430
431 col = strsep(&p, "\n");
432 if (col)
433 while (col[0] == '\t' || col[0] == ' ')
434 col++;
435 if (!col || col[0] == '\0') {
436 logerr("error: missing `cmd' field on line %d\n",
437 y + 1);
438 freecte(cte, 5);
439 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
456 static void
457 reloadentries(void)
458 {
459 unloadentries();
460 if (loadentries() < 0)
461 logwarn("warning: discarding old crontab entries\n");
462 }
463
464 static void
465 sighandler(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
480 static void
481 usage(void)
482 {
483 eprintf("usage: %s [-f file] [-n]\n", argv0);
484 }
485
486 int
487 main(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();
504 } ARGEND
505
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", strerr…
513 return 1;
514 }
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 exi…
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
565 return 0;
566 }
You are viewing proxied material from suckless.org. The copyright of proxied material belongs to its original authors. Any comments or complaints in relation to proxied material should be directed to the original authors of the content concerned. Please see the disclaimer for more details.