Logo Search packages:      
Sourcecode: fcron version File versions

database.c

/*
 * FCRON - periodic command scheduler 
 *
 *  Copyright 2000-2004 Thibault Godouet <fcron@free.fr>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 * 
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * 
 *  The GNU General Public License can also be found in the file
 *  `LICENSE' that comes with the fcron source distribution.
 */

 /* $Id: database.c,v 1.75 2005/02/26 15:13:00 thib Exp thib $ */

#include "fcron.h"

#include "database.h"
#include "job.h"
#include "getloadavg.h"

int is_leap_year(int year);
int get_nb_mdays(int year, int mon);
void set_wday(struct tm *date);
time_t mktime_no_dst(struct tm *t);
void goto_non_matching(cl_t *line, struct tm *tm, char option);
#define END_OF_INTERVAL 1    /* goto_non_matching() : option */
void run_serial_job(void);
void run_lavg_job(int i);
void run_queue_job(cl_t *line);
void resize_exe_array(void);

void
test_jobs(void)
  /* determine which jobs need to be run, and run them. */
{
    struct job_t *j;

/*      // */
    debug("Looking for jobs to execute ...");
/*      // */

    while ( (j=queue_base) && j->j_line->cl_nextexe <= now ){
      if ( j->j_line->cl_remain > 0 && --(j->j_line->cl_remain) > 0) {
          set_next_exe(j->j_line, STD, -1);
          debug("    cl_remain: %d", j->j_line->cl_remain);
          continue ;
      }

      j->j_line->cl_remain = j->j_line->cl_runfreq;

      if ( is_lavg(j->j_line->cl_option) )
          add_lavg_job(j->j_line, -1);
      else if ( is_serial(j->j_line->cl_option) )
          add_serial_job(j->j_line, -1);
      else
          run_normal_job(j->j_line, -1);
      
      set_next_exe(j->j_line, STD, -1);
    }

}


time_t
mktime_no_dst(struct tm *t)
/* same as mktime(), but without daylight saving time (dst) change adjustment
 * (ie. the returned time_t does not depend on the tm_isdst field) */
/* Remark : you may think than instead of creating a new function,
 *          it would be easier to set the tm_isdst field to -1.
 *          Unfortunately, the behaviour of mktime() with 
 *          tm_isdst set to -1 depends on the unix you run.
 *          In other word, it wouldn't be portable. */
{
    struct tm t2;
    time_t ti1;

    t2 = *t;

    ti1 = mktime(&t2);
    /* */
    debug("after  mktime() : %d:%d isdst:%d ti:%ld\n",
        t2.tm_hour, t2.tm_min, t2.tm_isdst, ti1);
    /* */

    /* check if there have been a dst change adjustment */
    if ( t->tm_isdst != t2.tm_isdst ) {

      time_t ti2;
      struct tm t3;

      /* recompute the time_t field with the other isdst value
       * it works well, unless in a special case, hence the test
       * below */
      t3 = *t;
      t3.tm_isdst = t2.tm_isdst;
      ti2 = mktime(&t3);
      /* */
      debug("after dst fix 1 : %d:%d isdst:%d ti:%ld\n",
             t3.tm_hour, t3.tm_min, t3.tm_isdst, ti2);
      /* */

      /* if t1 is in the "gap" of a dst change (for instance,
       * if t1 is 2:30 while at 2:00, it is 3:00 due to the dst change,
       * ie. 2:30 is never reached), the ti2 may be incorrect :
       * we check that it is correct before using it : */
      if ( t3.tm_hour == t->tm_hour || ti1 < ti2 ) {
          t2 = t3;
          ti1 = ti2;
      }

    }

    *t = t2;
    return ti1;
}


void
run_normal_job(cl_t *line, int info_fd)
/* run a job, and write "log" on info_fd if positive */
{

    if (line->cl_numexe <= 0 || 
      (is_exe_sev(line->cl_option) && line->cl_numexe < UCHAR_MAX)) {
      line->cl_numexe += 1;
      run_queue_job(line);
      send_msg_fd(info_fd, "Job %s started.", line->cl_shell);
    }
    else {
      warn_fd(info_fd, "    process already running: %s's %s", 
            line->cl_file->cf_user, line->cl_shell);
    } 

}

void
run_lavg_job(int i)
{

    run_queue_job(lavg_array[i].l_line);

    if ( is_serial(lavg_array[i].l_line->cl_option) )
      lavg_serial_running++;

    if (i < --lavg_num) {
      lavg_array[i] = lavg_array[lavg_num];
      lavg_array[lavg_num].l_line = NULL;
    }
    else
      lavg_array[i].l_line = NULL;  

}


void
run_serial_job(void)
    /* run the next serialized job */
{
/*      // */
/*      debug("running next serial job"); */
/*      //     */

    debug("num: %d running:%d  index:%d", serial_num, serial_running,
        serial_array_index);
    if ( serial_num != 0 ) {
      run_queue_job(serial_array[serial_array_index]);
      serial_array[serial_array_index] = NULL;

      serial_running++;
      if ( ++serial_array_index >= serial_array_size )
          serial_array_index -= serial_array_size;
      serial_num--;
      
    }
}
                

void 
resize_exe_array(void)
    /* make exe_array bigger */
{
    struct exe_t *ptr = NULL;
    short int old_size = exe_array_size;

    debug("Resizing exe_array");
    exe_array_size = (exe_array_size + EXE_GROW_SIZE);
      
    if ( (ptr = calloc(exe_array_size, sizeof(struct exe_t))) == NULL )
      die_e("could not calloc exe_array");

    memcpy(ptr, exe_array, (sizeof(struct exe_t) * old_size));
    free(exe_array);
    exe_array = ptr;
}


