/*
* atd.c - run jobs queued by at; run with root privileges.
* Copyright (C) 1993, 1994, 1996 Thomas Koenig
* Copyright (c) 2002, 2005 Ryan Murray
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
/* System Headers */
#include <sys/types.h>
#include <sys/stat.h>
#ifdef HAVE_SYS_WAIT_H
#include <sys/wait.h>
#endif
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#elif HAVE_SYS_FCNTL_H
#include <sys/fcntl.h>
#endif
#include <ctype.h>
#ifdef HAVE_DIRENT_H
#include <dirent.h>
#elif HAVE_SYS_DIRENT_H
#include <sys/dirent.h>
#elif HAVE_SYS_DIR_H
#include <sys/dir.h>
#endif
#ifdef HAVE_ERRNO_H
#include <errno.h>
#endif
#include <pwd.h>
#include <grp.h>
#include <signal.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_GETOPT_H
#include <getopt.h>
#endif
#ifdef HAVE_UNISTD_H
#include <syslog.h>
#endif
#include <sys/file.h>
#include <utime.h>
/* Local headers */
#include "privs.h"
#include "daemon.h"
#ifndef HAVE_GETLOADAVG
#include "getloadavg.h"
#endif
#ifdef WITH_SELINUX
#include <selinux/selinux.h>
#include <selinux/get_context_list.h>
int selinux_enabled = 0;
#endif
/* Macros */
#ifndef LOG_ATD
#define LOG_ATD LOG_DAEMON
#endif
#define BATCH_INTERVAL_DEFAULT 60
#define CHECK_INTERVAL 3600
#ifndef MAXHOSTNAMELEN
#define MAXHOSTNAMELEN 64
#endif
/* Global variables */
uid_t real_uid, effective_uid;
gid_t real_gid, effective_gid;
uid_t daemon_uid = (uid_t) - 3;
gid_t daemon_gid = (gid_t) - 3;
/* File scope variables */
static char *namep;
static double load_avg = LOADAVG_MX;
static time_t now;
static time_t last_chg;
static int nothing_to_do;
unsigned int batch_interval;
static int run_as_daemon = 0;
static int mail_with_hostname = 0;
static volatile sig_atomic_t term_signal = 0;
#ifdef WITH_PAM
#include <security/pam_appl.h>
static pam_handle_t *pamh = NULL;
static const struct pam_conv conv = {
NULL
};
#endif /* WITH_PAM */
/* Signal handlers */
RETSIGTYPE
set_term(int dummy)
{
term_signal = 1;
return;
}
RETSIGTYPE
sdummy(int dummy)
{
/* Empty signal handler */
nothing_to_do = 0;
return;
}
/* SIGCHLD handler - discards completion status of children */
RETSIGTYPE
release_zombie(int dummy)
{
int status;
pid_t pid;
while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
#ifdef DEBUG_ZOMBIE
if (WIFEXITED(status))
syslog(LOG_INFO, "pid %ld exited with status %d.", pid, WEXITSTATUS(status));
else if (WIFSIGNALED(status))
syslog(LOG_NOTICE, "pid %ld killed with signal %d.", pid, WTERMSIG(status));
else if (WIFSTOPPED(status))
syslog(LOG_NOTICE, "pid %ld stopped with signal %d.", pid, WSTOPSIG(status));
else
syslog(LOG_WARNING, "pid %ld unknown reason for SIGCHLD", pid);
#endif
}
return;
}
/* Local functions */
static int
write_string(int fd, const char *a)
{
return write(fd, a, strlen(a));
}
static int
isbatch(char queue)
{
return isupper(queue) || (queue == 'b');
}
#undef DEBUG_FORK
#ifdef DEBUG_FORK
static pid_t
myfork()
{
pid_t res;
res = fork();
if (res == 0)
kill(getpid(), SIGSTOP);
return res;
}
#define fork myfork
#endif
#undef ATD_MAIL_PROGRAM
#undef ATD_MAIL_NAME
#if defined(SENDMAIL)
#define ATD_MAIL_PROGRAM SENDMAIL
#define ATD_MAIL_NAME "sendmail"
#elif defined(MAILC)
#define ATD_MAIL_PROGRAM MAILC
#define ATD_MAIL_NAME "mail"
#elif defined(MAILX)
#define ATD_MAIL_PROGRAM MAILX
#define ATD_MAIL_NAME "mailx"
#endif
#ifdef WITH_SELINUX
static int
set_selinux_context(const char *name, const char *filename) {
security_context_t user_context = NULL;
security_context_t file_context = NULL;
int retval = 0;
char *seuser = NULL;
char *level = NULL;
if (getseuserbyname(name, &seuser, &level) == 0) {
retval = get_default_context_with_level(seuser, level, NULL, &user_context);
free(seuser);
free(level);
if (retval < 0) {
lerr("get_default_context_with_level: couldn't get security context for user %s", name);
retval = -1;
goto err;
}
}
/*
* Since crontab files are not directly executed,
* crond must ensure that the crontab file has
* a context that is appropriate for the context of
* the user cron job. It performs an entrypoint
* permission check for this purpose.
*/
if (fgetfilecon(STDIN_FILENO, &file_context) < 0) {
lerr("fgetfilecon FAILED %s", filename);
retval = -1;
goto err;
}
retval = selinux_check_access(user_context, file_context, "file", "entrypoint", NULL);
freecon(file_context);
if (retval < 0) {
lerr("Not allowed to set exec context to %s for user %s", user_context, name);
retval = -1;
goto err;
}
if (setexeccon(user_context) < 0) {
lerr("Could not set exec context to %s for user %s", user_context, name);
retval = -1;
goto err;
}
err:
if (retval < 0 && security_getenforce() != 1)
retval = 0;
if (user_context)
freecon(user_context);
return retval;
}
static int
selinux_log_callback (int type, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vsyslog (LOG_ERR, fmt, ap);
va_end(ap);
return 0;
}
#endif
static void
run_file(const char *filename, uid_t uid, gid_t gid)
{
/* Run a file by by spawning off a process which redirects I/O,
* spawns a subshell, then waits for it to complete and sends
* mail to the user.
*/
pid_t pid;
int fd_out, fd_in, fd_std;
char jobbuf[9];
char *mailname = NULL;
int mailsize = 128;
char *newname;
FILE *stream;
int send_mail = 0;
struct stat buf, lbuf;
off_t size;
struct passwd *pentry;
int fflags;
int nuid;
int ngid;
char queue;
char fmt[64];
unsigned long jobno;
int rc;
char hostbuf[MAXHOSTNAMELEN];
#ifdef WITH_PAM
int retcode;
#endif
#ifdef _SC_LOGIN_NAME_MAX
errno = 0;
rc = sysconf(_SC_LOGIN_NAME_MAX);
if (rc > 0)
mailsize = rc;
#else
# ifdef LOGIN_NAME_MAX
mailsize = LOGIN_NAME_MAX;
# endif
#endif
sscanf(filename, "%c%5lx", &queue, &jobno);
if ((mailname = malloc(mailsize+1)) == NULL)
pabort("Job %8lu : out of virtual memory", jobno);
sprintf(jobbuf, "%8lu", jobno);
if ((newname = strdup(filename)) == 0)
pabort("Job %8lu : out of virtual memory", jobno);
newname[0] = '=';
/* We try to make a hard link to lock the file. If we fail, then
* somebody else has already locked or deleted it (a second atd?); log the
* fact and return.
*/
PRIV_START
rc = link(filename, newname);
PRIV_END
if (rc == -1) {
syslog(LOG_WARNING, "could not lock job %lu: %m", jobno);
free(mailname);
free(newname);
return;
}
/* If something goes wrong between here and the unlink() call,
* the job gets restarted as soon as the "=" entry is cleared
* by the main atd loop.
*/
pid = fork();
if (pid == -1) {
lerr("Cannot fork for job execution");
free(mailname);
free(newname);
return;
}
else if (pid != 0) {
free(mailname);
free(newname);
return;
}
(void) setsid(); /* own session for process */
/* Let's see who we mail to. Hopefully, we can read it from
* the command file; if not, send it to the owner, or, failing that,
* to root.
*/
pentry = getpwuid(uid);
if (pentry == NULL) {
pabort("Userid %lu not found - aborting job %8lu (%.500s)",
(unsigned long) uid, jobno, filename);
}
syslog(LOG_INFO, "Starting job %lu (%.500s) for user '%s' (%lu)",
jobno, filename, pentry->pw_name, (unsigned long) uid);
PRIV_START
stream = fopen(filename, "r");
PRIV_END
if (stream == NULL)
perr("Cannot open input file");
if ((fd_in = dup(fileno(stream))) < 0)
perr("Error duplicating input file descriptor");
if (fstat(fd_in, &buf) == -1)
perr("Error in fstat of input file descriptor");
if (lstat(filename, &lbuf) == -1)
perr("Error in fstat of input file");
if (S_ISLNK(lbuf.st_mode))
perr("Symbolic link encountered in job %8lu (%.500s) - aborting",
jobno, filename);
if ((lbuf.st_dev != buf.st_dev) || (lbuf.st_ino != buf.st_ino) ||
(lbuf.st_uid != buf.st_uid) || (lbuf.st_gid != buf.st_gid) ||
(lbuf.st_size != buf.st_size))
perr("Somebody changed files from under us for job %8lu (%.500s) - "
"aborting", jobno, filename);
if (buf.st_nlink > 2) {
perr("Somebody is trying to run a linked script for job %8lu (%.500s)",
jobno, filename);
}
if ((fflags = fcntl(fd_in, F_GETFD)) < 0)
perr("Error in fcntl");
fcntl(fd_in, F_SETFD, fflags & ~FD_CLOEXEC);
if (flock(fd_in, LOCK_EX | LOCK_NB) != 0)
perr("Somebody already locked the job %8lu (%.500s) - "
"aborting", jobno, filename);
/*
* If the spool directory is mounted via NFS `atd' isn't able to
* read from the job file and will bump out here. The file is
* opened as "root" but it is read as "daemon" which fails over
* NFS and works with local file systems. It's not clear where
* the bug is located. -Joey
*/
sprintf(fmt, "#!/bin/sh\n# atrun uid=%%d gid=%%d\n# mail %%%ds %%d",
mailsize );
/* Unlink the file unless there was an error reading it (perhaps
* temporary).
* If the file has a bogus format there is no reason in trying
* to run it again and again.
*/
if (fscanf(stream, fmt,
&nuid, &ngid, mailname, &send_mail) != 4) {
if (ferror(stream))
perr("Error reading the job file");
unlink(filename);
pabort("File %.500s is in wrong format - aborting",
filename);
}
unlink(filename);
if (mailname[0] == '-')
pabort("illegal mail name %.300s in job %8lu (%.300s)", mailname,
jobno, filename);
if (nuid != uid)
pabort("Job %8lu (%.500s) - userid %d does not match file uid %d",
jobno, filename, nuid, uid);
fclose(stream);
if (chdir(ATSPOOL_DIR) < 0)
perr("Cannot chdir to " ATSPOOL_DIR);
/* Create a file to hold the output of the job we are about to run.
* Write the mail header. Complain in case
*/
if (unlink(filename) != -1) {
syslog(LOG_WARNING,"Warning: for duplicate output file for %.100s (dead job?)",
filename);
}
if ((fd_out = open(filename,
O_RDWR | O_CREAT | O_EXCL, S_IWUSR | S_IRUSR)) < 0)
perr("Cannot create output file");
PRIV_START
if (fchown(fd_out, uid, ngid) == -1)
syslog(LOG_WARNING, "Warning: could not change owner of output file for job %li to %i:%i: %s",
jobno, uid, ngid, strerror(errno));
PRIV_END
write_string(fd_out, "Subject: Output from your job ");
write_string(fd_out, jobbuf);
if (mail_with_hostname > 0) {
gethostname(hostbuf, MAXHOSTNAMELEN-1);
write_string(fd_out, " ");
write_string(fd_out, hostbuf);
}
write_string(fd_out, "\nTo: ");
write_string(fd_out, mailname);
write_string(fd_out, "\n\n");
fstat(fd_out, &buf);
size = buf.st_size;
#ifdef WITH_PAM
AT_START_PAM;
AT_OPEN_PAM_SESSION;
closelog();
openlog("atd", LOG_PID, LOG_ATD);
#endif
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
pid = fork();
if (pid < 0)
perr("Error in fork");
else if (pid == 0) {
char *nul = NULL;
char **nenvp = &nul;
char **pam_envp=0L;
PRIV_START
#ifdef WITH_PAM
pam_envp = pam_getenvlist(pamh);
if ( ( pam_envp != 0L ) && (pam_envp[0] != 0L) )
nenvp = pam_envp;
#endif
/* Set up things for the child; we want standard input from the
* input file, and standard output and error sent to our output file.
*/
if (lseek(fd_in, (off_t) 0, SEEK_SET) < 0)
perr("Error in lseek");
if (dup2(fd_in, STDIN_FILENO) < 0)
perr("Error in I/O redirection");
if (dup2(fd_out, STDOUT_FILENO) < 0)
perr("Error in I/O redirection");
if (dup2(fd_out, STDERR_FILENO) < 0)
perr("Error in I/O redirection");
close(fd_in);
close(fd_out);
nice((tolower((int) queue) - 'a' + 1) * 2);
#ifdef WITH_SELINUX
if (selinux_enabled > 0) {
if (set_selinux_context(pentry->pw_name, filename) < 0)
perr("SELinux Failed to set context\n");
}
#endif
if (initgroups(pentry->pw_name, pentry->pw_gid))
perr("Cannot initialize the supplementary group access list");
if (setgid(ngid) < 0)
perr("Cannot change group");
if (setuid(uid) < 0)
perr("Cannot set user id");
if (SIG_ERR == signal(SIGCHLD, SIG_DFL))
perr("Cannot reset signal handler to default");
chdir("/");
execle("/bin/sh", "sh", (char *) NULL, nenvp);
perr("Exec failed for /bin/sh");
/* perr exits, the PRIV_END is just for nice form */
PRIV_END
}
/* We're the parent. Let's wait.
We inherited the master's SIGCHLD handler, which does a
non-blocking waitpid. So this blocking one will eventually
return with an ECHILD error.
*/
waitpid(pid, (int *) NULL, 0);
/* Send mail. Unlink the output file after opening it, so it
* doesn't hang around after the run.
*/
fstat(fd_out, &buf);
lseek(fd_out, 0, SEEK_SET);
if (dup2(fd_out, STDIN_FILENO) < 0)
perr("Could not use jobfile as standard input.");
/* some sendmail implementations are confused if stdout, stderr are
* not available, so let them point to /dev/null
*/
if ((fd_std = open("/dev/null", O_WRONLY)) < 0)
perr("Could not open /dev/null.");
if (dup2(fd_std, STDOUT_FILENO) < 0)
perr("Could not use /dev/null as standard output.");
if (dup2(fd_std, STDERR_FILENO) < 0)
perr("Could not use /dev/null as standard error.");
if (fd_std != STDOUT_FILENO && fd_std != STDERR_FILENO)
close(fd_std);
if (unlink(filename) == -1)
syslog(LOG_WARNING, "Warning: removing output file for job %li failed: %s",
jobno, strerror(errno));
/* The job is now finished. We can delete its input file.
*/
if (chdir(ATJOB_DIR) != 0)
perr("Somebody removed %s directory from under us.", ATJOB_DIR);
/* This also removes the flock */
(void)close(fd_in);
unlink(newname);
free(newname);
#ifdef ATD_MAIL_PROGRAM
if (((send_mail != -1) && (buf.st_size != size)) || (send_mail == 1)) {
int mail_pid = -1;
mail_pid = fork();
if ( mail_pid == 0 ) {
PRIV_START
if (initgroups(pentry->pw_name, pentry->pw_gid))
perr("Cannot initialize the supplementary group access list");
if (setgid(gid) < 0)
perr("Cannot change group");
if (setuid(uid) < 0)
perr("Cannot set user id");
if (SIG_ERR == signal(SIGCHLD, SIG_DFL))
perr("Cannot reset signal handler to default");
chdir ("/");
#if defined(SENDMAIL)
execl(SENDMAIL, "sendmail", "-i", mailname, (char *) NULL);
#else
#error "No mail command specified."
#endif
perr("Exec failed for mail command");
PRIV_END
}
else if ( mail_pid == -1 ) {
syslog(LOG_ERR, "fork of mailer failed: %m");
}
/* Parent */
waitpid(mail_pid, (int *) NULL, 0);
}
#ifdef WITH_PAM
AT_CLOSE_PAM;
closelog();
openlog("atd", LOG_PID, LOG_ATD);
#endif
#endif
exit(EXIT_SUCCESS);
}
static time_t
run_loop()
{
DIR *spool;
struct dirent *dirent;
struct stat buf;
unsigned long ctm;
unsigned long jobno;
char queue;
char batch_queue = '\0';
time_t run_time, next_job;
char batch_name[] = "z2345678901234";
char lock_name[] = "z2345678901234";
uid_t batch_uid;
gid_t batch_gid;
int run_batch;
static time_t next_batch = 0;
double currlavg[3];
/* Main loop. Open spool directory for reading and look over all the
* files in there. If the filename indicates that the job should be run,
* run a function which sets its user and group id to that of the files
* and execs a /bin/sh, which executes the shell. The function will
* then remove the script (hopefully).
*
* Also, pick the oldest batch job to run, at most one per run of
* the main loop.
*/
next_job = now + CHECK_INTERVAL;
if (next_batch == 0)
next_batch = now;
/* To avoid spinning up the disk unnecessarily, stat the directory and
* return immediately if it hasn't changed since the last time we woke
* up.
*/
if (stat(".", &buf) == -1) {
lerr("Cannot stat " ATJOB_DIR);
return next_job;
}
if (nothing_to_do && buf.st_mtime <= last_chg)
return next_job;
last_chg = buf.st_mtime;
if ((spool = opendir(".")) == NULL) {
lerr("Cannot read " ATJOB_DIR);
return next_job;
}
run_batch = 0;
nothing_to_do = 1;
batch_uid = (uid_t) - 1;
batch_gid = (gid_t) - 1;
while ((dirent = readdir(spool)) != NULL) {
/* Avoid the stat if this doesn't look like a job file */
if (sscanf(dirent->d_name, "%c%5lx%8lx", &queue, &jobno, &ctm) != 3)
continue;
/* Chances are a '=' file has been deleted from under us.
* Ignore.
*/
if (stat(dirent->d_name, &buf) != 0)
continue;
if (!S_ISREG(buf.st_mode))
continue;
/* We don't want files which at(1) hasn't yet marked executable. */
if (!(buf.st_mode & S_IXUSR)) {
nothing_to_do = 0; /* it will probably become executable soon */
continue;
}
run_time = (time_t) ctm *60;
/* Skip lock files */
if (queue == '=') {
if ((buf.st_nlink == 1) && (run_time + CHECK_INTERVAL <= now)) {
int fd;
fd = open(dirent->d_name, O_RDONLY);
if (fd != -1) {
if (flock(fd, LOCK_EX | LOCK_NB) == 0) {
unlink(dirent->d_name);
syslog(LOG_NOTICE, "removing stale lock file %s\n", dirent->d_name);
}
(void)close(fd);
}
}
continue;
}
/* Skip any other file types which may have been invented in
* the meantime.
*/
if (!(isupper(queue) || islower(queue))) {
continue;
}
/* Is the file already locked?
*/
if (buf.st_nlink > 1) {
if (run_time < buf.st_mtime)
run_time = buf.st_mtime;
if (run_time + CHECK_INTERVAL <= now) {
/* Something went wrong the last time this was executed.
* Let's remove the lockfile and reschedule.
* We also change the timestamp to avoid rerunning the job more
* than once every CHECK_INTERVAL.
*/
strncpy(lock_name, dirent->d_name, sizeof(lock_name));
if (utime(lock_name, 0) < 0)
syslog(LOG_ERR, "utime couldn't be set for lock file %s\n", lock_name);
lock_name[sizeof(lock_name)-1] = '\0';
lock_name[0] = '=';
unlink(lock_name);
next_job = now;
nothing_to_do = 0;
}
continue;
}
/* If we got here, then there are jobs of some kind waiting.
* We could try to be smarter and leave nothing_to_do set if
* we end up processing all the jobs, but that's risky (run_file
* might fail and expect the job to be rescheduled), and it doesn't
* gain us much. */
nothing_to_do = 0;
/* There's a job for later. Note its execution time if it's
* the earliest so far.
*/
if (run_time > now) {
if (next_job > run_time) {
next_job = run_time;
}
continue;
}
if (isbatch(queue)) {
/* We could potentially run this batch job. If it's scheduled
* at a higher priority than anything before, keep its
* filename.
*/
run_batch++;
if (strcmp(batch_name, dirent->d_name) > 0) {
strncpy(batch_name, dirent->d_name, sizeof(batch_name));
batch_name[sizeof(batch_name)-1] = '\0';
batch_uid = buf.st_uid;
batch_gid = buf.st_gid;
batch_queue = queue;
}
}
else {
if (run_time <= now) {
run_file(dirent->d_name, buf.st_uid, buf.st_gid);
}
}
}
closedir(spool);
/* run the single batch file, if any
*/
if (run_batch && (next_batch <= now)) {
next_batch = now + batch_interval;
#ifdef GETLOADAVG_PRIVILEGED
START_PRIV
#endif
if (getloadavg(currlavg, 1) < 1) {
currlavg[0] = 0.0;
}
#ifdef GETLOADAVG_PRIVILEGED
END_PRIV
#endif
if (currlavg[0] < load_avg) {
run_file(batch_name, batch_uid, batch_gid);
run_batch--;
}
}
if (run_batch && (next_batch < next_job)) {
nothing_to_do = 0;
next_job = next_batch;
}
return next_job;
}
#ifdef HAVE_TIMER_CREATE
timer_t timer;
struct itimerspec timeout;
void timer_setup()
{
struct sigevent sev;
sev.sigev_notify = SIGEV_SIGNAL;
sev.sigev_signo = SIGHUP;
sev.sigev_value.sival_ptr = &timer;
memset(&timeout, 0, sizeof(timeout));
if (timer_create(CLOCK_REALTIME, &sev, &timer) < 0)
pabort("unable to create timer");
}
time_t atd_gettime()
{
struct timespec curtime;
clock_gettime(CLOCK_REALTIME, &curtime);
return curtime.tv_sec;
}
void atd_setalarm(time_t next)
{
timeout.it_value.tv_sec = next;
timer_settime(timer, TIMER_ABSTIME, &timeout, NULL);
pause();
}
#else
void timer_setup()
{
}
time_t atd_gettime()
{
return time(NULL);
}
void atd_setalarm(time_t next)
{
sleep(next - atd_gettime());
}
#endif
/* Global functions */
int
main(int argc, char *argv[])
{
/* Browse through ATJOB_DIR, checking all the jobfiles whether they should
* be executed and or deleted. The queue is coded into the first byte of
* the job filename, the next 5 bytes encode the serial number in hex, and
* the final 8 bytes encode the date (minutes since Eon) in hex. A file
* which has not been executed yet is denoted by its execute - bit set.
* For those files which are to be executed, run_file() is called, which forks
* off a child which takes care of I/O redirection, forks off another child
* for execution and yet another one, optionally, for sending mail.
* Files which already have run are removed during the next invocation.
*/
int c;
time_t next_invocation;
struct sigaction act;
struct passwd *pwe;
struct group *ge;
#ifdef WITH_SELINUX
selinux_enabled=is_selinux_enabled();
if (selinux_enabled) {
selinux_set_callback(SELINUX_CB_LOG, (union selinux_callback) selinux_log_callback);
}
#endif
/* We don't need root privileges all the time; running under uid and gid
* daemon is fine.
*/
if ((pwe = getpwnam(DAEMON_USERNAME)) == NULL)
perr("Cannot get uid for " DAEMON_USERNAME);
daemon_uid = pwe->pw_uid;
if ((ge = getgrnam(DAEMON_GROUPNAME)) == NULL)
perr("Cannot get gid for " DAEMON_GROUPNAME);
daemon_gid = ge->gr_gid;
RELINQUISH_PRIVS_ROOT(daemon_uid, daemon_gid)
#ifndef LOG_CRON
#define LOG_CRON LOG_DAEMON
#endif
openlog("atd", LOG_PID, LOG_CRON);
opterr = 0;
errno = 0;
run_as_daemon = 1;
batch_interval = BATCH_INTERVAL_DEFAULT;
while ((c = getopt(argc, argv, "sdnl:b:f")) != EOF) {
switch (c) {
case 'l':
if (sscanf(optarg, "%lf", &load_avg) != 1)
pabort("garbled option -l");
if (load_avg <= 0.)
load_avg = LOADAVG_MX;
break;
case 'b':
if (sscanf(optarg, "%ud", &batch_interval) != 1)
pabort("garbled option -b");
break;
case 'd':
daemon_debug++;
daemon_foreground++;
break;
case 'f':
daemon_foreground++;
break;
case 'n':
mail_with_hostname=1;
break;
case 's':
run_as_daemon = 0;
break;
case '?':
pabort("unknown option");
break;
default:
pabort("idiotic option - aborted");
break;
}
}
namep = argv[0];
if (chdir(ATJOB_DIR) != 0)
perr("Cannot change to " ATJOB_DIR);
if (optind < argc)
pabort("non-option arguments - not allowed");
sigaction(SIGCHLD, NULL, &act);
act.sa_handler = release_zombie;
act.sa_flags = SA_NOCLDSTOP;
sigaction(SIGCHLD, &act, NULL);
if (!run_as_daemon) {
now = atd_gettime();
run_loop();
exit(EXIT_SUCCESS);
}
/* Main loop. Let's sleep for a specified interval,
* or until the next job is scheduled, or until we get signaled.
* After any of these events, we rescan the queue.
* A signal handler setting term_signal will make sure there's
* a clean exit.
*/
sigaction(SIGHUP, NULL, &act);
act.sa_handler = sdummy;
sigaction(SIGHUP, &act, NULL);
sigaction(SIGTERM, NULL, &act);
act.sa_handler = set_term;
sigaction(SIGTERM, &act, NULL);
sigaction(SIGINT, NULL, &act);
act.sa_handler = set_term;
sigaction(SIGINT, &act, NULL);
timer_setup();
daemon_setup();
do {
now = atd_gettime();
next_invocation = run_loop();
if (next_invocation > now) {
atd_setalarm(next_invocation);
}
} while (!term_signal);
daemon_cleanup();
exit(EXIT_SUCCESS);
}