add comment explaining cl_Freq/20
[yacron.git] / database.c
blob773caa31212b301413aff16196ba24d35558cc32
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;
308 time_t tnow = time(NULL);
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) {
387 * delays on @daily, @hourly, etc are 1/20 of the frequency
388 * so they don't all start at once
389 * this also affects how they behave when the job returns EAGAIN
391 line.cl_Delay = line.cl_Freq / 20;
392 /* all minutes are permitted */
393 for (j=0; j<60; ++j)
394 line.cl_Mins[j] = 1;
395 for (j=0; j<24; ++j)
396 line.cl_Hrs[j] = 1;
397 for (j=1; j<32; ++j)
398 /* days are numbered 1..31 */
399 line.cl_Days[j] = 1;
400 for (j=0; j<12; ++j)
401 line.cl_Mons[j] = 1;
404 while (*ptr == ' ' || *ptr == '\t')
405 ++ptr;
407 } else {
409 * parse date ranges
412 ptr = ParseField(file->cf_UserName, line.cl_Mins, 60, 0, 1,
413 NULL, ptr);
414 ptr = ParseField(file->cf_UserName, line.cl_Hrs, 24, 0, 1,
415 NULL, ptr);
416 ptr = ParseField(file->cf_UserName, line.cl_Days, 32, 0, 1,
417 NULL, ptr);
418 ptr = ParseField(file->cf_UserName, line.cl_Mons, 12, -1, 1,
419 MonAry, ptr);
420 ptr = ParseField(file->cf_UserName, line.cl_Dow, 7, 0, 31,
421 DowAry, ptr);
423 * check failure
426 if (ptr == NULL)
427 continue;
430 * fix days and dow - if one is not * and the other
431 * is *, the other is set to 0, and vise-versa
434 FixDayDow(&line);
437 /* check for ID=... and AFTER=... and FREQ=... */
438 do {
439 if (strncmp(ptr, ID_TAG, strlen(ID_TAG)) == 0) {
440 if (line.cl_JobName) {
441 /* only assign ID_TAG once */
442 logn(LOG_WARNING, "failed parsing crontab for user %s: repeated %s\n", userName, ptr);
443 ptr = NULL;
444 } else {
445 ptr += strlen(ID_TAG);
447 * name = strsep(&ptr, seps):
448 * return name = ptr, and if ptr contains sep chars, overwrite first with 0 and point ptr to next char
449 * else set ptr=NULL
451 asprintf(&line.cl_Description, "job %s", strsep(&ptr, " \t"));
452 line.cl_JobName = line.cl_Description + 4;
453 if (!ptr)
454 logn(LOG_WARNING, "failed parsing crontab for user %s: no command after %s%s\n", userName, ID_TAG, line.cl_JobName);
456 } else if (strncmp(ptr, FREQ_TAG, strlen(FREQ_TAG)) == 0) {
457 if (line.cl_Freq) {
458 /* only assign FREQ_TAG once */
459 logn(LOG_WARNING, "failed parsing crontab for user %s: repeated %s\n", userName, ptr);
460 ptr = NULL;
461 } else {
462 char *base = ptr;
463 ptr += strlen(FREQ_TAG);
464 ptr = ParseInterval(&line.cl_Freq, ptr);
465 if (ptr && *ptr == '/')
466 ptr = ParseInterval(&line.cl_Delay, ++ptr);
467 else
468 line.cl_Delay = line.cl_Freq;
469 if (!ptr) {
470 logn(LOG_WARNING, "failed parsing crontab for user %s: %s\n", userName, base);
471 } else if (*ptr != ' ' && *ptr != '\t') {
472 logn(LOG_WARNING, "failed parsing crontab for user %s: no command after %s\n", userName, base);
473 ptr = NULL;
476 } else if (strncmp(ptr, WAIT_TAG, strlen(WAIT_TAG)) == 0) {
477 if (line.cl_Waiters) {
478 /* only assign WAIT_TAG once */
479 logn(LOG_WARNING, "failed parsing crontab for user %s: repeated %s\n", userName, ptr);
480 ptr = NULL;
481 } else {
482 short more = 1;
483 char *name;
484 ptr += strlen(WAIT_TAG);
485 do {
486 CronLine *job, **pjob;
487 if (strcspn(ptr,",") < strcspn(ptr," \t"))
488 name = strsep(&ptr, ",");
489 else {
490 more = 0;
491 name = strsep(&ptr, " \t");
493 if (!ptr || *ptr == 0) {
494 /* unexpectedly this was the last token in buf; so abort */
495 logn(LOG_WARNING, "failed parsing crontab for user %s: no command after %s%s\n", userName, WAIT_TAG, name);
496 ptr = NULL;
497 } else {
498 int waitfor = 0;
499 char *w, *wsave;
500 if ((w = index(name, '/')) != NULL) {
501 wsave = w++;
502 w = ParseInterval(&waitfor, w);
503 if (!w || *w != 0) {
504 logn(LOG_WARNING, "failed parsing crontab for user %s: %s%s\n", userName, WAIT_TAG, name);
505 ptr = NULL;
506 } else
507 /* truncate name */
508 *wsave = 0;
510 if (ptr) {
511 /* look for a matching CronLine */
512 pjob = &file->cf_LineBase;
513 while ((job = *pjob) != NULL) {
514 if (job->cl_JobName && strcmp(job->cl_JobName, name) == 0) {
515 CronWaiter *waiter = malloc(sizeof(CronWaiter));
516 CronNotifier *notif = malloc(sizeof(CronNotifier));
517 waiter->cw_Flag = -1;
518 waiter->cw_MaxWait = waitfor;
519 waiter->cw_NotifLine = job;
520 waiter->cw_Notifier = notif;
521 waiter->cw_Next = line.cl_Waiters; /* add to head of line.cl_Waiters */
522 line.cl_Waiters = waiter;
523 notif->cn_Waiter = waiter;
524 notif->cn_Next = job->cl_Notifs; /* add to head of job->cl_Notifs */
525 job->cl_Notifs = notif;
526 break;
527 } else
528 pjob = &job->cl_Next;
530 if (!job) {
531 logn(LOG_WARNING, "failed parsing crontab for user %s: unknown job %s\n", userName, name);
532 /* we can continue parsing this line, we just don't install any CronWaiter for the requested job */
536 } while (ptr && more);
538 } else
539 break;
540 if (!ptr)
541 break;
542 while (*ptr == ' ' || *ptr == '\t')
543 ++ptr;
544 } while (!line.cl_JobName || !line.cl_Waiters || !line.cl_Freq);
546 if (line.cl_JobName && (!ptr || *line.cl_JobName == 0)) {
547 /* we're aborting, or ID= was empty */
548 free(line.cl_Description);
549 line.cl_Description = NULL;
550 line.cl_JobName = NULL;
552 if (ptr && line.cl_Delay > 0 && !line.cl_JobName) {
553 logn(LOG_WARNING, "failed parsing crontab for user %s: writing timestamp requires job %s to be named\n", userName, ptr);
554 ptr = NULL;
556 if (!ptr) {
557 /* couldn't parse so we abort; free any cl_Waiters */
558 if (line.cl_Waiters) {
559 CronWaiter **pwaiters, *waiters;
560 pwaiters = &line.cl_Waiters;
561 while ((waiters = *pwaiters) != NULL) {
562 *pwaiters = waiters->cw_Next;
563 /* leave the Notifier allocated but disabled */
564 waiters->cw_Notifier->cn_Waiter = NULL;
565 free(waiters);
568 continue;
570 /* now we've added any ID=... or AFTER=... */
573 * copy command string
575 line.cl_Shell = strdup(ptr);
577 if (line.cl_Delay > 0) {
578 asprintf(&line.cl_Timestamp, "%s/%s.%s", TSDir, userName, line.cl_JobName);
579 line.cl_NotUntil = tnow + line.cl_Delay;
582 if (line.cl_JobName) {
583 if (DebugOpt)
584 logn(LOG_DEBUG, " Command %s Job %s\n", line.cl_Shell, line.cl_JobName);
585 } else {
586 /* when cl_JobName is NULL, we point cl_Description to cl_Shell */
587 line.cl_Description = line.cl_Shell;
588 if (DebugOpt)
589 logn(LOG_DEBUG, " Command %s\n", line.cl_Shell);
592 *pline = calloc(1, sizeof(CronLine));
593 /* copy working CronLine to newly allocated one */
594 **pline = line;
596 pline = &((*pline)->cl_Next);
599 *pline = NULL;
601 file->cf_Next = FileBase;
602 FileBase = file;
604 if (maxLines == 0 || maxEntries == 0)
605 logn(LOG_WARNING, "maximum number of lines reached for user %s\n", userName);
607 fclose(fi);
609 free(path);
612 char *
613 ParseInterval(int *interval, char *ptr)
615 int n = 0;
616 if (ptr && *ptr >= '0' && *ptr <= '9' && (n = strtol(ptr, &ptr, 10)) > 0)
617 switch (*ptr) {
618 case 'm':
619 n *= 60;
620 break;
621 case 'h':
622 n *= HOURLY_FREQ;
623 break;
624 case 'd':
625 n *= DAILY_FREQ;
626 break;
627 case 'w':
628 n *= WEEKLY_FREQ;
629 break;
630 default:
631 n = 0;
633 if (n > 0) {
634 *interval = n;
635 return (ptr+1);
636 } else
637 return (NULL);
640 char *
641 ParseField(char *user, char *ary, int modvalue, int off, int onvalue, const char **names, char *ptr)
643 char *base = ptr;
644 int n1 = -1;
645 int n2 = -1;
647 if (base == NULL)
648 return (NULL);
650 while (*ptr != ' ' && *ptr != '\t' && *ptr != '\n') {
651 int skip = 0;
654 * Handle numeric digit or symbol or '*'
657 if (*ptr == '*') {
658 n1 = 0; /* everything will be filled */
659 n2 = modvalue - 1;
660 skip = 1;
661 ++ptr;
662 } else if (*ptr >= '0' && *ptr <= '9') {
663 if (n1 < 0)
664 n1 = strtol(ptr, &ptr, 10) + off;
665 else
666 n2 = strtol(ptr, &ptr, 10) + off;
667 skip = 1;
668 } else if (names) {
669 int i;
671 for (i = 0; names[i]; ++i) {
672 if (strncmp(ptr, names[i], strlen(names[i])) == 0) {
673 break;
676 if (names[i]) {
677 ptr += strlen(names[i]);
678 if (n1 < 0)
679 n1 = i;
680 else
681 n2 = i;
682 skip = 1;
687 * handle optional range '-'
690 if (skip == 0) {
691 logn(LOG_WARNING, "failed parsing crontab for user %s: %s\n", user, base);
692 return(NULL);
694 if (*ptr == '-' && n2 < 0) {
695 ++ptr;
696 continue;
700 * collapse single-value ranges, handle skipmark, and fill
701 * in the character array appropriately.
704 if (n2 < 0)
705 n2 = n1;
707 if (*ptr == '/')
708 skip = strtol(ptr + 1, &ptr, 10);
711 * fill array, using a failsafe is the easiest way to prevent
712 * an endless loop
716 int s0 = 1;
717 int failsafe = 1024;
719 --n1;
720 do {
721 n1 = (n1 + 1) % modvalue;
723 if (--s0 == 0) {
724 ary[n1] = onvalue;
725 s0 = skip;
727 } while (n1 != n2 && --failsafe);
729 if (failsafe == 0) {
730 logn(LOG_WARNING, "failed parsing crontab for user %s: %s\n", user, base);
731 return(NULL);
734 if (*ptr != ',')
735 break;
736 ++ptr;
737 n1 = -1;
738 n2 = -1;
741 if (*ptr != ' ' && *ptr != '\t' && *ptr != '\n') {
742 logn(LOG_WARNING, "failed parsing crontab for user %s: %s\n", user, base);
743 return(NULL);
746 while (*ptr == ' ' || *ptr == '\t' || *ptr == '\n')
747 ++ptr;
749 if (DebugOpt) {
750 int i;
752 for (i = 0; i < modvalue; ++i)
753 logn(LOG_DEBUG, "%2x ", ary[i]);
754 logn(LOG_DEBUG, "\n");
757 return(ptr);
760 void
761 FixDayDow(CronLine *line)
763 unsigned short i,j;
764 short weekUsed = 0;
765 short daysUsed = 0;
767 for (i = 0; i < arysize(line->cl_Dow); ++i) {
768 if (line->cl_Dow[i] == 0) {
769 weekUsed = 1;
770 break;
773 for (i = 0; i < arysize(line->cl_Days); ++i) {
774 if (line->cl_Days[i] == 0) {
775 if (weekUsed) {
776 if (!daysUsed) {
777 daysUsed = 1;
778 /* change from "every Mon" to "ith Mon"
779 * 6th,7th... Dow are treated as 1st,2nd... */
780 for (j = 0; j < arysize(line->cl_Dow); ++j) {
781 line->cl_Dow[j] &= 1 << (i-1)%5;
783 } else {
784 /* change from "nth Mon" to "nth or ith Mon" */
785 for (j = 0; j < arysize(line->cl_Dow); ++j) {
786 if (line->cl_Dow[j])
787 line->cl_Dow[j] |= 1 << (i-1)%5;
790 /* continue cycling through cl_Days */
792 else {
793 daysUsed = 1;
794 break;
798 if (weekUsed) {
799 memset(line->cl_Days, 0, sizeof(line->cl_Days));
801 if (daysUsed && !weekUsed) {
802 memset(line->cl_Dow, 0, sizeof(line->cl_Dow));
807 * DeleteFile() - destroy a CronFile.
809 * The CronFile (*pfile) is destroyed if possible, and marked cf_Deleted
810 * if there are still active processes running on it. *pfile is relinked
811 * on success.
813 void
814 DeleteFile(CronFile **pfile)
816 CronFile *file = *pfile;
817 CronLine **pline = &file->cf_LineBase;
818 CronLine *line;
819 CronWaiter **pwaiters, *waiters;
820 CronNotifier **pnotifs, *notifs;
822 file->cf_Running = 0;
823 file->cf_Deleted = 1;
825 while ((line = *pline) != NULL) {
826 if (line->cl_Pid > 0) {
827 file->cf_Running = 1;
828 pline = &line->cl_Next;
829 } else {
830 *pline = line->cl_Next;
831 free(line->cl_Shell);
833 if (line->cl_JobName)
834 /* this frees both cl_Description and cl_JobName
835 * if cl_JobName is NULL, Description pointed to ch_Shell, which was already freed
837 free(line->cl_Description);
838 if (line->cl_Timestamp)
839 free(line->cl_Timestamp);
841 pnotifs = &line->cl_Notifs;
842 while ((notifs = *pnotifs) != NULL) {
843 *pnotifs = notifs->cn_Next;
844 if (notifs->cn_Waiter) {
845 notifs->cn_Waiter->cw_NotifLine = NULL;
846 notifs->cn_Waiter->cw_Notifier = NULL;
848 free(notifs);
850 pwaiters = &line->cl_Waiters;
851 while ((waiters = *pwaiters) != NULL) {
852 *pwaiters = waiters->cw_Next;
853 if (waiters->cw_Notifier)
854 waiters->cw_Notifier->cn_Waiter = NULL;
855 free(waiters);
858 free(line);
861 if (file->cf_Running == 0) {
862 *pfile = file->cf_Next;
863 free(file->cf_DPath);
864 free(file->cf_FileName);
865 free(file->cf_UserName);
866 free(file);
872 * TestJobs()
874 * determine which jobs need to be run. Under normal conditions, the
875 * period is about a minute (one scan). Worst case it will be one
876 * hour (60 scans).
880 TestJobs(time_t t1, time_t t2)
882 short nJobs = 0;
883 time_t t;
884 CronFile *file;
885 CronLine *line;
887 for (file = FileBase; file; file = file->cf_Next) {
888 if (file->cf_Deleted)
889 continue;
890 for (line = file->cf_LineBase; line; line = line->cl_Next) {
891 struct CronWaiter *waiter;
893 if (line->cl_Pid == -2) {
894 /* can job stop waiting? */
895 int ready = 1;
896 waiter = line->cl_Waiters;
897 while (waiter != NULL) {
898 if (waiter->cw_Flag > 0) {
899 /* notifier exited unsuccessfully */
900 ready = 2;
901 break;
902 } else if (waiter->cw_Flag < 0)
903 /* still waiting, notifier hasn't run to completion */
904 ready = 0;
905 waiter = waiter->cw_Next;
907 if (ready == 2) {
908 if (DebugOpt)
909 logn(LOG_DEBUG, "cancelled waiting: user %s %s\n", file->cf_UserName, line->cl_Description);
910 line->cl_Pid = 0;
911 } else if (ready) {
912 if (DebugOpt)
913 logn(LOG_DEBUG, "finished waiting: user %s %s\n", file->cf_UserName, line->cl_Description);
914 nJobs += ArmJob(file, line, 0, -1);
916 if (line->cl_NotUntil)
917 line->cl_NotUntil = t2;
925 * Find jobs > t1 and <= t2
928 for (t = t1 - t1 % 60; t <= t2; t += 60) {
929 if (t > t1) {
930 struct tm *tp = localtime(&t);
932 unsigned short n_wday = (tp->tm_mday - 1)%7 + 1;
933 if (n_wday >= 4) {
934 struct tm tnext = *tp;
935 tnext.tm_mday += 7;
936 if (mktime(&tnext) != (time_t)-1 && tnext.tm_mon != tp->tm_mon)
937 n_wday |= 16; /* last dow in month is always recognized as 5th */
940 for (file = FileBase; file; file = file->cf_Next) {
941 if (file->cf_Deleted)
942 continue;
943 for (line = file->cf_LineBase; line; line = line->cl_Next) {
944 if (line->cl_Pid != -1 && (line->cl_Freq == 0 || (line->cl_Freq > 0 && t2 >= line->cl_NotUntil))) {
945 /* (re)schedule job? */
946 if (line->cl_Mins[tp->tm_min] &&
947 line->cl_Hrs[tp->tm_hour] &&
948 (line->cl_Days[tp->tm_mday] || (n_wday && line->cl_Dow[tp->tm_wday]) ) &&
949 line->cl_Mons[tp->tm_mon]
951 if (line->cl_NotUntil)
952 line->cl_NotUntil = t2 - t2 % 60 + line->cl_Delay; /* save what minute this job was scheduled/started waiting, plus cl_Delay */
953 nJobs += ArmJob(file, line, t1, t2);
960 return(nJobs);
964 * ArmJob: if t2 is (time_t)-1, we force-schedule the job without any waiting
965 * else it will wait on any of its declared notifiers who will run <= t2 + cw_MaxWait
969 ArmJob(CronFile *file, CronLine *line, time_t t1, time_t t2)
971 struct CronWaiter *waiter;
972 if (line->cl_Pid > 0) {
973 logn(LOG_NOTICE, "process already running (%d): user %s %s\n",
974 line->cl_Pid,
975 file->cf_UserName,
976 line->cl_Description
978 } else if (t2 == -1 && line->cl_Pid != -1) {
979 line->cl_Pid = -1;
980 file->cf_Ready = 1;
981 return 1;
982 } else if (line->cl_Pid == 0) {
983 /* arming a waiting job (cl_Pid == -2) without forcing has no effect */
984 line->cl_Pid = -1;
985 /* if we have any waiters, zero them and arm cl_Pid=-2 */
986 waiter = line->cl_Waiters;
987 while (waiter != NULL) {
988 /* check if notifier will run <= t2 + cw_Max_Wait? */
989 if (!waiter->cw_NotifLine)
990 /* notifier deleted */
991 waiter->cw_Flag = 0;
992 else if (waiter->cw_NotifLine->cl_Pid != 0) {
993 /* if notifier is armed, or waiting, or running, we wait for it */
994 waiter->cw_Flag = -1;
995 line->cl_Pid = -2;
996 } else if (waiter->cw_NotifLine->cl_Freq < 0) {
997 /* arm any @noauto or @reboot jobs we're waiting on */
998 ArmJob(file, waiter->cw_NotifLine, t1, t2);
999 waiter->cw_Flag = -1;
1000 line->cl_Pid = -2;
1001 } else {
1002 time_t t;
1003 if (waiter->cw_MaxWait == 0)
1004 /* when no MaxWait interval specified, we always wait */
1005 waiter->cw_Flag = -1;
1006 else if (waiter->cw_NotifLine->cl_Freq == 0 || (waiter->cw_NotifLine->cl_Freq > 0 && t2 + waiter->cw_MaxWait >= waiter->cw_NotifLine->cl_NotUntil)) {
1007 /* default is don't wait */
1008 waiter->cw_Flag = 0;
1009 for (t = t1 - t1 % 60; t <= t2; t += 60) {
1010 if (t > t1) {
1011 struct tm *tp = localtime(&t);
1013 unsigned short n_wday = (tp->tm_mday - 1)%7 + 1;
1014 if (n_wday >= 4) {
1015 struct tm tnext = *tp;
1016 tnext.tm_mday += 7;
1017 if (mktime(&tnext) != (time_t)-1 && tnext.tm_mon != tp->tm_mon)
1018 n_wday |= 16; /* last dow in month is always recognized as 5th */
1020 if (line->cl_Mins[tp->tm_min] &&
1021 line->cl_Hrs[tp->tm_hour] &&
1022 (line->cl_Days[tp->tm_mday] || (n_wday && line->cl_Dow[tp->tm_wday]) ) &&
1023 line->cl_Mons[tp->tm_mon]
1025 /* notifier will run soon enough, we wait for it */
1026 waiter->cw_Flag = -1;
1027 line->cl_Pid = -2;
1028 break;
1034 waiter = waiter->cw_Next;
1036 if (line->cl_Pid == -1) {
1037 /* job is ready to run */
1038 file->cf_Ready = 1;
1039 if (DebugOpt)
1040 logn(LOG_DEBUG, "scheduled: user %s %s\n",
1041 file->cf_UserName,
1042 line->cl_Description
1044 return 1;
1045 } else if (DebugOpt)
1046 logn(LOG_DEBUG, "waiting: user %s %s\n",
1047 file->cf_UserName,
1048 line->cl_Description
1051 return 0;
1055 TestStartupJobs(void)
1057 short nJobs = 0;
1058 time_t t1 = time(NULL);
1059 CronFile *file;
1060 CronLine *line;
1062 t1 = t1 - t1 % 60 + 60;
1064 for (file = FileBase; file; file = file->cf_Next) {
1065 if (DebugOpt)
1066 logn(LOG_DEBUG, "TestStartup for FILE %s/%s USER %s:\n",
1067 file->cf_DPath, file->cf_FileName, file->cf_UserName);
1068 for (line = file->cf_LineBase; line; line = line->cl_Next) {
1069 struct CronWaiter *waiter;
1070 if (DebugOpt) {
1071 if (line->cl_JobName)
1072 logn(LOG_DEBUG, " LINE %s JOB %s\n", line->cl_Shell, line->cl_JobName);
1073 else
1074 logn(LOG_DEBUG, " LINE %s\n", line->cl_Shell);
1077 if (line->cl_Freq == -1) {
1078 /* freq is @reboot */
1080 line->cl_Pid = -1;
1081 /* if we have any waiters, reset them and arm Pid = -2 */
1082 waiter = line->cl_Waiters;
1083 while (waiter != NULL) {
1084 waiter->cw_Flag = -1;
1085 line->cl_Pid = -2;
1086 /* we only arm @noauto jobs we're waiting on, not other @reboot jobs */
1087 if (waiter->cw_NotifLine && waiter->cw_NotifLine->cl_Freq == -2)
1088 ArmJob(file, waiter->cw_NotifLine, t1, t1+60);
1089 waiter = waiter->cw_Next;
1091 if (line->cl_Pid == -1) {
1092 /* job is ready to run */
1093 file->cf_Ready = 1;
1094 ++nJobs;
1095 if (DebugOpt)
1096 logn(LOG_DEBUG, " scheduled: %s\n", line->cl_Description);
1097 } else if (DebugOpt)
1098 logn(LOG_DEBUG, " waiting: %s\n", line->cl_Description);
1102 } /* for line */
1104 return(nJobs);
1107 void
1108 RunJobs(void)
1110 CronFile *file;
1111 CronLine *line;
1113 for (file = FileBase; file; file = file->cf_Next) {
1114 if (file->cf_Ready) {
1115 file->cf_Ready = 0;
1117 for (line = file->cf_LineBase; line; line = line->cl_Next) {
1118 if (line->cl_Pid == -1) {
1120 RunJob(file, line);
1122 logn(LOG_INFO, "FILE %s/%s USER %s PID %3d %s\n",
1123 file->cf_DPath,
1124 file->cf_FileName,
1125 file->cf_UserName,
1126 line->cl_Pid,
1127 line->cl_Description
1129 if (line->cl_Pid < 0)
1130 /* how could this happen? RunJob will leave cl_Pid set to 0 or the actual pid */
1131 file->cf_Ready = 1;
1132 else if (line->cl_Pid > 0)
1133 file->cf_Running = 1;
1141 * CheckJobs() - check for job completion
1143 * Check for job completion, return number of CronFiles still running after
1144 * all done.
1148 CheckJobs(void)
1150 CronFile *file;
1151 CronLine *line;
1152 int nStillRunning = 0;
1154 for (file = FileBase; file; file = file->cf_Next) {
1155 if (file->cf_Running) {
1156 file->cf_Running = 0;
1158 for (line = file->cf_LineBase; line; line = line->cl_Next) {
1159 if (line->cl_Pid > 0) {
1160 int status;
1161 int r = waitpid(line->cl_Pid, &status, WNOHANG);
1163 if (r < 0 || r == line->cl_Pid) {
1164 if (r > 0 && WIFEXITED(status))
1165 status = WEXITSTATUS(status);
1166 else
1167 status = 1;
1168 if (DebugOpt)
1169 logn(LOG_DEBUG, "user %s %s finished with %d\n", file->cf_UserName, line->cl_Description, status);
1170 EndJob(file, line, status);
1171 if (line->cl_Pid)
1172 file->cf_Running = 1;
1173 } else if (r == 0) {
1174 file->cf_Running = 1;
1179 nStillRunning += file->cf_Running;
1181 return(nStillRunning);