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