crond.c 18 KB

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