Import Upstream version 1.7.2

This commit is contained in:
geos_one
2025-08-06 17:45:07 +02:00
commit 8035530473
75 changed files with 33406 additions and 0 deletions

39
anacron/ChangeLog.anacron Normal file
View File

@@ -0,0 +1,39 @@
Changes in Anacron 2.3.1
------------------------
* documentation no longer suggests adding local directories to the PATH
Changes in Anacron 2.3
----------------------
* anacron can now read an arbitrary anacrontab file, use the -t option
Changes in Anacron 2.1/2.2
--------------------------
* Sean 'Shaleh' Perry <shaleh@(debian.org|valinux.com)> is now maintainer
* if timestamp is from the future, re-run job
* ansi cleanup / code cleaning
Changes in Anacron 2.0.1
------------------------
* Minor cosmetic changes to log messages.
* Jobs are now started with "/" as their working directory. This is
more compatible with older Anacron versions, avoids annoying errors on
some systems, and generally seems to make more sense.
Summary of major changes in Anacron 2.0
---------------------------------------
* Complete rewrite in C. Should be backwards compatible with existing
Anacron installations.
* First release as a "generic" Linux package (was a Debian package).
* No longer needs special lock-files. Locking is done on the timestamp
files.
* Sends log messages to syslogd. There's no log file now.
* Output of jobs, if any, is mailed to the user.
* Added command line options: -s -f -n -d -q -u -V -h. See the manpage.
* Specific jobs can now be selected on the command line.
* Added SIGUSR1 handling, to cleanly stop execution.
* Jobs will now be started with their current directory set to the home
of the user running Anacron (usually root).

42
anacron/Makemodule.am Normal file
View File

@@ -0,0 +1,42 @@
# Makefile.am - two binaries crond and crontab
if ANACRON
sbin_PROGRAMS += anacron/anacron
anacron_anacron_SOURCES = \
anacron-paths.h \
anacron/global.h \
anacron/gregor.c \
anacron/gregor.h \
anacron/lock.c \
anacron/log.c \
anacron/main.c \
anacron/matchrx.c \
anacron/matchrx.h \
anacron/readtab.c \
anacron/runjob.c \
cronie_common.c
common_nodist += anacron-paths.h
nodist_anacron_anacron_SOURCES = $(common_nodist)
BUILT_SOURCES += $(common_nodist)
if NEED_OBSTACK
anacron_anacron_SOURCES += obstack/obstack.c
endif
anacron_anacron_LDADD = $(LIBSELINUX) $(LIBPAM) $(LIBAUDIT)
# This header contains all the paths.
# If they are configurable, they are declared in configure script.
# Depends on this Makefile, because it uses make variables.
CLEANFILES += anacron-paths.h
anacron-paths.h: Makefile
@echo 'creating $@'
@sed >$@ 's/ *\\$$//' <<\END #\
/* This file has been automatically generated. Do not edit. */ \
\
#ifndef _ANACRON_PATHS_H_ \
#define _ANACRON_PATHS_H_ \
#define ANACRON_SPOOL_DIR "$(ANACRON_SPOOL_DIR)" \
#define ANACRONTAB "$(ANACRONTAB)" \
#endif /* _ANACRON_PATHS_H_ */ \
END
endif

164
anacron/global.h Normal file
View File

