aboutsummaryrefslogtreecommitdiff
path: root/miscutils/crond.c
diff options
context:
space:
mode:
Diffstat (limited to 'miscutils/crond.c')
-rw-r--r--miscutils/crond.c140
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
667static void start_one_job(const char *user, CronLine *line) 750static 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
751static void start_one_job(const char *user, CronLine *line) 836static 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
837static void start_jobs(void) 923#if ENABLE_FEATURE_CROND_SPECIAL_TIMES
924static 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
936static 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 */