xref: /minix/minix/commands/cron/tab.c (revision 83133719)
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 			log(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 				log(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 		log(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 	log(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 	log(LOG_ERR, "%s: field '%s': values out of the %d-%d allowed range\n",
379 		file, data, min, max);
380 	return 0;
381 }
382 
383 void tab_parse(char *file, char *user)
384 /* Parse a crontab file and add its data to the tables.  Handle errors by
385  * yourself.  Table is owned by 'user' if non-null.
386  */
387 {
388 	crontab_t **atab, *tab;
389 	cronjob_t **ajob, *job;
390 	int fd;
391 	struct stat st;
392 	char *p, *q;
393 	size_t n;
394 	ssize_t r;
395 	int ok, wc;
396 
397 	for (atab= &crontabs; (tab= *atab) != nil; atab= &tab->next) {
398 		if (strcmp(file, tab->file) == 0) break;
399 	}
400 
401 	/* Try to open the file. */
402 	if ((fd= open(file, O_RDONLY)) < 0 || fstat(fd, &st) < 0) {
403 		if (errno != ENOENT) {
404 			log(LOG_ERR, "%s: %s\n", file, strerror(errno));
405 		}
406 		if (fd != -1) close(fd);
407 		return;
408 	}
409 
410 	/* Forget it if the file is awfully big. */
411 	if (st.st_size > TAB_MAX) {
412 		log(LOG_ERR, "%s: %lu bytes is bigger than my %lu limit\n",
413 			file,
414 			(unsigned long) st.st_size,
415 			(unsigned long) TAB_MAX);
416 		return;
417 	}
418 
419 	/* If the file is the same as before then don't bother. */
420 	if (tab != nil && st.st_mtime == tab->mtime) {
421 		close(fd);
422 		tab->current= 1;
423 		return;
424 	}
425 
426 	/* Create a new table structure. */
427 	tab= allocate(sizeof(*tab));
428 	tab->file= allocate((strlen(file) + 1) * sizeof(tab->file[0]));
429 	strcpy(tab->file, file);
430 	tab->user= nil;
431 	if (user != nil) {
432 		tab->user= allocate((strlen(user) + 1) * sizeof(tab->user[0]));
433 		strcpy(tab->user, user);
434 	}
435 	tab->data= allocate((st.st_size + 1) * sizeof(tab->data[0]));
436 	tab->jobs= nil;
437 	tab->mtime= st.st_mtime;
438 	tab->current= 0;
439 	tab->next= *atab;
440 	*atab= tab;
441 
442 	/* Pull a new table in core. */
443 	n= 0;
444 	while (n < st.st_size) {
445 		if ((r = read(fd, tab->data + n, st.st_size - n)) < 0) {
446 			log(LOG_CRIT, "%s: %s", file, strerror(errno));
447 			close(fd);
448 			return;
449 		}
450 		if (r == 0) break;
451 		n+= r;
452 	}
453 	close(fd);
454 	tab->data[n]= 0;
455 	if (strlen(tab->data) < n) {
456 		log(LOG_ERR, "%s contains a null character\n", file);
457 		return;
458 	}
459 
460 	/* Parse the file. */
461 	ajob= &tab->jobs;
462 	p= tab->data;
463 	ok= 1;
464 	while (ok && *p != 0) {
465 		q= get_token(&p);
466 		if (*q == '#' || q == p) {
467 			/* Comment or empty. */
468 			while (*p != 0 && *p++ != '\n') {}
469 			continue;
470 		}
471 
472 		/* One new job coming up. */
473 		*ajob= job= allocate(sizeof(*job));
474 		*(ajob= &job->next)= nil;
475 		job->tab= tab;
476 
477 		if (!range_parse(file, q, job->min, 0, 59, &wc)) {
478 			ok= 0;
479 			break;
480 		}
481 
482 		q= get_token(&p);
483 		if (!range_parse(file, q, job->hour, 0, 23, &wc)) {
484 			ok= 0;
485 			break;
486 		}
487 
488 		q= get_token(&p);
489 		if (!range_parse(file, q, job->mday, 1, 31, &wc)) {
490 			ok= 0;
491 			break;
492 		}
493 		job->do_mday= !wc;
494 
495 		q= get_token(&p);
496 		if (!range_parse(file, q, job->mon, 1, 12, &wc)) {
497 			ok= 0;
498 			break;
499 		}
500 		job->do_mday |= !wc;
501 
502 		q= get_token(&p);
503 		if (!range_parse(file, q, job->wday, 0, 7, &wc)) {
504 			ok= 0;
505 			break;
506 		}
507 		job->do_wday= !wc;
508 
509 		/* 7 is Sunday, but 0 is a common mistake because it is in the
510 		 * tm_wday range.  We allow and even prefer it internally.
511 		 */
512 		if (bit_isset(job->wday, 7)) {
513 			bit_clr(job->wday, 7);
514 			bit_set(job->wday, 0);
515 		}
516 
517 		/* The month range is 1-12, but tm_mon likes 0-11. */
518 		job->mon[0] >>= 1;
519 		if (bit_isset(job->mon, 8)) bit_set(job->mon, 7);
520 		job->mon[1] >>= 1;
521 
522 		/* Scan for options. */
523 		job->user= nil;
524 		while (q= get_token(&p), *q == '-') {
525 			q++;
526 			if (q[0] == '-' && q+1 == p) {
527 				/* -- */
528 				q= get_token(&p);
529 				break;
530 			}
531 			while (q < p) switch (*q++) {
532 			case 'u':
533 				if (q == p) q= get_token(&p);
534 				if (q == p) goto usage;
535 				memmove(q-1, q, p-q);	/* gross... */
536 				p[-1]= 0;
537 				job->user= q-1;
538 				q= p;
539 				break;
540 			default:
541 			usage:
542 				log(LOG_ERR,
543 			"%s: bad option -%c, good options are: -u username\n",
544 					file, q[-1]);
545 				ok= 0;
546 				goto endtab;
547 			}
548 		}
549 
550 		/* A crontab owned by a user can only do things as that user. */
551 		if (tab->user != nil) job->user= tab->user;
552 
553 		/* Inspect the first character of the command. */
554 		job->cmd= q;
555 		if (q == p || *q == '#') {
556 			/* Rest of the line is empty, i.e. the commands are on
557 			 * the following lines indented by one tab.
558 			 */
559 			while (*p != 0 && *p++ != '\n') {}
560 			if (*p++ != '\t') {
561 				log(LOG_ERR, "%s: contains an empty command\n",
562 					file);
563 				ok= 0;
564 				goto endtab;
565 			}
566 			while (*p != 0) {
567 				if ((*q = *p++) == '\n') {
568 					if (*p != '\t') break;
569 					p++;
570 				}
571 				q++;
572 			}
573 		} else {
574 			/* The command is on this line.  Alas we must now be
575 			 * backwards compatible and change %'s to newlines.
576 			 */
577 			p= q;
578 			while (*p != 0) {
579 				if ((*q = *p++) == '\n') break;
580 				if (*q == '%') *q= '\n';
581 				q++;
582 			}
583 		}
584 		*q= 0;
585 		job->rtime= now;
586 		job->late= 0;		/* It is on time. */
587 		job->atjob= 0;		/* True cron job. */
588 		job->pid= IDLE_PID;	/* Not running yet. */
589 		tab_reschedule(job);	/* Compute next time to run. */
590 	}
591   endtab:
592 
593 	if (ok) tab->current= 1;
594 }
595 
596 void tab_find_atjob(char *atdir)
597 /* Find the first to be executed AT job and kludge up an crontab job for it.
598  * We set tab->file to "atdir/jobname", tab->data to "atdir/past/jobname",
599  * and job->cmd to "jobname".
600  */
601 {
602 	DIR *spool;
603 	struct dirent *entry;
604 	time_t t0, t1;
605 	struct tm tmnow, tmt1;
606 	static char template[] = "96.365.1546.00";
607 	char firstjob[sizeof(template)];
608 	int i;
609 	crontab_t *tab;
610 	cronjob_t *job;
611 
612 	if ((spool= opendir(atdir)) == nil) return;
613 
614 	tmnow= *localtime(&now);
615 	t0= NEVER;
616 
617 	while ((entry= readdir(spool)) != nil) {
618 		/* Check if the name fits the template. */
619 		for (i= 0; template[i] != 0; i++) {
620 			if (template[i] == '.') {
621 				if (entry->d_name[i] != '.') break;
622 			} else {
623 				if (!isdigit(entry->d_name[i])) break;
624 			}
625 		}
626 		if (template[i] != 0 || entry->d_name[i] != 0) continue;
627 
628 		/* Convert the name to a time.  Careful with the century. */
629 		memset(&tmt1, 0, sizeof(tmt1));
630 		tmt1.tm_year= atoi(entry->d_name+0);
631 		while (tmt1.tm_year < tmnow.tm_year-10) tmt1.tm_year+= 100;
632 		tmt1.tm_mday= 1+atoi(entry->d_name+3);
633 		tmt1.tm_min= atoi(entry->d_name+7);
634 		tmt1.tm_hour= tmt1.tm_min / 100;
635 		tmt1.tm_min%= 100;
636 		tmt1.tm_isdst= -1;
637 		if ((t1= mktime(&tmt1)) == -1) {
638 			/* Illegal time?  Try in winter time. */
639 			tmt1.tm_isdst= 0;
640 			if ((t1= mktime(&tmt1)) == -1) continue;
641 		}
642 		if (t1 < t0) {
643 			t0= t1;
644 			strcpy(firstjob, entry->d_name);
645 		}
646 	}
647 	closedir(spool);
648 
649 	if (t0 == NEVER) return;	/* AT job spool is empty. */
650 
651 	/* Create new table and job structures. */
652 	tab= allocate(sizeof(*tab));
653 	tab->file= allocate((strlen(atdir) + 1 + sizeof(template))
654 						* sizeof(tab->file[0]));
655 	strcpy(tab->file, atdir);
656 	strcat(tab->file, "/");
657 	strcat(tab->file, firstjob);
658 	tab->data= allocate((strlen(atdir) + 6 + sizeof(template))
659 						* sizeof(tab->data[0]));
660 	strcpy(tab->data, atdir);
661 	strcat(tab->data, "/past/");
662 	strcat(tab->data, firstjob);
663 	tab->user= nil;
664 	tab->mtime= 0;
665 	tab->current= 1;
666 	tab->next= crontabs;
667 	crontabs= tab;
668 
669 	tab->jobs= job= allocate(sizeof(*job));
670 	job->next= nil;
671 	job->tab= tab;
672 	job->user= nil;
673 	job->cmd= tab->data + strlen(atdir) + 6;
674 	job->rtime= t0;
675 	job->late= 0;
676 	job->atjob= 1;		/* One AT job disguised as a cron job. */
677 	job->pid= IDLE_PID;
678 
679 	if (job->rtime < next) next= job->rtime;
680 }
681 
682 void tab_purge(void)
683 /* Remove table data that is no longer current.  E.g. a crontab got removed.
684  */
685 {
686 	crontab_t **atab, *tab;
687 	cronjob_t *job;
688 
689 	atab= &crontabs;
690 	while ((tab= *atab) != nil) {
691 		if (tab->current) {
692 			/* Table is fine. */
693 			tab->current= 0;
694 			atab= &tab->next;
695 		} else {
696 			/* Table is not, or no longer valid; delete. */
697 			*atab= tab->next;
698 			while ((job= tab->jobs) != nil) {
699 				tab->jobs= job->next;
700 				deallocate(job);
701 			}
702 			deallocate(tab->data);
703 			deallocate(tab->file);
704 			deallocate(tab->user);
705 			deallocate(tab);
706 		}
707 	}
708 }
709 
710 static cronjob_t *reap_or_find(pid_t pid)
711 /* Find a finished job or search for the next one to run. */
712 {
713 	crontab_t *tab;
714 	cronjob_t *job;
715 	cronjob_t *nextjob;
716 
717 	nextjob= nil;
718 	next= NEVER;
719 	for (tab= crontabs; tab != nil; tab= tab->next) {
720 		for (job= tab->jobs; job != nil; job= job->next) {
721 			if (job->pid == pid) {
722 				job->pid= IDLE_PID;
723 				tab_reschedule(job);
724 			}
725 			if (job->pid != IDLE_PID) continue;
726 			if (job->rtime < next) next= job->rtime;
727 			if (job->rtime <= now) nextjob= job;
728 		}
729 	}
730 	return nextjob;
731 }
732 
733 void tab_reap_job(pid_t pid)
734 /* A job has finished.  Try to find it among the crontab data and reschedule
735  * it.  Recompute time next to run a job.
736  */
737 {
738 	(void) reap_or_find(pid);
739 }
740 
741 cronjob_t *tab_nextjob(void)
742 /* Find a job that should be run now.  If none are found return null.
743  * Update 'next'.
744  */
745 {
746 	return reap_or_find(NO_PID);
747 }
748 
749 static void pr_map(FILE *fp, bitmap_t map)
750 {
751 	int last_bit= -1, bit;
752 	char *sep;
753 
754 	sep= "";
755 	for (bit= 0; bit < 64; bit++) {
756 		if (bit_isset(map, bit)) {
757 			if (last_bit == -1) last_bit= bit;
758 		} else {
759 			if (last_bit != -1) {
760 				fprintf(fp, "%s%d", sep, last_bit);
761 				if (last_bit != bit-1) {
762 					fprintf(fp, "-%d", bit-1);
763 				}
764 				last_bit= -1;
765 				sep= ",";
766 			}
767 		}
768 	}
769 }
770 
771 void tab_print(FILE *fp)
772 /* Print out a stored crontab file for debugging purposes. */
773 {
774 	crontab_t *tab;
775 	cronjob_t *job;
776 	char *p;
777 	struct tm tm;
778 
779 	for (tab= crontabs; tab != nil; tab= tab->next) {
780 		fprintf(fp, "tab->file = \"%s\"\n", tab->file);
781 		fprintf(fp, "tab->user = \"%s\"\n",
782 				tab->user == nil ? "(root)" : tab->user);
783 		fprintf(fp, "tab->mtime = %s", ctime(&tab->mtime));
784 
785 		for (job= tab->jobs; job != nil; job= job->next) {
786 			if (job->atjob) {
787 				fprintf(fp, "AT job");
788 			} else {
789 				pr_map(fp, job->min); fputc(' ', fp);
790 				pr_map(fp, job->hour); fputc(' ', fp);
791 				pr_map(fp, job->mday); fputc(' ', fp);
792 				pr_map(fp, job->mon); fputc(' ', fp);
793 				pr_map(fp, job->wday);
794 			}
795 			if (job->user != nil && job->user != tab->user) {
796 				fprintf(fp, " -u %s", job->user);
797 			}
798 			fprintf(fp, "\n\t");
799 			for (p= job->cmd; *p != 0; p++) {
800 				fputc(*p, fp);
801 				if (*p == '\n') fputc('\t', fp);
802 			}
803 			fputc('\n', fp);
804 			tm= *localtime(&job->rtime);
805 			fprintf(fp, "    rtime = %.24s%s\n", asctime(&tm),
806 				tm.tm_isdst ? " (DST)" : "");
807 			if (job->pid != IDLE_PID) {
808 				fprintf(fp, "    pid = %ld\n", (long) job->pid);
809 			}
810 		}
811 	}
812 }
813 
814 /*
815  * $PchId: tab.c,v 1.5 2000/07/25 22:07:51 philip Exp $
816  */
817