]> granicus.if.org Git - fcron/blob - database.c
Updated copyright years to 2013
[fcron] / database.c
1 /*
2  * FCRON - periodic command scheduler 
3  *
4  *  Copyright 2000-2013 Thibault Godouet <fcron@free.fr>
5  *
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.
10  *
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.
15  * 
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
19  * 
20  *  The GNU General Public License can also be found in the file
21  *  `LICENSE' that comes with the fcron source distribution.
22  */
23
24
25 #include "fcron.h"
26
27 #include "database.h"
28 #include "job.h"
29 #include "getloadavg.h"
30
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);
42
43 void
44 test_jobs(void)
45   /* determine which jobs need to be run, and run them. */
46 {
47     struct job_t *j;
48
49 /*      // */
50     debug("Looking for jobs to execute ...");
51 /*      // */
52
53     while ((j = queue_base) && j->j_line->cl_nextexe <= now) {
54
55         if (j->j_line->cl_remain > 0 && --(j->j_line->cl_remain) > 0) {
56             debug("    cl_remain: %d", j->j_line->cl_remain);
57         }
58         else {
59
60             j->j_line->cl_remain = j->j_line->cl_runfreq;
61
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);
66             else
67                 run_normal_job(j->j_line, -1);
68
69             set_hasrun(j->j_line->cl_option);
70         }
71
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.",
74                     j->j_line->cl_shell);
75             job_queue_remove(j->j_line);
76         }
77         else {
78             set_next_exe(j->j_line, STD, -1);
79         }
80     }
81
82 }
83
84
85 int
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 */
90 {
91     char *current_tz = getenv("TZ");
92
93     if (dest_tz != NULL &&
94         (current_tz == NULL || strcmp(dest_tz, current_tz) != 0)) {
95         my_setenv_overwrite("TZ", dest_tz);
96         return 1;
97     }
98     else
99         return 0;
100 }
101
102 void
103 switch_back_timezone(const char *orig_tz)
104 /* if orig_tz is NULL, unsets TZ
105  * otherwise, sets TZ to orig_tz */
106 {
107     if (orig_tz == NULL) {
108         my_unsetenv("TZ");
109     }
110     else {
111         my_setenv_overwrite("TZ", orig_tz);
112     }
113 }
114
115
116 time_t
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. */
125 /*
126  * WARNING : the content of t has to be valid (for instance, 0<=t->tm_hour<=23,
127  *           etc)
128  */
129 {
130     struct tm t2;
131     time_t ti1;
132
133     t2 = *t;
134
135     ti1 = mktime(&t2);
136     /* */
137     debug("after  mktime() : %d:%d isdst:%d ti:%ld\n",
138           t2.tm_hour, t2.tm_min, t2.tm_isdst, ti1);
139     /* */
140
141     /* check if there have been a dst change adjustment */
142     if (t->tm_isdst != t2.tm_isdst) {
143
144         time_t ti2;
145         struct tm t3;
146
147         /* recompute the time_t field with the other isdst value
148          * it works well, unless in a special case, hence the test
149          * below */
150         t3 = *t;
151         t3.tm_isdst = t2.tm_isdst;
152         ti2 = mktime(&t3);
153         /* */
154         debug("after dst fix 1 : %d:%d isdst:%d ti:%ld\n",
155               t3.tm_hour, t3.tm_min, t3.tm_isdst, ti2);
156         /* */
157
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) {
163             t2 = t3;
164             ti1 = ti2;
165         }
166
167     }
168
169     *t = t2;
170     return ti1;
171 }
172
173
174 void
175 run_normal_job(cl_t * line, int info_fd)
176 /* run a job, and write "log" on info_fd if positive */
177 {
178
179     if (line->cl_numexe <= 0 ||
180         (is_exe_sev(line->cl_option) && line->cl_numexe < UCHAR_MAX)) {
181         line->cl_numexe += 1;
182         run_queue_job(line);
183         send_msg_fd(info_fd, "Job %s started.", line->cl_shell);
184     }
185     else {
186         warn_fd(info_fd, "    process already running: %s's %s",
187                 line->cl_file->cf_user, line->cl_shell);
188     }
189
190 }
191
192 void
193 run_lavg_job(lavg_t * l)
194 {
195
196     run_queue_job(l->l_line);
197
198     if (is_serial(l->l_line->cl_option))
199         lavg_serial_running++;
200
201 }
202
203
204 void
205 run_serial_job(void)
206     /* run the next serialized job */
207 {
208 /*      // */
209 /*      debug("running next serial job"); */
210 /*      //     */
211
212     debug("num: %d running:%d  index:%d", serial_num, serial_running,
213           serial_array_index);
214     if (serial_num != 0) {
215         run_queue_job(serial_array[serial_array_index]);
216         serial_array[serial_array_index] = NULL;
217
218         serial_running++;
219         if (++serial_array_index >= serial_array_size)
220             serial_array_index -= serial_array_size;
221         serial_num--;
222
223     }
224 }
225
226
227 void
228 run_queue_job(cl_t * line)
229     /* run a job */
230 {
231
232     exe_t e = { NULL, 0, 0 };
233
234 /*      // */
235 /*      debug("run_queue_job"); */
236 /*      // */
237
238     e.e_line = line;
239
240     /* run the 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;
245     }
246
247 }
248
249 job_t *
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 */
254 {
255     struct job_t *j;
256     struct job_t *jprev = NULL;
257
258     if (queue_base == NULL)
259         return NULL;
260
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 */
265             if (jprev != NULL) {
266                 jprev->j_next = j->j_next;
267             }
268             else
269                 /* first element of the list */
270                 queue_base = j->j_next;
271
272             Free_safe(j);
273             return jprev;
274         }
275     }
276
277     /* the job wasn't there */
278     return NULL;
279 }
280
281 void
282 insert_nextexe(cl_t * line)
283     /* insert a job at the right position in the job queue */
284 {
285     struct job_t *newjob = NULL;
286     struct job_t *j = NULL;
287     struct job_t *jprev = NULL;
288
289     Alloc(newjob, job_t);
290     newjob->j_line = line;
291     newjob->j_next = NULL;
292
293     if (queue_base == NULL) {
294         /* no job in queue */
295         queue_base = newjob;
296         return;
297     }
298
299     jprev = job_queue_remove(line);
300     j = (jprev) ? jprev : queue_base;
301
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) {
306         j = queue_base;
307     }
308
309     /* a job can only be moved back */
310     while (j != NULL && (line->cl_nextexe >= j->j_line->cl_nextexe)) {
311         jprev = j;
312         j = j->j_next;
313     }
314     /* when we get out from the while(), newjob should be added between jprev and j */
315
316     newjob->j_next = j;
317
318     if (jprev == NULL)
319         queue_base = newjob;
320     else
321         jprev->j_next = newjob;
322
323 }
324
325 void
326 add_serial_job(cl_t * line, int info_fd)
327     /* add the next queued job in serial queue */
328 {
329     short int i;
330
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",
336                           line->cl_shell);
337         return;
338     }
339
340     send_msg_fd_debug(info_fd, "inserting in serial queue %s", line->cl_shell);
341
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,
347                      line->cl_shell);
348             if (is_notice_notrun(line->cl_option))
349                 mail_notrun(line, QUEUE_FULL, NULL);
350             return;
351         }
352         else {
353             cl_t **ptr = NULL;
354             short int old_size = serial_array_size;
355
356             debug("Resizing serial_array");
357             serial_array_size = (serial_array_size + SERIAL_GROW_SIZE);
358
359             ptr =
360                 alloc_safe(serial_array_size * sizeof(cl_t *), "serial_array");
361
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);
369             serial_array = ptr;
370         }
371     }
372
373     if ((i = serial_array_index + serial_num) >= serial_array_size)
374         i -= serial_array_size;
375
376     serial_array[i] = line;
377
378     serial_num++;
379     line->cl_numexe += 1;
380
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);
384
385
386 }
387
388
389 void
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 */
394 {
395     lavg_t *lavg_entry = NULL;
396
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);
402         return;
403     }
404 /*      // */
405     send_msg_fd_debug(info_fd, "inserting in lavg queue %s", line->cl_shell);
406 /*      // */
407
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);
416         return;
417     }
418
419     line->cl_numexe += 1;
420     set_run_if_late(line->cl_option);
421     if (is_strict(line->cl_option) && line->cl_runfreq == 1) {
422         struct tm *ft;
423         struct tm ftime;
424         time_t begin_of_cur_int, end_of_cur_int = 0;
425         int tz_changed = 0;
426
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);
434
435         /* handle timezone differences */
436         begin_of_cur_int = line->cl_nextexe - (line->cl_file->cf_tzdiff * 3600);
437
438         ft = localtime(&begin_of_cur_int);
439
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));
444
445         move_time_to(END_OF_INTERVAL, line, &ftime);
446
447         end_of_cur_int =
448             mktime_no_dst(&ftime) + (line->cl_file->cf_tzdiff * 3600);
449
450         if ((line->cl_until > 0) && (line->cl_until + now < end_of_cur_int))
451             lavg_entry->l_until = line->cl_until + now;
452         else {
453             lavg_entry->l_until = end_of_cur_int;
454             clear_run_if_late(line->cl_option);
455         }
456
457         if (tz_changed > 0)
458             switch_back_timezone(orig_tz_envvar);
459     }
460     else
461         lavg_entry->l_until = (line->cl_until > 0) ? now + line->cl_until : 0;
462
463 }
464
465
466 void
467 wait_chld(void)
468   /* wait_chld() - check for job completion */
469 {
470     int pid;
471     cl_t *line = NULL;
472     exe_t *e = NULL;
473
474 /*      // */
475 /*      debug("wait_chld"); */
476 /*      // */
477
478     while ((pid = wait3(NULL, WNOHANG, NULL)) > 0) {
479
480         for (e = exe_list_first(exe_list); e != NULL;
481              e = exe_list_next(exe_list)) {
482
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);
487                 }
488                 else {
489
490                     line = e->e_line;
491 /*                  debug("job finished: %s", line->cl_shell); */
492                     line->cl_numexe -= 1;
493                     line->cl_file->cf_running -= 1;
494
495                     if (is_serial_once(line->cl_option)) {
496                         clear_serial_once(line->cl_option);
497                         if (--serial_running < serial_max_running)
498                             run_serial_job();
499                     }
500                     else if (is_serial(line->cl_option)
501                              && !is_lavg(line->cl_option)) {
502                         if (--serial_running < serial_max_running)
503                             run_serial_job();
504                     }
505                     else if (is_lavg(line->cl_option)
506                              && is_serial(line->cl_option))
507                         lavg_serial_running--;
508                 }
509
510                 exe_list_remove_cur(exe_list);
511                 exe_list_end_iteration(exe_list);
512                 break;
513             }
514         }
515
516     }
517
518 }
519
520
521 void
522 wait_all(int *counter)
523    /* return after all jobs completion. */
524 {
525     int pid;
526     exe_t *e = NULL;
527
528     debug("Waiting for all jobs");
529
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);
537                 }
538                 else {
539
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;
543
544                     if (is_serial_once(e->e_line->cl_option))
545                         clear_serial_once(e->e_line->cl_option);
546
547                 }
548
549                 exe_list_remove_cur(exe_list);
550                 exe_list_end_iteration(exe_list);
551                 break;
552             }
553         }
554     }
555
556 }
557
558
559 int
560 is_leap_year(int year)
561   /* return 1 if it's a leap year otherwise return 0 */
562 {
563     return ((year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0)));
564
565 }
566
567
568 int
569 get_nb_mdays(int year, int mon)
570   /* return the number of days in a given month of a given year */
571 {
572     if (mon == 1) {             /* is February ? */
573         if (is_leap_year(year))
574             return 29;
575         else
576             return 28;
577     }
578     else if (mon <= 6)
579         if (mon % 2 == 0)
580             return 31;
581         else
582             return 30;
583     else if (mon % 2 == 0)
584         return 30;
585     else
586         return 31;
587
588 }
589
590
591 void
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 */
596 {
597     long nod = 0;
598     int i;
599
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;
603
604     /*     and month */
605     for (i = (date->tm_mon - 1); i >= 0; i--)
606         nod += get_nb_mdays((date->tm_year + 1900), i);
607
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) */
610
611     date->tm_wday = (nod % 7) + 6;
612
613     if (date->tm_wday >= 7)
614         date->tm_wday -= 7;
615
616     debug("   dow of %d-%d-%d : %d", (date->tm_mon + 1), date->tm_mday,
617           (date->tm_year + 1900), date->tm_wday);
618
619 }
620
621
622 void
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.
626      *
627      * Line must be periodical (i.e. is_freq_periodically(line->cl_option) == TRUE)
628      *
629      * ftime will contain this time and date when this function returns.
630      *
631      * Vocabulary:
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. */
636 {
637     int max = 0;
638
639     /* sanity check */
640     if (!is_freq_periodically(line->cl_option))
641         die("goto_beginning_next_period() called with a non periodical line");
642
643
644     /* number of days in ftime's month */
645     max = get_nb_mdays(ftime->tm_year, ftime->tm_mon);
646
647     /* STEP 1: find the beginning of the next period without ensuring
648      * there is no overflow (min>=60, hour>=24, etc) */
649
650     if (is_freq_mid(line->cl_option)) {
651
652         if (is_freq_mins(line->cl_option))
653             /* nothing to do : return */
654             return;
655         else if (is_freq_hrs(line->cl_option)) {
656             if (ftime->tm_min >= 30)
657                 ftime->tm_hour++;
658             ftime->tm_min = 30;
659         }
660         else {
661             ftime->tm_min = 0;
662             if (is_freq_days(line->cl_option)) {
663                 if (ftime->tm_hour >= 12)
664                     ftime->tm_mday++;
665                 ftime->tm_hour = 12;
666             }
667             else {
668                 ftime->tm_hour = 0;
669                 if (is_freq_dow(line->cl_option)) {
670                     int to_add = (ftime->tm_wday >= 4) ? 11 - ftime->tm_wday :
671                         4 - ftime->tm_wday;
672                     if (ftime->tm_mday + to_add > max) {
673                         ftime->tm_mon++;
674                         ftime->tm_mday = ftime->tm_mday + to_add - max;
675                     }
676                     else
677                         ftime->tm_mday += to_add;
678                 }
679                 else {
680                     if (is_freq_mons(line->cl_option)) {
681                         if (ftime->tm_mday >= 15)
682                             ftime->tm_mon++;
683                         ftime->tm_mday = 15;
684                     }
685                     else {
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 */
690                         ftime->tm_min++;
691                         warn("Line %s doesn't seem correct: consider "
692                              "reinstalling the corresponding fcrontab");
693                     }
694                 }
695             }
696         }
697
698     }
699
700     else {                      /* is_freq_mid(line->cl_option) */
701
702         if (is_freq_mins(line->cl_option))
703             /* nothing to do */
704             return;
705         else {
706             ftime->tm_min = 0;
707             if (is_freq_hrs(line->cl_option))
708                 ftime->tm_hour++;
709             else {
710                 ftime->tm_hour = 0;
711                 if (is_freq_days(line->cl_option))
712                     ftime->tm_mday++;
713                 else {
714                     if (is_freq_dow(line->cl_option)) {
715                         int to_add =
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;
719                             ftime->tm_mon++;
720                         }
721                         else
722                             ftime->tm_mday += to_add;
723                     }
724                     else {
725                         ftime->tm_mday = 1;
726                         if (is_freq_mons(line->cl_option))
727                             ftime->tm_mon++;
728                     }
729                 }
730             }
731         }
732
733     }                           /* is_freq_mid(line->cl_option) */
734
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) */
738     ftime->tm_sec = 0;
739
740     /* STEP 2: fix the overflows.
741      * (a value may exceed the max value of a field: fix it if necessary) */
742
743     if (ftime->tm_min >= 60) {
744         ftime->tm_min = 0;
745         ftime->tm_hour++;
746     }
747     if (ftime->tm_hour >= 24) {
748         ftime->tm_hour = 0;
749         ftime->tm_mday++;
750     }
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) {
754         ftime->tm_mday = 1;
755         ftime->tm_mon++;
756     }
757     if (ftime->tm_mon >= 12) {
758         ftime->tm_mon = 0;
759         ftime->tm_year++;
760     }
761
762     if (debug_opt)
763         set_wday(ftime);
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");
769
770 }
771
772
773 void
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.
779      *
780      * ftime will contain this time and date when move_time_to returns.
781      *
782      * Vocabulary:
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. */
787 {
788     struct tm tm_next_period;
789     time_t timet_ftime;
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;
794
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;
800
801     /* sanity checks */
802     if (where != BEGIN_NEXT_PERIOD && where != END_OF_INTERVAL)
803         die("move_time_to() called with invalid argument 'where': %d",
804             (int)where);
805
806
807     if (where == BEGIN_NEXT_PERIOD && is_freq_periodically(line->cl_option)) {
808         goto_beginning_next_period_periodical(line, ftime);
809         return;
810     }
811
812     /* In all other cases, we will have to walk through time */
813
814     if (is_freq_periodically(line->cl_option)) {
815
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 */
818
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);
822
823     }
824
825     timet_ftime = mktime_no_dst(ftime);
826
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;
834     }
835     else {
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;
839     }
840
841     /* */
842     debug("   ignore: %d %d %d %d %d", ignore_mins, ignore_hrs,
843           ignore_days, ignore_mons, ignore_dow);
844     /* */
845
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)))
852             ||
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)
858         ) {
859
860         ftime->tm_sec = 0;
861         if (ignore_mins)
862             ftime->tm_min = 60;
863         else {
864             do
865                 ftime->tm_min++;
866             while (bit_test(line->cl_mins, ftime->tm_min)
867                    && (ftime->tm_min < 60));
868         }
869         if (ftime->tm_min >= 60) {
870             ftime->tm_min = 0;
871             if (ignore_hrs && ignore_mins)
872                 ftime->tm_hour = 24;
873             else
874                 ftime->tm_hour++;
875             if (ftime->tm_hour >= 24) {
876                 ftime->tm_hour = 0;
877                 if (ignore_days && ignore_hrs && ignore_mins && ignore_dow)
878                     ftime->tm_mday = 32;        /* go to next month */
879                 else
880                     ftime->tm_mday++;
881                 if (ftime->tm_mday >
882                     get_nb_mdays((ftime->tm_year + 1900), ftime->tm_mon)) {
883                     ftime->tm_mday = 1;
884                     if (ignore_mons && ignore_days && ignore_dow
885                         && ignore_hrs && ignore_mins)
886                         ftime->tm_mon = 12;
887                     else
888                         ftime->tm_mon++;
889                     if (ftime->tm_mon >= 12) {
890                         ftime->tm_mon = 0;
891                         ftime->tm_year++;
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);
898                             return;
899                         }
900                     }
901                 }
902                 set_wday(ftime);
903             }
904         }
905
906         /* // */
907         {
908             /* set temporarily debug_opt to false to avoid having too many
909              * messages in the logs */
910             char debug_opt_previous = debug_opt;
911             debug_opt = 0;
912
913             timet_ftime = mktime_no_dst(ftime);
914
915             debug_opt = debug_opt_previous;
916         }
917         /* // */
918
919     }
920
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));
926     }
927
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) {
932             ftime->tm_min = 59;
933             if (--ftime->tm_hour < 0) {
934                 ftime->tm_hour = 23;
935                 if (--ftime->tm_mday < 1) {
936                     if (--ftime->tm_mon < 0) {
937                         ftime->tm_mon = 11;
938                         ftime->tm_year--;
939                     }
940                     ftime->tm_mday =
941                         get_nb_mdays((ftime->tm_year + 1900), ftime->tm_mon);
942                 }
943             }
944         }
945     }
946
947     debug("   %s %s %d/%d/%d wday:%d %02d:%02d (tzdiff=%d, timezone=%s)",
948           line->cl_shell,
949           (where ==
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");
955 }
956
957
958 void
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 */
961 {
962
963     time_t basetime;
964     struct tm *ft;
965     struct tm ftime;
966     int tz_changed = 0;
967
968     basetime = (option & FROM_CUR_NEXTEXE) ? line->cl_nextexe : now;
969
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);
977
978     if (is_td(line->cl_option)) {
979
980         time_t nextexe = 0;
981         int i;
982         int max;
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);
988
989         ft = localtime(&basetime_tz);
990
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));
995
996         /* creates a bug on DST change on some systems ?? */
997         /* ftime.tm_isdst = -1; */
998
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) {
1002             ftime.tm_min += 1;
1003             ftime.tm_sec = 0;
1004         }
1005
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);
1009
1010  setMonth:
1011         for (i = ftime.tm_mon; (bit_test(line->cl_mons, i) == 0) && (i < 12);
1012              i++) ;
1013         if (i >= 12) {
1014             ftime.tm_year++;
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;
1021             }
1022             if (has_changed < 3) {
1023                 has_changed = 3;
1024                 ftime.tm_mon = 0;
1025                 ftime.tm_mday = 1;
1026                 ftime.tm_hour = 0;
1027                 ftime.tm_min = 0;
1028             }
1029             else
1030                 ftime.tm_mon = 0;
1031             goto setMonth;
1032         }
1033         if (ftime.tm_mon != i) {
1034             ftime.tm_mon = i;
1035             if (has_changed < 2) {
1036                 has_changed = 2;
1037                 ftime.tm_mday = 1;
1038                 ftime.tm_hour = 0;
1039                 ftime.tm_min = 0;
1040             }
1041         }
1042
1043         /* set the number of days in that month */
1044         max = get_nb_mdays((ftime.tm_year + 1900), ftime.tm_mon);
1045
1046  setDay:
1047         if (is_dayand(line->cl_option)) {
1048             for (i = ftime.tm_mday;
1049                  (bit_test(line->cl_days, i) == 0) && (i <= max); i++) ;
1050             if (i > max) {
1051                 ftime.tm_mon++;
1052                 if (has_changed < 2) {
1053                     has_changed = 2;
1054                     ftime.tm_mday = 1;
1055                     ftime.tm_hour = 0;
1056                     ftime.tm_min = 0;
1057                 }
1058                 else
1059                     ftime.tm_mday = 1;
1060                 goto setMonth;
1061             }
1062             if (ftime.tm_mday != i) {
1063                 ftime.tm_mday = i;
1064                 if (has_changed < 1) {
1065                     has_changed = 1;
1066                     ftime.tm_hour = 0;
1067                     ftime.tm_min = 0;
1068                 }
1069             }
1070
1071             set_wday(&ftime);
1072
1073             /* check if the day of week is OK */
1074             if (bit_test(line->cl_dow, ftime.tm_wday) == 0) {
1075                 ftime.tm_mday++;
1076                 ftime.tm_hour = 0;
1077                 ftime.tm_min = 0;
1078                 goto setDay;
1079             }
1080         }
1081         else {                  /* dayor */
1082             int j;
1083
1084             set_wday(&ftime);
1085
1086             j = ftime.tm_wday;
1087             i = ftime.tm_mday;
1088             while ((bit_test(line->cl_days, i) == 0) &&
1089                    (bit_test(line->cl_dow, j) == 0)) {
1090                 if (i > max) {
1091                     ftime.tm_mon++;
1092                     if (has_changed < 2) {
1093                         has_changed = 2;
1094                         ftime.tm_mday = 1;
1095                         ftime.tm_hour = 0;
1096                         ftime.tm_min = 0;
1097                     }
1098                     else
1099                         ftime.tm_mday = 1;
1100                     goto setMonth;
1101                 }
1102                 if (j >= 7)
1103                     j -= 7;
1104                 i++;
1105                 j++;
1106             }
1107             if (ftime.tm_mday != i) {
1108                 ftime.tm_mday = i;
1109                 if (has_changed < 1) {
1110                     has_changed = 1;
1111                     ftime.tm_hour = 0;
1112                     ftime.tm_min = 0;
1113                 }
1114             }
1115         }
1116
1117  setHour:
1118         for (i = ftime.tm_hour; (bit_test(line->cl_hrs, i) == 0) && (i < 24);
1119              i++) ;
1120         if (i >= 24) {
1121             ftime.tm_mday++;
1122             if (has_changed < 1) {
1123                 has_changed = 1;
1124                 ftime.tm_hour = 0;
1125                 ftime.tm_min = 0;
1126             }
1127             else
1128                 ftime.tm_hour = 0;
1129             goto setDay;
1130         }
1131         if (ftime.tm_hour != i) {
1132             ftime.tm_hour = i;
1133             ftime.tm_min = 0;
1134         }
1135
1136
1137         for (i = ftime.tm_min; (bit_test(line->cl_mins, i) == 0) && (i < 60);
1138              i++) ;
1139         if (i >= 60) {
1140             ftime.tm_hour++;
1141             ftime.tm_min = 0;
1142             goto setHour;
1143         }
1144         ftime.tm_min = i;
1145
1146  set_cl_nextexe:
1147         /* set cl_nextexe (handle the timezone differences) */
1148
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);
1152
1153         if (is_random(line->cl_option)) {
1154             /* run the job at a random time during its interval of execution */
1155             struct tm int_end;
1156             time_t int_end_timet;
1157
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");
1164
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);
1168
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;
1172         }
1173         else if (is_td(line->cl_option) && line->cl_runfreq != 1
1174                  && line->cl_jitter > 0) {
1175             /* &-lines only:
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 */
1179             nextexe +=
1180                 (time_t) (((float)line->cl_jitter * (float)rand()) /
1181                           (float)RAND_MAX);
1182         }
1183
1184         line->cl_nextexe = nextexe + (line->cl_file->cf_tzdiff * 3600);
1185
1186         if (option != NO_GOTO) {
1187             if (is_random(line->cl_option)) {
1188                 ft = localtime(&nextexe);
1189                 memcpy(&ftime, ft, sizeof(ftime));
1190             }
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");
1198         }
1199
1200         /*
1201          * sanity check :
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();"
1206          *
1207          * We add a test here to limit the consequences that would have
1208          * an unknown bug in this function.
1209          */
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;
1215         }
1216
1217     }
1218     else {
1219         /* this is a job based on system up time */
1220
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 */
1228             debug
1229                 ("Setting cl_nextexe to LONG_MAX to prevent the line from running again.");
1230             line->cl_nextexe = LONG_MAX;
1231         }
1232         else {
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,
1239                       line->cl_nextexe);
1240                 error
1241                     ("Setting cl_nextexe to LONG_MAX to prevent an infinite loop.");
1242                 line->cl_nextexe = LONG_MAX;
1243             }
1244         }
1245
1246         ft = localtime(&(line->cl_nextexe));
1247
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));
1252
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);
1258     }
1259
1260     insert_nextexe(line);
1261
1262     if (tz_changed > 0)
1263         switch_back_timezone(orig_tz_envvar);
1264
1265 }
1266
1267
1268 void
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 */
1272 {
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;
1277     int tz_changed = 0;
1278
1279 /*  // */
1280     debug("  set_next_exe_notrun : %s %d", line->cl_shell, context);
1281 /*  // */
1282
1283
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);
1291
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;
1296     }
1297     else {
1298         previous_period = now - (line->cl_file->cf_tzdiff * 3600);
1299         set_next_exe_opt = NO_GOTO_LOG;
1300     }
1301     ft = localtime(&previous_period);
1302
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));
1309
1310     ftime.tm_sec = 0;
1311     move_time_to(BEGIN_NEXT_PERIOD, line, &ftime);
1312     next_period = mktime_no_dst(&ftime) + (line->cl_file->cf_tzdiff * 3600);
1313
1314     if (context == SYSDOWN_RUNATREBOOT)
1315         line->cl_nextexe = now;
1316     else
1317         set_next_exe(line, set_next_exe_opt, -1);
1318
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);
1322     }
1323
1324     if (tz_changed > 0)
1325         switch_back_timezone(orig_tz_envvar);
1326
1327 }
1328
1329 void
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 */
1332 {
1333     struct tm *since2 = NULL;
1334     struct tm since;
1335     int tz_changed = 0;
1336
1337     since2 = localtime(&line->cl_nextexe);
1338     memcpy(&since, since2, sizeof(since));
1339
1340     tz_changed = switch_timezone(orig_tz_envvar, line->cl_tz);
1341
1342     mail_notrun(line, SYSDOWN, &since);
1343
1344     if (tz_changed > 0)
1345         switch_back_timezone(orig_tz_envvar);
1346
1347 }
1348
1349 void
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) */
1352 {
1353     int pid = 0;
1354     FILE *mailf = 0;
1355     struct tm *time2 = NULL, time;
1356     char **sendmailenv = NULL;
1357
1358     switch (pid = fork()) {
1359     case -1:
1360         error_e("Fork error : could not mail for not run %s", line->cl_shell);
1361         return;
1362     case 0:
1363         /* child */
1364         break;
1365     default:
1366         /* parent */
1367
1368 /*  // */
1369         debug("Reporting by mail non execution of %s (pid %d)",
1370               line->cl_shell, pid);
1371 /*  // */
1372
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);
1376         return;
1377     }
1378
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);
1384
1385     if (context == QUEUE_FULL)
1386         time2 = localtime(&now);
1387     else
1388         time2 = localtime(&line->cl_nextexe);
1389     memcpy(&time, time2, sizeof(time));
1390
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);
1393
1394     switch (context) {
1395     case SYSDOWN:
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);
1405         break;
1406     case LAVG:
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");
1418         break;
1419     case QUEUE_FULL:
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);
1428         break;
1429     }
1430
1431     /* become user (for security reasons) */
1432     change_user_setup_env(line, &sendmailenv, NULL, NULL, NULL, NULL, NULL);
1433
1434     /* then, send mail */
1435     launch_mailer(line, mailf, sendmailenv);
1436
1437     /* we should not come here : launch_mailer does not return */
1438     die("mail_notrun : launch_mailer failed");
1439
1440 }
1441
1442 time_t
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 */
1446 {
1447     time_t tts = 0;
1448     lavg_t *l = NULL;
1449
1450 #ifdef NOLOADAVG
1451     for (l = lavg_list_first(lavg_list); l != NULL;
1452          l = lavg_list_next(lavg_list)) {
1453         run_lavg_job(l);
1454         lavg_list_remove_cur(lavg_list);
1455     }
1456
1457
1458     tts = time_to_sleep(lim);
1459     return tts;
1460 #else
1461
1462     int i = 0;
1463     double l_avg[3] = { 0, 0, 0 };
1464
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);
1474
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);
1479                 else
1480                     set_next_exe(l->l_line, NO_GOTO_LOG, -1);
1481
1482                 /* remove this job from the lavg queue */
1483                 l->l_line->cl_numexe -= 1;
1484                 lavg_list_remove_cur(lavg_list);
1485             }
1486             else {
1487                 debug("until %s %d", l->l_line->cl_shell, l->l_until);
1488                 run_lavg_job(l);
1489                 lavg_list_remove_cur(lavg_list);
1490             }
1491         }
1492
1493     /* we do this set here as the nextexe of lavg line may change before */
1494     tts = time_to_sleep(lim);
1495
1496     if (lavg_list->num_entries == 0)
1497         return tts;
1498
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 */
1503     l_avg[0] *= 10;
1504     l_avg[1] *= 10;
1505     l_avg[2] *= 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)) {
1511             continue;
1512         }
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)
1517             )
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])
1522             )
1523             ) {
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]);
1530             run_lavg_job(l);
1531             lavg_list_remove_cur(lavg_list);
1532
1533         }
1534     }
1535
1536
1537     if (lavg_list->num_entries == 0)
1538         return tts;
1539     else
1540         return (LAVG_SLEEP < tts) ? LAVG_SLEEP : tts;
1541
1542 #endif                          /* def NOLOADAVG */
1543
1544 }
1545
1546 time_t
1547 time_to_sleep(time_t lim)
1548   /* return the time to sleep until next task have to be executed. */
1549 {
1550     /* we set tts to a big value, unless some problems can occurs
1551      * with files without any line */
1552     time_t tts = lim;
1553     time_t ti = time(NULL);
1554
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;
1559     }
1560
1561     tts = tts - ti;
1562     if (tts < 0)
1563         tts = 0;
1564
1565 /*      debug("Time to sleep: %lds", tts); */
1566
1567     return tts;
1568 }