void
run_queue_job(cl_t *line)
    /* run a job */
{

/*      // */
/*      debug("run_queue_job"); */
/*      // */

    /* append job to the list of executed job */
    if ( exe_num >= exe_array_size )
      resize_exe_array();

    exe_array[exe_num].e_line = line;

    run_job(&exe_array[exe_num++]);

}



void
insert_nextexe(cl_t *line)
    /* insert a job the queue list */
{
    struct job_t *newjob;

    if (queue_base != NULL) {
      struct job_t *j;
      struct job_t *jprev = NULL;
      struct job_t *old_entry = NULL;

      /* find the job in the list */
      for (j = queue_base; j != NULL ; j = j->j_next)
          if ( j->j_line == line ) {
            old_entry = j;
            /* remove it from the list */
            if (jprev != NULL) {
                jprev->j_next = j->j_next;
                j = jprev;
            }
            else
                /* first element of the list */
                j = queue_base = j->j_next;

            break;
          }
          else
            jprev = j;

      jprev = NULL;
      if (j == NULL || line->cl_nextexe < j->j_line->cl_nextexe)
          j = queue_base;
      while (j != NULL && (line->cl_nextexe >= j->j_line->cl_nextexe)) {
          jprev = j;
          j = j->j_next;
      }         

      if (old_entry == NULL) {
          /* this job wasn't in the queue : we append it */
          Alloc(newjob, job_t);
          newjob->j_line = line;
      }
      else
          /* this job was already in the queue : we move it */
          newjob = old_entry;

      newjob->j_next = j;

      if (jprev == NULL)
          queue_base = newjob;
      else
          jprev->j_next = newjob;

    }
    else {
      /* no job in queue */
      Alloc(newjob, job_t);
      newjob->j_line = line;      
      queue_base = newjob;
    }

}

void
add_serial_job(cl_t *line, int info_fd)
    /* add the next queued job in serial queue */
{
    short int i;

    /* check if the line is already in the serial queue 
     * (we consider serial jobs currently running as in the queue) */
    if ( (is_serial_sev(line->cl_option) && line->cl_numexe >= UCHAR_MAX) ||
       (! is_serial_sev(line->cl_option) && line->cl_numexe > 0) ) {
      send_msg_fd_debug(info_fd, "already in serial queue %s", line->cl_shell);
      return;
    }

    send_msg_fd_debug(info_fd, "inserting in serial queue %s", line->cl_shell);

    if ( serial_num >= serial_array_size ) {
      if ( serial_num >= serial_queue_max ) {
          error_fd(info_fd, "Could not add job : serial queue is full "
                 "(%d jobs). Consider using option serialonce, fcron's "
                 "option -m and/or -q : %s",serial_queue_max,line->cl_shell);
          if ( is_notice_notrun(line->cl_option) )
            mail_notrun(line, QUEUE_FULL, NULL);
          return;
      }
      else {
          cl_t **ptr = NULL;
          short int old_size = serial_array_size;

          debug("Resizing serial_array");
          serial_array_size = (serial_array_size + SERIAL_GROW_SIZE);
      
          if ( (ptr = calloc(serial_array_size, sizeof(cl_t *))) == NULL )
            die_e("could not calloc serial_array");

          /* copy lines in order to have the first line at the index 0 */
          memcpy(ptr + serial_array_index, serial_array,
               (sizeof(cl_t*) * (old_size - serial_array_index)) );
          memcpy(ptr, serial_array + (old_size - serial_array_index),
               (sizeof(cl_t*) * serial_array_index));
          serial_array_index = 0;
          free(serial_array);
          serial_array = ptr;
      }
    }

    if ( (i = serial_array_index + serial_num) >= serial_array_size )
      i -= serial_array_size;
      
    serial_array[i] = line;

    serial_num++;
    line->cl_numexe += 1;

    send_msg_fd_debug(info_fd, "serial num: %d size:%d index:%d curline:%d "
                  "running:%d (%s)", serial_num, serial_array_size,
                  serial_array_index, i, serial_running, line->cl_shell);


}