@@ -0,0 +1,164 @@
/*
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>
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.
*/
#ifndef _ANACRON_GLOBAL_H
#define _ANACRON_GLOBAL_H
/* Syslog facility and priorities messages will be logged to (see syslog(3)).
* If you change these, please update the man page. */
#define SYSLOG_FACILITY LOG_CRON
#define EXPLAIN_LEVEL LOG_NOTICE /* informational messages */
#define COMPLAIN_LEVEL LOG_ERR /* error messages */
#define DEBUG_LEVEL LOG_DEBUG /* only used when DEBUG is defined */
/* Mail interface. (All MTAs should supply this command) */
#define SENDMAIL "/usr/sbin/sendmail"
/* End of user-configurable section */
#define FAILURE_EXIT 1
#define MAX_MSG 150
#include <signal.h>
#include <time.h>
#include "anacron-paths.h"
#include "cronie_common.h"
/* Some declarations */
struct env_rec1 {
char *assign;
struct env_rec1 *next;
};
typedef struct env_rec1 env_rec;
struct job_rec1 {
int period;
int named_period;
int delay;
char *ident;
char *command;
char *mailto;
int no_mail_output;
int tab_line;
int arg_num;
int timestamp_fd;
int input_fd;
int output_fd;
off_t mail_header_size;
pid_t job_pid;
pid_t mailer_pid;
int drop_job;
struct job_rec1 *next;
env_rec *prev_env_rec;
};
typedef struct job_rec1 job_rec;
/* Global variables */
extern pid_t primary_pid;
extern char *program_name;
extern char *anacrontab;
extern char *spooldir;
extern int old_umask;
extern sigset_t old_sigmask;
extern int serialize,force,update_only,now,no_daemon,quiet,testing_only;
extern int day_now;
extern int year,month,day_of_month;
extern int in_background;
extern job_rec *first_job_rec;
extern env_rec *first_env_rec;
extern char **job_args;
extern int job_nargs;
extern int njobs;
extern job_rec **job_array;
extern int running_jobs,running_mailers;
extern int complaints;
extern time_t start_sec;
/* time ranges for START_HOURS_RANGE */
extern int range_start;
extern int range_stop;
/* preferred hour for jobs */
extern int preferred_hour;
/* Function prototypes */
/* main.c */
int xopen(int fd, const char *file_name, int flags);
void xclose(int fd);
pid_t xfork(void);
#ifdef __GNUC__
#define PRINTF_FORMAT(n, m) \
__attribute__ ((format (printf, n, m)))
#else
#define PRINTF_FORMAT(n, m)
#endif
/* log.c */
void explain(const char *fmt, ...)PRINTF_FORMAT(1,2);
void explain_e(const char *fmt, ...)PRINTF_FORMAT(1,2);
void complain(const char *fmt, ...)PRINTF_FORMAT(1,2);
void complain_e(const char *fmt, ...)PRINTF_FORMAT(1,2);
void die(const char *fmt, ...)PRINTF_FORMAT(1,2) ATTRIBUTE_NORETURN;
void die_e(const char *fmt, ...)PRINTF_FORMAT(1,2) ATTRIBUTE_NORETURN;
void xdebug(const char *fmt, ...)PRINTF_FORMAT(1,2);
void xdebug_e(const char *fmt, ...)PRINTF_FORMAT(1,2);
void xcloselog(void);
#ifdef DEBUG
#define Debug(args) xdebug args
#define Debug_e(args) xdebug_e args
#else /* not DEBUG */
#define Debug(args) (void)(0)
#define Debug_e(args) (void)(0)
#endif /* not DEBUG */
/* readtab.c */
void read_tab(int cwd);
void arrange_jobs(void);
/* lock.c */
int consider_job(job_rec *jr);
void unlock(job_rec *jr);
void update_timestamp(job_rec *jr);
void fake_job(job_rec *jr);
/* runjob.c */
void tend_children();
void launch_job(job_rec *jr);
#endif

181
anacron/gregor.c Normal file
View File

@@ -0,0 +1,181 @@
/*
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>
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 <limits.h>
#include <time.h>
#include "gregor.h"
static const int
days_in_month[] = {
31, /* Jan */
28, /* Feb (non-leap) */
31, /* Mar */
30, /* Apr */
31, /* May */
30, /* Jun */
31, /* Jul */
31, /* Aug */
30, /* Sep */
31, /* Oct */
30, /* Nov */
31 /* Dec */
};
static int leap(int year);
int
day_num(int year, int month, int day)
/* Return the "day number" of the date year-month-day according to the
* "proleptic Gregorian calendar".
* If the given date is invalid, return -1.
*
* Here, "day number" is defined as the number of days since December 31,
* 1 B.C. (Gregorian). (January 1, 1 A.D. is day number 1 etc...)
*
* The Gregorian calendar was instituted by Pope Gregory XIII in 1582,
* and has gradually spread to become the international standard calendar.
* The proleptic Gregorian calendar is formed by projecting the date system
* of the Gregorian calendar to dates before its adoption.
*
* For more details, see:
* http://astro.nmsu.edu/~lhuber/leaphist.html
* http://www.magnet.ch/serendipity/hermetic/cal_stud/cal_art.htm
* and your local library.
*/
{
int dn;
int i;
int isleap; /* save three calls to leap() */
/* Some validity checks */
/* we don't deal with B.C. years here */
if (year < 1) return - 1;
/* conservative overflow estimate */
if (year > (INT_MAX / 366)) return - 1;
if (month > 12 || month < 1) return - 1;
if (day < 1) return - 1;
isleap = leap(year);
if (month != 2) {
if(day > days_in_month[month - 1]) return - 1;
}
else if ((isleap && day > 29) || (!isleap && day > 28))
return - 1;
/* First calculate the day number of December 31 last year */
/* save us from doing (year - 1) over and over */
i = year - 1;
/* 365 days in a "regular" year + number of leap days */
dn = (i * 365) + ((i / 4) - (i / 100) + (i / 400));
/* Now, day number of the last day of the previous month */
for (i = month - 1; i > 0; --i)
dn += days_in_month[i - 1];
/* Add 29 February ? */
if (month > 2 && isleap) ++dn;
/* How many days into month are we */
dn += day;
return dn;
}
static int
leap(int year)
/* Is this a leap year ? */
{
/* every year exactly divisible by 4 is "leap" */
/* unless it is exactly divisible by 100 */
/* but not by 400 */
return (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0));
}
int
days_last_month (void)
/* How many days did last month have? */
{
struct tm time_record;
time_t current_time;
time (&current_time);
localtime_r (&current_time, &time_record);
switch (time_record.tm_mon) {
case 0: return days_in_month[11];
case 2: return days_in_month[1] + (leap (time_record.tm_year + 1900) ? 1 : 0);
default: return days_in_month[time_record.tm_mon - 1];
}
}
int
days_this_month (void)
/* How many days does this month have? */
{
struct tm time_record;
time_t current_time;
time (&current_time);
localtime_r (&current_time, &time_record);
switch (time_record.tm_mon) {
case 1: return days_in_month[1] + (leap (time_record.tm_year + 1900) ? 1 : 0);
default: return days_in_month[time_record.tm_mon];
}
}
int
days_last_year (void)
/* How many days this last year have? */
{
struct tm time_record;
time_t current_time;
time (&current_time);
localtime_r (&current_time, &time_record);
if (leap(time_record.tm_year - 1 + 1900)) {
return 366;
}
return 365;
}
int
days_this_year (void)
/* How many days does this year have */
{
struct tm time_record;
time_t current_time;
time (&current_time);
localtime_r (&current_time, &time_record);
if (leap(time_record.tm_year + 1900)) {
return 366;
}
return 365;
}

