aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--miscutils/crond.c141
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
667static void start_one_job(const char *user, CronLine *line) 751static 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
751static void start_one_job(const char *user, CronLine *line) 837static 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
837static void start_jobs(void) 924#if ENABLE_FEATURE_CROND_SPECIAL_TIMES
925static 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
937static 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 */