void
add_lavg_job(cl_t *line, int info_fd)
    /* add the next queued job in lavg queue */
    /* WARNING : must be run before a set_next_exe() to get the strict option
     * working correctly */
{

    /* check if the line is already in the lavg queue
     * (we consider serial jobs currently running as in the queue) */
    if ( (is_lavg_sev(line->cl_option) && line->cl_numexe >= UCHAR_MAX) ||
       (! is_lavg_sev(line->cl_option) &&  line->cl_numexe > 0 ) ) {
      send_msg_fd_debug(info_fd, "already in lavg queue %s", line->cl_shell);
      return;
    }
/*    // */
    send_msg_fd_debug(info_fd, "inserting in lavg queue %s", line->cl_shell);
/*    // */
      
    /* append job to the list of lavg job */
    if ( lavg_num >= lavg_array_size ) {
      if ( lavg_num >= lavg_queue_max ) {
          error_fd(info_fd, "Could not add job : lavg queue is full (%d jobs)."
                 " Consider using options lavgonce, until, strict and/or "
                 "fcron's option -q.", lavg_queue_max, line->cl_shell);
          if ( is_notice_notrun(line->cl_option) )
            mail_notrun(line, QUEUE_FULL, NULL);
          return;
      }
      else {
          struct lavg_t *ptr = NULL;
          short int old_size = lavg_array_size;
            
          debug("Resizing lavg_array");
          lavg_array_size = (lavg_array_size + LAVG_GROW_SIZE);
            
          if ( (ptr = calloc(lavg_array_size, sizeof(lavg_t))) == NULL )
            die_e("could not calloc lavg_array");
            
          memcpy(ptr, lavg_array, (sizeof(lavg_t) * old_size));
          free(lavg_array);
          lavg_array = ptr;
      }
    }

    lavg_array[lavg_num].l_line = line;
    line->cl_numexe += 1;
    set_run_if_late(line->cl_option);
    if ( is_strict(line->cl_option) && line->cl_runfreq == 1) {
      struct tm *ft;
      struct tm ftime;
      time_t begin_of_cur_int, end_of_cur_int = 0;

      /* handle timezone differences */
      begin_of_cur_int = line->cl_nextexe - (line->cl_file->cf_tzdiff*3600);

      ft = localtime( &begin_of_cur_int );

      /* localtime() function seems to return every time the same pointer :
         it resets our previous changes, so we need to prevent it
         ( localtime() is used in the debug() function) */
      memcpy(&ftime, ft, sizeof(struct tm));

      goto_non_matching(line, &ftime, END_OF_INTERVAL);

      end_of_cur_int = mktime_no_dst(&ftime) + (line->cl_file->cf_tzdiff * 3600);

      if ((line->cl_until > 0) && (line->cl_until + now < end_of_cur_int))
          lavg_array[lavg_num].l_until = line->cl_until + now;
      else {
          lavg_array[lavg_num].l_until = end_of_cur_int;
          clear_run_if_late(line->cl_option);
      }
    }
    else
      lavg_array[lavg_num].l_until = 
          (line->cl_until > 0) ? now + line->cl_until : 0;

    lavg_num++;
}


void
wait_chld(void)
  /* wait_chld() - check for job completion */
{
    short int i = 0;
    int pid;
    cl_t *line = NULL;


/*      // */
/*      debug("wait_chld"); */
/*      // */

    while ( (pid = wait3(NULL, WNOHANG, NULL)) > 0 ) {
      i = 0;
      while ( i < exe_num ) {
          if (pid == exe_array[i].e_ctrl_pid) {
            if ( exe_array[i].e_line == NULL ) {
                /* the corresponding file has been removed from memory */
                debug("job finished: pid %d", pid);
            }
            else {
                
                line = exe_array[i].e_line;
/*              debug("job finished: %s", line->cl_shell); */
                line->cl_numexe -= 1;
                line->cl_file->cf_running -= 1;
                
                if ( is_serial_once(line->cl_option) ) {
                  clear_serial_once(line->cl_option);
                  if ( --serial_running < serial_max_running )
                      run_serial_job();
                }
                else if ( is_serial(line->cl_option)
                        && ! is_lavg(line->cl_option) ) {
                  if (--serial_running < serial_max_running)
                      run_serial_job();
                }
                else if ( is_lavg(line->cl_option) && 
                        is_serial(line->cl_option) )
                  lavg_serial_running--;
            }
            if (i < --exe_num) {
                exe_array[i] = exe_array[exe_num];
                exe_array[exe_num].e_line = NULL;
            }
            else
                exe_array[i].e_line = NULL;
            
            break;
          }
          i++;
      }
    }

}


void
wait_all(int *counter)
   /* return after all jobs completion. */
{
    short int i = 0;
    int pid;
    
    debug("Waiting for all jobs");
    
    while ( (*counter > 0) && (pid = wait3(NULL, 0, NULL)) > 0 ) {
      i = 0;
      while ( i < exe_num ) {
          if (pid == exe_array[i].e_ctrl_pid) {
            if ( exe_array[i].e_line == NULL ) {
                /* the corresponding file has been removed from memory */
                debug("job finished: pid %d", pid);
            }
            else {
                
                debug("job finished: %s", exe_array[i].e_line->cl_shell);
                exe_array[i].e_line->cl_numexe -= 1;
                exe_array[i].e_line->cl_file->cf_running -= 1;
                
                if ( is_serial_once(exe_array[i].e_line->cl_option) )
                  clear_serial_once(exe_array[i].e_line->cl_option);
                
            }
            if (i < --exe_num) {
                exe_array[i] = exe_array[exe_num];
                exe_array[exe_num].e_line = NULL;
            }
            else
                exe_array[i].e_line = NULL;
            
            break;
          }
          i++;
      }
    }    
    
}


int
is_leap_year(int year)
  /* return 1 if it's a leap year otherwise return 0 */
{
    return ( (year % 4 == 0) &&
           ( (year % 100 != 0) || (year % 400 == 0) ) );

}


int
get_nb_mdays(int year, int mon)
  /* return the number of days in a given month of a given year */
{
    if (mon == 1) { /* is February ? */
      if ( is_leap_year(year) )
          return 29;
      else
          return 28;
    }
    else if (mon <= 6)
      if (mon % 2 == 0)
          return 31;
      else
          return 30;
    else if (mon % 2 == 0)
      return 30;
    else 
      return 31;

}


void
set_wday(struct tm *date)
  /* we know that 01/01/2000 was a Saturday ( day number 6 )
   * so we count the number of days since 01/01/2000,
   * and add this number modulo 7 to the wday number */
{
    long nod = 0;
    int i;

    /* we add the number of days of each previous years */
    for (i =  (date->tm_year - 1); i >= 100 ; i--)
      nod += ( is_leap_year(i+1900) ) ? 366 : 365;

    /*     and month */
    for (i =  (date->tm_mon - 1); i >= 0; i--)
      nod += get_nb_mdays( (date->tm_year + 1900), i);

    /* then we add the number of days passed in the current month */
    nod += (date->tm_mday - 1); /* (mday is set from 1 to 31) */

    date->tm_wday = (nod % 7) + 6;

    if ( date->tm_wday >= 7 )
      date->tm_wday -= 7;

    debug("   dow of %d-%d-%d : %d", (date->tm_mon + 1), date->tm_mday, 
        ( date->tm_year + 1900 ), date->tm_wday); 

} 




