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