Remove building with NOCRYPTO option
[minix.git] / minix / commands / cron / tab.c
blobcdfcf453b4987d7193f188fa5ae48be8129ef325
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 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;
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 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;
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 cronlog(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 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;
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.
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;
398 for (atab= &crontabs; (tab= *atab) != nil; atab= &tab->next) {
399 if (strcmp(file, tab->file) == 0) break;
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));
407 if (fd != -1) close(fd);
408 return;
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;
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;
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);
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;
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;
451 if (r == 0) break;
452 n+= r;
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;
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;
473 /* One new job coming up. */
474 *ajob= job= allocate(sizeof(*job));
475 *(ajob= &job->next)= nil;
476 job->tab= tab;
478 if (!range_parse(file, q, job->min, 0, 59, &wc)) {
479 ok= 0;
480 break;
483 q= get_token(&p);
484 if (!range_parse(file, q, job->hour, 0, 23, &wc)) {
485 ok= 0;
486 break;
489 q= get_token(&p);
490 if (!range_parse(file, q, job->mday, 1, 31, &wc)) {
491 ok= 0;
492 break;
494 job->do_mday= !wc;
496 q= get_token(&p);
497 if (!range_parse(file, q, job->mon, 1, 12, &wc)) {
498 ok= 0;
499 break;
501 job->do_mday |= !wc;
503 q= get_token(&p);
504 if (!range_parse(file, q, job->wday, 0, 7, &wc)) {
505 ok= 0;
506 break;
508 job->do_wday= !wc;
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.
513 if (bit_isset(job->wday, 7)) {
514 bit_clr(job->wday, 7);
515 bit_set(job->wday, 0);
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;
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;
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;
551 /* A crontab owned by a user can only do things as that user. */
552 if (tab->user != nil) job->user= tab->user;
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.
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;
568 while (*p != 0) {
569 if ((*q = *p++) == '\n') {
570 if (*p != '\t') break;
571 p++;
573 q++;
575 } else {
576 /* The command is on this line. Alas we must now be
577 * backwards compatible and change %'s to newlines.
579 p= q;
580 while (*p != 0) {
581 if ((*q = *p++) == '\n') break;
582 if (*q == '%') *q= '\n';
583 q++;
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. */
593 endtab:
595 if (ok) tab->current= 1;
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".
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;
614 if ((spool= opendir(atdir)) == nil) return;
616 tmnow= *localtime(&now);
617 t0= NEVER;
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;
628 if (template[i] != 0 || entry->d_name[i] != 0) continue;
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;
644 if (t1 < t0) {
645 t0= t1;
646 strcpy(firstjob, entry->d_name);
649 closedir(spool);
651 if (t0 == NEVER) return; /* AT job spool is empty. */
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;
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;
681 if (job->rtime < next) next= job->rtime;
684 void tab_purge(void)
685 /* Remove table data that is no longer current. E.g. a crontab got removed.
688 crontab_t **atab, *tab;
689 cronjob_t *job;
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);
704 deallocate(tab->data);
705 deallocate(tab->file);
706 deallocate(tab->user);
707 deallocate(tab);
712 static cronjob_t *reap_or_find(pid_t pid)
713 /* Find a finished job or search for the next one to run. */
715 crontab_t *tab;
716 cronjob_t *job;
717 cronjob_t *nextjob;
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);
727 if (job->pid != IDLE_PID) continue;
728 if (job->rtime < next) next= job->rtime;
729 if (job->rtime <= now) nextjob= job;
732 return nextjob;
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.
740 (void) reap_or_find(pid);
743 cronjob_t *tab_nextjob(void)
744 /* Find a job that should be run now. If none are found return null.
745 * Update 'next'.
748 return reap_or_find(NO_PID);
751 static void pr_map(FILE *fp, bitmap_t map)
753 int last_bit= -1, bit;
754 char *sep;
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);
766 last_bit= -1;
767 sep= ",";
773 void tab_print(FILE *fp)
774 /* Print out a stored crontab file for debugging purposes. */
776 crontab_t *tab;
777 cronjob_t *job;
778 char *p;
779 struct tm tm;
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));
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);
797 if (job->user != nil && job->user != tab->user) {
798 fprintf(fp, " -u %s", job->user);
800 fprintf(fp, "\n\t");
801 for (p= job->cmd; *p != 0; p++) {
802 fputc(*p, fp);
803 if (*p == '\n') fputc('\t', fp);
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);
817 * $PchId: tab.c,v 1.5 2000/07/25 22:07:51 philip Exp $