void
goto_non_matching(cl_t *line, struct tm *ftime, char option)
    /* search the first the nearest time and date that does
     * not match the line */
{
    struct tm next_period;

    if ( is_freq_periodically(line->cl_option) ) {
      int max = get_nb_mdays(ftime->tm_year, ftime->tm_mon);
      struct tm ftime_initial;

      if (option == END_OF_INTERVAL)
          memcpy(&ftime_initial, ftime, sizeof(ftime_initial));

      if (is_freq_mid(line->cl_option)) {
          if (is_freq_mins(line->cl_option))
            /* nothing to do : return */
            return;
          else if (is_freq_hrs(line->cl_option)) {
            if (ftime->tm_min >= 30)
                ftime->tm_hour++;
            ftime->tm_min = 30;
          } else {
            ftime->tm_min = 0;
            if (is_freq_days(line->cl_option)) {
                if (ftime->tm_hour >= 12)
                  ftime->tm_mday++;
                ftime->tm_hour = 12;
            } else {
                ftime->tm_hour = 0;
                if (is_freq_dow(line->cl_option)) {
                  int to_add = (ftime->tm_wday >= 4) ? 11-ftime->tm_wday:
                      4 - ftime->tm_wday;
                  if (ftime->tm_mday + to_add > max) {
                      ftime->tm_mon++;
                      ftime->tm_mday = ftime->tm_mday + to_add - max;
                  } else
                      ftime->tm_mday += to_add;
                } else {
                  if (is_freq_mons(line->cl_option)) {
                      if (ftime->tm_mday >= 15)
                        ftime->tm_mon++;
                      ftime->tm_mday = 15;
                  }
                  else
                      /* weird : we have the bit freq_mid set, but
                       * none of freq_{mins|hour|days|dow|mons} is set :
                       * we does nothing */
                      ;
                }
            }
          }
      }
      else { /* is_freq_mid(line->cl_option) */
          if (is_freq_mins(line->cl_option))
            /* nothing to do */
            return;
          else {
            ftime->tm_min = 0;
            if (is_freq_hrs(line->cl_option))
                ftime->tm_hour++;
            else {
                ftime->tm_hour = 0;
                if (is_freq_days(line->cl_option))
                  ftime->tm_mday++;
                else {
                  if (is_freq_dow(line->cl_option)) {
                      int to_add=(ftime->tm_wday==0)?1: 8-ftime->tm_wday;
                      if (ftime->tm_mday + to_add > max) {
                        ftime->tm_mday = ftime->tm_mday + to_add - max;
                        ftime->tm_mon++;
                      } else
                        ftime->tm_mday += to_add;
                  } else {
                      ftime->tm_mday = 1;
                      if (is_freq_mons(line->cl_option))
                        ftime->tm_mon++;
                  }
                }
            }
          }
      }
      /* a value may exceed the max value of a field */
      if (ftime->tm_min >= 60) {
          ftime->tm_min = 0;
          ftime->tm_hour++;
      }
      if (ftime->tm_hour >= 24) {
          ftime->tm_hour = 0;
          ftime->tm_mday++;
      }
      /* the month field may have changed */
      max = get_nb_mdays((ftime->tm_year + 1900), ftime->tm_mon);
      if (ftime->tm_mday > max) {
          ftime->tm_mday = 1;
          ftime->tm_mon++;
      }
      if (ftime->tm_mon >= 12) {
          ftime->tm_mon = 0;
          ftime->tm_year++;
      }

      if (option != END_OF_INTERVAL) {
          if (debug_opt)
            set_wday(ftime);
          debug("   %s beginning of next period %d/%d/%d wday:%d %02d:%02d "
              "(tzdiff=%d)", line->cl_shell, (ftime->tm_mon + 1),
              ftime->tm_mday, (ftime->tm_year + 1900), ftime->tm_wday,
              ftime->tm_hour, ftime->tm_min, line->cl_file->cf_tzdiff);

          return;
      }
      else {
          memcpy(&next_period, ftime, sizeof(next_period));
          /* set ftime as we get it */
          memcpy(ftime, &ftime_initial, sizeof(ftime_initial));
      }
    }

    /* if the line is a periodical one without option END_OF_INTERVAL,
     * execution doesn't come here */

    {
      /* this line should be run once per interval of the selected field */

      /* to prevent from invinite loop with unvalid lines : */
      short int year_limit = MAXYEAR_SCHEDULE_TIME;
      /* we have to ignore the fields containing single numbers */
      char ignore_mins = (is_freq_mins(line->cl_option)) ? 1:0;
      char ignore_hrs = (is_freq_hrs(line->cl_option)) ? 1:0;
      char ignore_days = (is_freq_days(line->cl_option)) ? 1:0;
      char ignore_mons = (is_freq_mons(line->cl_option)) ? 1:0;
      char ignore_dow = (is_freq_dow(line->cl_option)) ? 1:0;
    
      if (option == END_OF_INTERVAL)
          /* we want to go to the end of the current interval */
          ignore_mins=ignore_hrs=ignore_days=ignore_mons=ignore_dow = 0;

      /* */
      debug("   ignore: %d %d %d %d %d", ignore_mins, ignore_hrs,
            ignore_days, ignore_mons, ignore_dow);
      /* */

      /* check if we are in an interval of execution */    
      while ((ignore_mins == 1 || bit_test(line->cl_mins, ftime->tm_min)) &&
             (ignore_hrs == 1 || bit_test(line->cl_hrs, ftime->tm_hour)) &&
             (
               (is_dayand(line->cl_option) &&
                (ignore_days==1||bit_test(line->cl_days,ftime->tm_mday)) &&
                (ignore_dow==1 || bit_test(line->cl_dow, ftime->tm_wday)))
               ||
               (is_dayor(line->cl_option) &&
                (ignore_days==1||bit_test(line->cl_days, ftime->tm_mday) ||
                 ignore_dow == 1 || bit_test(line->cl_dow,ftime->tm_wday)))
               ) &&
             (ignore_mons == 1 || bit_test(line->cl_mons, ftime->tm_mon))
          ) {
          
          if (ignore_mins) ftime->tm_min = 60; 
          else {
            do ftime->tm_min++ ;
            while ( bit_test(line->cl_mins, ftime->tm_min) 
                  && (ftime->tm_min < 60) );
          }
          if (ftime->tm_min >= 60) {
            ftime->tm_min = 0;
            if (ignore_hrs && ignore_mins) ftime->tm_hour = 24;
            else ftime->tm_hour++;
            if (ftime->tm_hour >= 24) {
                ftime->tm_hour = 0;
                if (ignore_days && ignore_hrs && ignore_mins && ignore_dow)
                  ftime->tm_mday = 32; /* go to next month */
                else ftime->tm_mday++;
                if (ftime->tm_mday > 
                  get_nb_mdays((ftime->tm_year+1900),ftime->tm_mon)) {
                  ftime->tm_mday = 1;
                  if(ignore_mons && ignore_days && ignore_dow
                     && ignore_hrs && ignore_mins)
                      ftime->tm_mon = 12;
                  else ftime->tm_mon++;
                  if (ftime->tm_mon >= 12) {
                      ftime->tm_mon = 0;
                      ftime->tm_year++;
                      if (--year_limit <= 0) {
                        error("Can't found a non matching date for %s "
                              "in the next %d years. Maybe this line "
                              "is corrupted : consider reinstalling "
                              "the fcrontab", line->cl_shell, 
                              MAXYEAR_SCHEDULE_TIME);
                        return;
                      }
                  }
                }
                set_wday(ftime);
            }
          }
          
          if (option == END_OF_INTERVAL && 
            is_freq_periodically(line->cl_option) &&
            ftime->tm_year <= next_period.tm_year &&
            ftime->tm_mon <= next_period.tm_mon &&
            ftime->tm_mday <= next_period.tm_mday &&
            ftime->tm_hour <= next_period.tm_hour &&
            ftime->tm_min <= next_period.tm_min) {
            /* we don't want to go in the next period ... */
            memcpy(ftime, &next_period, sizeof(next_period));
            break;
          }
      }

      if (option == END_OF_INTERVAL) {
          /* we want the end of the current interval, not the beginning
           * of the first non-matching interval */
          if (--ftime->tm_min < 0) {
            ftime->tm_min = 59;
            if (--ftime->tm_hour < 0) {
                ftime->tm_hour = 23;
                if (--ftime->tm_mday < 1) {
                  if (--ftime->tm_mon < 0) {
                      ftime->tm_mon = 11;
                      ftime->tm_year--;
                  }
                  ftime->tm_mday = get_nb_mdays( (ftime->tm_year + 1900),
                                           ftime->tm_mon);
                }
            }
          }
      }
      
      debug("   %s %s %d/%d/%d wday:%d %02d:%02d (tzdiff=%d)",line->cl_shell,
            (option == STD) ? "first non matching" : "end of interval",
            (ftime->tm_mon + 1), ftime->tm_mday, (ftime->tm_year + 1900),
            ftime->tm_wday, ftime->tm_hour, ftime->tm_min,
            line->cl_file->cf_tzdiff);
      return;
    }
}


