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