30
anacron/gregor.h Normal file
View File

@@ -0,0 +1,30 @@
/*
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>
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.
*/
int day_num(int year, int month, int day);
int days_last_month (void);
int days_this_month (void);
int days_last_year (void);
int days_this_year (void);

217
anacron/lock.c Normal file
View File

@@ -0,0 +1,217 @@
/*
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>
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.
*/
/* Lock and timestamp management
*/
#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include "global.h"
#include "gregor.h"
static void
open_tsfile(job_rec *jr)
/* Open the timestamp file for job jr */
{
jr->timestamp_fd = open(jr->ident, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
if (jr->timestamp_fd == -1)
die_e("Can't open timestamp file for job %s", jr->ident);
fcntl(jr->timestamp_fd, F_SETFD, 1); /* set close-on-exec flag */
/* We want to own this file, and set its mode to 0600. This is necessary
* in order to prevent other users from putting locks on it. */
if (fchown(jr->timestamp_fd, getuid(), getgid()))
die_e("Can't chown timestamp file %s", jr->ident);
if (fchmod(jr->timestamp_fd, S_IRUSR | S_IWUSR))
die_e("Can't chmod timestamp file %s", jr->ident);
}
static int
lock_file(int fd)
/* Attempt to put an exclusive fcntl() lock on file "fd"
* Return 1 on success, 0 on failure.
*/
{
int r;
struct flock sfl;
sfl.l_type = F_WRLCK;
sfl.l_start = 0;
sfl.l_whence = SEEK_SET;
sfl.l_len = 0; /* we lock all the file */
errno = 0;
r = fcntl(fd, F_SETLK, &sfl);
if (r != -1) return 1;
if (errno != EACCES && errno != EAGAIN)
die_e("fcntl() error");
return 0;
}
int
consider_job(job_rec *jr)
/* Check the timestamp of the job. If "its time has come", lock the job
* and return 1, if it's too early, or we can't get the lock, return 0.
*/
{
char timestamp[9];
int ts_year, ts_month, ts_day, dn;
ssize_t b;
open_tsfile(jr);
/* read timestamp */
b = read(jr->timestamp_fd, timestamp, 8);
if (b == -1) die_e("Error reading timestamp file %s", jr->ident);
timestamp[8] = 0;
/* is it too early? */
if (!force && b == 8)
{
int day_delta;
time_t jobtime;
struct tm *t;
if (sscanf(timestamp, "%4d%2d%2d", &ts_year, &ts_month, &ts_day) == 3)
dn = day_num(ts_year, ts_month, ts_day);
else
dn = 0;
day_delta = day_now - dn;
/*
* if day_delta is negative, we assume there was a clock skew
* and re-run any affected jobs
* otherwise we check if the job's time has come
*/
if (day_delta >= 0 && day_delta < jr->period)
{
/* yes, skip job */
xclose(jr->timestamp_fd);
return 0;
}
/*
* Check to see if it's a named period, in which case we need
* to figure it out.
*/
if (jr->named_period)
{
int period = 0, bypass = 0;
switch (jr->named_period)
{
case 1: /* monthly */
period = days_last_month ();
bypass = days_this_month ();
break;
case 2: /* yearly, annually */
period = days_last_year ();
bypass = days_this_year ();
break;
case 3: /* daily */
period = 1;
bypass = 1;
break;
case 4: /* weekly */
period = 7;
bypass = 7;
break;
default:
die ("Unknown named period for %s (%d)", jr->ident, jr->named_period);
}
printf ("Checking against %d with %d\n", day_delta, period);
if (day_delta < period && day_delta != bypass)
{
/* Job is still too young */
xclose (jr->timestamp_fd);
return 0;
}
}
jobtime = start_sec + jr->delay * 60;
t = localtime(&jobtime);
if (!now && preferred_hour != -1 && t->tm_hour != preferred_hour) {
Debug(("The job's %s preferred hour %d was missed, skipping the job.", jr->ident, preferred_hour));
xclose (jr->timestamp_fd);
return 0;
}
if (!now && range_start != -1 && range_stop != -1 &&
(t->tm_hour < range_start || t->tm_hour >= range_stop))
{
Debug(("The job `%s' falls out of the %02d:00-%02d:00 hours range, skipping.",
jr->ident, range_start, range_stop));
xclose (jr->timestamp_fd);
return 0;
}
}
/* no! try to grab the lock */
if (lock_file(jr->timestamp_fd)) return 1; /* success */
/* didn't get lock */
xclose(jr->timestamp_fd);
explain("Job `%s' locked by another anacron - skipping", jr->ident);
return 0;
}
void
unlock(job_rec *jr)
{
xclose(jr->timestamp_fd);
}
void
update_timestamp(job_rec *jr)
/* We write the date "now". "Now" can be either the time when anacron
* started, or the time when the job finished.
* I'm not quite sure which is more "right", but I've decided on the first
* option.
* Note that this is not the way it was with anacron 1.0.3 to 1.0.7.
*/
{
char stamp[10];
snprintf(stamp, 10, "%04d%02d%02d\n", year, month, day_of_month);
if (lseek(jr->timestamp_fd, 0, SEEK_SET))
die_e("Can't lseek timestamp file for job %s", jr->ident);
if (write(jr->timestamp_fd, stamp, 9) != 9)
die_e("Can't write timestamp file for job %s", jr->ident);
if (ftruncate(jr->timestamp_fd, 9))
die_e("ftruncate error");
}
void
fake_job(job_rec *jr)
/* We don't bother with any locking here. There's no point. */
{
open_tsfile(jr);
update_timestamp(jr);
xclose(jr->timestamp_fd);
}

230
anacron/log.c Normal file
View File

@@ -0,0 +1,230 @@
/*
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>
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.
*/
/* Error logging
*
* We have two levels of logging (plus debugging if DEBUG is defined):
* "explain" level for informational messages, and "complain" level for errors.
*
* We log everything to syslog, see the top of global.h for relevant
* definitions.
*
* Stderr gets "complain" messages when we're in the foreground,
* and "explain" messages when we're in the foreground, and not "quiet".
*/
#include <unistd.h>
#include <syslog.h>
#include <stdio.h>
#include <stdarg.h>
#include <errno.h>
#include <signal.h>
#include <sys/types.h>
#include <string.h>
#include <stdlib.h>
#include "global.h"
static char truncated[] = " (truncated)";
static char msg[MAX_MSG + 1];
static int log_open = 0;
/* Number of complaints that we've seen */
int complaints = 0;
static void
xopenlog(void)
{
if (!log_open)
{
openlog(program_name, LOG_PID, SYSLOG_FACILITY);
log_open = 1;
}
}
void
xcloselog(void)
{
if (log_open) closelog();
log_open = 0;
}
static void
make_msg(const char *fmt, va_list args)
/* Construct the message string from its parts */
{
int len;
/* There's some confusion in the documentation about what vsnprintf
* returns when the buffer overflows. Hmmm... */
len = vsnprintf(msg, sizeof(msg), fmt, args);
if (len < 0) {
strncpy(msg, "(vsnprintf failed)", sizeof(msg));
msg[sizeof(msg) - 1] = '\0';
return;
}
if ((size_t) len >= sizeof(msg) - 1)
strcpy(msg + sizeof(msg) - sizeof(truncated), truncated);
}
static void
slog(int priority, const char *fmt, va_list args)
/* Log a message, described by "fmt" and "args", with the specified
* "priority". */
{
make_msg(fmt, args);
xopenlog();
syslog(priority, "%s", msg);
if (!in_background)
{
if (priority == EXPLAIN_LEVEL && !quiet)
fprintf(stderr, "%s\n", msg);
else if (priority == COMPLAIN_LEVEL)
fprintf(stderr, "%s: %s\n", program_name, msg);
}
}
static void
log_e(int priority, const char *fmt, va_list args)
/* Same as slog(), but also appends an error description corresponding
* to "errno". */
{
int saved_errno;
saved_errno = errno;
make_msg(fmt, args);
xopenlog();
syslog(priority, "%s: %s", msg, strerror(saved_errno));
if (!in_background)
{
if (priority == EXPLAIN_LEVEL && !quiet)
fprintf(stderr, "%s: %s\n", msg, strerror(saved_errno));
else if (priority == COMPLAIN_LEVEL)
fprintf(stderr, "%s: %s: %s\n",
program_name, msg, strerror(saved_errno));
}
}
void
explain(const char *fmt, ...)
/* Log an "explain" level message */
{
va_list args;
va_start(args, fmt);
slog(EXPLAIN_LEVEL, fmt, args);
va_end(args);
}
void
explain_e(const char *fmt, ...)
/* Log an "explain" level message, with an error description */
{
va_list args;
va_start(args, fmt);
log_e(EXPLAIN_LEVEL, fmt, args);
va_end(args);
}
void
complain(const char *fmt, ...)
/* Log a "complain" level message */
{
va_list args;
va_start(args, fmt);
slog(COMPLAIN_LEVEL, fmt, args);
va_end(args);
complaints += 1;
}
void
complain_e(const char *fmt, ...)
/* Log a "complain" level message, with an error description */
{
va_list args;
va_start(args, fmt);
log_e(COMPLAIN_LEVEL, fmt, args);
va_end(args);
complaints += 1;
}
void
die(const char *fmt, ...)
/* Log a "complain" level message, and exit */
{
va_list args;
va_start(args, fmt);
slog(COMPLAIN_LEVEL, fmt, args);
va_end(args);
if (getpid() == primary_pid) complain("Aborted");
exit(FAILURE_EXIT);
}
void
die_e(const char *fmt, ...)
/* Log a "complain" level message, with an error description, and exit */
{
va_list args;
va_start(args, fmt);
log_e(COMPLAIN_LEVEL, fmt, args);
va_end(args);
if (getpid() == primary_pid) complain("Aborted");
exit(FAILURE_EXIT);
}
#ifdef DEBUG
/* These are called through the Debug() and Debug_e() macros, defined
* in global.h */
void
xdebug(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
slog(DEBUG_LEVEL, fmt, args);
va_end(args);
}
void
xdebug_e(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
log_e(DEBUG_LEVEL, fmt, args);
va_end(args);
}
#endif /* DEBUG */

517
anacron/main.c Normal file
View File

@@ -0,0 +1,517 @@
/*
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>
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 "config.h"
#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"
#include "cronie_common.h"
pid_t primary_pid;
int day_now;
int year, month, day_of_month; /* date anacron started */
char *program_name;
char *anacrontab = NULL;
char *spooldir = NULL;
int serialize, force, update_only, now,
no_daemon, quiet, testing_only; /* command-line options */
char **job_args; /* vector of "job" command-line arguments */
int job_nargs; /* number of these */
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;
int preferred_hour = -1;
static void
print_version(void)
{
printf("Anacron from project %s\n"
"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>."
"\n\n", PACKAGE_STRING);
}
static void
print_usage(void)
{
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");
}
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':
free(anacrontab);
anacrontab = strdup(optarg);
break;
case 'T':
testing_only = 1;
break;
case 'S':
free(spooldir);
spooldir = strdup(optarg);
break;
case 'V':
print_version();
exit(EXIT_SUCCESS);
case 'h':
print_usage();
exit(EXIT_SUCCESS);
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: `*' */
job_nargs = 1;
job_args = &defarg;
}
else
{
job_nargs = argc - optind;
job_args = argv + optind;
}
}
pid_t
xfork(void)
/* 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
go_background(void)
/* Become a daemon. The foreground process exits successfully. */
{
pid_t pid;
/* stdin is already closed */
xclose(STDOUT_FILENO);
/* coverity[leaked_handle] fd 1 closed automatically */
xopen(STDOUT_FILENO, "/dev/null", O_WRONLY);
xclose(STDERR_FILENO);
/* coverity[leaked_handle] fd 2 closed automatically */
xopen(STDERR_FILENO, "/dev/null", O_WRONLY);
pid = xfork();
if (pid != 0)
{
/* parent */
exit(EXIT_SUCCESS);
}
else
{
/* child */
primary_pid = getpid();
if (setsid() == -1) die_e("setsid() error");
in_background = 1;
}
}
static void
handle_sigalrm(int unused ATTRIBUTE_UNUSED)
{
got_sigalrm = 1;
}
static void
handle_sigchld(int unused ATTRIBUTE_UNUSED)
{
got_sigchld = 1;
}
static void
handle_sigusr1(int unused ATTRIBUTE_UNUSED)
{
got_sigusr1 = 1;
}
static void
set_signal_handling(void)
/* 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
wait_signal(void)
/* 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
wait_children(void)
/* 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
orderly_termination(void)
/* Execution is diverted here, when we get SIGUSR1 */
{
explain("Received SIGUSR1");
got_sigusr1 = 0;
wait_children();
explain("Exited");
exit(EXIT_SUCCESS);
}
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
wait_jobs(void)
/* 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
record_start_time(void)
{
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);
}
static unsigned int
time_till(job_rec *jr)
/* Return the number of seconds that we have to wait until it's time
* to start job jr.
*/
{
time_t tj, tn;
if (now) return 0;
tn = time(NULL);
tj = start_sec + (time_t)jr->delay * 60;
if (tj < tn) return 0;
if (tj - tn > 3600*24)
{
explain("System time manipulation detected, job `%s' will run immediately",
jr->ident);
return 0;
}
return (unsigned int)(tj - tn);
}
static void
fake_jobs(void)
{
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
explain_intentions(void)
{
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.");
srandom((unsigned int)(getpid() + tv.tv_usec));
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");
xclose(STDIN_FILENO);
xopen(STDIN_FILENO, "/dev/null", O_RDONLY);
if (!no_daemon && !testing_only)
go_background();
else
primary_pid = getpid();
record_start_time();
read_tab(cwd);
close(cwd);
arrange_jobs();
if (testing_only)
{
if (complaints) exit (EXIT_FAILURE);
exit (EXIT_SUCCESS);
}
if (update_only)
{
fake_jobs();
exit(EXIT_SUCCESS);
}
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");
exit(EXIT_SUCCESS);
}

92
anacron/matchrx.c Normal file
View File

@@ -0,0 +1,92 @@
/*
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 <stdio.h>
#include <regex.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include "matchrx.h"
int
match_rx(const char *rx, char *string, unsigned int n_sub, /* char **substrings */...)
/* Return 1 if the regular expression "*rx" matches the string "*string",
* 0 if not, -1 on error.
* "Extended" regular expressions are used.
* Additionally, there should be "n_sub" "substrings" arguments. These,
* if not NULL, and if the match succeeds are set to point to the
* corresponding substrings of the regexp.
* The original string is changed, and the substrings must not overlap,
* or even be directly adjacent.
* This is not the most efficient, or elegant way of doing this.
*/
{
int r;
unsigned int n;
regex_t crx;
va_list va;
char **substring;
regmatch_t *sub_offsets;
sub_offsets = malloc(sizeof(regmatch_t) * (n_sub + 1));
if (sub_offsets == NULL)
return -1;
memset(sub_offsets, 0, sizeof(regmatch_t) * (n_sub + 1));
if (regcomp(&crx, rx, REG_EXTENDED)) {
free(sub_offsets);
return -1;
}
r = regexec(&crx, string, n_sub + 1, sub_offsets, 0);
if (r != 0 && r != REG_NOMATCH) {
free(sub_offsets);
return -1;
}
regfree(&crx);
if (r == REG_NOMATCH) {
free(sub_offsets);
return 0;
}
va_start(va, n_sub);
n = 1;
while (n <= n_sub)
{
substring = va_arg(va, char**);
if (substring != NULL)
{
if (sub_offsets[n].rm_so == -1) {
va_end(va);
free(sub_offsets);
return - 1;
}
*substring = string + sub_offsets[n].rm_so;
*(string + sub_offsets[n].rm_eo) = 0;
}
n++;
}
va_end(va);
free(sub_offsets);
return 1;
}

