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