2 * FCRON - periodic command scheduler
4 * Copyright 2000-2013 Thibault Godouet <fcron@free.fr>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 * The GNU General Public License can also be found in the file
21 * `LICENSE' that comes with the fcron source distribution.
29 #include "getloadavg.h"
31 int is_leap_year(int year);
32 int get_nb_mdays(int year, int mon);
33 void set_wday(struct tm *date);
34 time_t mktime_no_dst(struct tm *t);
35 void goto_beginning_next_period_periodical(cl_t * line, struct tm *ftime);
36 void move_time_to(int where, cl_t * line, struct tm *ftime);
37 #define BEGIN_NEXT_PERIOD 11 /* move_time_to()'s where possible value */
38 #define END_OF_INTERVAL 12 /* move_time_to()'s where possible value */
39 void run_serial_job(void);
40 void run_lavg_job(lavg_t * l);
41 void run_queue_job(cl_t * line);
45 /* determine which jobs need to be run, and run them. */
50 debug("Looking for jobs to execute ...");
53 while ((j = queue_base) && j->j_line->cl_nextexe <= now) {
55 if (j->j_line->cl_remain > 0 && --(j->j_line->cl_remain) > 0) {
56 debug(" cl_remain: %d", j->j_line->cl_remain);
60 j->j_line->cl_remain = j->j_line->cl_runfreq;
62 if (is_lavg(j->j_line->cl_option))
63 add_lavg_job(j->j_line, -1);
64 else if (is_serial(j->j_line->cl_option))
65 add_serial_job(j->j_line, -1);
67 run_normal_job(j->j_line, -1);
69 set_hasrun(j->j_line->cl_option);
72 if (is_runonce(j->j_line->cl_option) && is_hasrun(j->j_line->cl_option)) {
73 explain("Line %s has runonce set: not re-scheduling it.",
75 job_queue_remove(j->j_line);
78 set_next_exe(j->j_line, STD, -1);
86 switch_timezone(const char *orig_tz, const char *dest_tz)
87 /* check if we have already switched to dest_tz timezone, otherwise do it */
88 /* If dest_tz is NULL, this function does nothing */
89 /* Returns 1 if this function has switched the timezone, 0 otherwise */
91 char *current_tz = getenv("TZ");
93 if (dest_tz != NULL &&
94 (current_tz == NULL || strcmp(dest_tz, current_tz) != 0)) {
95 my_setenv_overwrite("TZ", dest_tz);
103 switch_back_timezone(const char *orig_tz)
104 /* if orig_tz is NULL, unsets TZ
105 * otherwise, sets TZ to orig_tz */
107 if (orig_tz == NULL) {
111 my_setenv_overwrite("TZ", orig_tz);
117 mktime_no_dst(struct tm *t)
118 /* same as mktime(), but without daylight saving time (dst) change adjustment
119 * (ie. the returned time_t does not depend on the tm_isdst field) */
120 /* Remark : you may think that instead of creating a new function,
121 * it would be easier to set the tm_isdst field to -1.
122 * Unfortunately, the behaviour of mktime() with
123 * tm_isdst set to -1 depends on the unix you run.
124 * In other word, it wouldn't be portable. */
126 * WARNING : the content of t has to be valid (for instance, 0<=t->tm_hour<=23,
137 debug("after mktime() : %d:%d isdst:%d ti:%ld\n",
138 t2.tm_hour, t2.tm_min, t2.tm_isdst, ti1);
141 /* check if there have been a dst change adjustment */
142 if (t->tm_isdst != t2.tm_isdst) {
147 /* recompute the time_t field with the other isdst value
148 * it works well, unless in a special case, hence the test
151 t3.tm_isdst = t2.tm_isdst;
154 debug("after dst fix 1 : %d:%d isdst:%d ti:%ld\n",
155 t3.tm_hour, t3.tm_min, t3.tm_isdst, ti2);
158 /* if t1 is in the "gap" of a dst change (for instance,
159 * if t1 is 2:30 while at 2:00, it is 3:00 due to the dst change,
160 * ie. 2:30 is never reached), the ti2 may be incorrect :
161 * we check that it is correct before using it : */
162 if (t3.tm_hour == t->tm_hour || ti1 < ti2) {
175 run_normal_job(cl_t * line, int info_fd)
176 /* run a job, and write "log" on info_fd if positive */
179 if (line->cl_numexe <= 0 ||
180 (is_exe_sev(line->cl_option) && line->cl_numexe < UCHAR_MAX)) {
181 line->cl_numexe += 1;
183 send_msg_fd(info_fd, "Job %s started.", line->cl_shell);
186 warn_fd(info_fd, " process already running: %s's %s",
187 line->cl_file->cf_user, line->cl_shell);
193 run_lavg_job(lavg_t * l)
196 run_queue_job(l->l_line);
198 if (is_serial(l->l_line->cl_option))
199 lavg_serial_running++;
206 /* run the next serialized job */
209 /* debug("running next serial job"); */
212 debug("num: %d running:%d index:%d", serial_num, serial_running,
214 if (serial_num != 0) {
215 run_queue_job(serial_array[serial_array_index]);
216 serial_array[serial_array_index] = NULL;
219 if (++serial_array_index >= serial_array_size)
220 serial_array_index -= serial_array_size;
228 run_queue_job(cl_t * line)
232 exe_t e = { NULL, 0, 0 };
235 /* debug("run_queue_job"); */
241 if (run_job(&e) == OK) {
242 /* append job to the list of executed job */
243 exe_list_add(exe_list, &e);
244 line->cl_file->cf_running += 1;
250 job_queue_remove(cl_t * line)
251 /* remove a job from the queue list
252 * returns a pointer to the previous entry,
253 * or NULL if the line either wasn't in the queue or was the first entry */
256 struct job_t *jprev = NULL;
258 if (queue_base == NULL)
261 /* find the job in the list */
262 for (j = queue_base; j != NULL; jprev = j, j = j->j_next) {
263 if (j->j_line == line) {
264 /* remove it from the list */
266 jprev->j_next = j->j_next;
269 /* first element of the list */
270 queue_base = j->j_next;
277 /* the job wasn't there */
282 insert_nextexe(cl_t * line)
283 /* insert a job at the right position in the job queue */
285 struct job_t *newjob = NULL;
286 struct job_t *j = NULL;
287 struct job_t *jprev = NULL;
289 Alloc(newjob, job_t);
290 newjob->j_line = line;
291 newjob->j_next = NULL;
293 if (queue_base == NULL) {
294 /* no job in queue */
299 jprev = job_queue_remove(line);
300 j = (jprev) ? jprev : queue_base;
302 /* check if we should start from queue_base or from jprev
303 * (in some cases, e.g. fcrontab has just been edited, the line should
304 * be moved *forward* in the queue) */
305 if (jprev == NULL || line->cl_nextexe < jprev->j_line->cl_nextexe) {
309 /* a job can only be moved back */
310 while (j != NULL && (line->cl_nextexe >= j->j_line->cl_nextexe)) {
314 /* when we get out from the while(), newjob should be added between jprev and j */
321 jprev->j_next = newjob;
326 add_serial_job(cl_t * line, int info_fd)
327 /* add the next queued job in serial queue */
331 /* check if the line is already in the serial queue
332 * (we consider serial jobs currently running as in the queue) */
333 if ((is_serial_sev(line->cl_option) && line->cl_numexe >= UCHAR_MAX) ||
334 (!is_serial_sev(line->cl_option) && line->cl_numexe > 0)) {
335 send_msg_fd_debug(info_fd, "already in serial queue %s",
340 send_msg_fd_debug(info_fd, "inserting in serial queue %s", line->cl_shell);
342 if (serial_num >= serial_array_size) {
343 if (serial_num >= serial_queue_max) {
344 error_fd(info_fd, "Could not add job : serial queue is full "
345 "(%d jobs). Consider using option serialonce, fcron's "
346 "option -m and/or -q : %s", serial_queue_max,
348 if (is_notice_notrun(line->cl_option))
349 mail_notrun(line, QUEUE_FULL, NULL);
354 short int old_size = serial_array_size;
356 debug("Resizing serial_array");
357 serial_array_size = (serial_array_size + SERIAL_GROW_SIZE);
360 alloc_safe(serial_array_size * sizeof(cl_t *), "serial_array");
362 /* copy lines in order to have the first line at the index 0 */
363 memcpy(ptr + serial_array_index, serial_array,
364 (sizeof(cl_t *) * (old_size - serial_array_index)));
365 memcpy(ptr, serial_array + (old_size - serial_array_index),
366 (sizeof(cl_t *) * serial_array_index));
367 serial_array_index = 0;
368 Free_safe(serial_array);
373 if ((i = serial_array_index + serial_num) >= serial_array_size)
374 i -= serial_array_size;
376 serial_array[i] = line;
379 line->cl_numexe += 1;
381 send_msg_fd_debug(info_fd, "serial num: %d size:%d index:%d curline:%d "
382 "running:%d (%s)", serial_num, serial_array_size,
383 serial_array_index, i, serial_running, line->cl_shell);
390 add_lavg_job(cl_t * line, int info_fd)
391 /* add the next queued job in lavg queue */
392 /* WARNING : must be run before a set_next_exe() to get the strict option
393 * working correctly */
395 lavg_t *lavg_entry = NULL;
397 /* check if the line is already in the lavg queue
398 * (we consider serial jobs currently running as in the queue) */
399 if ((is_lavg_sev(line->cl_option) && line->cl_numexe >= UCHAR_MAX) ||
400 (!is_lavg_sev(line->cl_option) && line->cl_numexe > 0)) {
401 send_msg_fd_debug(info_fd, "already in lavg queue %s", line->cl_shell);
405 send_msg_fd_debug(info_fd, "inserting in lavg queue %s", line->cl_shell);
408 /* append job to the list of lavg job */
409 lavg_entry = lavg_list_add_line(lavg_list, line);
410 if (lavg_entry == NULL) {
411 error_fd(info_fd, "Could not add job : lavg queue is full (%d jobs)."
412 " Consider using options lavgonce, until, strict and/or "
413 "fcron's option -q.", lavg_list->max_entries, line->cl_shell);
414 if (is_notice_notrun(line->cl_option))
415 mail_notrun(line, QUEUE_FULL, NULL);
419 line->cl_numexe += 1;
420 set_run_if_late(line->cl_option);
421 if (is_strict(line->cl_option) && line->cl_runfreq == 1) {
424 time_t begin_of_cur_int, end_of_cur_int = 0;
427 /* Switch to another timezone if necessary. */
428 /* If line should be scheduled in a different time zone
429 * (ie. cl_tz != NULL),
430 * switch to that timezone now, do the calculations,
431 * and switch back to the local timezone at the end
432 * of the function. */
433 tz_changed = switch_timezone(orig_tz_envvar, line->cl_tz);
435 /* handle timezone differences */
436 begin_of_cur_int = line->cl_nextexe - (line->cl_file->cf_tzdiff * 3600);
438 ft = localtime(&begin_of_cur_int);
440 /* localtime() function seems to return every time the same pointer :
441 * it resets our previous changes, so we need to prevent it
442 * ( localtime() is used in the debug() function) */
443 memcpy(&ftime, ft, sizeof(struct tm));
445 move_time_to(END_OF_INTERVAL, line, &ftime);
448 mktime_no_dst(&ftime) + (line->cl_file->cf_tzdiff * 3600);
450 if ((line->cl_until > 0) && (line->cl_until + now < end_of_cur_int))
451 lavg_entry->l_until = line->cl_until + now;
453 lavg_entry->l_until = end_of_cur_int;
454 clear_run_if_late(line->cl_option);
458 switch_back_timezone(orig_tz_envvar);
461 lavg_entry->l_until = (line->cl_until > 0) ? now + line->cl_until : 0;
468 /* wait_chld() - check for job completion */
475 /* debug("wait_chld"); */
478 while ((pid = wait3(NULL, WNOHANG, NULL)) > 0) {
480 for (e = exe_list_first(exe_list); e != NULL;
481 e = exe_list_next(exe_list)) {
483 if (pid == e->e_ctrl_pid) {
484 if (e->e_line == NULL) {
485 /* the corresponding file has been removed from memory */
486 debug("job finished: pid %d", pid);
491 /* debug("job finished: %s", line->cl_shell); */
492 line->cl_numexe -= 1;
493 line->cl_file->cf_running -= 1;
495 if (is_serial_once(line->cl_option)) {
496 clear_serial_once(line->cl_option);
497 if (--serial_running < serial_max_running)
500 else if (is_serial(line->cl_option)
501 && !is_lavg(line->cl_option)) {
502 if (--serial_running < serial_max_running)
505 else if (is_lavg(line->cl_option)
506 && is_serial(line->cl_option))
507 lavg_serial_running--;
510 exe_list_remove_cur(exe_list);
511 exe_list_end_iteration(exe_list);
522 wait_all(int *counter)
523 /* return after all jobs completion. */
528 debug("Waiting for all jobs");
530 while ((*counter > 0) && (pid = wait3(NULL, 0, NULL)) > 0) {
531 for (e = exe_list_first(exe_list); e != NULL;
532 e = exe_list_next(exe_list)) {
533 if (pid == e->e_ctrl_pid) {
534 if (e->e_line == NULL) {
535 /* the corresponding file has been removed from memory */
536 debug("job finished: pid %d", pid);
540 debug("job finished: %s", e->e_line->cl_shell);
541 e->e_line->cl_numexe -= 1;
542 e->e_line->cl_file->cf_running -= 1;
544 if (is_serial_once(e->e_line->cl_option))
545 clear_serial_once(e->e_line->cl_option);
549 exe_list_remove_cur(exe_list);
550 exe_list_end_iteration(exe_list);
560 is_leap_year(int year)
561 /* return 1 if it's a leap year otherwise return 0 */
563 return ((year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0)));
569 get_nb_mdays(int year, int mon)
570 /* return the number of days in a given month of a given year */
572 if (mon == 1) { /* is February ? */
573 if (is_leap_year(year))
583 else if (mon % 2 == 0)
592 set_wday(struct tm *date)
593 /* we know that 01/01/2000 was a Saturday ( day number 6 )
594 * so we count the number of days since 01/01/2000,
595 * and add this number modulo 7 to the wday number */
600 /* we add the number of days of each previous years */
601 for (i = (date->tm_year - 1); i >= 100; i--)
602 nod += (is_leap_year(i + 1900)) ? 366 : 365;
605 for (i = (date->tm_mon - 1); i >= 0; i--)
606 nod += get_nb_mdays((date->tm_year + 1900), i);
608 /* then we add the number of days passed in the current month */
609 nod += (date->tm_mday - 1); /* (mday is set from 1 to 31) */
611 date->tm_wday = (nod % 7) + 6;
613 if (date->tm_wday >= 7)
616 debug(" dow of %d-%d-%d : %d", (date->tm_mon + 1), date->tm_mday,
617 (date->tm_year + 1900), date->tm_wday);
623 goto_beginning_next_period_periodical(cl_t * line, struct tm *ftime)
624 /* From ftime, search the first/nearest time and date of the line's next
625 * period of execution.
627 * Line must be periodical (i.e. is_freq_periodically(line->cl_option) == TRUE)
629 * ftime will contain this time and date when this function returns.
632 * interval of execution= a continuous interval of time during which
633 * the line can be executed.
634 * period of execution= a continuous interval of time during which
635 * the line is to be executed once and only once. */
640 if (!is_freq_periodically(line->cl_option))
641 die("goto_beginning_next_period() called with a non periodical line");
644 /* number of days in ftime's month */
645 max = get_nb_mdays(ftime->tm_year, ftime->tm_mon);
647 /* STEP 1: find the beginning of the next period without ensuring
648 * there is no overflow (min>=60, hour>=24, etc) */
650 if (is_freq_mid(line->cl_option)) {
652 if (is_freq_mins(line->cl_option))
653 /* nothing to do : return */
655 else if (is_freq_hrs(line->cl_option)) {
656 if (ftime->tm_min >= 30)
662 if (is_freq_days(line->cl_option)) {
663 if (ftime->tm_hour >= 12)
669 if (is_freq_dow(line->cl_option)) {
670 int to_add = (ftime->tm_wday >= 4) ? 11 - ftime->tm_wday :
672 if (ftime->tm_mday + to_add > max) {
674 ftime->tm_mday = ftime->tm_mday + to_add - max;
677 ftime->tm_mday += to_add;
680 if (is_freq_mons(line->cl_option)) {
681 if (ftime->tm_mday >= 15)
686 /* weird : we have the bit freq_mid set, but
687 * none of freq_{mins|hour|days|dow|mons} is set :
688 * we do nothing but increase tm_min by 1
689 * so as we don't return a time in the past */
691 warn("Line %s doesn't seem correct: consider "
692 "reinstalling the corresponding fcrontab");
700 else { /* is_freq_mid(line->cl_option) */
702 if (is_freq_mins(line->cl_option))
707 if (is_freq_hrs(line->cl_option))
711 if (is_freq_days(line->cl_option))
714 if (is_freq_dow(line->cl_option)) {
716 (ftime->tm_wday == 0) ? 1 : 8 - ftime->tm_wday;
717 if (ftime->tm_mday + to_add > max) {
718 ftime->tm_mday = ftime->tm_mday + to_add - max;
722 ftime->tm_mday += to_add;
726 if (is_freq_mons(line->cl_option))
733 } /* is_freq_mid(line->cl_option) */
735 /* we set tm_sec to 0 here and not before to ensure we will never return
736 * a time in the past (case is_freq_mins(line->cl_option)
737 * where we do nothing) */
740 /* STEP 2: fix the overflows.
741 * (a value may exceed the max value of a field: fix it if necessary) */
743 if (ftime->tm_min >= 60) {
747 if (ftime->tm_hour >= 24) {
751 /* the month field may have changed */
752 max = get_nb_mdays((ftime->tm_year + 1900), ftime->tm_mon);
753 if (ftime->tm_mday > max) {
757 if (ftime->tm_mon >= 12) {
764 debug(" %s beginning of next period %d/%d/%d wday:%d %02d:%02d "
765 "(tzdiff=%d, timezone=%s)", line->cl_shell, (ftime->tm_mon + 1),
766 ftime->tm_mday, (ftime->tm_year + 1900), ftime->tm_wday,
767 ftime->tm_hour, ftime->tm_min, line->cl_file->cf_tzdiff,
768 (line->cl_tz != NULL) ? line->cl_tz : "localtime");
774 move_time_to(int where, cl_t * line, struct tm *ftime)
775 /* IF WHERE == BEGIN_NEXT_PERIOD: from ftime, search the first/nearest time and date
776 * of the line's next period of execution.
777 * IF WHERE == END_OF_INTERVAL: search the last time and date
778 * of the line's interval of execution containing ftime.
780 * ftime will contain this time and date when move_time_to returns.
783 * interval of execution= a continuous interval of time during which
784 * the line can be executed.
785 * period of execution= a continuous interval of time during which
786 * the line is to be executed once and only once. */
788 struct tm tm_next_period;
790 /* by default we set timet_next_period to now + 10 years, which will
791 * always be later than the end of the interval of execution
792 * so as to make the test timet_ftime < timet_next_period always true */
793 time_t timet_next_period = now + 10 * 365 * 24 * 3600;
795 /* to prevent from infinite loop with unvalid lines : */
796 short int year_limit = MAXYEAR_SCHEDULE_TIME;
797 /* Depending on the situation we may need to ignore some fields
798 * while we walk through time */
799 char ignore_mins, ignore_hrs, ignore_days, ignore_mons, ignore_dow;
802 if (where != BEGIN_NEXT_PERIOD && where != END_OF_INTERVAL)
803 die("move_time_to() called with invalid argument 'where': %d",
807 if (where == BEGIN_NEXT_PERIOD && is_freq_periodically(line->cl_option)) {
808 goto_beginning_next_period_periodical(line, ftime);
812 /* In all other cases, we will have to walk through time */
814 if (is_freq_periodically(line->cl_option)) {
816 /* In this case we want to make sure we won't go after the end
817 * of the period of execution, so we need to set next_period */
819 memcpy(&tm_next_period, ftime, sizeof(tm_next_period));
820 goto_beginning_next_period_periodical(line, &tm_next_period);
821 timet_next_period = mktime_no_dst(&tm_next_period);
825 timet_ftime = mktime_no_dst(ftime);
827 if (where == BEGIN_NEXT_PERIOD) {
828 /* we have to ignore the fields containing single numbers */
829 ignore_mins = (is_freq_mins(line->cl_option)) ? 1 : 0;
830 ignore_hrs = (is_freq_hrs(line->cl_option)) ? 1 : 0;
831 ignore_days = (is_freq_days(line->cl_option)) ? 1 : 0;
832 ignore_mons = (is_freq_mons(line->cl_option)) ? 1 : 0;
833 ignore_dow = (is_freq_dow(line->cl_option)) ? 1 : 0;
836 /* we want to go to the end of the current interval:
837 * we don't ignore anything */
838 ignore_mins = ignore_hrs = ignore_days = ignore_mons = ignore_dow = 0;
842 debug(" ignore: %d %d %d %d %d", ignore_mins, ignore_hrs,
843 ignore_days, ignore_mons, ignore_dow);
846 /* while we are in an interval of execution and not in the next period */
847 while ((ignore_mins == 1 || bit_test(line->cl_mins, ftime->tm_min)) &&
848 (ignore_hrs == 1 || bit_test(line->cl_hrs, ftime->tm_hour)) &&
849 ((is_dayand(line->cl_option) &&
850 (ignore_days == 1 || bit_test(line->cl_days, ftime->tm_mday)) &&
851 (ignore_dow == 1 || bit_test(line->cl_dow, ftime->tm_wday)))
853 (is_dayor(line->cl_option) &&
854 (ignore_days == 1 || bit_test(line->cl_days, ftime->tm_mday) ||
855 ignore_dow == 1 || bit_test(line->cl_dow, ftime->tm_wday)))
856 ) && (ignore_mons == 1 || bit_test(line->cl_mons, ftime->tm_mon))
857 && (timet_ftime < timet_next_period)
866 while (bit_test(line->cl_mins, ftime->tm_min)
867 && (ftime->tm_min < 60));
869 if (ftime->tm_min >= 60) {
871 if (ignore_hrs && ignore_mins)
875 if (ftime->tm_hour >= 24) {
877 if (ignore_days && ignore_hrs && ignore_mins && ignore_dow)
878 ftime->tm_mday = 32; /* go to next month */
882 get_nb_mdays((ftime->tm_year + 1900), ftime->tm_mon)) {
884 if (ignore_mons && ignore_days && ignore_dow
885 && ignore_hrs && ignore_mins)
889 if (ftime->tm_mon >= 12) {
892 if (--year_limit <= 0) {
893 error("Can't found a non matching date for %s "
894 "in the next %d years. Maybe this line "
895 "is corrupted : consider reinstalling "
896 "the fcrontab", line->cl_shell,
897 MAXYEAR_SCHEDULE_TIME);
908 /* set temporarily debug_opt to false to avoid having too many
909 * messages in the logs */
910 char debug_opt_previous = debug_opt;
913 timet_ftime = mktime_no_dst(ftime);
915 debug_opt = debug_opt_previous;
921 if (timet_ftime > timet_next_period) {
922 /* the end of the interval if after the end of the period:
923 * we don't want to go in the next period, so we return
924 * the value of the end of the period. */
925 memcpy(ftime, &tm_next_period, sizeof(tm_next_period));
928 if (where == END_OF_INTERVAL) {
929 /* we want the end of the current interval, not the beginning
930 * of the first non-matching interval : go back by one minute */
931 if (--ftime->tm_min < 0) {
933 if (--ftime->tm_hour < 0) {
935 if (--ftime->tm_mday < 1) {
936 if (--ftime->tm_mon < 0) {
941 get_nb_mdays((ftime->tm_year + 1900), ftime->tm_mon);
947 debug(" %s %s %d/%d/%d wday:%d %02d:%02d (tzdiff=%d, timezone=%s)",
950 END_OF_INTERVAL) ? "end of interval" : "begin of next period",
951 (ftime->tm_mon + 1), ftime->tm_mday, (ftime->tm_year + 1900),
952 ftime->tm_wday, ftime->tm_hour, ftime->tm_min,
953 line->cl_file->cf_tzdiff,
954 (line->cl_tz != NULL) ? line->cl_tz : "localtime");
959 set_next_exe(cl_t * line, char option, int info_fd)
960 /* set the cl_nextexe of a given cl_t and insert it in the queue */
968 basetime = (option & FROM_CUR_NEXTEXE) ? line->cl_nextexe : now;
970 /* Switch to another timezone if necessary. */
971 /* If line should be scheduled in a different time zone
972 * (ie. cl_tz != NULL),
973 * switch to that timezone now, do the calculations,
974 * and switch back to the local timezone at the end
975 * of the function. */
976 tz_changed = switch_timezone(orig_tz_envvar, line->cl_tz);
978 if (is_td(line->cl_option)) {
983 char has_changed = 0;
984 /* to prevent from invinite loop with unvalid lines : */
985 short int year_limit = MAXYEAR_SCHEDULE_TIME;
986 /* timezone difference */
987 time_t basetime_tz = basetime - (line->cl_file->cf_tzdiff * 3600);
989 ft = localtime(&basetime_tz);
991 /* localtime() function seem to return every time the same pointer :
992 * it resets our previous changes, so we need to prevent it
993 * ( localtime() is used in the debug() function) */
994 memcpy(&ftime, ft, sizeof(struct tm));
996 /* creates a bug on DST change on some systems ?? */
997 /* ftime.tm_isdst = -1; */
999 /* to prevent multiple execution of &-jobs in the same minute
1000 * (but not if the user has explicitely asked to run jobs immediately) */
1001 if (first_sleep > 0 || option == STD || line->cl_runfreq != 1) {
1006 if (line->cl_runfreq == 1 && option != NO_GOTO && option != NO_GOTO_LOG)
1007 /* %-line: go to next period */
1008 move_time_to(BEGIN_NEXT_PERIOD, line, &ftime);
1011 for (i = ftime.tm_mon; (bit_test(line->cl_mons, i) == 0) && (i < 12);
1015 if (--year_limit <= 0) {
1016 error("Can't found a matching date for %s in the next %d"
1017 " years. Maybe this line is corrupted : consider"
1018 " reinstalling the fcrontab.",
1019 line->cl_shell, MAXYEAR_SCHEDULE_TIME);
1020 goto set_cl_nextexe;
1022 if (has_changed < 3) {
1033 if (ftime.tm_mon != i) {
1035 if (has_changed < 2) {
1043 /* set the number of days in that month */
1044 max = get_nb_mdays((ftime.tm_year + 1900), ftime.tm_mon);
1047 if (is_dayand(line->cl_option)) {
1048 for (i = ftime.tm_mday;
1049 (bit_test(line->cl_days, i) == 0) && (i <= max); i++) ;
1052 if (has_changed < 2) {
1062 if (ftime.tm_mday != i) {
1064 if (has_changed < 1) {
1073 /* check if the day of week is OK */
1074 if (bit_test(line->cl_dow, ftime.tm_wday) == 0) {
1088 while ((bit_test(line->cl_days, i) == 0) &&
1089 (bit_test(line->cl_dow, j) == 0)) {
1092 if (has_changed < 2) {
1107 if (ftime.tm_mday != i) {
1109 if (has_changed < 1) {
1118 for (i = ftime.tm_hour; (bit_test(line->cl_hrs, i) == 0) && (i < 24);
1122 if (has_changed < 1) {
1131 if (ftime.tm_hour != i) {
1137 for (i = ftime.tm_min; (bit_test(line->cl_mins, i) == 0) && (i < 60);
1147 /* set cl_nextexe (handle the timezone differences) */
1149 /* NOTE : the output of mktime does not depend on the timezone,
1150 * hence, nextexe is correct even if option timezone is used. */
1151 nextexe = mktime_no_dst(&ftime);
1153 if (is_random(line->cl_option)) {
1154 /* run the job at a random time during its interval of execution */
1156 time_t int_end_timet;
1158 debug(" cmd: %s begin int exec %d/%d/%d wday:%d %02d:%02d "
1159 "(tzdiff=%d, timezone=%s)", line->cl_shell,
1160 (ftime.tm_mon + 1), ftime.tm_mday, (ftime.tm_year + 1900),
1161 ftime.tm_wday, ftime.tm_hour, ftime.tm_min,
1162 line->cl_file->cf_tzdiff,
1163 (line->cl_tz != NULL) ? line->cl_tz : "localtime");
1165 memcpy(&int_end, &ftime, sizeof(int_end));
1166 move_time_to(END_OF_INTERVAL, line, &int_end);
1167 int_end_timet = mktime_no_dst(&int_end);
1169 /* set a random time to add to the first allowed time of execution */
1170 nextexe += ((i = int_end_timet - nextexe) > 0) ?
1171 (time_t) (((float)i * (float)rand()) / (float)RAND_MAX) : 0;
1173 else if (is_td(line->cl_option) && line->cl_runfreq != 1
1174 && line->cl_jitter > 0) {
1176 * run the command between nextexe and nextexe+jitter seconds,
1177 * as a way not to have 100 jobs all starting exactly at
1178 * the second 0 of the minute they should run */
1180 (time_t) (((float)line->cl_jitter * (float)rand()) /
1184 line->cl_nextexe = nextexe + (line->cl_file->cf_tzdiff * 3600);
1186 if (option != NO_GOTO) {
1187 if (is_random(line->cl_option)) {
1188 ft = localtime(&nextexe);
1189 memcpy(&ftime, ft, sizeof(ftime));
1191 send_msg_fd_debug(info_fd, " cmd: %s next exec %d/%d/%d wday:%d "
1192 "%02d:%02d:%02d (tzdiff=%d, timezone=%s)",
1193 line->cl_shell, (ftime.tm_mon + 1), ftime.tm_mday,
1194 (ftime.tm_year + 1900), ftime.tm_wday,
1195 ftime.tm_hour, ftime.tm_min, ftime.tm_sec,
1196 line->cl_file->cf_tzdiff,
1197 (line->cl_tz != NULL) ? line->cl_tz : "system's");
1202 * if the nextexe is set to the past because of a bug,
1203 * the line will be executed again immediately, and it is most likely
1204 * to be set again in the past next time.
1205 * It would create a nasty infinite loop, a kind of "while(1) fork();"
1207 * We add a test here to limit the consequences that would have
1208 * an unknown bug in this function.
1210 if (line->cl_nextexe <= now) {
1211 error("BUG ??? Fcron thinks the next exe time of %s is %ld, "
1212 "hence before now (%ld). To avoid infinite loop, nextexe"
1213 " will be set at now+5s.", line->cl_shell, line->cl_nextexe);
1214 line->cl_nextexe = now + 5;
1219 /* this is a job based on system up time */
1221 if (line->cl_timefreq == LONG_MAX) {
1222 /* when timefreq is set to LONG_MAX, it means that next time nextexe
1223 * is updated we want it to be the furthest away possible so as the job
1224 * is never executed again (unless at the next reboot/fcron startup
1225 * if the line as the appropriate options set) */
1226 /* NOTE: the options runonce/hasrun should be used to achieve this,
1227 * but we keep this here as an extra safety */
1229 ("Setting cl_nextexe to LONG_MAX to prevent the line from running again.");
1230 line->cl_nextexe = LONG_MAX;
1233 line->cl_nextexe = basetime + line->cl_timefreq;
1234 if (line->cl_nextexe <= basetime) {
1235 /* there was an integer overflow! */
1236 error("Error while setting next exe time for job %s: cl_nextexe"
1237 " overflowed. basetime=%lu, cl_timefreq=%lu, cl_nextexe=%lu.",
1238 line->cl_shell, basetime, line->cl_timefreq,
1241 ("Setting cl_nextexe to LONG_MAX to prevent an infinite loop.");
1242 line->cl_nextexe = LONG_MAX;
1246 ft = localtime(&(line->cl_nextexe));
1248 /* localtime() function seem to return every time the same pointer :
1249 * it resets our previous changes, so we need to prevent it
1250 * ( localtime() is used in the debug() function) */
1251 memcpy(&ftime, ft, sizeof(struct tm));
1253 send_msg_fd_debug(info_fd, " cmd: %s next exec %d/%d/%d wday:%d "
1254 "%02d:%02d:%02d (system time)", line->cl_shell,
1255 (ftime.tm_mon + 1), ftime.tm_mday,
1256 (ftime.tm_year + 1900), ftime.tm_wday, ftime.tm_hour,
1257 ftime.tm_min, ftime.tm_sec);
1260 insert_nextexe(line);
1263 switch_back_timezone(orig_tz_envvar);
1269 set_next_exe_notrun(cl_t * line, char context)
1270 /* set the time of the next execution and send a mail to tell user his job
1271 * has not run if necessary */
1273 time_t previous_period = 0, next_period = 0;
1274 struct tm *ft = NULL;
1275 struct tm ftime, last_nextexe;
1276 char set_next_exe_opt = 0;
1280 debug(" set_next_exe_notrun : %s %d", line->cl_shell, context);
1284 /* Switch to another timezone if necessary. */
1285 /* If line should be scheduled in a different time zone
1286 * (ie. cl_tz != NULL),
1287 * switch to that timezone now, do the calculations,
1288 * and switch back to the local timezone at the end
1289 * of the function. */
1290 tz_changed = switch_timezone(orig_tz_envvar, line->cl_tz);
1292 if (context == SYSDOWN || context == SYSDOWN_RUNATREBOOT) {
1293 /* handle timezone differences */
1294 previous_period = line->cl_nextexe - (line->cl_file->cf_tzdiff * 3600);
1295 set_next_exe_opt = NO_GOTO;
1298 previous_period = now - (line->cl_file->cf_tzdiff * 3600);
1299 set_next_exe_opt = NO_GOTO_LOG;
1301 ft = localtime(&previous_period);
1303 /* localtime() function seem to return every time the same pointer :
1304 * it resets our previous changes, so we need to prevent it
1305 * ( localtime() is used in the debug() function) */
1306 memcpy(&ftime, ft, sizeof(ftime));
1307 /* we also copy it to last_nextexe which will be used in mail_notrun */
1308 memcpy(&last_nextexe, ft, sizeof(last_nextexe));
1311 move_time_to(BEGIN_NEXT_PERIOD, line, &ftime);
1312 next_period = mktime_no_dst(&ftime) + (line->cl_file->cf_tzdiff * 3600);
1314 if (context == SYSDOWN_RUNATREBOOT)
1315 line->cl_nextexe = now;
1317 set_next_exe(line, set_next_exe_opt, -1);
1319 if (line->cl_nextexe >= next_period) {
1320 /* line has not run during one or more period(s) : send a mail */
1321 mail_notrun(line, context, &last_nextexe);
1325 switch_back_timezone(orig_tz_envvar);
1330 mail_notrun_time_t(cl_t * line, char context, time_t since_time_t)
1331 /* Same as mail_notrun() but with 'since' defined as a time_t instead of a struct tm */
1333 struct tm *since2 = NULL;
1337 since2 = localtime(&line->cl_nextexe);
1338 memcpy(&since, since2, sizeof(since));
1340 tz_changed = switch_timezone(orig_tz_envvar, line->cl_tz);
1342 mail_notrun(line, SYSDOWN, &since);
1345 switch_back_timezone(orig_tz_envvar);
1350 mail_notrun(cl_t * line, char context, struct tm *since)
1351 /* send a mail to tell user a job has not run (and why) */
1355 struct tm *time2 = NULL, time;
1356 char **sendmailenv = NULL;
1358 switch (pid = fork()) {
1360 error_e("Fork error : could not mail for not run %s", line->cl_shell);
1369 debug("Reporting by mail non execution of %s (pid %d)",
1370 line->cl_shell, pid);
1373 /* create an entry in exe_list */
1374 /* set line to NULL as this is not a line ... */
1375 exe_list_add_line(exe_list, NULL);
1379 /* If line should be scheduled in a different time zone
1380 * (ie. cl_tz != NULL),
1381 * switch to that timezone now, before we do the calculations. */
1382 /* No need to switch back as this function does NOT return. */
1383 switch_timezone(orig_tz_envvar, line->cl_tz);
1385 if (context == QUEUE_FULL)
1386 time2 = localtime(&now);
1388 time2 = localtime(&line->cl_nextexe);
1389 memcpy(&time, time2, sizeof(time));
1391 /* create a temp file, and write in it the message to send */
1392 mailf = create_mail(line, "Non-execution of fcron job", NULL, NULL, NULL);
1396 fprintf(mailf, "Line %s has not run since and including "
1397 "%d/%d/%d wday:%d %02d:%02d (timezone=%s)\n"
1398 "due to system's down state.\n",
1399 line->cl_shell, (since->tm_mon + 1), since->tm_mday,
1400 (since->tm_year + 1900), since->tm_wday, since->tm_hour,
1401 since->tm_min, (line->cl_tz) ? line->cl_tz : "system's");
1402 fprintf(mailf, "It will be next executed at %d/%d/%d wday:"
1403 "%d %02d:%02d\n", (time.tm_mon + 1), time.tm_mday,
1404 (time.tm_year + 1900), time.tm_wday, time.tm_hour, time.tm_min);
1407 fprintf(mailf, "Line %s has not run since and including "
1408 "%d/%d/%d wday:%d %02d:%02d (timezone=%s)\n",
1409 line->cl_shell, (since->tm_mon + 1), since->tm_mday,
1410 (since->tm_year + 1900), since->tm_wday, since->tm_hour,
1411 since->tm_min, (line->cl_tz) ? line->cl_tz : "system's");
1412 fprintf(mailf, "due to a too high system load average or "
1413 "too many lavg-serial jobs.\n");
1414 fprintf(mailf, "It will be next executed at %d/%d/%d "
1415 "wday:%d %02d:%02d (timezone=%s)\n", (time.tm_mon + 1),
1416 time.tm_mday, (time.tm_year + 1900), time.tm_wday, time.tm_hour,
1417 time.tm_min, (line->cl_tz) ? line->cl_tz : "system's");
1420 fprintf(mailf, "Line %s couldn't be added to lavg or serial queue which"
1421 " is full ( %d/%d/%d wday:%d %02d:%02d (timezone=%s)).\n",
1422 line->cl_shell, (time.tm_mon + 1), time.tm_mday,
1423 (time.tm_year + 1900), time.tm_wday, time.tm_hour, time.tm_min,
1424 (line->cl_tz) ? line->cl_tz : "system's");
1425 fprintf(mailf, "Consider using options lavgonce, until, strict, "
1426 "serialonce and/or fcron's option -m.\n");
1427 fprintf(mailf, "Note that job %s has not run.\n", line->cl_shell);
1431 /* become user (for security reasons) */
1432 change_user_setup_env(line, &sendmailenv, NULL, NULL, NULL, NULL, NULL);
1434 /* then, send mail */
1435 launch_mailer(line, mailf, sendmailenv);
1437 /* we should not come here : launch_mailer does not return */
1438 die("mail_notrun : launch_mailer failed");
1443 check_lavg(time_t lim)
1444 /* run a job based on system load average if one should be run
1445 * and return the time to sleep */
1451 for (l = lavg_list_first(lavg_list); l != NULL;
1452 l = lavg_list_next(lavg_list)) {
1454 lavg_list_remove_cur(lavg_list);
1458 tts = time_to_sleep(lim);
1463 double l_avg[3] = { 0, 0, 0 };
1465 /* first, check if some lines must be executed because of until */
1466 for (l = lavg_list_first(lavg_list); l != NULL;
1467 l = lavg_list_next(lavg_list))
1468 if ((l->l_line->cl_until > 0 || l->l_line->cl_runfreq == 1)
1469 && l->l_until < now) {
1470 if (!is_run_if_late(l->l_line->cl_option)) {
1471 if (!is_nolog(l->l_line->cl_option))
1472 explain("Interval of execution exceeded : %s (not run)",
1473 l->l_line->cl_shell);
1475 /* set time of the next execution and send a mail if needed */
1476 if (is_td(l->l_line->cl_option) &&
1477 is_notice_notrun(l->l_line->cl_option))
1478 set_next_exe_notrun(l->l_line, LAVG);
1480 set_next_exe(l->l_line, NO_GOTO_LOG, -1);
1482 /* remove this job from the lavg queue */
1483 l->l_line->cl_numexe -= 1;
1484 lavg_list_remove_cur(lavg_list);
1487 debug("until %s %d", l->l_line->cl_shell, l->l_until);
1489 lavg_list_remove_cur(lavg_list);
1493 /* we do this set here as the nextexe of lavg line may change before */
1494 tts = time_to_sleep(lim);
1496 if (lavg_list->num_entries == 0)
1499 if ((i = getloadavg(l_avg, 3)) != 3)
1500 debug("got only %d lavg values", i);
1501 debug("get_lavg: %lf, %lf, %lf", l_avg[0], l_avg[1], l_avg[2]);
1502 /* the 3 values stored in the fcron lines are the real value *= 10 */
1506 for (l = lavg_list_first(lavg_list); l != NULL;
1507 l = lavg_list_next(lavg_list)) {
1508 /* check if the line should be executed */
1509 if (lavg_serial_running >= serial_max_running &&
1510 is_serial(l->l_line->cl_option)) {
1513 if ((is_land(l->l_line->cl_option)
1514 && (l_avg[0] < l->l_line->cl_lavg[0] || l->l_line->cl_lavg[0] == 0)
1515 && (l_avg[1] < l->l_line->cl_lavg[1] || l->l_line->cl_lavg[1] == 0)
1516 && (l_avg[2] < l->l_line->cl_lavg[2] || l->l_line->cl_lavg[2] == 0)
1518 || (is_lor(l->l_line->cl_option)
1519 && (l_avg[0] < l->l_line->cl_lavg[0]
1520 || l_avg[1] < l->l_line->cl_lavg[1]
1521 || l_avg[2] < l->l_line->cl_lavg[2])
1524 debug("lavg %s %s %.0f:%d %.0f:%d %.0f:%d",
1525 l->l_line->cl_shell,
1526 (is_lor(l->l_line->cl_option)) ? "or" : "and",
1527 l_avg[0], l->l_line->cl_lavg[0],
1528 l_avg[1], l->l_line->cl_lavg[1], l_avg[2],
1529 l->l_line->cl_lavg[2]);
1531 lavg_list_remove_cur(lavg_list);
1537 if (lavg_list->num_entries == 0)
1540 return (LAVG_SLEEP < tts) ? LAVG_SLEEP : tts;
1542 #endif /* def NOLOADAVG */
1547 time_to_sleep(time_t lim)
1548 /* return the time to sleep until next task have to be executed. */
1550 /* we set tts to a big value, unless some problems can occurs
1551 * with files without any line */
1553 time_t ti = time(NULL);
1555 /* note : jobs in queue_base are sorted */
1556 if (queue_base != NULL) {
1557 if (queue_base->j_line->cl_nextexe < lim)
1558 tts = queue_base->j_line->cl_nextexe;
1565 /* debug("Time to sleep: %lds", tts); */