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