diff options
Diffstat (limited to 'miscutils/crond.c')
-rw-r--r-- | miscutils/crond.c | 140 |
1 files changed, 121 insertions, 19 deletions
diff --git a/miscutils/crond.c b/miscutils/crond.c index 88e7b47b3..c0c8bef11 100644 --- a/miscutils/crond.c +++ b/miscutils/crond.c | |||
@@ -35,6 +35,22 @@ | |||
35 | //config: help | 35 | //config: help |
36 | //config: Command output will be sent to corresponding user via email. | 36 | //config: Command output will be sent to corresponding user via email. |
37 | //config: | 37 | //config: |
38 | //config:config FEATURE_CROND_SPECIAL_TIMES | ||
39 | //config: bool "Support special times (@reboot, @daily, etc) in crontabs" | ||
40 | //config: default y | ||
41 | //config: depends on CROND | ||
42 | //config: help | ||
43 | //config: string meaning | ||
44 | //config: ------ ------- | ||
45 | //config: @reboot Run once, at startup | ||
46 | //config: @yearly Run once a year: "0 0 1 1 *" | ||
47 | //config: @annually Same as @yearly: "0 0 1 1 *" | ||
48 | //config: @monthly Run once a month: "0 0 1 * *" | ||
49 | //config: @weekly Run once a week: "0 0 * * 0" | ||
50 | //config: @daily Run once a day: "0 0 * * *" | ||
51 | //config: @midnight Same as @daily: "0 0 * * *" | ||
52 | //config: @hourly Run once an hour: "0 * * * *" | ||
53 | //config: | ||
38 | //config:config FEATURE_CROND_DIR | 54 | //config:config FEATURE_CROND_DIR |
39 | //config: string "crond spool directory" | 55 | //config: string "crond spool directory" |
40 | //config: default "/var/spool/cron" | 56 | //config: default "/var/spool/cron" |
@@ -74,6 +90,7 @@ | |||
74 | 90 | ||
75 | #define CRON_DIR CONFIG_FEATURE_CROND_DIR | 91 | #define CRON_DIR CONFIG_FEATURE_CROND_DIR |
76 | #define CRONTABS CONFIG_FEATURE_CROND_DIR "/crontabs" | 92 | #define CRONTABS CONFIG_FEATURE_CROND_DIR "/crontabs" |
93 | #define CRON_REBOOT CONFIG_PID_FILE_PATH "/crond.reboot" | ||
77 | #ifndef SENDMAIL | 94 | #ifndef SENDMAIL |
78 | # define SENDMAIL "sendmail" | 95 | # define SENDMAIL "sendmail" |
79 | #endif | 96 | #endif |
@@ -101,6 +118,8 @@ typedef struct CronLine { | |||
101 | struct CronLine *cl_next; | 118 | struct CronLine *cl_next; |
102 | char *cl_cmd; /* shell command */ | 119 | char *cl_cmd; /* shell command */ |
103 | pid_t cl_pid; /* >0:running, <0:needs to be started in this minute, 0:dormant */ | 120 | pid_t cl_pid; /* >0:running, <0:needs to be started in this minute, 0:dormant */ |
121 | #define START_ME_REBOOT -2 | ||
122 | #define START_ME_NORMAL -1 | ||
104 | #if ENABLE_FEATURE_CROND_CALL_SENDMAIL | 123 | #if ENABLE_FEATURE_CROND_CALL_SENDMAIL |
105 | int cl_empty_mail_size; /* size of mail header only, 0 if no mailfile */ | 124 | int cl_empty_mail_size; /* size of mail header only, 0 if no mailfile */ |
106 | char *cl_mailto; /* whom to mail results, may be NULL */ | 125 | char *cl_mailto; /* whom to mail results, may be NULL */ |
@@ -465,21 +484,85 @@ static void load_crontab(const char *fileName) | |||
465 | //line of the crontab's owner. HOME and SHELL may be overridden by settings | 484 | //line of the crontab's owner. HOME and SHELL may be overridden by settings |
466 | //in the crontab; LOGNAME may not. | 485 | //in the crontab; LOGNAME may not. |
467 | 486 | ||
487 | #if ENABLE_FEATURE_CROND_SPECIAL_TIMES | ||
488 | if (tokens[0][0] == '@') { | ||
489 | /* | ||
490 | * "@daily /a/script/to/run PARAM1 PARAM2..." | ||
491 | */ | ||
492 | typedef struct SpecialEntry { | ||
493 | const char *name; | ||
494 | const char tokens[8]; | ||
495 | } SpecialEntry; | ||
496 | static const SpecialEntry SpecAry[] = { | ||
497 | /* hour day month weekday */ | ||
498 | { "yearly", "0\0" "1\0" "1\0" "*" }, | ||
499 | { "annually", "0\0" "1\0" "1\0" "*" }, | ||
500 | { "monthly", "0\0" "1\0" "*\0" "*" }, | ||
501 | { "weekly", "0\0" "*\0" "*\0" "0" }, | ||
502 | { "daily", "0\0" "*\0" "*\0" "*" }, | ||
503 | { "midnight", "0\0" "*\0" "*\0" "*" }, | ||
504 | { "hourly", "*\0" "*\0" "*\0" "*" }, | ||
505 | { "reboot", "" }, | ||
506 | }; | ||
507 | const SpecialEntry *e = SpecAry; | ||
508 | |||
509 | if (n < 2) | ||
510 | continue; | ||
511 | for (;;) { | ||
512 | if (strcmp(e->name, tokens[0] + 1) == 0) { | ||
513 | /* | ||
514 | * tokens[1] is only the first word of command, | ||
515 | * can'r use it. | ||
516 | * find the entire command in unmodified string: | ||
517 | */ | ||
518 | tokens[5] = skip_whitespace( | ||
519 | skip_non_whitespace( | ||
520 | skip_whitespace(parser->data))); | ||
521 | if (e->tokens[0]) { | ||
522 | char *et = (char*)e->tokens; | ||
523 | /* minute is "0" for all specials */ | ||
524 | tokens[0] = (char*)"0"; | ||
525 | tokens[1] = et; | ||
526 | tokens[2] = et + 2; | ||
527 | tokens[3] = et + 4; | ||
528 | tokens[4] = et + 6; | ||
529 | } | ||
530 | goto got_it; | ||
531 | } | ||
532 | if (!e->tokens[0]) | ||
533 | break; | ||
534 | e++; | ||
535 | } | ||
536 | continue; /* bad line (unrecognized '@foo') */ | ||
537 | } | ||
538 | #endif | ||
468 | /* check if a minimum of tokens is specified */ | 539 | /* check if a minimum of tokens is specified */ |
469 | if (n < 6) | 540 | if (n < 6) |
470 | continue; | 541 | continue; |
542 | IF_FEATURE_CROND_SPECIAL_TIMES( | ||
543 | got_it: | ||
544 | ) | ||
471 | *pline = line = xzalloc(sizeof(*line)); | 545 | *pline = line = xzalloc(sizeof(*line)); |
472 | /* parse date ranges */ | 546 | #if ENABLE_FEATURE_CROND_SPECIAL_TIMES |
473 | ParseField(file->cf_username, line->cl_Mins, 60, 0, NULL, tokens[0]); | 547 | if (tokens[0][0] == '@') { /* "@reboot" line */ |
474 | ParseField(file->cf_username, line->cl_Hrs, 24, 0, NULL, tokens[1]); | 548 | file->cf_wants_starting = 1; |
475 | ParseField(file->cf_username, line->cl_Days, 32, 0, NULL, tokens[2]); | 549 | line->cl_pid = START_ME_REBOOT; /* wants to start */ |
476 | ParseField(file->cf_username, line->cl_Mons, 12, -1, MonAry, tokens[3]); | 550 | /* line->cl_Mins/Hrs/etc stay zero: never match any time */ |
477 | ParseField(file->cf_username, line->cl_Dow, 7, 0, DowAry, tokens[4]); | 551 | } else |
478 | /* | 552 | #endif |
479 | * fix days and dow - if one is not "*" and the other | 553 | { |
480 | * is "*", the other is set to 0, and vise-versa | 554 | /* parse date ranges */ |
481 | */ | 555 | ParseField(file->cf_username, line->cl_Mins, 60, 0, NULL, tokens[0]); |
482 | FixDayDow(line); | 556 | ParseField(file->cf_username, line->cl_Hrs, 24, 0, NULL, tokens[1]); |
557 | ParseField(file->cf_username, line->cl_Days, 32, 0, NULL, tokens[2]); | ||
558 | ParseField(file->cf_username, line->cl_Mons, 12, -1, MonAry, tokens[3]); | ||
559 | ParseField(file->cf_username, line->cl_Dow, 7, 0, DowAry, tokens[4]); | ||
560 | /* | ||
561 | * fix days and dow - if one is not "*" and the other | ||
562 | * is "*", the other is set to 0, and vise-versa | ||
563 | */ | ||
564 | FixDayDow(line); | ||
565 | } | ||
483 | #if ENABLE_FEATURE_CROND_CALL_SENDMAIL | 566 | #if ENABLE_FEATURE_CROND_CALL_SENDMAIL |
484 | /* copy mailto (can be NULL) */ | 567 | /* copy mailto (can be NULL) */ |
485 | line->cl_mailto = xstrdup(mailTo); | 568 | line->cl_mailto = xstrdup(mailTo); |
@@ -664,7 +747,7 @@ fork_job(const char *user, int mailFd, CronLine *line, bool run_sendmail) | |||
664 | return pid; | 747 | return pid; |
665 | } | 748 | } |
666 | 749 | ||
667 | static void start_one_job(const char *user, CronLine *line) | 750 | static pid_t start_one_job(const char *user, CronLine *line) |
668 | { | 751 | { |
669 | char mailFile[128]; | 752 | char mailFile[128]; |
670 | int mailFd = -1; | 753 | int mailFd = -1; |
@@ -698,6 +781,8 @@ static void start_one_job(const char *user, CronLine *line) | |||
698 | free(mailFile2); | 781 | free(mailFile2); |
699 | } | 782 | } |
700 | } | 783 | } |
784 | |||
785 | return line->cl_pid; | ||
701 | } | 786 | } |
702 | 787 | ||
703 | /* | 788 | /* |
@@ -748,7 +833,7 @@ static void process_finished_job(const char *user, CronLine *line) | |||
748 | 833 | ||
749 | #else /* !ENABLE_FEATURE_CROND_CALL_SENDMAIL */ | 834 | #else /* !ENABLE_FEATURE_CROND_CALL_SENDMAIL */ |
750 | 835 | ||
751 | static void start_one_job(const char *user, CronLine *line) | 836 | static pid_t start_one_job(const char *user, CronLine *line) |
752 | { | 837 | { |
753 | const char *shell; | 838 | const char *shell; |
754 | struct passwd *pas; | 839 | struct passwd *pas; |
@@ -782,6 +867,7 @@ static void start_one_job(const char *user, CronLine *line) | |||
782 | pid = 0; | 867 | pid = 0; |
783 | } | 868 | } |
784 | line->cl_pid = pid; | 869 | line->cl_pid = pid; |
870 | return pid; | ||
785 | } | 871 | } |
786 | 872 | ||
787 | #define process_finished_job(user, line) ((line)->cl_pid = 0) | 873 | #define process_finished_job(user, line) ((line)->cl_pid = 0) |
@@ -825,7 +911,7 @@ static void flag_starting_jobs(time_t t1, time_t t2) | |||
825 | log8("user %s: process already running: %s", | 911 | log8("user %s: process already running: %s", |
826 | file->cf_username, line->cl_cmd); | 912 | file->cf_username, line->cl_cmd); |
827 | } else if (line->cl_pid == 0) { | 913 | } else if (line->cl_pid == 0) { |
828 | line->cl_pid = -1; | 914 | line->cl_pid = START_ME_NORMAL; |
829 | file->cf_wants_starting = 1; | 915 | file->cf_wants_starting = 1; |
830 | } | 916 | } |
831 | } | 917 | } |
@@ -834,7 +920,20 @@ static void flag_starting_jobs(time_t t1, time_t t2) | |||
834 | } | 920 | } |
835 | } | 921 | } |
836 | 922 | ||
837 | static void start_jobs(void) | 923 | #if ENABLE_FEATURE_CROND_SPECIAL_TIMES |
924 | static int touch_reboot_file(void) | ||
925 | { | ||
926 | int fd = open(CRON_REBOOT, O_WRONLY | O_CREAT | O_EXCL | O_TRUNC, 0000); | ||
927 | if (fd >= 0) { | ||
928 | close(fd); | ||
929 | return 1; | ||
930 | } | ||
931 | /* File (presumably) exists - this is not the first run after reboot */ | ||
932 | return 0; | ||
933 | } | ||
934 | #endif | ||
935 | |||
936 | static void start_jobs(int wants_start) | ||
838 | { | 937 | { |
839 | CronFile *file; | 938 | CronFile *file; |
840 | CronLine *line; | 939 | CronLine *line; |
@@ -846,11 +945,10 @@ static void start_jobs(void) | |||
846 | file->cf_wants_starting = 0; | 945 | file->cf_wants_starting = 0; |
847 | for (line = file->cf_lines; line; line = line->cl_next) { | 946 | for (line = file->cf_lines; line; line = line->cl_next) { |
848 | pid_t pid; | 947 | pid_t pid; |
849 | if (line->cl_pid >= 0) | 948 | if (line->cl_pid != wants_start) |
850 | continue; | 949 | continue; |
851 | 950 | ||
852 | start_one_job(file->cf_username, line); | 951 | pid = start_one_job(file->cf_username, line); |
853 | pid = line->cl_pid; | ||
854 | log8("USER %s pid %3d cmd %s", | 952 | log8("USER %s pid %3d cmd %s", |
855 | file->cf_username, (int)pid, line->cl_cmd); | 953 | file->cf_username, (int)pid, line->cl_cmd); |
856 | if (pid < 0) { | 954 | if (pid < 0) { |
@@ -950,6 +1048,10 @@ int crond_main(int argc UNUSED_PARAM, char **argv) | |||
950 | log8("crond (busybox "BB_VER") started, log level %d", G.log_level); | 1048 | log8("crond (busybox "BB_VER") started, log level %d", G.log_level); |
951 | rescan_crontab_dir(); | 1049 | rescan_crontab_dir(); |
952 | write_pidfile(CONFIG_PID_FILE_PATH "/crond.pid"); | 1050 | write_pidfile(CONFIG_PID_FILE_PATH "/crond.pid"); |
1051 | #if ENABLE_FEATURE_CROND_SPECIAL_TIMES | ||
1052 | if (touch_reboot_file()) | ||
1053 | start_jobs(START_ME_REBOOT); /* start @reboot entries, if any */ | ||
1054 | #endif | ||
953 | 1055 | ||
954 | /* Main loop */ | 1056 | /* Main loop */ |
955 | t2 = time(NULL); | 1057 | t2 = time(NULL); |
@@ -1002,7 +1104,7 @@ int crond_main(int argc UNUSED_PARAM, char **argv) | |||
1002 | } else if (dt > 0) { | 1104 | } else if (dt > 0) { |
1003 | /* Usual case: time advances forward, as expected */ | 1105 | /* Usual case: time advances forward, as expected */ |
1004 | flag_starting_jobs(t1, t2); | 1106 | flag_starting_jobs(t1, t2); |
1005 | start_jobs(); | 1107 | start_jobs(START_ME_NORMAL); |
1006 | sleep_time = 60; | 1108 | sleep_time = 60; |
1007 | if (check_completions() > 0) { | 1109 | if (check_completions() > 0) { |
1008 | /* some jobs are still running */ | 1110 | /* some jobs are still running */ |