Ignore:
Timestamp:
Feb 25, 2011, 9:26:54 PM (13 years ago)
Author:
Bruno Cornec
Message:
  • Update mindi-busybox to 1.18.3 to avoid problems with the tar command which is now failing on recent versions with busybox 1.7.3
File:
1 edited

Legend:

Unmodified
Added
Removed
  • branches/2.2.9/mindi-busybox/miscutils/crond.c

    r1765 r2725  
    99 * Vladimir Oleynik <dzo@simtreas.ru> (C) 2002
    1010 *
    11  * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
     11 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
    1212 */
    1313
    14 #include <sys/syslog.h>
    1514#include "libbb.h"
    16 
    17 #ifndef CRONTABS
    18 #define CRONTABS        "/var/spool/cron/crontabs"
    19 #endif
    20 #ifndef TMPDIR
    21 #define TMPDIR          "/var/spool/cron"
    22 #endif
     15#include <syslog.h>
     16
     17/* glibc frees previous setenv'ed value when we do next setenv()
     18 * of the same variable. uclibc does not do this! */
     19#if (defined(__GLIBC__) && !defined(__UCLIBC__)) /* || OTHER_SAFE_LIBC... */
     20# define SETENV_LEAKS 0
     21#else
     22# define SETENV_LEAKS 1
     23#endif
     24
     25
     26#define TMPDIR          CONFIG_FEATURE_CROND_DIR
     27#define CRONTABS        CONFIG_FEATURE_CROND_DIR "/crontabs"
    2328#ifndef SENDMAIL
    24 #define SENDMAIL        "sendmail"
     29# define SENDMAIL       "sendmail"
    2530#endif
    2631#ifndef SENDMAIL_ARGS
    27 #define SENDMAIL_ARGS   "-ti", "oem"
     32# define SENDMAIL_ARGS  "-ti"
    2833#endif
    2934#ifndef CRONUPDATE
    30 #define CRONUPDATE      "cron.update"
     35# define CRONUPDATE     "cron.update"
    3136#endif
    3237#ifndef MAXLINES
    33 #define MAXLINES        256 /* max lines in non-root crontabs */
    34 #endif
     38# define MAXLINES       256  /* max lines in non-root crontabs */
     39#endif
     40
    3541
    3642typedef struct CronFile {
    37     struct CronFile *cf_Next;
    38     struct CronLine *cf_LineBase;
    39     char *cf_User;      /* username                     */
    40     int cf_Ready;       /* bool: one or more jobs ready */
    41     int cf_Running;     /* bool: one or more jobs running */
    42     int cf_Deleted;     /* marked for deletion, ignore */
     43    struct CronFile *cf_next;
     44    struct CronLine *cf_lines;
     45    char *cf_username;
     46    smallint cf_wants_starting;     /* bool: one or more jobs ready */
     47    smallint cf_has_running;        /* bool: one or more jobs running */
     48    smallint cf_deleted;            /* marked for deletion (but still has running jobs) */
    4349} CronFile;
    4450
    4551typedef struct CronLine {
    46     struct CronLine *cl_Next;
    47     char *cl_Shell;     /* shell command                        */
    48     pid_t cl_Pid;       /* running pid, 0, or armed (-1)        */
    49     int cl_MailFlag;    /* running pid is for mail              */
    50     int cl_MailPos;     /* 'empty file' size                    */
    51     char cl_Mins[60];   /* 0-59                                 */
    52     char cl_Hrs[24];    /* 0-23                                 */
    53     char cl_Days[32];   /* 1-31                                 */
    54     char cl_Mons[12];   /* 0-11                                 */
    55     char cl_Dow[7];     /* 0-6, beginning sunday                */
     52    struct CronLine *cl_next;
     53    char *cl_cmd;                   /* shell command */
     54    pid_t cl_pid;                   /* >0:running, <0:needs to be started in this minute, 0:dormant */
     55#if ENABLE_FEATURE_CROND_CALL_SENDMAIL
     56    int cl_empty_mail_size;         /* size of mail header only, 0 if no mailfile */
     57    char *cl_mailto;                /* whom to mail results, may be NULL */
     58#endif
     59    /* ordered by size, not in natural order. makes code smaller: */
     60    char cl_Dow[7];                 /* 0-6, beginning sunday */
     61    char cl_Mons[12];               /* 0-11 */
     62    char cl_Hrs[24];                /* 0-23 */
     63    char cl_Days[32];               /* 1-31 */
     64    char cl_Mins[60];               /* 0-59 */
    5665} CronLine;
    5766
    58 #define RUN_RANOUT      1
    59 #define RUN_RUNNING     2
    60 #define RUN_FAILED      3
    61 
    62 #define DaemonUid 0
    63 
    64 #if ENABLE_DEBUG_CROND_OPTION
    65 static unsigned DebugOpt;
    66 #endif
    67 
    68 static unsigned LogLevel = 8;
    69 static const char *LogFile;
    70 static const char *CDir = CRONTABS;
    71 
    72 static void startlogger(void);
    73 
    74 static void CheckUpdates(void);
    75 static void SynchronizeDir(void);
    76 static int TestJobs(time_t t1, time_t t2);
    77 static void RunJobs(void);
    78 static int CheckJobs(void);
    79 
    80 static void RunJob(const char *user, CronLine * line);
    81 
    82 #if ENABLE_FEATURE_CROND_CALL_SENDMAIL
    83 static void EndJob(const char *user, CronLine * line);
     67
     68#define DAEMON_UID 0
     69
     70
     71enum {
     72    OPT_l = (1 << 0),
     73    OPT_L = (1 << 1),
     74    OPT_f = (1 << 2),
     75    OPT_b = (1 << 3),
     76    OPT_S = (1 << 4),
     77    OPT_c = (1 << 5),
     78    OPT_d = (1 << 6) * ENABLE_FEATURE_CROND_D,
     79};
     80#if ENABLE_FEATURE_CROND_D
     81# define DebugOpt (option_mask32 & OPT_d)
    8482#else
    85 #define EndJob(user, line)  line->cl_Pid = 0
    86 #endif
    87 
    88 static void DeleteFile(const char *userName);
    89 
    90 static CronFile *FileBase;
    91 
    92 
     83# define DebugOpt 0
     84#endif
     85
     86
     87struct globals {
     88    unsigned log_level; /* = 8; */
     89    time_t crontab_dir_mtime;
     90    const char *log_filename;
     91    const char *crontab_dir_name; /* = CRONTABS; */
     92    CronFile *cron_files;
     93#if SETENV_LEAKS
     94    char *env_var_user;
     95    char *env_var_home;
     96#endif
     97} FIX_ALIASING;
     98#define G (*(struct globals*)&bb_common_bufsiz1)
     99#define INIT_G() do { \
     100    G.log_level = 8; \
     101    G.crontab_dir_name = CRONTABS; \
     102} while (0)
     103
     104
     105/* 0 is the most verbose, default 8 */
     106#define LVL5  "\x05"
     107#define LVL7  "\x07"
     108#define LVL8  "\x08"
     109#define WARN9 "\x49"
     110#define DIE9  "\xc9"
     111/* level >= 20 is "error" */
     112#define ERR20 "\x14"
     113
     114static void crondlog(const char *ctl, ...) __attribute__ ((format (printf, 1, 2)));
    93115static void crondlog(const char *ctl, ...)
    94116{
    95117    va_list va;
    96     const char *fmt;
    97     int level = (int) (ctl[0] & 0xf);
    98     int type = level == 20 ?
    99         LOG_ERR : ((ctl[0] & 0100) ? LOG_WARNING : LOG_NOTICE);
    100 
     118    int level = (ctl[0] & 0x1f);
    101119
    102120    va_start(va, ctl);
    103     fmt = ctl + 1;
    104     if (level >= LogLevel) {
    105 
    106 #if ENABLE_DEBUG_CROND_OPTION
    107         if (DebugOpt) {
    108             vfprintf(stderr, fmt, va);
    109         } else
    110 #endif
    111         if (LogFile == 0) {
    112             vsyslog(type, fmt, va);
     121    if (level >= (int)G.log_level) {
     122        /* Debug mode: all to (non-redirected) stderr, */
     123        /* Syslog mode: all to syslog (logmode = LOGMODE_SYSLOG), */
     124        if (!DebugOpt && G.log_filename) {
     125            /* Otherwise (log to file): we reopen log file at every write: */
     126            int logfd = open_or_warn(G.log_filename, O_WRONLY | O_CREAT | O_APPEND);
     127            if (logfd >= 0)
     128                xmove_fd(logfd, STDERR_FILENO);
     129        }
     130        /* When we log to syslog, level > 8 is logged at LOG_ERR
     131         * syslog level, level <= 8 is logged at LOG_INFO. */
     132        if (level > 8) {
     133            bb_verror_msg(ctl + 1, va, /* strerr: */ NULL);
    113134        } else {
    114 #if !ENABLE_DEBUG_CROND_OPTION
    115             int logfd = open(LogFile, O_WRONLY | O_CREAT | O_APPEND, 0600);
    116 #else
    117             int logfd = open3_or_warn(LogFile, O_WRONLY | O_CREAT | O_APPEND, 0600);
    118 #endif
    119             if (logfd >= 0) {
    120                 vdprintf(logfd, fmt, va);
    121                 close(logfd);
    122             }
     135            char *msg = NULL;
     136            vasprintf(&msg, ctl + 1, va);
     137            bb_info_msg("%s: %s", applet_name, msg);
     138            free(msg);
    123139        }
    124140    }
    125141    va_end(va);
    126     if (ctl[0] & 0200) {
     142    if (ctl[0] & 0x80)
    127143        exit(20);
    128     }
    129 }
    130 
    131 int crond_main(int ac, char **av);
    132 int crond_main(int ac, char **av)
    133 {
    134     unsigned opt;
    135     char *lopt, *Lopt, *copt;
    136     USE_DEBUG_CROND_OPTION(char *dopt;)
    137 
    138     opt_complementary = "f-b:b-f:S-L:L-S" USE_DEBUG_CROND_OPTION(":d-l");
    139     opterr = 0;         /* disable getopt 'errors' message. */
    140     opt = getopt32(av, "l:L:fbSc:" USE_DEBUG_CROND_OPTION("d:"),
    141             &lopt, &Lopt, &copt USE_DEBUG_CROND_OPTION(, &dopt));
    142     if (opt & 1) /* -l */
    143         LogLevel = xatou(lopt);
    144     if (opt & 2) /* -L */
    145         if (*Lopt)
    146             LogFile = Lopt;
    147     if (opt & 32) /* -c */
    148         if (*copt)
    149             CDir = copt;
    150 #if ENABLE_DEBUG_CROND_OPTION
    151     if (opt & 64) { /* -d */
    152         DebugOpt = xatou(dopt);
    153         LogLevel = 0;
    154     }
    155 #endif
    156 
    157     /* close stdin and stdout, stderr.
    158      * close unused descriptors -  don't need.
    159      * optional detach from controlling terminal
    160      */
    161     if (!(opt & 4))
    162         bb_daemonize_or_rexec(DAEMON_CLOSE_EXTRA_FDS, av);
    163 
    164     xchdir(CDir);
    165     signal(SIGHUP, SIG_IGN); /* ? original crond dies on HUP... */
    166 
    167     startlogger();  /* need if syslog mode selected */
    168 
    169     /*
    170      * main loop - synchronize to 1 second after the minute, minimum sleep
    171      *             of 1 second.
    172      */
    173     crondlog("\011%s " BB_VER " started, log level %d\n",
    174              applet_name, LogLevel);
    175 
    176     SynchronizeDir();
    177 
    178     {
    179         time_t t1 = time(NULL);
    180         time_t t2;
    181         long dt;
    182         int rescan = 60;
    183         short sleep_time = 60;
    184 
    185         write_pidfile("/var/run/crond.pid");
    186         for (;;) {
    187             sleep((sleep_time + 1) - (short) (time(NULL) % sleep_time));
    188 
    189             t2 = time(NULL);
    190             dt = t2 - t1;
    191 
    192             /*
    193              * The file 'cron.update' is checked to determine new cron
    194              * jobs.  The directory is rescanned once an hour to deal
    195              * with any screwups.
    196              *
    197              * check for disparity.  Disparities over an hour either way
    198              * result in resynchronization.  A reverse-indexed disparity
    199              * less then an hour causes us to effectively sleep until we
    200              * match the original time (i.e. no re-execution of jobs that
    201              * have just been run).  A forward-indexed disparity less then
    202              * an hour causes intermediate jobs to be run, but only once
    203              * in the worst case.
    204              *
    205              * when running jobs, the inequality used is greater but not
    206              * equal to t1, and less then or equal to t2.
    207              */
    208 
    209             if (--rescan == 0) {
    210                 rescan = 60;
    211                 SynchronizeDir();
    212             }
    213             CheckUpdates();
    214 #if ENABLE_DEBUG_CROND_OPTION
    215             if (DebugOpt)
    216                 crondlog("\005Wakeup dt=%d\n", dt);
    217 #endif
    218             if (dt < -60 * 60 || dt > 60 * 60) {
    219                 t1 = t2;
    220                 crondlog("\111time disparity of %d minutes detected\n", dt / 60);
    221             } else if (dt > 0) {
    222                 TestJobs(t1, t2);
    223                 RunJobs();
    224                 sleep(5);
    225                 if (CheckJobs() > 0) {
    226                     sleep_time = 10;
    227                 } else {
    228                     sleep_time = 60;
    229                 }
    230                 t1 = t2;
    231             }
    232         }
    233     }
    234     return 0; /* not reached */
    235 }
    236 
    237 static int ChangeUser(const char *user)
    238 {
    239     struct passwd *pas;
    240     const char *err_msg;
    241 
    242     /*
    243      * Obtain password entry and change privileges
    244      */
    245     pas = getpwnam(user);
    246     if (pas == 0) {
    247         crondlog("\011failed to get uid for %s", user);
    248         return -1;
    249     }
    250     setenv("USER", pas->pw_name, 1);
    251     setenv("HOME", pas->pw_dir, 1);
    252     setenv("SHELL", DEFAULT_SHELL, 1);
    253 
    254     /*
    255      * Change running state to the user in question
    256      */
    257     err_msg = change_identity_e2str(pas);
    258     if (err_msg) {
    259         crondlog("\011%s for user %s", err_msg, user);
    260         return -1;
    261     }
    262     if (chdir(pas->pw_dir) < 0) {
    263         crondlog("\011chdir failed: %s: %m", pas->pw_dir);
    264         if (chdir(TMPDIR) < 0) {
    265             crondlog("\011chdir failed: %s: %m", TMPDIR);
    266             return -1;
    267         }
    268     }
    269     return pas->pw_uid;
    270 }
    271 
    272 static void startlogger(void)
    273 {
    274     if (LogFile == 0) {
    275         openlog(applet_name, LOG_CONS | LOG_PID, LOG_CRON);
    276     }
    277 #if ENABLE_DEBUG_CROND_OPTION
    278     else {              /* test logfile */
    279         int logfd;
    280 
    281         logfd = open3_or_warn(LogFile, O_WRONLY | O_CREAT | O_APPEND, 0600);
    282         if (logfd >= 0) {
    283             close(logfd);
    284         }
    285     }
    286 #endif
    287 }
    288 
     144}
    289145
    290146static const char DowAry[] ALIGN1 =
     
    298154;
    299155
    300 static char *ParseField(char *user, char *ary, int modvalue, int off,
     156static void ParseField(char *user, char *ary, int modvalue, int off,
    301157                const char *names, char *ptr)
    302158/* 'names' is a pointer to a set of 3-char abbreviations */
     
    306162    int n2 = -1;
    307163
    308     if (base == NULL) {
    309         return NULL;
    310     }
    311 
    312     while (*ptr != ' ' && *ptr != '\t' && *ptr != '\n') {
     164    // this can't happen due to config_read()
     165    /*if (base == NULL)
     166        return;*/
     167
     168    while (1) {
    313169        int skip = 0;
    314170
    315171        /* Handle numeric digit or symbol or '*' */
    316 
    317172        if (*ptr == '*') {
    318             n1 = 0;     /* everything will be filled */
     173            n1 = 0;  /* everything will be filled */
    319174            n2 = modvalue - 1;
    320175            skip = 1;
    321176            ++ptr;
    322         } else if (*ptr >= '0' && *ptr <= '9') {
     177        } else if (isdigit(*ptr)) {
     178            char *endp;
    323179            if (n1 < 0) {
    324                 n1 = strtol(ptr, &ptr, 10) + off;
     180                n1 = strtol(ptr, &endp, 10) + off;
    325181            } else {
    326                 n2 = strtol(ptr, &ptr, 10) + off;
    327             }
     182                n2 = strtol(ptr, &endp, 10) + off;
     183            }
     184            ptr = endp; /* gcc likes temp var for &endp */
    328185            skip = 1;
    329186        } else if (names) {
     
    346203
    347204        /* handle optional range '-' */
    348 
    349205        if (skip == 0) {
    350             crondlog("\111failed user %s parsing %s\n", user, base);
    351             return NULL;
     206            goto err;
    352207        }
    353208        if (*ptr == '-' && n2 < 0) {
     
    360215         * in the character array appropriately.
    361216         */
    362 
    363217        if (n2 < 0) {
    364218            n2 = n1;
    365219        }
    366220        if (*ptr == '/') {
    367             skip = strtol(ptr + 1, &ptr, 10);
    368         }
     221            char *endp;
     222            skip = strtol(ptr + 1, &endp, 10);
     223            ptr = endp; /* gcc likes temp var for &endp */
     224        }
     225
    369226        /*
    370227         * fill array, using a failsafe is the easiest way to prevent
    371228         * an endless loop
    372229         */
    373 
    374230        {
    375231            int s0 = 1;
     
    384240                    s0 = skip;
    385241                }
    386             }
    387             while (n1 != n2 && --failsafe);
    388 
    389             if (failsafe == 0) {
    390                 crondlog("\111failed user %s parsing %s\n", user, base);
    391                 return NULL;
    392             }
     242                if (--failsafe == 0) {
     243                    goto err;
     244                }
     245            } while (n1 != n2);
    393246        }
    394247        if (*ptr != ',') {
     
    400253    }
    401254
    402     if (*ptr != ' ' && *ptr != '\t' && *ptr != '\n') {
    403         crondlog("\111failed user %s parsing %s\n", user, base);
    404         return NULL;
    405     }
    406 
    407     while (*ptr == ' ' || *ptr == '\t' || *ptr == '\n') {
    408         ++ptr;
    409     }
    410 #if ENABLE_DEBUG_CROND_OPTION
    411     if (DebugOpt) {
     255    if (*ptr) {
     256 err:
     257        crondlog(WARN9 "user %s: parse error at %s", user, base);
     258        return;
     259    }
     260
     261    if (DebugOpt && (G.log_level <= 5)) { /* like LVL5 */
     262        /* can't use crondlog, it inserts '\n' */
    412263        int i;
    413 
    414         for (i = 0; i < modvalue; ++i) {
    415             crondlog("\005%d", ary[i]);
    416         }
    417         crondlog("\005\n");
    418     }
    419 #endif
    420 
    421     return ptr;
    422 }
    423 
    424 static void FixDayDow(CronLine * line)
    425 {
    426     int i;
     264        for (i = 0; i < modvalue; ++i)
     265            fprintf(stderr, "%d", (unsigned char)ary[i]);
     266        bb_putchar_stderr('\n');
     267    }
     268}
     269
     270static void FixDayDow(CronLine *line)
     271{
     272    unsigned i;
    427273    int weekUsed = 0;
    428274    int daysUsed = 0;
    429275
    430     for (i = 0; i < (int)(ARRAY_SIZE(line->cl_Dow)); ++i) {
     276    for (i = 0; i < ARRAY_SIZE(line->cl_Dow); ++i) {
    431277        if (line->cl_Dow[i] == 0) {
    432278            weekUsed = 1;
     
    434280        }
    435281    }
    436     for (i = 0; i < (int)(ARRAY_SIZE(line->cl_Days)); ++i) {
     282    for (i = 0; i < ARRAY_SIZE(line->cl_Days); ++i) {
    437283        if (line->cl_Days[i] == 0) {
    438284            daysUsed = 1;
     
    440286        }
    441287    }
    442     if (weekUsed && !daysUsed) {
    443         memset(line->cl_Days, 0, sizeof(line->cl_Days));
    444     }
    445     if (daysUsed && !weekUsed) {
    446         memset(line->cl_Dow, 0, sizeof(line->cl_Dow));
    447     }
    448 }
    449 
    450 
    451 
    452 static void SynchronizeFile(const char *fileName)
    453 {
    454     int maxEntries = MAXLINES;
     288    if (weekUsed != daysUsed) {
     289        if (weekUsed)
     290            memset(line->cl_Days, 0, sizeof(line->cl_Days));
     291        else /* daysUsed */
     292            memset(line->cl_Dow, 0, sizeof(line->cl_Dow));
     293    }
     294}
     295
     296/*
     297 * delete_cronfile() - delete user database
     298 *
     299 * Note: multiple entries for same user may exist if we were unable to
     300 * completely delete a database due to running processes.
     301 */
     302//FIXME: we will start a new job even if the old job is running
     303//if crontab was reloaded: crond thinks that "new" job is different from "old"
     304//even if they are in fact completely the same. Example
     305//Crontab was:
     306// 0-59 * * * * job1
     307// 0-59 * * * * long_running_job2
     308//User edits crontab to:
     309// 0-59 * * * * job1_updated
     310// 0-59 * * * * long_running_job2
     311//Bug: crond can now start another long_running_job2 even if old one
     312//is still running.
     313//OTOH most other versions of cron do not wait for job termination anyway,
     314//they end up with multiple copies of jobs if they don't terminate soon enough.
     315static void delete_cronfile(const char *userName)
     316{
     317    CronFile **pfile = &G.cron_files;
     318    CronFile *file;
     319
     320    while ((file = *pfile) != NULL) {
     321        if (strcmp(userName, file->cf_username) == 0) {
     322            CronLine **pline = &file->cf_lines;
     323            CronLine *line;
     324
     325            file->cf_has_running = 0;
     326            file->cf_deleted = 1;
     327
     328            while ((line = *pline) != NULL) {
     329                if (line->cl_pid > 0) {
     330                    file->cf_has_running = 1;
     331                    pline = &line->cl_next;
     332                } else {
     333                    *pline = line->cl_next;
     334                    free(line->cl_cmd);
     335                    free(line);
     336                }
     337            }
     338            if (file->cf_has_running == 0) {
     339                *pfile = file->cf_next;
     340                free(file->cf_username);
     341                free(file);
     342                continue;
     343            }
     344        }
     345        pfile = &file->cf_next;
     346    }
     347}
     348
     349static void load_crontab(const char *fileName)
     350{
     351    struct parser_t *parser;
     352    struct stat sbuf;
    455353    int maxLines;
    456     char buf[1024];
    457 
    458     if (strcmp(fileName, "root") == 0) {
    459         maxEntries = 65535;
    460     }
    461     maxLines = maxEntries * 10;
    462 
    463     if (fileName) {
    464         FILE *fi;
    465 
    466         DeleteFile(fileName);
    467 
    468         fi = fopen(fileName, "r");
    469         if (fi != NULL) {
    470             struct stat sbuf;
    471 
    472             if (fstat(fileno(fi), &sbuf) == 0 && sbuf.st_uid == DaemonUid) {
    473                 CronFile *file = xzalloc(sizeof(CronFile));
    474                 CronLine **pline;
    475 
    476                 file->cf_User = strdup(fileName);
    477                 pline = &file->cf_LineBase;
    478 
    479                 while (fgets(buf, sizeof(buf), fi) != NULL && --maxLines) {
    480                     CronLine line;
    481                     char *ptr;
    482 
    483                     trim(buf);
    484                     if (buf[0] == 0 || buf[0] == '#') {
    485                         continue;
    486                     }
    487                     if (--maxEntries == 0) {
    488                         break;
    489                     }
    490                     memset(&line, 0, sizeof(line));
    491 
    492 #if ENABLE_DEBUG_CROND_OPTION
    493                     if (DebugOpt) {
    494                         crondlog("\111User %s Entry %s\n", fileName, buf);
    495                     }
    496 #endif
    497 
    498                     /* parse date ranges */
    499                     ptr = ParseField(file->cf_User, line.cl_Mins, 60, 0, NULL, buf);
    500                     ptr = ParseField(file->cf_User, line.cl_Hrs, 24, 0, NULL, ptr);
    501                     ptr = ParseField(file->cf_User, line.cl_Days, 32, 0, NULL, ptr);
    502                     ptr = ParseField(file->cf_User, line.cl_Mons, 12, -1, MonAry, ptr);
    503                     ptr = ParseField(file->cf_User, line.cl_Dow, 7, 0, DowAry, ptr);
    504 
    505                     /* check failure */
    506                     if (ptr == NULL) {
    507                         continue;
    508                     }
    509 
    510                     /*
    511                      * fix days and dow - if one is not * and the other
    512                      * is *, the other is set to 0, and vise-versa
    513                      */
    514 
    515                     FixDayDow(&line);
    516 
    517                     *pline = xzalloc(sizeof(CronLine));
    518                     **pline = line;
    519 
    520                     /* copy command */
    521                     (*pline)->cl_Shell = strdup(ptr);
    522 
    523 #if ENABLE_DEBUG_CROND_OPTION
    524                     if (DebugOpt) {
    525                         crondlog("\111    Command %s\n", ptr);
    526                     }
    527 #endif
    528 
    529                     pline = &((*pline)->cl_Next);
    530                 }
    531                 *pline = NULL;
    532 
    533                 file->cf_Next = FileBase;
    534                 FileBase = file;
    535 
    536                 if (maxLines == 0 || maxEntries == 0) {
    537                     crondlog("\111Maximum number of lines reached for user %s\n", fileName);
    538                 }
    539             }
    540             fclose(fi);
    541         }
    542     }
    543 }
    544 
    545 static void CheckUpdates(void)
     354    char *tokens[6];
     355#if ENABLE_FEATURE_CROND_CALL_SENDMAIL
     356    char *mailTo = NULL;
     357#endif
     358
     359    delete_cronfile(fileName);
     360
     361    if (!getpwnam(fileName)) {
     362        crondlog(LVL7 "ignoring file '%s' (no such user)", fileName);
     363        return;
     364    }
     365
     366    parser = config_open(fileName);
     367    if (!parser)
     368        return;
     369
     370    maxLines = (strcmp(fileName, "root") == 0) ? 65535 : MAXLINES;
     371
     372    if (fstat(fileno(parser->fp), &sbuf) == 0 && sbuf.st_uid == DAEMON_UID) {
     373        CronFile *file = xzalloc(sizeof(CronFile));
     374        CronLine **pline;
     375        int n;
     376
     377        file->cf_username = xstrdup(fileName);
     378        pline = &file->cf_lines;
     379
     380        while (1) {
     381            CronLine *line;
     382
     383            if (!--maxLines)
     384                break;
     385            n = config_read(parser, tokens, 6, 1, "# \t", PARSE_NORMAL | PARSE_KEEP_COPY);
     386            if (!n)
     387                break;
     388
     389            if (DebugOpt)
     390                crondlog(LVL5 "user:%s entry:%s", fileName, parser->data);
     391
     392            /* check if line is setting MAILTO= */
     393            if (0 == strncmp(tokens[0], "MAILTO=", 7)) {
     394#if ENABLE_FEATURE_CROND_CALL_SENDMAIL
     395                free(mailTo);
     396                mailTo = (tokens[0][7]) ? xstrdup(&tokens[0][7]) : NULL;
     397#endif /* otherwise just ignore such lines */
     398                continue;
     399            }
     400            /* check if a minimum of tokens is specified */
     401            if (n < 6)
     402                continue;
     403            *pline = line = xzalloc(sizeof(*line));
     404            /* parse date ranges */
     405            ParseField(file->cf_username, line->cl_Mins, 60, 0, NULL, tokens[0]);
     406            ParseField(file->cf_username, line->cl_Hrs, 24, 0, NULL, tokens[1]);
     407            ParseField(file->cf_username, line->cl_Days, 32, 0, NULL, tokens[2]);
     408            ParseField(file->cf_username, line->cl_Mons, 12, -1, MonAry, tokens[3]);
     409            ParseField(file->cf_username, line->cl_Dow, 7, 0, DowAry, tokens[4]);
     410            /*
     411             * fix days and dow - if one is not "*" and the other
     412             * is "*", the other is set to 0, and vise-versa
     413             */
     414            FixDayDow(line);
     415#if ENABLE_FEATURE_CROND_CALL_SENDMAIL
     416            /* copy mailto (can be NULL) */
     417            line->cl_mailto = xstrdup(mailTo);
     418#endif
     419            /* copy command */
     420            line->cl_cmd = xstrdup(tokens[5]);
     421            if (DebugOpt) {
     422                crondlog(LVL5 " command:%s", tokens[5]);
     423            }
     424            pline = &line->cl_next;
     425//bb_error_msg("M[%s]F[%s][%s][%s][%s][%s][%s]", mailTo, tokens[0], tokens[1], tokens[2], tokens[3], tokens[4], tokens[5]);
     426        }
     427        *pline = NULL;
     428
     429        file->cf_next = G.cron_files;
     430        G.cron_files = file;
     431
     432        if (maxLines == 0) {
     433            crondlog(WARN9 "user %s: too many lines", fileName);
     434        }
     435    }
     436    config_close(parser);
     437}
     438
     439static void process_cron_update_file(void)
    546440{
    547441    FILE *fi;
    548442    char buf[256];
    549443
    550     fi = fopen(CRONUPDATE, "r");
     444    fi = fopen_for_read(CRONUPDATE);
    551445    if (fi != NULL) {
    552         remove(CRONUPDATE);
     446        unlink(CRONUPDATE);
    553447        while (fgets(buf, sizeof(buf), fi) != NULL) {
    554             SynchronizeFile(strtok(buf, " \t\r\n"));
     448            /* use first word only */
     449            skip_non_whitespace(buf)[0] = '\0';
     450            load_crontab(buf);
    555451        }
    556452        fclose(fi);
     
    558454}
    559455
    560 static void SynchronizeDir(void)
    561 {
    562     /* Attempt to delete the database. */
    563 
    564     for (;;) {
    565         CronFile *file;
    566 
    567         for (file = FileBase; file && file->cf_Deleted; file = file->cf_Next);
    568         if (file == NULL) {
    569             break;
    570         }
    571         DeleteFile(file->cf_User);
    572     }
    573 
    574     /*
    575      * Remove cron update file
    576      *
    577      * Re-chdir, in case directory was renamed & deleted, or otherwise
    578      * screwed up.
    579      *
    580      * scan directory and add associated users
    581      */
    582 
    583     remove(CRONUPDATE);
    584     if (chdir(CDir) < 0) {
    585         crondlog("\311cannot find %s\n", CDir);
    586     }
     456static void rescan_crontab_dir(void)
     457{
     458    CronFile *file;
     459
     460    /* Delete all files until we only have ones with running jobs (or none) */
     461 again:
     462    for (file = G.cron_files; file; file = file->cf_next) {
     463        if (!file->cf_deleted) {
     464            delete_cronfile(file->cf_username);
     465            goto again;
     466        }
     467    }
     468
     469    /* Remove cron update file */
     470    unlink(CRONUPDATE);
     471    /* Re-chdir, in case directory was renamed & deleted */
     472    if (chdir(G.crontab_dir_name) < 0) {
     473        crondlog(DIE9 "chdir(%s)", G.crontab_dir_name);
     474    }
     475
     476    /* Scan directory and add associated users */
    587477    {
    588478        DIR *dir = opendir(".");
    589479        struct dirent *den;
    590480
    591         if (dir) {
    592             while ((den = readdir(dir))) {
    593                 if (strchr(den->d_name, '.') != NULL) {
    594                     continue;
    595                 }
    596                 if (getpwnam(den->d_name)) {
    597                     SynchronizeFile(den->d_name);
    598                 } else {
    599                     crondlog("\007ignoring %s\n", den->d_name);
    600                 }
    601             }
    602             closedir(dir);
    603         } else {
    604             crondlog("\311cannot open current dir!\n");
    605         }
    606     }
    607 }
    608 
    609 
    610 /*
    611  *  DeleteFile() - delete user database
    612  *
    613  *  Note: multiple entries for same user may exist if we were unable to
    614  *  completely delete a database due to running processes.
    615  */
    616 
    617 static void DeleteFile(const char *userName)
    618 {
    619     CronFile **pfile = &FileBase;
    620     CronFile *file;
    621 
    622     while ((file = *pfile) != NULL) {
    623         if (strcmp(userName, file->cf_User) == 0) {
    624             CronLine **pline = &file->cf_LineBase;
    625             CronLine *line;
    626 
    627             file->cf_Running = 0;
    628             file->cf_Deleted = 1;
    629 
    630             while ((line = *pline) != NULL) {
    631                 if (line->cl_Pid > 0) {
    632                     file->cf_Running = 1;
    633                     pline = &line->cl_Next;
    634                 } else {
    635                     *pline = line->cl_Next;
    636                     free(line->cl_Shell);
    637                     free(line);
    638                 }
    639             }
    640             if (file->cf_Running == 0) {
    641                 *pfile = file->cf_Next;
    642                 free(file->cf_User);
    643                 free(file);
    644             } else {
    645                 pfile = &file->cf_Next;
    646             }
    647         } else {
    648             pfile = &file->cf_Next;
    649         }
    650     }
    651 }
    652 
    653 /*
    654  * TestJobs()
    655  *
    656  * determine which jobs need to be run.  Under normal conditions, the
    657  * period is about a minute (one scan).  Worst case it will be one
    658  * hour (60 scans).
    659  */
    660 
    661 static int TestJobs(time_t t1, time_t t2)
    662 {
    663     int nJobs = 0;
    664     time_t t;
    665 
    666     /* Find jobs > t1 and <= t2 */
    667 
    668     for (t = t1 - t1 % 60; t <= t2; t += 60) {
    669         if (t > t1) {
    670             struct tm *tp = localtime(&t);
    671             CronFile *file;
    672             CronLine *line;
    673 
    674             for (file = FileBase; file; file = file->cf_Next) {
    675 #if ENABLE_DEBUG_CROND_OPTION
    676                 if (DebugOpt)
    677                     crondlog("\005FILE %s:\n", file->cf_User);
    678 #endif
    679                 if (file->cf_Deleted)
    680                     continue;
    681                 for (line = file->cf_LineBase; line; line = line->cl_Next) {
    682 #if ENABLE_DEBUG_CROND_OPTION
    683                     if (DebugOpt)
    684                         crondlog("\005    LINE %s\n", line->cl_Shell);
    685 #endif
    686                     if (line->cl_Mins[tp->tm_min] && line->cl_Hrs[tp->tm_hour] &&
    687                         (line->cl_Days[tp->tm_mday] || line->cl_Dow[tp->tm_wday])
    688                         && line->cl_Mons[tp->tm_mon]) {
    689 #if ENABLE_DEBUG_CROND_OPTION
    690                         if (DebugOpt) {
    691                             crondlog("\005    JobToDo: %d %s\n",
    692                                 line->cl_Pid, line->cl_Shell);
    693                         }
    694 #endif
    695                         if (line->cl_Pid > 0) {
    696                             crondlog("\010    process already running: %s %s\n",
    697                                 file->cf_User, line->cl_Shell);
    698                         } else if (line->cl_Pid == 0) {
    699                             line->cl_Pid = -1;
    700                             file->cf_Ready = 1;
    701                             ++nJobs;
    702                         }
    703                     }
    704                 }
    705             }
    706         }
    707     }
    708     return nJobs;
    709 }
    710 
    711 static void RunJobs(void)
    712 {
    713     CronFile *file;
    714     CronLine *line;
    715 
    716     for (file = FileBase; file; file = file->cf_Next) {
    717         if (file->cf_Ready) {
    718             file->cf_Ready = 0;
    719 
    720             for (line = file->cf_LineBase; line; line = line->cl_Next) {
    721                 if (line->cl_Pid < 0) {
    722 
    723                     RunJob(file->cf_User, line);
    724 
    725                     crondlog("\010USER %s pid %3d cmd %s\n",
    726                         file->cf_User, line->cl_Pid, line->cl_Shell);
    727                     if (line->cl_Pid < 0) {
    728                         file->cf_Ready = 1;
    729                     }
    730                     else if (line->cl_Pid > 0) {
    731                         file->cf_Running = 1;
    732                     }
    733                 }
    734             }
    735         }
    736     }
    737 }
    738 
    739 /*
    740  * CheckJobs() - check for job completion
    741  *
    742  * Check for job completion, return number of jobs still running after
    743  * all done.
    744  */
    745 
    746 static int CheckJobs(void)
    747 {
    748     CronFile *file;
    749     CronLine *line;
    750     int nStillRunning = 0;
    751 
    752     for (file = FileBase; file; file = file->cf_Next) {
    753         if (file->cf_Running) {
    754             file->cf_Running = 0;
    755 
    756             for (line = file->cf_LineBase; line; line = line->cl_Next) {
    757                 if (line->cl_Pid > 0) {
    758                     int status;
    759                     int r = wait4(line->cl_Pid, &status, WNOHANG, NULL);
    760 
    761                     if (r < 0 || r == line->cl_Pid) {
    762                         EndJob(file->cf_User, line);
    763                         if (line->cl_Pid) {
    764                             file->cf_Running = 1;
    765                         }
    766                     } else if (r == 0) {
    767                         file->cf_Running = 1;
    768                     }
    769                 }
    770             }
    771         }
    772         nStillRunning += file->cf_Running;
    773     }
    774     return nStillRunning;
    775 }
    776 
    777 
     481        if (!dir)
     482            crondlog(DIE9 "chdir(%s)", "."); /* exits */
     483        while ((den = readdir(dir)) != NULL) {
     484            if (strchr(den->d_name, '.') != NULL) {
     485                continue;
     486            }
     487            load_crontab(den->d_name);
     488        }
     489        closedir(dir);
     490    }
     491}
     492
     493#if SETENV_LEAKS
     494/* We set environment *before* vfork (because we want to use vfork),
     495 * so we cannot use setenv() - repeated calls to setenv() may leak memory!
     496 * Using putenv(), and freeing memory after unsetenv() won't leak */
     497static void safe_setenv(char **pvar_val, const char *var, const char *val)
     498{
     499    char *var_val = *pvar_val;
     500
     501    if (var_val) {
     502        bb_unsetenv_and_free(var_val);
     503    }
     504    *pvar_val = xasprintf("%s=%s", var, val);
     505    putenv(*pvar_val);
     506}
     507#endif
     508
     509static void set_env_vars(struct passwd *pas)
     510{
     511#if SETENV_LEAKS
     512    safe_setenv(&G.env_var_user, "USER", pas->pw_name);
     513    safe_setenv(&G.env_var_home, "HOME", pas->pw_dir);
     514    /* if we want to set user's shell instead: */
     515    /*safe_setenv(G.env_var_shell, "SHELL", pas->pw_shell);*/
     516#else
     517    xsetenv("USER", pas->pw_name);
     518    xsetenv("HOME", pas->pw_dir);
     519#endif
     520    /* currently, we use constant one: */
     521    /*setenv("SHELL", DEFAULT_SHELL, 1); - done earlier */
     522}
     523
     524static void change_user(struct passwd *pas)
     525{
     526    /* careful: we're after vfork! */
     527    change_identity(pas); /* - initgroups, setgid, setuid */
     528    if (chdir(pas->pw_dir) < 0) {
     529        crondlog(WARN9 "chdir(%s)", pas->pw_dir);
     530        if (chdir(TMPDIR) < 0) {
     531            crondlog(DIE9 "chdir(%s)", TMPDIR); /* exits */
     532        }
     533    }
     534}
     535
     536// TODO: sendmail should be _run-time_ option, not compile-time!
    778537#if ENABLE_FEATURE_CROND_CALL_SENDMAIL
    779 static void
    780 ForkJob(const char *user, CronLine * line, int mailFd,
    781         const char *prog, const char *cmd, const char *arg, const char *mailf)
    782 {
    783     /* Fork as the user in question and run program */
    784     pid_t pid = fork();
    785 
    786     line->cl_Pid = pid;
     538
     539static pid_t
     540fork_job(const char *user, int mailFd,
     541        const char *prog,
     542        const char *shell_cmd /* if NULL, we run sendmail */
     543) {
     544    struct passwd *pas;
     545    pid_t pid;
     546
     547    /* prepare things before vfork */
     548    pas = getpwnam(user);
     549    if (!pas) {
     550        crondlog(WARN9 "can't get uid for %s", user);
     551        goto err;
     552    }
     553    set_env_vars(pas);
     554
     555    pid = vfork();
    787556    if (pid == 0) {
    788557        /* CHILD */
    789 
    790         /* Change running state to the user in question */
    791 
    792         if (ChangeUser(user) < 0) {
    793             exit(0);
    794         }
    795 #if ENABLE_DEBUG_CROND_OPTION
     558        /* initgroups, setgid, setuid, and chdir to home or TMPDIR */
     559        change_user(pas);
    796560        if (DebugOpt) {
    797             crondlog("\005Child Running %s\n", prog);
    798         }
    799 #endif
    800 
     561            crondlog(LVL5 "child running %s", prog);
     562        }
    801563        if (mailFd >= 0) {
    802             dup2(mailFd, mailf != NULL);
    803             dup2((mailf ? mailFd : 1), 2);
    804             close(mailFd);
    805         }
    806         execl(prog, prog, cmd, arg, NULL);
    807         crondlog("\024cannot exec, user %s cmd %s %s %s\n", user, prog, cmd, arg);
    808         if (mailf) {
    809             fdprintf(1, "Exec failed: %s -c %s\n", prog, arg);
    810         }
    811         exit(0);
    812     } else if (pid < 0) {
     564            xmove_fd(mailFd, shell_cmd ? 1 : 0);
     565            dup2(1, 2);
     566        }
     567        /* crond 3.0pl1-100 puts tasks in separate process groups */
     568        bb_setpgrp();
     569        execlp(prog, prog, (shell_cmd ? "-c" : SENDMAIL_ARGS), shell_cmd, (char *) NULL);
     570        crondlog(ERR20 "can't execute '%s' for user %s", prog, user);
     571        if (shell_cmd) {
     572            fdprintf(1, "Exec failed: %s -c %s\n", prog, shell_cmd);
     573        }
     574        _exit(EXIT_SUCCESS);
     575    }
     576
     577    if (pid < 0) {
    813578        /* FORK FAILED */
    814         crondlog("\024cannot fork, user %s\n", user);
    815         line->cl_Pid = 0;
    816         if (mailf) {
    817             remove(mailf);
    818         }
    819     } else if (mailf) {
    820         /* PARENT, FORK SUCCESS
    821          * rename mail-file based on pid of process
    822          */
    823         char mailFile2[128];
    824 
    825         snprintf(mailFile2, sizeof(mailFile2), TMPDIR "/cron.%s.%d", user, pid);
    826         rename(mailf, mailFile2);
    827     }
     579        crondlog(ERR20 "can't vfork");
     580 err:
     581        pid = 0;
     582    } /* else: PARENT, FORK SUCCESS */
     583
    828584    /*
    829585     * Close the mail file descriptor.. we can't just leave it open in
    830586     * a structure, closing it later, because we might run out of descriptors
    831587     */
    832 
    833588    if (mailFd >= 0) {
    834589        close(mailFd);
    835590    }
    836 }
    837 
    838 static void RunJob(const char *user, CronLine * line)
     591    return pid;
     592}
     593
     594static void start_one_job(const char *user, CronLine *line)
    839595{
    840596    char mailFile[128];
    841     int mailFd;
    842 
    843     line->cl_Pid = 0;
    844     line->cl_MailFlag = 0;
    845 
    846     /* open mail file - owner root so nobody can screw with it. */
    847 
    848     snprintf(mailFile, sizeof(mailFile), TMPDIR "/cron.%s.%d", user, getpid());
    849     mailFd = open(mailFile, O_CREAT | O_TRUNC | O_WRONLY | O_EXCL | O_APPEND, 0600);
    850 
     597    int mailFd = -1;
     598
     599    line->cl_pid = 0;
     600    line->cl_empty_mail_size = 0;
     601
     602    if (line->cl_mailto) {
     603        /* Open mail file (owner is root so nobody can screw with it) */
     604        snprintf(mailFile, sizeof(mailFile), "%s/cron.%s.%d", TMPDIR, user, getpid());
     605        mailFd = open(mailFile, O_CREAT | O_TRUNC | O_WRONLY | O_EXCL | O_APPEND, 0600);
     606
     607        if (mailFd >= 0) {
     608            fdprintf(mailFd, "To: %s\nSubject: cron: %s\n\n", line->cl_mailto,
     609                line->cl_cmd);
     610            line->cl_empty_mail_size = lseek(mailFd, 0, SEEK_CUR);
     611        } else {
     612            crondlog(ERR20 "can't create mail file %s for user %s, "
     613                    "discarding output", mailFile, user);
     614        }
     615    }
     616
     617    line->cl_pid = fork_job(user, mailFd, DEFAULT_SHELL, line->cl_cmd);
    851618    if (mailFd >= 0) {
    852         line->cl_MailFlag = 1;
    853         fdprintf(mailFd, "To: %s\nSubject: cron: %s\n\n", user,
    854             line->cl_Shell);
    855         line->cl_MailPos = lseek(mailFd, 0, SEEK_CUR);
    856     } else {
    857         crondlog("\024cannot create mail file user %s file %s, output to /dev/null\n", user, mailFile);
    858     }
    859 
    860     ForkJob(user, line, mailFd, DEFAULT_SHELL, "-c", line->cl_Shell, mailFile);
     619        if (line->cl_pid <= 0) {
     620            unlink(mailFile);
     621        } else {
     622            /* rename mail-file based on pid of process */
     623            char *mailFile2 = xasprintf("%s/cron.%s.%d", TMPDIR, user, (int)line->cl_pid);
     624            rename(mailFile, mailFile2); // TODO: xrename?
     625            free(mailFile2);
     626        }
     627    }
    861628}
    862629
    863630/*
    864  * EndJob - called when job terminates and when mail terminates
     631 * process_finished_job - called when job terminates and when mail terminates
    865632 */
    866 
    867 static void EndJob(const char *user, CronLine * line)
    868 {
     633static void process_finished_job(const char *user, CronLine *line)
     634{
     635    pid_t pid;
    869636    int mailFd;
    870637    char mailFile[128];
    871638    struct stat sbuf;
    872639
    873     /* No job */
    874 
    875     if (line->cl_Pid <= 0) {
    876         line->cl_Pid = 0;
     640    pid = line->cl_pid;
     641    line->cl_pid = 0;
     642    if (pid <= 0) {
     643        /* No job */
    877644        return;
    878645    }
     646    if (line->cl_empty_mail_size <= 0) {
     647        /* End of job and no mail file, or end of sendmail job */
     648        return;
     649    }
    879650
    880651    /*
    881      * End of job and no mail file
    882      * End of sendmail job
     652     * End of primary job - check for mail file.
     653     * If size has changed and the file is still valid, we send it.
    883654     */
    884 
    885     snprintf(mailFile, sizeof(mailFile), TMPDIR "/cron.%s.%d", user, line->cl_Pid);
    886     line->cl_Pid = 0;
    887 
    888     if (line->cl_MailFlag != 1) {
    889         return;
    890     }
    891     line->cl_MailFlag = 0;
    892 
    893     /*
    894      * End of primary job - check for mail file.  If size has increased and
    895      * the file is still valid, we sendmail it.
    896      */
    897 
     655    snprintf(mailFile, sizeof(mailFile), "%s/cron.%s.%d", TMPDIR, user, (int)pid);
    898656    mailFd = open(mailFile, O_RDONLY);
    899     remove(mailFile);
     657    unlink(mailFile);
    900658    if (mailFd < 0) {
    901659        return;
    902660    }
    903661
    904     if (fstat(mailFd, &sbuf) < 0 || sbuf.st_uid != DaemonUid
    905      || sbuf.st_nlink != 0 || sbuf.st_size == line->cl_MailPos
     662    if (fstat(mailFd, &sbuf) < 0
     663     || sbuf.st_uid != DAEMON_UID
     664     || sbuf.st_nlink != 0
     665     || sbuf.st_size == line->cl_empty_mail_size
    906666     || !S_ISREG(sbuf.st_mode)
    907667    ) {
     
    909669        return;
    910670    }
    911     ForkJob(user, line, mailFd, SENDMAIL, SENDMAIL_ARGS, NULL);
    912 }
    913 #else
    914 /* crond without sendmail */
    915 
    916 static void RunJob(const char *user, CronLine * line)
    917 {
     671    line->cl_empty_mail_size = 0;
     672    /* if (line->cl_mailto) - always true if cl_empty_mail_size was nonzero */
     673        line->cl_pid = fork_job(user, mailFd, SENDMAIL, NULL);
     674}
     675
     676#else /* !ENABLE_FEATURE_CROND_CALL_SENDMAIL */
     677
     678static void start_one_job(const char *user, CronLine *line)
     679{
     680    struct passwd *pas;
     681    pid_t pid;
     682
     683    pas = getpwnam(user);
     684    if (!pas) {
     685        crondlog(WARN9 "can't get uid for %s", user);
     686        goto err;
     687    }
     688
     689    /* Prepare things before vfork */
     690    set_env_vars(pas);
     691
    918692    /* Fork as the user in question and run program */
    919     pid_t pid = fork();
    920 
     693    pid = vfork();
    921694    if (pid == 0) {
    922695        /* CHILD */
    923 
    924         /* Change running state to the user in question */
    925 
    926         if (ChangeUser(user) < 0) {
    927             exit(0);
    928         }
    929 #if ENABLE_DEBUG_CROND_OPTION
     696        /* initgroups, setgid, setuid, and chdir to home or TMPDIR */
     697        change_user(pas);
    930698        if (DebugOpt) {
    931             crondlog("\005Child Running %s\n", DEFAULT_SHELL);
    932         }
    933 #endif
    934 
    935         execl(DEFAULT_SHELL, DEFAULT_SHELL, "-c", line->cl_Shell, NULL);
    936         crondlog("\024cannot exec, user %s cmd %s -c %s\n", user,
    937                  DEFAULT_SHELL, line->cl_Shell);
    938         exit(0);
    939     } else if (pid < 0) {
     699            crondlog(LVL5 "child running %s", DEFAULT_SHELL);
     700        }
     701        /* crond 3.0pl1-100 puts tasks in separate process groups */
     702        bb_setpgrp();
     703        execl(DEFAULT_SHELL, DEFAULT_SHELL, "-c", line->cl_cmd, (char *) NULL);
     704        crondlog(ERR20 "can't execute '%s' for user %s", DEFAULT_SHELL, user);
     705        _exit(EXIT_SUCCESS);
     706    }
     707    if (pid < 0) {
    940708        /* FORK FAILED */
    941         crondlog("\024cannot, user %s\n", user);
     709        crondlog(ERR20 "can't vfork");
     710 err:
    942711        pid = 0;
    943712    }
    944     line->cl_Pid = pid;
    945 }
    946 #endif /* ENABLE_FEATURE_CROND_CALL_SENDMAIL */
     713    line->cl_pid = pid;
     714}
     715
     716#define process_finished_job(user, line)  ((line)->cl_pid = 0)
     717
     718#endif /* !ENABLE_FEATURE_CROND_CALL_SENDMAIL */
     719
     720/*
     721 * Determine which jobs need to be run.  Under normal conditions, the
     722 * period is about a minute (one scan).  Worst case it will be one
     723 * hour (60 scans).
     724 */
     725static void flag_starting_jobs(time_t t1, time_t t2)
     726{
     727    time_t t;
     728
     729    /* Find jobs > t1 and <= t2 */
     730
     731    for (t = t1 - t1 % 60; t <= t2; t += 60) {
     732        struct tm *ptm;
     733        CronFile *file;
     734        CronLine *line;
     735
     736        if (t <= t1)
     737            continue;
     738
     739        ptm = localtime(&t);
     740        for (file = G.cron_files; file; file = file->cf_next) {
     741            if (DebugOpt)
     742                crondlog(LVL5 "file %s:", file->cf_username);
     743            if (file->cf_deleted)
     744                continue;
     745            for (line = file->cf_lines; line; line = line->cl_next) {
     746                if (DebugOpt)
     747                    crondlog(LVL5 " line %s", line->cl_cmd);
     748                if (line->cl_Mins[ptm->tm_min]
     749                 && line->cl_Hrs[ptm->tm_hour]
     750                 && (line->cl_Days[ptm->tm_mday] || line->cl_Dow[ptm->tm_wday])
     751                 && line->cl_Mons[ptm->tm_mon]
     752                ) {
     753                    if (DebugOpt) {
     754                        crondlog(LVL5 " job: %d %s",
     755                            (int)line->cl_pid, line->cl_cmd);
     756                    }
     757                    if (line->cl_pid > 0) {
     758                        crondlog(LVL8 "user %s: process already running: %s",
     759                            file->cf_username, line->cl_cmd);
     760                    } else if (line->cl_pid == 0) {
     761                        line->cl_pid = -1;
     762                        file->cf_wants_starting = 1;
     763                    }
     764                }
     765            }
     766        }
     767    }
     768}
     769
     770static void start_jobs(void)
     771{
     772    CronFile *file;
     773    CronLine *line;
     774
     775    for (file = G.cron_files; file; file = file->cf_next) {
     776        if (!file->cf_wants_starting)
     777            continue;
     778
     779        file->cf_wants_starting = 0;
     780        for (line = file->cf_lines; line; line = line->cl_next) {
     781            pid_t pid;
     782            if (line->cl_pid >= 0)
     783                continue;
     784
     785            start_one_job(file->cf_username, line);
     786            pid = line->cl_pid;
     787            crondlog(LVL8 "USER %s pid %3d cmd %s",
     788                file->cf_username, (int)pid, line->cl_cmd);
     789            if (pid < 0) {
     790                file->cf_wants_starting = 1;
     791            }
     792            if (pid > 0) {
     793                file->cf_has_running = 1;
     794            }
     795        }
     796    }
     797}
     798
     799/*
     800 * Check for job completion, return number of jobs still running after
     801 * all done.
     802 */
     803static int check_completions(void)
     804{
     805    CronFile *file;
     806    CronLine *line;
     807    int num_still_running = 0;
     808
     809    for (file = G.cron_files; file; file = file->cf_next) {
     810        if (!file->cf_has_running)
     811            continue;
     812
     813        file->cf_has_running = 0;
     814        for (line = file->cf_lines; line; line = line->cl_next) {
     815            int r;
     816
     817            if (line->cl_pid <= 0)
     818                continue;
     819
     820            r = waitpid(line->cl_pid, NULL, WNOHANG);
     821            if (r < 0 || r == line->cl_pid) {
     822                process_finished_job(file->cf_username, line);
     823                if (line->cl_pid == 0) {
     824                    /* sendmail was not started for it */
     825                    continue;
     826                }
     827                /* else: sendmail was started, job is still running, fall thru */
     828            }
     829            /* else: r == 0: "process is still running" */
     830            file->cf_has_running = 1;
     831        }
     832//FIXME: if !file->cf_has_running && file->deleted: delete it!
     833//otherwise deleted entries will stay forever, right?
     834        num_still_running += file->cf_has_running;
     835    }
     836    return num_still_running;
     837}
     838
     839int crond_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
     840int crond_main(int argc UNUSED_PARAM, char **argv)
     841{
     842    time_t t2;
     843    int rescan;
     844    int sleep_time;
     845    unsigned opts;
     846
     847    INIT_G();
     848
     849    /* "-b after -f is ignored", and so on for every pair a-b */
     850    opt_complementary = "f-b:b-f:S-L:L-S" IF_FEATURE_CROND_D(":d-l")
     851            ":l+:d+"; /* -l and -d have numeric param */
     852    opts = getopt32(argv, "l:L:fbSc:" IF_FEATURE_CROND_D("d:"),
     853            &G.log_level, &G.log_filename, &G.crontab_dir_name
     854            IF_FEATURE_CROND_D(,&G.log_level));
     855    /* both -d N and -l N set the same variable: G.log_level */
     856
     857    if (!(opts & OPT_f)) {
     858        /* close stdin, stdout, stderr.
     859         * close unused descriptors - don't need them. */
     860        bb_daemonize_or_rexec(DAEMON_CLOSE_EXTRA_FDS, argv);
     861    }
     862
     863    if (!(opts & OPT_d) && G.log_filename == NULL) {
     864        /* logging to syslog */
     865        openlog(applet_name, LOG_CONS | LOG_PID, LOG_CRON);
     866        logmode = LOGMODE_SYSLOG;
     867    }
     868
     869    xchdir(G.crontab_dir_name);
     870    //signal(SIGHUP, SIG_IGN); /* ? original crond dies on HUP... */
     871    xsetenv("SHELL", DEFAULT_SHELL); /* once, for all future children */
     872    crondlog(LVL8 "crond (busybox "BB_VER") started, log level %d", G.log_level);
     873    rescan_crontab_dir();
     874    write_pidfile("/var/run/crond.pid");
     875
     876    /* Main loop */
     877    t2 = time(NULL);
     878    rescan = 60;
     879    sleep_time = 60;
     880    for (;;) {
     881        struct stat sbuf;
     882        time_t t1;
     883        long dt;
     884
     885        t1 = t2;
     886
     887        /* Synchronize to 1 minute, minimum 1 second */
     888        sleep(sleep_time - (time(NULL) % sleep_time) + 1);
     889
     890        t2 = time(NULL);
     891        dt = (long)t2 - (long)t1;
     892
     893        /*
     894         * The file 'cron.update' is checked to determine new cron
     895         * jobs.  The directory is rescanned once an hour to deal
     896         * with any screwups.
     897         *
     898         * Check for time jump.  Disparities over an hour either way
     899         * result in resynchronization.  A negative disparity
     900         * less than an hour causes us to effectively sleep until we
     901         * match the original time (i.e. no re-execution of jobs that
     902         * have just been run).  A positive disparity less than
     903         * an hour causes intermediate jobs to be run, but only once
     904         * in the worst case.
     905         *
     906         * When running jobs, the inequality used is greater but not
     907         * equal to t1, and less then or equal to t2.
     908         */
     909        if (stat(G.crontab_dir_name, &sbuf) != 0)
     910            sbuf.st_mtime = 0; /* force update (once) if dir was deleted */
     911        if (G.crontab_dir_mtime != sbuf.st_mtime) {
     912            G.crontab_dir_mtime = sbuf.st_mtime;
     913            rescan = 1;
     914        }
     915        if (--rescan == 0) {
     916            rescan = 60;
     917            rescan_crontab_dir();
     918        }
     919        process_cron_update_file();
     920        if (DebugOpt)
     921            crondlog(LVL5 "wakeup dt=%ld", dt);
     922        if (dt < -60 * 60 || dt > 60 * 60) {
     923            crondlog(WARN9 "time disparity of %ld minutes detected", dt / 60);
     924            /* and we do not run any jobs in this case */
     925        } else if (dt > 0) {
     926            /* Usual case: time advances forward, as expected */
     927            flag_starting_jobs(t1, t2);
     928            start_jobs();
     929            if (check_completions() > 0) {
     930                /* some jobs are still running */
     931                sleep_time = 10;
     932            } else {
     933                sleep_time = 60;
     934            }
     935        }
     936        /* else: time jumped back, do not run any jobs */
     937    } /* for (;;) */
     938
     939    return 0; /* not reached */
     940}
Note: See TracChangeset for help on using the changeset viewer.