void 
set_next_exe(cl_t *line, char option, int info_fd)
  /* set the cl_nextexe of a given cl_t and insert it in the queue */
{

    time_t basetime;
    struct tm *ft;
    struct tm ftime;

    basetime = (option & FROM_CUR_NEXTEXE) ? line->cl_nextexe : now ;

    if ( is_td(line->cl_option) ) {

      time_t nextexe = 0;
      int i;
      int max;
      char has_changed = 0;
      /* to prevent from invinite loop with unvalid lines : */
      short int year_limit = MAXYEAR_SCHEDULE_TIME;
      /* timezone difference */
      time_t basetime_tz = basetime - (line->cl_file->cf_tzdiff * 3600);

      ft = localtime(&basetime_tz);

      /* localtime() function seem to return every time the same pointer :
         it resets our previous changes, so we need to prevent it
         ( localtime() is used in the debug() function) */
      memcpy(&ftime, ft, sizeof(struct tm));

      /* creates a bug on DST change on some systems ?? */
      /* ftime.tm_isdst = -1; */

      /* to prevent multiple execution of &-jobs in the same minute
       * (but not if the user has explicitely asked to run jobs immediately) */
      if (first_sleep > 0 || option == STD || line->cl_runfreq != 1) {
          ftime.tm_min += 1;
          ftime.tm_sec = 0;
      }
          
      if (line->cl_runfreq == 1 && option != NO_GOTO && option != NO_GOTO_LOG)
          goto_non_matching(line, &ftime, STD);

      setMonth:
      for (i = ftime.tm_mon; (bit_test(line->cl_mons, i)==0) && (i<12); i++);
      if (i >= 12) {
          ftime.tm_year++;
          if (--year_limit <= 0) {
            error("Can't found a matching date for %s in the next %d"
                  " years. Maybe this line is corrupted : consider"
                  " reinstalling the fcrontab.",
                  line->cl_shell, MAXYEAR_SCHEDULE_TIME);
            goto set_cl_nextexe;
          }
          if ( has_changed < 3) {
            has_changed = 3;
            ftime.tm_mon = 0;
            ftime.tm_mday = 1;
            ftime.tm_hour = 0;
            ftime.tm_min = 0;
          } else
            ftime.tm_mon = 0;       
          goto setMonth;
      }
      if (ftime.tm_mon !=  i) {
          ftime.tm_mon = i;
          if ( has_changed < 2) {
            has_changed = 2;
            ftime.tm_mday = 1;
            ftime.tm_hour = 0;
            ftime.tm_min = 0;
          }
      }         

      /* set the number of days in that month */
      max = get_nb_mdays( (ftime.tm_year + 1900), ftime.tm_mon);

      setDay:
      if ( is_dayand(line->cl_option) ) {
          for (i = ftime.tm_mday; 
             (bit_test(line->cl_days, i) == 0) && (i <= max); i++);
          if (i > max) {
            ftime.tm_mon++;
            if ( has_changed < 2) {
                has_changed = 2;
                ftime.tm_mday = 1;
                ftime.tm_hour = 0;
                ftime.tm_min = 0;
            } else
                ftime.tm_mday = 1;
            goto setMonth;
          }
          if ( ftime.tm_mday != i ) {
            ftime.tm_mday = i;
            if ( has_changed < 1) {
                has_changed = 1;
                ftime.tm_hour = 0;
                ftime.tm_min = 0;   
            }    
          }

          set_wday(&ftime);

          /* check if the day of week is OK */
          if ( bit_test(line->cl_dow, ftime.tm_wday) == 0 ) {
            ftime.tm_mday++;
            ftime.tm_hour = 0;
            ftime.tm_min = 0;
            goto setDay;
          }
      } else {  /* dayor */
          int j;

          set_wday(&ftime);

          j = ftime.tm_wday;
          i = ftime.tm_mday;
          while( (bit_test(line->cl_days, i) == 0)  &&
               (bit_test(line->cl_dow, j) == 0) ) {
            if (i > max) {
                ftime.tm_mon++;
                if ( has_changed < 2) {
                  has_changed = 2;
                  ftime.tm_mday = 1;
                  ftime.tm_hour = 0;
                  ftime.tm_min = 0;
                } else
                  ftime.tm_mday = 1;
                goto setMonth;
            }
            if (j >= 7)
                j -= 7;
            i++;
            j++;
          }
          if ( ftime.tm_mday != i ) {
            ftime.tm_mday = i;
            if ( has_changed < 1) {
                has_changed = 1;
                ftime.tm_hour = 0;
                ftime.tm_min = 0;
            }    
          }
      }

      setHour:
      for (i=ftime.tm_hour; (bit_test(line->cl_hrs, i)==0) && (i<24); i++);
      if (i >= 24) {
          ftime.tm_mday++;
          if ( has_changed < 1) {
            has_changed = 1;
            ftime.tm_hour = 0;
            ftime.tm_min = 0;
          } else
            ftime.tm_hour = 0;
          goto setDay;
      }
      if ( ftime.tm_hour != i ) {
          ftime.tm_hour = i;
          ftime.tm_min = 0;
      }
   

      for (i=ftime.tm_min; (bit_test(line->cl_mins, i)==0) && (i<60); i++);
      if (i >= 60) {
          ftime.tm_hour++;
          ftime.tm_min = 0;
          goto setHour;
      }
      ftime.tm_min = i;
   
      set_cl_nextexe:
      /* set cl_nextexe (handle the timezone differences) */

      nextexe = mktime_no_dst(&ftime);

      if ( is_random(line->cl_option) ) {
          /* run the job at a random time during its interval of execution */
          struct tm intend;
          time_t intend_int;

          debug("   cmd: %s begin int exec %d/%d/%d wday:%d %02d:%02d "
              "(tzdiff=%d)", line->cl_shell, (ftime.tm_mon + 1),
              ftime.tm_mday, (ftime.tm_year + 1900), ftime.tm_wday,
              ftime.tm_hour, ftime.tm_min, line->cl_file->cf_tzdiff);

          memcpy(&intend, &ftime, sizeof(intend));
          goto_non_matching(line, &intend, END_OF_INTERVAL);
          intend_int = mktime_no_dst(&intend);

          /* set a random time to add to the first allowed time of execution */
          nextexe += ( (i= intend_int - nextexe) > 0) ? 
            (time_t)(((float)i * (float)rand())/(float)RAND_MAX) : 0;
      }

      line->cl_nextexe = nextexe + (line->cl_file->cf_tzdiff * 3600);

      if ( option != NO_GOTO ) {
          if ( is_random(line->cl_option) ) {
            ft = localtime(&nextexe);
            memcpy(&ftime, ft, sizeof(ftime));
          }
          send_msg_fd_debug(info_fd, "   cmd: %s next exec %d/%d/%d wday:%d "
                        "%02d:%02d (tzdiff=%d w/ sys time)", line->cl_shell,
                        (ftime.tm_mon + 1), ftime.tm_mday,
                        (ftime.tm_year + 1900), ftime.tm_wday,
                        ftime.tm_hour, ftime.tm_min,
                        line->cl_file->cf_tzdiff);
      }

      /* 
       * sanity check : 
       * if the nextexe is set to the past because of a bug,
       * the line will be executed again immediately, and it is most likely
       * to be set again in the past next time.
       * It would create a nasty infinite loop, a kind of "while(1) fork();"
       *
       * We add a test here to limit the consequences that would have
       * an unknown bug in this function.
       */
      if ( line->cl_nextexe <= now ) {
          error("BUG ??? Fcron thinks the next exe time of %s is %ld, "
              "hence before now (%ld). To avoid infinite loop, nextexe"
              " will be set at now+5s.");
          line->cl_nextexe = now + 5;
      }

      
    }
    else {
      /* this is a job based on system up time */
      line->cl_nextexe = basetime + line->cl_timefreq;

      ft = localtime( &(line->cl_nextexe) );

      /* localtime() function seem to return every time the same pointer :
         it resets our previous changes, so we need to prevent it
         ( localtime() is used in the debug() function) */
      memcpy(&ftime, ft, sizeof(struct tm));

      send_msg_fd_debug(info_fd, "   cmd: %s next exec %d/%d/%d wday:%d "
                    "%02d:%02d (system time)", line->cl_shell,
                    (ftime.tm_mon + 1), ftime.tm_mday, (ftime.tm_year+1900),
                    ftime.tm_wday, ftime.tm_hour, ftime.tm_min);
    }
    
    insert_nextexe(line);

}


