Patch from David Meggy to make the swap default to the new version if no
[oweals/busybox.git] / miscutils / crond.c
1 /*
2  * crond -d[#] -c <crondir> -f -b
3  *
4  * run as root, but NOT setuid root
5  *
6  * Copyright 1994 Matthew Dillon (dillon@apollo.west.oic.com)
7  * May be distributed under the GNU General Public License
8  *
9  * Vladimir Oleynik <dzo@simtreas.ru> (C) 2002 to be used in busybox
10  */
11
12 #define VERSION "2.3.2"
13
14 #undef FEATURE_DEBUG_OPT
15
16
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <stdarg.h>
20 #include <string.h>
21 #include <errno.h>
22 #include <time.h>
23 #include <dirent.h>
24 #include <fcntl.h>
25 #include <unistd.h>
26 #include <syslog.h>
27 #include <signal.h>
28 #include <getopt.h>
29 #include <sys/ioctl.h>
30 #include <sys/wait.h>
31 #include <sys/stat.h>
32 #include <sys/resource.h>
33
34 #include "busybox.h"
35
36 #define arysize(ary)    (sizeof(ary)/sizeof((ary)[0]))
37
38 #ifndef CRONTABS
39 #define CRONTABS        "/var/spool/cron/crontabs"
40 #endif
41 #ifndef TMPDIR
42 #define TMPDIR          "/var/spool/cron"
43 #endif
44 #ifndef SENDMAIL
45 #define SENDMAIL        "/usr/sbin/sendmail"
46 #endif
47 #ifndef SENDMAIL_ARGS
48 #define SENDMAIL_ARGS   "-ti", "oem"
49 #endif
50 #ifndef CRONUPDATE
51 #define CRONUPDATE      "cron.update"
52 #endif
53 #ifndef MAXLINES
54 #define MAXLINES        256             /* max lines in non-root crontabs */
55 #endif
56
57 typedef struct CronFile {
58     struct CronFile *cf_Next;
59     struct CronLine *cf_LineBase;
60     char        *cf_User;       /* username                     */
61     int         cf_Ready;       /* bool: one or more jobs ready */
62     int         cf_Running;     /* bool: one or more jobs running */
63     int         cf_Deleted;     /* marked for deletion, ignore  */
64 } CronFile;
65
66 typedef struct CronLine {
67     struct CronLine *cl_Next;
68     char        *cl_Shell;      /* shell command                        */
69     pid_t       cl_Pid;         /* running pid, 0, or armed (-1)        */
70     int         cl_MailFlag;    /* running pid is for mail              */
71     int         cl_MailPos;     /* 'empty file' size                    */
72     char        cl_Mins[60];    /* 0-59                                 */
73     char        cl_Hrs[24];     /* 0-23                                 */
74     char        cl_Days[32];    /* 1-31                                 */
75     char        cl_Mons[12];    /* 0-11                                 */
76     char        cl_Dow[7];      /* 0-6, beginning sunday                */
77 } CronLine;
78
79 #define RUN_RANOUT      1
80 #define RUN_RUNNING     2
81 #define RUN_FAILED      3
82
83 #define DaemonUid 0
84
85 #ifdef FEATURE_DEBUG_OPT
86 static short DebugOpt;
87 #endif
88
89 static short LogLevel = 8;
90 static const char  *LogFile;
91 static const char  *CDir = CRONTABS;
92
93 static void startlogger(void);
94
95 static void CheckUpdates(void);
96 static void SynchronizeDir(void);
97 static int TestJobs(time_t t1, time_t t2);
98 static void RunJobs(void);
99 static int CheckJobs(void);
100
101 static void RunJob(const char *user, CronLine *line);
102 #ifdef CONFIG_FEATURE_CROND_CALL_SENDMAIL
103 static void EndJob(const char *user, CronLine *line);
104 #else
105 #define EndJob(user, line)  line->cl_Pid = 0
106 #endif
107
108 static void DeleteFile(const char *userName);
109
110 static CronFile *FileBase;
111
112
113 static void
114 crondlog(const char *ctl, ...)
115 {
116     va_list va;
117     const char *fmt;
118     int level = (int)(ctl[0] & 0xf);
119     int type = level == 20 ?
120                     LOG_ERR : ((ctl[0] & 0100) ? LOG_WARNING : LOG_NOTICE);
121
122
123     va_start(va, ctl);
124     fmt = ctl+1;
125     if (level >= LogLevel) {
126
127 #ifdef FEATURE_DEBUG_OPT
128         if (DebugOpt) vfprintf(stderr, fmt, va);
129         else
130 #endif
131             if (LogFile == 0) vsyslog(type, fmt, va);
132             else {
133                  int  logfd;
134
135                  if ((logfd = open(LogFile, O_WRONLY|O_CREAT|O_APPEND, 600)) >= 0) {
136                     vdprintf(logfd, fmt, va);
137                     close(logfd);
138 #ifdef FEATURE_DEBUG_OPT
139                  } else {
140                     bb_perror_msg("Can't open log file");
141 #endif
142                 }
143             }
144     }
145     va_end(va);
146     if(ctl[0] & 0200)
147         exit(20);
148 }
149
150 int
151 crond_main(int ac, char **av)
152 {
153     unsigned long opt;
154     char *lopt, *Lopt, *copt;
155 #ifdef FEATURE_DEBUG_OPT
156     char *dopt;
157     bb_opt_complementaly = "f-b:b-f:S-L:L-S:d-l";
158 #else
159     bb_opt_complementaly = "f-b:b-f:S-L:L-S";
160 #endif
161
162     opterr = 0;         /* disable getopt 'errors' message.*/
163     opt = bb_getopt_ulflags(ac, av, "l:L:fbSc:"
164 #ifdef FEATURE_DEBUG_OPT
165                                 "d:"
166 #endif
167             , &lopt, &Lopt, &copt
168 #ifdef FEATURE_DEBUG_OPT
169             , &dopt
170 #endif
171             );
172     if(opt & 1)
173         LogLevel = atoi(lopt);
174     if(opt & 2)
175         if (*Lopt != 0) LogFile = Lopt;
176     if(opt & 32) {
177         if (*copt != 0) CDir = copt;
178         }
179 #ifdef FEATURE_DEBUG_OPT
180     if(opt & 64) {
181         DebugOpt = atoi(dopt);
182         LogLevel = 0;
183     }
184 #endif
185
186     /*
187      * change directory
188      */
189
190     if (chdir(CDir) != 0)
191         bb_perror_msg_and_die("%s", CDir);
192
193     signal(SIGHUP,SIG_IGN);   /* hmm.. but, if kill -HUP original
194                                  * version - his died. ;(
195                                  */
196     /*
197      * close stdin and stdout, stderr.
198      * close unused descriptors -  don't need.
199      * optional detach from controlling terminal
200      */
201
202     if (!(opt & 4)) {
203         if(daemon(1, 0) < 0) {
204                 bb_perror_msg_and_die("daemon");
205         } 
206 #if defined(__uClinux__)
207         else {
208             /* reexec for vfork() do continue parent */
209             vfork_daemon_rexec(ac, av, "-f");
210         }
211 #endif /* uClinux */
212     }
213
214     (void)startlogger();                /* need if syslog mode selected */
215
216     /*
217      * main loop - synchronize to 1 second after the minute, minimum sleep
218      *             of 1 second.
219      */
220
221     crondlog("\011%s " VERSION " dillon, started, log level %d\n", bb_applet_name,
222                                                 LogLevel);
223
224     SynchronizeDir();
225
226     {
227         time_t t1 = time(NULL);
228         time_t t2;
229         long dt;
230         short rescan = 60;
231         short sleep_time = 60;
232
233         for (;;) {
234             sleep((sleep_time + 1) - (short)(time(NULL) % sleep_time));
235
236             t2 = time(NULL);
237             dt = t2 - t1;
238
239             /*
240              * The file 'cron.update' is checked to determine new cron
241              * jobs.  The directory is rescanned once an hour to deal
242              * with any screwups.
243              *
244              * check for disparity.  Disparities over an hour either way
245              * result in resynchronization.  A reverse-indexed disparity
246              * less then an hour causes us to effectively sleep until we
247              * match the original time (i.e. no re-execution of jobs that
248              * have just been run).  A forward-indexed disparity less then
249              * an hour causes intermediate jobs to be run, but only once
250              * in the worst case.
251              *
252              * when running jobs, the inequality used is greater but not
253              * equal to t1, and less then or equal to t2.
254              */
255
256             if (--rescan == 0) {
257                 rescan = 60;
258                 SynchronizeDir();
259             }
260             CheckUpdates();
261 #ifdef FEATURE_DEBUG_OPT
262             if (DebugOpt)
263                 crondlog("\005Wakeup dt=%d\n", dt);
264 #endif
265             if (dt < -60*60 || dt > 60*60) {
266                 t1 = t2;
267                 crondlog("\111time disparity of %d minutes detected\n", dt / 60);
268             } else if (dt > 0) {
269                 TestJobs(t1, t2);
270                 RunJobs();
271                 sleep(5);
272                 if (CheckJobs() > 0)
273                    sleep_time = 10;
274                 else
275                    sleep_time = 60;
276                 t1 = t2;
277             }
278         }
279     }
280     /* not reached */
281 }
282
283
284 #if defined(FEATURE_DEBUG_OPT) || defined(CONFIG_FEATURE_CROND_CALL_SENDMAIL)
285 /*
286     write to temp file..
287 */
288 static void
289 fdprintf(int fd, const char *ctl, ...)
290 {
291     va_list va;
292
293     va_start(va, ctl);
294     vdprintf(fd, ctl, va);
295     va_end(va);
296 }
297 #endif
298
299
300 static int
301 ChangeUser(const char *user)
302 {
303     struct passwd *pas;
304     const char *err_msg;
305
306     /*
307      * Obtain password entry and change privilages
308      */
309
310     if ((pas = getpwnam(user)) == 0) {
311         crondlog("\011failed to get uid for %s", user);
312         return(-1);
313     }
314     setenv("USER", pas->pw_name, 1);
315     setenv("HOME", pas->pw_dir, 1);
316     setenv("SHELL", DEFAULT_SHELL, 1);
317
318     /*
319      * Change running state to the user in question
320      */
321     err_msg = change_identity_e2str(pas);
322     if (err_msg) {
323         crondlog("\011%s for user %s", err_msg, user);
324         return(-1);
325     }
326         if (chdir(pas->pw_dir) < 0) {
327         crondlog("\011chdir failed: %s: %m", pas->pw_dir);
328             if (chdir(TMPDIR) < 0) {
329             crondlog("\011chdir failed: %s: %m", TMPDIR);
330                 return(-1);
331             }
332         }
333     return(pas->pw_uid);
334 }
335
336 static void
337 startlogger(void)
338 {
339     if (LogFile == 0)
340         openlog(bb_applet_name, LOG_CONS|LOG_PID, LOG_CRON);
341 #ifdef FEATURE_DEBUG_OPT
342     else { /* test logfile */
343     int  logfd;
344
345         if ((logfd = open(LogFile, O_WRONLY|O_CREAT|O_APPEND, 600)) >= 0)
346            close(logfd);
347         else
348            bb_perror_msg("Failed to open log file '%s' reason", LogFile);
349     }
350 #endif
351 }
352
353
354 static const char * const DowAry[] = {
355     "sun",
356     "mon",
357     "tue",
358     "wed",
359     "thu",
360     "fri",
361     "sat",
362
363     "Sun",
364     "Mon",
365     "Tue",
366     "Wed",
367     "Thu",
368     "Fri",
369     "Sat",
370     NULL
371 };
372
373 static const char * const MonAry[] = {
374     "jan",
375     "feb",
376     "mar",
377     "apr",
378     "may",
379     "jun",
380     "jul",
381     "aug",
382     "sep",
383     "oct",
384     "nov",
385     "dec",
386
387     "Jan",
388     "Feb",
389     "Mar",
390     "Apr",
391     "May",
392     "Jun",
393     "Jul",
394     "Aug",
395     "Sep",
396     "Oct",
397     "Nov",
398     "Dec",
399     NULL
400 };
401
402 static char *
403 ParseField(char *user, char *ary, int modvalue, int off,
404                                 const char * const *names, char *ptr)
405 {
406     char *base = ptr;
407     int n1 = -1;
408     int n2 = -1;
409
410     if (base == NULL)
411         return(NULL);
412
413     while (*ptr != ' ' && *ptr != '\t' && *ptr != '\n') {
414         int skip = 0;
415
416         /*
417          * Handle numeric digit or symbol or '*'
418          */
419
420         if (*ptr == '*') {
421             n1 = 0;                     /* everything will be filled */
422             n2 = modvalue - 1;
423             skip = 1;
424             ++ptr;
425         } else if (*ptr >= '0' && *ptr <= '9') {
426             if (n1 < 0)
427                 n1 = strtol(ptr, &ptr, 10) + off;
428             else
429                 n2 = strtol(ptr, &ptr, 10) + off;
430             skip = 1;
431         } else if (names) {
432             int i;
433
434             for (i = 0; names[i]; ++i) {
435                 if (strncmp(ptr, names[i], strlen(names[i])) == 0) {
436                     break;
437                 }
438             }
439             if (names[i]) {
440                 ptr += strlen(names[i]);
441                 if (n1 < 0)
442                     n1 = i;
443                 else
444                     n2 = i;
445                 skip = 1;
446             }
447         }
448
449         /*
450          * handle optional range '-'
451          */
452
453         if (skip == 0) {
454             crondlog("\111failed user %s parsing %s\n", user, base);
455             return(NULL);
456         }
457         if (*ptr == '-' && n2 < 0) {
458             ++ptr;
459             continue;
460         }
461
462         /*
463          * collapse single-value ranges, handle skipmark, and fill
464          * in the character array appropriately.
465          */
466
467         if (n2 < 0)
468             n2 = n1;
469
470         if (*ptr == '/')
471             skip = strtol(ptr + 1, &ptr, 10);
472
473         /*
474          * fill array, using a failsafe is the easiest way to prevent
475          * an endless loop
476          */
477
478         {
479             int s0 = 1;
480             int failsafe = 1024;
481
482             --n1;
483             do {
484                 n1 = (n1 + 1) % modvalue;
485
486                 if (--s0 == 0) {
487                     ary[n1 % modvalue] = 1;
488                     s0 = skip;
489                 }
490             } while (n1 != n2 && --failsafe);
491
492             if (failsafe == 0) {
493                 crondlog("\111failed user %s parsing %s\n", user, base);
494                 return(NULL);
495             }
496         }
497         if (*ptr != ',')
498             break;
499         ++ptr;
500         n1 = -1;
501         n2 = -1;
502     }
503
504     if (*ptr != ' ' && *ptr != '\t' && *ptr != '\n') {
505         crondlog("\111failed user %s parsing %s\n", user, base);
506         return(NULL);
507     }
508
509     while (*ptr == ' ' || *ptr == '\t' || *ptr == '\n')
510         ++ptr;
511
512 #ifdef FEATURE_DEBUG_OPT
513     if (DebugOpt) {
514         int i;
515
516         for (i = 0; i < modvalue; ++i)
517             crondlog("\005%d", ary[i]);
518         crondlog("\005\n");
519     }
520 #endif
521
522     return(ptr);
523 }
524
525 static void
526 FixDayDow(CronLine *line)
527 {
528     short i;
529     short weekUsed = 0;
530     short daysUsed = 0;
531
532     for (i = 0; i < arysize(line->cl_Dow); ++i) {
533         if (line->cl_Dow[i] == 0) {
534             weekUsed = 1;
535             break;
536         }
537     }
538     for (i = 0; i < arysize(line->cl_Days); ++i) {
539         if (line->cl_Days[i] == 0) {
540             daysUsed = 1;
541             break;
542         }
543     }
544     if (weekUsed && !daysUsed) {
545         memset(line->cl_Days, 0, sizeof(line->cl_Days));
546     }
547     if (daysUsed && !weekUsed) {
548         memset(line->cl_Dow, 0, sizeof(line->cl_Dow));
549     }
550 }
551
552
553
554 static void
555 SynchronizeFile(const char *fileName)
556 {
557     int maxEntries = MAXLINES;
558     int maxLines;
559     char buf[1024];
560
561     if (strcmp(fileName, "root") == 0)
562         maxEntries = 65535;
563     maxLines = maxEntries * 10;
564
565     if (fileName) {
566         FILE *fi;
567
568         DeleteFile(fileName);
569
570         if ((fi = fopen(fileName, "r")) != NULL) {
571             struct stat sbuf;
572
573             if (fstat(fileno(fi), &sbuf) == 0 && sbuf.st_uid == DaemonUid) {
574                 CronFile *file = calloc(1, sizeof(CronFile));
575                 CronLine **pline;
576
577                 file->cf_User = strdup(fileName);
578                 pline = &file->cf_LineBase;
579
580                 while (fgets(buf, sizeof(buf), fi) != NULL && --maxLines) {
581                     CronLine line;
582                     char *ptr;
583
584                     if (buf[0])
585                         buf[strlen(buf)-1] = 0;
586
587                     if (buf[0] == 0 || buf[0] == '#' || buf[0] == ' ' || buf[0] == '\t')
588                         continue;
589
590                     if (--maxEntries == 0)
591                         break;
592
593                     memset(&line, 0, sizeof(line));
594
595 #ifdef FEATURE_DEBUG_OPT
596                     if (DebugOpt)
597                         crondlog("\111User %s Entry %s\n", fileName, buf);
598 #endif
599
600                     /*
601                      * parse date ranges
602                      */
603
604                     ptr = ParseField(file->cf_User, line.cl_Mins, 60, 0, NULL, buf);
605                     ptr = ParseField(file->cf_User, line.cl_Hrs,  24, 0, NULL, ptr);
606                     ptr = ParseField(file->cf_User, line.cl_Days, 32, 0, NULL, ptr);
607                     ptr = ParseField(file->cf_User, line.cl_Mons, 12, -1, MonAry, ptr);
608                     ptr = ParseField(file->cf_User, line.cl_Dow, 7, 0, DowAry, ptr);
609
610                     /*
611                      * check failure
612                      */
613
614                     if (ptr == NULL)
615                         continue;
616
617                     /*
618                      * fix days and dow - if one is not * and the other
619                      * is *, the other is set to 0, and vise-versa
620                      */
621
622                     FixDayDow(&line);
623
624                     *pline = calloc(1, sizeof(CronLine));
625                     **pline = line;
626
627                     /*
628                      * copy command
629                      */
630
631                     (*pline)->cl_Shell = strdup(ptr);
632
633 #ifdef FEATURE_DEBUG_OPT
634                     if (DebugOpt) {
635                         crondlog("\111    Command %s\n", ptr);
636                     }
637 #endif
638
639                     pline = &((*pline)->cl_Next);
640                 }
641                 *pline = NULL;
642
643                 file->cf_Next = FileBase;
644                 FileBase = file;
645
646                 if (maxLines == 0 || maxEntries == 0)
647                     crondlog("\111Maximum number of lines reached for user %s\n", fileName);
648             }
649             fclose(fi);
650         }
651     }
652 }
653
654 static void
655 CheckUpdates(void)
656 {
657     FILE *fi;
658     char buf[256];
659
660     if ((fi = fopen(CRONUPDATE, "r")) != NULL) {
661         remove(CRONUPDATE);
662         while (fgets(buf, sizeof(buf), fi) != NULL) {
663             SynchronizeFile(strtok(buf, " \t\r\n"));
664         }
665         fclose(fi);
666     }
667 }
668
669 static void
670 SynchronizeDir(void)
671 {
672     /*
673      * Attempt to delete the database.
674      */
675
676     for (;;) {
677         CronFile *file;
678
679         for (file = FileBase; file && file->cf_Deleted; file = file->cf_Next)
680             ;
681         if (file == NULL)
682             break;
683         DeleteFile(file->cf_User);
684     }
685
686     /*
687      * Remove cron update file
688      *
689      * Re-chdir, in case directory was renamed & deleted, or otherwise
690      * screwed up.
691      *
692      * scan directory and add associated users
693      */
694
695     remove(CRONUPDATE);
696     if (chdir(CDir) < 0) {
697         crondlog("\311unable to find %s\n", CDir);
698     }
699     {
700         DIR *dir;
701         struct dirent *den;
702
703         if ((dir = opendir("."))) {
704             while ((den = readdir(dir))) {
705                 if (strchr(den->d_name, '.') != NULL)
706                     continue;
707                 if (getpwnam(den->d_name))
708                     SynchronizeFile(den->d_name);
709                 else
710                     crondlog("\007ignoring %s\n", den->d_name);
711             }
712             closedir(dir);
713         } else {
714             crondlog("\311Unable to open current dir!\n");
715         }
716     }
717 }
718
719
720 /*
721  *  DeleteFile() - delete user database
722  *
723  *  Note: multiple entries for same user may exist if we were unable to
724  *  completely delete a database due to running processes.
725  */
726
727 static void
728 DeleteFile(const char *userName)
729 {
730     CronFile **pfile = &FileBase;
731     CronFile *file;
732
733     while ((file = *pfile) != NULL) {
734         if (strcmp(userName, file->cf_User) == 0) {
735             CronLine **pline = &file->cf_LineBase;
736             CronLine *line;
737
738             file->cf_Running = 0;
739             file->cf_Deleted = 1;
740
741             while ((line = *pline) != NULL) {
742                 if (line->cl_Pid > 0) {
743                     file->cf_Running = 1;
744                     pline = &line->cl_Next;
745                 } else {
746                     *pline = line->cl_Next;
747                     free(line->cl_Shell);
748                     free(line);
749                 }
750             }
751             if (file->cf_Running == 0) {
752                 *pfile = file->cf_Next;
753                 free(file->cf_User);
754                 free(file);
755             } else {
756                 pfile = &file->cf_Next;
757             }
758         } else {
759             pfile = &file->cf_Next;
760         }
761     }
762 }
763
764 /*
765  * TestJobs()
766  *
767  * determine which jobs need to be run.  Under normal conditions, the
768  * period is about a minute (one scan).  Worst case it will be one
769  * hour (60 scans).
770  */
771
772 static int
773 TestJobs(time_t t1, time_t t2)
774 {
775     short nJobs = 0;
776     time_t t;
777
778     /*
779      * Find jobs > t1 and <= t2
780      */
781
782     for (t = t1 - t1 % 60; t <= t2; t += 60) {
783         if (t > t1) {
784             struct tm *tp = localtime(&t);
785             CronFile *file;
786             CronLine *line;
787
788             for (file = FileBase; file; file = file->cf_Next) {
789 #ifdef FEATURE_DEBUG_OPT
790                 if (DebugOpt)
791                     crondlog("\005FILE %s:\n", file->cf_User);
792 #endif
793                 if (file->cf_Deleted)
794                     continue;
795                 for (line = file->cf_LineBase; line; line = line->cl_Next) {
796 #ifdef FEATURE_DEBUG_OPT
797                     if (DebugOpt)
798                         crondlog("\005    LINE %s\n", line->cl_Shell);
799 #endif
800                     if (line->cl_Mins[tp->tm_min] &&
801                         line->cl_Hrs[tp->tm_hour] &&
802                         (line->cl_Days[tp->tm_mday] || line->cl_Dow[tp->tm_wday]) &&
803                         line->cl_Mons[tp->tm_mon]
804                     ) {
805 #ifdef FEATURE_DEBUG_OPT
806                         if (DebugOpt)
807                             crondlog("\005    JobToDo: %d %s\n", line->cl_Pid, line->cl_Shell);
808 #endif
809                         if (line->cl_Pid > 0) {
810                             crondlog("\010    process already running: %s %s\n",
811                                 file->cf_User,
812                                 line->cl_Shell
813                             );
814                         } else if (line->cl_Pid == 0) {
815                             line->cl_Pid = -1;
816                             file->cf_Ready = 1;
817                             ++nJobs;
818                         }
819                     }
820                 }
821             }
822         }
823     }
824     return(nJobs);
825 }
826
827 static void
828 RunJobs(void)
829 {
830     CronFile *file;
831     CronLine *line;
832
833     for (file = FileBase; file; file = file->cf_Next) {
834         if (file->cf_Ready) {
835             file->cf_Ready = 0;
836
837             for (line = file->cf_LineBase; line; line = line->cl_Next) {
838                 if (line->cl_Pid < 0) {
839
840                     RunJob(file->cf_User, line);
841
842                     crondlog("\010USER %s pid %3d cmd %s\n",
843                         file->cf_User,
844                         line->cl_Pid,
845                         line->cl_Shell
846                     );
847                     if (line->cl_Pid < 0)
848                         file->cf_Ready = 1;
849                     else if (line->cl_Pid > 0)
850                         file->cf_Running = 1;
851                 }
852             }
853         }
854     }
855 }
856
857 /*
858  * CheckJobs() - check for job completion
859  *
860  * Check for job completion, return number of jobs still running after
861  * all done.
862  */
863
864 static int
865 CheckJobs(void)
866 {
867     CronFile *file;
868     CronLine *line;
869     int nStillRunning = 0;
870
871     for (file = FileBase; file; file = file->cf_Next) {
872         if (file->cf_Running) {
873             file->cf_Running = 0;
874
875             for (line = file->cf_LineBase; line; line = line->cl_Next) {
876                 if (line->cl_Pid > 0) {
877                     int status;
878                     int r = wait4(line->cl_Pid, &status, WNOHANG, NULL);
879
880                     if (r < 0 || r == line->cl_Pid) {
881                         EndJob(file->cf_User, line);
882                         if (line->cl_Pid)
883                             file->cf_Running = 1;
884                     } else if (r == 0) {
885                         file->cf_Running = 1;
886                     }
887                 }
888             }
889         }
890         nStillRunning += file->cf_Running;
891     }
892     return(nStillRunning);
893 }
894
895
896 #ifdef CONFIG_FEATURE_CROND_CALL_SENDMAIL
897 static void
898 ForkJob(const char *user, CronLine *line, int mailFd,
899         const char *prog, const char *cmd, const char *arg, const char *mailf)
900 {
901     /*
902      * Fork as the user in question and run program
903      */
904     pid_t pid = fork();
905
906     line->cl_Pid = pid;
907     if (pid == 0) {
908         /*
909          * CHILD
910          */
911
912         /*
913          * Change running state to the user in question
914          */
915
916         if (ChangeUser(user) < 0)
917             exit(0);
918
919 #ifdef FEATURE_DEBUG_OPT
920         if (DebugOpt)
921             crondlog("\005Child Running %s\n", prog);
922 #endif
923
924         if (mailFd >= 0) {
925             dup2(mailFd, mailf != NULL);
926             dup2((mailf ? mailFd : 1), 2);
927             close(mailFd);
928         }
929         execl(prog, prog, cmd, arg, NULL);
930         crondlog("\024unable to exec, user %s cmd %s %s %s\n", user,
931             prog, cmd, arg);
932         if(mailf)
933             fdprintf(1, "Exec failed: %s -c %s\n", prog, arg);
934         exit(0);
935     } else if (pid < 0) {
936         /*
937          * FORK FAILED
938          */
939         crondlog("\024couldn't fork, user %s\n", user);
940         line->cl_Pid = 0;
941         if(mailf)
942             remove(mailf);
943     } else if(mailf) {
944         /*
945          * PARENT, FORK SUCCESS
946          *
947          * rename mail-file based on pid of process
948          */
949         char mailFile2[128];
950
951         snprintf(mailFile2, sizeof(mailFile2), TMPDIR "/cron.%s.%d",
952                                     user, pid);
953         rename(mailf, mailFile2);
954     }
955     /*
956      * Close the mail file descriptor.. we can't just leave it open in
957      * a structure, closing it later, because we might run out of descriptors
958      */
959
960     if (mailFd >= 0)
961         close(mailFd);
962 }
963
964 static void
965 RunJob(const char *user, CronLine *line)
966 {
967     char mailFile[128];
968     int mailFd;
969
970     line->cl_Pid = 0;
971     line->cl_MailFlag = 0;
972
973     /*
974      * open mail file - owner root so nobody can screw with it.
975      */
976
977     snprintf(mailFile, sizeof(mailFile), TMPDIR "/cron.%s.%d",
978              user, getpid());
979     mailFd = open(mailFile, O_CREAT|O_TRUNC|O_WRONLY|O_EXCL|O_APPEND, 0600);
980
981     if (mailFd >= 0) {
982         line->cl_MailFlag = 1;
983         fdprintf(mailFd, "To: %s\nSubject: cron: %s\n\n", user,
984                                                     line->cl_Shell);
985         line->cl_MailPos = lseek(mailFd, 0, 1);
986     } else {
987             crondlog("\024unable to create mail file user %s file %s, output to /dev/null\n",
988                         user, mailFile);
989     }
990
991     ForkJob(user, line, mailFd, DEFAULT_SHELL, "-c", line->cl_Shell, mailFile);
992 }
993
994 /*
995  * EndJob - called when job terminates and when mail terminates
996  */
997
998 static void
999 EndJob(const char *user, CronLine *line)
1000 {
1001     int mailFd;
1002     char mailFile[128];
1003     struct stat sbuf;
1004
1005     /*
1006      * No job
1007      */
1008
1009     if (line->cl_Pid <= 0) {
1010         line->cl_Pid = 0;
1011         return;
1012     }
1013
1014     /*
1015      * End of job and no mail file
1016      * End of sendmail job
1017      */
1018
1019     snprintf(mailFile, sizeof(mailFile), TMPDIR "/cron.%s.%d",
1020              user, line->cl_Pid);
1021     line->cl_Pid = 0;
1022
1023     if (line->cl_MailFlag != 1)
1024         return;
1025
1026     line->cl_MailFlag = 0;
1027
1028     /*
1029      * End of primary job - check for mail file.  If size has increased and
1030      * the file is still valid, we sendmail it.
1031      */
1032
1033     mailFd = open(mailFile, O_RDONLY);
1034     remove(mailFile);
1035     if (mailFd < 0) {
1036         return;
1037     }
1038
1039     if (fstat(mailFd, &sbuf) < 0 ||
1040         sbuf.st_uid != DaemonUid ||
1041         sbuf.st_nlink != 0 ||
1042         sbuf.st_size == line->cl_MailPos ||
1043         !S_ISREG(sbuf.st_mode)
1044     ) {
1045         close(mailFd);
1046         return;
1047     }
1048     ForkJob(user, line, mailFd, SENDMAIL, SENDMAIL_ARGS, NULL);
1049 }
1050 #else
1051 /* crond whithout sendmail */
1052
1053 static void
1054 RunJob(const char *user, CronLine *line)
1055 {
1056         /*
1057      * Fork as the user in question and run program
1058          */
1059     pid_t pid = fork();
1060
1061     if (pid == 0) {
1062         /*
1063          * CHILD
1064          */
1065
1066         /*
1067          * Change running state to the user in question
1068          */
1069
1070         if (ChangeUser(user) < 0)
1071             exit(0);
1072
1073 #ifdef FEATURE_DEBUG_OPT
1074         if (DebugOpt)
1075             crondlog("\005Child Running %s\n", DEFAULT_SHELL);
1076 #endif
1077
1078         execl(DEFAULT_SHELL, DEFAULT_SHELL, "-c", line->cl_Shell, NULL);
1079         crondlog("\024unable to exec, user %s cmd %s -c %s\n", user,
1080             DEFAULT_SHELL, line->cl_Shell);
1081         exit(0);
1082     } else if (pid < 0) {
1083         /*
1084          * FORK FAILED
1085          */
1086         crondlog("\024couldn't fork, user %s\n", user);
1087         pid = 0;
1088     }
1089     line->cl_Pid = pid;
1090 }
1091 #endif /* CONFIG_FEATURE_CROND_CALL_SENDMAIL */