#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <bitstring.h>
#include <err.h>
#include <errno.h>
#include <grp.h>
#include <poll.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <time.h>
#include <unistd.h>
#include "config.h"
#include "pathnames.h"
#include "macros.h"
#include "structs.h"
#include "funcs.h"
#include "globals.h"
enum timejump { negative, small, medium, large };
static void usage(void),
run_reboot_jobs(cron_db *),
find_jobs(time_t, cron_db *, int, int),
set_time(int),
cron_sleep(time_t, sigset_t *),
sigchld_handler(int),
sigchld_reaper(void),
parse_args(int c, char *v[]);
static int open_socket(void);
static volatile sig_atomic_t got_sigchld;
static time_t timeRunning, virtualTime, clockTime;
static long GMToff;
static cron_db *database;
static at_db *at_database;
static double batch_maxload = BATCH_MAXLOAD;
static int NoFork;
static time_t StartTime;
gid_t cron_gid;
int cronSock;
static void
usage(void)
{
fprintf(stderr, "usage: %s [-n] [-l load_avg]\n", __progname);
exit(EXIT_FAILURE);
}
int
main(int argc, char *argv[])
{
struct sigaction sact;
sigset_t blocked, omask;
struct group *grp;
setvbuf(stdout, NULL, _IOLBF, 0);
setvbuf(stderr, NULL, _IOLBF, 0);
parse_args(argc, argv);
bzero((char *)&sact, sizeof sact);
sigemptyset(&sact.sa_mask);
sact.sa_flags = SA_RESTART;
sact.sa_handler = sigchld_handler;
(void) sigaction(SIGCHLD, &sact, NULL);
sact.sa_handler = SIG_IGN;
(void) sigaction(SIGHUP, &sact, NULL);
(void) sigaction(SIGPIPE, &sact, NULL);
openlog(__progname, LOG_PID, LOG_CRON);
if (pledge("stdio rpath wpath cpath fattr getpw unix id dns proc exec",
NULL) == -1) {
warn("pledge");
syslog(LOG_ERR, "(CRON) PLEDGE (%m)");
exit(EXIT_FAILURE);
}
if ((grp = getgrnam(CRON_GROUP)) == NULL) {
warnx("can't find cron group %s", CRON_GROUP);
syslog(LOG_ERR, "(CRON) DEATH (can't find cron group)");
exit(EXIT_FAILURE);
}
cron_gid = grp->gr_gid;
cronSock = open_socket();
if (putenv("PATH="_PATH_DEFPATH) < 0) {
warn("putenv");
syslog(LOG_ERR, "(CRON) DEATH (%m)");
exit(EXIT_FAILURE);
}
if (NoFork == 0) {
if (daemon(0, 0) == -1) {
syslog(LOG_ERR, "(CRON) DEATH (%m)");
exit(EXIT_FAILURE);
}
syslog(LOG_INFO, "(CRON) STARTUP (%s)", CRON_VERSION);
}
load_database(&database);
scan_atjobs(&at_database, NULL);
set_time(TRUE);
run_reboot_jobs(database);
timeRunning = virtualTime = clockTime;
sigemptyset(&blocked);
sigaddset(&blocked, SIGCHLD);
sigaddset(&blocked, SIGHUP);
sigprocmask(SIG_BLOCK, &blocked, &omask);
while (TRUE) {
int timeDiff;
enum timejump wakeupKind;
do {
cron_sleep(timeRunning + 1, &omask);
set_time(FALSE);
} while (clockTime == timeRunning);
timeRunning = clockTime;
timeDiff = timeRunning - virtualTime;
if (timeDiff == 1) {
virtualTime = timeRunning;
find_jobs(virtualTime, database, TRUE, TRUE);
} else {
if (timeDiff > (3*MINUTE_COUNT) ||
timeDiff < -(3*MINUTE_COUNT))
wakeupKind = large;
else if (timeDiff > 5)
wakeupKind = medium;
else if (timeDiff > 0)
wakeupKind = small;
else
wakeupKind = negative;
switch (wakeupKind) {
case small:
do {
if (job_runqueue())
sleep(10);
virtualTime++;
find_jobs(virtualTime, database,
TRUE, TRUE);
} while (virtualTime < timeRunning);
break;
case medium:
find_jobs(timeRunning, database, TRUE, FALSE);
do {
if (job_runqueue())
sleep(10);
virtualTime++;
find_jobs(virtualTime, database,
FALSE, TRUE);
set_time(FALSE);
} while (virtualTime< timeRunning &&
clockTime == timeRunning);
break;
case negative:
find_jobs(timeRunning, database, TRUE, FALSE);
break;
default:
virtualTime = timeRunning;
find_jobs(timeRunning, database, TRUE, TRUE);
}
}
job_runqueue();
atrun(at_database, batch_maxload,
timeRunning * SECONDS_PER_MINUTE - GMToff);
load_database(&database);
scan_atjobs(&at_database, NULL);
}
}
static void
run_reboot_jobs(cron_db *db)
{
user *u;
entry *e;
TAILQ_FOREACH(u, &db->users, entries) {
SLIST_FOREACH(e, &u->crontab, entries) {
if (e->flags & WHEN_REBOOT)
job_add(e, u);
}
}
(void) job_runqueue();
}
static void
find_jobs(time_t vtime, cron_db *db, int doWild, int doNonWild)
{
time_t virtualSecond = vtime * SECONDS_PER_MINUTE;
struct tm *tm = gmtime(&virtualSecond);
int minute, hour, dom, month, dow;
user *u;
entry *e;
minute = tm->tm_min -FIRST_MINUTE;
hour = tm->tm_hour -FIRST_HOUR;
dom = tm->tm_mday -FIRST_DOM;
month = tm->tm_mon +1 -FIRST_MONTH;
dow = tm->tm_wday -FIRST_DOW;
TAILQ_FOREACH(u, &db->users, entries) {
SLIST_FOREACH(e, &u->crontab, entries) {
if (bit_test(e->minute, minute) &&
bit_test(e->hour, hour) &&
bit_test(e->month, month) &&
( ((e->flags & DOM_STAR) || (e->flags & DOW_STAR))
? (bit_test(e->dow,dow) && bit_test(e->dom,dom))
: (bit_test(e->dow,dow) || bit_test(e->dom,dom))
)
) {
if ((doNonWild &&
!(e->flags & (MIN_STAR|HR_STAR))) ||
(doWild && (e->flags & (MIN_STAR|HR_STAR))))
job_add(e, u);
}
}
}
}
static void
set_time(int initialize)
{
struct tm tm;
static int isdst;
StartTime = time(NULL);
tm = *localtime(&StartTime);
if (initialize || tm.tm_isdst != isdst) {
isdst = tm.tm_isdst;
GMToff = get_gmtoff(&StartTime, &tm);
}
clockTime = (StartTime + GMToff) / (time_t)SECONDS_PER_MINUTE;
}
static void
cron_sleep(time_t target, sigset_t *mask)
{
int fd, nfds;
unsigned char poke;
struct timespec t1, t2, timeout;
struct sockaddr_un s_un;
socklen_t sunlen;
static struct pollfd pfd[1];
clock_gettime(CLOCK_REALTIME, &t1);
t1.tv_sec += GMToff;
timeout.tv_sec = (target * SECONDS_PER_MINUTE - t1.tv_sec) + 1;
if (timeout.tv_sec < 0)
timeout.tv_sec = 0;
timeout.tv_nsec = 0;
pfd[0].fd = cronSock;
pfd[0].events = POLLIN;
while (timespecisset(&timeout) && timeout.tv_sec < 65) {
poke = RELOAD_CRON | RELOAD_AT;
nfds = ppoll(pfd, 1, &timeout, mask);
switch (nfds) {
case -1:
if (errno != EINTR && errno != EAGAIN) {
syslog(LOG_ERR, "(CRON) DEATH (ppoll failure: %m)");
exit(EXIT_FAILURE);
}
if (errno == EINTR) {
if (got_sigchld) {
got_sigchld = 0;
sigchld_reaper();
}
}
break;
case 0:
return;
default:
sunlen = sizeof(s_un);
fd = accept4(cronSock, (struct sockaddr *)&s_un,
&sunlen, SOCK_NONBLOCK);
if (fd >= 0) {
(void) read(fd, &poke, 1);
close(fd);
if (poke & RELOAD_CRON) {
timespecclear(&database->mtime);
load_database(&database);
}
if (poke & RELOAD_AT) {
clock_gettime(CLOCK_REALTIME, &t2);
timespecclear(&at_database->mtime);
if (scan_atjobs(&at_database, &t2))
atrun(at_database,
batch_maxload, t2.tv_sec);
}
}
}
clock_gettime(CLOCK_REALTIME, &t2);
t2.tv_sec += GMToff;
timespecsub(&t2, &t1, &t1);
timespecsub(&timeout, &t1, &timeout);
memcpy(&t1, &t2, sizeof(t1));
if (timeout.tv_sec < 0)
timespecclear(&timeout);
}
}
static int
open_socket(void)
{
int sock, rc;
mode_t omask;
struct sockaddr_un s_un;
sock = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
if (sock == -1) {
warn("socket");
syslog(LOG_ERR, "(CRON) DEATH (can't create socket)");
exit(EXIT_FAILURE);
}
bzero(&s_un, sizeof(s_un));
if (strlcpy(s_un.sun_path, _PATH_CRON_SOCK, sizeof(s_un.sun_path))
>= sizeof(s_un.sun_path)) {
warnc(ENAMETOOLONG, _PATH_CRON_SOCK);
syslog(LOG_ERR, "(CRON) DEATH (socket path too long)");
exit(EXIT_FAILURE);
}
s_un.sun_family = AF_UNIX;
if (connect(sock, (struct sockaddr *)&s_un, sizeof(s_un)) == 0) {
warnx("already running");
syslog(LOG_ERR, "(CRON) DEATH (already running)");
exit(EXIT_FAILURE);
}
if (errno != ENOENT)
unlink(s_un.sun_path);
omask = umask(007);
rc = bind(sock, (struct sockaddr *)&s_un, sizeof(s_un));
umask(omask);
if (rc != 0) {
warn("bind");
syslog(LOG_ERR, "(CRON) DEATH (can't bind socket)");
exit(EXIT_FAILURE);
}
if (listen(sock, SOMAXCONN)) {
warn("listen");
syslog(LOG_ERR, "(CRON) DEATH (can't listen on socket)");
exit(EXIT_FAILURE);
}
if (setegid(cron_gid) == 0) {
chown(s_un.sun_path, -1, cron_gid);
(void)setegid(getgid());
}
chmod(s_un.sun_path, 0660);
return(sock);
}
static void
sigchld_handler(int x)
{
got_sigchld = 1;
}
static void
sigchld_reaper(void)
{
int waiter;
pid_t pid;
do {
pid = waitpid(-1, &waiter, WNOHANG);
switch (pid) {
case -1:
if (errno == EINTR)
continue;
break;
case 0:
break;
default:
job_exit(pid);
break;
}
} while (pid > 0);
}
static void
parse_args(int argc, char *argv[])
{
int argch;
char *ep;
while (-1 != (argch = getopt(argc, argv, "l:n"))) {
switch (argch) {
case 'l':
errno = 0;
batch_maxload = strtod(optarg, &ep);
if (*ep != '\0' || ep == optarg || errno == ERANGE ||
batch_maxload < 0) {
warnx("illegal load average: %s", optarg);
usage();
}
break;
case 'n':
NoFork = 1;
break;
default:
usage();
}
}
}