void
set_next_exe_notrun(cl_t *line, char context)
    /* set the time of the next execution and send a mail to tell user his job
     * has not run if necessary */
{
    time_t previous_period = 0, next_period = 0;
    struct tm *ft = NULL;
    struct tm ftime, last_nextexe;
    char set_next_exe_opt = 0;

/*  // */
    debug("  set_next_exe_notrun : %s %d", line->cl_shell, context);
/*  // */

    if (context == SYSDOWN) {
      /* handle timezone differences */
      previous_period = line->cl_nextexe - (line->cl_file->cf_tzdiff * 3600);
      set_next_exe_opt = NO_GOTO;
    }
    else {
      previous_period = now - (line->cl_file->cf_tzdiff * 3600);
      set_next_exe_opt = NO_GOTO_LOG;
    }
    ft = localtime(&previous_period);

    /* localtime() function seem to return every time the same pointer :
       it resets our previous changes, so we need to prevent it
       ( localtime() is used in the debug() function) */
    memcpy(&ftime, ft, sizeof(ftime));
    /* we also copy it to last_nextexe which will be used in mail_notrun */
    memcpy(&last_nextexe, ft, sizeof(last_nextexe));
    
    ftime.tm_sec = 0;
    goto_non_matching(line, &ftime, STD);
    next_period = mktime_no_dst(&ftime) + (line->cl_file->cf_tzdiff * 3600);

    set_next_exe(line, set_next_exe_opt, -1);
    if ( line->cl_nextexe >= next_period ) {
      /* line has not run during one or more period(s) : send a mail */
      mail_notrun(line, context, &last_nextexe);
    }

}

