5 * Copyright 1994 Matthew Dillon (dillon@apollo.backplane.com)
6 * Copyright 2009-2011 James Pryor <profjim@jimpryor.net>
7 * May be distributed under the GNU General Public License
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
);
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
= NULL
;
29 const char *DowAry
[] = {
48 const char *MonAry
[] = {
77 const char *FreqAry
[] = {
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.
94 CheckUpdates(const char *dpath
, const char *user_override
, time_t t1
, time_t t2
)
97 char buf
[SMALL_BUFFER
];
98 char *fname
, *ptok
, *job
;
101 if (!(path
= concat(dpath
, "/", CRONUPDATE
, NULL
))) {
103 perror("CheckUpdates");
106 if ((fi
= fopen(path
, "r")) != NULL
) {
108 printlogf(LOG_INFO
, "reading %s/%s\n", dpath
, CRONUPDATE
);
109 while (fgets(buf
, sizeof(buf
), fi
) != NULL
) {
111 * if buf has only sep chars, return NULL and point ptok at buf's terminating 0
112 * else return pointer to first non-sep of buf and
113 * if there's a following sep, overwrite it to 0 and point ptok to next char
114 * else point ptok at buf's terminating 0
116 fname
= strtok_r(buf
, " \t\n", &ptok
);
119 SynchronizeFile(dpath
, fname
, user_override
);
120 else if (!getpwnam(fname
))
121 printlogf(LOG_WARNING
, "ignoring %s/%s (non-existent user)\n", dpath
, fname
);
122 else if (*ptok
== 0 || *ptok
== '\n') {
123 SynchronizeFile(dpath
, fname
, fname
);
124 ReadTimestamps(fname
);
126 /* if fname is followed by whitespace, we prod any following jobs */
127 CronFile
*file
= FileBase
;
129 if (strcmp(file
->cf_UserName
, fname
) == 0)
131 file
= file
->cf_Next
;
134 printlogf(LOG_WARNING
, "unable to prod for user %s: no crontab\n", fname
);
137 /* calling strtok(ptok...) then strtok(NULL) is equiv to calling strtok_r(NULL,..&ptok) */
138 while ((job
= strtok(ptok
, " \t\n")) != NULL
) {
145 line
= file
->cf_LineBase
;
147 if (line
->cl_JobName
&& strcmp(line
->cl_JobName
, job
) == 0)
149 line
= line
->cl_Next
;
152 ArmJob(file
, line
, t1
, force
);
154 printlogf(LOG_WARNING
, "unable to prod for user %s: unknown job %s\n", fname
, job
);
155 /* we can continue parsing this line, we just don't install any CronWaiter for the requested job */
167 SynchronizeDir(const char *dpath
, const char *user_override
, int initial_scan
)
176 printlogf(LOG_DEBUG
, "Synchronizing %s\n", dpath
);
179 * Delete all database CronFiles for this directory. DeleteFile() will
180 * free *pfile and relink the *pfile pointer, or in the alternative will
181 * mark it as deleted.
184 while ((file
= *pfile
) != NULL
) {
185 if (file
->cf_Deleted
== 0 && strcmp(file
->cf_DPath
, dpath
) == 0) {
188 pfile
= &file
->cf_Next
;
193 * Since we are resynchronizing the entire directory, remove the
194 * the CRONUPDATE file.
196 if (!(path
= concat(dpath
, "/", CRONUPDATE
, NULL
))) {
198 perror("SynchronizeDir");
205 * Scan the specified directory
207 if ((dir
= opendir(dpath
)) != NULL
) {
208 while ((den
= readdir(dir
)) != NULL
) {
209 if (strchr(den
->d_name
, '.') != NULL
)
211 if (strcmp(den
->d_name
, CRONUPDATE
) == 0)
214 SynchronizeFile(dpath
, den
->d_name
, user_override
);
215 } else if (getpwnam(den
->d_name
)) {
216 SynchronizeFile(dpath
, den
->d_name
, den
->d_name
);
218 printlogf(LOG_WARNING
, "ignoring %s/%s (non-existent user)\n",
225 printlogf(LOG_ERR
, "unable to scan directory %s\n", dpath
);
226 /* softerror, do not exit the program */
232 ReadTimestamps(const char *user
)
237 char buf
[SMALL_BUFFER
];
243 while (file
!= NULL
) {
244 if (file
->cf_Deleted
== 0 && (!user
|| strcmp(user
, file
->cf_UserName
) == 0)) {
245 line
= file
->cf_LineBase
;
246 while (line
!= NULL
) {
247 if (line
->cl_Timestamp
) {
248 if ((fi
= fopen(line
->cl_Timestamp
, "r")) != NULL
) {
249 if (fgets(buf
, sizeof(buf
), fi
) != NULL
) {
252 if (strncmp(buf
, "after ", 6) == 0) {
257 ptr
= strptime(ptr
, CRONSTAMP_FMT
, &tm
);
258 if (ptr
&& (*ptr
== 0 || *ptr
== '\n'))
259 /* strptime uses current seconds when seconds not specified? anyway, we don't get round minutes */
263 if (sec
== (time_t)-1) {
264 printlogf(LOG_ERR
, "unable to parse timestamp (user %s job %s)\n", file
->cf_UserName
, line
->cl_JobName
);
265 /* we continue checking other timestamps in this CronFile */
267 /* sec -= sec % 60; */
269 line
->cl_NotUntil
= sec
;
271 line
->cl_LastRan
= sec
;
272 freq
= (line
->cl_Freq
> 0) ? line
->cl_Freq
: line
->cl_Delay
;
273 /* if (line->cl_NotUntil < line->cl_LastRan + freq) */
274 line
->cl_NotUntil
= line
->cl_LastRan
+ freq
;
281 printlogf(LOG_NOTICE
, "no timestamp found (user %s job %s)\n", file
->cf_UserName
, line
->cl_JobName
);
282 /* write a fake timestamp file so our initial NotUntil doesn't keep being reset every hour when crond does a SynchronizeDir */
283 if ((fi
= fopen(line
->cl_Timestamp
, "w")) != NULL
) {
284 if (strftime(buf
, sizeof(buf
), CRONSTAMP_FMT
, localtime(&line
->cl_NotUntil
)))
285 if (fputs("after ", fi
) >= 0)
286 if (fputs(buf
,fi
) >= 0)
291 printlogf(LOG_WARNING
, "unable to write timestamp to %s (user %s %s)\n", line
->cl_Timestamp
, file
->cf_UserName
, line
->cl_Description
);
294 line
= line
->cl_Next
;
297 file
= file
->cf_Next
;
302 SynchronizeFile(const char *dpath
, const char *fileName
, const char *userName
)
308 char buf
[RW_BUFFER
]; /* max length for crontab lines */
315 if (strcmp(userName
, "root") == 0)
318 maxEntries
= MAXLINES
;
319 maxLines
= maxEntries
* 10;
322 * Delete any existing copy of this CronFile
325 while ((file
= *pfile
) != NULL
) {
326 if (file
->cf_Deleted
== 0 && strcmp(file
->cf_DPath
, dpath
) == 0 &&
327 strcmp(file
->cf_FileName
, fileName
) == 0
331 pfile
= &file
->cf_Next
;
335 if (!(path
= concat(dpath
, "/", fileName
, NULL
))) {
337 perror("SynchronizeFile");
340 if ((fi
= fopen(path
, "r")) != NULL
) {
343 if (fstat(fileno(fi
), &sbuf
) == 0 && sbuf
.st_uid
== DaemonUid
) {
344 CronFile
*file
= calloc(1, sizeof(CronFile
));
346 time_t tnow
= time(NULL
);
349 file
->cf_UserName
= strdup(userName
);
350 file
->cf_FileName
= strdup(fileName
);
351 file
->cf_DPath
= strdup(dpath
);
352 pline
= &file
->cf_LineBase
;
354 /* fgets reads at most size-1 chars until \n or EOF, then adds a\0; \n if present is stored in buf */
355 while (fgets(buf
, sizeof(buf
), fi
) != NULL
&& --maxLines
) {
360 while (*ptr
== ' ' || *ptr
== '\t' || *ptr
== '\n')
364 if (len
&& ptr
[len
-1] == '\n')
367 if (*ptr
== 0 || *ptr
== '#')
370 if (--maxEntries
== 0)
373 memset(&line
, 0, sizeof(line
));
376 printlogf(LOG_DEBUG
, "User %s Entry %s\n", userName
, buf
);
385 for (j
= 0; FreqAry
[j
]; ++j
) {
386 if (strncmp(ptr
, FreqAry
[j
], strlen(FreqAry
[j
])) == 0) {
391 ptr
+= strlen(FreqAry
[j
]);
404 line
.cl_Freq
= HOURLY_FREQ
;
407 line
.cl_Freq
= DAILY_FREQ
;
410 line
.cl_Freq
= WEEKLY_FREQ
;
413 line
.cl_Freq
= MONTHLY_FREQ
;
416 line
.cl_Freq
= YEARLY_FREQ
;
418 /* else line.cl_Freq will remain 0 */
422 if (!line
.cl_Freq
|| (*ptr
!= ' ' && *ptr
!= '\t')) {
423 printlogf(LOG_WARNING
, "failed parsing crontab for user %s: %s\n", userName
, buf
);
427 if (line
.cl_Delay
< 0) {
429 * delays on @daily, @hourly, etc are 1/20 of the frequency
430 * so they don't all start at once
431 * this also affects how they behave when the job returns EAGAIN
433 line
.cl_Delay
= line
.cl_Freq
/ 20;
434 line
.cl_Delay
-= line
.cl_Delay
% 60;
435 if (line
.cl_Delay
== 0)
437 /* all minutes are permitted */
443 /* days are numbered 1..31 */
449 while (*ptr
== ' ' || *ptr
== '\t')
457 ptr
= ParseField(file
->cf_UserName
, line
.cl_Mins
, 60, 0, 1,
459 ptr
= ParseField(file
->cf_UserName
, line
.cl_Hrs
, 24, 0, 1,
461 ptr
= ParseField(file
->cf_UserName
, line
.cl_Days
, 32, 0, 1,
463 ptr
= ParseField(file
->cf_UserName
, line
.cl_Mons
, 12, -1, 1,
465 ptr
= ParseField(file
->cf_UserName
, line
.cl_Dow
, 7, 0, 31,
475 * fix days and dow - if one is not * and the other
476 * is *, the other is set to 0, and vise-versa
482 /* check for ID=... and AFTER=... and FREQ=... */
484 if (strncmp(ptr
, ID_TAG
, strlen(ID_TAG
)) == 0) {
485 if (line
.cl_JobName
) {
486 /* only assign ID_TAG once */
487 printlogf(LOG_WARNING
, "failed parsing crontab for user %s: repeated %s\n", userName
, ptr
);
490 ptr
+= strlen(ID_TAG
);
492 * name = strsep(&ptr, seps):
493 * return name = ptr, and if ptr contains sep chars, overwrite first with 0 and point ptr to next char
496 if (!(line
.cl_Description
= concat("job ", strsep(&ptr
, " \t"), NULL
))) {
498 perror("SynchronizeFile");
501 line
.cl_JobName
= line
.cl_Description
+ 4;
503 printlogf(LOG_WARNING
, "failed parsing crontab for user %s: no command after %s%s\n", userName
, ID_TAG
, line
.cl_JobName
);
505 } else if (strncmp(ptr
, FREQ_TAG
, strlen(FREQ_TAG
)) == 0) {
507 /* only assign FREQ_TAG once */
508 printlogf(LOG_WARNING
, "failed parsing crontab for user %s: repeated %s\n", userName
, ptr
);
512 ptr
+= strlen(FREQ_TAG
);
513 ptr
= ParseInterval(&line
.cl_Freq
, ptr
);
514 if (ptr
&& *ptr
== '/')
515 ptr
= ParseInterval(&line
.cl_Delay
, ++ptr
);
517 line
.cl_Delay
= line
.cl_Freq
;
519 printlogf(LOG_WARNING
, "failed parsing crontab for user %s: %s\n", userName
, base
);
520 } else if (*ptr
!= ' ' && *ptr
!= '\t') {
521 printlogf(LOG_WARNING
, "failed parsing crontab for user %s: no command after %s\n", userName
, base
);
525 } else if (strncmp(ptr
, WAIT_TAG
, strlen(WAIT_TAG
)) == 0) {
526 if (line
.cl_Waiters
) {
527 /* only assign WAIT_TAG once */
528 printlogf(LOG_WARNING
, "failed parsing crontab for user %s: repeated %s\n", userName
, ptr
);
533 ptr
+= strlen(WAIT_TAG
);
535 CronLine
*job
, **pjob
;
536 if (strcspn(ptr
,",") < strcspn(ptr
," \t"))
537 name
= strsep(&ptr
, ",");
540 name
= strsep(&ptr
, " \t");
542 if (!ptr
|| *ptr
== 0) {
543 /* unexpectedly this was the last token in buf; so abort */
544 printlogf(LOG_WARNING
, "failed parsing crontab for user %s: no command after %s%s\n", userName
, WAIT_TAG
, name
);
549 if ((w
= strchr(name
, '/')) != NULL
) {
551 w
= ParseInterval(&waitfor
, w
);
553 printlogf(LOG_WARNING
, "failed parsing crontab for user %s: %s%s\n", userName
, WAIT_TAG
, name
);
560 /* look for a matching CronLine */
561 pjob
= &file
->cf_LineBase
;
562 while ((job
= *pjob
) != NULL
) {
563 if (job
->cl_JobName
&& strcmp(job
->cl_JobName
, name
) == 0) {
564 CronWaiter
*waiter
= malloc(sizeof(CronWaiter
));
565 CronNotifier
*notif
= malloc(sizeof(CronNotifier
));
566 waiter
->cw_Flag
= -1;
567 waiter
->cw_MaxWait
= waitfor
;
568 waiter
->cw_NotifLine
= job
;
569 waiter
->cw_Notifier
= notif
;
570 waiter
->cw_Next
= line
.cl_Waiters
; /* add to head of line.cl_Waiters */
571 line
.cl_Waiters
= waiter
;
572 notif
->cn_Waiter
= waiter
;
573 notif
->cn_Next
= job
->cl_Notifs
; /* add to head of job->cl_Notifs */
574 job
->cl_Notifs
= notif
;
577 pjob
= &job
->cl_Next
;
580 printlogf(LOG_WARNING
, "failed parsing crontab for user %s: unknown job %s\n", userName
, name
);
581 /* we can continue parsing this line, we just don't install any CronWaiter for the requested job */
585 } while (ptr
&& more
);
591 while (*ptr
== ' ' || *ptr
== '\t')
593 } while (!line
.cl_JobName
|| !line
.cl_Waiters
|| !line
.cl_Freq
);
595 if (line
.cl_JobName
&& (!ptr
|| *line
.cl_JobName
== 0)) {
596 /* we're aborting, or ID= was empty */
597 free(line
.cl_Description
);
598 line
.cl_Description
= NULL
;
599 line
.cl_JobName
= NULL
;
601 if (ptr
&& line
.cl_Delay
> 0 && !line
.cl_JobName
) {
602 printlogf(LOG_WARNING
, "failed parsing crontab for user %s: writing timestamp requires job %s to be named\n", userName
, ptr
);
606 /* couldn't parse so we abort; free any cl_Waiters */
607 if (line
.cl_Waiters
) {
608 CronWaiter
**pwaiters
, *waiters
;
609 pwaiters
= &line
.cl_Waiters
;
610 while ((waiters
= *pwaiters
) != NULL
) {
611 *pwaiters
= waiters
->cw_Next
;
612 /* leave the Notifier allocated but disabled */
613 waiters
->cw_Notifier
->cn_Waiter
= NULL
;
619 /* now we've added any ID=... or AFTER=... */
622 * copy command string
624 line
.cl_Shell
= strdup(ptr
);
626 if (line
.cl_Delay
> 0) {
627 if (!(line
.cl_Timestamp
= concat(TSDir
, "/", userName
, ".", line
.cl_JobName
, NULL
))) {
629 perror("SynchronizeFile");
632 line
.cl_NotUntil
= tnow
+ line
.cl_Delay
;
635 if (line
.cl_JobName
) {
637 printlogf(LOG_DEBUG
, " Command %s Job %s\n", line
.cl_Shell
, line
.cl_JobName
);
639 /* when cl_JobName is NULL, we point cl_Description to cl_Shell */
640 line
.cl_Description
= line
.cl_Shell
;
642 printlogf(LOG_DEBUG
, " Command %s\n", line
.cl_Shell
);
645 *pline
= calloc(1, sizeof(CronLine
));
646 /* copy working CronLine to newly allocated one */
649 pline
= &((*pline
)->cl_Next
);
654 file
->cf_Next
= FileBase
;
657 if (maxLines
== 0 || maxEntries
== 0)
658 printlogf(LOG_WARNING
, "maximum number of lines reached for user %s\n", userName
);
666 ParseInterval(int *interval
, char *ptr
)
669 if (ptr
&& *ptr
>= '0' && *ptr
<= '9' && (n
= strtol(ptr
, &ptr
, 10)) > 0)
694 ParseField(char *user
, char *ary
, int modvalue
, int off
, int onvalue
, const char **names
, char *ptr
)
703 while (*ptr
!= ' ' && *ptr
!= '\t' && *ptr
!= '\n') {
707 * Handle numeric digit or symbol or '*'
711 n1
= 0; /* everything will be filled */
715 } else if (*ptr
>= '0' && *ptr
<= '9') {
717 n1
= strtol(ptr
, &ptr
, 10) + off
;
719 n2
= strtol(ptr
, &ptr
, 10) + off
;
724 for (i
= 0; names
[i
]; ++i
) {
725 if (strncmp(ptr
, names
[i
], strlen(names
[i
])) == 0) {
730 ptr
+= strlen(names
[i
]);
740 * handle optional range '-'
744 printlogf(LOG_WARNING
, "failed parsing crontab for user %s: %s\n", user
, base
);
747 if (*ptr
== '-' && n2
< 0) {
753 * collapse single-value ranges, handle skipmark, and fill
754 * in the character array appropriately.
763 skip
= strtol(ptr
+ 1, &ptr
, 10);
766 * fill array, using a failsafe is the easiest way to prevent
776 n1
= (n1
+ 1) % modvalue
;
782 } while (n1
!= n2
&& --failsafe
);
785 printlogf(LOG_WARNING
, "failed parsing crontab for user %s: %s\n", user
, base
);
796 if (*ptr
!= ' ' && *ptr
!= '\t' && *ptr
!= '\n') {
797 printlogf(LOG_WARNING
, "failed parsing crontab for user %s: %s\n", user
, base
);
801 while (*ptr
== ' ' || *ptr
== '\t' || *ptr
== '\n')
807 for (i
= 0; i
< modvalue
; ++i
)
809 printlogf(LOG_DEBUG
, "%2x ", ary
[i
]);
811 printlogf(LOG_DEBUG
, "%d", ary
[i
]);
812 printlogf(LOG_DEBUG
, "\n");
819 FixDayDow(CronLine
*line
)
825 for (i
= 0; i
< arysize(line
->cl_Dow
); ++i
) {
826 if (line
->cl_Dow
[i
] == 0) {
831 for (i
= 0; i
< arysize(line
->cl_Days
); ++i
) {
832 if (line
->cl_Days
[i
] == 0) {
836 /* change from "every Mon" to "ith Mon"
837 * 6th,7th... Dow are treated as 1st,2nd... */
838 for (j
= 0; j
< arysize(line
->cl_Dow
); ++j
) {
839 line
->cl_Dow
[j
] &= 1 << (i
-1)%5;
842 /* change from "nth Mon" to "nth or ith Mon" */
843 for (j
= 0; j
< arysize(line
->cl_Dow
); ++j
) {
845 line
->cl_Dow
[j
] |= 1 << (i
-1)%5;
848 /* continue cycling through cl_Days */
857 memset(line
->cl_Days
, 0, sizeof(line
->cl_Days
));
859 if (daysUsed
&& !weekUsed
) {
860 memset(line
->cl_Dow
, 0, sizeof(line
->cl_Dow
));
865 * DeleteFile() - destroy a CronFile.
867 * The CronFile (*pfile) is destroyed if possible, and marked cf_Deleted
868 * if there are still active processes running on it. *pfile is relinked
872 DeleteFile(CronFile
**pfile
)
874 CronFile
*file
= *pfile
;
875 CronLine
**pline
= &file
->cf_LineBase
;
877 CronWaiter
**pwaiters
, *waiters
;
878 CronNotifier
**pnotifs
, *notifs
;
880 file
->cf_Running
= 0;
881 file
->cf_Deleted
= 1;
883 while ((line
= *pline
) != NULL
) {
884 if (line
->cl_Pid
> 0) {
885 file
->cf_Running
= 1;
886 pline
= &line
->cl_Next
;
888 *pline
= line
->cl_Next
;
889 free(line
->cl_Shell
);
891 if (line
->cl_JobName
)
892 /* this frees both cl_Description and cl_JobName
893 * if cl_JobName is NULL, Description pointed to ch_Shell, which was already freed
895 free(line
->cl_Description
);
896 if (line
->cl_Timestamp
)
897 free(line
->cl_Timestamp
);
899 pnotifs
= &line
->cl_Notifs
;
900 while ((notifs
= *pnotifs
) != NULL
) {
901 *pnotifs
= notifs
->cn_Next
;
902 if (notifs
->cn_Waiter
) {
903 notifs
->cn_Waiter
->cw_NotifLine
= NULL
;
904 notifs
->cn_Waiter
->cw_Notifier
= NULL
;
908 pwaiters
= &line
->cl_Waiters
;
909 while ((waiters
= *pwaiters
) != NULL
) {
910 *pwaiters
= waiters
->cw_Next
;
911 if (waiters
->cw_Notifier
)
912 waiters
->cw_Notifier
->cn_Waiter
= NULL
;
919 if (file
->cf_Running
== 0) {
920 *pfile
= file
->cf_Next
;
921 free(file
->cf_DPath
);
922 free(file
->cf_FileName
);
923 free(file
->cf_UserName
);
932 * determine which jobs need to be run. Under normal conditions, the
933 * period is about a minute (one scan). Worst case it will be one
938 TestJobs(time_t t1
, time_t t2
)
945 for (file
= FileBase
; file
; file
= file
->cf_Next
) {
946 if (file
->cf_Deleted
)
948 for (line
= file
->cf_LineBase
; line
; line
= line
->cl_Next
) {
949 struct CronWaiter
*waiter
;
951 if (line
->cl_Pid
== -2) {
952 /* can job stop waiting? */
954 waiter
= line
->cl_Waiters
;
955 while (waiter
!= NULL
) {
956 if (waiter
->cw_Flag
> 0) {
957 /* notifier exited unsuccessfully */
960 } else if (waiter
->cw_Flag
< 0)
961 /* still waiting, notifier hasn't run to completion */
963 waiter
= waiter
->cw_Next
;
967 printlogf(LOG_DEBUG
, "cancelled waiting: user %s %s\n", file
->cf_UserName
, line
->cl_Description
);
971 printlogf(LOG_DEBUG
, "finished waiting: user %s %s\n", file
->cf_UserName
, line
->cl_Description
);
972 nJobs
+= ArmJob(file
, line
, 0, -1);
974 if (line->cl_NotUntil)
975 line->cl_NotUntil = t2;
983 * Find jobs > t1 and <= t2
986 for (t
= t1
- t1
% 60; t
<= t2
; t
+= 60) {
988 struct tm
*tp
= localtime(&t
);
990 unsigned short n_wday
= (tp
->tm_mday
- 1)%7 + 1;
992 struct tm tnext
= *tp
;
994 if (mktime(&tnext
) != (time_t)-1 && tnext
.tm_mon
!= tp
->tm_mon
)
995 n_wday
|= 16; /* last dow in month is always recognized as 5th */
998 for (file
= FileBase
; file
; file
= file
->cf_Next
) {
999 if (file
->cf_Deleted
)
1001 for (line
= file
->cf_LineBase
; line
; line
= line
->cl_Next
) {
1002 if ((line
->cl_Pid
== -2 || line
->cl_Pid
== 0) && (line
->cl_Freq
== 0 || (line
->cl_Freq
> 0 && t2
>= line
->cl_NotUntil
))) {
1003 /* (re)schedule job? */
1004 if (line
->cl_Mins
[tp
->tm_min
] &&
1005 line
->cl_Hrs
[tp
->tm_hour
] &&
1006 (line
->cl_Days
[tp
->tm_mday
] || (n_wday
&& line
->cl_Dow
[tp
->tm_wday
]) ) &&
1007 line
->cl_Mons
[tp
->tm_mon
]
1009 if (line
->cl_NotUntil
)
1010 line
->cl_NotUntil
= t2
- t2
% 60 + line
->cl_Delay
; /* save what minute this job was scheduled/started waiting, plus cl_Delay */
1011 nJobs
+= ArmJob(file
, line
, t1
, t2
);
1022 * ArmJob: if t2 is (time_t)-1, we force-schedule the job without any waiting
1023 * else it will wait on any of its declared notifiers who will run <= t2 + cw_MaxWait
1027 ArmJob(CronFile
*file
, CronLine
*line
, time_t t1
, time_t t2
)
1029 struct CronWaiter
*waiter
;
1030 if (line
->cl_Pid
> 0) {
1031 printlogf(LOG_NOTICE
, "process already running (%d): user %s %s\n",
1034 line
->cl_Description
1036 } else if (t2
== -1 && line
->cl_Pid
!= -1) {
1040 } else if (line
->cl_Pid
== 0) {
1041 /* arming a waiting job (cl_Pid == -2) without forcing has no effect */
1043 /* if we have any waiters, zero them and arm cl_Pid=-2 */
1044 waiter
= line
->cl_Waiters
;
1045 while (waiter
!= NULL
) {
1046 /* check if notifier will run <= t2 + cw_Max_Wait? */
1047 if (!waiter
->cw_NotifLine
)
1048 /* notifier deleted */
1049 waiter
->cw_Flag
= 0;
1050 else if (waiter
->cw_NotifLine
->cl_Pid
!= 0) {
1051 /* if notifier is armed, or waiting, or running, we wait for it */
1052 waiter
->cw_Flag
= -1;
1054 } else if (waiter
->cw_NotifLine
->cl_Freq
< 0) {
1055 /* arm any @noauto or @reboot jobs we're waiting on */
1056 ArmJob(file
, waiter
->cw_NotifLine
, t1
, t2
);
1057 waiter
->cw_Flag
= -1;
1061 if (waiter
->cw_MaxWait
== 0)
1062 /* when no MaxWait interval specified, we always wait */
1063 waiter
->cw_Flag
= -1;
1064 else if (waiter
->cw_NotifLine
->cl_Freq
== 0 || (waiter
->cw_NotifLine
->cl_Freq
> 0 && t2
+ waiter
->cw_MaxWait
>= waiter
->cw_NotifLine
->cl_NotUntil
)) {
1065 /* default is don't wait */
1066 waiter
->cw_Flag
= 0;
1067 for (t
= t1
- t1
% 60; t
<= t2
; t
+= 60) {
1069 struct tm
*tp
= localtime(&t
);
1071 unsigned short n_wday
= (tp
->tm_mday
- 1)%7 + 1;
1073 struct tm tnext
= *tp
;
1075 if (mktime(&tnext
) != (time_t)-1 && tnext
.tm_mon
!= tp
->tm_mon
)
1076 n_wday
|= 16; /* last dow in month is always recognized as 5th */
1078 if (line
->cl_Mins
[tp
->tm_min
] &&
1079 line
->cl_Hrs
[tp
->tm_hour
] &&
1080 (line
->cl_Days
[tp
->tm_mday
] || (n_wday
&& line
->cl_Dow
[tp
->tm_wday
]) ) &&
1081 line
->cl_Mons
[tp
->tm_mon
]
1083 /* notifier will run soon enough, we wait for it */
1084 waiter
->cw_Flag
= -1;
1092 waiter
= waiter
->cw_Next
;
1094 if (line
->cl_Pid
== -1) {
1095 /* job is ready to run */
1098 printlogf(LOG_DEBUG
, "scheduled: user %s %s\n",
1100 line
->cl_Description
1103 } else if (DebugOpt
)
1104 printlogf(LOG_DEBUG
, "waiting: user %s %s\n",
1106 line
->cl_Description
1113 TestStartupJobs(void)
1116 time_t t1
= time(NULL
);
1120 t1
= t1
- t1
% 60 + 60;
1122 for (file
= FileBase
; file
; file
= file
->cf_Next
) {
1124 printlogf(LOG_DEBUG
, "TestStartup for FILE %s/%s USER %s:\n",
1125 file
->cf_DPath
, file
->cf_FileName
, file
->cf_UserName
);
1126 for (line
= file
->cf_LineBase
; line
; line
= line
->cl_Next
) {
1127 struct CronWaiter
*waiter
;
1129 if (line
->cl_JobName
)
1130 printlogf(LOG_DEBUG
, " LINE %s JOB %s\n", line
->cl_Shell
, line
->cl_JobName
);
1132 printlogf(LOG_DEBUG
, " LINE %s\n", line
->cl_Shell
);
1135 if (line
->cl_Freq
== -1) {
1136 /* freq is @reboot */
1139 /* if we have any waiters, reset them and arm Pid = -2 */
1140 waiter
= line
->cl_Waiters
;
1141 while (waiter
!= NULL
) {
1142 waiter
->cw_Flag
= -1;
1144 /* we only arm @noauto jobs we're waiting on, not other @reboot jobs */
1145 if (waiter
->cw_NotifLine
&& waiter
->cw_NotifLine
->cl_Freq
== -2)
1146 ArmJob(file
, waiter
->cw_NotifLine
, t1
, t1
+60);
1147 waiter
= waiter
->cw_Next
;
1149 if (line
->cl_Pid
== -1) {
1150 /* job is ready to run */
1154 printlogf(LOG_DEBUG
, " scheduled: %s\n", line
->cl_Description
);
1155 } else if (DebugOpt
)
1156 printlogf(LOG_DEBUG
, " waiting: %s\n", line
->cl_Description
);
1171 for (file
= FileBase
; file
; file
= file
->cf_Next
) {
1172 if (file
->cf_Ready
) {
1175 for (line
= file
->cf_LineBase
; line
; line
= line
->cl_Next
) {
1176 if (line
->cl_Pid
== -1) {
1180 printlogf(LOG_INFO
, "FILE %s/%s USER %s PID %3d %s\n",
1185 line
->cl_Description
1187 if (line
->cl_Pid
< 0)
1188 /* QUESTION how could this happen? RunJob will leave cl_Pid set to 0 or the actual pid */
1190 else if (line
->cl_Pid
> 0)
1191 file
->cf_Running
= 1;
1199 * CheckJobs() - check for job completion
1201 * Check for job completion, return number of CronFiles still running after
1210 int nStillRunning
= 0;
1212 for (file
= FileBase
; file
; file
= file
->cf_Next
) {
1213 if (file
->cf_Running
) {
1214 file
->cf_Running
= 0;
1216 for (line
= file
->cf_LineBase
; line
; line
= line
->cl_Next
) {
1217 if (line
->cl_Pid
> 0) {
1219 int r
= waitpid(line
->cl_Pid
, &status
, WNOHANG
);
1221 /* waitpid returns -1 for error, 0 if cl_Pid still running, cl_Pid if it's dead */
1223 if (r
< 0 || r
== line
->cl_Pid
) {
1224 if (r
> 0 && WIFEXITED(status
))
1225 status
= WEXITSTATUS(status
);
1228 EndJob(file
, line
, status
);
1230 } else if (r
== 0) {
1231 file
->cf_Running
= 1;
1236 nStillRunning
+= file
->cf_Running
;
1238 return(nStillRunning
);