xref: /minix/minix/commands/cron/tab.c (revision 9f988b79)
1 /*	tab.c - process crontabs and create in-core crontab data
2  *							Author: Kees J. Bot
3  *								7 Dec 1996
4  * Changes:
5  * 17 Jul 2000 by Philip Homburg
6  *	- Tab_reschedule() rewritten (and fixed).
7  */
8 #define nil ((void*)0)
9 #include <sys/types.h>
10 #include <assert.h>
11 #include <stdio.h>
12 #include <unistd.h>
13 #include <fcntl.h>
14 #include <stdlib.h>
15 #include <string.h>
16 #include <errno.h>
17 #include <limits.h>
18 #include <time.h>
19 #include <dirent.h>
20 #include <sys/stat.h>
21 #include "misc.h"
22 #include "tab.h"
23 
24 static int nextbit(bitmap_t map, int bit)
25 /* Return the next bit set in 'map' from 'bit' onwards, cyclic. */
26 {
27 	for (;;) {
28 		bit= (bit+1) & 63;
29 		if (bit_isset(map, bit)) break;
30 	}
31 	return bit;
32 }
33 
34 void tab_reschedule(cronjob_t *job)
35 /* Reschedule one job.  Compute the next time to run the job in job->rtime.
36  */
37 {
38 	struct tm prevtm, nexttm, tmptm;
39 	time_t nodst_rtime, dst_rtime;
40 
41 	/* AT jobs are only run once. */
42 	if (job->atjob) { job->rtime= NEVER; return; }
43 
44 	/* Was the job scheduled late last time? */
45 	if (job->late) job->rtime= now;
46 
47 	prevtm= *localtime(&job->rtime);
48 	prevtm.tm_sec= 0;
49 
50 	nexttm= prevtm;
51 	nexttm.tm_min++;	/* Minimal increment */
52 
53 	for (;;)
54 	{
55 		if (nexttm.tm_min > 59)
56 		{
57 			nexttm.tm_min= 0;
58 			nexttm.tm_hour++;
59 		}
60 		if (nexttm.tm_hour > 23)
61 		{
62 			nexttm.tm_min= 0;
63 			nexttm.tm_hour= 0;
64 			nexttm.tm_mday++;
65 		}
66 		if (nexttm.tm_mday > 31)
67 		{
68 			nexttm.tm_hour= nexttm.tm_min= 0;
69 			nexttm.tm_mday= 1;
70 			nexttm.tm_mon++;
71 		}
72 		if (nexttm.tm_mon >= 12)
73 		{
74 			nexttm.tm_hour= nexttm.tm_min= 0;
75 			nexttm.tm_mday= 1;
76 			nexttm.tm_mon= 0;
77 			nexttm.tm_year++;
78 		}
79 
80 		/* Verify tm_year. A crontab entry cannot restrict tm_year
81 		 * directly. However, certain dates (such as Feb, 29th) do
82 		 * not occur every year. We limit the difference between
83 		 * nexttm.tm_year and prevtm.tm_year to detect impossible dates
84 		 * (e.g, Feb, 31st). In theory every date occurs within a
85 		 * period of 4 years. However, some years at the end of a
86 		 * century are not leap years (e.g, the year 2100). An extra
87 		 * factor of 2 should be enough.
88 		 */
89 		if (nexttm.tm_year-prevtm.tm_year > 2*4)
90 		{
91 			job->rtime= NEVER;
92 			return;			/* Impossible combination */
93 		}
94 
95 		if (!job->do_wday)
96 		{
97 			/* Verify the mon and mday fields. If do_wday and
98 			 * do_mday are both true we have to merge both
99 			 * schedules. This is done after the call to mktime.
100 			 */
101 			if (!bit_isset(job->mon, nexttm.tm_mon))
102 			{
103 				/* Clear other fields */
104 				nexttm.tm_mday= 1;
105 				nexttm.tm_hour= nexttm.tm_min= 0;
106 
107 				nexttm.tm_mon++;
108 				continue;
109 			}
110 
111 			/* Verify mday */
112 			if (!bit_isset(job->mday, nexttm.tm_mday))
113 			{
114 				/* Clear other fields */
115 				nexttm.tm_hour= nexttm.tm_min= 0;
116 
117 				nexttm.tm_mday++;
118 				continue;
119 			}
120 		}
121 
122 		/* Verify hour */
123 		if (!bit_isset(job->hour, nexttm.tm_hour))
124 		{
125 			/* Clear tm_min field */
126 			nexttm.tm_min= 0;
127 
128 			nexttm.tm_hour++;
129 			continue;
130 		}
131 
132 		/* Verify min */
133 		if (!bit_isset(job->min, nexttm.tm_min))
134 		{
135 			nexttm.tm_min++;
136 			continue;
137 		}
138 
139 		/* Verify that we don't have any problem with DST. Try
140 		 * tm_isdst=0 first. */
141 		tmptm= nexttm;
142 		tmptm.tm_isdst= 0;
143 #if 0
144 		fprintf(stderr,
145 	"tab_reschedule: trying %04d-%02d-%02d %02d:%02d:%02d isdst=0\n",
146 				1900+nexttm.tm_year, nexttm.tm_mon+1,
147 				nexttm.tm_mday, nexttm.tm_hour,
148 				nexttm.tm_min, nexttm.tm_sec);
149 #endif
150 		nodst_rtime= job->rtime= mktime(&tmptm);
151 		if (job->rtime == -1) {
152 			/* This should not happen. */
153 			cronlog(LOG_ERR,
154 			"mktime failed for %04d-%02d-%02d %02d:%02d:%02d",
155 				1900+nexttm.tm_year, nexttm.tm_mon+1,
156 				nexttm.tm_mday, nexttm.tm_hour,
157 				nexttm.tm_min, nexttm.tm_sec);
158 			job->rtime= NEVER;
159 			return;
160 		}
161 		tmptm= *localtime(&job->rtime);
162 		if (tmptm.tm_hour != nexttm.tm_hour ||
163 			tmptm.tm_min != nexttm.tm_min)
164 		{
165 			assert(tmptm.tm_isdst);
166 			tmptm= nexttm;
167 			tmptm.tm_isdst= 1;
168 #if 0
169 			fprintf(stderr,
170 	"tab_reschedule: trying %04d-%02d-%02d %02d:%02d:%02d isdst=1\n",
171 				1900+nexttm.tm_year, nexttm.tm_mon+1,
172 				nexttm.tm_mday, nexttm.tm_hour,
173 				nexttm.tm_min, nexttm.tm_sec);
174 #endif
175 			dst_rtime= job->rtime= mktime(&tmptm);
176 			if (job->rtime == -1) {
177 				/* This should not happen. */
178 				cronlog(LOG_ERR,
179 			"mktime failed for %04d-%02d-%02d %02d:%02d:%02d\n",
180 					1900+nexttm.tm_year, nexttm.tm_mon+1,
181 					nexttm.tm_mday, nexttm.tm_hour,
182 					nexttm.tm_min, nexttm.tm_sec);
183 				job->rtime= NEVER;
184 				return;
185 			}
186 			tmptm= *localtime(&job->rtime);
187 			if (tmptm.tm_hour != nexttm.tm_hour ||
188 				tmptm.tm_min != nexttm.tm_min)
189 			{
190 				assert(!tmptm.tm_isdst);
191 				/* We have a problem. This time neither
192 				 * exists with DST nor without DST.
193 				 * Use the latest time, which should be
194 				 * nodst_rtime.
195 				 */
196 				assert(nodst_rtime > dst_rtime);
197 				job->rtime= nodst_rtime;
198 #if 0
199 				fprintf(stderr,
200 			"During DST trans. %04d-%02d-%02d %02d:%02d:%02d\n",
201 					1900+nexttm.tm_year, nexttm.tm_mon+1,
202 					nexttm.tm_mday, nexttm.tm_hour,
203 					nexttm.tm_min, nexttm.tm_sec);
204 #endif
205 			}
206 		}
207 
208 		/* Verify this the combination (tm_year, tm_mon, tm_mday). */
209 		if (tmptm.tm_mday != nexttm.tm_mday ||
210 			tmptm.tm_mon != nexttm.tm_mon ||
211 			tmptm.tm_year != nexttm.tm_year)
212 		{
213 			/* Wrong day */
214 #if 0
215 			fprintf(stderr, "Wrong day\n");
216 #endif
217 			nexttm.tm_hour= nexttm.tm_min= 0;
218 			nexttm.tm_mday++;
219 			continue;
220 		}
221 
222 		/* Check tm_wday */
223 		if (job->do_wday && bit_isset(job->wday, tmptm.tm_wday))
224 		{
225 			/* OK, wday matched */
226 			break;
227 		}
228 
229 		/* Check tm_mday */
230 		if (job->do_mday && bit_isset(job->mon, tmptm.tm_mon) &&
231 			bit_isset(job->mday, tmptm.tm_mday))
232 		{
233 			/* OK, mon and mday matched */
234 			break;
235 		}
236 
237 		if (!job->do_wday && !job->do_mday)
238 		{
239 			/* No need to match wday and mday */
240 			break;
241 		}
242 
243 		/* Wrong day */
244 #if 0
245 		fprintf(stderr, "Wrong mon+mday or wday\n");
246 #endif
247 		nexttm.tm_hour= nexttm.tm_min= 0;
248 		nexttm.tm_mday++;
249 	}
250 #if 0
251 	fprintf(stderr, "Using %04d-%02d-%02d %02d:%02d:%02d \n",
252 		1900+nexttm.tm_year, nexttm.tm_mon+1, nexttm.tm_mday,
253 		nexttm.tm_hour, nexttm.tm_min, nexttm.tm_sec);
254 	tmptm= *localtime(&job->rtime);
255 	fprintf(stderr, "Act. %04d-%02d-%02d %02d:%02d:%02d isdst=%d\n",
256 		1900+tmptm.tm_year, tmptm.tm_mon+1, tmptm.tm_mday,
257 		tmptm.tm_hour, tmptm.tm_min, tmptm.tm_sec,
258 		tmptm.tm_isdst);
259 #endif
260 
261 
262 	/* Is job issuing lagging behind with the progress of time? */
263 	job->late= (job->rtime < now);
264 
265   	/* The result is in job->rtime. */
266   	if (job->rtime < next) next= job->rtime;
267 }
268 
269 #define isdigit(c)	((unsigned) ((c) - '0') < 10)
270 
271 static char *get_token(char **ptr)
272 /* Return a pointer to the next token in a string.  Move *ptr to the end of
273  * the token, and return a pointer to the start.  If *ptr == start of token
274  * then we're stuck against a newline or end of string.
275  */
276 {
277 	char *start, *p;
278 
279 	p= *ptr;
280 	while (*p == ' ' || *p == '\t') p++;
281 
282 	start= p;
283 	while (*p != ' ' && *p != '\t' && *p != '\n' && *p != 0) p++;
284 	*ptr= p;
285 	return start;
286 }
287 
288 static int range_parse(char *file, char *data, bitmap_t map,
289 	int min, int max, int *wildcard)
290 /* Parse a comma separated series of 'n', 'n-m' or 'n:m' numbers.  'n'
291  * includes number 'n' in the bit map, 'n-m' includes all numbers between
292  * 'n' and 'm' inclusive, and 'n:m' includes 'n+k*m' for any k if in range.
293  * Numbers must fall between 'min' and 'max'.  A '*' means all numbers.  A
294  * '?' is allowed as a synonym for the current minute, which only makes sense
295  * in the minute field, i.e. max must be 59.  Return true iff parsed ok.
296  */
297 {
298 	char *p;
299 	int end;
300 	int n, m;
301 
302 	/* Clear all bits. */
303 	for (n= 0; n < 8; n++) map[n]= 0;
304 
305 	p= data;
306 	while (*p != ' ' && *p != '\t' && *p != '\n' && *p != 0) p++;
307 	end= *p;
308 	*p= 0;
309 	p= data;
310 
311 	if (*p == 0) {
312 		cronlog(LOG_ERR, "%s: not enough time fields\n", file);
313 		return 0;
314 	}
315 
316 	/* Is it a '*'? */
317 	if (p[0] == '*' && p[1] == 0) {
318 		for (n= min; n <= max; n++) bit_set(map, n);
319 		p[1]= end;
320 		*wildcard= 1;
321 		return 1;
322 	}
323 	*wildcard= 0;
324 
325 	/* Parse a comma separated series of numbers or ranges. */
326 	for (;;) {
327 		if (*p == '?' && max == 59 && p[1] != '-') {
328 			n= localtime(&now)->tm_min;
329 			p++;
330 		} else {
331 			if (!isdigit(*p)) goto syntax;
332 			n= 0;
333 			do {
334 				n= 10 * n + (*p++ - '0');
335 				if (n > max) goto range;
336 			} while (isdigit(*p));
337 		}
338 		if (n < min) goto range;
339 
340 		if (*p == '-') {	/* A range of the form 'n-m'? */
341 			p++;
342 			if (!isdigit(*p)) goto syntax;
343 			m= 0;
344 			do {
345 				m= 10 * m + (*p++ - '0');
346 				if (m > max) goto range;
347 			} while (isdigit(*p));
348 			if (m < n) goto range;
349 			do {
350 				bit_set(map, n);
351 			} while (++n <= m);
352 		} else
353 		if (*p == ':') {	/* A repeat of the form 'n:m'? */
354 			p++;
355 			if (!isdigit(*p)) goto syntax;
356 			m= 0;
357 			do {
358 				m= 10 * m + (*p++ - '0');
359 				if (m > (max-min+1)) goto range;
360 			} while (isdigit(*p));
361 			if (m == 0) goto range;
362 			while (n >= min) n-= m;
363 			while ((n+= m) <= max) bit_set(map, n);
364 		} else {
365 					/* Simply a number */
366 			bit_set(map, n);
367 		}
368 		if (*p == 0) break;
369 		if (*p++ != ',') goto syntax;
370 	}
371 	*p= end;
372 	return 1;
373   syntax:
374 	cronlog(LOG_ERR, "%s: field '%s': bad syntax for a %d-%d time field\n",
375 		file, data, min, max);
376 	return 0;
377   range:
378 	cronlog(LOG_ERR,
379 		"%s: field '%s': values out of the %d-%d allowed range\n",
380 		file, data, min, max);
381 	return 0;
382 }
383 
384 void tab_parse(char *file, char *user)
385 /* Parse a crontab file and add its data to the tables.  Handle errors by
386  * yourself.  Table is owned by 'user' if non-null.
387  */
388 {
389 	crontab_t **atab, *tab;
390 	cronjob_t **ajob, *job;
391 	int fd;
392 	struct stat st;
393 	char *p, *q;
394 	size_t n;
395 	ssize_t r;
396 	int ok, wc;
397 
398 	for (atab= &crontabs; (tab= *atab) != nil; atab= &tab->next) {
399 		if (strcmp(file, tab->file) == 0) break;
400 	}
401 
402 	/* Try to open the file. */
403 	if ((fd= open(file, O_RDONLY)) < 0 || fstat(fd, &st) < 0) {
404 		if (errno != ENOENT) {
405 			cronlog(LOG_ERR, "%s: %s\n", file, strerror(errno));
406 		}
407 		if (fd != -1) close(fd);
408 		return;
409 	}
410 
411 	/* Forget it if the file is awfully big. */
412 	if (st.st_size > TAB_MAX) {
413 		cronlog(LOG_ERR, "%s: %lu bytes is bigger than my %lu limit\n",
414 			file,
415 			(unsigned long) st.st_size,
416 			(unsigned long) TAB_MAX);
417 		return;
418 	}
419 
420 	/* If the file is the same as before then don't bother. */
421 	if (tab != nil && st.st_mtime == tab->mtime) {
422 		close(fd);
423 		tab->current= 1;
424 		return;
425 	}
426 
427 	/* Create a new table structure. */
428 	tab= allocate(sizeof(*tab));
429 	tab->file= allocate((strlen(file) + 1) * sizeof(tab->file[0]));
430 	strcpy(tab->file, file);
431 	tab->user= nil;
432 	if (user != nil) {
433 		tab->user= allocate((strlen(user) + 1) * sizeof(tab->user[0]));
434 		strcpy(tab->user, user);
435 	}
436 	tab->data= allocate((st.st_size + 1) * sizeof(tab->data[0]));
437 	tab->jobs= nil;
438 	tab->mtime= st.st_mtime;
439 	tab->current= 0;
440 	tab->next= *atab;
441 	*atab= tab;
442 
443 	/* Pull a new table in core. */
444 	n= 0;
445 	while (n < st.st_size) {
446 		if ((r = read(fd, tab->data + n, st.st_size - n)) < 0) {
447 			cronlog(LOG_CRIT, "%s: %s", file, strerror(errno));
448 			close(fd);
449 			return;
450 		}
451 		if (r == 0) break;
452 		n+= r;
453 	}
454 	close(fd);
455 	tab->data[n]= 0;
456 	if (strlen(tab->data) < n) {
457 		cronlog(LOG_ERR, "%s contains a null character\n", file);
458 		return;
459 	}
460 
461 	/* Parse the file. */
462 	ajob= &tab->jobs;
463 	p= tab->data;
464 	ok= 1;
465 	while (ok && *p != 0) {
466 		q= get_token(&p);
467 		if (*q == '#' || q == p) {
468 			/* Comment or empty. */
469 			while (*p != 0 && *p++ != '\n') {}
470 			continue;
471 		}
472 
473 		/* One new job coming up. */
474 		*ajob= job= allocate(sizeof(*job));
475 		*(ajob= &job->next)= nil;
476 		job->tab= tab;
477 
478 		if (!range_parse(file, q, job->min, 0, 59, &wc)) {
479 			ok= 0;
480 			break;
481 		}
482 
483 		q= get_token(&p);
484 		if (!range_parse(file, q, job->hour, 0, 23, &wc)) {
485 			ok= 0;
486 			break;
487 		}
488 
489 		q= get_token(&p);
490 		if (!range_parse(file, q, job->mday, 1, 31, &wc)) {
491 			ok= 0;
492 			break;
493 		}
494 		job->do_mday= !wc;
495 
496 		q= get_token(&p);
497 		if (!range_parse(file, q, job->mon, 1, 12, &wc)) {
498 			ok= 0;
499 			break;
500 		}
501 		job->do_mday |= !wc;
502 
503 		q= get_token(&p);
504 		if (!range_parse(file, q, job->wday, 0, 7, &wc)) {
505 			ok= 0;
506 			break;
507 		}
508 		job->do_wday= !wc;
509 
510 		/* 7 is Sunday, but 0 is a common mistake because it is in the
511 		 * tm_wday range.  We allow and even prefer it internally.
512 		 */
513 		if (bit_isset(job->wday, 7)) {
514 			bit_clr(job->wday, 7);
515 			bit_set(job->wday, 0);
516 		}
517 
518 		/* The month range is 1-12, but tm_mon likes 0-11. */
519 		job->mon[0] >>= 1;
520 		if (bit_isset(job->mon, 8)) bit_set(job->mon, 7);
521 		job->mon[1] >>= 1;
522 
523 		/* Scan for options. */
524 		job->user= nil;
525 		while (q= get_token(&p), *q == '-') {
526 			q++;
527 			if (q[0] == '-' && q+1 == p) {
528 				/* -- */
529 				q= get_token(&p);
530 				break;
531 			}
532 			while (q < p) switch (*q++) {
533 			case 'u':
534 				if (q == p) q= get_token(&p);
535 				if (q == p) goto usage;
536 				memmove(q-1, q, p-q);	/* gross... */
537 				p[-1]= 0;
538 				job->user= q-1;
539 				q= p;
540 				break;
541 			default:
542 			usage:
543 				cronlog(LOG_ERR,
544 			"%s: bad option -%c, good options are: -u username\n",
545 					file, q[-1]);
546 				ok= 0;
547 				goto endtab;
548 			}
549 		}
550 
551 		/* A crontab owned by a user can only do things as that user. */
552 		if (tab->user != nil) job->user= tab->user;
553 
554 		/* Inspect the first character of the command. */
555 		job->cmd= q;
556 		if (q == p || *q == '#') {
557 			/* Rest of the line is empty, i.e. the commands are on
558 			 * the following lines indented by one tab.
559 			 */
560 			while (*p != 0 && *p++ != '\n') {}
561 			if (*p++ != '\t') {
562 				cronlog(LOG_ERR,
563 					"%s: contains an empty command\n",
564 					file);
565 				ok= 0;
566 				goto endtab;
567 			}
568 			while (*p != 0) {
569 				if ((*q = *p++) == '\n') {
570 					if (*p != '\t') break;
571 					p++;
572 				}
573 				q++;
574 			}
575 		} else {
576 			/* The command is on this line.  Alas we must now be
577 			 * backwards compatible and change %'s to newlines.
578 			 */
579 			p= q;
580 			while (*p != 0) {
581 				if ((*q = *p++) == '\n') break;
582 				if (*q == '%') *q= '\n';
583 				q++;
584 			}
585 		}
586 		*q= 0;
587 		job->rtime= now;
588 		job->late= 0;		/* It is on time. */
589 		job->atjob= 0;		/* True cron job. */
590 		job->pid= IDLE_PID;	/* Not running yet. */
591 		tab_reschedule(job);	/* Compute next time to run. */
592 	}
593   endtab:
594 
595 	if (ok) tab->current= 1;
596 }
597 
598 void tab_find_atjob(char *atdir)
599 /* Find the first to be executed AT job and kludge up an crontab job for it.
600  * We set tab->file to "atdir/jobname", tab->data to "atdir/past/jobname",
601  * and job->cmd to "jobname".
602  */
603 {
604 	DIR *spool;
605 	struct dirent *entry;
606 	time_t t0, t1;
607 	struct tm tmnow, tmt1;
608 	static char template[] = "96.365.1546.00";
609 	char firstjob[sizeof(template)];
610 	int i;
611 	crontab_t *tab;
612 	cronjob_t *job;
613 
614 	if ((spool= opendir(atdir)) == nil) return;
615 
616 	tmnow= *localtime(&now);
617 	t0= NEVER;
618 
619 	while ((entry= readdir(spool)) != nil) {
620 		/* Check if the name fits the template. */
621 		for (i= 0; template[i] != 0; i++) {
622 			if (template[i] == '.') {
623 				if (entry->d_name[i] != '.') break;
624 			} else {
625 				if (!isdigit(entry->d_name[i])) break;
626 			}
627 		}
628 		if (template[i] != 0 || entry->d_name[i] != 0) continue;
629 
630 		/* Convert the name to a time.  Careful with the century. */
631 		memset(&tmt1, 0, sizeof(tmt1));
632 		tmt1.tm_year= atoi(entry->d_name+0);
633 		while (tmt1.tm_year < tmnow.tm_year-10) tmt1.tm_year+= 100;
634 		tmt1.tm_mday= 1+atoi(entry->d_name+3);
635 		tmt1.tm_min= atoi(entry->d_name+7);
636 		tmt1.tm_hour= tmt1.tm_min / 100;
637 		tmt1.tm_min%= 100;
638 		tmt1.tm_isdst= -1;
639 		if ((t1= mktime(&tmt1)) == -1) {
640 			/* Illegal time?  Try in winter time. */
641 			tmt1.tm_isdst= 0;
642 			if ((t1= mktime(&tmt1)) == -1) continue;
643 		}
644 		if (t1 < t0) {
645 			t0= t1;
646 			strcpy(firstjob, entry->d_name);
647 		}
648 	}
649 	closedir(spool);
650 
651 	if (t0 == NEVER) return;	/* AT job spool is empty. */
652 
653 	/* Create new table and job structures. */
654 	tab= allocate(sizeof(*tab));
655 	tab->file= allocate((strlen(atdir) + 1 + sizeof(template))
656 						* sizeof(tab->file[0]));
657 	strcpy(tab->file, atdir);
658 	strcat(tab->file, "/");
659 	strcat(tab->file, firstjob);
660 	tab->data= allocate((strlen(atdir) + 6 + sizeof(template))
661 						* sizeof(tab->data[0]));
662 	strcpy(tab->data, atdir);
663 	strcat(tab->data, "/past/");
664 	strcat(tab->data, firstjob);
665 	tab->user= nil;
666 	tab->mtime= 0;
667 	tab->current= 1;
668 	tab->next= crontabs;
669 	crontabs= tab;
670 
671 	tab->jobs= job= allocate(sizeof(*job));
672 	job->next= nil;
673 	job->tab= tab;
674 	job->user= nil;
675 	job->cmd= tab->data + strlen(atdir) + 6;
676 	job->rtime= t0;
677 	job->late= 0;
678 	job->atjob= 1;		/* One AT job disguised as a cron job. */
679 	job->pid= IDLE_PID;
680 
681 	if (job->rtime < next) next= job->rtime;
682 }
683 
684 void tab_purge(void)
685 /* Remove table data that is no longer current.  E.g. a crontab got removed.
686  */
687 {
688 	crontab_t **atab, *tab;
689 	cronjob_t *job;
690 
691 	atab= &crontabs;
692 	while ((tab= *atab) != nil) {
693 		if (tab->current) {
694 			/* Table is fine. */
695 			tab->current= 0;
696 			atab= &tab->next;
697 		} else {
698 			/* Table is not, or no longer valid; delete. */
699 			*atab= tab->next;
700 			while ((job= tab->jobs) != nil) {
701 				tab->jobs= job->next;
702 				deallocate(job);
703 			}
704 			deallocate(tab->data);
705 			deallocate(tab->file);
706 			deallocate(tab->user);
707 			deallocate(tab);
708 		}
709 	}
710 }
711 
712 static cronjob_t *reap_or_find(pid_t pid)
713 /* Find a finished job or search for the next one to run. */
714 {
715 	crontab_t *tab;
716 	cronjob_t *job;
717 	cronjob_t *nextjob;
718 
719 	nextjob= nil;
720 	next= NEVER;
721 	for (tab= crontabs; tab != nil; tab= tab->next) {
722 		for (job= tab->jobs; job != nil; job= job->next) {
723 			if (job->pid == pid) {
724 				job->pid= IDLE_PID;
725 				tab_reschedule(job);
726 			}
727 			if (job->pid != IDLE_PID) continue;
728 			if (job->rtime < next) next= job->rtime;
729 			if (job->rtime <= now) nextjob= job;
730 		}
731 	}
732 	return nextjob;
733 }
734 
735 void tab_reap_job(pid_t pid)
736 /* A job has finished.  Try to find it among the crontab data and reschedule
737  * it.  Recompute time next to run a job.
738  */
739 {
740 	(void) reap_or_find(pid);
741 }
742 
743 cronjob_t *tab_nextjob(void)
744 /* Find a job that should be run now.  If none are found return null.
745  * Update 'next'.
746  */
747 {
748 	return reap_or_find(NO_PID);
749 }
750 
751 static void pr_map(FILE *fp, bitmap_t map)
752 {
753 	int last_bit= -1, bit;
754 	char *sep;
755 
756 	sep= "";
757 	for (bit= 0; bit < 64; bit++) {
758 		if (bit_isset(map, bit)) {
759 			if (last_bit == -1) last_bit= bit;
760 		} else {
761 			if (last_bit != -1) {
762 				fprintf(fp, "%s%d", sep, last_bit);
763 				if (last_bit != bit-1) {
764 					fprintf(fp, "-%d", bit-1);
765 				}
766 				last_bit= -1;
767 				sep= ",";
768 			}
769 		}
770 	}
771 }
772 
773 void tab_print(FILE *fp)
774 /* Print out a stored crontab file for debugging purposes. */
775 {
776 	crontab_t *tab;
777 	cronjob_t *job;
778 	char *p;
779 	struct tm tm;
780 
781 	for (tab= crontabs; tab != nil; tab= tab->next) {
782 		fprintf(fp, "tab->file = \"%s\"\n", tab->file);
783 		fprintf(fp, "tab->user = \"%s\"\n",
784 				tab->user == nil ? "(root)" : tab->user);
785 		fprintf(fp, "tab->mtime = %s", ctime(&tab->mtime));
786 
787 		for (job= tab->jobs; job != nil; job= job->next) {
788 			if (job->atjob) {
789 				fprintf(fp, "AT job");
790 			} else {
791 				pr_map(fp, job->min); fputc(' ', fp);
792 				pr_map(fp, job->hour); fputc(' ', fp);
793 				pr_map(fp, job->mday); fputc(' ', fp);
794 				pr_map(fp, job->mon); fputc(' ', fp);
795 				pr_map(fp, job->wday);
796 			}
797 			if (job->user != nil && job->user != tab->user) {
798 				fprintf(fp, " -u %s", job->user);
799 			}
800 			fprintf(fp, "\n\t");
801 			for (p= job->cmd; *p != 0; p++) {
802 				fputc(*p, fp);
803 				if (*p == '\n') fputc('\t', fp);
804 			}
805 			fputc('\n', fp);
806 			tm= *localtime(&job->rtime);
807 			fprintf(fp, "    rtime = %.24s%s\n", asctime(&tm),
808 				tm.tm_isdst ? " (DST)" : "");
809 			if (job->pid != IDLE_PID) {
810 				fprintf(fp, "    pid = %ld\n", (long) job->pid);
811 			}
812 		}
813 	}
814 }
815 
816 /*
817  * $PchId: tab.c,v 1.5 2000/07/25 22:07:51 philip Exp $
818  */
819