toybox/toys/pending/crond.c
<<
>>
Prefs
   1/* crond.c - daemon to execute scheduled commands.
   2 *
   3 * Copyright 2014 Ranjan Kumar <ranjankumar.bth@gmail.com>
   4 *
   5 * No Standard
   6
   7USE_CROND(NEWTOY(crond, "fbSl#<0=8d#<0L:c:[-bf][-LS][-ld]", TOYFLAG_USR|TOYFLAG_SBIN|TOYFLAG_NEEDROOT))
   8
   9config CROND
  10  bool "crond"
  11  default n
  12  help
  13    usage: crond [-fbS] [-l N] [-d N] [-L LOGFILE] [-c DIR]
  14
  15    A daemon to execute scheduled commands.
  16
  17    -b Background (default)
  18    -c crontab dir
  19    -d Set log level, log to stderr
  20    -f Foreground
  21    -l Set log level. 0 is the most verbose, default 8
  22    -S Log to syslog (default)
  23    -L Log to file
  24*/
  25
  26#define FOR_crond
  27#include "toys.h"
  28
  29GLOBALS(
  30  char *crontabs_dir;
  31  char *logfile;
  32  int loglevel_d;
  33  int loglevel;
  34
  35  time_t crontabs_dir_mtime;
  36  uint8_t flagd;
  37)
  38
  39typedef struct _var {
  40  struct _var *next, *prev;
  41  char *name, *val;
  42} VAR;
  43
  44typedef struct _job {
  45  struct _job *next, *prev;
  46  char min[60], hour[24], dom[31], mon[12], dow[7], *cmd;
  47  int isrunning, needstart, mailsize;
  48  pid_t pid;
  49} JOB;
  50
  51typedef struct _cronfile {
  52  struct _cronfile *next, *prev;
  53  struct double_list *job, *var;
  54  char *username, *mailto;
  55  int invalid;
  56} CRONFILE;
  57
  58static char days[]={"sun""mon""tue""wed""thu""fri""sat"};
  59static char months[]={"jan""feb""mar""apr""may""jun""jul"
  60  "aug""sep""oct""nov""dec"};
  61CRONFILE *gclist;
  62
  63#define LOG_EXIT 0
  64#define LOG_LEVEL5 5
  65#define LOG_LEVEL7 7
  66#define LOG_LEVEL8 8
  67#define LOG_LEVEL9 9 // warning
  68#define LOG_ERROR 20
  69
  70static void loginfo(uint8_t loglevel, char *msg, ...)
  71{
  72  va_list s, d;
  73
  74  va_start(s, msg);
  75  va_copy(d, s);
  76  if (loglevel >= TT.loglevel) {
  77    int used;
  78    char *smsg;
  79
  80    if (!TT.flagd && TT.logfile) {
  81      int fd = open(TT.logfile, O_WRONLY | O_CREAT | O_APPEND, 0666);
  82      if (fd==-1) perror_msg("'%s", TT.logfile);
  83      else {
  84        dup2(fd, 2);
  85        close(fd);
  86      }
  87    }
  88    used = vsnprintf(NULL, 0, msg, d);
  89    smsg = xzalloc(++used);
  90    vsnprintf(smsg, used, msg, s);
  91    if (TT.flagd || TT.logfile) {
  92      fflush(NULL);
  93      smsg[used-1] = '\n';
  94      writeall((loglevel > 8) ? 2 : 1, smsg, used);
  95    } else syslog((loglevel > 8) ? LOG_ERR : LOG_INFO, "%s", smsg);
  96    free(smsg);
  97  }
  98  va_end(d);
  99  va_end(s);
 100  if (!loglevel) exit(20);
 101}
 102
 103/*
 104 * Names can also be used for the 'month' and 'day of week' fields
 105 * (First three letters of the particular day or month).
 106 */
 107static int getindex(char *src, int size)
 108{
 109  int i;
 110  char *field = (size == 12) ? months : days;
 111
 112  // strings are not allowed for min, hour and dom fields.
 113  if (!(size == 7 || size == 12)) return -1;
 114
 115  for (i = 0; field[i]; i += 3) {
 116    if (!strncasecmp(src, &field[i], 3))
 117      return (i/3);
 118  }
 119  return -1;
 120}
 121
 122// set elements of minute, hour, day of month, month and day of week arrays.
 123static void fillarray(char *dst, int start, int end, int skip)
 124{
 125  int sk = 1;
 126
 127  if (end < 0) {
 128    dst[start] = 1;
 129    return;
 130  }
 131  if (!skip) skip = 1;
 132  do {
 133    if (!--sk) {
 134      dst[start] = 1;
 135      sk = skip;
 136    }
 137  } while (start++ != end);
 138}
 139
 140static long getval(char *num, long low, long high)
 141{
 142  long val = strtol(num, &num, 10);
 143
 144  if (*num || (val < low) || (val > high)) return -1;
 145  return val;
 146}
 147
 148//static int parse_and_fillarray(char *dst, int size, char *src)
 149static int parse_and_fillarray(char *dst, int min, int max, char *src)
 150{
 151  int start, end, skip = 0;
 152  char *ptr = strchr(src, '/');
 153
 154  if (ptr) {
 155    *ptr++ = 0;
 156    if ((skip = getval(ptr, min, (min ? max: max-1))) < 0) goto ERROR;
 157  }
 158
 159  if (*src == '-' || *src == ',') goto ERROR;
 160  if (*src == '*') {
 161    if (*(src+1)) goto ERROR;
 162    fillarray(dst, 0, max-1, skip);
 163  } else {
 164    for (;;) {
 165      char *ctoken = strsep(&src, ","), *dtoken;
 166
 167      if (!ctoken) break;
 168      if (!*ctoken) goto ERROR;
 169
 170      // Get start position.
 171      dtoken = strsep(&ctoken, "-");
 172      if (isdigit(*dtoken)) {
 173        if ((start = getval(dtoken, min, (min ? max : max-1))) < 0) goto ERROR;
 174        start = min ? (start-1) : start;
 175      } else if ((start = getindex(dtoken, max)) < 0) goto ERROR;
 176
 177      // Get end position.
 178      if (!ctoken) end = -1; // e.g. N1,N2,N3
 179      else if (*ctoken) {// e.g. N-M
 180        if (isdigit(*ctoken)) {
 181          if ((end = getval(ctoken, min, (min ? max : max-1))) < 0) goto ERROR;
 182          end = min ? (end-1) : end;
 183        } else if ((end = getindex(ctoken, max)) < 0) goto ERROR;
 184        if (end == start) end = -1;
 185      } else goto ERROR; // error condition 'N-'
 186      fillarray(dst, start, end, skip);
 187    }
 188  }
 189
 190  if (TT.flagd && (TT.loglevel <= 5)) {
 191    for (start = 0; start < max; start++)
 192      fprintf(stderr, "%d", (unsigned char)dst[start]);
 193    fputc('\n', stderr);
 194  }
 195  return 0;
 196ERROR:
 197  loginfo(LOG_LEVEL9, "parse error at %s", src);
 198  return -1;
 199}
 200
 201static char *omitspace(char *line)
 202{
 203  while (*line == ' ' || *line == '\t') line++;
 204  return line;
 205}
 206
 207static void parse_line(char *line, CRONFILE *cfile)
 208{
 209  int count = 0;
 210  char *name, *val, *tokens[5] = {0,};
 211  VAR *v;
 212  JOB *j;
 213
 214  line = omitspace(line);
 215  if (!*line || *line == '#') return;
 216
 217  /*
 218   * TODO: Enhancement to support 8 special strings
 219   * @reboot -> Run once at startup.
 220   * @yearly -> Run once a year (0 0 1 1 *).
 221   * @annually -> Same as above.
 222   * @monthly -> Run once a month (0 0 1 * *).
 223   * @weekly -> Run once a week (0 0 * * 0).
 224   * @daily -> Run once a day (0 0 * * *).
 225   * @midnight -> same as above.
 226   * @hourly -> Run once an hour (0 * * * *).
 227   */
 228  if (*line == '@') return;
 229  if (TT.flagd) loginfo(LOG_LEVEL5, "user:%s entry:%s", cfile->username, line);
 230  while (count<5) {
 231    int len = strcspn(line, " \t");
 232
 233    if (line[len]) line[len++] = '\0';
 234    tokens[count++] = line;
 235    line += len;
 236    line = omitspace(line);
 237    if (!*line) break;
 238  }
 239
 240  switch (count) {
 241    case 1: // form SHELL=/bin/sh
 242      name = tokens[0];
 243      if ((val = strchr(name, '='))) *val++ = 0;
 244      if (!val || !*val) return;
 245      break;
 246    case 2: // form SHELL =/bin/sh or SHELL= /bin/sh
 247      name = tokens[0];
 248      if ((val = strchr(name, '='))) {
 249        *val = 0;
 250        val = tokens[1];
 251      } else {
 252        if (*(tokens[1]) != '=') return;
 253        val = tokens[1] + 1;
 254      }
 255      if (!*val) return;
 256      break;
 257    case 3: // NAME = VAL
 258      name = tokens[0];
 259      val = tokens[2];
 260      if (*(tokens[1]) != '=') return;
 261      break;
 262    case 5:
 263      // don't have any cmd to execute.
 264      if (!*line) return;
 265      j = xzalloc(sizeof(JOB));
 266
 267      if (parse_and_fillarray(j->min, 0, sizeof(j->min), tokens[0]))
 268        goto STOP_PARSING;
 269      if (parse_and_fillarray(j->hour, 0, sizeof(j->hour), tokens[1]))
 270        goto STOP_PARSING;
 271      if (parse_and_fillarray(j->dom, 1, sizeof(j->dom), tokens[2]))
 272        goto STOP_PARSING;
 273      if (parse_and_fillarray(j->mon, 1, sizeof(j->mon), tokens[3]))
 274        goto STOP_PARSING;
 275      if (parse_and_fillarray(j->dow, 0, sizeof(j->dow), tokens[4]))
 276        goto STOP_PARSING;
 277      j->cmd = xstrdup(line);
 278
 279      if (TT.flagd) loginfo(LOG_LEVEL5, " command:%s", j->cmd);
 280      dlist_add_nomalloc((struct double_list **)&cfile->job, (struct double_list *)j);
 281      return;
 282STOP_PARSING:
 283      free(j);
 284      return;
 285    default: return;
 286  }
 287  if (!strcmp(name, "MAILTO")) cfile->mailto = xstrdup(val);
 288  else {
 289    v = xzalloc(sizeof(VAR));
 290    v->name = xstrdup(name);
 291    v->val = xstrdup(val);
 292    dlist_add_nomalloc((struct double_list **)&cfile->var, (struct double_list *)v);
 293  }
 294}
 295
 296static void free_jobs(JOB **jlist)
 297{
 298  JOB *j = dlist_pop(jlist);
 299  free(j->cmd);
 300  free(j);
 301}
 302
 303static void free_cronfile(CRONFILE **list)
 304{
 305  CRONFILE *l = dlist_pop(list);
 306  VAR *v, *vnode = (VAR *)l->var;
 307
 308  if (l->username != l->mailto) free(l->mailto);
 309  free(l->username);
 310  while (vnode && (v = dlist_pop(&vnode))) {
 311    free(v->name);
 312    free(v->val);
 313    free(v);
 314  }
 315  free(l);
 316}
 317
 318/*
 319 * Iterate all cronfiles to identify the completed jobs and freed them.
 320 * If all jobs got completed for a cronfile, freed cronfile too.
 321 */
 322static void remove_completed_jobs()
 323{
 324  CRONFILE *lstart, *list = gclist;
 325
 326  lstart = list;
 327  while (list) {
 328    int delete = 1;
 329    JOB *jstart, *jlist = (JOB *)list->job;
 330
 331    list->invalid = 1;
 332    jstart = jlist;
 333    while (jlist) {
 334      jlist->isrunning = 0;
 335      if (jlist->pid > 0) {
 336        jlist->isrunning = 1;
 337        delete = 0;
 338        jlist = jlist->next;
 339      } else {
 340        if (jlist == jstart) { // if 1st node has to delete.
 341          jstart = jstart->next;
 342          free_jobs(&jlist);
 343          continue;
 344        } else free_jobs(&jlist);
 345      }
 346      if (jlist == jstart) break;
 347    }
 348    list->job = (struct double_list *)jlist;
 349
 350    if (delete) {
 351      if (lstart == list) {
 352        lstart = lstart->next;
 353        free_cronfile(&list);
 354        continue;
 355      } else free_cronfile(&list);
 356    }
 357    list = list->next;
 358    if (lstart == list) break;
 359  }
 360  gclist = list;
 361}
 362
 363// Scan cronfiles and prepare the list of cronfiles with their jobs.
 364static void scan_cronfiles()
 365{
 366  DIR *dp;
 367  struct dirent *entry;
 368
 369  remove_completed_jobs();
 370  if (chdir(TT.crontabs_dir)) loginfo(LOG_EXIT, "chdir(%s)", TT.crontabs_dir);
 371  if (!(dp = opendir("."))) loginfo(LOG_EXIT, "chdir(%s)", ".");
 372
 373  while ((entry = readdir(dp))) {
 374    int fd;
 375    char *line;
 376    CRONFILE *cfile;
 377
 378    if (entry->d_name[0] == '.' && (!entry->d_name[1] ||
 379          (entry->d_name[1] == '.' && !entry->d_name[2]))) 
 380      continue;
 381
 382    if (!getpwnam(entry->d_name)) {
 383      loginfo(LOG_LEVEL7, "ignoring file '%s' (no such user)", entry->d_name);
 384      continue;
 385    }
 386    if ((fd = open(entry->d_name, O_RDONLY)) < 0) continue;
 387
 388    // one node for each user
 389    cfile = xzalloc(sizeof(CRONFILE));
 390    cfile->username = xstrdup(entry->d_name);
 391
 392    for (; (line = get_line(fd)); free(line))
 393      parse_line(line, cfile);
 394
 395    // If there is no job for a cron, remove the VAR list.
 396    if (!cfile->job) {
 397      VAR *v, *vnode = (VAR *)cfile->var;
 398
 399      free(cfile->username);
 400      if (cfile->mailto) free(cfile->mailto);
 401
 402      while (vnode && (v = dlist_pop(&vnode))) {
 403        free(v->name);
 404        free(v->val);
 405        free(v);
 406      }
 407      free(cfile);
 408    } else {
 409      if (!cfile->mailto) cfile->mailto = cfile->username;
 410      dlist_add_nomalloc((struct double_list **)&gclist,
 411          (struct double_list *)cfile);
 412    }
 413    close(fd);
 414  }
 415  closedir(dp);
 416}
 417
 418/*
 419 * Set env variables, if any in the cronfile. Execute given job with the given
 420 * SHELL or Default SHELL and send an e-mail with respect to every successfully
 421 * completed job (as per the given param 'prog').
 422 */
 423static void do_fork(CRONFILE *cfile, JOB *job, int fd, char *prog)
 424{
 425  pid_t pid = vfork();
 426
 427  if (pid == 0) {
 428    VAR *v, *vstart = (VAR *)cfile->var;
 429    struct passwd *pwd = getpwnam(cfile->username);
 430
 431    if (!pwd) loginfo(LOG_LEVEL9, "can't get uid for %s", cfile->username);
 432    else {
 433      char *file = "/bin/sh";
 434
 435      if (setenv("USER", pwd->pw_name, 1)) _exit(1);
 436      for (v = vstart; v;) {
 437        if (!strcmp("SHELL", v->name)) file = v->val;
 438        if (setenv(v->name, v->val, 1)) _exit(1);
 439        if ((v=v->next) == vstart) break;
 440      }
 441      if (!getenv("HOME")) {
 442        if (setenv("HOME", pwd->pw_dir, 1))
 443          _exit(1);
 444      }
 445      xsetuser(pwd);
 446      if (chdir(pwd->pw_dir)) loginfo(LOG_LEVEL9, "chdir(%s)", pwd->pw_dir);
 447      if (prog) file = prog;
 448      if (TT.flagd) loginfo(LOG_LEVEL5, "child running %s", file);
 449
 450      if (fd >= 0) {
 451        int newfd = prog ? 0 : 1;
 452        if (fd != newfd) {
 453          dup2(fd, newfd);
 454          close(fd);
 455        }
 456        dup2(1, 2);
 457      }
 458      setpgrp();
 459      execlp(file, file, (prog ? "-ti" : "-c"), (prog ? NULL : job->cmd), (char *) NULL);
 460      loginfo(LOG_ERROR, "can't execute '%s' for user %s", file, cfile->username);
 461
 462      if (!prog) dprintf(1, "Exec failed: %s -c %s\n", file, job->cmd);
 463      _exit(EXIT_SUCCESS);
 464    }
 465  }
 466  if (pid < 0) {
 467    loginfo(LOG_ERROR, "can't vfork");
 468    pid = 0;
 469  }
 470  if (fd >=0) close(fd);
 471  job->pid = pid;
 472}
 473
 474// Send an e-mail for each successfully completed jobs.
 475static void sendmail(CRONFILE *cfile, JOB *job)
 476{
 477  pid_t pid = job->pid;
 478  int mailfd;
 479  struct stat sb;
 480
 481  job->pid = 0;
 482  if (pid <=0 || job->mailsize <=0) {
 483    job->isrunning = 0;
 484    job->needstart = 1;
 485    return;
 486  }
 487  snprintf(toybuf, sizeof(toybuf), "/var/spool/cron/cron.%s.%d",
 488      cfile->username, (int)pid);
 489
 490  mailfd = open(toybuf, O_RDONLY);
 491  unlink(toybuf);
 492  if (mailfd < 0) return;
 493
 494  if (fstat(mailfd, &sb) == -1 || sb.st_uid != 0 || sb.st_nlink != 0
 495      || sb.st_size == job->mailsize || !S_ISREG(sb.st_mode)) {
 496    xclose(mailfd);
 497    return;
 498  }
 499  job->mailsize = 0;
 500  do_fork(cfile, job, mailfd, "sendmail");
 501}
 502
 503// Count the number of jobs, which are not completed.
 504static int count_running_jobs()
 505{
 506  CRONFILE *cfile = gclist;
 507  JOB *job, *jstart;
 508  int count = 0;
 509
 510  while (cfile) {
 511    job = jstart = (JOB *)cfile->job;
 512    while (job) {
 513      int ret;
 514
 515      if (!job->isrunning || job->pid<=0) goto NEXT_JOB;
 516      job->isrunning = 0;
 517      ret = waitpid(job->pid, NULL, WNOHANG);
 518      if (ret < 0 || ret == job->pid) {
 519        sendmail(cfile, job);
 520        if (job->pid) count += (job->isrunning=1);
 521        else {
 522          job->isrunning = 0;
 523          job->needstart = 1;
 524        }
 525      }
 526      else count += (job->isrunning=1);
 527
 528NEXT_JOB:
 529      if ((job = job->next) == jstart) break;
 530    }
 531    if ((cfile = cfile->next) == gclist) break;
 532  }
 533  return count;
 534}
 535
 536// Execute jobs one by one and prepare for the e-mail sending.
 537static void execute_jobs(void)
 538{
 539  CRONFILE *cfile = gclist;
 540  JOB *job, *jstart;
 541
 542  while (cfile) {
 543    job = jstart = (JOB *)cfile->job;
 544    while (job) {
 545      if (job->needstart) {
 546        job->needstart = 0;
 547        if (job->pid < 0) {
 548          int mailfd = -1;
 549
 550          job->mailsize = job->pid = 0;
 551          snprintf(toybuf, sizeof(toybuf), "/var/spool/cron/cron.%s.%d",
 552              cfile->username, getpid());
 553          if ((mailfd = open(toybuf, O_CREAT|O_TRUNC|O_WRONLY|O_EXCL|O_APPEND,
 554                  0600)) < 0) {
 555            loginfo(LOG_ERROR, "can't create mail file %s for user %s, "
 556                "discarding output", toybuf, cfile->username);
 557          } else {
 558            dprintf(mailfd, "To: %s\nSubject: cron: %s\n\n", cfile->mailto, job->cmd);
 559            job->mailsize = lseek(mailfd, 0, SEEK_CUR);
 560          }
 561          do_fork(cfile, job, mailfd, NULL);
 562          if (mailfd >= 0) {
 563            if (job->pid <= 0) unlink(toybuf);
 564            else {
 565              char *mailfile = xmprintf("/var/spool/cron/cron.%s.%d",
 566                  cfile->username, (int)job->pid);
 567              rename(toybuf, mailfile);
 568              free(mailfile);
 569            }
 570          }
 571          loginfo(LOG_LEVEL8, "USER %s pid %3d cmd %s", 
 572              cfile->username, job->pid, job->cmd);
 573          if (job->pid < 0) job->needstart = 1;
 574          else job->isrunning = 1;
 575        }
 576      }
 577      if ((job = job->next) == jstart) break;
 578    }
 579    if ((cfile = cfile->next) == gclist) break;
 580  }
 581}
 582
 583// Identify jobs, which needs to be started at the given time interval.
 584static void schedule_jobs(time_t ctime, time_t ptime)
 585{
 586  time_t tm = ptime-ptime%60;
 587
 588  for (; tm <= ctime; tm += 60) {
 589    struct tm *lt;
 590    CRONFILE *cfile = gclist;
 591    JOB *job, *jstart;
 592
 593    if (tm <= ptime) continue;
 594    lt = localtime(&tm);
 595
 596    while (cfile) {
 597      if (TT.flagd) loginfo(LOG_LEVEL5, "file %s:", cfile->username);
 598      if (cfile->invalid) goto NEXT_CRONFILE;
 599      job = jstart = (JOB *)cfile->job;
 600
 601      while (job) {
 602        if (TT.flagd) loginfo(LOG_LEVEL5, " line %s", job->cmd);
 603
 604        if (job->min[lt->tm_min] && job->hour[lt->tm_hour]
 605            && (job->dom[lt->tm_mday] || job->dow[lt->tm_wday])
 606            && job->mon[lt->tm_mon-1]) {
 607          if (TT.flagd)
 608            loginfo(LOG_LEVEL5, " job: %d %s\n", (int)job->pid, job->cmd);
 609          if (job->pid > 0) {
 610            loginfo(LOG_LEVEL8, "user %s: process already running: %s",
 611                cfile->username, job->cmd);
 612          } else if (!job->pid) {
 613            job->pid = -1;
 614            job->needstart = 1;
 615            job->isrunning = 0;
 616          }
 617        }
 618        if ((job = job->next) == jstart) break;
 619      }
 620NEXT_CRONFILE:
 621      if ((cfile = cfile->next) == gclist) break;
 622    }
 623  }
 624}
 625
 626void crond_main(void)
 627{
 628  time_t ctime, ptime;
 629  int sleepfor = 60;
 630  struct stat sb;
 631
 632  TT.flagd = (toys.optflags & FLAG_d);
 633
 634  // Setting default params.
 635  if (TT.flagd) TT.loglevel = TT.loglevel_d;
 636  if (!(toys.optflags & (FLAG_f | FLAG_b))) toys.optflags |= FLAG_b;
 637  if (!(toys.optflags & (FLAG_S | FLAG_L))) toys.optflags |= FLAG_S;
 638
 639  if ((toys.optflags & FLAG_c)
 640      && (TT.crontabs_dir[strlen(TT.crontabs_dir)-1] != '/'))
 641    TT.crontabs_dir = xmprintf("%s/", TT.crontabs_dir);
 642
 643  if (!TT.crontabs_dir) TT.crontabs_dir = xstrdup("/var/spool/cron/crontabs/");
 644  if (toys.optflags & FLAG_b) daemon(0,0);
 645
 646  if (!TT.flagd && !TT.logfile)
 647    openlog(toys.which->name, LOG_CONS | LOG_PID, LOG_CRON);
 648
 649  // Set default shell once.
 650  if (setenv("SHELL", "/bin/sh", 1)) error_exit("Can't set default shell");
 651  xchdir(TT.crontabs_dir);
 652  loginfo(LOG_LEVEL8, "crond started, log level %d", TT.loglevel);
 653
 654  if (stat(TT.crontabs_dir, &sb)) sb.st_mtime = 0;
 655  TT.crontabs_dir_mtime = sb.st_mtime;
 656  scan_cronfiles();
 657  ctime = time(NULL);
 658
 659  while (1) {
 660    long tdiff;
 661
 662    ptime = ctime;
 663    sleep(sleepfor - (ptime%sleepfor) +1);
 664    tdiff =(long) ((ctime = time(NULL)) - ptime);
 665
 666    if (stat(TT.crontabs_dir, &sb)) sb.st_mtime = 0;
 667    if (TT.crontabs_dir_mtime != sb.st_mtime) {
 668      TT.crontabs_dir_mtime = sb.st_mtime;
 669      scan_cronfiles();
 670    }
 671
 672    if (TT.flagd) loginfo(LOG_LEVEL5, "wakeup diff=%ld\n", tdiff);
 673    if (tdiff < -60 * 60 || tdiff > 60 * 60)
 674      loginfo(LOG_LEVEL9, "time disparity of %ld minutes detected", tdiff / 60);
 675    else if (tdiff > 0) {
 676      schedule_jobs(ctime, ptime);
 677      execute_jobs();
 678      if (count_running_jobs()) sleepfor = 10;
 679      else sleepfor = 60;
 680    }
 681  }
 682}
 683