mirror of
https://github.com/pygos/cron
synced 2024-12-22 09:20:49 +01:00
Initial commit
Signed-off-by: David Oberhollenzer <david.oberhollenzer@tele2.at>
This commit is contained in:
commit
d2b3a983e1
15 changed files with 992 additions and 0 deletions
15
.gitignore
vendored
Normal file
15
.gitignore
vendored
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
*.o
|
||||||
|
.deps
|
||||||
|
.dirstamp
|
||||||
|
Makefile
|
||||||
|
Makefile.in
|
||||||
|
config.*
|
||||||
|
aclocal.m4
|
||||||
|
autom4te.cache/
|
||||||
|
compile
|
||||||
|
configure
|
||||||
|
depcomp
|
||||||
|
install-sh
|
||||||
|
missing
|
||||||
|
stamp-h1
|
||||||
|
gcrond
|
13
LICENSE
Normal file
13
LICENSE
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
Copyright (c) 2018 David Oberhollenzer <david.oberhollenzer@tele2.at>
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
13
Makefile.am
Normal file
13
Makefile.am
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
ACLOCAL_AMFLAGS = -I m4
|
||||||
|
|
||||||
|
AM_CPPFLAGS = -D_GNU_SOURCE
|
||||||
|
AM_CFLAGS = $(WARN_CFLAGS)
|
||||||
|
|
||||||
|
sbin_PROGRAMS = gcrond
|
||||||
|
|
||||||
|
gcrond_SOURCES = gcrond.c gcrond.h rdcron.c crontab.c cronscan.c rdline.c
|
||||||
|
|
||||||
|
crontabdir = @GCRONDIR@
|
||||||
|
crontab_DATA = crontab/0-example
|
||||||
|
|
||||||
|
EXTRA_DIST = crontab/0-example LICENSE README.md
|
122
README.md
Normal file
122
README.md
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
# About
|
||||||
|
|
||||||
|
This package contains a small cron implementation called `gcrond`.
|
||||||
|
|
||||||
|
It was written due to a perceived lack of a proper, simple cron
|
||||||
|
implementation. All other cron implementation I came across were either decade
|
||||||
|
old, abandoned pieces of horror ("Cool, I didn't even know that C syntax
|
||||||
|
allows this!") or hopelessly integrated into other, much larger projects (e.g.
|
||||||
|
absorbed by SystemD or in the case of OpenBSD cron, married to special OpenBSD
|
||||||
|
syscalls).
|
||||||
|
|
||||||
|
It was a fun little exercise and it seems to work so far. No idea about
|
||||||
|
standards compliance tough, the implementation was mostly written against
|
||||||
|
the Wikipedia article about Cron.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
The source code in this package is provided under the OpenBSD flavored ISC
|
||||||
|
license. So you can practically do as you wish, as long as you retain the
|
||||||
|
original copyright notice. The software is provided "as is" (as usual) with
|
||||||
|
no warranty whatsoever (e.g. it might actually do what it was designed for,
|
||||||
|
but it could just as well set your carpet on fire).
|
||||||
|
|
||||||
|
The sub directory `m4` contains third party macro files used by the build
|
||||||
|
system which may be subject to their own, respective licenses.
|
||||||
|
|
||||||
|
|
||||||
|
## Portability
|
||||||
|
|
||||||
|
The program in this package has been written for and tested on a GNU/Linux
|
||||||
|
system, so there may be some GNU-isms in there in addition to Linux specific
|
||||||
|
code. Depending on your target platform, some minor porting effort may be
|
||||||
|
required.
|
||||||
|
|
||||||
|
|
||||||
|
# Building and installing
|
||||||
|
|
||||||
|
This package uses autotools. If you downloaded a distribution tar ball, simply
|
||||||
|
run the `configure` script and then `make` after the Makefile has been
|
||||||
|
generated. A list of possible `configure` options can be viewed by running
|
||||||
|
`configure --help`.
|
||||||
|
|
||||||
|
If you really wish to do so, run `make install` to install the program on your
|
||||||
|
system.
|
||||||
|
|
||||||
|
When working with the git tree, run the `autogen.sh` script to generate the
|
||||||
|
configure script and friends.
|
||||||
|
|
||||||
|
|
||||||
|
# Crontab File Format
|
||||||
|
|
||||||
|
The cron daemon reads its configuration from all files it can find
|
||||||
|
in `/etc/crontab.d/` (exact path can be configured).
|
||||||
|
|
||||||
|
The files are read line by line. Empty lines or lines starting with '#' are
|
||||||
|
skipped.
|
||||||
|
|
||||||
|
Each non-empty line consists of the typical cron fields:
|
||||||
|
|
||||||
|
1. The `minute` field. Legal values are from 0 to 59.
|
||||||
|
2. The `hour` field. Legal values are from 0 to 23.
|
||||||
|
3. The `day of month` field. Legal values are from 1 to 31 (or fewer, depending
|
||||||
|
on the month.
|
||||||
|
4. The `month` field. Legal values are from 1 to 12 (January to December)
|
||||||
|
or the mnemonics `JAN`, `FEB`, `MAR`, `APR`, ...
|
||||||
|
5. The `day of week` field. Legal values are from 0 to 6 (Sunday to Saturday)
|
||||||
|
or the mnemonics `SUN`, `MON`, `TUE`, `WED`, ...
|
||||||
|
6. The command to execute.
|
||||||
|
|
||||||
|
|
||||||
|
The fields are separated by spaces. For the time matching fields, multiple
|
||||||
|
comma separated values can be specified (e.g. `MON,WED,FRI` for a job that
|
||||||
|
should run on Mondays, Wednesdays and Fridays).
|
||||||
|
|
||||||
|
The wild-card character `*` matches any legal value. An stepping can be
|
||||||
|
specified by appending `/` and then a stepping (e.g. for the minute field,
|
||||||
|
`*/5` would let a job run every five minutes).
|
||||||
|
|
||||||
|
A range of values can also be specified as `<lower>-<upper>`, for instance
|
||||||
|
`MON-FRI` would match every day from Monday to Friday (equivalent to `1-5`).
|
||||||
|
|
||||||
|
Intervals and specific values can be combined, for instance a day of month
|
||||||
|
field `*/7,13,25` would trigger once a week, starting from the first of the
|
||||||
|
month (1,7,14,21,28), but additionally include the 13th and the 25th. The
|
||||||
|
same could be expressed as `1-31/7,13,25`.
|
||||||
|
|
||||||
|
|
||||||
|
Instead of specifying a terse cron matching expression, the first five fields
|
||||||
|
can be replaced with one of the following mnemonics:
|
||||||
|
|
||||||
|
- `@yearly` or `@anually` is equivalent to `0 0 1 1 *`, i.e. 1st of January
|
||||||
|
at midnight
|
||||||
|
- `@monthly` is equivalent to `0 0 1 * *`, i.e. 1st of every month at midnight
|
||||||
|
- `@weekly` is equivalent to `0 0 * * 0`, i.e. every Sunday at midnight
|
||||||
|
- `@daily` is equivalent to `0 0 * * *`, i.e. every day at midnight
|
||||||
|
- `@hourly` is equivalent to `0 * * * *`, i.e. every first minute of the hour
|
||||||
|
|
||||||
|
Lastly, the command field is not broken down but passed to `/bin/sh -c`
|
||||||
|
*as is*.
|
||||||
|
|
||||||
|
|
||||||
|
# Security Considerations
|
||||||
|
|
||||||
|
The cron daemon currently has no means of specifying a user to run the jobs as,
|
||||||
|
so if cron runs as root, the jobs it starts do as well. Since by default it
|
||||||
|
reads its configuration from `/etc` which by default is only writable by root,
|
||||||
|
this shouldn't be too much of a problem when using cron for typical system
|
||||||
|
administration tasks.
|
||||||
|
|
||||||
|
If a job should run as another user, tools such as `su`, `runuser`, `setpriv`
|
||||||
|
et cetera need to be used.
|
||||||
|
|
||||||
|
# Possible Future Directions
|
||||||
|
|
||||||
|
The following things would be nice to have:
|
||||||
|
|
||||||
|
- decent logging for cron and the output of the jobs.
|
||||||
|
- cron jobs per user, e.g. scan `~/.crontab.d` or similar and run the collected
|
||||||
|
jobs as the respective user.
|
||||||
|
- timezone handling
|
||||||
|
- some usable strategy for handling time jumps, e.g. caused by a job that
|
||||||
|
syncs time with an NTP server on a system without RTC.
|
3
autogen.sh
Executable file
3
autogen.sh
Executable file
|
@ -0,0 +1,3 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
autoreconf --force --install --symlink
|
38
configure.ac
Normal file
38
configure.ac
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
AC_PREREQ([2.60])
|
||||||
|
|
||||||
|
AC_INIT([gcron], [0.1], [david.oberhollenzer@tele2.at], gcron)
|
||||||
|
AC_CONFIG_MACRO_DIR([m4])
|
||||||
|
AM_INIT_AUTOMAKE([foreign subdir-objects dist-xz])
|
||||||
|
AM_SILENT_RULES([yes])
|
||||||
|
AC_PROG_CC
|
||||||
|
AC_PROG_CC_C99
|
||||||
|
AC_PROG_INSTALL
|
||||||
|
AC_PROG_RANLIB
|
||||||
|
|
||||||
|
UL_WARN_ADD([-Wall])
|
||||||
|
UL_WARN_ADD([-Wextra])
|
||||||
|
UL_WARN_ADD([-Wunused])
|
||||||
|
UL_WARN_ADD([-Wmissing-prototypes])
|
||||||
|
UL_WARN_ADD([-Wmissing-declarations])
|
||||||
|
UL_WARN_ADD([-Wwrite-strings])
|
||||||
|
UL_WARN_ADD([-Wjump-misses-init])
|
||||||
|
UL_WARN_ADD([-Wuninitialized])
|
||||||
|
UL_WARN_ADD([-Winit-self])
|
||||||
|
UL_WARN_ADD([-Wlogical-op])
|
||||||
|
UL_WARN_ADD([-Wunused-but-set-parameter])
|
||||||
|
UL_WARN_ADD([-Wunused-but-set-variable])
|
||||||
|
UL_WARN_ADD([-Wunused-parameter])
|
||||||
|
UL_WARN_ADD([-Wunused-result])
|
||||||
|
UL_WARN_ADD([-Wunused-variable])
|
||||||
|
UL_WARN_ADD([-Wduplicated-cond])
|
||||||
|
UL_WARN_ADD([-Wduplicated-branches])
|
||||||
|
UL_WARN_ADD([-Wrestrict])
|
||||||
|
UL_WARN_ADD([-Wnull-dereference])
|
||||||
|
UL_WARN_ADD([-pedantic])
|
||||||
|
|
||||||
|
AC_SUBST([WARN_CFLAGS])
|
||||||
|
|
||||||
|
AC_CONFIG_HEADERS([config.h])
|
||||||
|
AC_DEFINE_DIR(GCRONDIR, sysconfdir/crontab.d, [crontab source directory])
|
||||||
|
|
||||||
|
AC_OUTPUT([Makefile])
|
56
cronscan.c
Normal file
56
cronscan.c
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
/* SPDX-License-Identifier: ISC */
|
||||||
|
#include "gcrond.h"
|
||||||
|
|
||||||
|
int cronscan(const char *directory, crontab_t **list)
|
||||||
|
{
|
||||||
|
crontab_t *cron, *tail = NULL;
|
||||||
|
struct dirent *ent;
|
||||||
|
int dfd, ret = 0;
|
||||||
|
DIR *dir;
|
||||||
|
|
||||||
|
dir = opendir(directory);
|
||||||
|
if (dir == NULL) {
|
||||||
|
perror(directory);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
dfd = dirfd(dir);
|
||||||
|
if (dfd < 0) {
|
||||||
|
perror(directory);
|
||||||
|
closedir(dir);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
errno = 0;
|
||||||
|
ent = readdir(dir);
|
||||||
|
|
||||||
|
if (ent == NULL) {
|
||||||
|
if (errno != 0) {
|
||||||
|
perror(directory);
|
||||||
|
ret = -1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, ".."))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
cron = rdcron(dfd, ent->d_name);
|
||||||
|
if (cron == NULL)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (tail == NULL) {
|
||||||
|
*list = cron;
|
||||||
|
tail = cron;
|
||||||
|
} else {
|
||||||
|
tail->next = cron;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (tail->next != NULL)
|
||||||
|
tail = tail->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
closedir(dir);
|
||||||
|
return ret;
|
||||||
|
}
|
61
crontab.c
Normal file
61
crontab.c
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
/* SPDX-License-Identifier: ISC */
|
||||||
|
#include "gcrond.h"
|
||||||
|
|
||||||
|
void cron_tm_to_mask(crontab_t *out, struct tm *t)
|
||||||
|
{
|
||||||
|
memset(out, 0, sizeof(*out));
|
||||||
|
out->minute = 1UL << ((unsigned long)t->tm_min);
|
||||||
|
out->hour = 1 << t->tm_hour;
|
||||||
|
out->dayofmonth = 1 << (t->tm_mday - 1);
|
||||||
|
out->month = 1 << t->tm_mon;
|
||||||
|
out->dayofweek = 1 << t->tm_wday;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool cron_should_run(const crontab_t *t, const crontab_t *mask)
|
||||||
|
{
|
||||||
|
if ((t->minute & mask->minute) == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if ((t->hour & mask->hour) == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if ((t->dayofmonth & mask->dayofmonth) == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if ((t->month & mask->month) == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if ((t->dayofweek & mask->dayofweek) == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void delcron(crontab_t *cron)
|
||||||
|
{
|
||||||
|
if (cron != NULL) {
|
||||||
|
free(cron->exec);
|
||||||
|
free(cron);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int runjob(crontab_t *tab)
|
||||||
|
{
|
||||||
|
pid_t pid;
|
||||||
|
|
||||||
|
if (tab->exec == NULL)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
pid = fork();
|
||||||
|
if (pid == -1) {
|
||||||
|
perror("fork");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pid != 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
execl("/bin/sh", "sh", "-c", tab->exec, (char *) 0);
|
||||||
|
perror("runnig shell interpreter");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
7
crontab/0-example
Normal file
7
crontab/0-example
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# +-------- minute (0 - 59)
|
||||||
|
# | +------ hour (0 - 23)
|
||||||
|
# | | +---- day of month (1 - 31)
|
||||||
|
# | | | +-- month (1 - 12)
|
||||||
|
# | | | | +-- day of week (0 - 6)
|
||||||
|
# | | | | |
|
||||||
|
# * * * * * command to execute
|
128
gcrond.c
Normal file
128
gcrond.c
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
/* SPDX-License-Identifier: ISC */
|
||||||
|
#include "gcrond.h"
|
||||||
|
|
||||||
|
static crontab_t *jobs;
|
||||||
|
static sig_atomic_t run = 1;
|
||||||
|
static sig_atomic_t rescan = 1;
|
||||||
|
|
||||||
|
static void read_config(void)
|
||||||
|
{
|
||||||
|
if (cronscan(GCRONDIR, &jobs)) {
|
||||||
|
fputs("Error reading configuration. Continuing anyway.\n",
|
||||||
|
stderr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cleanup_config(void)
|
||||||
|
{
|
||||||
|
crontab_t *t;
|
||||||
|
|
||||||
|
while (jobs != NULL) {
|
||||||
|
t = jobs;
|
||||||
|
jobs = jobs->next;
|
||||||
|
delcron(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int timeout_minutes(int minutes)
|
||||||
|
{
|
||||||
|
time_t now = time(NULL);
|
||||||
|
struct tm t;
|
||||||
|
|
||||||
|
localtime_r(&now, &t);
|
||||||
|
return minutes * 60 + 30 - t.tm_sec;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int calc_timeout(void)
|
||||||
|
{
|
||||||
|
time_t now = time(NULL), future;
|
||||||
|
struct tm tmstruct;
|
||||||
|
crontab_t mask, *t;
|
||||||
|
int minutes;
|
||||||
|
|
||||||
|
for (minutes = 0; minutes < 120; ++minutes) {
|
||||||
|
future = now + minutes * 60;
|
||||||
|
|
||||||
|
localtime_r(&future, &tmstruct);
|
||||||
|
cron_tm_to_mask(&mask, &tmstruct);
|
||||||
|
|
||||||
|
for (t = jobs; t != NULL; t = t->next) {
|
||||||
|
if (cron_should_run(t, &mask))
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out:
|
||||||
|
return timeout_minutes(minutes ? minutes : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void runjobs(void)
|
||||||
|
{
|
||||||
|
time_t now = time(NULL);
|
||||||
|
struct tm tmstruct;
|
||||||
|
crontab_t mask, *t;
|
||||||
|
|
||||||
|
localtime_r(&now, &tmstruct);
|
||||||
|
cron_tm_to_mask(&mask, &tmstruct);
|
||||||
|
|
||||||
|
for (t = jobs; t != NULL; t = t->next) {
|
||||||
|
if (cron_should_run(t, &mask))
|
||||||
|
runjob(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sighandler(int signo)
|
||||||
|
{
|
||||||
|
pid_t pid;
|
||||||
|
|
||||||
|
switch (signo) {
|
||||||
|
case SIGINT:
|
||||||
|
case SIGTERM:
|
||||||
|
run = 0;
|
||||||
|
break;
|
||||||
|
case SIGHUP:
|
||||||
|
rescan = 1;
|
||||||
|
break;
|
||||||
|
case SIGCHLD:
|
||||||
|
while ((pid = waitpid(-1, NULL, WNOHANG)) != -1)
|
||||||
|
;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
struct timespec stime;
|
||||||
|
struct sigaction act;
|
||||||
|
int timeout;
|
||||||
|
|
||||||
|
memset(&act, 0, sizeof(act));
|
||||||
|
act.sa_handler = sighandler;
|
||||||
|
sigaction(SIGINT, &act, NULL);
|
||||||
|
sigaction(SIGTERM, &act, NULL);
|
||||||
|
sigaction(SIGHUP, &act, NULL);
|
||||||
|
sigaction(SIGCHLD, &act, NULL);
|
||||||
|
|
||||||
|
while (run) {
|
||||||
|
if (rescan == 1) {
|
||||||
|
cleanup_config();
|
||||||
|
read_config();
|
||||||
|
timeout = timeout_minutes(1);
|
||||||
|
rescan = 0;
|
||||||
|
} else {
|
||||||
|
runjobs();
|
||||||
|
timeout = calc_timeout();
|
||||||
|
}
|
||||||
|
|
||||||
|
stime.tv_sec = timeout;
|
||||||
|
stime.tv_nsec = 0;
|
||||||
|
|
||||||
|
while (nanosleep(&stime, &stime) != 0 && run && !rescan) {
|
||||||
|
if (errno != EINTR) {
|
||||||
|
perror("nanosleep");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
66
gcrond.h
Normal file
66
gcrond.h
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
/* SPDX-License-Identifier: ISC */
|
||||||
|
#ifndef GCROND_H
|
||||||
|
#define GCROND_H
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
|
||||||
|
|
||||||
|
typedef struct crontab_t {
|
||||||
|
struct crontab_t *next;
|
||||||
|
char *exec;
|
||||||
|
|
||||||
|
uint64_t minute;
|
||||||
|
uint32_t hour;
|
||||||
|
uint32_t dayofmonth;
|
||||||
|
uint16_t month;
|
||||||
|
uint8_t dayofweek;
|
||||||
|
} crontab_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const char *filename; /* input file name */
|
||||||
|
size_t lineno; /* current line number */
|
||||||
|
FILE *fp;
|
||||||
|
char *line;
|
||||||
|
} rdline_t;
|
||||||
|
|
||||||
|
int rdline_init(rdline_t *t, int dirfd, const char *filename);
|
||||||
|
|
||||||
|
void rdline_complain(rdline_t *t, const char *msg, ...);
|
||||||
|
|
||||||
|
void rdline_cleanup(rdline_t *t);
|
||||||
|
|
||||||
|
int rdline(rdline_t *t);
|
||||||
|
|
||||||
|
crontab_t *rdcron(int dirfd, const char *filename);
|
||||||
|
|
||||||
|
void delcron(crontab_t *cron);
|
||||||
|
|
||||||
|
int cronscan(const char *directory, crontab_t **list);
|
||||||
|
|
||||||
|
void cron_tm_to_mask(crontab_t *out, struct tm *t);
|
||||||
|
|
||||||
|
bool cron_should_run(const crontab_t *t, const crontab_t *mask);
|
||||||
|
|
||||||
|
int runjob(crontab_t *tab);
|
||||||
|
|
||||||
|
#endif /* GCROND_H */
|
35
m4/ac_define_dir.m4
Normal file
35
m4/ac_define_dir.m4
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
dnl @synopsis AC_DEFINE_DIR(VARNAME, DIR [, DESCRIPTION])
|
||||||
|
dnl
|
||||||
|
dnl This macro sets VARNAME to the expansion of the DIR variable,
|
||||||
|
dnl taking care of fixing up ${prefix} and such.
|
||||||
|
dnl
|
||||||
|
dnl VARNAME is then offered as both an output variable and a C
|
||||||
|
dnl preprocessor symbol.
|
||||||
|
dnl
|
||||||
|
dnl Example:
|
||||||
|
dnl
|
||||||
|
dnl AC_DEFINE_DIR([DATADIR], [datadir], [Where data are placed to.])
|
||||||
|
dnl
|
||||||
|
dnl @category Misc
|
||||||
|
dnl @author Stepan Kasal <kasal@ucw.cz>
|
||||||
|
dnl @author Andreas Schwab <schwab@suse.de>
|
||||||
|
dnl @author Guido U. Draheim <guidod@gmx.de>
|
||||||
|
dnl @author Alexandre Oliva
|
||||||
|
dnl @version 2006-10-13
|
||||||
|
dnl @license AllPermissive
|
||||||
|
|
||||||
|
AC_DEFUN([AC_DEFINE_DIR], [
|
||||||
|
prefix_NONE=
|
||||||
|
exec_prefix_NONE=
|
||||||
|
test "x$prefix" = xNONE && prefix_NONE=yes && prefix=$ac_default_prefix
|
||||||
|
test "x$exec_prefix" = xNONE && exec_prefix_NONE=yes && exec_prefix=$prefix
|
||||||
|
dnl In Autoconf 2.60, ${datadir} refers to ${datarootdir}, which in turn
|
||||||
|
dnl refers to ${prefix}. Thus we have to use `eval' twice.
|
||||||
|
eval ac_define_dir="\"[$]$2\""
|
||||||
|
eval ac_define_dir="\"$ac_define_dir\""
|
||||||
|
AC_SUBST($1, "$ac_define_dir")
|
||||||
|
AC_DEFINE_UNQUOTED($1, "$ac_define_dir", [$3])
|
||||||
|
test "$prefix_NONE" && prefix=NONE
|
||||||
|
test "$exec_prefix_NONE" && exec_prefix=NONE
|
||||||
|
])
|
||||||
|
|
40
m4/compiler.m4
Normal file
40
m4/compiler.m4
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
dnl Copyright (C) 2008-2011 Free Software Foundation, Inc.
|
||||||
|
dnl This file is free software; the Free Software Foundation
|
||||||
|
dnl gives unlimited permission to copy and/or distribute it,
|
||||||
|
dnl with or without modifications, as long as this notice is preserved.
|
||||||
|
|
||||||
|
dnl From Simon Josefsson
|
||||||
|
dnl -- derivated from coreutils m4/warnings.m4
|
||||||
|
|
||||||
|
# UL_AS_VAR_APPEND(VAR, VALUE)
|
||||||
|
# ----------------------------
|
||||||
|
# Provide the functionality of AS_VAR_APPEND if Autoconf does not have it.
|
||||||
|
m4_ifdef([AS_VAR_APPEND],
|
||||||
|
[m4_copy([AS_VAR_APPEND], [UL_AS_VAR_APPEND])],
|
||||||
|
[m4_define([UL_AS_VAR_APPEND],
|
||||||
|
[AS_VAR_SET([$1], [AS_VAR_GET([$1])$2])])])
|
||||||
|
|
||||||
|
# UL_ADD_WARN(COMPILER_OPTION [, VARNAME])
|
||||||
|
# ------------------------
|
||||||
|
# Adds parameter to WARN_CFLAGS (or to $VARNAME) if the compiler supports it.
|
||||||
|
AC_DEFUN([UL_WARN_ADD], [
|
||||||
|
m4_define([warnvarname], m4_default([$2],WARN_CFLAGS))
|
||||||
|
AS_VAR_PUSHDEF([ul_Warn], [ul_cv_warn_$1])dnl
|
||||||
|
AC_CACHE_CHECK([whether compiler handles $1], m4_defn([ul_Warn]), [
|
||||||
|
# store AC_LANG_WERROR status, then turn it on
|
||||||
|
save_ac_[]_AC_LANG_ABBREV[]_werror_flag="${ac_[]_AC_LANG_ABBREV[]_werror_flag}"
|
||||||
|
AC_LANG_WERROR
|
||||||
|
|
||||||
|
ul_save_CPPFLAGS="$CPPFLAGS"
|
||||||
|
CPPFLAGS="-Werror ${CPPFLAGS} $1"
|
||||||
|
AC_PREPROC_IFELSE([AC_LANG_PROGRAM([])],
|
||||||
|
[AS_VAR_SET(ul_Warn, [yes])],
|
||||||
|
[AS_VAR_SET(ul_Warn, [no])])
|
||||||
|
# restore AC_LANG_WERROR
|
||||||
|
ac_[]_AC_LANG_ABBREV[]_werror_flag="${save_ac_[]_AC_LANG_ABBREV[]_werror_flag}"
|
||||||
|
|
||||||
|
CPPFLAGS="$ul_save_CPPFLAGS"
|
||||||
|
])
|
||||||
|
AS_VAR_IF(ul_Warn, [yes], [UL_AS_VAR_APPEND(warnvarname, [" $1"])])
|
||||||
|
])
|
||||||
|
|
318
rdcron.c
Normal file
318
rdcron.c
Normal file
|
@ -0,0 +1,318 @@
|
||||||
|
/* SPDX-License-Identifier: ISC */
|
||||||
|
#include "gcrond.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const char *name;
|
||||||
|
int value;
|
||||||
|
} enum_map_t;
|
||||||
|
|
||||||
|
static const enum_map_t weekday[] = {
|
||||||
|
{ "MON", 1 },
|
||||||
|
{ "TUE", 2 },
|
||||||
|
{ "WED", 3 },
|
||||||
|
{ "THU", 4 },
|
||||||
|
{ "FRI", 5 },
|
||||||
|
{ "SAT", 6 },
|
||||||
|
{ "SUN", 0 },
|
||||||
|
{ NULL, 0 },
|
||||||
|
};
|
||||||
|
|
||||||
|
static const enum_map_t month[] = {
|
||||||
|
{ "JAN", 1 },
|
||||||
|
{ "FEB", 2 },
|
||||||
|
{ "MAR", 3 },
|
||||||
|
{ "APR", 4 },
|
||||||
|
{ "MAY", 5 },
|
||||||
|
{ "JUN", 6 },
|
||||||
|
{ "JUL", 7 },
|
||||||
|
{ "AUG", 8 },
|
||||||
|
{ "SEP", 9 },
|
||||||
|
{ "OCT", 10 },
|
||||||
|
{ "NOV", 11 },
|
||||||
|
{ "DEC", 12 },
|
||||||
|
{ NULL, 0 },
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct {
|
||||||
|
const char *macro;
|
||||||
|
crontab_t tab;
|
||||||
|
} intervals[] = {
|
||||||
|
{
|
||||||
|
.macro = "yearly",
|
||||||
|
.tab = {
|
||||||
|
.minute = 0x01,
|
||||||
|
.hour = 0x01,
|
||||||
|
.dayofmonth = 0x01,
|
||||||
|
.month = 0x01,
|
||||||
|
.dayofweek = 0xFF
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
.macro = "annually",
|
||||||
|
.tab = {
|
||||||
|
.minute = 0x01,
|
||||||
|
.hour = 0x01,
|
||||||
|
.dayofmonth = 0x01,
|
||||||
|
.month = 0x01,
|
||||||
|
.dayofweek = 0xFF
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
.macro = "monthly",
|
||||||
|
.tab = {
|
||||||
|
.minute = 0x01,
|
||||||
|
.hour = 0x01,
|
||||||
|
.dayofmonth = 0x01,
|
||||||
|
.month = 0xFFFF,
|
||||||
|
.dayofweek = 0xFF
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
.macro = "weekly",
|
||||||
|
.tab = {
|
||||||
|
.minute = 0x01,
|
||||||
|
.hour = 0x01,
|
||||||
|
.dayofmonth = 0xFFFFFFFF,
|
||||||
|
.month = 0xFFFF,
|
||||||
|
.dayofweek = 0x01
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
.macro = "daily",
|
||||||
|
.tab = {
|
||||||
|
.minute = 0x01,
|
||||||
|
.hour = 0x01,
|
||||||
|
.dayofmonth = 0xFFFFFFFF,
|
||||||
|
.month = 0xFFFF,
|
||||||
|
.dayofweek = 0xFF
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
.macro = "hourly",
|
||||||
|
.tab = {
|
||||||
|
.minute = 0x01,
|
||||||
|
.hour = 0xFFFFFFFF,
|
||||||
|
.dayofmonth = 0xFFFFFFFF,
|
||||||
|
.month = 0xFFFF,
|
||||||
|
.dayofweek = 0xFF
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/*****************************************************************************/
|
||||||
|
|
||||||
|
static char *readnum(char *line, int *out, int minval, int maxval,
|
||||||
|
const enum_map_t *mnemonic, rdline_t *rd)
|
||||||
|
{
|
||||||
|
int i, temp, value = 0;
|
||||||
|
const enum_map_t *ev;
|
||||||
|
|
||||||
|
if (!isdigit(*line)) {
|
||||||
|
if (!mnemonic)
|
||||||
|
goto fail_mn;
|
||||||
|
|
||||||
|
for (i = 0; isalnum(line[i]); ++i)
|
||||||
|
;
|
||||||
|
if (i == 0)
|
||||||
|
goto fail_mn;
|
||||||
|
|
||||||
|
temp = line[i];
|
||||||
|
line[i] = '\0';
|
||||||
|
|
||||||
|
for (ev = mnemonic; ev->name != NULL; ++ev) {
|
||||||
|
if (!strcmp(line, mnemonic->name) == 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ev->name == NULL) {
|
||||||
|
rdline_complain(rd, "unexpected '%s'", line);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
line[i] = temp;
|
||||||
|
*out = ev->value;
|
||||||
|
return line + i;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (isdigit(*line)) {
|
||||||
|
i = ((*(line++)) - '0');
|
||||||
|
if (value > (maxval - i) / 10)
|
||||||
|
goto fail_of;
|
||||||
|
value = value * 10 + i;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value < minval)
|
||||||
|
goto fail_uf;
|
||||||
|
|
||||||
|
*out = value;
|
||||||
|
return line;
|
||||||
|
fail_of:
|
||||||
|
rdline_complain(rd, "value exceeds maximum (%d > %d)", value, maxval);
|
||||||
|
return NULL;
|
||||||
|
fail_uf:
|
||||||
|
rdline_complain(rd, "value too small (%d < %d)", value, minval);
|
||||||
|
return NULL;
|
||||||
|
fail_mn:
|
||||||
|
rdline_complain(rd, "expected numeric value");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *readfield(char *line, uint64_t *out, int minval, int maxval,
|
||||||
|
const enum_map_t *mnemonic, rdline_t *rd)
|
||||||
|
{
|
||||||
|
int value, endvalue, step;
|
||||||
|
uint64_t v = 0;
|
||||||
|
next:
|
||||||
|
if (*line == '*') {
|
||||||
|
++line;
|
||||||
|
value = minval;
|
||||||
|
endvalue = maxval;
|
||||||
|
} else {
|
||||||
|
line = readnum(line, &value, minval, maxval, mnemonic, rd);
|
||||||
|
if (!line)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
if (*line == '-') {
|
||||||
|
line = readnum(line + 1, &endvalue, minval, maxval,
|
||||||
|
mnemonic, rd);
|
||||||
|
if (!line)
|
||||||
|
goto fail;
|
||||||
|
} else {
|
||||||
|
endvalue = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (endvalue < value)
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*line == '/') {
|
||||||
|
line = readnum(line + 1, &step, 1, maxval + 1, NULL, rd);
|
||||||
|
if (!line)
|
||||||
|
goto fail;
|
||||||
|
} else {
|
||||||
|
step = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (value <= endvalue) {
|
||||||
|
v |= 1UL << (unsigned long)(value - minval);
|
||||||
|
value += step;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*line == ',') {
|
||||||
|
++line;
|
||||||
|
goto next;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*line != '\0' && !isspace(*line))
|
||||||
|
goto fail;
|
||||||
|
while (isspace(*line))
|
||||||
|
++line;
|
||||||
|
|
||||||
|
*out = v;
|
||||||
|
return line;
|
||||||
|
fail:
|
||||||
|
rdline_complain(rd, "invalid time range expression");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*****************************************************************************/
|
||||||
|
|
||||||
|
static char *cron_interval(crontab_t *cron, rdline_t *rd)
|
||||||
|
{
|
||||||
|
char *arg = rd->line;
|
||||||
|
size_t i, j;
|
||||||
|
|
||||||
|
if (*(arg++) != '@')
|
||||||
|
goto fail;
|
||||||
|
for (j = 0; isalpha(arg[j]); ++j)
|
||||||
|
;
|
||||||
|
if (j == 0 || !isspace(arg[j]))
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
for (i = 0; i < ARRAY_SIZE(intervals); ++i) {
|
||||||
|
if (strlen(intervals[i].macro) != j)
|
||||||
|
continue;
|
||||||
|
if (strncmp(intervals[i].macro, arg, j) == 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i == ARRAY_SIZE(intervals))
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
cron->minute = intervals[i].tab.minute;
|
||||||
|
cron->hour = intervals[i].tab.hour;
|
||||||
|
cron->dayofmonth = intervals[i].tab.dayofmonth;
|
||||||
|
cron->month = intervals[i].tab.month;
|
||||||
|
cron->dayofweek = intervals[i].tab.dayofweek;
|
||||||
|
return arg + j;
|
||||||
|
fail:
|
||||||
|
rdline_complain(rd, "unknown interval '%s'", arg);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *cron_fields(crontab_t *cron, rdline_t *rd)
|
||||||
|
{
|
||||||
|
char *arg = rd->line;
|
||||||
|
uint64_t value;
|
||||||
|
|
||||||
|
if ((arg = readfield(arg, &value, 0, 59, NULL, rd)) == NULL)
|
||||||
|
return NULL;
|
||||||
|
cron->minute = value;
|
||||||
|
|
||||||
|
if ((arg = readfield(arg, &value, 0, 23, NULL, rd)) == NULL)
|
||||||
|
return NULL;
|
||||||
|
cron->hour = value;
|
||||||
|
|
||||||
|
if ((arg = readfield(arg, &value, 1, 31, NULL, rd)) == NULL)
|
||||||
|
return NULL;
|
||||||
|
cron->dayofmonth = value;
|
||||||
|
|
||||||
|
if ((arg = readfield(arg, &value, 1, 12, month, rd)) == NULL)
|
||||||
|
return NULL;
|
||||||
|
cron->month = value;
|
||||||
|
|
||||||
|
if ((arg = readfield(arg, &value, 0, 6, weekday, rd)) == NULL)
|
||||||
|
return NULL;
|
||||||
|
cron->dayofweek = value;
|
||||||
|
|
||||||
|
return arg;
|
||||||
|
}
|
||||||
|
|
||||||
|
crontab_t *rdcron(int dirfd, const char *filename)
|
||||||
|
{
|
||||||
|
crontab_t *cron, *list = NULL;
|
||||||
|
rdline_t rd;
|
||||||
|
char *ptr;
|
||||||
|
|
||||||
|
if (rdline_init(&rd, dirfd, filename))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
while (rdline(&rd) == 0) {
|
||||||
|
cron = calloc(1, sizeof(*cron));
|
||||||
|
if (cron == NULL) {
|
||||||
|
rdline_complain(&rd, strerror(errno));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rd.line[0] == '@') {
|
||||||
|
ptr = cron_interval(cron, &rd);
|
||||||
|
} else {
|
||||||
|
ptr = cron_fields(cron, &rd);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ptr == NULL) {
|
||||||
|
free(cron);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (isspace(*ptr))
|
||||||
|
++ptr;
|
||||||
|
|
||||||
|
cron->exec = strdup(ptr);
|
||||||
|
if (cron->exec == NULL) {
|
||||||
|
rdline_complain(&rd, strerror(errno));
|
||||||
|
free(cron);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
cron->next = list;
|
||||||
|
list = cron;
|
||||||
|
}
|
||||||
|
|
||||||
|
rdline_cleanup(&rd);
|
||||||
|
return list;
|
||||||
|
}
|
77
rdline.c
Normal file
77
rdline.c
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
/* SPDX-License-Identifier: ISC */
|
||||||
|
#include "gcrond.h"
|
||||||
|
|
||||||
|
int rdline(rdline_t *t)
|
||||||
|
{
|
||||||
|
size_t i, len;
|
||||||
|
|
||||||
|
do {
|
||||||
|
free(t->line);
|
||||||
|
t->line = NULL;
|
||||||
|
errno = 0;
|
||||||
|
len = 0;
|
||||||
|
|
||||||
|
if (getline(&t->line, &len, t->fp) < 0) {
|
||||||
|
if (errno) {
|
||||||
|
rdline_complain(t, strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
t->lineno += 1;
|
||||||
|
|
||||||
|
for (i = 0; isspace(t->line[i]); ++i)
|
||||||
|
;
|
||||||
|
|
||||||
|
if (t->line[i] == '\0' || t->line[i] == '#') {
|
||||||
|
t->line[0] = '\0';
|
||||||
|
} else if (i) {
|
||||||
|
memmove(t->line, t->line + i, len - i + 1);
|
||||||
|
}
|
||||||
|
} while (t->line[0] == '\0');
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rdline_complain(rdline_t *t, const char *msg, ...)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
|
||||||
|
fprintf(stderr, "%s: %zu: ", t->filename, t->lineno);
|
||||||
|
|
||||||
|
va_start(ap, msg);
|
||||||
|
vfprintf(stderr, msg, ap);
|
||||||
|
va_end(ap);
|
||||||
|
|
||||||
|
fputc('\n', stderr);
|
||||||
|
}
|
||||||
|
|
||||||
|
int rdline_init(rdline_t *t, int dirfd, const char *filename)
|
||||||
|
{
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
memset(t, 0, sizeof(*t));
|
||||||
|
|
||||||
|
fd = openat(dirfd, filename, O_RDONLY);
|
||||||
|
if (fd == -1) {
|
||||||
|
perror(filename);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
t->fp = fdopen(fd, "r");
|
||||||
|
if (t->fp == NULL) {
|
||||||
|
perror("fdopen");
|
||||||
|
close(fd);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
t->filename = filename;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rdline_cleanup(rdline_t *t)
|
||||||
|
{
|
||||||
|
free(t->line);
|
||||||
|
fclose(t->fp);
|
||||||
|
}
|
Loading…
Reference in a new issue