26
anacron/matchrx.h Normal file
View File

@@ -0,0 +1,26 @@
/*
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.
*/
int match_rx(const char *rx, char *string,
unsigned int n_sub, /* char **substrings */...);

451
anacron/readtab.c Normal file
View File

@@ -0,0 +1,451 @@
/*
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>
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.
*/
/* /etc/anacrontab parsing, and job sorting
*/
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <obstack.h>
#include <limits.h>
#include <fnmatch.h>
#include <unistd.h>
#include <signal.h>
#include "global.h"
#include "matchrx.h"
static struct obstack input_o; /* holds input line */
static struct obstack tab_o; /* holds processed data read from anacrontab */
static FILE *tab;
job_rec **job_array;
int njobs; /* number of jobs to run */
static int jobs_read; /* number of jobs read */
static int line_num; /* current line in anacrontab */
static job_rec *last_job_rec; /* last job stored in memory, at the moment */
static env_rec *last_env_rec; /* last environment assignment stored */
static int random_number = 0;
/* some definitions for the obstack macros */
#define obstack_chunk_alloc xmalloc
#define obstack_chunk_free free
static void *
xmalloc (size_t size)
/* Just like standard malloc(), only never returns NULL. */
{
void * ptr;
ptr = malloc(size);
if (ptr == NULL)
die("Memory exhausted");
return ptr;
}
static int
conv2int(const char *s)
/* Return the int or -1 on over/under-flow
*/
{
long l;
errno = 0;
l = strtol(s, NULL, 10);
/* we use negative as error, so I am really returning unsigned int */
if (errno == ERANGE || l < 0 || l > INT_MAX) return - 1;
return (int)l;
}
static char *
read_tab_line (void)
/* Read one line and return a pointer to it.
Return NULL if no more lines.
*/
{
int c, prev=0;
if (feof(tab)) return NULL;
while (1)
{
c = getc(tab);
if ((c == '\n' && prev != '\\') || c == EOF)
{
if (0 != prev) obstack_1grow(&input_o, (char)prev);
break;
}
if (('\\' != prev || c != '\n') && 0 != prev && '\n' != prev) obstack_1grow(&input_o, (char)prev);
else if ('\n' == prev) obstack_1grow(&input_o, ' ');
prev = c;
}
if (ferror(tab)) die_e("Error reading %s", anacrontab);
obstack_1grow(&input_o, '\0');
return obstack_finish(&input_o);
}
static int
job_arg_num(const char *ident)
/* Return the command-line-argument number referring to this job-identifier.
* If it isn't specified, return -1.
*/
{
int i, r;
for (i = 0; i < job_nargs; i++)
{
r = fnmatch(job_args[i], ident, 0);
if (r == 0) return i;
if (r != FNM_NOMATCH) die("fnmatch() error");
}
return - 1;
}
static void
register_env(const char *env_var, const char *value)
/* Store the environment assignment "env_var"="value" */
{
env_rec *er;
int var_len, val_len;
var_len = (int)strlen(env_var);
val_len = (int)strlen(value);
if (!var_len) {
return;
}
er = obstack_alloc(&tab_o, sizeof(env_rec));
if (er == NULL) {
die_e("Cannot allocate memory.");
}
er->assign = obstack_alloc(&tab_o, var_len + 1 + val_len + 1);
if (er->assign == NULL) {
die_e("Cannot allocate memory.");
}
strcpy(er->assign, env_var);
er->assign[var_len] = '=';
strcpy(er->assign + var_len + 1, value);
er->assign[var_len + 1 + val_len] = 0;
if (last_env_rec != NULL) last_env_rec->next = er;
else first_env_rec = er;
last_env_rec = er;
Debug(("on line %d: %s", line_num, er->assign));
}
static void
register_job(const char *periods, const char *delays,
const char *ident, char *command)
/* Store a job definition */
{
int period, delay;
job_rec *jr;
int ident_len, command_len;
ident_len = (int)strlen(ident);
command_len = (int)strlen(command);
jobs_read++;
period = conv2int(periods);
delay = conv2int(delays);
if (period < 0 || delay < 0)
{
complain("%s: number out of range on line %d, skipping",
anacrontab, line_num);
return;
}
jr = obstack_alloc(&tab_o, sizeof(job_rec));
if (jr == NULL) {
die_e("Cannot allocate memory.");
}
jr->period = period;
jr->named_period = 0;
delay += random_number;
jr->delay = delay;
jr->tab_line = line_num;
jr->ident = obstack_alloc(&tab_o, ident_len + 1);
if (jr->ident == NULL) {
die_e("Cannot allocate memory.");
}
strcpy(jr->ident, ident);
jr->arg_num = job_arg_num(ident);
jr->command = obstack_alloc(&tab_o, command_len + 1);
if (jr->command == NULL) {
die_e("Cannot allocate memory.");
}
strcpy(jr->command, command);
jr->job_pid = jr->mailer_pid = 0;
if (last_job_rec != NULL) last_job_rec->next = jr;
else first_job_rec = jr;
last_job_rec = jr;
jr->prev_env_rec = last_env_rec;
jr->next = NULL;
Debug(("Read job - period=%d, delay=%d, ident=%s, command=%s",
jr->period, jr->delay, jr->ident, jr->command));
}
static void
register_period_job(const char *periods, const char *delays,
const char *ident, char *command)
/* Store a job definition with a named period */
{
int delay;
job_rec *jr;
int ident_len, command_len;
ident_len = (int)strlen(ident);
command_len = (int)strlen(command);
jobs_read++;
delay = conv2int(delays);
if (delay < 0)
{
complain("%s: number out of range on line %d, skipping",
anacrontab, line_num);
return;
}
jr = obstack_alloc(&tab_o, sizeof(job_rec));
if (jr == NULL) {
die_e("Cannot allocate memory.");
}
if (!strncmp ("@monthly", periods, 8)) {
jr->named_period = 1;
} else if (!strncmp("@yearly", periods, 7) || !strncmp("@annually", periods, 9) || !strncmp(/* backwards compat misspelling */"@annualy", periods, 8)) {
jr->named_period = 2;
} else if (!strncmp ("@daily", periods, 6)) {
jr->named_period = 3;
} else if (!strncmp ("@weekly", periods, 7)) {
jr->named_period = 4;
} else {
complain("%s: Unknown named period on line %d, skipping",
anacrontab, line_num);
}
jr->period = 0;
delay += random_number;
jr->delay = delay;
jr->tab_line = line_num;
jr->ident = obstack_alloc(&tab_o, ident_len + 1);
if (jr->ident == NULL) {
die_e("Cannot allocate memory.");
}
strcpy(jr->ident, ident);
jr->arg_num = job_arg_num(ident);
jr->command = obstack_alloc(&tab_o, command_len + 1);
if (jr->command == NULL) {
die_e("Cannot allocate memory.");
}
strcpy(jr->command, command);
jr->job_pid = jr->mailer_pid = 0;
if (last_job_rec != NULL) last_job_rec->next = jr;
else first_job_rec = jr;
last_job_rec = jr;
jr->prev_env_rec = last_env_rec;
jr->next = NULL;
Debug(("Read job - period %d, delay=%d, ident%s, command=%s",
jr->named_period, jr->delay, jr->ident, jr->command));
}
static long int
unbiased_rand(long int max)
{
long int rn;
long int divisor;
divisor = RAND_MAX / (max + 1);
do {
rn = random() / divisor;
} while (rn > max);
return rn;
}
static void
parse_tab_line(char *line)
{
int r;
char *env_var;
char *value;
char *periods;
char *delays;
char *ident;
char *command;
char *from;
char *to;
char *pref_hour;
/* an empty line? */
r = match_rx("^[ \t]*($|#)", line, 0);
if (r == -1) goto reg_err;
if (r)
{
Debug(("line %d empty", line_num));
return;
}
/* an environment assignment? */
r = match_rx("^[ \t]*([^ \t=]+)[ \t]*=(.*)$", line, 2,
&env_var, &value);
if (r == -1) goto reg_err;
if (r)
{
if (strncmp(env_var, "START_HOURS_RANGE", 17) == 0)
{
r = match_rx("^([[:digit:]]+)-([[:digit:]]+)$", value, 2, &from, &to);
if (r == -1) goto reg_err;
if (r == 0) goto reg_invalid;
range_start = atoi(from);
range_stop = atoi(to);
if (range_stop < range_start) {
range_start = 0; range_stop = 0;
goto reg_invalid;
}
Debug(("Jobs will start in the %02d:00-%02d:00 range.", range_start, range_stop));
}
else if (strncmp(env_var, "RANDOM_DELAY", 12) == 0) {
r = match_rx("^([[:digit:]]+)$", value, 0);
if (r == -1) goto reg_err;
if (r == 0) goto reg_invalid;
random_number = (int)unbiased_rand(atoi(value));
Debug(("Randomized delay set: %d", random_number));
}
else if (strncmp(env_var, "PREFERRED_HOUR", 14) == 0) {
r = match_rx("^([[:digit:]]+)$", value, 1, &pref_hour);
if (r == -1) goto reg_err;
if (r) {
preferred_hour = atoi(pref_hour);
if ((preferred_hour < 0) || (preferred_hour > 24)) {
preferred_hour = -1;
goto reg_invalid;
}
}
}
register_env(env_var, value);
return;
}
/* a job? */
r = match_rx("^[ \t]*([[:digit:]]+)[ \t]+([[:digit:]]+)[ \t]+"
"([^ \t/]+)[ \t]+([^ \t].*)$",
line, 4, &periods, &delays, &ident, &command);
if (r == -1) goto reg_err;
if (r)
{
register_job(periods, delays, ident, command);
return;
}
/* A period job? */
r = match_rx("^[ \t]*(@[^ \t]+)[ \t]+([[:digit:]]+)[ \t]+"
"([^ \t/]+)[ \t]+([^ \t].*)$",
line, 4, &periods, &delays, &ident, &command);
if (r == -1) goto reg_err;
if (r)
{
register_period_job(periods, delays, ident, command);
return;
}
reg_invalid:
complain("Invalid syntax in %s on line %d - skipping this line",
anacrontab, line_num);
return;
reg_err:
die("Regex error reading %s", anacrontab);
}
void
read_tab(int cwd)
/* Read the anacrontab file into memory */
{
char *tab_line;
first_job_rec = last_job_rec = NULL;
first_env_rec = last_env_rec = NULL;
jobs_read = 0;
line_num = 0;
/* Open the anacrontab file */
if (fchdir(cwd)) die_e("Can't chdir to original cwd");
tab = fopen(anacrontab, "r");
if (chdir(spooldir)) die_e("Can't chdir to %s", spooldir);
if (tab == NULL) die_e("Error opening %s", anacrontab);
/* Initialize the obstacks */
obstack_init(&input_o);
obstack_init(&tab_o);
while ((tab_line = read_tab_line()) != NULL)
{
line_num++;
parse_tab_line(tab_line);
obstack_free(&input_o, tab_line);
}
if (fclose(tab)) die_e("Error closing %s", anacrontab);
}
static int
execution_order(const job_rec **job1, const job_rec **job2)
/* Comparison function for sorting the jobs.
*/
{
int d;
d = (*job1)->arg_num - (*job2)->arg_num;
if (d != 0 && now) return d;
d = (*job1)->delay - (*job2)->delay;
if (d != 0) return d;
d = (*job1)->tab_line - (*job2)->tab_line;
return d;
}
void
arrange_jobs(void)
/* Make an array of pointers to jobs that are going to be executed,
* and arrange them in the order of execution.
* Also lock these jobs.
*/
{
job_rec *j;
j = first_job_rec;
njobs = 0;
while (j != NULL)
{
if (j->arg_num != -1 && (update_only || testing_only || consider_job(j)))
{
njobs++;
obstack_grow(&tab_o, &j, sizeof(j));
}
j = j->next;
}
job_array = obstack_finish(&tab_o);
/* sort the jobs */
qsort(job_array, (size_t)njobs, sizeof(*job_array),
(int (*)(const void *, const void *))execution_order);
}

438
anacron/runjob.c Normal file
View File

@@ -0,0 +1,438 @@
/*
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 */
{
/* If mail functionality is not disabled, pipe outputs to temp file */
if (!jr->no_mail_output) {
/* 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];
char *no_mail_output;
/* get hostname */
if (gethostname(hostname, 512)) {
strcpy (hostname,"unknown machine");
}
setup_env(jr);
no_mail_output = getenv("NO_MAIL_OUTPUT");
jr->no_mail_output = no_mail_output != NULL && *no_mail_output;
/* Set up email functionality if it isn't disabled */
if (!jr->no_mail_output) {
/* 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);
mail_output = !jr->no_mail_output && file_size(jr->output_fd) > jr->mail_header_size;
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);
/* output_fd and input_fd are only set if mail functionality is enabled */
if (!jr->no_mail_output) {
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++;
}
}