]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - usr.sbin/cron/cron/cron.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.git] / usr.sbin / cron / cron / cron.c
1 /* Copyright 1988,1990,1993,1994 by Paul Vixie
2  * All rights reserved
3  *
4  * Distribute freely, except: don't remove my name from the source or
5  * documentation (don't take credit for my work), mark your changes (don't
6  * get me blamed for your possible bugs), don't alter or remove this
7  * notice.  May be sold if buildable source is provided to buyer.  No
8  * warrantee of any kind, express or implied, is included with this
9  * software; use at your own risk, responsibility for damages (if any) to
10  * anyone resulting from the use of this software rests entirely with the
11  * user.
12  *
13  * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
14  * I'll try to keep a version up to date.  I can be reached as follows:
15  * Paul Vixie          <paul@vix.com>          uunet!decwrl!vixie!paul
16  */
17
18 #if !defined(lint) && !defined(LINT)
19 static const char rcsid[] =
20   "$FreeBSD$";
21 #endif
22
23 #define MAIN_PROGRAM
24
25
26 #include "cron.h"
27 #include <sys/mman.h>
28 #include <sys/signal.h>
29 #if SYS_TIME_H
30 # include <sys/time.h>
31 #else
32 # include <time.h>
33 #endif
34
35
36 static  void    usage(void),
37                 run_reboot_jobs(cron_db *),
38                 cron_tick(cron_db *, int),
39                 cron_sync(int),
40                 cron_sleep(cron_db *, int),
41                 cron_clean(cron_db *),
42 #ifdef USE_SIGCHLD
43                 sigchld_handler(int),
44 #endif
45                 sighup_handler(int),
46                 parse_args(int c, char *v[]);
47
48 static int      run_at_secres(cron_db *);
49
50 static time_t   last_time = 0;
51 static int      dst_enabled = 0;
52 struct pidfh *pfh;
53
54 static void
55 usage() {
56 #if DEBUGGING
57     char **dflags;
58 #endif
59
60         fprintf(stderr, "usage: cron [-j jitter] [-J rootjitter] "
61                         "[-m mailto] [-s] [-o] [-x debugflag[,...]]\n");
62 #if DEBUGGING
63         fprintf(stderr, "\ndebugflags: ");
64
65         for(dflags = DebugFlagNames; *dflags; dflags++) {
66                 fprintf(stderr, "%s ", *dflags);
67         }
68         fprintf(stderr, "\n");
69 #endif
70
71         exit(ERROR_EXIT);
72 }
73
74 static void
75 open_pidfile(void)
76 {
77         char    pidfile[MAX_FNAME];
78         char    buf[MAX_TEMPSTR];
79         int     otherpid;
80
81         (void) snprintf(pidfile, sizeof(pidfile), PIDFILE, PIDDIR);
82         pfh = pidfile_open(pidfile, 0600, &otherpid);
83         if (pfh == NULL) {
84                 if (errno == EEXIST) {
85                         snprintf(buf, sizeof(buf),
86                             "cron already running, pid: %d", otherpid);
87                 } else {
88                         snprintf(buf, sizeof(buf),
89                             "can't open or create %s: %s", pidfile,
90                             strerror(errno));
91                 }
92                 log_it("CRON", getpid(), "DEATH", buf);
93                 errx(ERROR_EXIT, "%s", buf);
94         }
95 }
96
97 int
98 main(argc, argv)
99         int     argc;
100         char    *argv[];
101 {
102         cron_db database;
103         int runnum;
104         int secres1, secres2;
105         struct tm *tm;
106
107         ProgramName = argv[0];
108
109 #if defined(BSD)
110         setlinebuf(stdout);
111         setlinebuf(stderr);
112 #endif
113
114         parse_args(argc, argv);
115
116 #ifdef USE_SIGCHLD
117         (void) signal(SIGCHLD, sigchld_handler);
118 #else
119         (void) signal(SIGCLD, SIG_IGN);
120 #endif
121         (void) signal(SIGHUP, sighup_handler);
122
123         open_pidfile();
124         set_cron_uid();
125         set_cron_cwd();
126
127 #if defined(POSIX)
128         setenv("PATH", _PATH_DEFPATH, 1);
129 #endif
130
131         /* if there are no debug flags turned on, fork as a daemon should.
132          */
133 # if DEBUGGING
134         if (DebugFlags) {
135 # else
136         if (0) {
137 # endif
138                 (void) fprintf(stderr, "[%d] cron started\n", getpid());
139         } else {
140                 if (daemon(1, 0) == -1) {
141                         pidfile_remove(pfh);
142                         log_it("CRON",getpid(),"DEATH","can't become daemon");
143                         exit(0);
144                 }
145         }
146
147         if (madvise(NULL, 0, MADV_PROTECT) != 0)
148                 log_it("CRON", getpid(), "WARNING", "madvise() failed");
149
150         pidfile_write(pfh);
151         database.head = NULL;
152         database.tail = NULL;
153         database.mtime = (time_t) 0;
154         load_database(&database);
155         secres1 = secres2 = run_at_secres(&database);
156         run_reboot_jobs(&database);
157         cron_sync(secres1);
158         runnum = 0;
159         while (TRUE) {
160 # if DEBUGGING
161             /* if (!(DebugFlags & DTEST)) */
162 # endif /*DEBUGGING*/
163                         cron_sleep(&database, secres1);
164
165                 if (secres1 == 0 || runnum % 60 == 0) {
166                         load_database(&database);
167                         secres2 = run_at_secres(&database);
168                         if (secres2 != secres1) {
169                                 secres1 = secres2;
170                                 if (secres1 != 0) {
171                                         runnum = 0;
172                                 } else {
173                                         /*
174                                          * Going from 1 sec to 60 sec res. If we
175                                          * are already at minute's boundary, so
176                                          * let it run, otherwise schedule for the
177                                          * next minute.
178                                          */
179                                         tm = localtime(&TargetTime);
180                                         if (tm->tm_sec > 0)  {
181                                                 cron_sync(secres2);
182                                                 continue;
183                                         }
184                                 }
185                         }
186                 }
187
188                 /* do this iteration
189                  */
190                 cron_tick(&database, secres1);
191
192                 /* sleep 1 or 60 seconds
193                  */
194                 TargetTime += (secres1 != 0) ? 1 : 60;
195                 runnum += 1;
196         }
197 }
198
199
200 static void
201 run_reboot_jobs(db)
202         cron_db *db;
203 {
204         register user           *u;
205         register entry          *e;
206
207         for (u = db->head;  u != NULL;  u = u->next) {
208                 for (e = u->crontab;  e != NULL;  e = e->next) {
209                         if (e->flags & WHEN_REBOOT) {
210                                 job_add(e, u);
211                         }
212                 }
213         }
214         (void) job_runqueue();
215 }
216
217
218 static void
219 cron_tick(cron_db *db, int secres)
220 {
221         static struct tm        lasttm;
222         static time_t   diff = 0, /* time difference in seconds from the last offset change */
223                 difflimit = 0; /* end point for the time zone correction */
224         struct tm       otztm; /* time in the old time zone */
225         int             otzsecond, otzminute, otzhour, otzdom, otzmonth, otzdow;
226         register struct tm      *tm = localtime(&TargetTime);
227         register int            second, minute, hour, dom, month, dow;
228         register user           *u;
229         register entry          *e;
230
231         /* make 0-based values out of these so we can use them as indicies
232          */
233         second = (secres == 0) ? 0 : tm->tm_sec -FIRST_SECOND;
234         minute = tm->tm_min -FIRST_MINUTE;
235         hour = tm->tm_hour -FIRST_HOUR;
236         dom = tm->tm_mday -FIRST_DOM;
237         month = tm->tm_mon +1 /* 0..11 -> 1..12 */ -FIRST_MONTH;
238         dow = tm->tm_wday -FIRST_DOW;
239
240         Debug(DSCH, ("[%d] tick(%d,%d,%d,%d,%d,%d)\n",
241                 getpid(), second, minute, hour, dom, month, dow))
242
243         if (dst_enabled && last_time != 0 
244         && TargetTime > last_time /* exclude stepping back */
245         && tm->tm_gmtoff != lasttm.tm_gmtoff ) {
246
247                 diff = tm->tm_gmtoff - lasttm.tm_gmtoff;
248
249                 if ( diff > 0 ) { /* ST->DST */
250                         /* mark jobs for an earlier run */
251                         difflimit = TargetTime + diff;
252                         for (u = db->head;  u != NULL;  u = u->next) {
253                                 for (e = u->crontab;  e != NULL;  e = e->next) {
254                                         e->flags &= ~NOT_UNTIL;
255                                         if ( e->lastrun >= TargetTime )
256                                                 e->lastrun = 0;
257                                         /* not include the ends of hourly ranges */
258                                         if ( e->lastrun < TargetTime - 3600 )
259                                                 e->flags |= RUN_AT;
260                                         else
261                                                 e->flags &= ~RUN_AT;
262                                 }
263                         }
264                 } else { /* diff < 0 : DST->ST */
265                         /* mark jobs for skipping */
266                         difflimit = TargetTime - diff;
267                         for (u = db->head;  u != NULL;  u = u->next) {
268                                 for (e = u->crontab;  e != NULL;  e = e->next) {
269                                         e->flags |= NOT_UNTIL;
270                                         e->flags &= ~RUN_AT;
271                                 }
272                         }
273                 }
274         }
275
276         if (diff != 0) {
277                 /* if the time was reset of the end of special zone is reached */
278                 if (last_time == 0 || TargetTime >= difflimit) {
279                         /* disable the TZ switch checks */
280                         diff = 0;
281                         difflimit = 0;
282                         for (u = db->head;  u != NULL;  u = u->next) {
283                                 for (e = u->crontab;  e != NULL;  e = e->next) {
284                                         e->flags &= ~(RUN_AT|NOT_UNTIL);
285                                 }
286                         }
287                 } else {
288                         /* get the time in the old time zone */
289                         time_t difftime = TargetTime + tm->tm_gmtoff - diff;
290                         gmtime_r(&difftime, &otztm);
291
292                         /* make 0-based values out of these so we can use them as indicies
293                          */
294                         otzsecond = (secres == 0) ? 0 : otztm.tm_sec -FIRST_SECOND;
295                         otzminute = otztm.tm_min -FIRST_MINUTE;
296                         otzhour = otztm.tm_hour -FIRST_HOUR;
297                         otzdom = otztm.tm_mday -FIRST_DOM;
298                         otzmonth = otztm.tm_mon +1 /* 0..11 -> 1..12 */ -FIRST_MONTH;
299                         otzdow = otztm.tm_wday -FIRST_DOW;
300                 }
301         }
302
303         /* the dom/dow situation is odd.  '* * 1,15 * Sun' will run on the
304          * first and fifteenth AND every Sunday;  '* * * * Sun' will run *only*
305          * on Sundays;  '* * 1,15 * *' will run *only* the 1st and 15th.  this
306          * is why we keep 'e->dow_star' and 'e->dom_star'.  yes, it's bizarre.
307          * like many bizarre things, it's the standard.
308          */
309         for (u = db->head;  u != NULL;  u = u->next) {
310                 for (e = u->crontab;  e != NULL;  e = e->next) {
311                         Debug(DSCH|DEXT, ("user [%s:%d:%d:...] cmd=\"%s\"\n",
312                                           env_get("LOGNAME", e->envp),
313                                           e->uid, e->gid, e->cmd))
314
315                         if ( diff != 0 && (e->flags & (RUN_AT|NOT_UNTIL)) ) {
316                                 if (bit_test(e->second, otzsecond)
317                                  && bit_test(e->minute, otzminute)
318                                  && bit_test(e->hour, otzhour)
319                                  && bit_test(e->month, otzmonth)
320                                  && ( ((e->flags & DOM_STAR) || (e->flags & DOW_STAR))
321                                           ? (bit_test(e->dow,otzdow) && bit_test(e->dom,otzdom))
322                                           : (bit_test(e->dow,otzdow) || bit_test(e->dom,otzdom))
323                                         )
324                                    ) {
325                                         if ( e->flags & RUN_AT ) {
326                                                 e->flags &= ~RUN_AT;
327                                                 e->lastrun = TargetTime;
328                                                 job_add(e, u);
329                                                 continue;
330                                         } else 
331                                                 e->flags &= ~NOT_UNTIL;
332                                 } else if ( e->flags & NOT_UNTIL )
333                                         continue;
334                         }
335
336                         if (bit_test(e->second, second)
337                          && bit_test(e->minute, minute)
338                          && bit_test(e->hour, hour)
339                          && bit_test(e->month, month)
340                          && ( ((e->flags & DOM_STAR) || (e->flags & DOW_STAR))
341                               ? (bit_test(e->dow,dow) && bit_test(e->dom,dom))
342                               : (bit_test(e->dow,dow) || bit_test(e->dom,dom))
343                             )
344                            ) {
345                                 e->flags &= ~RUN_AT;
346                                 e->lastrun = TargetTime;
347                                 job_add(e, u);
348                         }
349                 }
350         }
351
352         last_time = TargetTime;
353         lasttm = *tm;
354 }
355
356
357 /* the task here is to figure out how long it's going to be until :00 of the
358  * following minute and initialize TargetTime to this value.  TargetTime
359  * will subsequently slide 60 seconds at a time, with correction applied
360  * implicitly in cron_sleep().  it would be nice to let cron execute in
361  * the "current minute" before going to sleep, but by restarting cron you
362  * could then get it to execute a given minute's jobs more than once.
363  * instead we have the chance of missing a minute's jobs completely, but
364  * that's something sysadmin's know to expect what with crashing computers..
365  */
366 static void
367 cron_sync(int secres) {
368         struct tm *tm;
369
370         TargetTime = time((time_t*)0);
371         if (secres != 0) {
372                 TargetTime += 1;
373         } else {
374                 tm = localtime(&TargetTime);
375                 TargetTime += (60 - tm->tm_sec);
376         }
377 }
378
379 static int
380 timespec_subtract(struct timespec *result, struct timespec *x,
381     struct timespec *y)
382 {
383         time_t nsec;
384
385         /* Perform the carry for the later subtraction by updating y. */
386         if (x->tv_nsec < y->tv_nsec) {
387                 nsec = (y->tv_nsec - x->tv_nsec) / 10000000 + 1;
388                 y->tv_nsec -= 1000000000 * nsec;
389                 y->tv_sec += nsec;
390         }
391         if (x->tv_nsec - y->tv_nsec > 1000000000) {
392                 nsec = (x->tv_nsec - y->tv_nsec) / 1000000000;
393                 y->tv_nsec += 1000000000 * nsec;
394                 y->tv_sec -= nsec;
395         }
396      
397         /* tv_nsec is certainly positive. */
398         result->tv_sec = x->tv_sec - y->tv_sec;
399         result->tv_nsec = x->tv_nsec - y->tv_nsec;
400      
401         /* Return True if result is negative. */
402         return (x->tv_sec < y->tv_sec);
403 }
404
405 static void
406 cron_sleep(cron_db *db, int secres)
407 {
408         int seconds_to_wait;
409         int rval;
410         struct timespec ctime, ttime, stime, remtime;
411
412         /*
413          * Loop until we reach the top of the next minute, sleep when possible.
414          */
415
416         for (;;) {
417                 clock_gettime(CLOCK_REALTIME, &ctime);
418                 ttime.tv_sec = TargetTime;
419                 ttime.tv_nsec = 0;
420                 timespec_subtract(&stime, &ttime, &ctime);
421
422                 /*
423                  * If the seconds_to_wait value is insane, jump the cron
424                  */
425
426                 if (stime.tv_sec < -600 || stime.tv_sec > 600) {
427                         cron_clean(db);
428                         cron_sync(secres);
429                         continue;
430                 }
431
432                 seconds_to_wait = (stime.tv_nsec > 0) ? stime.tv_sec + 1 :
433                     stime.tv_sec;
434
435                 Debug(DSCH, ("[%d] TargetTime=%ld, sec-to-wait=%d\n",
436                         getpid(), (long)TargetTime, seconds_to_wait))
437
438                 /*
439                  * If we've run out of wait time or there are no jobs left
440                  * to run, break
441                  */
442
443                 if (stime.tv_sec < 0)
444                         break;
445                 if (job_runqueue() == 0) {
446                         Debug(DSCH, ("[%d] sleeping for %d seconds\n",
447                                 getpid(), seconds_to_wait))
448
449                         for (;;) {
450                                 rval = nanosleep(&stime, &remtime);
451                                 if (rval == 0 || errno != EINTR)
452                                         break;
453                                 stime.tv_sec = remtime.tv_sec;
454                                 stime.tv_nsec = remtime.tv_nsec;
455                         }
456                 }
457         }
458 }
459
460
461 /* if the time was changed abruptly, clear the flags related
462  * to the daylight time switch handling to avoid strange effects
463  */
464
465 static void
466 cron_clean(db)
467         cron_db *db;
468 {
469         user            *u;
470         entry           *e;
471
472         last_time = 0;
473
474         for (u = db->head;  u != NULL;  u = u->next) {
475                 for (e = u->crontab;  e != NULL;  e = e->next) {
476                         e->flags &= ~(RUN_AT|NOT_UNTIL);
477                 }
478         }
479 }
480
481 #ifdef USE_SIGCHLD
482 static void
483 sigchld_handler(int x)
484 {
485         WAIT_T          waiter;
486         PID_T           pid;
487
488         for (;;) {
489 #ifdef POSIX
490                 pid = waitpid(-1, &waiter, WNOHANG);
491 #else
492                 pid = wait3(&waiter, WNOHANG, (struct rusage *)0);
493 #endif
494                 switch (pid) {
495                 case -1:
496                         Debug(DPROC,
497                                 ("[%d] sigchld...no children\n", getpid()))
498                         return;
499                 case 0:
500                         Debug(DPROC,
501                                 ("[%d] sigchld...no dead kids\n", getpid()))
502                         return;
503                 default:
504                         Debug(DPROC,
505                                 ("[%d] sigchld...pid #%d died, stat=%d\n",
506                                 getpid(), pid, WEXITSTATUS(waiter)))
507                 }
508         }
509 }
510 #endif /*USE_SIGCHLD*/
511
512
513 static void
514 sighup_handler(int x)
515 {
516         log_close();
517 }
518
519
520 static void
521 parse_args(argc, argv)
522         int     argc;
523         char    *argv[];
524 {
525         int     argch;
526         char    *endp;
527
528         while ((argch = getopt(argc, argv, "j:J:m:osx:")) != -1) {
529                 switch (argch) {
530                 case 'j':
531                         Jitter = strtoul(optarg, &endp, 10);
532                         if (*optarg == '\0' || *endp != '\0' || Jitter > 60)
533                                 errx(ERROR_EXIT,
534                                      "bad value for jitter: %s", optarg);
535                         break;
536                 case 'J':
537                         RootJitter = strtoul(optarg, &endp, 10);
538                         if (*optarg == '\0' || *endp != '\0' || RootJitter > 60)
539                                 errx(ERROR_EXIT,
540                                      "bad value for root jitter: %s", optarg);
541                         break;
542                 case 'm':
543                         defmailto = optarg;
544                         break;
545                 case 'o':
546                         dst_enabled = 0;
547                         break;
548                 case 's':
549                         dst_enabled = 1;
550                         break;
551                 case 'x':
552                         if (!set_debug_flags(optarg))
553                                 usage();
554                         break;
555                 default:
556                         usage();
557                 }
558         }
559 }
560
561 static int
562 run_at_secres(cron_db *db)
563 {
564         user *u;
565         entry *e;
566
567         for (u = db->head;  u != NULL;  u = u->next) {
568                 for (e = u->crontab;  e != NULL;  e = e->next) {
569                         if ((e->flags & SEC_RES) != 0)
570                                 return 1;
571                 }
572         }
573         return 0;
574 }