cronie/anacron/runjob.c

425 lines
11 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
Anacron - run commands periodically
Copyright (C) 1998 Itai Tzur <itzur@actcom.co.il>
Copyright (C) 1999 Sean 'Shaleh' Perry <shaleh@debian.org>
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 Street, Fifth Floor, Boston, MA 02110-1301 USA.
The GNU General Public License can also be found in the file
`COPYING' that comes with the Anacron source distribution.
*/
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <pwd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <limits.h>
#include "global.h"
#include "cronie_common.h"
#include <langinfo.h>
static int
temp_file(job_rec *jr)
/* Open a temporary file and return its file descriptor */
{
char *dir;
char template[PATH_MAX+1];
int fdin = -1;
int fdout;
int len;
dir = getenv("TMPDIR");
if (dir == NULL || *dir == '\0')
dir = P_tmpdir;
len = snprintf(template, sizeof(template), "%s/$anacronXXXXXX", dir);
if (len < 0)
die_e("snprintf failed");
else if ((size_t) len >= sizeof(template))
die_e("TMPDIR too long");
fdout = mkstemp(template);
if (fdout == -1) die_e("Can't open temporary file for writing");
fdin = open(template, O_RDONLY, S_IRUSR | S_IWUSR);
if (fdin == -1) die_e("Can't open temporary file for reading");
if (unlink(template)) die_e("Can't unlink temporary file");
fcntl(fdout, F_SETFD, FD_CLOEXEC); /* set close-on-exec flag */
fcntl(fdin, F_SETFD, FD_CLOEXEC); /* set close-on-exec flag */
jr->input_fd = fdin;
jr->output_fd = fdout;
return fdout;
}
static off_t
file_size(int fd)
/* Return the size of temporary file fd */
{
struct stat st;
if (fstat(fd, &st)) die_e("Can't fstat temporary file");
return st.st_size;
}
static char *
username(void)
{
struct passwd *ps;
static char *user;
if (user)
return user;
ps = getpwuid(geteuid());
if (ps == NULL || ps->pw_name == NULL) die_e("getpwuid() error");
user = strdup(ps->pw_name);
if (user == NULL) die_e("memory allocation error");
return user;
}
static void
xputenv(const char *s)
{
char *name = NULL, *val = NULL;
char *eq_ptr;
size_t eq_index;
if (s == NULL) {
die_e("Invalid environment string");
}
eq_ptr = strchr(s, '=');
if (eq_ptr == NULL) {
die_e("Invalid environment string");
}
eq_index = (size_t) (eq_ptr - s);
name = malloc((eq_index + 1) * sizeof(char));
if (name == NULL) {
die_e("Not enough memory to set the environment");
}
val = malloc((strlen(s) - eq_index) * sizeof(char));
if (val == NULL) {
die_e("Not enough memory to set the environment");
}
strncpy(name, s, eq_index);
name[eq_index] = '\0';
strcpy(val, s + eq_index + 1);
if (setenv(name, val, 1)) {
die_e("Can't set the environment");
}
free(name);
free(val);
return;
}
static void
setup_env(const job_rec *jr)
/* Setup the environment for the job according to /etc/anacrontab */
{
env_rec *er;
er = first_env_rec;
if (er == NULL || jr->prev_env_rec == NULL) return;
xputenv(er->assign);
while (er != jr->prev_env_rec)
{
er = er->next;
xputenv(er->assign);
}
}
static void
run_job(const job_rec *jr)
/* This is called to start the job, after the fork */
{
/* setup stdout and stderr */
xclose(1);
xclose(2);
if (dup2(jr->output_fd, 1) != 1 || dup2(jr->output_fd, 2) != 2)
die_e("dup2() error"); /* dup2 also clears close-on-exec flag */
in_background = 0; /* now, errors will be mailed to the user */
if (chdir("/")) die_e("Can't chdir to '/'");
if (sigprocmask(SIG_SETMASK, &old_sigmask, NULL))
die_e("sigprocmask error");
xcloselog();
execl("/bin/sh", "/bin/sh", "-c", jr->command, (char *)NULL);
die_e("execl() error");
}
static void
xwrite(int fd, const char *string)
/* Write (using write()) the string "string" to temporary file "fd".
* Don't return on failure */
{
if (write(fd, string, strlen(string)) == -1)
die_e("Can't write to temporary file");
}
static int
xwait(pid_t pid , int *status)
/* Check if child process "pid" has finished. If it has, return 1 and its
* exit status in "*status". If not, return 0.
*/
{
pid_t r;
r = waitpid(pid, status, WNOHANG);
if (r == -1) die_e("waitpid() error");
if (r == 0) return 0;
return 1;
}
static void
launch_mailer(job_rec *jr)
{
pid_t pid;
struct stat buf;
if (jr->mailto == NULL)
{
explain("Empty MAILTO set, not mailing output");
return;
}
/* Check that we have a way of sending mail. */
if(stat(SENDMAIL, &buf))
{
complain("Can't find sendmail at %s, not mailing output", SENDMAIL);
return;
}
pid = xfork();
if (pid == 0)
{
/* child */
in_background = 1;
/* set stdin to the job's output */
xclose(STDIN_FILENO);
if (dup2(jr->input_fd, STDIN_FILENO) != 0) die_e("Can't dup2()");
if (lseek(STDIN_FILENO, 0, SEEK_SET) != 0) die_e("Can't lseek()");
if (sigprocmask(SIG_SETMASK, &old_sigmask, NULL))
die_e("sigprocmask error");
xcloselog();
/* Ensure stdout/stderr are sane before exec-ing sendmail */
/* coverity[leaked_handle] STDOUT closed automatically */
xclose(STDOUT_FILENO); xopen(STDOUT_FILENO, "/dev/null", O_WRONLY);
/* coverity[leaked_handle] STDERR closed automatically */
xclose(STDERR_FILENO); xopen(STDERR_FILENO, "/dev/null", O_WRONLY);
xclose(jr->output_fd);
/* Ensure stdin is not appendable ... ? */
/* fdflags = fcntl(0, F_GETFL); fdflags &= ~O_APPEND; */
/* fcntl(0, F_SETFL, fdflags ); */
/* Here, I basically mirrored the way /usr/sbin/sendmail is called
* by cron on a Debian system, except for the "-oem" and "-or0s"
* options, which don't seem to be appropriate here.
* Hopefully, this will keep all the MTAs happy. */
execl(SENDMAIL, SENDMAIL, "-FAnacron", "-odi",
jr->mailto, (char *)NULL);
die_e("Can't exec " SENDMAIL);
}
/* parent */
/* record mailer pid */
jr->mailer_pid = pid;
running_mailers++;
}
static void
tend_mailer(job_rec *jr, int status)
{
if (WIFEXITED(status) && WEXITSTATUS(status) != 0)
complain("Tried to mail output of job `%s', "
"but mailer process (" SENDMAIL ") exited with status %d",
jr->ident, WEXITSTATUS(status));
else if (!WIFEXITED(status) && WIFSIGNALED(status))
complain("Tried to mail output of job `%s', "
"but mailer process (" SENDMAIL ") got signal %d",
jr->ident, WTERMSIG(status));
else if (!WIFEXITED(status) && !WIFSIGNALED(status))
complain("Tried to mail output of job `%s', "
"but mailer process (" SENDMAIL ") terminated abnormally"
, jr->ident);
jr->mailer_pid = 0;
running_mailers--;
}
void
launch_job(job_rec *jr)
{
pid_t pid;
int fd;
char hostname[512];
char *mailto;
char *mailfrom;
char mailto_expanded[MAX_EMAILSTR];
char mailfrom_expanded[MAX_EMAILSTR];
/* get hostname */
if (gethostname(hostname, 512)) {
strcpy (hostname,"unknown machine");
}
setup_env(jr);
/* Get the destination email address if set, or current user otherwise */
mailto = getenv("MAILTO");
if (mailto == NULL) {
mailto = username();
}
else {
if (expand_envvar(mailto, mailto_expanded, sizeof(mailto_expanded))) {
mailto = mailto_expanded;
}
else {
complain("The environment variable 'MAILTO' could not be expanded. The non-expanded value will be used.");
}
}
/* Get the source email address if set, or current user otherwise */
mailfrom = getenv("MAILFROM");
if (mailfrom == NULL) {
mailfrom = username();
}
else {
if (expand_envvar(mailfrom, mailfrom_expanded, sizeof(mailfrom_expanded))) {
mailfrom = mailfrom_expanded;
}
else {
complain("The environment variable 'MAILFROM' could not be expanded. The non-expanded value will be used.");
}
}
/* create temporary file for stdout and stderr of the job */
temp_file(jr); fd = jr->output_fd;
/* write mail header */
xwrite(fd, "From: ");
xwrite(fd, "Anacron <");
xwrite(fd, mailfrom);
xwrite(fd, ">\n");
xwrite(fd, "To: ");
xwrite(fd, mailto);
xwrite(fd, "\n");
xwrite(fd, "MIME-Version: 1.0\n");
xwrite(fd, "Content-Type: text/plain; charset=\"");
xwrite(fd, nl_langinfo(CODESET));
xwrite(fd, "\"\n");
xwrite(fd, "Content-Transfer-Encoding: 8bit\n");
xwrite(fd, "Subject: Anacron job '");
xwrite(fd, jr->ident);
xwrite(fd, "' on ");
xwrite(fd, hostname);
xwrite(fd, "\n\n");
if (*mailto == '\0')
jr->mailto = NULL;
else
/* ugly but works without strdup() */
jr->mailto = mailto;
jr->mail_header_size = file_size(fd);
pid = xfork();
if (pid == 0)
{
/* child */
in_background = 1;
run_job(jr);
/* execution never gets here */
}
/* parent */
explain("Job `%s' started", jr->ident);
jr->job_pid = pid;
running_jobs++;
}
static void
tend_job(job_rec *jr, int status)
/* Take care of a finished job */
{
int mail_output;
const char *m;
update_timestamp(jr);
unlock(jr);
if (file_size(jr->output_fd) > jr->mail_header_size) mail_output = 1;
else mail_output = 0;
m = mail_output ? " (produced output)" : "";
if (WIFEXITED(status) && WEXITSTATUS(status) == 0)
explain("Job `%s' terminated%s", jr->ident, m);
else if (WIFEXITED(status))
explain("Job `%s' terminated (exit status: %d)%s",
jr->ident, WEXITSTATUS(status), m);
else if (WIFSIGNALED(status))
complain("Job `%s' terminated due to signal %d%s",
jr->ident, WTERMSIG(status), m);
else /* is this possible? */
complain("Job `%s' terminated abnormally%s", jr->ident, m);
jr->job_pid = 0;
running_jobs--;
if (mail_output) launch_mailer(jr);
xclose(jr->output_fd);
xclose(jr->input_fd);
}
void
tend_children(void)
/* This is called whenever we get a SIGCHLD.
* Takes care of zombie children.
*/
{
int j;
int status;
j = 0;
while (j < njobs)
{
if (job_array[j]->mailer_pid != 0 &&
xwait(job_array[j]->mailer_pid, &status))
tend_mailer(job_array[j], status);
if (job_array[j]->job_pid != 0 &&
xwait(job_array[j]->job_pid, &status))
tend_job(job_array[j], status);
j++;
}
}