5 kx
5 kx /*
5 kx * MAIN.C
5 kx *
5 kx * crond [-s dir] [-c dir] [-t dir] [-m user@host] [-M mailer] [-S|-L [file]] [-l level] [-b|-f|-d]
5 kx * run as root, but NOT setuid root
5 kx *
5 kx * Copyright 1994 Matthew Dillon (dillon@apollo.backplane.com)
5 kx * Copyright 2009-2011 James Pryor <profjim@jimpryor.net>
5 kx * May be distributed under the GNU General Public License
5 kx */
5 kx
5 kx #include "defs.h"
5 kx
5 kx Prototype short DebugOpt;
5 kx Prototype short LogLevel;
5 kx Prototype short ForegroundOpt;
5 kx Prototype short SyslogOpt;
5 kx Prototype const char *CDir;
5 kx Prototype const char *SCDir;
5 kx Prototype const char *TSDir;
5 kx Prototype const char *LogFile;
5 kx Prototype const char *LogHeader;
5 kx Prototype uid_t DaemonUid;
5 kx Prototype pid_t DaemonPid;
5 kx Prototype const char *SendMail;
5 kx Prototype const char *Mailto;
5 kx Prototype char *TempDir;
5 kx Prototype char *TempFileFmt;
5 kx
5 kx short DebugOpt = 0;
5 kx short LogLevel = LOG_LEVEL;
5 kx short ForegroundOpt = 0;
5 kx short SyslogOpt = 1;
5 kx const char *CDir = CRONTABS;
5 kx const char *SCDir = SCRONTABS;
5 kx const char *TSDir = CRONSTAMPS;
5 kx const char *LogFile = NULL; /* opened with mode 0600 */
5 kx const char *LogHeader = LOGHEADER;
5 kx const char *SendMail = NULL;
5 kx const char *Mailto = NULL;
5 kx char *TempDir;
5 kx char *TempFileFmt;
5 kx
5 kx uid_t DaemonUid;
5 kx pid_t DaemonPid;
5 kx
5 kx int
5 kx main(int ac, char **av)
5 kx {
5 kx const char *LevelAry[] = {
5 kx "emerg",
5 kx "alert",
5 kx "crit",
5 kx "err",
5 kx "warning",
5 kx "notice",
5 kx "info",
5 kx "debug",
5 kx "panic",
5 kx "error",
5 kx "warn",
5 kx NULL
5 kx };
5 kx int i;
5 kx
5 kx /*
5 kx * parse options
5 kx */
5 kx
5 kx DaemonUid = getuid();
5 kx
5 kx opterr = 0;
5 kx
5 kx while ((i = getopt(ac,av,"dl:L:fbSc:s:m:M:t:")) != -1) {
5 kx switch (i) {
5 kx case 'l':
5 kx {
5 kx char *ptr;
5 kx int j;
5 kx ptr = optarg;
5 kx for (j = 0; LevelAry[j]; ++j) {
5 kx if (strncmp(ptr, LevelAry[j], strlen(LevelAry[j])) == 0) {
5 kx break;
5 kx }
5 kx }
5 kx switch(j) {
5 kx case 0:
5 kx case 8:
5 kx /* #define LOG_EMERG 0 [* system is unusable *] */
5 kx LogLevel = LOG_EMERG;
5 kx break;
5 kx case 1:
5 kx /* #define LOG_ALERT 1 [* action must be taken immediately *] */
5 kx LogLevel = LOG_ALERT;
5 kx break;
5 kx case 2:
5 kx /* #define LOG_CRIT 2 [* critical conditions *] */
5 kx LogLevel = LOG_CRIT;
5 kx break;
5 kx case 3:
5 kx case 9:
5 kx /* #define LOG_ERR 3 [* error conditions *] */
5 kx LogLevel = LOG_ERR;
5 kx break;
5 kx case 4:
5 kx case 10:
5 kx /* #define LOG_WARNING 4 [* warning conditions *] */
5 kx LogLevel = LOG_WARNING;
5 kx break;
5 kx case 5:
5 kx /* #define LOG_NOTICE 5 [* normal but significant condition *] */
5 kx LogLevel = LOG_NOTICE;
5 kx break;
5 kx case 6:
5 kx /* #define LOG_INFO 6 [* informational *] */
5 kx LogLevel = LOG_INFO;
5 kx break;
5 kx case 7:
5 kx /* #define LOG_DEBUG 7 [* debug-level messages *] */
5 kx LogLevel = LOG_DEBUG;
5 kx break;
5 kx default:
5 kx LogLevel = atoi(optarg);
5 kx }
5 kx }
5 kx break;
5 kx case 'd':
5 kx DebugOpt = 1;
5 kx LogLevel = LOG_DEBUG;
5 kx /* fall through to include f too */
5 kx case 'f':
5 kx ForegroundOpt = 1;
5 kx break;
5 kx case 'b':
5 kx ForegroundOpt = 0;
5 kx break;
5 kx case 'S': /* log through syslog */
5 kx SyslogOpt = 1;
5 kx break;
5 kx case 'L': /* use internal log formatter */
5 kx SyslogOpt = 0;
5 kx LogFile = optarg;
5 kx /* if LC_TIME is defined, we use it for logging to file instead of compiled-in TIMESTAMP_FMT */
5 kx if (getenv("LC_TIME") != NULL) {
5 kx LogHeader = LOCALE_LOGHEADER;
5 kx }
5 kx break;
5 kx case 'c':
5 kx if (*optarg != 0) CDir = optarg;
5 kx break;
5 kx case 's':
5 kx if (*optarg != 0) SCDir = optarg;
5 kx break;
5 kx case 't':
5 kx if (*optarg != 0) TSDir = optarg;
5 kx break;
5 kx case 'M':
5 kx if (*optarg != 0) SendMail = optarg;
5 kx break;
5 kx case 'm':
5 kx if (*optarg != 0) Mailto = optarg;
5 kx break;
5 kx default:
5 kx /*
5 kx * check for parse error
5 kx */
5 kx printf("dillon's cron daemon " VERSION "\n");
5 kx printf("crond [-s dir] [-c dir] [-t dir] [-m user@host] [-M mailer] [-S|-L [file]] [-l level] [-b|-f|-d]\n");
5 kx printf("-s directory of system crontabs (defaults to %s)\n", SCRONTABS);
5 kx printf("-c directory of per-user crontabs (defaults to %s)\n", CRONTABS);
5 kx printf("-t directory of timestamps (defaults to %s)\n", CRONSTAMPS);
5 kx printf("-m user@host where should cron output be directed? (defaults to local user)\n");
5 kx printf("-M mailer (defaults to %s)\n", SENDMAIL);
5 kx printf("-S log to syslog using identity '%s' (default)\n", LOG_IDENT);
5 kx printf("-L file log to specified file instead of syslog\n");
5 kx printf("-l loglevel log events <= this level (defaults to %s (level %d))\n", LevelAry[LOG_LEVEL], LOG_LEVEL);
5 kx printf("-b run in background (default)\n");
5 kx printf("-f run in foreground\n");
5 kx printf("-d run in debugging mode\n");
5 kx exit(2);
5 kx }
5 kx }
5 kx
5 kx /*
5 kx * close stdin and stdout.
5 kx * close unused descriptors - don't need.
5 kx * optional detach from controlling terminal
5 kx */
5 kx
5 kx fclose(stdin);
5 kx fclose(stdout);
5 kx
5 kx i = open("/dev/null", O_RDWR);
5 kx if (i < 0) {
5 kx perror("open: /dev/null");
5 kx exit(1);
5 kx }
5 kx dup2(i, 0);
5 kx dup2(i, 1);
5 kx
5 kx /* create tempdir with permissions 0755 for cron output */
5 kx TempDir = strdup(TMPDIR "/cron.XXXXXX");
5 kx if (mkdtemp(TempDir) == NULL) {
5 kx perror("mkdtemp");
5 kx exit(1);
5 kx }
5 kx if (chmod(TempDir, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH)) {
5 kx perror("chmod");
5 kx exit(1);
5 kx }
5 kx if (!(TempFileFmt = concat(TempDir, "/cron.%s.%d", NULL))) {
5 kx errno = ENOMEM;
5 kx perror("main");
5 kx exit(1);
5 kx }
5 kx
5 kx if (ForegroundOpt == 0) {
5 kx
5 kx int fd;
5 kx int pid;
5 kx
5 kx if ((pid = fork()) < 0) {
5 kx /* fork failed */
5 kx perror("fork");
5 kx exit(1);
5 kx } else if (pid > 0) {
5 kx /* parent */
5 kx exit(0);
5 kx }
5 kx /* child continues */
5 kx
5 kx /* become session leader, detach from terminal */
5 kx
5 kx if (setsid() < 0)
5 kx perror("setsid");
5 kx if ((fd = open("/dev/tty", O_RDWR)) >= 0) {
5 kx ioctl(fd, TIOCNOTTY, 0);
5 kx close(fd);
5 kx }
5 kx
5 kx /* setup logging for backgrounded daemons */
5 kx
5 kx if (SyslogOpt) {
5 kx /* start SIGHUP and SIGCHLD handling while stderr still open */
5 kx initsignals();
5 kx /* 2> /dev/null */
5 kx fclose(stderr);
5 kx dup2(1, 2);
5 kx
5 kx /* open syslog */
5 kx openlog(LOG_IDENT, LOG_CONS|LOG_PID, LOG_CRON);
5 kx
5 kx } else {
5 kx /* open logfile */
5 kx if ((fd = open(LogFile, O_WRONLY|O_CREAT|O_APPEND, 0600)) >= 0) {
5 kx /* start SIGHUP ignoring, SIGCHLD handling while stderr still open */
5 kx initsignals();
5 kx /* 2> LogFile */
5 kx fclose(stderr);
5 kx dup2(fd, 2);
5 kx } else {
5 kx int n = errno;
5 kx fdprintf(2, "failed to open logfile '%s', reason: %s", LogFile, strerror(n));
5 kx exit(n);
5 kx }
5 kx }
5 kx } else {
5 kx /* daemon in foreground */
5 kx
5 kx /* stay in existing session, but start a new process group */
5 kx if (setpgid(0,0)) {
5 kx perror("setpgid");
5 kx exit(1);
5 kx }
5 kx
5 kx /* stderr stays open, start SIGHUP ignoring, SIGCHLD handling */
5 kx initsignals();
5 kx }
5 kx
5 kx /* close all other fds, including the ones we opened as /dev/null and LogFile */
5 kx for (i = 3; i < MAXOPEN; ++i) {
5 kx close(i);
5 kx }
5 kx
5 kx
5 kx /*
5 kx * main loop - synchronize to 1 second after the minute, minimum sleep
5 kx * of 1 second.
5 kx */
5 kx
5 kx printlogf(LOG_NOTICE,"%s " VERSION " dillon's cron daemon, started with loglevel %s\n", av[0], LevelAry[LogLevel]);
5 kx SynchronizeDir(CDir, NULL, 1);
5 kx SynchronizeDir(SCDir, "root", 1);
5 kx ReadTimestamps(NULL);
5 kx TestStartupJobs(); /* @startup jobs only run when crond is started, not when their crontab is loaded */
5 kx
5 kx {
5 kx time_t t1 = time(NULL);
5 kx time_t t2;
5 kx long dt;
5 kx short rescan = 60;
5 kx short stime = 60;
5 kx
5 kx for (;;) {
5 kx sleep((stime + 1) - (short)(time(NULL) % stime));
5 kx
5 kx t2 = time(NULL);
5 kx dt = t2 - t1;
5 kx
5 kx /*
5 kx * The file 'cron.update' is checked to determine new cron
5 kx * jobs. The directory is rescanned once an hour to deal
5 kx * with any screwups.
5 kx *
5 kx * check for disparity. Disparities over an hour either way
5 kx * result in resynchronization. A reverse-indexed disparity
5 kx * less then an hour causes us to effectively sleep until we
5 kx * match the original time (i.e. no re-execution of jobs that
5 kx * have just been run). A forward-indexed disparity less then
5 kx * an hour causes intermediate jobs to be run, but only once
5 kx * in the worst case.
5 kx *
5 kx * when running jobs, the inequality used is greater but not
5 kx * equal to t1, and less then or equal to t2.
5 kx */
5 kx
5 kx if (--rescan == 0) {
5 kx /*
5 kx * If we resynchronize while jobs are running, we'll clobber
5 kx * the job pids, so we won't know what's already running.
5 kx */
5 kx if (CheckJobs() > 0) {
5 kx rescan = 1;
5 kx } else {
5 kx rescan = 60;
5 kx SynchronizeDir(CDir, NULL, 0);
5 kx SynchronizeDir(SCDir, "root", 0);
5 kx ReadTimestamps(NULL);
5 kx }
5 kx }
5 kx if (rescan < 60) {
5 kx CheckUpdates(CDir, NULL, t1, t2);
5 kx CheckUpdates(SCDir, "root", t1, t2);
5 kx }
5 kx if (DebugOpt)
5 kx printlogf(LOG_DEBUG, "Wakeup dt=%d\n", dt);
5 kx if (dt < -60*60 || dt > 60*60) {
5 kx t1 = t2;
5 kx printlogf(LOG_NOTICE,"time disparity of %d minutes detected\n", dt / 60);
5 kx } else if (dt > 0) {
5 kx TestJobs(t1, t2);
5 kx RunJobs();
5 kx sleep(5);
5 kx if (CheckJobs() > 0)
5 kx stime = 10;
5 kx else
5 kx stime = 60;
5 kx t1 = t2;
5 kx }
5 kx }
5 kx }
5 kx /* not reached */
5 kx }
5 kx