ArmJob: fixed comparison btw MaxWait and NotUntil
[yacron.git] / database.c
blob0b7050db0143730f71d2e22a942624c7d88cecfc
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 int 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(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 int
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 if (DebugOpt)
102 logn(LOG_DEBUG, "CheckUpdates on %s/%s\n", dpath, CRONUPDATE);
104 asprintf(&path, "%s/%s", dpath, CRONUPDATE);
105 if ((fi = fopen(path, "r")) != NULL) {
106 remove(path);
107 while (fgets(buf, sizeof(buf), fi) != NULL) {
109 * if buf has only sep chars, return NULL and point ptok at buf's terminating 0
110 * else return pointer to first non-sep of buf and
111 * if there's a following sep, overwrite it to 0 and point ptok to next char
112 * else point ptok at buf's terminating 0
114 fname = strtok_r(buf, " \t\n", &ptok);
116 if (user_override)
117 SynchronizeFile(dpath, fname, user_override);
118 else if (!getpwnam(fname))
119 logn(LOG_WARNING, "ignoring %s/%s (non-existent user)\n", dpath, fname);
120 else if (*ptok == 0 || *ptok == '\n')
121 SynchronizeFile(dpath, fname, fname);
122 else {
123 /* if fname is followed by whitespace, we prod any following jobs */
124 CronFile *file = FileBase;
125 while (file) {
126 if (strcmp(file->cf_UserName, fname) == 0)
127 break;
128 file = file->cf_Next;
130 if (!file)
131 logn(LOG_WARNING, "unable to prod for user %s: no crontab\n", fname);
132 else {
133 CronLine *line;
134 /* calling strtok(ptok...) then strtok(NULL) is equiv to calling strtok_r(NULL,..&ptok) */
135 while ((job = strtok(ptok, " \t\n")) != NULL) {
136 time_t force = t2;
137 ptok = NULL;
138 if (*job == '!') {
139 force = (time_t)-1;
140 ++job;
142 line = file->cf_LineBase;
143 while (line) {
144 if (line->cl_JobName && strcmp(line->cl_JobName, job) == 0)
145 break;
146 line = line->cl_Next;
148 if (line)
149 ArmJob(file, line, t1, force);
150 else {
151 logn(LOG_WARNING, "unable to prod for user %s: unknown job %s\n", fname, job);
152 /* we can continue parsing this line, we just don't install any CronWaiter for the requested job */
158 fclose(fi);
160 free(path);
161 return (fi != NULL);
164 void
165 SynchronizeDir(const char *dpath, const char *user_override, int initial_scan)
167 CronFile **pfile;
168 CronFile *file;
169 struct dirent *den;
170 DIR *dir;
171 char *path;
174 * Delete all database CronFiles for this directory. DeleteFile() will
175 * free *pfile and relink the *pfile pointer, or in the alternative will
176 * mark it as deleted.
178 pfile = &FileBase;
179 while ((file = *pfile) != NULL) {
180 if (file->cf_Deleted == 0 && strcmp(file->cf_DPath, dpath) == 0) {
181 DeleteFile(pfile);
182 } else {
183 pfile = &file->cf_Next;
188 * Since we are resynchronizing the entire directory, remove the
189 * the CRONUPDATE file.
191 asprintf(&path, "%s/%s", dpath, CRONUPDATE);
192 remove(path);
193 free(path);
196 * Scan the specified directory
198 if ((dir = opendir(dpath)) != NULL) {
199 while ((den = readdir(dir)) != NULL) {
200 if (strchr(den->d_name, '.') != NULL)
201 continue;
202 if (strcmp(den->d_name, CRONUPDATE) == 0)
203 continue;
204 if (user_override) {
205 SynchronizeFile(dpath, den->d_name, user_override);
206 } else if (getpwnam(den->d_name)) {
207 SynchronizeFile(dpath, den->d_name, den->d_name);
208 } else {
209 logn(LOG_WARNING, "ignoring %s/%s (non-existent user)\n",
210 dpath, den->d_name);
213 closedir(dir);
214 } else {
215 if (initial_scan)
216 logn(LOG_ERR, "unable to scan directory %s\n", dpath);
217 /* softerror, do not exit the program */
222 void
223 ReadTimestamps(int initial_scan)
225 CronFile *file;
226 CronLine *line;
227 FILE *fi;
228 char buf[256];
229 char *ptr;
230 struct tm tm;
231 time_t sec, freq;
233 file = FileBase;
234 while (file != NULL) {
235 if (file->cf_Deleted == 0) {
236 line = file->cf_LineBase;
237 while (line != NULL) {
238 if (line->cl_Timestamp) {
239 if ((fi = fopen(line->cl_Timestamp, "r")) != NULL) {
240 if (fgets(buf, sizeof(buf), fi) != NULL) {
241 sec = (time_t)-1;
242 ptr = strptime(buf, TIMESTAMP_FMT, &tm);
243 if (ptr && (*ptr == 0 || *ptr == '\n'))
244 sec = mktime(&tm);
245 if (sec == (time_t)-1) {
246 logn(LOG_WARNING, "unable to parse timestamp (user %s job %s)\n", file->cf_UserName, line->cl_JobName);
247 /* we continue checking other timestamps in this CronFile */
248 } else {
249 line->cl_LastRan = sec;
250 freq = (line->cl_Freq > 0) ? line->cl_Freq : line->cl_Delay;
251 if (line->cl_NotUntil < line->cl_LastRan + freq)
252 line->cl_NotUntil = line->cl_LastRan + freq;
255 fclose(fi);
256 } else if (initial_scan) {
257 logn(LOG_NOTICE, "no timestamp found (user %s job %s)\n", file->cf_UserName, line->cl_JobName);
258 /* softerror, do not exit the program */
261 line = line->cl_Next;
264 file = file->cf_Next;
268 void
269 SynchronizeFile(const char *dpath, const char *fileName, const char *userName)
271 CronFile **pfile;
272 CronFile *file;
273 int maxEntries;
274 int maxLines;
275 char buf[1024];
276 char *path;
277 FILE *fi;
280 * Limit entries
282 if (strcmp(userName, "root") == 0)
283 maxEntries = 65535;
284 else
285 maxEntries = MAXLINES;
286 maxLines = maxEntries * 10;
289 * Delete any existing copy of this CronFile
291 pfile = &FileBase;
292 while ((file = *pfile) != NULL) {
293 if (file->cf_Deleted == 0 && strcmp(file->cf_DPath, dpath) == 0 &&
294 strcmp(file->cf_FileName, fileName) == 0
296 DeleteFile(pfile);
297 } else {
298 pfile = &file->cf_Next;
302 asprintf(&path, "%s/%s", dpath, fileName);
303 if ((fi = fopen(path, "r")) != NULL) {
304 struct stat sbuf;
306 if (fstat(fileno(fi), &sbuf) == 0 && sbuf.st_uid == DaemonUid) {
307 CronFile *file = calloc(1, sizeof(CronFile));
308 CronLine **pline;
310 file->cf_UserName = strdup(userName);
311 file->cf_FileName = strdup(fileName);
312 file->cf_DPath = strdup(dpath);
313 pline = &file->cf_LineBase;
315 while (fgets(buf, sizeof(buf), fi) != NULL && --maxLines) {
316 CronLine line;
317 char *ptr = buf;
318 int len;
320 while (*ptr == ' ' || *ptr == '\t' || *ptr == '\n')
321 ++ptr;
323 len = strlen(ptr);
324 if (len && ptr[len-1] == '\n')
325 ptr[--len] = 0;
327 if (*ptr == 0 || *ptr == '#')
328 continue;
330 if (--maxEntries == 0)
331 break;
333 bzero(&line, sizeof(line));
335 if (DebugOpt)
336 logn(LOG_DEBUG, "User %s Entry %s\n", userName, buf);
338 if (*ptr == '@') {
340 * parse @hourly, etc
342 int j;
343 line.cl_Delay = -1;
344 ptr += 1;
345 for (j = 0; FreqAry[j]; ++j) {
346 if (strncmp(ptr, FreqAry[j], strlen(FreqAry[j])) == 0) {
347 break;
350 if (FreqAry[j]) {
351 ptr += strlen(FreqAry[j]);
352 switch(j) {
353 case 0:
354 /* noauto */
355 line.cl_Freq = -2;
356 line.cl_Delay = 0;
357 break;
358 case 1:
359 /* reboot */
360 line.cl_Freq = -1;
361 line.cl_Delay = 0;
362 break;
363 case 2:
364 line.cl_Freq = HOURLY_FREQ;
365 break;
366 case 3:
367 line.cl_Freq = DAILY_FREQ;
368 break;
369 case 4:
370 line.cl_Freq = WEEKLY_FREQ;
371 break;
372 case 5:
373 line.cl_Freq = MONTHLY_FREQ;
374 case 6:
375 line.cl_Freq = YEARLY_FREQ;
376 /* else line.cl_Freq will remain 0 */
380 if (!line.cl_Freq || (*ptr != ' ' && *ptr != '\t')) {
381 logn(LOG_WARNING, "failed parsing crontab for user %s: %s\n", userName, buf);
382 continue;
385 if (line.cl_Delay < 0) {
386 line.cl_Delay = line.cl_Freq / 20;
387 /* all minutes are permitted */
388 for (j=0; j<60; ++j)
389 line.cl_Mins[j] = 1;
390 for (j=0; j<24; ++j)
391 line.cl_Hrs[j] = 1;
392 for (j=1; j<32; ++j)
393 /* days are numbered 1..31 */
394 line.cl_Days[j] = 1;
395 for (j=0; j<12; ++j)
396 line.cl_Mons[j] = 1;
399 while (*ptr == ' ' || *ptr == '\t')
400 ++ptr;
402 } else {
404 * parse date ranges
407 ptr = ParseField(file->cf_UserName, line.cl_Mins, 60, 0, 1,
408 NULL, ptr);
409 ptr = ParseField(file->cf_UserName, line.cl_Hrs, 24, 0, 1,
410 NULL, ptr);
411 ptr = ParseField(file->cf_UserName, line.cl_Days, 32, 0, 1,
412 NULL, ptr);
413 ptr = ParseField(file->cf_UserName, line.cl_Mons, 12, -1, 1,
414 MonAry, ptr);
415 ptr = ParseField(file->cf_UserName, line.cl_Dow, 7, 0, 31,
416 DowAry, ptr);
418 * check failure
421 if (ptr == NULL)
422 continue;
425 * fix days and dow - if one is not * and the other
426 * is *, the other is set to 0, and vise-versa
429 FixDayDow(&line);
432 /* check for ID=... and AFTER=... and FREQ=... */
433 do {
434 if (strncmp(ptr, ID_TAG, strlen(ID_TAG)) == 0) {
435 if (line.cl_JobName) {
436 /* only assign ID_TAG once */
437 logn(LOG_WARNING, "failed parsing crontab for user %s: repeated %s\n", userName, ptr);
438 ptr = NULL;
439 } else {
440 ptr += strlen(ID_TAG);
442 * name = strsep(&ptr, seps):
443 * return name = ptr, and if ptr contains sep chars, overwrite first with 0 and point ptr to next char
444 * else set ptr=NULL
446 asprintf(&line.cl_Description, "job %s", strsep(&ptr, " \t"));
447 line.cl_JobName = line.cl_Description + 4;
448 if (!ptr)
449 logn(LOG_WARNING, "failed parsing crontab for user %s: no command after %s%s\n", userName, ID_TAG, line.cl_JobName);
451 } else if (strncmp(ptr, FREQ_TAG, strlen(FREQ_TAG)) == 0) {
452 if (line.cl_Freq) {
453 /* only assign FREQ_TAG once */
454 logn(LOG_WARNING, "failed parsing crontab for user %s: repeated %s\n", userName, ptr);
455 ptr = NULL;
456 } else {
457 char *base = ptr;
458 ptr += strlen(FREQ_TAG);
459 ptr = ParseInterval(&line.cl_Freq, ptr);
460 if (ptr && *ptr == '/')
461 ptr = ParseInterval(&line.cl_Delay, ++ptr);
462 else
463 line.cl_Delay = line.cl_Freq;
464 if (!ptr) {
465 logn(LOG_WARNING, "failed parsing crontab for user %s: %s\n", userName, base);
466 } else if (*ptr != ' ' && *ptr != '\t') {
467 logn(LOG_WARNING, "failed parsing crontab for user %s: no command after %s\n", userName, base);
468 ptr = NULL;
471 } else if (strncmp(ptr, WAIT_TAG, strlen(WAIT_TAG)) == 0) {
472 if (line.cl_Waiters) {
473 /* only assign WAIT_TAG once */
474 logn(LOG_WARNING, "failed parsing crontab for user %s: repeated %s\n", userName, ptr);
475 ptr = NULL;
476 } else {
477 short more = 1;
478 char *name;
479 ptr += strlen(WAIT_TAG);
480 do {
481 CronLine *job, **pjob;
482 if (strcspn(ptr,",") < strcspn(ptr," \t"))
483 name = strsep(&ptr, ",");
484 else {
485 more = 0;
486 name = strsep(&ptr, " \t");
488 if (!ptr || *ptr == 0) {
489 /* unexpectedly this was the last token in buf; so abort */
490 logn(LOG_WARNING, "failed parsing crontab for user %s: no command after %s%s\n", userName, WAIT_TAG, name);
491 ptr = NULL;
492 } else {
493 int waitfor = 0;
494 char *w, *wsave;
495 if ((w = index(name, '/')) != NULL) {
496 wsave = w++;
497 w = ParseInterval(&waitfor, w);
498 if (!w || *w != 0) {
499 logn(LOG_WARNING, "failed parsing crontab for user %s: %s%s\n", userName, WAIT_TAG, name);
500 ptr = NULL;
501 } else
502 /* truncate name */
503 *wsave = 0;
505 if (ptr) {
506 /* look for a matching CronLine */
507 pjob = &file->cf_LineBase;
508 while ((job = *pjob) != NULL) {
509 if (job->cl_JobName && strcmp(job->cl_JobName, name) == 0) {
510 CronWaiter *waiter = malloc(sizeof(CronWaiter));
511 CronNotifier *notif = malloc(sizeof(CronNotifier));
512 waiter->cw_Flag = -1;
513 waiter->cw_MaxWait = waitfor;
514 waiter->cw_NotifLine = job;
515 waiter->cw_Notifier = notif;
516 waiter->cw_Next = line.cl_Waiters; /* add to head of line.cl_Waiters */
517 line.cl_Waiters = waiter;
518 notif->cn_Waiter = waiter;
519 notif->cn_Next = job->cl_Notifs; /* add to head of job->cl_Notifs */
520 job->cl_Notifs = notif;
521 break;
522 } else
523 pjob = &job->cl_Next;
525 if (!job) {
526 logn(LOG_WARNING, "failed parsing crontab for user %s: unknown job %s\n", userName, name);
527 /* we can continue parsing this line, we just don't install any CronWaiter for the requested job */
531 } while (ptr && more);
533 } else
534 break;
535 if (!ptr)
536 break;
537 while (*ptr == ' ' || *ptr == '\t')
538 ++ptr;
539 } while (!line.cl_JobName || !line.cl_Waiters || !line.cl_Freq);
541 if (line.cl_JobName && (!ptr || *line.cl_JobName == 0)) {
542 /* we're aborting, or ID= was empty */
543 free(line.cl_Description);
544 line.cl_Description = NULL;
545 line.cl_JobName = NULL;
547 if (ptr && line.cl_Delay > 0 && !line.cl_JobName) {
548 logn(LOG_WARNING, "failed parsing crontab for user %s: writing timestamp requires job %s to be named\n", userName, ptr);
549 ptr = NULL;
551 if (!ptr) {
552 /* couldn't parse so we abort; free any cl_Waiters */
553 if (line.cl_Waiters) {
554 CronWaiter **pwaiters, *waiters;
555 pwaiters = &line.cl_Waiters;
556 while ((waiters = *pwaiters) != NULL) {
557 *pwaiters = waiters->cw_Next;
558 /* leave the Notifier allocated but disabled */
559 waiters->cw_Notifier->cn_Waiter = NULL;
560 free(waiters);
563 continue;
565 /* now we've added any ID=... or AFTER=... */
568 * copy command string
570 line.cl_Shell = strdup(ptr);
572 if (line.cl_Delay > 0) {
573 asprintf(&line.cl_Timestamp, "%s/%s.%s", TSDir, userName, line.cl_JobName);
574 line.cl_NotUntil = time(NULL) + line.cl_Delay;
577 if (line.cl_JobName) {
578 if (DebugOpt)
579 logn(LOG_DEBUG, " Command %s Job %s\n", line.cl_Shell, line.cl_JobName);
580 } else {
581 /* when cl_JobName is NULL, we point cl_Description to cl_Shell */
582 line.cl_Description = line.cl_Shell;
583 if (DebugOpt)
584 logn(LOG_DEBUG, " Command %s\n", line.cl_Shell);
587 *pline = calloc(1, sizeof(CronLine));
588 /* copy working CronLine to newly allocated one */
589 **pline = line;
591 pline = &((*pline)->cl_Next);
594 *pline = NULL;
596 file->cf_Next = FileBase;
597 FileBase = file;
599 if (maxLines == 0 || maxEntries == 0)
600 logn(LOG_WARNING, "maximum number of lines reached for user %s\n", userName);
602 fclose(fi);
604 free(path);
607 char *
608 ParseInterval(int *interval, char *ptr)
610 int n = 0;
611 if (ptr && *ptr >= '0' && *ptr <= '9' && (n = strtol(ptr, &ptr, 10)) > 0)
612 switch (*ptr) {
613 case 'm':
614 n *= 60;
615 break;
616 case 'h':
617 n *= HOURLY_FREQ;
618 break;
619 case 'd':
620 n *= DAILY_FREQ;
621 break;
622 case 'w':
623 n *= WEEKLY_FREQ;
624 break;
625 default:
626 n = 0;
628 if (n > 0) {
629 *interval = n;
630 return (ptr+1);
631 } else
632 return (NULL);
635 char *
636 ParseField(char *user, char *ary, int modvalue, int off, int onvalue, const char **names, char *ptr)
638 char *base = ptr;
639 int n1 = -1;
640 int n2 = -1;
642 if (base == NULL)
643 return (NULL);
645 while (*ptr != ' ' && *ptr != '\t' && *ptr != '\n') {
646 int skip = 0;
649 * Handle numeric digit or symbol or '*'
652 if (*ptr == '*') {
653 n1 = 0; /* everything will be filled */
654 n2 = modvalue - 1;
655 skip = 1;
656 ++ptr;
657 } else if (*ptr >= '0' && *ptr <= '9') {
658 if (n1 < 0)
659 n1 = strtol(ptr, &ptr, 10) + off;
660 else
661 n2 = strtol(ptr, &ptr, 10) + off;
662 skip = 1;
663 } else if (names) {
664 int i;
666 for (i = 0; names[i]; ++i) {
667 if (strncmp(ptr, names[i], strlen(names[i])) == 0) {
668 break;
671 if (names[i]) {
672 ptr += strlen(names[i]);
673 if (n1 < 0)
674 n1 = i;
675 else
676 n2 = i;
677 skip = 1;
682 * handle optional range '-'
685 if (skip == 0) {
686 logn(LOG_WARNING, "failed parsing crontab for user %s: %s\n", user, base);
687 return(NULL);
689 if (*ptr == '-' && n2 < 0) {
690 ++ptr;
691 continue;
695 * collapse single-value ranges, handle skipmark, and fill
696 * in the character array appropriately.
699 if (n2 < 0)
700 n2 = n1;
702 if (*ptr == '/')
703 skip = strtol(ptr + 1, &ptr, 10);
706 * fill array, using a failsafe is the easiest way to prevent
707 * an endless loop
711 int s0 = 1;
712 int failsafe = 1024;
714 --n1;
715 do {
716 n1 = (n1 + 1) % modvalue;
718 if (--s0 == 0) {
719 ary[n1] = onvalue;
720 s0 = skip;
722 } while (n1 != n2 && --failsafe);
724 if (failsafe == 0) {
725 logn(LOG_WARNING, "failed parsing crontab for user %s: %s\n", user, base);
726 return(NULL);
729 if (*ptr != ',')
730 break;
731 ++ptr;
732 n1 = -1;
733 n2 = -1;
736 if (*ptr != ' ' && *ptr != '\t' && *ptr != '\n') {
737 logn(LOG_WARNING, "failed parsing crontab for user %s: %s\n", user, base);
738 return(NULL);
741 while (*ptr == ' ' || *ptr == '\t' || *ptr == '\n')
742 ++ptr;
744 if (DebugOpt) {
745 int i;
747 for (i = 0; i < modvalue; ++i)
748 logn(LOG_DEBUG, "%2x ", ary[i]);
749 logn(LOG_DEBUG, "\n");
752 return(ptr);
755 void
756 FixDayDow(CronLine *line)
758 unsigned short i,j;
759 short weekUsed = 0;
760 short daysUsed = 0;
762 for (i = 0; i < arysize(line->cl_Dow); ++i) {
763 if (line->cl_Dow[i] == 0) {
764 weekUsed = 1;
765 break;
768 for (i = 0; i < arysize(line->cl_Days); ++i) {
769 if (line->cl_Days[i] == 0) {
770 if (weekUsed) {
771 if (!daysUsed) {
772 daysUsed = 1;
773 /* change from "every Mon" to "ith Mon"
774 * 6th,7th... Dow are treated as 1st,2nd... */
775 for (j = 0; j < arysize(line->cl_Dow); ++j) {
776 line->cl_Dow[j] &= 1 << (i-1)%5;
778 } else {
779 /* change from "nth Mon" to "nth or ith Mon" */
780 for (j = 0; j < arysize(line->cl_Dow); ++j) {
781 if (line->cl_Dow[j])
782 line->cl_Dow[j] |= 1 << (i-1)%5;
785 /* continue cycling through cl_Days */
787 else {
788 daysUsed = 1;
789 break;
793 if (weekUsed) {
794 memset(line->cl_Days, 0, sizeof(line->cl_Days));
796 if (daysUsed && !weekUsed) {
797 memset(line->cl_Dow, 0, sizeof(line->cl_Dow));
802 * DeleteFile() - destroy a CronFile.
804 * The CronFile (*pfile) is destroyed if possible, and marked cf_Deleted
805 * if there are still active processes running on it. *pfile is relinked
806 * on success.
808 void
809 DeleteFile(CronFile **pfile)
811 CronFile *file = *pfile;
812 CronLine **pline = &file->cf_LineBase;
813 CronLine *line;
814 CronWaiter **pwaiters, *waiters;
815 CronNotifier **pnotifs, *notifs;
817 file->cf_Running = 0;
818 file->cf_Deleted = 1;
820 while ((line = *pline) != NULL) {
821 if (line->cl_Pid > 0) {
822 file->cf_Running = 1;
823 pline = &line->cl_Next;
824 } else {
825 *pline = line->cl_Next;
826 free(line->cl_Shell);
828 if (line->cl_JobName)
829 /* this frees both cl_Description and cl_JobName
830 * if cl_JobName is NULL, Description pointed to ch_Shell, which was already freed
832 free(line->cl_Description);
833 if (line->cl_Timestamp)
834 free(line->cl_Timestamp);
836 pnotifs = &line->cl_Notifs;
837 while ((notifs = *pnotifs) != NULL) {
838 *pnotifs = notifs->cn_Next;
839 if (notifs->cn_Waiter) {
840 notifs->cn_Waiter->cw_NotifLine = NULL;
841 notifs->cn_Waiter->cw_Notifier = NULL;
843 free(notifs);
845 pwaiters = &line->cl_Waiters;
846 while ((waiters = *pwaiters) != NULL) {
847 *pwaiters = waiters->cw_Next;
848 if (waiters->cw_Notifier)
849 waiters->cw_Notifier->cn_Waiter = NULL;
850 free(waiters);
853 free(line);
856 if (file->cf_Running == 0) {
857 *pfile = file->cf_Next;
858 free(file->cf_DPath);
859 free(file->cf_FileName);
860 free(file->cf_UserName);
861 free(file);
867 * TestJobs()
869 * determine which jobs need to be run. Under normal conditions, the
870 * period is about a minute (one scan). Worst case it will be one
871 * hour (60 scans).
875 TestJobs(time_t t1, time_t t2)
877 short nJobs = 0;
878 time_t t;
879 CronFile *file;
880 CronLine *line;
882 for (file = FileBase; file; file = file->cf_Next) {
883 if (file->cf_Deleted)
884 continue;
885 for (line = file->cf_LineBase; line; line = line->cl_Next) {
886 struct CronWaiter *waiter;
888 if (line->cl_Pid == -2) {
889 /* can job stop waiting? */
890 int ready = 1;
891 waiter = line->cl_Waiters;
892 while (waiter != NULL) {
893 if (waiter->cw_Flag > 0) {
894 /* notifier exited unsuccessfully */
895 ready = 2;
896 break;
897 } else if (waiter->cw_Flag < 0)
898 /* still waiting, notifier hasn't run to completion */
899 ready = 0;
900 waiter = waiter->cw_Next;
902 if (ready == 2) {
903 if (DebugOpt)
904 logn(LOG_DEBUG, "cancelled waiting: user %s %s\n", file->cf_UserName, line->cl_Description);
905 line->cl_Pid = 0;
906 } else if (ready) {
907 if (DebugOpt)
908 logn(LOG_DEBUG, "finished waiting: user %s %s\n", file->cf_UserName, line->cl_Description);
909 nJobs += ArmJob(file, line, 0, -1);
911 if (line->cl_NotUntil)
912 line->cl_NotUntil = t2;
920 * Find jobs > t1 and <= t2
923 for (t = t1 - t1 % 60; t <= t2; t += 60) {
924 if (t > t1) {
925 struct tm *tp = localtime(&t);
927 unsigned short n_wday = (tp->tm_mday - 1)%7 + 1;
928 if (n_wday >= 4) {
929 struct tm tnext = *tp;
930 tnext.tm_mday += 7;
931 if (mktime(&tnext) != (time_t)-1 && tnext.tm_mon != tp->tm_mon)
932 n_wday |= 16; /* last dow in month is always recognized as 5th */
935 for (file = FileBase; file; file = file->cf_Next) {
936 if (file->cf_Deleted)
937 continue;
938 for (line = file->cf_LineBase; line; line = line->cl_Next) {
939 if (line->cl_Pid != -1 && (line->cl_Freq == 0 || (line->cl_Freq > 0 && t2 >= line->cl_NotUntil))) {
940 /* (re)schedule job? */
941 if (line->cl_Mins[tp->tm_min] &&
942 line->cl_Hrs[tp->tm_hour] &&
943 (line->cl_Days[tp->tm_mday] || (n_wday && line->cl_Dow[tp->tm_wday]) ) &&
944 line->cl_Mons[tp->tm_mon]
946 if (line->cl_NotUntil)
947 line->cl_NotUntil = t2 - t2 % 60; /* save what minute this job was scheduled/started waiting */
948 nJobs += ArmJob(file, line, t1, t2);
955 return(nJobs);
959 * ArmJob: if t2 is (time_t)-1, we force-schedule the job without any waiting
960 * else it will wait on any of its declared notifiers who will run <= t2 + cw_MaxWait
964 ArmJob(CronFile *file, CronLine *line, time_t t1, time_t t2)
966 struct CronWaiter *waiter;
967 if (line->cl_Pid > 0) {
968 logn(LOG_NOTICE, "process already running (%d): user %s %s\n",
969 line->cl_Pid,
970 file->cf_UserName,
971 line->cl_Description
973 } else if (t2 == -1 && line->cl_Pid != -1) {
974 line->cl_Pid = -1;
975 file->cf_Ready = 1;
976 return 1;
977 } else if (line->cl_Pid == 0) {
978 /* arming a waiting job (cl_Pid == -2) without forcing has no effect */
979 line->cl_Pid = -1;
980 /* if we have any waiters, zero them and arm cl_Pid=-2 */
981 waiter = line->cl_Waiters;
982 while (waiter != NULL) {
983 /* check if notifier will run <= t2 + cw_Max_Wait? */
984 if (!waiter->cw_NotifLine)
985 /* notifier deleted */
986 waiter->cw_Flag = 0;
987 else if (waiter->cw_NotifLine->cl_Pid != 0) {
988 /* if notifier is armed, or waiting, or running, we wait for it */
989 waiter->cw_Flag = -1;
990 line->cl_Pid = -2;
991 } else if (waiter->cw_NotifLine->cl_Freq < 0) {
992 /* arm any @noauto or @reboot jobs we're waiting on */
993 ArmJob(file, waiter->cw_NotifLine, t1, t2);
994 waiter->cw_Flag = -1;
995 line->cl_Pid = -2;
996 } else {
997 time_t t;
998 if (waiter->cw_MaxWait == 0)
999 /* when no MaxWait interval specified, we always wait */
1000 waiter->cw_Flag = -1;
1001 else if (waiter->cw_NotifLine->cl_Freq == 0 || (waiter->cw_NotifLine->cl_Freq > 0 && t2 + waiter->cw_MaxWait >= waiter->cw_NotifLine->cl_NotUntil)) {
1002 /* default is don't wait */
1003 waiter->cw_Flag = 0;
1004 for (t = t1 - t1 % 60; t <= t2; t += 60) {
1005 if (t > t1) {
1006 struct tm *tp = localtime(&t);
1008 unsigned short n_wday = (tp->tm_mday - 1)%7 + 1;
1009 if (n_wday >= 4) {
1010 struct tm tnext = *tp;
1011 tnext.tm_mday += 7;
1012 if (mktime(&tnext) != (time_t)-1 && tnext.tm_mon != tp->tm_mon)
1013 n_wday |= 16; /* last dow in month is always recognized as 5th */
1015 if (line->cl_Mins[tp->tm_min] &&
1016 line->cl_Hrs[tp->tm_hour] &&
1017 (line->cl_Days[tp->tm_mday] || (n_wday && line->cl_Dow[tp->tm_wday]) ) &&
1018 line->cl_Mons[tp->tm_mon]
1020 /* notifier will run soon enough, we wait for it */
1021 waiter->cw_Flag = -1;
1022 line->cl_Pid = -2;
1023 break;
1029 waiter = waiter->cw_Next;
1031 if (line->cl_Pid == -1) {
1032 /* job is ready to run */
1033 file->cf_Ready = 1;
1034 if (DebugOpt)
1035 logn(LOG_DEBUG, "scheduled: user %s %s\n",
1036 file->cf_UserName,
1037 line->cl_Description
1039 return 1;
1040 } else if (DebugOpt)
1041 logn(LOG_DEBUG, "waiting: user %s %s\n",
1042 file->cf_UserName,
1043 line->cl_Description
1046 return 0;
1050 TestStartupJobs(void)
1052 short nJobs = 0;
1053 time_t t1 = time(NULL);
1054 CronFile *file;
1055 CronLine *line;
1057 t1 = t1 - t1 % 60 + 60;
1059 for (file = FileBase; file; file = file->cf_Next) {
1060 if (DebugOpt)
1061 logn(LOG_DEBUG, "TestStartup for FILE %s/%s USER %s:\n",
1062 file->cf_DPath, file->cf_FileName, file->cf_UserName);
1063 for (line = file->cf_LineBase; line; line = line->cl_Next) {
1064 struct CronWaiter *waiter;
1065 if (DebugOpt) {
1066 if (line->cl_JobName)
1067 logn(LOG_DEBUG, " LINE %s JOB %s\n", line->cl_Shell, line->cl_JobName);
1068 else
1069 logn(LOG_DEBUG, " LINE %s\n", line->cl_Shell);
1072 if (line->cl_Freq == -1) {
1073 /* freq is @reboot */
1075 line->cl_Pid = -1;
1076 /* if we have any waiters, reset them and arm Pid = -2 */
1077 waiter = line->cl_Waiters;
1078 while (waiter != NULL) {
1079 waiter->cw_Flag = -1;
1080 line->cl_Pid = -2;
1081 /* we only arm @noauto jobs we're waiting on, not other @reboot jobs */
1082 if (waiter->cw_NotifLine && waiter->cw_NotifLine->cl_Freq == -2)
1083 ArmJob(file, waiter->cw_NotifLine, t1, t1+60);
1084 waiter = waiter->cw_Next;
1086 if (line->cl_Pid == -1) {
1087 /* job is ready to run */
1088 file->cf_Ready = 1;
1089 ++nJobs;
1090 if (DebugOpt)
1091 logn(LOG_DEBUG, " scheduled: %s\n", line->cl_Description);
1092 } else if (DebugOpt)
1093 logn(LOG_DEBUG, " waiting: %s\n", line->cl_Description);
1097 } /* for line */
1099 return(nJobs);
1102 void
1103 RunJobs(void)
1105 CronFile *file;
1106 CronLine *line;
1108 for (file = FileBase; file; file = file->cf_Next) {
1109 if (file->cf_Ready) {
1110 file->cf_Ready = 0;
1112 for (line = file->cf_LineBase; line; line = line->cl_Next) {
1113 if (line->cl_Pid == -1) {
1115 RunJob(file, line);
1117 logn(LOG_INFO, "FILE %s/%s USER %s PID %3d %s\n",
1118 file->cf_DPath,
1119 file->cf_FileName,
1120 file->cf_UserName,
1121 line->cl_Pid,
1122 line->cl_Description
1124 if (line->cl_Pid < 0)
1125 /* how could this happen? RunJob will leave cl_Pid set to 0 or the actual pid */
1126 file->cf_Ready = 1;
1127 else if (line->cl_Pid > 0)
1128 file->cf_Running = 1;
1136 * CheckJobs() - check for job completion
1138 * Check for job completion, return number of CronFiles still running after
1139 * all done.
1143 CheckJobs(void)
1145 CronFile *file;
1146 CronLine *line;
1147 int nStillRunning = 0;
1149 for (file = FileBase; file; file = file->cf_Next) {
1150 if (file->cf_Running) {
1151 file->cf_Running = 0;
1153 for (line = file->cf_LineBase; line; line = line->cl_Next) {
1154 if (line->cl_Pid > 0) {
1155 int status;
1156 int r = waitpid(line->cl_Pid, &status, WNOHANG);
1158 if (r < 0 || r == line->cl_Pid) {
1159 if (r > 0 && WIFEXITED(status))
1160 status = WEXITSTATUS(status);
1161 else
1162 status = 1;
1163 EndJob(file, line, status);
1164 if (line->cl_Pid)
1165 file->cf_Running = 1;
1166 } else if (r == 0) {
1167 file->cf_Running = 1;
1172 nStillRunning += file->cf_Running;
1174 return(nStillRunning);