mirror of
https://github.com/pygos/cron
synced 2024-11-14 01:17:10 +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