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