suspend re-scheduling of a waiting job for cl_Delay
[yacron.git] / database.c
blob9954c5aeac1ad2f15ed5cf9d6c8234e5987ee109
2 /*
3 * DATABASE.C
5 * Copyright 1994 Matthew Dillon (dillon@apollo.backplane.com)
6 * Copyright 2009 James Pryor <profjim@jimpryor.net>
7 * May be distributed under the GNU General Public License
8 */
10 #include "defs.h"
12 Prototype void CheckUpdates(const char *dpath, const char *user_override, time_t t1, time_t t2);
13 Prototype void SynchronizeDir(const char *dpath, const char *user_override, int initial_scan);
14 Prototype void ReadTimestamps(const char *user, int initial_scan);
15 Prototype int TestJobs(time_t t1, time_t t2);
16 Prototype int TestStartupJobs(void);
17 Prototype int ArmJob(CronFile *file, CronLine *line, time_t t1, time_t t2);
18 Prototype void RunJobs(void);
19 Prototype int CheckJobs(void);
21 void SynchronizeFile(const char *dpath, const char *fname, const char *uname);
22 void DeleteFile(CronFile **pfile);
23 char *ParseInterval(int *interval, char *ptr);
24 char *ParseField(char *userName, char *ary, int modvalue, int off, int onvalue, const char **names, char *ptr);
25 void FixDayDow(CronLine *line);
27 CronFile *FileBase;
29 const char *DowAry[] = {
30 "sun",
31 "mon",
32 "tue",
33 "wed",
34 "thu",
35 "fri",
36 "sat",
38 "Sun",
39 "Mon",
40 "Tue",
41 "Wed",
42 "Thu",
43 "Fri",
44 "Sat",
45 NULL
48 const char *MonAry[] = {
49 "jan",
50 "feb",
51 "mar",
52 "apr",
53 "may",
54 "jun",
55 "jul",
56 "aug",
57 "sep",
58 "oct",
59 "nov",
60 "dec",
62 "Jan",
63 "Feb",
64 "Mar",
65 "Apr",
66 "May",
67 "Jun",
68 "Jul",
69 "Aug",
70 "Sep",
71 "Oct",
72 "Nov",
73 "Dec",
74 NULL
77 const char *FreqAry[] = {
78 "noauto",
79 "reboot",
80 "hourly",
81 "daily",
82 "weekly",
83 "monthly",
84 "yearly",
85 NULL
89 * Check the cron.update file in the specified directory. If user_override
90 * is NULL then the files in the directory belong to the user whose name is
91 * the file, otherwise they belong to the user_override user.
93 void
94 CheckUpdates(const char *dpath, const char *user_override, time_t t1, time_t t2)
96 FILE *fi;
97 char buf[256];
98 char *fname, *ptok, *job;
99 char *path;
101 asprintf(&path, "%s/%s", dpath, CRONUPDATE);
102 if ((fi = fopen(path, "r")) != NULL) {
103 remove(path);
104 logn(LOG_INFO, "reading %s/%s\n", dpath, CRONUPDATE);
105 while (fgets(buf, sizeof(buf), fi) != NULL) {
107 * if buf has only sep chars, return NULL and point ptok at buf's terminating 0
108 * else return pointer to first non-sep of buf and
109 * if there's a following sep, overwrite it to 0 and point ptok to next char
110 * else point ptok at buf's terminating 0
112 fname = strtok_r(buf, " \t\n", &ptok);
114 if (user_override)
115 SynchronizeFile(dpath, fname, user_override);
116 else if (!getpwnam(fname))
117 logn(LOG_WARNING, "ignoring %s/%s (non-existent user)\n", dpath, fname);
118 else if (*ptok == 0 || *ptok == '\n') {
119 SynchronizeFile(dpath, fname, fname);
120 ReadTimestamps(fname, 1);
121 } else {
122 /* if fname is followed by whitespace, we prod any following jobs */
123 CronFile *file = FileBase;
124 while (file) {
125 if (strcmp(file->cf_UserName, fname) == 0)
126 break;
127 file = file->cf_Next;
129 if (!file)
130 logn(LOG_WARNING, "unable to prod for user %s: no crontab\n", fname);
131 else {
132 CronLine *line;
133 /* calling strtok(ptok...) then strtok(NULL) is equiv to calling strtok_r(NULL,..&ptok) */
134 while ((job = strtok(ptok, " \t\n")) != NULL) {
135 time_t force = t2;
136 ptok = NULL;
137 if (*job == '!') {
138 force = (time_t)-1;
139 ++job;
141 line = file->cf_LineBase;
142 while (line) {
143 if (line->cl_JobName && strcmp(line->cl_JobName, job) == 0)
144 break;
145 line = line->cl_Next;
147 if (line)
148 ArmJob(file, line, t1, force);
149 else {
150 logn(LOG_WARNING, "unable to prod for user %s: unknown job %s\n", fname, job);
151 /* we can continue parsing this line, we just don't install any CronWaiter for the requested job */
157 fclose(fi);
159 free(path);
162 void
163 SynchronizeDir(const char *dpath, const char *user_override, int initial_scan)
165 CronFile **pfile;
166 CronFile *file;
167 struct dirent *den;
168 DIR *dir;
169 char *path;
172 * Delete all database CronFiles for this directory. DeleteFile() will
173 * free *pfile and relink the *pfile pointer, or in the alternative will
174 * mark it as deleted.
176 pfile = &FileBase;
177 while ((file = *pfile) != NULL) {
178 if (file->cf_Deleted == 0 && strcmp(file->cf_DPath, dpath) == 0) {
179 DeleteFile(pfile);
180 } else {
181 pfile = &file->cf_Next;
186 * Since we are resynchronizing the entire directory, remove the
187 * the CRONUPDATE file.
189 asprintf(&path, "%s/%s", dpath, CRONUPDATE);
190 remove(path);
191 free(path);
194 * Scan the specified directory
196 if ((dir = opendir(dpath)) != NULL) {
197 while ((den = readdir(dir)) != NULL) {
198 if (strchr(den->d_name, '.') != NULL)
199 continue;
200 if (strcmp(den->d_name, CRONUPDATE) == 0)
201 continue;
202 if (user_override) {
203 SynchronizeFile(dpath, den->d_name, user_override);
204 } else if (getpwnam(den->d_name)) {
205 SynchronizeFile(dpath, den->d_name, den->d_name);
206 } else {
207 logn(LOG_WARNING, "ignoring %s/%s (non-existent user)\n",
208 dpath, den->d_name);
211 closedir(dir);
212 } else {
213 if (initial_scan)
214 logn(LOG_ERR, "unable to scan directory %s\n", dpath);
215 /* softerror, do not exit the program */
220 void
221 ReadTimestamps(const char *user, int initial_scan)
223 CronFile *file;
224 CronLine *line;
225 FILE *fi;
226 char buf[256];
227 char *ptr;
228 struct tm tm;
229 time_t sec, freq;
231 file = FileBase;
232 while (file != NULL) {
233 if (file->cf_Deleted == 0 && (!user || strcmp(user, file->cf_UserName) == 0)) {
234 line = file->cf_LineBase;
235 while (line != NULL) {
236 if (line->cl_Timestamp) {
237 if ((fi = fopen(line->cl_Timestamp, "r")) != NULL) {
238 if (fgets(buf, sizeof(buf), fi) != NULL) {
239 sec = (time_t)-1;
240 ptr = strptime(buf, TIMESTAMP_FMT, &tm);
241 if (ptr && (*ptr == 0 || *ptr == '\n'))
242 sec = mktime(&tm);
243 if (sec == (time_t)-1) {
244 logn(LOG_WARNING, "unable to parse timestamp (user %s job %s)\n", file->cf_UserName, line->cl_JobName);
245 /* we continue checking other timestamps in this CronFile */
246 } else {
247 line->cl_LastRan = sec;
248 freq = (line->cl_Freq > 0) ? line->cl_Freq : line->cl_Delay;
249 if (line->cl_NotUntil < line->cl_LastRan + freq)
250 line->cl_NotUntil = line->cl_LastRan + freq;
253 fclose(fi);
254 } else {
255 if (initial_scan)
256 logn(LOG_NOTICE, "no timestamp found (user %s job %s)\n", file->cf_UserName, line->cl_JobName);
257 /* softerror, do not exit the program */
260 line = line->cl_Next;
263 file = file->cf_Next;
267 void
268 SynchronizeFile(const char *dpath, const char *fileName, const char *userName)
270 CronFile **pfile;
271 CronFile *file;
272 int maxEntries;
273 int maxLines;
274 char buf[1024];
275 char *path;
276 FILE *fi;
279 * Limit entries
281 if (strcmp(userName, "root") == 0)
282 maxEntries = 65535;
283 else
284 maxEntries = MAXLINES;
285 maxLines = maxEntries * 10;
288 * Delete any existing copy of this CronFile
290 pfile = &FileBase;
291 while ((file = *pfile) != NULL) {
292 if (file->cf_Deleted == 0 && strcmp(file->cf_DPath, dpath) == 0 &&
293 strcmp(file->cf_FileName, fileName) == 0
295 DeleteFile(pfile);
296 } else {
297 pfile = &file->cf_Next;
301 asprintf(&path, "%s/%s", dpath, fileName);
302 if ((fi = fopen(path, "r")) != NULL) {
303 struct stat sbuf;
305 if (fstat(fileno(fi), &sbuf) == 0 && sbuf.st_uid == DaemonUid) {
306 CronFile *file = calloc(1, sizeof(CronFile));
307 CronLine **pline;
309 file->cf_UserName = strdup(userName);
310 file->cf_FileName = strdup(fileName);
311 file->cf_DPath = strdup(dpath);
312 pline = &file->cf_LineBase;
314 while (fgets(buf, sizeof(buf), fi) != NULL && --maxLines) {
315 CronLine line;
316 char *ptr = buf;
317 int len;
319 while (*ptr == ' ' || *ptr == '\t' || *ptr == '\n')
320 ++ptr;
322 len = strlen(ptr);
323 if (len && ptr[len-1] == '\n')
324 ptr[--len] = 0;
326 if (*ptr == 0 || *ptr == '#')
327 continue;
329 if (--maxEntries == 0)
330 break;
332 bzero(&line, sizeof(line));
334 if (DebugOpt)
335 logn(LOG_DEBUG, "User %s Entry %s\n", userName, buf);
337 if (*ptr == '@') {
339 * parse @hourly, etc
341 int j;
342 line.cl_Delay = -1;
343 ptr += 1;
344 for (j = 0; FreqAry[j]; ++j) {
345 if (strncmp(ptr, FreqAry[j], strlen(FreqAry[j])) == 0) {
346 break;
349 if (FreqAry[j]) {
350 ptr += strlen(FreqAry[j]);
351 switch(j) {
352 case 0:
353 /* noauto */
354 line.cl_Freq = -2;
355 line.cl_Delay = 0;
356 break;
357 case 1:
358 /* reboot */
359 line.cl_Freq = -1;
360 line.cl_Delay = 0;
361 break;
362 case 2:
363 line.cl_Freq = HOURLY_FREQ;
364 break;
365 case 3:
366 line.cl_Freq = DAILY_FREQ;
367 break;
368 case 4:
369 line.cl_Freq = WEEKLY_FREQ;
370 break;
371 case 5:
372 line.cl_Freq = MONTHLY_FREQ;
373 case 6:
374 line.cl_Freq = YEARLY_FREQ;
375 /* else line.cl_Freq will remain 0 */
379 if (!line.cl_Freq || (*ptr != ' ' && *ptr != '\t')) {
380 logn(LOG_WARNING, "failed parsing crontab for user %s: %s\n", userName, buf);
381 continue;
384 if (line.cl_Delay < 0) {
385 line.cl_Delay = line.cl_Freq / 20;
386 /* all minutes are permitted */
387 for (j=0; j<60; ++j)
388 line.cl_Mins[j] = 1;
389 for (j=0; j<24; ++j)
390 line.cl_Hrs[j] = 1;
391 for (j=1; j<32; ++j)
392 /* days are numbered 1..31 */
393 line.cl_Days[j] = 1;
394 for (j=0; j<12; ++j)
395 line.cl_Mons[j] = 1;
398 while (*ptr == ' ' || *ptr == '\t')
399 ++ptr;
401 } else {
403 * parse date ranges
406 ptr = ParseField(file->cf_UserName, line.cl_Mins, 60, 0, 1,
407 NULL, ptr);
408 ptr = ParseField(file->cf_UserName, line.cl_Hrs, 24, 0, 1,
409 NULL, ptr);
410 ptr = ParseField(file->cf_UserName, line.cl_Days, 32, 0, 1,
411 NULL, ptr);
412 ptr = ParseField(file->cf_UserName, line.cl_Mons, 12, -1, 1,
413 MonAry, ptr);
414 ptr = ParseField(file->cf_UserName, line.cl_Dow, 7, 0, 31,
415 DowAry, ptr);
417 * check failure
420 if (ptr == NULL)
421 continue;
424 * fix days and dow - if one is not * and the other
425 * is *, the other is set to 0, and vise-versa
428 FixDayDow(&line);
431 /* check for ID=... and AFTER=... and FREQ=... */
432 do {
433 if (strncmp(ptr, ID_TAG, strlen(ID_TAG)) == 0) {
434 if (line.cl_JobName) {
435 /* only assign ID_TAG once */
436 logn(LOG_WARNING, "failed parsing crontab for user %s: repeated %s\n", userName, ptr);
437 ptr = NULL;
438 } else {
439 ptr += strlen(ID_TAG);
441 * name = strsep(&ptr, seps):
442 * return name = ptr, and if ptr contains sep chars, overwrite first with 0 and point ptr to next char
443 * else set ptr=NULL
445 asprintf(&line.cl_Description, "job %s", strsep(&ptr, " \t"));
446 line.cl_JobName = line.cl_Description + 4;
447 if (!ptr)
448 logn(LOG_WARNING, "failed parsing crontab for user %s: no command after %s%s\n", userName, ID_TAG, line.cl_JobName);
450 } else if (strncmp(ptr, FREQ_TAG, strlen(FREQ_TAG)) == 0) {
451 if (line.cl_Freq) {
452 /* only assign FREQ_TAG once */
453 logn(LOG_WARNING, "failed parsing crontab for user %s: repeated %s\n", userName, ptr);
454 ptr = NULL;
455 } else {
456 char *base = ptr;
457 ptr += strlen(FREQ_TAG);
458 ptr = ParseInterval(&line.cl_Freq, ptr);
459 if (ptr && *ptr == '/')
460 ptr = ParseInterval(&line.cl_Delay, ++ptr);
461 else
462 line.cl_Delay = line.cl_Freq;
463 if (!ptr) {
464 logn(LOG_WARNING, "failed parsing crontab for user %s: %s\n", userName, base);
465 } else if (*ptr != ' ' && *ptr != '\t') {
466 logn(LOG_WARNING, "failed parsing crontab for user %s: no command after %s\n", userName, base);
467 ptr = NULL;
470 } else if (strncmp(ptr, WAIT_TAG, strlen(WAIT_TAG)) == 0) {
471 if (line.cl_Waiters) {
472 /* only assign WAIT_TAG once */
473 logn(LOG_WARNING, "failed parsing crontab for user %s: repeated %s\n", userName, ptr);
474 ptr = NULL;
475 } else {
476 short more = 1;
477 char *name;
478 ptr += strlen(WAIT_TAG);
479 do {
480 CronLine *job, **pjob;
481 if (strcspn(ptr,",") < strcspn(ptr," \t"))
482 name = strsep(&ptr, ",");
483 else {
484 more = 0;
485 name = strsep(&ptr, " \t");
487 if (!ptr || *ptr == 0) {
488 /* unexpectedly this was the last token in buf; so abort */
489 logn(LOG_WARNING, "failed parsing crontab for user %s: no command after %s%s\n", userName, WAIT_TAG, name);
490 ptr = NULL;
491 } else {
492 int waitfor = 0;
493 char *w, *wsave;
494 if ((w = index(name, '/')) != NULL) {
495 wsave = w++;
496 w = ParseInterval(&waitfor, w);
497 if (!w || *w != 0) {
498 logn(LOG_WARNING, "failed parsing crontab for user %s: %s%s\n", userName, WAIT_TAG, name);
499 ptr = NULL;
500 } else
501 /* truncate name */
502 *wsave = 0;
504 if (ptr) {
505 /* look for a matching CronLine */
506 pjob = &file->cf_LineBase;
507 while ((job = *pjob) != NULL) {
508 if (job->cl_JobName && strcmp(job->cl_JobName, name) == 0) {
509 CronWaiter *waiter = malloc(sizeof(CronWaiter));
510 CronNotifier *notif = malloc(sizeof(CronNotifier));
511 waiter->cw_Flag = -1;
512 waiter->cw_MaxWait = waitfor;
513 waiter->cw_NotifLine = job;
514 waiter->cw_Notifier = notif;
515 waiter->cw_Next = line.cl_Waiters; /* add to head of line.cl_Waiters */
516 line.cl_Waiters = waiter;
517 notif->cn_Waiter = waiter;
518 notif->cn_Next = job->cl_Notifs; /* add to head of job->cl_Notifs */
519 job->cl_Notifs = notif;
520 break;
521 } else
522 pjob = &job->cl_Next;
524 if (!job) {
525 logn(LOG_WARNING, "failed parsing crontab for user %s: unknown job %s\n", userName, name);
526 /* we can continue parsing this line, we just don't install any CronWaiter for the requested job */
530 } while (ptr && more);
532 } else
533 break;
534 if (!ptr)
535 break;
536 while (*ptr == ' ' || *ptr == '\t')
537 ++ptr;
538 } while (!line.cl_JobName || !line.cl_Waiters || !line.cl_Freq);
540 if (line.cl_JobName && (!ptr || *line.cl_JobName == 0)) {
541 /* we're aborting, or ID= was empty */
542 free(line.cl_Description);
543 line.cl_Description = NULL;
544 line.cl_JobName = NULL;
546 if (ptr && line.cl_Delay > 0 && !line.cl_JobName) {
547 logn(LOG_WARNING, "failed parsing crontab for user %s: writing timestamp requires job %s to be named\n", userName, ptr);
548 ptr = NULL;
550 if (!ptr) {
551 /* couldn't parse so we abort; free any cl_Waiters */
552 if (line.cl_Waiters) {
553 CronWaiter **pwaiters, *waiters;
554 pwaiters = &line.cl_Waiters;
555 while ((waiters = *pwaiters) != NULL) {
556 *pwaiters = waiters->cw_Next;
557 /* leave the Notifier allocated but disabled */
558 waiters->cw_Notifier->cn_Waiter = NULL;
559 free(waiters);
562 continue;
564 /* now we've added any ID=... or AFTER=... */
567 * copy command string
569 line.cl_Shell = strdup(ptr);
571 if (line.cl_Delay > 0) {
572 asprintf(&line.cl_Timestamp, "%s/%s.%s", TSDir, userName, line.cl_JobName);
573 line.cl_NotUntil = time(NULL) + line.cl_Delay;
576 if (line.cl_JobName) {
577 if (DebugOpt)
578 logn(LOG_DEBUG, " Command %s Job %s\n", line.cl_Shell, line.cl_JobName);
579 } else {
580 /* when cl_JobName is NULL, we point cl_Description to cl_Shell */
581 line.cl_Description = line.cl_Shell;
582 if (DebugOpt)
583 logn(LOG_DEBUG, " Command %s\n", line.cl_Shell);
586 *pline = calloc(1, sizeof(CronLine));
587 /* copy working CronLine to newly allocated one */
588 **pline = line;
590 pline = &((*pline)->cl_Next);
593 *pline = NULL;
595 file->cf_Next = FileBase;
596 FileBase = file;
598 if (maxLines == 0 || maxEntries == 0)
599 logn(LOG_WARNING, "maximum number of lines reached for user %s\n", userName);
601 fclose(fi);
603 free(path);
606 char *
607 ParseInterval(int *interval, char *ptr)
609 int n = 0;
610 if (ptr && *ptr >= '0' && *ptr <= '9' && (n = strtol(ptr, &ptr, 10)) > 0)
611 switch (*ptr) {
612 case 'm':
613 n *= 60;
614 break;
615 case 'h':
616 n *= HOURLY_FREQ;
617 break;
618 case 'd':
619 n *= DAILY_FREQ;
620 break;
621 case 'w':
622 n *= WEEKLY_FREQ;
623 break;
624 default:
625 n = 0;
627 if (n > 0) {
628 *interval = n;
629 return (ptr+1);
630 } else
631 return (NULL);
634 char *
635 ParseField(char *user, char *ary, int modvalue, int off, int onvalue, const char **names, char *ptr)
637 char *base = ptr;
638 int n1 = -1;
639 int n2 = -1;
641 if (base == NULL)
642 return (NULL);
644 while (*ptr != ' ' && *ptr != '\t' && *ptr != '\n') {
645 int skip = 0;
648 * Handle numeric digit or symbol or '*'
651 if (*ptr == '*') {
652 n1 = 0; /* everything will be filled */
653 n2 = modvalue - 1;
654 skip = 1;
655 ++ptr;
656 } else if (*ptr >= '0' && *ptr <= '9') {
657 if (n1 < 0)
658 n1 = strtol(ptr, &ptr, 10) + off;
659 else
660 n2 = strtol(ptr, &ptr, 10) + off;
661 skip = 1;
662 } else if (names) {
663 int i;
665 for (i = 0; names[i]; ++i) {
666 if (strncmp(ptr, names[i], strlen(names[i])) == 0) {
667 break;
670 if (names[i]) {
671 ptr += strlen(names[i]);
672 if (n1 < 0)
673 n1 = i;
674 else
675 n2 = i;
676 skip = 1;
681 * handle optional range '-'
684 if (skip == 0) {
685 logn(LOG_WARNING, "failed parsing crontab for user %s: %s\n", user, base);
686 return(NULL);
688 if (*ptr == '-' && n2 < 0) {
689 ++ptr;
690 continue;
694 * collapse single-value ranges, handle skipmark, and fill
695 * in the character array appropriately.
698 if (n2 < 0)
699 n2 = n1;
701 if (*ptr == '/')
702 skip = strtol(ptr + 1, &ptr, 10);
705 * fill array, using a failsafe is the easiest way to prevent
706 * an endless loop
710 int s0 = 1;
711 int failsafe = 1024;
713 --n1;
714 do {
715 n1 = (n1 + 1) % modvalue;
717 if (--s0 == 0) {
718 ary[n1] = onvalue;
719 s0 = skip;
721 } while (n1 != n2 && --failsafe);
723 if (failsafe == 0) {
724 logn(LOG_WARNING, "failed parsing crontab for user %s: %s\n", user, base);
725 return(NULL);
728 if (*ptr != ',')
729 break;
730 ++ptr;
731 n1 = -1;
732 n2 = -1;
735 if (*ptr != ' ' && *ptr != '\t' && *ptr != '\n') {
736 logn(LOG_WARNING, "failed parsing crontab for user %s: %s\n", user, base);
737 return(NULL);
740 while (*ptr == ' ' || *ptr == '\t' || *ptr == '\n')
741 ++ptr;
743 if (DebugOpt) {
744 int i;
746 for (i = 0; i < modvalue; ++i)
747 logn(LOG_DEBUG, "%2x ", ary[i]);
748 logn(LOG_DEBUG, "\n");
751 return(ptr);
754 void
755 FixDayDow(CronLine *line)
757 unsigned short i,j;
758 short weekUsed = 0;
759 short daysUsed = 0;
761 for (i = 0; i < arysize(line->cl_Dow); ++i) {
762 if (line->cl_Dow[i] == 0) {
763 weekUsed = 1;
764 break;
767 for (i = 0; i < arysize(line->cl_Days); ++i) {
768 if (line->cl_Days[i] == 0) {
769 if (weekUsed) {
770 if (!daysUsed) {
771 daysUsed = 1;
772 /* change from "every Mon" to "ith Mon"
773 * 6th,7th... Dow are treated as 1st,2nd... */
774 for (j = 0; j < arysize(line->cl_Dow); ++j) {
775 line->cl_Dow[j] &= 1 << (i-1)%5;
777 } else {
778 /* change from "nth Mon" to "nth or ith Mon" */
779 for (j = 0; j < arysize(line->cl_Dow); ++j) {
780 if (line->cl_Dow[j])
781 line->cl_Dow[j] |= 1 << (i-1)%5;
784 /* continue cycling through cl_Days */
786 else {
787 daysUsed = 1;
788 break;
792 if (weekUsed) {
793 memset(line->cl_Days, 0, sizeof(line->cl_Days));
795 if (daysUsed && !weekUsed) {
796 memset(line->cl_Dow, 0, sizeof(line->cl_Dow));
801 * DeleteFile() - destroy a CronFile.
803 * The CronFile (*pfile) is destroyed if possible, and marked cf_Deleted
804 * if there are still active processes running on it. *pfile is relinked
805 * on success.
807 void
808 DeleteFile(CronFile **pfile)
810 CronFile *file = *pfile;
811 CronLine **pline = &file->cf_LineBase;
812 CronLine *line;
813 CronWaiter **pwaiters, *waiters;
814 CronNotifier **pnotifs, *notifs;
816 file->cf_Running = 0;
817 file->cf_Deleted = 1;
819 while ((line = *pline) != NULL) {
820 if (line->cl_Pid > 0) {
821 file->cf_Running = 1;
822 pline = &line->cl_Next;
823 } else {
824 *pline = line->cl_Next;
825 free(line->cl_Shell);
827 if (line->cl_JobName)
828 /* this frees both cl_Description and cl_JobName
829 * if cl_JobName is NULL, Description pointed to ch_Shell, which was already freed
831 free(line->cl_Description);
832 if (line->cl_Timestamp)
833 free(line->cl_Timestamp);
835 pnotifs = &line->cl_Notifs;
836 while ((notifs = *pnotifs) != NULL) {
837 *pnotifs = notifs->cn_Next;
838 if (notifs->cn_Waiter) {
839 notifs->cn_Waiter->cw_NotifLine = NULL;
840 notifs->cn_Waiter->cw_Notifier = NULL;
842 free(notifs);
844 pwaiters = &line->cl_Waiters;
845 while ((waiters = *pwaiters) != NULL) {
846 *pwaiters = waiters->cw_Next;
847 if (waiters->cw_Notifier)
848 waiters->cw_Notifier->cn_Waiter = NULL;
849 free(waiters);
852 free(line);
855 if (file->cf_Running == 0) {
856 *pfile = file->cf_Next;
857 free(file->cf_DPath);
858 free(file->cf_FileName);
859 free(file->cf_UserName);
860 free(file);
866 * TestJobs()
868 * determine which jobs need to be run. Under normal conditions, the
869 * period is about a minute (one scan). Worst case it will be one
870 * hour (60 scans).
874 TestJobs(time_t t1, time_t t2)
876 short nJobs = 0;
877 time_t t;
878 CronFile *file;
879 CronLine *line;
881 for (file = FileBase; file; file = file->cf_Next) {
882 if (file->cf_Deleted)
883 continue;
884 for (line = file->cf_LineBase; line; line = line->cl_Next) {
885 struct CronWaiter *waiter;
887 if (line->cl_Pid == -2) {
888 /* can job stop waiting? */
889 int ready = 1;
890 waiter = line->cl_Waiters;
891 while (waiter != NULL) {
892 if (waiter->cw_Flag > 0) {
893 /* notifier exited unsuccessfully */
894 ready = 2;
895 break;
896 } else if (waiter->cw_Flag < 0)
897 /* still waiting, notifier hasn't run to completion */
898 ready = 0;
899 waiter = waiter->cw_Next;
901 if (ready == 2) {
902 if (DebugOpt)
903 logn(LOG_DEBUG, "cancelled waiting: user %s %s\n", file->cf_UserName, line->cl_Description);
904 line->cl_Pid = 0;
905 } else if (ready) {
906 if (DebugOpt)
907 logn(LOG_DEBUG, "finished waiting: user %s %s\n", file->cf_UserName, line->cl_Description);
908 nJobs += ArmJob(file, line, 0, -1);
910 if (line->cl_NotUntil)
911 line->cl_NotUntil = t2;
919 * Find jobs > t1 and <= t2
922 for (t = t1 - t1 % 60; t <= t2; t += 60) {
923 if (t > t1) {
924 struct tm *tp = localtime(&t);
926 unsigned short n_wday = (tp->tm_mday - 1)%7 + 1;
927 if (n_wday >= 4) {
928 struct tm tnext = *tp;
929 tnext.tm_mday += 7;
930 if (mktime(&tnext) != (time_t)-1 && tnext.tm_mon != tp->tm_mon)
931 n_wday |= 16; /* last dow in month is always recognized as 5th */
934 for (file = FileBase; file; file = file->cf_Next) {
935 if (file->cf_Deleted)
936 continue;
937 for (line = file->cf_LineBase; line; line = line->cl_Next) {
938 if (line->cl_Pid != -1 && (line->cl_Freq == 0 || (line->cl_Freq > 0 && t2 >= line->cl_NotUntil))) {
939 /* (re)schedule job? */
940 if (line->cl_Mins[tp->tm_min] &&
941 line->cl_Hrs[tp->tm_hour] &&
942 (line->cl_Days[tp->tm_mday] || (n_wday && line->cl_Dow[tp->tm_wday]) ) &&
943 line->cl_Mons[tp->tm_mon]
945 if (line->cl_NotUntil)
946 line->cl_NotUntil = t2 - t2 % 60 + line->cl_Delay; /* save what minute this job was scheduled/started waiting, plus cl_Delay */
947 nJobs += ArmJob(file, line, t1, t2);
954 return(nJobs);
958 * ArmJob: if t2 is (time_t)-1, we force-schedule the job without any waiting
959 * else it will wait on any of its declared notifiers who will run <= t2 + cw_MaxWait
963 ArmJob(CronFile *file, CronLine *line, time_t t1, time_t t2)
965 struct CronWaiter *waiter;
966 if (line->cl_Pid > 0) {
967 logn(LOG_NOTICE, "process already running (%d): user %s %s\n",
968 line->cl_Pid,
969 file->cf_UserName,
970 line->cl_Description
972 } else if (t2 == -1 && line->cl_Pid != -1) {
973 line->cl_Pid = -1;
974 file->cf_Ready = 1;
975 return 1;
976 } else if (line->cl_Pid == 0) {
977 /* arming a waiting job (cl_Pid == -2) without forcing has no effect */
978 line->cl_Pid = -1;
979 /* if we have any waiters, zero them and arm cl_Pid=-2 */
980 waiter = line->cl_Waiters;
981 while (waiter != NULL) {
982 /* check if notifier will run <= t2 + cw_Max_Wait? */
983 if (!waiter->cw_NotifLine)
984 /* notifier deleted */
985 waiter->cw_Flag = 0;
986 else if (waiter->cw_NotifLine->cl_Pid != 0) {
987 /* if notifier is armed, or waiting, or running, we wait for it */
988 waiter->cw_Flag = -1;
989 line->cl_Pid = -2;
990 } else if (waiter->cw_NotifLine->cl_Freq < 0) {
991 /* arm any @noauto or @reboot jobs we're waiting on */
992 ArmJob(file, waiter->cw_NotifLine, t1, t2);
993 waiter->cw_Flag = -1;
994 line->cl_Pid = -2;
995 } else {
996 time_t t;
997 if (waiter->cw_MaxWait == 0)
998 /* when no MaxWait interval specified, we always wait */
999 waiter->cw_Flag = -1;
1000 else if (waiter->cw_NotifLine->cl_Freq == 0 || (waiter->cw_NotifLine->cl_Freq > 0 && t2 + waiter->cw_MaxWait >= waiter->cw_NotifLine->cl_NotUntil)) {
1001 /* default is don't wait */
1002 waiter->cw_Flag = 0;
1003 for (t = t1 - t1 % 60; t <= t2; t += 60) {
1004 if (t > t1) {
1005 struct tm *tp = localtime(&t);
1007 unsigned short n_wday = (tp->tm_mday - 1)%7 + 1;
1008 if (n_wday >= 4) {
1009 struct tm tnext = *tp;
1010 tnext.tm_mday += 7;
1011 if (mktime(&tnext) != (time_t)-1 && tnext.tm_mon != tp->tm_mon)
1012 n_wday |= 16; /* last dow in month is always recognized as 5th */
1014 if (line->cl_Mins[tp->tm_min] &&
1015 line->cl_Hrs[tp->tm_hour] &&
1016 (line->cl_Days[tp->tm_mday] || (n_wday && line->cl_Dow[tp->tm_wday]) ) &&
1017 line->cl_Mons[tp->tm_mon]
1019 /* notifier will run soon enough, we wait for it */
1020 waiter->cw_Flag = -1;
1021 line->cl_Pid = -2;
1022 break;
1028 waiter = waiter->cw_Next;
1030 if (line->cl_Pid == -1) {
1031 /* job is ready to run */
1032 file->cf_Ready = 1;
1033 if (DebugOpt)
1034 logn(LOG_DEBUG, "scheduled: user %s %s\n",
1035 file->cf_UserName,
1036 line->cl_Description
1038 return 1;
1039 } else if (DebugOpt)
1040 logn(LOG_DEBUG, "waiting: user %s %s\n",
1041 file->cf_UserName,
1042 line->cl_Description
1045 return 0;
1049 TestStartupJobs(void)
1051 short nJobs = 0;
1052 time_t t1 = time(NULL);
1053 CronFile *file;
1054 CronLine *line;
1056 t1 = t1 - t1 % 60 + 60;
1058 for (file = FileBase; file; file = file->cf_Next) {
1059 if (DebugOpt)
1060 logn(LOG_DEBUG, "TestStartup for FILE %s/%s USER %s:\n",
1061 file->cf_DPath, file->cf_FileName, file->cf_UserName);
1062 for (line = file->cf_LineBase; line; line = line->cl_Next) {
1063 struct CronWaiter *waiter;
1064 if (DebugOpt) {
1065 if (line->cl_JobName)
1066 logn(LOG_DEBUG, " LINE %s JOB %s\n", line->cl_Shell, line->cl_JobName);
1067 else
1068 logn(LOG_DEBUG, " LINE %s\n", line->cl_Shell);
1071 if (line->cl_Freq == -1) {
1072 /* freq is @reboot */
1074 line->cl_Pid = -1;
1075 /* if we have any waiters, reset them and arm Pid = -2 */
1076 waiter = line->cl_Waiters;
1077 while (waiter != NULL) {
1078 waiter->cw_Flag = -1;
1079 line->cl_Pid = -2;
1080 /* we only arm @noauto jobs we're waiting on, not other @reboot jobs */
1081 if (waiter->cw_NotifLine && waiter->cw_NotifLine->cl_Freq == -2)
1082 ArmJob(file, waiter->cw_NotifLine, t1, t1+60);
1083 waiter = waiter->cw_Next;
1085 if (line->cl_Pid == -1) {
1086 /* job is ready to run */
1087 file->cf_Ready = 1;
1088 ++nJobs;
1089 if (DebugOpt)
1090 logn(LOG_DEBUG, " scheduled: %s\n", line->cl_Description);
1091 } else if (DebugOpt)
1092 logn(LOG_DEBUG, " waiting: %s\n", line->cl_Description);
1096 } /* for line */
1098 return(nJobs);
1101 void
1102 RunJobs(void)
1104 CronFile *file;
1105 CronLine *line;
1107 for (file = FileBase; file; file = file->cf_Next) {
1108 if (file->cf_Ready) {
1109 file->cf_Ready = 0;
1111 for (line = file->cf_LineBase; line; line = line->cl_Next) {
1112 if (line->cl_Pid == -1) {
1114 RunJob(file, line);
1116 logn(LOG_INFO, "FILE %s/%s USER %s PID %3d %s\n",
1117 file->cf_DPath,
1118 file->cf_FileName,
1119 file->cf_UserName,
1120 line->cl_Pid,
1121 line->cl_Description
1123 if (line->cl_Pid < 0)
1124 /* how could this happen? RunJob will leave cl_Pid set to 0 or the actual pid */
1125 file->cf_Ready = 1;
1126 else if (line->cl_Pid > 0)
1127 file->cf_Running = 1;
1135 * CheckJobs() - check for job completion
1137 * Check for job completion, return number of CronFiles still running after
1138 * all done.
1142 CheckJobs(void)
1144 CronFile *file;
1145 CronLine *line;
1146 int nStillRunning = 0;
1148 for (file = FileBase; file; file = file->cf_Next) {
1149 if (file->cf_Running) {
1150 file->cf_Running = 0;
1152 for (line = file->cf_LineBase; line; line = line->cl_Next) {
1153 if (line->cl_Pid > 0) {
1154 int status;
1155 int r = waitpid(line->cl_Pid, &status, WNOHANG);
1157 if (r < 0 || r == line->cl_Pid) {
1158 if (r > 0 && WIFEXITED(status))
1159 status = WEXITSTATUS(status);
1160 else
1161 status = 1;
1162 EndJob(file, line, status);
1163 if (line->cl_Pid)
1164 file->cf_Running = 1;
1165 } else if (r == 0) {
1166 file->cf_Running = 1;
1171 nStillRunning += file->cf_Running;
1173 return(nStillRunning);