Contents of /trunk/mkinitrd-magellan/busybox/miscutils/crond.c
Parent Directory | Revision Log
Revision 532 -
(show annotations)
(download)
Sat Sep 1 22:45:15 2007 UTC (17 years ago) by niro
File MIME type: text/plain
File size: 20874 byte(s)
Sat Sep 1 22:45:15 2007 UTC (17 years ago) by niro
File MIME type: text/plain
File size: 20874 byte(s)
-import if magellan mkinitrd; it is a fork of redhats mkinitrd-5.0.8 with all magellan patches and features; deprecates magellan-src/mkinitrd
1 | /* vi: set sw=4 ts=4: */ |
2 | /* |
3 | * crond -d[#] -c <crondir> -f -b |
4 | * |
5 | * run as root, but NOT setuid root |
6 | * |
7 | * Copyright 1994 Matthew Dillon (dillon@apollo.west.oic.com) |
8 | * Vladimir Oleynik <dzo@simtreas.ru> (C) 2002 |
9 | * |
10 | * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. |
11 | */ |
12 | |
13 | #define VERSION "2.3.2" |
14 | |
15 | #include "busybox.h" |
16 | #include <sys/syslog.h> |
17 | |
18 | #define arysize(ary) (sizeof(ary)/sizeof((ary)[0])) |
19 | |
20 | #ifndef CRONTABS |
21 | #define CRONTABS "/var/spool/cron/crontabs" |
22 | #endif |
23 | #ifndef TMPDIR |
24 | #define TMPDIR "/var/spool/cron" |
25 | #endif |
26 | #ifndef SENDMAIL |
27 | #define SENDMAIL "/usr/sbin/sendmail" |
28 | #endif |
29 | #ifndef SENDMAIL_ARGS |
30 | #define SENDMAIL_ARGS "-ti", "oem" |
31 | #endif |
32 | #ifndef CRONUPDATE |
33 | #define CRONUPDATE "cron.update" |
34 | #endif |
35 | #ifndef MAXLINES |
36 | #define MAXLINES 256 /* max lines in non-root crontabs */ |
37 | #endif |
38 | |
39 | typedef struct CronFile { |
40 | struct CronFile *cf_Next; |
41 | struct CronLine *cf_LineBase; |
42 | char *cf_User; /* username */ |
43 | int cf_Ready; /* bool: one or more jobs ready */ |
44 | int cf_Running; /* bool: one or more jobs running */ |
45 | int cf_Deleted; /* marked for deletion, ignore */ |
46 | } CronFile; |
47 | |
48 | typedef struct CronLine { |
49 | struct CronLine *cl_Next; |
50 | char *cl_Shell; /* shell command */ |
51 | pid_t cl_Pid; /* running pid, 0, or armed (-1) */ |
52 | int cl_MailFlag; /* running pid is for mail */ |
53 | int cl_MailPos; /* 'empty file' size */ |
54 | char cl_Mins[60]; /* 0-59 */ |
55 | char cl_Hrs[24]; /* 0-23 */ |
56 | char cl_Days[32]; /* 1-31 */ |
57 | char cl_Mons[12]; /* 0-11 */ |
58 | char cl_Dow[7]; /* 0-6, beginning sunday */ |
59 | } CronLine; |
60 | |
61 | #define RUN_RANOUT 1 |
62 | #define RUN_RUNNING 2 |
63 | #define RUN_FAILED 3 |
64 | |
65 | #define DaemonUid 0 |
66 | |
67 | #if ENABLE_DEBUG_CROND_OPTION |
68 | static unsigned DebugOpt; |
69 | #endif |
70 | |
71 | static unsigned LogLevel = 8; |
72 | static const char *LogFile; |
73 | static const char *CDir = CRONTABS; |
74 | |
75 | static void startlogger(void); |
76 | |
77 | static void CheckUpdates(void); |
78 | static void SynchronizeDir(void); |
79 | static int TestJobs(time_t t1, time_t t2); |
80 | static void RunJobs(void); |
81 | static int CheckJobs(void); |
82 | |
83 | static void RunJob(const char *user, CronLine * line); |
84 | |
85 | #if ENABLE_FEATURE_CROND_CALL_SENDMAIL |
86 | static void EndJob(const char *user, CronLine * line); |
87 | #else |
88 | #define EndJob(user, line) line->cl_Pid = 0 |
89 | #endif |
90 | |
91 | static void DeleteFile(const char *userName); |
92 | |
93 | static CronFile *FileBase; |
94 | |
95 | |
96 | static void crondlog(const char *ctl, ...) |
97 | { |
98 | va_list va; |
99 | const char *fmt; |
100 | int level = (int) (ctl[0] & 0xf); |
101 | int type = level == 20 ? |
102 | LOG_ERR : ((ctl[0] & 0100) ? LOG_WARNING : LOG_NOTICE); |
103 | |
104 | |
105 | va_start(va, ctl); |
106 | fmt = ctl + 1; |
107 | if (level >= LogLevel) { |
108 | |
109 | #if ENABLE_DEBUG_CROND_OPTION |
110 | if (DebugOpt) { |
111 | vfprintf(stderr, fmt, va); |
112 | } else |
113 | #endif |
114 | if (LogFile == 0) { |
115 | vsyslog(type, fmt, va); |
116 | } else { |
117 | int logfd = open(LogFile, O_WRONLY | O_CREAT | O_APPEND, 0600); |
118 | if (logfd >= 0) { |
119 | vdprintf(logfd, fmt, va); |
120 | close(logfd); |
121 | #if ENABLE_DEBUG_CROND_OPTION |
122 | } else { |
123 | bb_perror_msg("can't open log file"); |
124 | #endif |
125 | } |
126 | } |
127 | } |
128 | va_end(va); |
129 | if (ctl[0] & 0200) { |
130 | exit(20); |
131 | } |
132 | } |
133 | |
134 | int crond_main(int ac, char **av) |
135 | { |
136 | unsigned opt; |
137 | char *lopt, *Lopt, *copt; |
138 | |
139 | #if ENABLE_DEBUG_CROND_OPTION |
140 | char *dopt; |
141 | |
142 | opt_complementary = "f-b:b-f:S-L:L-S:d-l"; |
143 | #else |
144 | opt_complementary = "f-b:b-f:S-L:L-S"; |
145 | #endif |
146 | |
147 | opterr = 0; /* disable getopt 'errors' message. */ |
148 | opt = getopt32(ac, av, "l:L:fbSc:" |
149 | #if ENABLE_DEBUG_CROND_OPTION |
150 | "d:" |
151 | #endif |
152 | , &lopt, &Lopt, &copt |
153 | #if ENABLE_DEBUG_CROND_OPTION |
154 | , &dopt |
155 | #endif |
156 | ); |
157 | if (opt & 1) { |
158 | LogLevel = xatou(lopt); |
159 | } |
160 | if (opt & 2) { |
161 | if (*Lopt != 0) { |
162 | LogFile = Lopt; |
163 | } |
164 | } |
165 | if (opt & 32) { |
166 | if (*copt != 0) { |
167 | CDir = copt; |
168 | } |
169 | } |
170 | #if ENABLE_DEBUG_CROND_OPTION |
171 | if (opt & 64) { |
172 | DebugOpt = xatou(dopt); |
173 | LogLevel = 0; |
174 | } |
175 | #endif |
176 | |
177 | /* |
178 | * change directory |
179 | */ |
180 | |
181 | xchdir(CDir); |
182 | signal(SIGHUP, SIG_IGN); /* hmm.. but, if kill -HUP original |
183 | * version - his died. ;( |
184 | */ |
185 | /* |
186 | * close stdin and stdout, stderr. |
187 | * close unused descriptors - don't need. |
188 | * optional detach from controlling terminal |
189 | */ |
190 | |
191 | if (!(opt & 4)) { |
192 | #ifdef BB_NOMMU |
193 | /* reexec for vfork() do continue parent */ |
194 | vfork_daemon_rexec(1, 0, ac, av, "-f"); |
195 | #else |
196 | xdaemon(1, 0); |
197 | #endif |
198 | } |
199 | |
200 | (void) startlogger(); /* need if syslog mode selected */ |
201 | |
202 | /* |
203 | * main loop - synchronize to 1 second after the minute, minimum sleep |
204 | * of 1 second. |
205 | */ |
206 | |
207 | crondlog("\011%s " VERSION " dillon, started, log level %d\n", |
208 | applet_name, LogLevel); |
209 | |
210 | SynchronizeDir(); |
211 | |
212 | { |
213 | time_t t1 = time(NULL); |
214 | time_t t2; |
215 | long dt; |
216 | int rescan = 60; |
217 | short sleep_time = 60; |
218 | |
219 | for (;;) { |
220 | sleep((sleep_time + 1) - (short) (time(NULL) % sleep_time)); |
221 | |
222 | t2 = time(NULL); |
223 | dt = t2 - t1; |
224 | |
225 | /* |
226 | * The file 'cron.update' is checked to determine new cron |
227 | * jobs. The directory is rescanned once an hour to deal |
228 | * with any screwups. |
229 | * |
230 | * check for disparity. Disparities over an hour either way |
231 | * result in resynchronization. A reverse-indexed disparity |
232 | * less then an hour causes us to effectively sleep until we |
233 | * match the original time (i.e. no re-execution of jobs that |
234 | * have just been run). A forward-indexed disparity less then |
235 | * an hour causes intermediate jobs to be run, but only once |
236 | * in the worst case. |
237 | * |
238 | * when running jobs, the inequality used is greater but not |
239 | * equal to t1, and less then or equal to t2. |
240 | */ |
241 | |
242 | if (--rescan == 0) { |
243 | rescan = 60; |
244 | SynchronizeDir(); |
245 | } |
246 | CheckUpdates(); |
247 | #if ENABLE_DEBUG_CROND_OPTION |
248 | if (DebugOpt) |
249 | crondlog("\005Wakeup dt=%d\n", dt); |
250 | #endif |
251 | if (dt < -60 * 60 || dt > 60 * 60) { |
252 | t1 = t2; |
253 | crondlog("\111time disparity of %d minutes detected\n", dt / 60); |
254 | } else if (dt > 0) { |
255 | TestJobs(t1, t2); |
256 | RunJobs(); |
257 | sleep(5); |
258 | if (CheckJobs() > 0) { |
259 | sleep_time = 10; |
260 | } else { |
261 | sleep_time = 60; |
262 | } |
263 | t1 = t2; |
264 | } |
265 | } |
266 | } |
267 | return 0; /* not reached */ |
268 | } |
269 | |
270 | static int ChangeUser(const char *user) |
271 | { |
272 | struct passwd *pas; |
273 | const char *err_msg; |
274 | |
275 | /* |
276 | * Obtain password entry and change privileges |
277 | */ |
278 | pas = getpwnam(user); |
279 | if (pas == 0) { |
280 | crondlog("\011failed to get uid for %s", user); |
281 | return -1; |
282 | } |
283 | setenv("USER", pas->pw_name, 1); |
284 | setenv("HOME", pas->pw_dir, 1); |
285 | setenv("SHELL", DEFAULT_SHELL, 1); |
286 | |
287 | /* |
288 | * Change running state to the user in question |
289 | */ |
290 | err_msg = change_identity_e2str(pas); |
291 | if (err_msg) { |
292 | crondlog("\011%s for user %s", err_msg, user); |
293 | return -1; |
294 | } |
295 | if (chdir(pas->pw_dir) < 0) { |
296 | crondlog("\011chdir failed: %s: %m", pas->pw_dir); |
297 | if (chdir(TMPDIR) < 0) { |
298 | crondlog("\011chdir failed: %s: %m", TMPDIR); |
299 | return -1; |
300 | } |
301 | } |
302 | return pas->pw_uid; |
303 | } |
304 | |
305 | static void startlogger(void) |
306 | { |
307 | if (LogFile == 0) { |
308 | openlog(applet_name, LOG_CONS | LOG_PID, LOG_CRON); |
309 | } |
310 | #if ENABLE_DEBUG_CROND_OPTION |
311 | else { /* test logfile */ |
312 | int logfd; |
313 | |
314 | if ((logfd = open(LogFile, O_WRONLY | O_CREAT | O_APPEND, 0600)) >= 0) { |
315 | close(logfd); |
316 | } else { |
317 | bb_perror_msg("failed to open log file '%s': ", LogFile); |
318 | } |
319 | } |
320 | #endif |
321 | } |
322 | |
323 | |
324 | static const char *const DowAry[] = { |
325 | "sun", |
326 | "mon", |
327 | "tue", |
328 | "wed", |
329 | "thu", |
330 | "fri", |
331 | "sat", |
332 | |
333 | "Sun", |
334 | "Mon", |
335 | "Tue", |
336 | "Wed", |
337 | "Thu", |
338 | "Fri", |
339 | "Sat", |
340 | NULL |
341 | }; |
342 | |
343 | static const char *const MonAry[] = { |
344 | "jan", |
345 | "feb", |
346 | "mar", |
347 | "apr", |
348 | "may", |
349 | "jun", |
350 | "jul", |
351 | "aug", |
352 | "sep", |
353 | "oct", |
354 | "nov", |
355 | "dec", |
356 | |
357 | "Jan", |
358 | "Feb", |
359 | "Mar", |
360 | "Apr", |
361 | "May", |
362 | "Jun", |
363 | "Jul", |
364 | "Aug", |
365 | "Sep", |
366 | "Oct", |
367 | "Nov", |
368 | "Dec", |
369 | NULL |
370 | }; |
371 | |
372 | static char *ParseField(char *user, char *ary, int modvalue, int off, |
373 | const char *const *names, char *ptr) |
374 | { |
375 | char *base = ptr; |
376 | int n1 = -1; |
377 | int n2 = -1; |
378 | |
379 | if (base == NULL) { |
380 | return NULL; |
381 | } |
382 | |
383 | while (*ptr != ' ' && *ptr != '\t' && *ptr != '\n') { |
384 | int skip = 0; |
385 | |
386 | /* Handle numeric digit or symbol or '*' */ |
387 | |
388 | if (*ptr == '*') { |
389 | n1 = 0; /* everything will be filled */ |
390 | n2 = modvalue - 1; |
391 | skip = 1; |
392 | ++ptr; |
393 | } else if (*ptr >= '0' && *ptr <= '9') { |
394 | if (n1 < 0) { |
395 | n1 = strtol(ptr, &ptr, 10) + off; |
396 | } else { |
397 | n2 = strtol(ptr, &ptr, 10) + off; |
398 | } |
399 | skip = 1; |
400 | } else if (names) { |
401 | int i; |
402 | |
403 | for (i = 0; names[i]; ++i) { |
404 | if (strncmp(ptr, names[i], strlen(names[i])) == 0) { |
405 | break; |
406 | } |
407 | } |
408 | if (names[i]) { |
409 | ptr += strlen(names[i]); |
410 | if (n1 < 0) { |
411 | n1 = i; |
412 | } else { |
413 | n2 = i; |
414 | } |
415 | skip = 1; |
416 | } |
417 | } |
418 | |
419 | /* handle optional range '-' */ |
420 | |
421 | if (skip == 0) { |
422 | crondlog("\111failed user %s parsing %s\n", user, base); |
423 | return NULL; |
424 | } |
425 | if (*ptr == '-' && n2 < 0) { |
426 | ++ptr; |
427 | continue; |
428 | } |
429 | |
430 | /* |
431 | * collapse single-value ranges, handle skipmark, and fill |
432 | * in the character array appropriately. |
433 | */ |
434 | |
435 | if (n2 < 0) { |
436 | n2 = n1; |
437 | } |
438 | if (*ptr == '/') { |
439 | skip = strtol(ptr + 1, &ptr, 10); |
440 | } |
441 | /* |
442 | * fill array, using a failsafe is the easiest way to prevent |
443 | * an endless loop |
444 | */ |
445 | |
446 | { |
447 | int s0 = 1; |
448 | int failsafe = 1024; |
449 | |
450 | --n1; |
451 | do { |
452 | n1 = (n1 + 1) % modvalue; |
453 | |
454 | if (--s0 == 0) { |
455 | ary[n1 % modvalue] = 1; |
456 | s0 = skip; |
457 | } |
458 | } |
459 | while (n1 != n2 && --failsafe); |
460 | |
461 | if (failsafe == 0) { |
462 | crondlog("\111failed user %s parsing %s\n", user, base); |
463 | return NULL; |
464 | } |
465 | } |
466 | if (*ptr != ',') { |
467 | break; |
468 | } |
469 | ++ptr; |
470 | n1 = -1; |
471 | n2 = -1; |
472 | } |
473 | |
474 | if (*ptr != ' ' && *ptr != '\t' && *ptr != '\n') { |
475 | crondlog("\111failed user %s parsing %s\n", user, base); |
476 | return NULL; |
477 | } |
478 | |
479 | while (*ptr == ' ' || *ptr == '\t' || *ptr == '\n') { |
480 | ++ptr; |
481 | } |
482 | #if ENABLE_DEBUG_CROND_OPTION |
483 | if (DebugOpt) { |
484 | int i; |
485 | |
486 | for (i = 0; i < modvalue; ++i) { |
487 | crondlog("\005%d", ary[i]); |
488 | } |
489 | crondlog("\005\n"); |
490 | } |
491 | #endif |
492 | |
493 | return ptr; |
494 | } |
495 | |
496 | static void FixDayDow(CronLine * line) |
497 | { |
498 | int i; |
499 | int weekUsed = 0; |
500 | int daysUsed = 0; |
501 | |
502 | for (i = 0; i < (int)(arysize(line->cl_Dow)); ++i) { |
503 | if (line->cl_Dow[i] == 0) { |
504 | weekUsed = 1; |
505 | break; |
506 | } |
507 | } |
508 | for (i = 0; i < (int)(arysize(line->cl_Days)); ++i) { |
509 | if (line->cl_Days[i] == 0) { |
510 | daysUsed = 1; |
511 | break; |
512 | } |
513 | } |
514 | if (weekUsed && !daysUsed) { |
515 | memset(line->cl_Days, 0, sizeof(line->cl_Days)); |
516 | } |
517 | if (daysUsed && !weekUsed) { |
518 | memset(line->cl_Dow, 0, sizeof(line->cl_Dow)); |
519 | } |
520 | } |
521 | |
522 | |
523 | |
524 | static void SynchronizeFile(const char *fileName) |
525 | { |
526 | int maxEntries = MAXLINES; |
527 | int maxLines; |
528 | char buf[1024]; |
529 | |
530 | if (strcmp(fileName, "root") == 0) { |
531 | maxEntries = 65535; |
532 | } |
533 | maxLines = maxEntries * 10; |
534 | |
535 | if (fileName) { |
536 | FILE *fi; |
537 | |
538 | DeleteFile(fileName); |
539 | |
540 | fi = fopen(fileName, "r"); |
541 | if (fi != NULL) { |
542 | struct stat sbuf; |
543 | |
544 | if (fstat(fileno(fi), &sbuf) == 0 && sbuf.st_uid == DaemonUid) { |
545 | CronFile *file = xzalloc(sizeof(CronFile)); |
546 | CronLine **pline; |
547 | |
548 | file->cf_User = strdup(fileName); |
549 | pline = &file->cf_LineBase; |
550 | |
551 | while (fgets(buf, sizeof(buf), fi) != NULL && --maxLines) { |
552 | CronLine line; |
553 | char *ptr; |
554 | |
555 | trim(buf); |
556 | if (buf[0] == 0 || buf[0] == '#') { |
557 | continue; |
558 | } |
559 | if (--maxEntries == 0) { |
560 | break; |
561 | } |
562 | memset(&line, 0, sizeof(line)); |
563 | |
564 | #if ENABLE_DEBUG_CROND_OPTION |
565 | if (DebugOpt) { |
566 | crondlog("\111User %s Entry %s\n", fileName, buf); |
567 | } |
568 | #endif |
569 | |
570 | /* parse date ranges */ |
571 | ptr = ParseField(file->cf_User, line.cl_Mins, 60, 0, NULL, buf); |
572 | ptr = ParseField(file->cf_User, line.cl_Hrs, 24, 0, NULL, ptr); |
573 | ptr = ParseField(file->cf_User, line.cl_Days, 32, 0, NULL, ptr); |
574 | ptr = ParseField(file->cf_User, line.cl_Mons, 12, -1, MonAry, ptr); |
575 | ptr = ParseField(file->cf_User, line.cl_Dow, 7, 0, DowAry, ptr); |
576 | |
577 | /* check failure */ |
578 | if (ptr == NULL) { |
579 | continue; |
580 | } |
581 | |
582 | /* |
583 | * fix days and dow - if one is not * and the other |
584 | * is *, the other is set to 0, and vise-versa |
585 | */ |
586 | |
587 | FixDayDow(&line); |
588 | |
589 | *pline = xzalloc(sizeof(CronLine)); |
590 | **pline = line; |
591 | |
592 | /* copy command */ |
593 | (*pline)->cl_Shell = strdup(ptr); |
594 | |
595 | #if ENABLE_DEBUG_CROND_OPTION |
596 | if (DebugOpt) { |
597 | crondlog("\111 Command %s\n", ptr); |
598 | } |
599 | #endif |
600 | |
601 | pline = &((*pline)->cl_Next); |
602 | } |
603 | *pline = NULL; |
604 | |
605 | file->cf_Next = FileBase; |
606 | FileBase = file; |
607 | |
608 | if (maxLines == 0 || maxEntries == 0) { |
609 | crondlog("\111Maximum number of lines reached for user %s\n", fileName); |
610 | } |
611 | } |
612 | fclose(fi); |
613 | } |
614 | } |
615 | } |
616 | |
617 | static void CheckUpdates(void) |
618 | { |
619 | FILE *fi; |
620 | char buf[256]; |
621 | |
622 | fi = fopen(CRONUPDATE, "r"); |
623 | if (fi != NULL) { |
624 | remove(CRONUPDATE); |
625 | while (fgets(buf, sizeof(buf), fi) != NULL) { |
626 | SynchronizeFile(strtok(buf, " \t\r\n")); |
627 | } |
628 | fclose(fi); |
629 | } |
630 | } |
631 | |
632 | static void SynchronizeDir(void) |
633 | { |
634 | /* Attempt to delete the database. */ |
635 | |
636 | for (;;) { |
637 | CronFile *file; |
638 | |
639 | for (file = FileBase; file && file->cf_Deleted; file = file->cf_Next); |
640 | if (file == NULL) { |
641 | break; |
642 | } |
643 | DeleteFile(file->cf_User); |
644 | } |
645 | |
646 | /* |
647 | * Remove cron update file |
648 | * |
649 | * Re-chdir, in case directory was renamed & deleted, or otherwise |
650 | * screwed up. |
651 | * |
652 | * scan directory and add associated users |
653 | */ |
654 | |
655 | remove(CRONUPDATE); |
656 | if (chdir(CDir) < 0) { |
657 | crondlog("\311cannot find %s\n", CDir); |
658 | } |
659 | { |
660 | DIR *dir = opendir("."); |
661 | struct dirent *den; |
662 | |
663 | if (dir) { |
664 | while ((den = readdir(dir))) { |
665 | if (strchr(den->d_name, '.') != NULL) { |
666 | continue; |
667 | } |
668 | if (getpwnam(den->d_name)) { |
669 | SynchronizeFile(den->d_name); |
670 | } else { |
671 | crondlog("\007ignoring %s\n", den->d_name); |
672 | } |
673 | } |
674 | closedir(dir); |
675 | } else { |
676 | crondlog("\311cannot open current dir!\n"); |
677 | } |
678 | } |
679 | } |
680 | |
681 | |
682 | /* |
683 | * DeleteFile() - delete user database |
684 | * |
685 | * Note: multiple entries for same user may exist if we were unable to |
686 | * completely delete a database due to running processes. |
687 | */ |
688 | |
689 | static void DeleteFile(const char *userName) |
690 | { |
691 | CronFile **pfile = &FileBase; |
692 | CronFile *file; |
693 | |
694 | while ((file = *pfile) != NULL) { |
695 | if (strcmp(userName, file->cf_User) == 0) { |
696 | CronLine **pline = &file->cf_LineBase; |
697 | CronLine *line; |
698 | |
699 | file->cf_Running = 0; |
700 | file->cf_Deleted = 1; |
701 | |
702 | while ((line = *pline) != NULL) { |
703 | if (line->cl_Pid > 0) { |
704 | file->cf_Running = 1; |
705 | pline = &line->cl_Next; |
706 | } else { |
707 | *pline = line->cl_Next; |
708 | free(line->cl_Shell); |
709 | free(line); |
710 | } |
711 | } |
712 | if (file->cf_Running == 0) { |
713 | *pfile = file->cf_Next; |
714 | free(file->cf_User); |
715 | free(file); |
716 | } else { |
717 | pfile = &file->cf_Next; |
718 | } |
719 | } else { |
720 | pfile = &file->cf_Next; |
721 | } |
722 | } |
723 | } |
724 | |
725 | /* |
726 | * TestJobs() |
727 | * |
728 | * determine which jobs need to be run. Under normal conditions, the |
729 | * period is about a minute (one scan). Worst case it will be one |
730 | * hour (60 scans). |
731 | */ |
732 | |
733 | static int TestJobs(time_t t1, time_t t2) |
734 | { |
735 | int nJobs = 0; |
736 | time_t t; |
737 | |
738 | /* Find jobs > t1 and <= t2 */ |
739 | |
740 | for (t = t1 - t1 % 60; t <= t2; t += 60) { |
741 | if (t > t1) { |
742 | struct tm *tp = localtime(&t); |
743 | CronFile *file; |
744 | CronLine *line; |
745 | |
746 | for (file = FileBase; file; file = file->cf_Next) { |
747 | #if ENABLE_DEBUG_CROND_OPTION |
748 | if (DebugOpt) |
749 | crondlog("\005FILE %s:\n", file->cf_User); |
750 | #endif |
751 | if (file->cf_Deleted) |
752 | continue; |
753 | for (line = file->cf_LineBase; line; line = line->cl_Next) { |
754 | #if ENABLE_DEBUG_CROND_OPTION |
755 | if (DebugOpt) |
756 | crondlog("\005 LINE %s\n", line->cl_Shell); |
757 | #endif |
758 | if (line->cl_Mins[tp->tm_min] && line->cl_Hrs[tp->tm_hour] && |
759 | (line->cl_Days[tp->tm_mday] || line->cl_Dow[tp->tm_wday]) |
760 | && line->cl_Mons[tp->tm_mon]) { |
761 | #if ENABLE_DEBUG_CROND_OPTION |
762 | if (DebugOpt) { |
763 | crondlog("\005 JobToDo: %d %s\n", |
764 | line->cl_Pid, line->cl_Shell); |
765 | } |
766 | #endif |
767 | if (line->cl_Pid > 0) { |
768 | crondlog("\010 process already running: %s %s\n", |
769 | file->cf_User, line->cl_Shell); |
770 | } else if (line->cl_Pid == 0) { |
771 | line->cl_Pid = -1; |
772 | file->cf_Ready = 1; |
773 | ++nJobs; |
774 | } |
775 | } |
776 | } |
777 | } |
778 | } |
779 | } |
780 | return nJobs; |
781 | } |
782 | |
783 | static void RunJobs(void) |
784 | { |
785 | CronFile *file; |
786 | CronLine *line; |
787 | |
788 | for (file = FileBase; file; file = file->cf_Next) { |
789 | if (file->cf_Ready) { |
790 | file->cf_Ready = 0; |
791 | |
792 | for (line = file->cf_LineBase; line; line = line->cl_Next) { |
793 | if (line->cl_Pid < 0) { |
794 | |
795 | RunJob(file->cf_User, line); |
796 | |
797 | crondlog("\010USER %s pid %3d cmd %s\n", |
798 | file->cf_User, line->cl_Pid, line->cl_Shell); |
799 | if (line->cl_Pid < 0) { |
800 | file->cf_Ready = 1; |
801 | } |
802 | else if (line->cl_Pid > 0) { |
803 | file->cf_Running = 1; |
804 | } |
805 | } |
806 | } |
807 | } |
808 | } |
809 | } |
810 | |
811 | /* |
812 | * CheckJobs() - check for job completion |
813 | * |
814 | * Check for job completion, return number of jobs still running after |
815 | * all done. |
816 | */ |
817 | |
818 | static int CheckJobs(void) |
819 | { |
820 | CronFile *file; |
821 | CronLine *line; |
822 | int nStillRunning = 0; |
823 | |
824 | for (file = FileBase; file; file = file->cf_Next) { |
825 | if (file->cf_Running) { |
826 | file->cf_Running = 0; |
827 | |
828 | for (line = file->cf_LineBase; line; line = line->cl_Next) { |
829 | if (line->cl_Pid > 0) { |
830 | int status; |
831 | int r = wait4(line->cl_Pid, &status, WNOHANG, NULL); |
832 | |
833 | if (r < 0 || r == line->cl_Pid) { |
834 | EndJob(file->cf_User, line); |
835 | if (line->cl_Pid) { |
836 | file->cf_Running = 1; |
837 | } |
838 | } else if (r == 0) { |
839 | file->cf_Running = 1; |
840 | } |
841 | } |
842 | } |
843 | } |
844 | nStillRunning += file->cf_Running; |
845 | } |
846 | return nStillRunning; |
847 | } |
848 | |
849 | |
850 | #if ENABLE_FEATURE_CROND_CALL_SENDMAIL |
851 | static void |
852 | ForkJob(const char *user, CronLine * line, int mailFd, |
853 | const char *prog, const char *cmd, const char *arg, const char *mailf) |
854 | { |
855 | /* Fork as the user in question and run program */ |
856 | pid_t pid = fork(); |
857 | |
858 | line->cl_Pid = pid; |
859 | if (pid == 0) { |
860 | /* CHILD */ |
861 | |
862 | /* Change running state to the user in question */ |
863 | |
864 | if (ChangeUser(user) < 0) { |
865 | exit(0); |
866 | } |
867 | #if ENABLE_DEBUG_CROND_OPTION |
868 | if (DebugOpt) { |
869 | crondlog("\005Child Running %s\n", prog); |
870 | } |
871 | #endif |
872 | |
873 | if (mailFd >= 0) { |
874 | dup2(mailFd, mailf != NULL); |
875 | dup2((mailf ? mailFd : 1), 2); |
876 | close(mailFd); |
877 | } |
878 | execl(prog, prog, cmd, arg, NULL); |
879 | crondlog("\024cannot exec, user %s cmd %s %s %s\n", user, prog, cmd, arg); |
880 | if (mailf) { |
881 | fdprintf(1, "Exec failed: %s -c %s\n", prog, arg); |
882 | } |
883 | exit(0); |
884 | } else if (pid < 0) { |
885 | /* FORK FAILED */ |
886 | crondlog("\024cannot fork, user %s\n", user); |
887 | line->cl_Pid = 0; |
888 | if (mailf) { |
889 | remove(mailf); |
890 | } |
891 | } else if (mailf) { |
892 | /* PARENT, FORK SUCCESS |
893 | * rename mail-file based on pid of process |
894 | */ |
895 | char mailFile2[128]; |
896 | |
897 | snprintf(mailFile2, sizeof(mailFile2), TMPDIR "/cron.%s.%d", user, pid); |
898 | rename(mailf, mailFile2); |
899 | } |
900 | /* |
901 | * Close the mail file descriptor.. we can't just leave it open in |
902 | * a structure, closing it later, because we might run out of descriptors |
903 | */ |
904 | |
905 | if (mailFd >= 0) { |
906 | close(mailFd); |
907 | } |
908 | } |
909 | |
910 | static void RunJob(const char *user, CronLine * line) |
911 | { |
912 | char mailFile[128]; |
913 | int mailFd; |
914 | |
915 | line->cl_Pid = 0; |
916 | line->cl_MailFlag = 0; |
917 | |
918 | /* open mail file - owner root so nobody can screw with it. */ |
919 | |
920 | snprintf(mailFile, sizeof(mailFile), TMPDIR "/cron.%s.%d", user, getpid()); |
921 | mailFd = open(mailFile, O_CREAT | O_TRUNC | O_WRONLY | O_EXCL | O_APPEND, 0600); |
922 | |
923 | if (mailFd >= 0) { |
924 | line->cl_MailFlag = 1; |
925 | fdprintf(mailFd, "To: %s\nSubject: cron: %s\n\n", user, |
926 | line->cl_Shell); |
927 | line->cl_MailPos = lseek(mailFd, 0, SEEK_CUR); |
928 | } else { |
929 | crondlog("\024cannot create mail file user %s file %s, output to /dev/null\n", user, mailFile); |
930 | } |
931 | |
932 | ForkJob(user, line, mailFd, DEFAULT_SHELL, "-c", line->cl_Shell, mailFile); |
933 | } |
934 | |
935 | /* |
936 | * EndJob - called when job terminates and when mail terminates |
937 | */ |
938 | |
939 | static void EndJob(const char *user, CronLine * line) |
940 | { |
941 | int mailFd; |
942 | char mailFile[128]; |
943 | struct stat sbuf; |
944 | |
945 | /* No job */ |
946 | |
947 | if (line->cl_Pid <= 0) { |
948 | line->cl_Pid = 0; |
949 | return; |
950 | } |
951 | |
952 | /* |
953 | * End of job and no mail file |
954 | * End of sendmail job |
955 | */ |
956 | |
957 | snprintf(mailFile, sizeof(mailFile), TMPDIR "/cron.%s.%d", user, line->cl_Pid); |
958 | line->cl_Pid = 0; |
959 | |
960 | if (line->cl_MailFlag != 1) { |
961 | return; |
962 | } |
963 | line->cl_MailFlag = 0; |
964 | |
965 | /* |
966 | * End of primary job - check for mail file. If size has increased and |
967 | * the file is still valid, we sendmail it. |
968 | */ |
969 | |
970 | mailFd = open(mailFile, O_RDONLY); |
971 | remove(mailFile); |
972 | if (mailFd < 0) { |
973 | return; |
974 | } |
975 | |
976 | if (fstat(mailFd, &sbuf) < 0 || sbuf.st_uid != DaemonUid || sbuf.st_nlink != 0 || |
977 | sbuf.st_size == line->cl_MailPos || !S_ISREG(sbuf.st_mode)) { |
978 | close(mailFd); |
979 | return; |
980 | } |
981 | ForkJob(user, line, mailFd, SENDMAIL, SENDMAIL_ARGS, NULL); |
982 | } |
983 | #else |
984 | /* crond without sendmail */ |
985 | |
986 | static void RunJob(const char *user, CronLine * line) |
987 | { |
988 | /* Fork as the user in question and run program */ |
989 | pid_t pid = fork(); |
990 | |
991 | if (pid == 0) { |
992 | /* CHILD */ |
993 | |
994 | /* Change running state to the user in question */ |
995 | |
996 | if (ChangeUser(user) < 0) { |
997 | exit(0); |
998 | } |
999 | #if ENABLE_DEBUG_CROND_OPTION |
1000 | if (DebugOpt) { |
1001 | crondlog("\005Child Running %s\n", DEFAULT_SHELL); |
1002 | } |
1003 | #endif |
1004 | |
1005 | execl(DEFAULT_SHELL, DEFAULT_SHELL, "-c", line->cl_Shell, NULL); |
1006 | crondlog("\024cannot exec, user %s cmd %s -c %s\n", user, |
1007 | DEFAULT_SHELL, line->cl_Shell); |
1008 | exit(0); |
1009 | } else if (pid < 0) { |
1010 | /* FORK FAILED */ |
1011 | crondlog("\024cannot, user %s\n", user); |
1012 | pid = 0; |
1013 | } |
1014 | line->cl_Pid = pid; |
1015 | } |
1016 | #endif /* ENABLE_FEATURE_CROND_CALL_SENDMAIL */ |