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