iso9660fs: initialize buffer cache
[minix.git] / commands / cron / tab.c
blob2cdce435bdc2d503bd9dd4a29b74d7805dfc9c2c
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"
24 static int nextbit(bitmap_t map, int bit)
25 /* Return the next bit set in 'map' from 'bit' onwards, cyclic. */
27 for (;;) {
28 bit= (bit+1) & 63;
29 if (bit_isset(map, bit)) break;
31 return bit;
34 void tab_reschedule(cronjob_t *job)
35 /* Reschedule one job. Compute the next time to run the job in job->rtime.
38 struct tm prevtm, nexttm, tmptm;
39 time_t nodst_rtime, dst_rtime;
41 /* AT jobs are only run once. */
42 if (job->atjob) { job->rtime= NEVER; return; }
44 /* Was the job scheduled late last time? */
45 if (job->late) job->rtime= now;
47 prevtm= *localtime(&job->rtime);
48 prevtm.tm_sec= 0;
50 nexttm= prevtm;
51 nexttm.tm_min++; /* Minimal increment */
53 for (;;)
55 if (nexttm.tm_min > 59)
57 nexttm.tm_min= 0;
58 nexttm.tm_hour++;
60 if (nexttm.tm_hour > 23)
62 nexttm.tm_min= 0;
63 nexttm.tm_hour= 0;
64 nexttm.tm_mday++;
66 if (nexttm.tm_mday > 31)
68 nexttm.tm_hour= nexttm.tm_min= 0;
69 nexttm.tm_mday= 1;
70 nexttm.tm_mon++;
72 if (nexttm.tm_mon >= 12)
74 nexttm.tm_hour= nexttm.tm_min= 0;
75 nexttm.tm_mday= 1;
76 nexttm.tm_mon= 0;
77 nexttm.tm_year++;
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.
89 if (nexttm.tm_year-prevtm.tm_year > 2*4)
91 job->rtime= NEVER;
92 return; /* Impossible combination */
95 if (!job->do_wday)
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.
101 if (!bit_isset(job->mon, nexttm.tm_mon))
103 /* Clear other fields */
104 nexttm.tm_mday= 1;
105 nexttm.tm_hour= nexttm.tm_min= 0;
107 nexttm.tm_mon++;
108 continue;
111 /* Verify mday */
112 if (!bit_isset(job->mday, nexttm.tm_mday))
114 /* Clear other fields */
115 nexttm.tm_hour= nexttm.tm_min= 0;
117 nexttm.tm_mday++;
118 continue;
122 /* Verify hour */
123 if (!bit_isset(job->hour, nexttm.tm_hour))
125 /* Clear tm_min field */
126 nexttm.tm_min= 0;
128 nexttm.tm_hour++;
129 continue;
132 /* Verify min */
133 if (!bit_isset(job->min, nexttm.tm_min))
135 nexttm.tm_min++;
136 continue;
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;
161 tmptm= *localtime(&job->rtime);
162 if (tmptm.tm_hour != nexttm.tm_hour ||
163 tmptm.tm_min != nexttm.tm_min)
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;
186 tmptm= *localtime(&job->rtime);
187 if (tmptm.tm_hour != nexttm.tm_hour ||
188 tmptm.tm_min != nexttm.tm_min)
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.
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
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)
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;
222 /* Check tm_wday */
223 if (job->do_wday && bit_isset(job->wday, tmptm.tm_wday))
225 /* OK, wday matched */
226 break;
229 /* Check tm_mday */
230 if (job->do_mday && bit_isset(job->mon, tmptm.tm_mon) &&
231 bit_isset(job->mday, tmptm.tm_mday))
233 /* OK, mon and mday matched */
234 break;
237 if (!job->do_wday && !job->do_mday)
239 /* No need to match wday and mday */
240 break;
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++;
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
262 /* Is job issuing lagging behind with the progress of time? */
263 job->late= (job->rtime < now);
265 /* The result is in job->rtime. */
266 if (job->rtime < next) next= job->rtime;
269 #define isdigit(c) ((unsigned) ((c) - '0') < 10)
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.
277 char *start, *p;
279 p= *ptr;
280 while (*p == ' ' || *p == '\t') p++;
282 start= p;
283 while (*p != ' ' && *p != '\t' && *p != '\n' && *p != 0) p++;
284 *ptr= p;
285 return start;
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.
298 char *p;
299 int end;
300 int n, m;
302 /* Clear all bits. */
303 for (n= 0; n < 8; n++) map[n]= 0;
305 p= data;
306 while (*p != ' ' && *p != '\t' && *p != '\n' && *p != 0) p++;
307 end= *p;
308 *p= 0;
309 p= data;
311 if (*p == 0) {
312 log(LOG_ERR, "%s: not enough time fields\n", file);
313 return 0;
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;
323 *wildcard= 0;
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));
338 if (n < min) goto range;
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);
368 if (*p == 0) break;
369 if (*p++ != ',') goto syntax;
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;
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.
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;
397 for (atab= &crontabs; (tab= *atab) != nil; atab= &tab->next) {
398 if (strcmp(file, tab->file) == 0) break;
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));
406 if (fd != -1) close(fd);
407 return;
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;
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;
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);
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;
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;
450 if (r == 0) break;
451 n+= r;
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;
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;
472 /* One new job coming up. */
473 *ajob= job= allocate(sizeof(*job));
474 *(ajob= &job->next)= nil;
475 job->tab= tab;
477 if (!range_parse(file, q, job->min, 0, 59, &wc)) {
478 ok= 0;
479 break;
482 q= get_token(&p);
483 if (!range_parse(file, q, job->hour, 0, 23, &wc)) {
484 ok= 0;
485 break;
488 q= get_token(&p);
489 if (!range_parse(file, q, job->mday, 1, 31, &wc)) {
490 ok= 0;
491 break;
493 job->do_mday= !wc;
495 q= get_token(&p);
496 if (!range_parse(file, q, job->mon, 1, 12, &wc)) {
497 ok= 0;
498 break;
500 job->do_mday |= !wc;
502 q= get_token(&p);
503 if (!range_parse(file, q, job->wday, 0, 7, &wc)) {
504 ok= 0;
505 break;
507 job->do_wday= !wc;
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.
512 if (bit_isset(job->wday, 7)) {
513 bit_clr(job->wday, 7);
514 bit_set(job->wday, 0);
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;
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;
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;
550 /* A crontab owned by a user can only do things as that user. */
551 if (tab->user != nil) job->user= tab->user;
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.
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;
566 while (*p != 0) {
567 if ((*q = *p++) == '\n') {
568 if (*p != '\t') break;
569 p++;
571 q++;
573 } else {
574 /* The command is on this line. Alas we must now be
575 * backwards compatible and change %'s to newlines.
577 p= q;
578 while (*p != 0) {
579 if ((*q = *p++) == '\n') break;
580 if (*q == '%') *q= '\n';
581 q++;
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. */
591 endtab:
593 if (ok) tab->current= 1;
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".
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;
612 if ((spool= opendir(atdir)) == nil) return;
614 tmnow= *localtime(&now);
615 t0= NEVER;
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;
626 if (template[i] != 0 || entry->d_name[i] != 0) continue;
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;
642 if (t1 < t0) {
643 t0= t1;
644 strcpy(firstjob, entry->d_name);
647 closedir(spool);
649 if (t0 == NEVER) return; /* AT job spool is empty. */
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;
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;
679 if (job->rtime < next) next= job->rtime;
682 void tab_purge(void)
683 /* Remove table data that is no longer current. E.g. a crontab got removed.
686 crontab_t **atab, *tab;
687 cronjob_t *job;
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);
702 deallocate(tab->data);
703 deallocate(tab->file);
704 deallocate(tab->user);
705 deallocate(tab);
710 static cronjob_t *reap_or_find(pid_t pid)
711 /* Find a finished job or search for the next one to run. */
713 crontab_t *tab;
714 cronjob_t *job;
715 cronjob_t *nextjob;
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);
725 if (job->pid != IDLE_PID) continue;
726 if (job->rtime < next) next= job->rtime;
727 if (job->rtime <= now) nextjob= job;
730 return nextjob;
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.
738 (void) reap_or_find(pid);
741 cronjob_t *tab_nextjob(void)
742 /* Find a job that should be run now. If none are found return null.
743 * Update 'next'.
746 return reap_or_find(NO_PID);
749 static void pr_map(FILE *fp, bitmap_t map)
751 int last_bit= -1, bit;
752 char *sep;
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);
764 last_bit= -1;
765 sep= ",";
771 void tab_print(FILE *fp)
772 /* Print out a stored crontab file for debugging purposes. */
774 crontab_t *tab;
775 cronjob_t *job;
776 char *p;
777 struct tm tm;
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));
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);
795 if (job->user != nil && job->user != tab->user) {
796 fprintf(fp, " -u %s", job->user);
798 fprintf(fp, "\n\t");
799 for (p= job->cmd; *p != 0; p++) {
800 fputc(*p, fp);
801 if (*p == '\n') fputc('\t', fp);
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);
815 * $PchId: tab.c,v 1.5 2000/07/25 22:07:51 philip Exp $