void
mail_notrun(cl_t *line, char context, struct tm *since)
    /* send a mail to tell user a job has not run (and why) */
{
    int pid = 0;
    FILE *mailf = 0;
    struct tm *time2 = NULL, time;

    switch ( pid = fork() ) {
    case -1:
      error_e("Fork error : could not mail for not run %s", line->cl_shell);
      return;
    case 0:
      /* child */
      break;
    default:
      /* parent */

/*  // */
      debug("Reporting by mail non execution of %s (pid %d)", 
            line->cl_shell, pid);
/*  // */

      /* create an entry in exe_array */
      if ( exe_num >= exe_array_size )
          resize_exe_array();
      /* set line to NULL as this is not a line ... */
      exe_array[exe_num].e_line = NULL;
      exe_array[exe_num].e_ctrl_pid = pid;
      exe_num++;
      return;
    }

    if ( context == QUEUE_FULL )
      time2 = localtime(&now);
    else
      time2 = localtime(&line->cl_nextexe);
    memcpy(&time, time2, sizeof(time));

    /* create a temp file, and write in it the message to send */
    mailf = create_mail(line, "Non-execution of fcron job");

    switch ( context ) {
    case SYSDOWN:
      fprintf(mailf, "Line %s has not run since and including "
             "%d/%d/%d wday:%d %02d:%02d\ndue to system's down state.\n",
             line->cl_shell, (since->tm_mon + 1), since->tm_mday,
             (since->tm_year + 1900), since->tm_wday, since->tm_hour,
             since->tm_min);
      fprintf(mailf, "It will be next executed at %d/%d/%d wday:"
             "%d %02d:%02d\n", (time.tm_mon + 1), time.tm_mday,
             (time.tm_year+1900), time.tm_wday, time.tm_hour, time.tm_min);
      break;
    case LAVG:
      fprintf(mailf, "Line %s has not run since and including "
             "%d/%d/%d wday:%d %02d:%02d\n", line->cl_shell,
             (since->tm_mon + 1), since->tm_mday, (since->tm_year + 1900),
             since->tm_wday, since->tm_hour, since->tm_min);
      fprintf(mailf, "due to a too high system load average or "
             "too many lavg-serial jobs.\n");
      fprintf(mailf, "It will be next executed at %d/%d/%d "
             "wday:%d %02d:%02d\n", (time.tm_mon + 1), time.tm_mday,
             (time.tm_year+1900), time.tm_wday, time.tm_hour, time.tm_min);
      break;
    case QUEUE_FULL:
      fprintf(mailf,"Line %s couldn't be added to lavg or serial queue which"
             " is full ( %d/%d/%d wday:%d %02d:%02d ).\n", line->cl_shell,
             (time.tm_mon + 1), time.tm_mday, (time.tm_year + 1900),
             time.tm_wday, time.tm_hour, time.tm_min);
      fprintf(mailf, "Consider using options lavgonce, until, strict, "
             "serialonce and/or fcron's option -m.\n");
      fprintf(mailf, "Note that job %s has not run.\n", line->cl_shell);
      break;
    }
    
    /* become user (for security reasons) */
    if (change_user(line) < 0)
      return ;

    /* then, send mail */
    launch_mailer(line, mailf);
    
    /* we should not come here : launch_mailer does not return */
    error("mail_notrun : launch_mailer failed");
    
}

