diff options
author | Denys Vlasenko <vda.linux@googlemail.com> | 2017-07-08 20:53:11 +0200 |
---|---|---|
committer | Denys Vlasenko <vda.linux@googlemail.com> | 2017-07-08 20:53:11 +0200 |
commit | 75fbea3879b251ef4a66c6da23a6c0e3a706c016 (patch) | |
tree | f12c5e6bda16c582fcdb6214543f969f6ef9edb0 | |
parent | 1572f520ccfe9e45d4cb9b18bb7b728eb2bbb571 (diff) | |
download | busybox-w32-75fbea3879b251ef4a66c6da23a6c0e3a706c016.tar.gz busybox-w32-75fbea3879b251ef4a66c6da23a6c0e3a706c016.tar.bz2 busybox-w32-75fbea3879b251ef4a66c6da23a6c0e3a706c016.zip |
crond: support @daily etc
function old new delta
start_jobs - 348 +348
load_crontab 766 936 +170
static.SpecAry - 96 +96
crond_main 1424 1134 -290
------------------------------------------------------------------------------
(add/remove: 2/0 grow/shrink: 1/1 up/down: 614/-290) Total: 324 bytes
Based on patch by Jonathan Kolb <kolbyjack@gmail.com>
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
-rw-r--r-- | miscutils/crond.c | 141 |
1 files changed, 122 insertions, 19 deletions
diff --git a/miscutils/crond.c b/miscutils/crond.c index 88e7b47b3..8a399446c 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 */ |
@@ -452,6 +471,59 @@ static void load_crontab(const char *fileName) | |||
452 | shell = xstrdup(&tokens[0][6]); | 471 | shell = xstrdup(&tokens[0][6]); |
453 | continue; | 472 | continue; |
454 | } | 473 | } |
474 | #if ENABLE_FEATURE_CROND_SPECIAL_TIMES | ||
475 | if (tokens[0][0] == '@') { | ||
476 | /* | ||
477 | * "@daily /a/script/to/run PARAM1 PARAM2..." | ||
478 | */ | ||
479 | typedef struct SpecialEntry { | ||
480 | const char *name; | ||
481 | const char tokens[8]; | ||
482 | } SpecialEntry; | ||
483 | static const SpecialEntry SpecAry[] = { | ||
484 | /* hour day month weekday */ | ||
485 | { "yearly", "0\0" "1\0" "1\0" "*" }, | ||
486 | { "annually", "0\0" "1\0" "1\0" "*" }, | ||
487 | { "monthly", "0\0" "1\0" "*\0" "*" }, | ||
488 | { "weekly", "0\0" "*\0" "*\0" "0" }, | ||
489 | { "daily", "0\0" "*\0" "*\0" "*" }, | ||
490 | { "midnight", "0\0" "*\0" "*\0" "*" }, | ||
491 | { "hourly", "*\0" "*\0" "*\0" "*" }, | ||
492 | { "reboot", "" }, | ||
493 | }; | ||
494 | const SpecialEntry *e = SpecAry; | ||
495 | |||
496 | if (n < 2) | ||
497 | continue; | ||
498 | for (;;) { | ||
499 | if (strcmp(e->name, tokens[0] + 1) == 0) { | ||
500 | /* | ||
501 | * tokens[1] is only the first word of command, | ||
502 | * find the entire command in unmodified string: | ||
503 | */ | ||
504 | tokens[5] = strstr( | ||
505 | skip_non_whitespace(skip_whitespace(parser->data)), | ||
506 | /* ^^^^ avoids mishandling e.g. "@daily aily PARAM" */ | ||
507 | tokens[1] | ||
508 | ); | ||
509 | if (e->tokens[0]) { | ||
510 | char *et = (char*)e->tokens; | ||
511 | /* minute is "0" for all specials */ | ||
512 | tokens[0] = (char*)"0"; | ||
513 | tokens[1] = et; | ||
514 | tokens[2] = et + 2; | ||
515 | tokens[3] = et + 4; | ||
516 | tokens[4] = et + 6; | ||
517 | } | ||
518 | goto got_it; | ||
519 | } | ||
520 | if (!e->tokens[0]) | ||
521 | break; | ||
522 | e++; | ||
523 | } | ||
524 | continue; /* bad line (unrecognized '@foo') */ | ||
525 | } | ||
526 | #endif | ||
455 | //TODO: handle HOME= too? "man crontab" says: | 527 | //TODO: handle HOME= too? "man crontab" says: |
456 | //name = value | 528 | //name = value |
457 | // | 529 | // |
@@ -468,18 +540,30 @@ static void load_crontab(const char *fileName) | |||
468 | /* check if a minimum of tokens is specified */ | 540 | /* check if a minimum of tokens is specified */ |
469 | if (n < 6) | 541 | if (n < 6) |
470 | continue; | 542 | continue; |
543 | IF_FEATURE_CROND_SPECIAL_TIMES( | ||
544 | got_it: | ||
545 | ) | ||
471 | *pline = line = xzalloc(sizeof(*line)); | 546 | *pline = line = xzalloc(sizeof(*line)); |
472 | /* parse date ranges */ | 547 | #if ENABLE_FEATURE_CROND_SPECIAL_TIMES |
473 | ParseField(file->cf_username, line->cl_Mins, 60, 0, NULL, tokens[0]); | 548 | if (tokens[0][0] == '@') { /* "@reboot" line */ |
474 | ParseField(file->cf_username, line->cl_Hrs, 24, 0, NULL, tokens[1]); | 549 | file->cf_wants_starting = 1; |
475 | ParseField(file->cf_username, line->cl_Days, 32, 0, NULL, tokens[2]); | 550 | line->cl_pid = START_ME_REBOOT; /* wants to start */ |
476 | ParseField(file->cf_username, line->cl_Mons, 12, -1, MonAry, tokens[3]); | 551 | /* line->cl_Mins/Hrs/etc stay zero: never match any time */ |
477 | ParseField(file->cf_username, line->cl_Dow, 7, 0, DowAry, tokens[4]); | 552 | } else |
478 | /* | 553 | #endif |
479 | * fix days and dow - if one is not "*" and the other | 554 | { |
480 | * is "*", the other is set to 0, and vise-versa | 555 | /* parse date ranges */ |
481 | */ | 556 | ParseField(file->cf_username, line->cl_Mins, 60, 0, NULL, tokens[0]); |
482 | FixDayDow(line); | 557 | ParseField(file->cf_username, line->cl_Hrs, 24, 0, NULL, tokens[1]); |
558 | ParseField(file->cf_username, line->cl_Days, 32, 0, NULL, tokens[2]); | ||
559 | ParseField(file->cf_username, line->cl_Mons, 12, -1, MonAry, tokens[3]); | ||
560 | ParseField(file->cf_username, line->cl_Dow, 7, 0, DowAry, tokens[4]); | ||
561 | /* | ||
562 | * fix days and dow - if one is not "*" and the other | ||
563 | * is "*", the other is set to 0, and vise-versa | ||
564 | */ | ||
565 | FixDayDow(line); | ||
566 | } | ||
483 | #if ENABLE_FEATURE_CROND_CALL_SENDMAIL | 567 | #if ENABLE_FEATURE_CROND_CALL_SENDMAIL |
484 | /* copy mailto (can be NULL) */ | 568 | /* copy mailto (can be NULL) */ |
485 | line->cl_mailto = xstrdup(mailTo); | 569 | line->cl_mailto = xstrdup(mailTo); |
@@ -664,7 +748,7 @@ fork_job(const char *user, int mailFd, CronLine *line, bool run_sendmail) | |||
664 | return pid; | 748 | return pid; |
665 | } | 749 | } |
666 | 750 | ||
667 | static void start_one_job(const char *user, CronLine *line) | 751 | static pid_t start_one_job(const char *user, CronLine *line) |
668 | { | 752 | { |
669 | char mailFile[128]; | 753 | char mailFile[128]; |
670 | int mailFd = -1; | 754 | int mailFd = -1; |
@@ -698,6 +782,8 @@ static void start_one_job(const char *user, CronLine *line) | |||
698 | free(mailFile2); | 782 | free(mailFile2); |
699 | } | 783 | } |
700 | } | 784 | } |
785 | |||
786 | return line->cl_pid; | ||
701 | } | 787 | } |
702 | 788 | ||
703 | /* | 789 | /* |
@@ -748,7 +834,7 @@ static void process_finished_job(const char *user, CronLine *line) | |||
748 | 834 | ||
749 | #else /* !ENABLE_FEATURE_CROND_CALL_SENDMAIL */ | 835 | #else /* !ENABLE_FEATURE_CROND_CALL_SENDMAIL */ |
750 | 836 | ||
751 | static void start_one_job(const char *user, CronLine *line) | 837 | static pid_t start_one_job(const char *user, CronLine *line) |
752 | { | 838 | { |
753 | const char *shell; | 839 | const char *shell; |
754 | struct passwd *pas; | 840 | struct passwd *pas; |
@@ -782,6 +868,7 @@ static void start_one_job(const char *user, CronLine *line) | |||
782 | pid = 0; | 868 | pid = 0; |
783 | } | 869 | } |
784 | line->cl_pid = pid; | 870 | line->cl_pid = pid; |
871 | return pid; | ||
785 | } | 872 | } |
786 | 873 | ||
787 | #define process_finished_job(user, line) ((line)->cl_pid = 0) | 874 | #define process_finished_job(user, line) ((line)->cl_pid = 0) |
@@ -825,7 +912,7 @@ static void flag_starting_jobs(time_t t1, time_t t2) | |||
825 | log8("user %s: process already running: %s", | 912 | log8("user %s: process already running: %s", |
826 | file->cf_username, line->cl_cmd); | 913 | file->cf_username, line->cl_cmd); |
827 | } else if (line->cl_pid == 0) { | 914 | } else if (line->cl_pid == 0) { |
828 | line->cl_pid = -1; | 915 | line->cl_pid = START_ME_NORMAL; |
829 | file->cf_wants_starting = 1; | 916 | file->cf_wants_starting = 1; |
830 | } | 917 | } |
831 | } | 918 | } |
@@ -834,7 +921,20 @@ static void flag_starting_jobs(time_t t1, time_t t2) | |||
834 | } | 921 | } |
835 | } | 922 | } |
836 | 923 | ||
837 | static void start_jobs(void) | 924 | #if ENABLE_FEATURE_CROND_SPECIAL_TIMES |
925 | static int touch_reboot_file(void) | ||
926 | { | ||
927 | int fd = open(CRON_REBOOT, O_WRONLY | O_CREAT | O_EXCL | O_TRUNC, 0000); | ||
928 | if (fd >= 0) { | ||
929 | close(fd); | ||
930 | return 1; | ||
931 | } | ||
932 | /* File (presumably) exists - this is not the first run after reboot */ | ||
933 | return 0; | ||
934 | } | ||
935 | #endif | ||
936 | |||
937 | static void start_jobs(int wants_start) | ||
838 | { | 938 | { |
839 | CronFile *file; | 939 | CronFile *file; |
840 | CronLine *line; | 940 | CronLine *line; |
@@ -846,11 +946,10 @@ static void start_jobs(void) | |||
846 | file->cf_wants_starting = 0; | 946 | file->cf_wants_starting = 0; |
847 | for (line = file->cf_lines; line; line = line->cl_next) { | 947 | for (line = file->cf_lines; line; line = line->cl_next) { |
848 | pid_t pid; | 948 | pid_t pid; |
849 | if (line->cl_pid >= 0) | 949 | if (line->cl_pid != wants_start) |
850 | continue; | 950 | continue; |
851 | 951 | ||
852 | start_one_job(file->cf_username, line); | 952 | pid = start_one_job(file->cf_username, line); |
853 | pid = line->cl_pid; | ||
854 | log8("USER %s pid %3d cmd %s", | 953 | log8("USER %s pid %3d cmd %s", |
855 | file->cf_username, (int)pid, line->cl_cmd); | 954 | file->cf_username, (int)pid, line->cl_cmd); |
856 | if (pid < 0) { | 955 | if (pid < 0) { |
@@ -950,6 +1049,10 @@ int crond_main(int argc UNUSED_PARAM, char **argv) | |||
950 | log8("crond (busybox "BB_VER") started, log level %d", G.log_level); | 1049 | log8("crond (busybox "BB_VER") started, log level %d", G.log_level); |
951 | rescan_crontab_dir(); | 1050 | rescan_crontab_dir(); |
952 | write_pidfile(CONFIG_PID_FILE_PATH "/crond.pid"); | 1051 | write_pidfile(CONFIG_PID_FILE_PATH "/crond.pid"); |
1052 | #if ENABLE_FEATURE_CROND_SPECIAL_TIMES | ||
1053 | if (touch_reboot_file()) | ||
1054 | start_jobs(START_ME_REBOOT); /* start @reboot entries, if any */ | ||
1055 | #endif | ||
953 | 1056 | ||
954 | /* Main loop */ | 1057 | /* Main loop */ |
955 | t2 = time(NULL); | 1058 | t2 = time(NULL); |
@@ -1002,7 +1105,7 @@ int crond_main(int argc UNUSED_PARAM, char **argv) | |||
1002 | } else if (dt > 0) { | 1105 | } else if (dt > 0) { |
1003 | /* Usual case: time advances forward, as expected */ | 1106 | /* Usual case: time advances forward, as expected */ |
1004 | flag_starting_jobs(t1, t2); | 1107 | flag_starting_jobs(t1, t2); |
1005 | start_jobs(); | 1108 | start_jobs(START_ME_NORMAL); |
1006 | sleep_time = 60; | 1109 | sleep_time = 60; |
1007 | if (check_completions() > 0) { | 1110 | if (check_completions() > 0) { |
1008 | /* some jobs are still running */ | 1111 | /* some jobs are still running */ |