busybox/miscutils/crond.c
<<
>>
Prefs
   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 * (version 2.3.2)
   9 * Vladimir Oleynik <dzo@simtreas.ru> (C) 2002
  10 *
  11 * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
  12 */
  13
  14#include "libbb.h"
  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"
  28#ifndef SENDMAIL
  29#define SENDMAIL        "sendmail"
  30#endif
  31#ifndef SENDMAIL_ARGS
  32#define SENDMAIL_ARGS   "-ti", "oem"
  33#endif
  34#ifndef CRONUPDATE
  35#define CRONUPDATE      "cron.update"
  36#endif
  37#ifndef MAXLINES
  38#define MAXLINES        256     /* max lines in non-root crontabs */
  39#endif
  40
  41
  42typedef struct CronFile {
  43        struct CronFile *cf_Next;
  44        struct CronLine *cf_LineBase;
  45        char *cf_User;                  /* username                     */
  46        smallint cf_Ready;              /* bool: one or more jobs ready */
  47        smallint cf_Running;            /* bool: one or more jobs running */
  48        smallint cf_Deleted;            /* marked for deletion, ignore  */
  49} CronFile;
  50
  51typedef struct CronLine {
  52        struct CronLine *cl_Next;
  53        char *cl_Shell;         /* shell command                        */
  54        pid_t cl_Pid;           /* running pid, 0, or armed (-1)        */
  55#if ENABLE_FEATURE_CROND_CALL_SENDMAIL
  56        int cl_MailPos;         /* 'empty file' size                    */
  57        smallint cl_MailFlag;   /* running pid is for mail              */
  58        char *cl_MailTo;        /* whom to mail results                 */
  59#endif
  60        /* ordered by size, not in natural order. makes code smaller: */
  61        char cl_Dow[7];         /* 0-6, beginning sunday                */
  62        char cl_Mons[12];       /* 0-11                                 */
  63        char cl_Hrs[24];        /* 0-23                                 */
  64        char cl_Days[32];       /* 1-31                                 */
  65        char cl_Mins[60];       /* 0-59                                 */
  66} CronLine;
  67
  68
  69#define DaemonUid 0
  70
  71
  72enum {
  73        OPT_l = (1 << 0),
  74        OPT_L = (1 << 1),
  75        OPT_f = (1 << 2),
  76        OPT_b = (1 << 3),
  77        OPT_S = (1 << 4),
  78        OPT_c = (1 << 5),
  79        OPT_d = (1 << 6) * ENABLE_FEATURE_CROND_D,
  80};
  81#if ENABLE_FEATURE_CROND_D
  82#define DebugOpt (option_mask32 & OPT_d)
  83#else
  84#define DebugOpt 0
  85#endif
  86
  87
  88struct globals {
  89        unsigned LogLevel; /* = 8; */
  90        const char *LogFile;
  91        const char *CDir; /* = CRONTABS; */
  92        CronFile *FileBase;
  93#if SETENV_LEAKS
  94        char *env_var_user;
  95        char *env_var_home;
  96#endif
  97};
  98#define G (*(struct globals*)&bb_common_bufsiz1)
  99#define LogLevel           (G.LogLevel               )
 100#define LogFile            (G.LogFile                )
 101#define CDir               (G.CDir                   )
 102#define FileBase           (G.FileBase               )
 103#define env_var_user       (G.env_var_user           )
 104#define env_var_home       (G.env_var_home           )
 105#define INIT_G() do { \
 106        LogLevel = 8; \
 107        CDir = CRONTABS; \
 108} while (0)
 109
 110
 111static void CheckUpdates(void);
 112static void SynchronizeDir(void);
 113static int TestJobs(time_t t1, time_t t2);
 114static void RunJobs(void);
 115static int CheckJobs(void);
 116static void RunJob(const char *user, CronLine *line);
 117#if ENABLE_FEATURE_CROND_CALL_SENDMAIL
 118static void EndJob(const char *user, CronLine *line);
 119#else
 120#define EndJob(user, line)  ((line)->cl_Pid = 0)
 121#endif
 122static void DeleteFile(const char *userName);
 123
 124
 125#define LVL5  "\x05"
 126#define LVL7  "\x07"
 127#define LVL8  "\x08"
 128#define LVL9  "\x09"
 129#define WARN9 "\x49"
 130#define DIE9  "\xc9"
 131/* level >= 20 is "error" */
 132#define ERR20 "\x14"
 133
 134static void crondlog(const char *ctl, ...)
 135{
 136        va_list va;
 137        int level = (ctl[0] & 0x1f);
 138
 139        va_start(va, ctl);
 140        if (level >= (int)LogLevel) {
 141                /* Debug mode: all to (non-redirected) stderr, */
 142                /* Syslog mode: all to syslog (logmode = LOGMODE_SYSLOG), */
 143                if (!DebugOpt && LogFile) {
 144                        /* Otherwise (log to file): we reopen log file at every write: */
 145                        int logfd = open3_or_warn(LogFile, O_WRONLY | O_CREAT | O_APPEND, 0666);
 146                        if (logfd >= 0)
 147                                xmove_fd(logfd, STDERR_FILENO);
 148                }
 149// TODO: ERR -> error, WARN -> warning, LVL -> info
 150                bb_verror_msg(ctl + 1, va, /* strerr: */ NULL);
 151        }
 152        va_end(va);
 153        if (ctl[0] & 0x80)
 154                exit(20);
 155}
 156
 157int crond_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 158int crond_main(int argc UNUSED_PARAM, char **argv)
 159{
 160        unsigned opt;
 161
 162        INIT_G();
 163
 164        /* "-b after -f is ignored", and so on for every pair a-b */
 165        opt_complementary = "f-b:b-f:S-L:L-S" USE_FEATURE_CROND_D(":d-l")
 166                        ":l+:d+"; /* -l and -d have numeric param */
 167        opt = getopt32(argv, "l:L:fbSc:" USE_FEATURE_CROND_D("d:"),
 168                        &LogLevel, &LogFile, &CDir
 169                        USE_FEATURE_CROND_D(,&LogLevel));
 170        /* both -d N and -l N set the same variable: LogLevel */
 171
 172        if (!(opt & OPT_f)) {
 173                /* close stdin, stdout, stderr.
 174                 * close unused descriptors - don't need them. */
 175                bb_daemonize_or_rexec(DAEMON_CLOSE_EXTRA_FDS, argv);
 176        }
 177
 178        if (!DebugOpt && LogFile == NULL) {
 179                /* logging to syslog */
 180                openlog(applet_name, LOG_CONS | LOG_PID, LOG_CRON);
 181                logmode = LOGMODE_SYSLOG;
 182        }
 183
 184        xchdir(CDir);
 185        //signal(SIGHUP, SIG_IGN); /* ? original crond dies on HUP... */
 186        xsetenv("SHELL", DEFAULT_SHELL); /* once, for all future children */
 187        crondlog(LVL9 "crond (busybox "BB_VER") started, log level %d", LogLevel);
 188        SynchronizeDir();
 189
 190        /* main loop - synchronize to 1 second after the minute, minimum sleep
 191         * of 1 second. */
 192        {
 193                time_t t1 = time(NULL);
 194                time_t t2;
 195                long dt;
 196                int rescan = 60;
 197                int sleep_time = 60;
 198
 199                write_pidfile("/var/run/crond.pid");
 200                for (;;) {
 201                        sleep((sleep_time + 1) - (time(NULL) % sleep_time));
 202
 203                        t2 = time(NULL);
 204                        dt = (long)t2 - (long)t1;
 205
 206                        /*
 207                         * The file 'cron.update' is checked to determine new cron
 208                         * jobs.  The directory is rescanned once an hour to deal
 209                         * with any screwups.
 210                         *
 211                         * check for disparity.  Disparities over an hour either way
 212                         * result in resynchronization.  A reverse-indexed disparity
 213                         * less then an hour causes us to effectively sleep until we
 214                         * match the original time (i.e. no re-execution of jobs that
 215                         * have just been run).  A forward-indexed disparity less then
 216                         * an hour causes intermediate jobs to be run, but only once
 217                         * in the worst case.
 218                         *
 219                         * when running jobs, the inequality used is greater but not
 220                         * equal to t1, and less then or equal to t2.
 221                         */
 222                        if (--rescan == 0) {
 223                                rescan = 60;
 224                                SynchronizeDir();
 225                        }
 226                        CheckUpdates();
 227                        if (DebugOpt)
 228                                crondlog(LVL5 "wakeup dt=%ld", dt);
 229                        if (dt < -60 * 60 || dt > 60 * 60) {
 230                                crondlog(WARN9 "time disparity of %d minutes detected", dt / 60);
 231                        } else if (dt > 0) {
 232                                TestJobs(t1, t2);
 233                                RunJobs();
 234                                sleep(5);
 235                                if (CheckJobs() > 0) {
 236                                        sleep_time = 10;
 237                                } else {
 238                                        sleep_time = 60;
 239                                }
 240                        }
 241                        t1 = t2;
 242                }
 243        }
 244        return 0; /* not reached */
 245}
 246
 247#if SETENV_LEAKS
 248/* We set environment *before* vfork (because we want to use vfork),
 249 * so we cannot use setenv() - repeated calls to setenv() may leak memory!
 250 * Using putenv(), and freeing memory after unsetenv() won't leak */
 251static void safe_setenv(char **pvar_val, const char *var, const char *val)
 252{
 253        char *var_val = *pvar_val;
 254
 255        if (var_val) {
 256                bb_unsetenv(var_val);
 257                free(var_val);
 258        }
 259        *pvar_val = xasprintf("%s=%s", var, val);
 260        putenv(*pvar_val);
 261}
 262#endif
 263
 264static void SetEnv(struct passwd *pas)
 265{
 266#if SETENV_LEAKS
 267        safe_setenv(&env_var_user, "USER", pas->pw_name);
 268        safe_setenv(&env_var_home, "HOME", pas->pw_dir);
 269        /* if we want to set user's shell instead: */
 270        /*safe_setenv(env_var_user, "SHELL", pas->pw_shell);*/
 271#else
 272        xsetenv("USER", pas->pw_name);
 273        xsetenv("HOME", pas->pw_dir);
 274#endif
 275        /* currently, we use constant one: */
 276        /*setenv("SHELL", DEFAULT_SHELL, 1); - done earlier */
 277}
 278
 279static void ChangeUser(struct passwd *pas)
 280{
 281        /* careful: we're after vfork! */
 282        change_identity(pas); /* - initgroups, setgid, setuid */
 283        if (chdir(pas->pw_dir) < 0) {
 284                crondlog(LVL9 "can't chdir(%s)", pas->pw_dir);
 285                if (chdir(TMPDIR) < 0) {
 286                        crondlog(DIE9 "can't chdir(%s)", TMPDIR); /* exits */
 287                }
 288        }
 289}
 290
 291static const char DowAry[] ALIGN1 =
 292        "sun""mon""tue""wed""thu""fri""sat"
 293        /* "Sun""Mon""Tue""Wed""Thu""Fri""Sat" */
 294;
 295
 296static const char MonAry[] ALIGN1 =
 297        "jan""feb""mar""apr""may""jun""jul""aug""sep""oct""nov""dec"
 298        /* "Jan""Feb""Mar""Apr""May""Jun""Jul""Aug""Sep""Oct""Nov""Dec" */
 299;
 300
 301static void ParseField(char *user, char *ary, int modvalue, int off,
 302                                const char *names, char *ptr)
 303/* 'names' is a pointer to a set of 3-char abbreviations */
 304{
 305        char *base = ptr;
 306        int n1 = -1;
 307        int n2 = -1;
 308
 309        // this can't happen due to config_read()
 310        /*if (base == NULL)
 311                return;*/
 312
 313        while (1) {
 314                int skip = 0;
 315
 316                /* Handle numeric digit or symbol or '*' */
 317                if (*ptr == '*') {
 318                        n1 = 0;         /* everything will be filled */
 319                        n2 = modvalue - 1;
 320                        skip = 1;
 321                        ++ptr;
 322                } else if (isdigit(*ptr)) {
 323                        if (n1 < 0) {
 324                                n1 = strtol(ptr, &ptr, 10) + off;
 325                        } else {
 326                                n2 = strtol(ptr, &ptr, 10) + off;
 327                        }
 328                        skip = 1;
 329                } else if (names) {
 330                        int i;
 331
 332                        for (i = 0; names[i]; i += 3) {
 333                                /* was using strncmp before... */
 334                                if (strncasecmp(ptr, &names[i], 3) == 0) {
 335                                        ptr += 3;
 336                                        if (n1 < 0) {
 337                                                n1 = i / 3;
 338                                        } else {
 339                                                n2 = i / 3;
 340                                        }
 341                                        skip = 1;
 342                                        break;
 343                                }
 344                        }
 345                }
 346
 347                /* handle optional range '-' */
 348                if (skip == 0) {
 349                        goto err;
 350                }
 351                if (*ptr == '-' && n2 < 0) {
 352                        ++ptr;
 353                        continue;
 354                }
 355
 356                /*
 357                 * collapse single-value ranges, handle skipmark, and fill
 358                 * in the character array appropriately.
 359                 */
 360                if (n2 < 0) {
 361                        n2 = n1;
 362                }
 363                if (*ptr == '/') {
 364                        skip = strtol(ptr + 1, &ptr, 10);
 365                }
 366
 367                /*
 368                 * fill array, using a failsafe is the easiest way to prevent
 369                 * an endless loop
 370                 */
 371                {
 372                        int s0 = 1;
 373                        int failsafe = 1024;
 374
 375                        --n1;
 376                        do {
 377                                n1 = (n1 + 1) % modvalue;
 378
 379                                if (--s0 == 0) {
 380                                        ary[n1 % modvalue] = 1;
 381                                        s0 = skip;
 382                                }
 383                                if (--failsafe == 0) {
 384                                        goto err;
 385                                }
 386                        } while (n1 != n2);
 387
 388                }
 389                if (*ptr != ',') {
 390                        break;
 391                }
 392                ++ptr;
 393                n1 = -1;
 394                n2 = -1;
 395        }
 396
 397        if (*ptr) {
 398 err:
 399                crondlog(WARN9 "user %s: parse error at %s", user, base);
 400                return;
 401        }
 402
 403        if (DebugOpt && (LogLevel <= 5)) { /* like LVL5 */
 404                /* can't use crondlog, it inserts '\n' */
 405                int i;
 406                for (i = 0; i < modvalue; ++i)
 407                        fprintf(stderr, "%d", (unsigned char)ary[i]);
 408                fputc('\n', stderr);
 409        }
 410}
 411
 412static void FixDayDow(CronLine *line)
 413{
 414        unsigned i;
 415        int weekUsed = 0;
 416        int daysUsed = 0;
 417
 418        for (i = 0; i < ARRAY_SIZE(line->cl_Dow); ++i) {
 419                if (line->cl_Dow[i] == 0) {
 420                        weekUsed = 1;
 421                        break;
 422                }
 423        }
 424        for (i = 0; i < ARRAY_SIZE(line->cl_Days); ++i) {
 425                if (line->cl_Days[i] == 0) {
 426                        daysUsed = 1;
 427                        break;
 428                }
 429        }
 430        if (weekUsed != daysUsed) {
 431                if (weekUsed)
 432                        memset(line->cl_Days, 0, sizeof(line->cl_Days));
 433                else /* daysUsed */
 434                        memset(line->cl_Dow, 0, sizeof(line->cl_Dow));
 435        }
 436}
 437
 438static void SynchronizeFile(const char *fileName)
 439{
 440        struct parser_t *parser;
 441        struct stat sbuf;
 442        int maxLines;
 443        char *tokens[6];
 444#if ENABLE_FEATURE_CROND_CALL_SENDMAIL
 445        char *mailTo = NULL;
 446#endif
 447
 448        if (!fileName)
 449                return;
 450
 451        DeleteFile(fileName);
 452        parser = config_open(fileName);
 453        if (!parser)
 454                return;
 455
 456        maxLines = (strcmp(fileName, "root") == 0) ? 65535 : MAXLINES;
 457
 458        if (fstat(fileno(parser->fp), &sbuf) == 0 && sbuf.st_uid == DaemonUid) {
 459                CronFile *file = xzalloc(sizeof(CronFile));
 460                CronLine **pline;
 461                int n;
 462
 463                file->cf_User = xstrdup(fileName);
 464                pline = &file->cf_LineBase;
 465
 466                while (1) {
 467                        CronLine *line;
 468
 469                        if (!--maxLines)
 470                                break;
 471                        n = config_read(parser, tokens, 6, 1, "# \t", PARSE_NORMAL | PARSE_KEEP_COPY);
 472                        if (!n)
 473                                break;
 474
 475                        if (DebugOpt)
 476                                crondlog(LVL5 "user:%s entry:%s", fileName, parser->data);
 477
 478                        /* check if line is setting MAILTO= */
 479                        if (0 == strncmp(tokens[0], "MAILTO=", 7)) {
 480#if ENABLE_FEATURE_CROND_CALL_SENDMAIL
 481                                free(mailTo);
 482                                mailTo = (tokens[0][7]) ? xstrdup(&tokens[0][7]) : NULL;
 483#endif /* otherwise just ignore such lines */
 484                                continue;
 485                        }
 486                        /* check if a minimum of tokens is specified */
 487                        if (n < 6)
 488                                continue;
 489                        *pline = line = xzalloc(sizeof(*line));
 490                        /* parse date ranges */
 491                        ParseField(file->cf_User, line->cl_Mins, 60, 0, NULL, tokens[0]);
 492                        ParseField(file->cf_User, line->cl_Hrs, 24, 0, NULL, tokens[1]);
 493                        ParseField(file->cf_User, line->cl_Days, 32, 0, NULL, tokens[2]);
 494                        ParseField(file->cf_User, line->cl_Mons, 12, -1, MonAry, tokens[3]);
 495                        ParseField(file->cf_User, line->cl_Dow, 7, 0, DowAry, tokens[4]);
 496                        /*
 497                         * fix days and dow - if one is not "*" and the other
 498                         * is "*", the other is set to 0, and vise-versa
 499                         */
 500                        FixDayDow(line);
 501#if ENABLE_FEATURE_CROND_CALL_SENDMAIL
 502                        /* copy mailto (can be NULL) */
 503                        line->cl_MailTo = xstrdup(mailTo);
 504#endif
 505                        /* copy command */
 506                        line->cl_Shell = xstrdup(tokens[5]);
 507                        if (DebugOpt) {
 508                                crondlog(LVL5 " command:%s", tokens[5]);
 509                        }
 510                        pline = &line->cl_Next;
 511//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]);
 512                }
 513                *pline = NULL;
 514
 515                file->cf_Next = FileBase;
 516                FileBase = file;
 517
 518                if (maxLines == 0) {
 519                        crondlog(WARN9 "user %s: too many lines", fileName);
 520                }
 521        }
 522        config_close(parser);
 523}
 524
 525static void CheckUpdates(void)
 526{
 527        FILE *fi;
 528        char buf[256];
 529
 530        fi = fopen_for_read(CRONUPDATE);
 531        if (fi != NULL) {
 532                unlink(CRONUPDATE);
 533                while (fgets(buf, sizeof(buf), fi) != NULL) {
 534                        /* use first word only */
 535                        SynchronizeFile(strtok(buf, " \t\r\n"));
 536                }
 537                fclose(fi);
 538        }
 539}
 540
 541static void SynchronizeDir(void)
 542{
 543        CronFile *file;
 544        /* Attempt to delete the database. */
 545 again:
 546        for (file = FileBase; file; file = file->cf_Next) {
 547                if (!file->cf_Deleted) {
 548                        DeleteFile(file->cf_User);
 549                        goto again;
 550                }
 551        }
 552
 553        /*
 554         * Remove cron update file
 555         *
 556         * Re-chdir, in case directory was renamed & deleted, or otherwise
 557         * screwed up.
 558         *
 559         * scan directory and add associated users
 560         */
 561        unlink(CRONUPDATE);
 562        if (chdir(CDir) < 0) {
 563                crondlog(DIE9 "can't chdir(%s)", CDir);
 564        }
 565        {
 566                DIR *dir = opendir(".");
 567                struct dirent *den;
 568
 569                if (!dir)
 570                        crondlog(DIE9 "can't chdir(%s)", "."); /* exits */
 571                while ((den = readdir(dir)) != NULL) {
 572                        if (strchr(den->d_name, '.') != NULL) {
 573                                continue;
 574                        }
 575                        if (getpwnam(den->d_name)) {
 576                                SynchronizeFile(den->d_name);
 577                        } else {
 578                                crondlog(LVL7 "ignoring %s", den->d_name);
 579                        }
 580                }
 581                closedir(dir);
 582        }
 583}
 584
 585/*
 586 *  DeleteFile() - delete user database
 587 *
 588 *  Note: multiple entries for same user may exist if we were unable to
 589 *  completely delete a database due to running processes.
 590 */
 591static void DeleteFile(const char *userName)
 592{
 593        CronFile **pfile = &FileBase;
 594        CronFile *file;
 595
 596        while ((file = *pfile) != NULL) {
 597                if (strcmp(userName, file->cf_User) == 0) {
 598                        CronLine **pline = &file->cf_LineBase;
 599                        CronLine *line;
 600
 601                        file->cf_Running = 0;
 602                        file->cf_Deleted = 1;
 603
 604                        while ((line = *pline) != NULL) {
 605                                if (line->cl_Pid > 0) {
 606                                        file->cf_Running = 1;
 607                                        pline = &line->cl_Next;
 608                                } else {
 609                                        *pline = line->cl_Next;
 610                                        free(line->cl_Shell);
 611                                        free(line);
 612                                }
 613                        }
 614                        if (file->cf_Running == 0) {
 615                                *pfile = file->cf_Next;
 616                                free(file->cf_User);
 617                                free(file);
 618                        } else {
 619                                pfile = &file->cf_Next;
 620                        }
 621                } else {
 622                        pfile = &file->cf_Next;
 623                }
 624        }
 625}
 626
 627/*
 628 * TestJobs()
 629 *
 630 * determine which jobs need to be run.  Under normal conditions, the
 631 * period is about a minute (one scan).  Worst case it will be one
 632 * hour (60 scans).
 633 */
 634static int TestJobs(time_t t1, time_t t2)
 635{
 636        int nJobs = 0;
 637        time_t t;
 638
 639        /* Find jobs > t1 and <= t2 */
 640
 641        for (t = t1 - t1 % 60; t <= t2; t += 60) {
 642                struct tm *tp;
 643                CronFile *file;
 644                CronLine *line;
 645
 646                if (t <= t1)
 647                        continue;
 648
 649                tp = localtime(&t);
 650                for (file = FileBase; file; file = file->cf_Next) {
 651                        if (DebugOpt)
 652                                crondlog(LVL5 "file %s:", file->cf_User);
 653                        if (file->cf_Deleted)
 654                                continue;
 655                        for (line = file->cf_LineBase; line; line = line->cl_Next) {
 656                                if (DebugOpt)
 657                                        crondlog(LVL5 " line %s", line->cl_Shell);
 658                                if (line->cl_Mins[tp->tm_min] && line->cl_Hrs[tp->tm_hour]
 659                                 && (line->cl_Days[tp->tm_mday] || line->cl_Dow[tp->tm_wday])
 660                                 && line->cl_Mons[tp->tm_mon]
 661                                ) {
 662                                        if (DebugOpt) {
 663                                                crondlog(LVL5 " job: %d %s",
 664                                                        (int)line->cl_Pid, line->cl_Shell);
 665                                        }
 666                                        if (line->cl_Pid > 0) {
 667                                                crondlog(LVL8 "user %s: process already running: %s",
 668                                                        file->cf_User, line->cl_Shell);
 669                                        } else if (line->cl_Pid == 0) {
 670                                                line->cl_Pid = -1;
 671                                                file->cf_Ready = 1;
 672                                                ++nJobs;
 673                                        }
 674                                }
 675                        }
 676                }
 677        }
 678        return nJobs;
 679}
 680
 681static void RunJobs(void)
 682{
 683        CronFile *file;
 684        CronLine *line;
 685
 686        for (file = FileBase; file; file = file->cf_Next) {
 687                if (!file->cf_Ready)
 688                        continue;
 689
 690                file->cf_Ready = 0;
 691                for (line = file->cf_LineBase; line; line = line->cl_Next) {
 692                        if (line->cl_Pid >= 0)
 693                                continue;
 694
 695                        RunJob(file->cf_User, line);
 696                        crondlog(LVL8 "USER %s pid %3d cmd %s",
 697                                file->cf_User, (int)line->cl_Pid, line->cl_Shell);
 698                        if (line->cl_Pid < 0) {
 699                                file->cf_Ready = 1;
 700                        } else if (line->cl_Pid > 0) {
 701                                file->cf_Running = 1;
 702                        }
 703                }
 704        }
 705}
 706
 707/*
 708 * CheckJobs() - check for job completion
 709 *
 710 * Check for job completion, return number of jobs still running after
 711 * all done.
 712 */
 713static int CheckJobs(void)
 714{
 715        CronFile *file;
 716        CronLine *line;
 717        int nStillRunning = 0;
 718
 719        for (file = FileBase; file; file = file->cf_Next) {
 720                if (file->cf_Running) {
 721                        file->cf_Running = 0;
 722
 723                        for (line = file->cf_LineBase; line; line = line->cl_Next) {
 724                                int status, r;
 725                                if (line->cl_Pid <= 0)
 726                                        continue;
 727
 728                                r = waitpid(line->cl_Pid, &status, WNOHANG);
 729                                if (r < 0 || r == line->cl_Pid) {
 730                                        EndJob(file->cf_User, line);
 731                                        if (line->cl_Pid) {
 732                                                file->cf_Running = 1;
 733                                        }
 734                                } else if (r == 0) {
 735                                        file->cf_Running = 1;
 736                                }
 737                        }
 738                }
 739                nStillRunning += file->cf_Running;
 740        }
 741        return nStillRunning;
 742}
 743
 744#if ENABLE_FEATURE_CROND_CALL_SENDMAIL
 745
 746// TODO: sendmail should be _run-time_ option, not compile-time!
 747
 748static void
 749ForkJob(const char *user, CronLine *line, int mailFd,
 750                const char *prog, const char *cmd, const char *arg,
 751                const char *mail_filename)
 752{
 753        struct passwd *pas;
 754        pid_t pid;
 755
 756        /* prepare things before vfork */
 757        pas = getpwnam(user);
 758        if (!pas) {
 759                crondlog(LVL9 "can't get uid for %s", user);
 760                goto err;
 761        }
 762        SetEnv(pas);
 763
 764        pid = vfork();
 765        if (pid == 0) {
 766                /* CHILD */
 767                /* change running state to the user in question */
 768                ChangeUser(pas);
 769                if (DebugOpt) {
 770                        crondlog(LVL5 "child running %s", prog);
 771                }
 772                if (mailFd >= 0) {
 773                        xmove_fd(mailFd, mail_filename ? 1 : 0);
 774                        dup2(1, 2);
 775                }
 776                /* crond 3.0pl1-100 puts tasks in separate process groups */
 777                bb_setpgrp();
 778                execlp(prog, prog, cmd, arg, (char *) NULL);
 779                crondlog(ERR20 "can't exec, user %s cmd %s %s %s", user, prog, cmd, arg);
 780                if (mail_filename) {
 781                        fdprintf(1, "Exec failed: %s -c %s\n", prog, arg);
 782                }
 783                _exit(EXIT_SUCCESS);
 784        }
 785
 786        line->cl_Pid = pid;
 787        if (pid < 0) {
 788                /* FORK FAILED */
 789                crondlog(ERR20 "can't vfork");
 790 err:
 791                line->cl_Pid = 0;
 792                if (mail_filename) {
 793                        unlink(mail_filename);
 794                }
 795        } else if (mail_filename) {
 796                /* PARENT, FORK SUCCESS
 797                 * rename mail-file based on pid of process
 798                 */
 799                char mailFile2[128];
 800
 801                snprintf(mailFile2, sizeof(mailFile2), "%s/cron.%s.%d", TMPDIR, user, pid);
 802                rename(mail_filename, mailFile2); // TODO: xrename?
 803        }
 804
 805        /*
 806         * Close the mail file descriptor.. we can't just leave it open in
 807         * a structure, closing it later, because we might run out of descriptors
 808         */
 809        if (mailFd >= 0) {
 810                close(mailFd);
 811        }
 812}
 813
 814static void RunJob(const char *user, CronLine *line)
 815{
 816        char mailFile[128];
 817        int mailFd = -1;
 818
 819        line->cl_Pid = 0;
 820        line->cl_MailFlag = 0;
 821
 822        if (line->cl_MailTo) {
 823                /* open mail file - owner root so nobody can screw with it. */
 824                snprintf(mailFile, sizeof(mailFile), "%s/cron.%s.%d", TMPDIR, user, getpid());
 825                mailFd = open(mailFile, O_CREAT | O_TRUNC | O_WRONLY | O_EXCL | O_APPEND, 0600);
 826
 827                if (mailFd >= 0) {
 828                        line->cl_MailFlag = 1;
 829                        fdprintf(mailFd, "To: %s\nSubject: cron: %s\n\n", line->cl_MailTo,
 830                                line->cl_Shell);
 831                        line->cl_MailPos = lseek(mailFd, 0, SEEK_CUR);
 832                } else {
 833                        crondlog(ERR20 "cannot create mail file %s for user %s, "
 834                                        "discarding output", mailFile, user);
 835                }
 836        }
 837
 838        ForkJob(user, line, mailFd, DEFAULT_SHELL, "-c", line->cl_Shell, mailFile);
 839}
 840
 841/*
 842 * EndJob - called when job terminates and when mail terminates
 843 */
 844static void EndJob(const char *user, CronLine *line)
 845{
 846        int mailFd;
 847        char mailFile[128];
 848        struct stat sbuf;
 849
 850        /* No job */
 851        if (line->cl_Pid <= 0) {
 852                line->cl_Pid = 0;
 853                return;
 854        }
 855
 856        /*
 857         * End of job and no mail file
 858         * End of sendmail job
 859         */
 860        snprintf(mailFile, sizeof(mailFile), "%s/cron.%s.%d", TMPDIR, user, line->cl_Pid);
 861        line->cl_Pid = 0;
 862
 863        if (line->cl_MailFlag == 0) {
 864                return;
 865        }
 866        line->cl_MailFlag = 0;
 867
 868        /*
 869         * End of primary job - check for mail file.  If size has increased and
 870         * the file is still valid, we sendmail it.
 871         */
 872        mailFd = open(mailFile, O_RDONLY);
 873        unlink(mailFile);
 874        if (mailFd < 0) {
 875                return;
 876        }
 877
 878        if (fstat(mailFd, &sbuf) < 0 || sbuf.st_uid != DaemonUid
 879         || sbuf.st_nlink != 0 || sbuf.st_size == line->cl_MailPos
 880         || !S_ISREG(sbuf.st_mode)
 881        ) {
 882                close(mailFd);
 883                return;
 884        }
 885        if (line->cl_MailTo)
 886                ForkJob(user, line, mailFd, SENDMAIL, SENDMAIL_ARGS, NULL);
 887}
 888
 889#else /* crond without sendmail */
 890
 891static void RunJob(const char *user, CronLine *line)
 892{
 893        struct passwd *pas;
 894        pid_t pid;
 895
 896        /* prepare things before vfork */
 897        pas = getpwnam(user);
 898        if (!pas) {
 899                crondlog(LVL9 "can't get uid for %s", user);
 900                goto err;
 901        }
 902        SetEnv(pas);
 903
 904        /* fork as the user in question and run program */
 905        pid = vfork();
 906        if (pid == 0) {
 907                /* CHILD */
 908                /* change running state to the user in question */
 909                ChangeUser(pas);
 910                if (DebugOpt) {
 911                        crondlog(LVL5 "child running %s", DEFAULT_SHELL);
 912                }
 913                /* crond 3.0pl1-100 puts tasks in separate process groups */
 914                bb_setpgrp();
 915                execl(DEFAULT_SHELL, DEFAULT_SHELL, "-c", line->cl_Shell, (char *) NULL);
 916                crondlog(ERR20 "can't exec, user %s cmd %s %s %s", user,
 917                                 DEFAULT_SHELL, "-c", line->cl_Shell);
 918                _exit(EXIT_SUCCESS);
 919        }
 920        if (pid < 0) {
 921                /* FORK FAILED */
 922                crondlog(ERR20 "can't vfork");
 923 err:
 924                pid = 0;
 925        }
 926        line->cl_Pid = pid;
 927}
 928
 929#endif /* ENABLE_FEATURE_CROND_CALL_SENDMAIL */
 930