time_t
check_lavg(time_t lim)
    /* run a job based on system load average if one should be run
     * and return the time to sleep */
{
    time_t tts = 0;

#ifdef NOLOADAVG
    while ( lavg_num > 0 )
      run_lavg_job(0);

    tts = time_to_sleep(lim);
    return tts;
#else

    int i = 0;
    double l_avg[3]= {0, 0, 0};

    /* first, check if some lines must be executed because of until */
    while ( i < lavg_num )
      if ( (lavg_array[i].l_line->cl_until > 0 
            || lavg_array[i].l_line->cl_runfreq == 1)
           && lavg_array[i].l_until < now){
          if ( ! is_run_if_late(lavg_array[i].l_line->cl_option) ) {
            if ( ! is_nolog(lavg_array[i].l_line->cl_option) )
                explain("Interval of execution exceeded : %s (not run)",
                      lavg_array[i].l_line->cl_shell);

            /* set time of the next execution and send a mail if needed */
            if ( is_td(lavg_array[i].l_line->cl_option) &&
                 is_notice_notrun(lavg_array[i].l_line->cl_option) )
                set_next_exe_notrun(lavg_array[i].l_line, LAVG);
            else
                set_next_exe(lavg_array[i].l_line, NO_GOTO_LOG, -1);

            /* remove this job from the lavg queue */
            lavg_array[i].l_line->cl_numexe -= 1;
            if (i < --lavg_num) {
                lavg_array[i] = lavg_array[lavg_num];
                lavg_array[lavg_num].l_line = NULL;
            }
            else
                lavg_array[i].l_line = NULL;

          }
          else {
            debug("until %s %d", lavg_array[i].l_line->cl_shell,
                  lavg_array[i].l_until);
            run_lavg_job(i);
          }
      } else
          i++;

    /* we do this set here as the nextexe of lavg line may change before */
    tts = time_to_sleep(lim);

    if ( lavg_num == 0 )
      return tts;
      
    if ( (i = getloadavg(l_avg, 3)) != 3 )
      debug("got only %d lavg values", i);
    debug("get_lavg: %lf, %lf, %lf", l_avg[0], l_avg[1], l_avg[2]);
    /* the 3 values stored in the fcron lines are the real value *= 10 */
    l_avg[0] *= 10;
    l_avg[1] *= 10;
    l_avg[2] *= 10;
    i = 0;
    while ( i < lavg_num ) {
      /* check if the line should be executed */
      if ( lavg_serial_running >= serial_max_running && 
           is_serial(lavg_array[i].l_line->cl_option) ) {
          i++;
          continue;
      }
      if ( ( is_land(lavg_array[i].l_line->cl_option)
             && ( l_avg[0] < lavg_array[i].l_line->cl_lavg[0]
                || lavg_array[i].l_line->cl_lavg[0] == 0 )
             && ( l_avg[1] < lavg_array[i].l_line->cl_lavg[1]
                || lavg_array[i].l_line->cl_lavg[1] == 0 )
             && ( l_avg[2] < lavg_array[i].l_line->cl_lavg[2]
                || lavg_array[i].l_line->cl_lavg[2] == 0 ) 
          )
           || 
           ( is_lor(lavg_array[i].l_line->cl_option) 
             &&  ( l_avg[0] < lavg_array[i].l_line->cl_lavg[0]
                 || l_avg[1] < lavg_array[i].l_line->cl_lavg[1]
                 || l_avg[2] < lavg_array[i].l_line->cl_lavg[2] )
             )
          ) {
          debug("lavg %s %s %.0f:%d %.0f:%d %.0f:%d",
              lavg_array[i].l_line->cl_shell,
              (is_lor(lavg_array[i].l_line->cl_option)) ? "or" : "and",
              l_avg[0], lavg_array[i].l_line->cl_lavg[0],
              l_avg[1], lavg_array[i].l_line->cl_lavg[1],
              l_avg[2], lavg_array[i].l_line->cl_lavg[2]);
          run_lavg_job(i);

      } else
          i++;
    }

    
    if ( lavg_num == 0 )
      return tts;
    else
      return (LAVG_SLEEP < tts) ? LAVG_SLEEP : tts;
    
#endif /* def NOLOADAVG */ 

}

time_t
time_to_sleep(time_t lim)
  /* return the time to sleep until next task have to be executed. */
{
    /* we set tts to a big value, unless some problems can occurs
     * with files without any line */
    time_t tts = lim;
    time_t ti = time(NULL);

    /* note : jobs in queue_base are sorted */
    if ( queue_base != NULL ) {
      if ( queue_base->j_line->cl_nextexe < lim )
          tts = queue_base->j_line->cl_nextexe;
    }
          
    tts = tts - ti;
    if ( tts < 0)
      tts = 0;

/*      debug("Time to sleep: %lds", tts); */

    return tts;
}


Generated by  Doxygen 1.6.0   Back to index