cronie/anacron/main.c

518 lines
12 KiB
C
Raw Permalink Normal View History

2017-05-08 15:30:03 +02:00
/*
Anacron - run commands periodically
Copyright (C) 1998 Itai Tzur <itzur@actcom.co.il>
Copyright (C) 1999 Sean 'Shaleh' Perry <shaleh@debian.org>
Copyright (C) 2004 Pascal Hakim <pasc@redellipse.net>
2019-08-06 18:08:05 +02:00
2017-05-08 15:30:03 +02:00
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.
2019-08-06 18:08:05 +02:00
2017-05-08 15:30:03 +02:00
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.
2019-08-06 18:08:05 +02:00
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.
2017-05-08 15:30:03 +02:00
The GNU General Public License can also be found in the file
`COPYING' that comes with the Anacron source distribution.
*/
2019-08-06 18:08:05 +02:00
#include "config.h"
2017-05-08 15:30:03 +02:00
#include <time.h>
#include <sys/time.h>
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <stdlib.h>
#include <locale.h>
#include "global.h"
#include "gregor.h"
2019-08-06 18:08:05 +02:00
#include "cronie_common.h"
2017-05-08 15:30:03 +02:00
pid_t primary_pid;
int day_now;
int year, month, day_of_month; /* date anacron started */
char *program_name;
2023-07-01 12:15:55 +02:00
char *anacrontab = NULL;
char *spooldir = NULL;
2017-05-08 15:30:03 +02:00
int serialize, force, update_only, now,
no_daemon, quiet, testing_only; /* command-line options */
2019-08-06 18:08:05 +02:00
char **job_args; /* vector of "job" command-line arguments */
int job_nargs; /* number of these */
2017-05-08 15:30:03 +02:00
char *defarg = "*";
int in_background; /* are we in the background? */
sigset_t old_sigmask; /* signal mask when started */
job_rec *first_job_rec;
env_rec *first_env_rec;
time_t start_sec; /* time anacron started */
static volatile int got_sigalrm, got_sigchld, got_sigusr1;
int running_jobs, running_mailers; /* , number of */
int range_start = -1;
int range_stop = -1;
2019-08-06 18:08:05 +02:00
int preferred_hour = -1;
2017-05-08 15:30:03 +02:00
static void
2019-08-06 18:08:05 +02:00
print_version(void)
2017-05-08 15:30:03 +02:00
{
2019-08-06 18:08:05 +02:00
printf("Anacron from project %s\n"
2017-05-08 15:30:03 +02:00
"Copyright (C) 1998 Itai Tzur <itzur@actcom.co.il>\n"
"Copyright (C) 1999 Sean 'Shaleh' Perry <shaleh@debian.org>\n"
"Copyright (C) 2004 Pascal Hakim <pasc@redellipse.net>\n"
"\n"
"Mail comments, suggestions and bug reports to <pasc@redellipse.net>."
2019-08-06 18:08:05 +02:00
"\n\n", PACKAGE_STRING);
2017-05-08 15:30:03 +02:00
}
static void
2019-08-06 18:08:05 +02:00
print_usage(void)
2017-05-08 15:30:03 +02:00
{
2019-08-06 18:08:05 +02:00
printf("Usage:\n");
printf(" %s [options] [job] ...\n", program_name);
printf(" %s -T [-t anacrontab-file]\n", program_name);
printf("\nOptions:\n");
printf(" -s Serialize execution of jobs\n");
printf(" -f Force execution of jobs, even before their time\n");
printf(" -n Run jobs with no delay, implies -s\n");
printf(" -d Don't fork to the background\n");
printf(" -q Suppress stderr messages, only applicable with -d\n");
printf(" -u Update the timestamps without actually running anything\n");
printf(" -V Print version information\n");
printf(" -h Print this message\n");
printf(" -t <file> Use alternative anacrontab\n");
printf(" -T Test an anacrontab\n");
printf(" -S <dir> Select a different spool directory\n");
printf("\nSee the anacron(8) manpage for more details.\n");
2017-05-08 15:30:03 +02:00
}
static void
parse_opts(int argc, char *argv[])
/* Parse command-line options */
{
int opt;
quiet = no_daemon = serialize = force = update_only = now = 0;
opterr = 0;
while ((opt = getopt(argc, argv, "sfundqt:TS:Vh")) != EOF)
{
switch (opt)
{
case 's':
serialize = 1;
break;
case 'f':
force = 1;
break;
case 'u':
update_only = 1;
break;
case 'n':
now = serialize = 1;
break;
case 'd':
no_daemon = 1;
break;
case 'q':
quiet = 1;
break;
case 't':
2023-07-01 12:15:55 +02:00
free(anacrontab);
2017-05-08 15:30:03 +02:00
anacrontab = strdup(optarg);
break;
case 'T':
testing_only = 1;
break;
case 'S':
2023-07-01 12:15:55 +02:00
free(spooldir);
2017-05-08 15:30:03 +02:00
spooldir = strdup(optarg);
break;
case 'V':
print_version();
2019-08-06 18:08:05 +02:00
exit(EXIT_SUCCESS);
2017-05-08 15:30:03 +02:00
case 'h':
print_usage();
2019-08-06 18:08:05 +02:00
exit(EXIT_SUCCESS);
2017-05-08 15:30:03 +02:00
case '?':
fprintf(stderr, "%s: invalid option: %c\n",
program_name, optopt);
fprintf(stderr, "type: `%s -h' for more information\n",
program_name);
exit(FAILURE_EXIT);
}
}
if (optind == argc)
{
/* no arguments. Equivalent to: `*' */
2019-08-06 18:08:05 +02:00
job_nargs = 1;
job_args = &defarg;
2017-05-08 15:30:03 +02:00
}
else
{
2019-08-06 18:08:05 +02:00
job_nargs = argc - optind;
job_args = argv + optind;
2017-05-08 15:30:03 +02:00
}
}
pid_t
2019-08-06 18:08:05 +02:00
xfork(void)
2017-05-08 15:30:03 +02:00
/* Like fork(), only never returns on failure */
{
pid_t pid;
pid = fork();
if (pid == -1) die_e("Can't fork");
return pid;
}
int
xopen(int fd, const char *file_name, int flags)
/* Like open, only it:
* a) never returns on failure, and
* b) if "fd" is non-negative, expect the file to open
* on file-descriptor "fd".
*/
{
int rfd;
rfd = open(file_name, flags);
if (fd >= 0 && rfd != fd)
die_e("Can't open %s on file-descriptor %d", file_name, fd);
else if (rfd < 0)
die_e("Can't open %s", file_name);
return rfd;
}
void
xclose(int fd)
/* Like close(), only doesn't return on failure */
{
if (close(fd)) die_e("Can't close file descriptor %d", fd);
}
static void
2019-08-06 18:08:05 +02:00
go_background(void)
2017-05-08 15:30:03 +02:00
/* Become a daemon. The foreground process exits successfully. */
{
pid_t pid;
/* stdin is already closed */
if (fclose(stdout)) die_e("Can't close stdout");
2023-07-01 12:15:55 +02:00
/* coverity[leaked_handle] fd 1 closed automatically */
2017-05-08 15:30:03 +02:00
xopen(1, "/dev/null", O_WRONLY);
if (fclose(stderr)) die_e("Can't close stderr");
2023-07-01 12:15:55 +02:00
/* coverity[leaked_handle] fd 2 closed automatically */
2017-05-08 15:30:03 +02:00
xopen(2, "/dev/null", O_WRONLY);
pid = xfork();
if (pid != 0)
{
/* parent */
2019-08-06 18:08:05 +02:00
exit(EXIT_SUCCESS);
2017-05-08 15:30:03 +02:00
}
else
{
/* child */
primary_pid = getpid();
if (setsid() == -1) die_e("setsid() error");
in_background = 1;
}
}
2019-08-06 18:08:05 +02:00
static void
handle_sigalrm(int unused ATTRIBUTE_UNUSED)
2017-05-08 15:30:03 +02:00
{
got_sigalrm = 1;
}
2019-08-06 18:08:05 +02:00
static void
handle_sigchld(int unused ATTRIBUTE_UNUSED)
2017-05-08 15:30:03 +02:00
{
got_sigchld = 1;
}
2019-08-06 18:08:05 +02:00
static void
handle_sigusr1(int unused ATTRIBUTE_UNUSED)
2017-05-08 15:30:03 +02:00
{
got_sigusr1 = 1;
}
static void
2019-08-06 18:08:05 +02:00
set_signal_handling(void)
2017-05-08 15:30:03 +02:00
/* We only use SIGALRM, SIGCHLD and SIGUSR1, and we unblock them only
* in wait_signal().
*/
{
sigset_t ss;
struct sigaction sa;
got_sigalrm = got_sigchld = got_sigusr1 = 0;
/* block SIGALRM, SIGCHLD and SIGUSR1 */
if (sigemptyset(&ss) ||
sigaddset(&ss, SIGALRM) ||
sigaddset(&ss, SIGCHLD) ||
sigaddset(&ss, SIGUSR1)) die_e("sigset error");
if (sigprocmask(SIG_BLOCK, &ss, NULL)) die_e ("sigprocmask error");
/* setup SIGALRM handler */
sa.sa_handler = handle_sigalrm;
sa.sa_mask = ss;
sa.sa_flags = 0;
if (sigaction(SIGALRM, &sa, NULL)) die_e("sigaction error");
/* setup SIGCHLD handler */
sa.sa_handler = handle_sigchld;
sa.sa_mask = ss;
sa.sa_flags = SA_NOCLDSTOP;
if (sigaction(SIGCHLD, &sa, NULL)) die_e("sigaction error");
/* setup SIGUSR1 handler */
sa.sa_handler = handle_sigusr1;
sa.sa_mask = ss;
sa.sa_flags = 0;
if (sigaction(SIGUSR1, &sa, NULL)) die_e("sigaction error");
}
static void
2019-08-06 18:08:05 +02:00
wait_signal(void)
2017-05-08 15:30:03 +02:00
/* Return after a signal is caught */
{
sigset_t ss;
if (sigprocmask(0, NULL, &ss)) die_e("sigprocmask error");
if (sigdelset(&ss, SIGALRM) ||
sigdelset(&ss, SIGCHLD) ||
sigdelset(&ss, SIGUSR1)) die_e("sigset error");
sigsuspend(&ss);
}
static void
2019-08-06 18:08:05 +02:00
wait_children(void)
2017-05-08 15:30:03 +02:00
/* Wait until we have no more children (of any kind) */
{
while (running_jobs > 0 || running_mailers > 0)
{
wait_signal();
if (got_sigchld) tend_children();
got_sigchld = 0;
if (got_sigusr1) explain("Received SIGUSR1");
got_sigusr1 = 0;
}
}
static void
2019-08-06 18:08:05 +02:00
orderly_termination(void)
2017-05-08 15:30:03 +02:00
/* Execution is diverted here, when we get SIGUSR1 */
{
explain("Received SIGUSR1");
got_sigusr1 = 0;
wait_children();
explain("Exited");
2019-08-06 18:08:05 +02:00
exit(EXIT_SUCCESS);
2017-05-08 15:30:03 +02:00
}
static void
xsleep(unsigned int n)
/* Sleep for n seconds, servicing SIGCHLDs and SIGUSR1s in the meantime.
* If n=0, return immediately.
*/
{
if (n == 0) return;
alarm(n);
do
{
wait_signal();
if (got_sigchld) tend_children();
got_sigchld = 0;
if (got_sigusr1) orderly_termination();
}
while (!got_sigalrm);
got_sigalrm = 0;
}
static void
2019-08-06 18:08:05 +02:00
wait_jobs(void)
2017-05-08 15:30:03 +02:00
/* Wait until there are no running jobs,
* servicing SIGCHLDs and SIGUSR1s in the meantime.
*/
{
while (running_jobs > 0)
{
wait_signal();
if (got_sigchld) tend_children();
got_sigchld = 0;
if (got_sigusr1) orderly_termination();
}
}
static void
2019-08-06 18:08:05 +02:00
record_start_time(void)
2017-05-08 15:30:03 +02:00
{
struct tm *tm_now;
start_sec = time(NULL);
tm_now = localtime(&start_sec);
year = tm_now->tm_year + 1900;
month = tm_now->tm_mon + 1;
day_of_month = tm_now->tm_mday;
day_now = day_num(year, month, day_of_month);
if (day_now == -1) die("Invalid date (this is really embarrassing)");
if (!update_only && !testing_only)
explain("Anacron started on %04d-%02d-%02d",
year, month, day_of_month);
}
2019-08-06 18:08:05 +02:00
static unsigned int
2017-05-08 15:30:03 +02:00
time_till(job_rec *jr)
/* Return the number of seconds that we have to wait until it's time
* to start job jr.
*/
{
2019-08-06 18:08:05 +02:00
time_t tj, tn;
2017-05-08 15:30:03 +02:00
if (now) return 0;
tn = time(NULL);
2019-08-06 18:08:05 +02:00
tj = start_sec + (time_t)jr->delay * 60;
2017-05-08 15:30:03 +02:00
if (tj < tn) return 0;
if (tj - tn > 3600*24)
{
explain("System time manipulation detected, job `%s' will run immediately",
jr->ident);
return 0;
}
2019-08-06 18:08:05 +02:00
return (unsigned int)(tj - tn);
2017-05-08 15:30:03 +02:00
}
static void
2019-08-06 18:08:05 +02:00
fake_jobs(void)
2017-05-08 15:30:03 +02:00
{
int j;
j = 0;
while (j < njobs)
{
fake_job(job_array[j]);
explain("Updated timestamp for job `%s' to %04d-%02d-%02d",
job_array[j]->ident, year, month, day_of_month);
j++;
}
}
static void
2019-08-06 18:08:05 +02:00
explain_intentions(void)
2017-05-08 15:30:03 +02:00
{
int j;
j = 0;
while (j < njobs)
{
if (now)
{
explain("Will run job `%s'", job_array[j]->ident);
}
else
{
explain("Will run job `%s' in %d min.",
job_array[j]->ident, job_array[j]->delay);
}
j++;
}
if (serialize && njobs > 0)
explain("Jobs will be executed sequentially");
}
int
main(int argc, char *argv[])
{
int j;
int cwd;
struct timeval tv;
struct timezone tz;
anacrontab = NULL;
spooldir = NULL;
setlocale(LC_ALL, "");
if (gettimeofday(&tv, &tz) != 0)
explain("Can't get exact time, failure.");
2019-08-06 18:08:05 +02:00
srandom((unsigned int)(getpid() + tv.tv_usec));
2017-05-08 15:30:03 +02:00
if((program_name = strrchr(argv[0], '/')) == NULL)
program_name = argv[0];
else
++program_name; /* move pointer to char after '/' */
parse_opts(argc, argv);
if (anacrontab == NULL)
anacrontab = strdup(ANACRONTAB);
if (spooldir == NULL)
spooldir = strdup(ANACRON_SPOOL_DIR);
if ((cwd = open ("./", O_RDONLY)) == -1) {
die_e ("Can't save current directory");
}
in_background = 0;
if (chdir(spooldir)) die_e("Can't chdir to %s", spooldir );
if (sigprocmask(0, NULL, &old_sigmask)) die_e("sigset error");
if (fclose(stdin)) die_e("Can't close stdin");
2019-08-06 18:08:05 +02:00
xopen(STDIN_FILENO, "/dev/null", O_RDONLY);
2017-05-08 15:30:03 +02:00
if (!no_daemon && !testing_only)
go_background();
else
primary_pid = getpid();
record_start_time();
read_tab(cwd);
close(cwd);
arrange_jobs();
if (testing_only)
{
2019-08-06 18:08:05 +02:00
if (complaints) exit (EXIT_FAILURE);
2017-05-08 15:30:03 +02:00
2019-08-06 18:08:05 +02:00
exit (EXIT_SUCCESS);
2017-05-08 15:30:03 +02:00
}
if (update_only)
{
fake_jobs();
2019-08-06 18:08:05 +02:00
exit(EXIT_SUCCESS);
2017-05-08 15:30:03 +02:00
}
explain_intentions();
set_signal_handling();
running_jobs = running_mailers = 0;
for(j = 0; j < njobs; ++j)
{
xsleep(time_till(job_array[j]));
if (serialize) wait_jobs();
launch_job(job_array[j]);
}
wait_children();
explain("Normal exit (%d job%s run)", njobs, njobs == 1 ? "" : "s");
2019-08-06 18:08:05 +02:00
exit(EXIT_SUCCESS);
2017-05-08 15:30:03 +02:00
}