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