summaryrefslogtreecommitdiffabout
authorSergey Poznyakoff <gray@gnu.org>2020-04-29 16:12:45 (GMT)
committer Sergey Poznyakoff <gray@gnu.org>2020-04-29 16:12:45 (GMT)
commit8196b4281500ef4f6e0cba4ff1f20f6d41e92f17 (patch) (side-by-side diff)
treecec8dfeaf2a2cf0ae73816a84f8dba41bd14274b
parent3ababf4edb89be0b164d8e9cb1cad5b009de9778 (diff)
downloadwydawca-8196b4281500ef4f6e0cba4ff1f20f6d41e92f17.tar.gz
wydawca-8196b4281500ef4f6e0cba4ff1f20f6d41e92f17.tar.bz2
Improve statistic reporter scheduling.
Use crontab format specification to define the frequency of statistic report generation. * src/wydawca.h (stat_report_schedule): New variable. Replaces stat_report_interval. * configure.ac: Check for struct tm.tm_gmtoff. * src/config.c: New keyword: stat-report-schedule. * src/micron.c: New file. * src/micron.h: New file. * src/Makefile.am: Add new files. * src/timer.c (wy_thr_stat): Use micron scheduler. * doc/wydawca.texi: Document stat-report-schedule. * NEWS: Document stat-report-schedule.
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--NEWS8
-rw-r--r--configure.ac5
-rw-r--r--doc/wydawca.texi72
-rw-r--r--src/Makefile.am2
-rw-r--r--src/config.c85
-rw-r--r--src/micron.c398
-rw-r--r--src/micron.h40
-rw-r--r--src/timer.c5
-rw-r--r--src/wydawca.h3
9 files changed, 559 insertions, 59 deletions
diff --git a/NEWS b/NEWS
index ebe721e..2778180 100644
--- a/NEWS
+++ b/NEWS
@@ -1,4 +1,4 @@
-Wydawca NEWS -- history of user-visible changes. 2020-04-27
+Wydawca NEWS -- history of user-visible changes. 2020-04-29
See the end of file for copying conditions.
Please send Wydawca bug reports to <bug-wydawca@gnu.org.ua>.
@@ -75,7 +75,11 @@ notification connections. Default is 16.
This new statement configures the maximum idle timeout of an
upload notification connection. Default is 10 seconds.
-** stat-report-interval statement
+** stat-report-schedule statement
+
+Schedules generation of statistic reports in daemon mode. The
+argument is a crontab(5) time specification. The default is
+"@hourly".
** The "statistics" event.
diff --git a/configure.ac b/configure.ac
index 1aed22a..2cffbed 100644
--- a/configure.ac
+++ b/configure.ac
@@ -56,6 +56,11 @@ AC_TYPE_SIZE_T
AC_HEADER_STDBOOL
AC_SYS_LARGEFILE
+AC_CHECK_MEMBERS([struct tm.tm_gmtoff],,,
+ [#include <sys/types.h>
+#include <time.h>
+])
+
# Checks for library functions.
AC_FUNC_FORK
AC_FUNC_MALLOC
diff --git a/doc/wydawca.texi b/doc/wydawca.texi
index 349d9ba..ddfcca9 100644
--- a/doc/wydawca.texi
+++ b/doc/wydawca.texi
@@ -2234,11 +2234,36 @@ EOT;
@node statistics
@section Statistics
@cindex statistics
-At the end of the run, @command{wydawca} can print a detailed
-statistics of its execution on the diagnostic channel @samp{info}.
-The statistics is printed only if at least one of its items is not zero.
+Periodically @command{wydawca} produces statistic dumps. These dumps
+are displayed on the diagnostic channel @samp{info} (and optionally
+mailed to the admimistrator). The frequency with which they are
+produced is defined by the @code{stat-report-schedule} configuration
+statement.
+
+@deffn {Config} stat-report-schedule time
+Schedules generation of statistic reports. The @var{time} argument
+is a time specification in @samp{crontab} format
+(@pxref{crontab,,,crontab(5),crontab(5) manual page}). By default,
+reports are generated hourly.
+
+To create reports each three hours, set
+
+@example
+stat-report-schedule "0 */3 * * *";
+@end example
+
+To create them at midnight, use
+
+@example
+stat-report-schedule "@@midnight";
+@end example
+@end deffn
+
+Statistic report is suppressed if there were no uploads since the last
+report.
+
The following example illustrates what you might get if you configured
-full statistics output:
+full statistic reports:
@smallexample
errors: 0
@@ -2256,8 +2281,8 @@ symlinks created: 0
symlinks removed: 0
@end smallexample
- Each item in this statistics is configurable, and a
-unique configuration keyword is associated with it. The statistics
+ Each item in this report is configurable, and a
+unique configuration keyword is associated with it. The statistic
items and their corresponding keywords are described in the table
below:
@@ -2322,12 +2347,12 @@ A symlink is created.
A symlink is removed.
@end table
-There are two ways to enable the statistics logging. The
-@dfn{built-in} statistics output is enabled using the
+There are two ways to enable statistic reports. The
+@dfn{built-in} statistic output is enabled using the
@code{statistics} keyword.
@deffn {Config} statistics list
- The amount of information included in the statistics summary is
+ The amount of information included in statistic report is
configured using the @code{statistics} statement. This statement takes
a list of arguments, each one being one of the keywords, described
above. For example, the following statement causes only the
@@ -2354,10 +2379,10 @@ statistics none;
@end smallexample
@kwindex all@r{, statistics}
- Another special keyword is @samp{all}. It enables full statistics
-output. This keyword may also be followed by any number of statistics
-keywords, which are in this case @emph{excluded} from the
-summary. For example, to output all statistics, except errors and
+ Another special keyword is @samp{all}. It enables full statistic
+report. This keyword may also be followed by any number of statistic
+item names, which are in this case @emph{excluded} from the
+summary. For example, to output all statistic data, except errors and
warnings one would set:
@smallexample
@@ -2547,7 +2572,7 @@ detailed description.
@item statistics
This event produces statistics about the recent jobs performed
by @command{wydawca}. In daemon mode, it is scheduled periodically
-as controlled by @code{stat-report-interval} statement. In cron mode
+as controlled by the @code{stat-report-schedule} statement. In cron mode
it is emitted when all spools have been processed.
For compatibility with @command{wydawca} versions prior to 3.1.95, the
@@ -2603,7 +2628,7 @@ constructed from the name of the user @command{wydawca} runs as
runs at.
@deffn {mod_mailutils} admin-address email
-Sets the admin email address or addresses. The statistics
+Sets the admin email address or addresses. The statistic
notifications and any notifications configured to be sent to admins
will be forwarded to this address. The @var{email} argument is either
a @acronym{RFC} 822 email address, or a list of such addresses. For
@@ -2821,8 +2846,8 @@ where @var{name} is the message name as used in @code{define-message}.
@end deffn
@deffn {mail-statistics} statistics item-list
-The argument is a list of statistics keywords as described in
-@ref{statistics}. A report will be sent only if statistics
+The argument is a list of statistic item names as described in
+@ref{statistics}. A report will be sent only if statistic
counters for at least one of the requested categories are not
zero. For example, the following statement requires sending
notifications only if there occurred any errors or access violation
@@ -2843,9 +2868,9 @@ The statistics message is sent to addresses configured by
@code{admin-address} statement (@pxref{mod_mailutils, admin-address}).
@cindex variables in admin notifications
-The variables available for use in statistics reports are:
+The variables available for use in statistic reports are:
-@anchor{statistics variables}
+@anchor{statistic variables}
@multitable @columnfractions 0.30 0.70
@headitem Variable @tab Replaced with
@kwindex date
@@ -3277,7 +3302,7 @@ This statement is ignored if the @code{message} statement is present.
@deffn {mod_logstat config} message text
Specifies the message to be logged. The @var{text} argument can
-contain references to statistics variables (@pxref{statistics
+contain references to statistic variables (@pxref{statistic
variables}).
@end deffn
@@ -3462,7 +3487,12 @@ umask @var{mask:@i{octal}};
# @xref{archivation, archive-signatures}.
archive-signatures @var{arg:@i{boolean}};
-# @r{Print these stats at the end of each run.}
+# @r{Schedule generation of statistic reports.}
+# @xref{statistics}.
+stat-report-schedule @var{time:@i{crontab-time}};
+
+# @r{Generate statistic reports if one or more of these items
+# changed.}
# @xref{statistics}.
statistics @var{items:@i{string}};
diff --git a/src/Makefile.am b/src/Makefile.am
index 5b70f5e..15238d4 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -44,6 +44,8 @@ wydawca_SOURCES=\
null.c\
timer.c\
thread_name.c\
+ micron.h\
+ micron.c\
queue.h
if COND_INOTIFY
diff --git a/src/config.c b/src/config.c
index f213fc7..3ba0e5a 100644
--- a/src/config.c
+++ b/src/config.c
@@ -249,7 +249,7 @@ string_to_wy_event(grecs_locus_t * locus, const char *val,
int
-wy_assert_string_arg(grecs_locus_t * locus,
+wy_assert_string_arg(grecs_locus_t *locus,
enum grecs_callback_command cmd,
const grecs_value_t * value)
{
@@ -280,7 +280,7 @@ get_arg(grecs_value_t * value, unsigned n, int type)
}
static int
-cb_interval(enum grecs_callback_command cmd, grecs_node_t * node,
+cb_interval(enum grecs_callback_command cmd, grecs_node_t *node,
void *varptr, void *cb_data)
{
int rc;
@@ -426,7 +426,7 @@ parse_statmask(grecs_locus_t *loc, grecs_value_t *val, unsigned long *pmask)
}
int
-wy_cb_statistics(enum grecs_callback_command cmd, grecs_node_t * node,
+wy_cb_statistics(enum grecs_callback_command cmd, grecs_node_t *node,
void *varptr, void *cb_data)
{
return parse_statmask(&node->locus, node->v.value, varptr);
@@ -434,7 +434,7 @@ wy_cb_statistics(enum grecs_callback_command cmd, grecs_node_t * node,
static int
-cb_sql_host(enum grecs_callback_command cmd, grecs_node_t * node,
+cb_sql_host(enum grecs_callback_command cmd, grecs_node_t *node,
void *varptr, void *cb_data)
{
struct sqlconn *pconn = varptr;
@@ -476,7 +476,7 @@ cb_sql_host(enum grecs_callback_command cmd, grecs_node_t * node,
}
static int
-cb_sql(enum grecs_callback_command cmd, grecs_node_t * node,
+cb_sql(enum grecs_callback_command cmd, grecs_node_t *node,
void *varptr, void *cb_data)
{
struct sqlconn *pconn;
@@ -539,7 +539,7 @@ static struct grecs_keyword sql_kw[] = {
};
static int
-cb_syslog_facility(enum grecs_callback_command cmd, grecs_node_t * node,
+cb_syslog_facility(enum grecs_callback_command cmd, grecs_node_t *node,
void *varptr, void *cb_data)
{
grecs_locus_t *locus = &node->locus;
@@ -577,7 +577,7 @@ static struct grecs_keyword syslog_kw[] = {
};
static int
-cb_metadata_mode(enum grecs_callback_command cmd, grecs_node_t * node,
+cb_metadata_mode(enum grecs_callback_command cmd, grecs_node_t *node,
void *varptr, void *cb_data)
{
grecs_locus_t *locus = &node->locus;
@@ -603,7 +603,7 @@ cb_metadata_mode(enum grecs_callback_command cmd, grecs_node_t * node,
}
static int
-arg_to_uid(grecs_value_t * value, uid_t * uid)
+arg_to_uid(grecs_value_t *value, uid_t *uid)
{
char const *user = value->v.string;
unsigned long n;
@@ -642,7 +642,7 @@ arg_to_uid(grecs_value_t * value, uid_t * uid)
}
static int
-arg_to_gid(grecs_value_t * value, gid_t * gid)
+arg_to_gid(grecs_value_t *value, gid_t *gid)
{
char const *group = value->v.string;
unsigned long n;
@@ -681,7 +681,7 @@ arg_to_gid(grecs_value_t * value, gid_t * gid)
}
static int
-cb_metadata_owner(enum grecs_callback_command cmd, grecs_node_t * node,
+cb_metadata_owner(enum grecs_callback_command cmd, grecs_node_t *node,
void *varptr, void *cb_data)
{
grecs_value_t *value = node->v.value, *uval, *gval;
@@ -732,7 +732,7 @@ get_backup_version(grecs_locus_t * locus, const char *ctx,
}
static int
-cb_backup(enum grecs_callback_command cmd, grecs_node_t * node,
+cb_backup(enum grecs_callback_command cmd, grecs_node_t *node,
void *varptr, void *cb_data)
{
enum backup_type *ptype = varptr;
@@ -769,7 +769,7 @@ static struct grecs_keyword archive_kw[] = {
};
static int
-cb_archive(enum grecs_callback_command cmd, grecs_node_t * node,
+cb_archive(enum grecs_callback_command cmd, grecs_node_t *node,
void *varptr, void *cb_data)
{
struct archive_descr *arch = varptr;
@@ -829,7 +829,7 @@ cb_archive(enum grecs_callback_command cmd, grecs_node_t * node,
}
static int
-cb_event(enum grecs_callback_command cmd, grecs_node_t * node,
+cb_event(enum grecs_callback_command cmd, grecs_node_t *node,
void *varptr, void *cb_data)
{
enum wy_event *pev = varptr;
@@ -859,7 +859,7 @@ static struct grecs_keyword notify_event_kw[] = {
};
static int
-cb_notify_event(enum grecs_callback_command cmd, grecs_node_t * node,
+cb_notify_event(enum grecs_callback_command cmd, grecs_node_t *node,
void *varptr, void *cb_data)
{
struct notification *ntf;
@@ -905,7 +905,7 @@ string_to_dictionary_type(const char *str)
}
static int
-cb_dictionary_type(enum grecs_callback_command cmd, grecs_node_t * node,
+cb_dictionary_type(enum grecs_callback_command cmd, grecs_node_t *node,
void *varptr, void *cb_data)
{
enum dictionary_type *ptype = varptr;
@@ -922,7 +922,7 @@ cb_dictionary_type(enum grecs_callback_command cmd, grecs_node_t * node,
}
static int
-cb_dictionary_params(enum grecs_callback_command cmd, grecs_node_t * node,
+cb_dictionary_params(enum grecs_callback_command cmd, grecs_node_t *node,
void *varptr, void *cb_data)
{
struct dictionary *meth = varptr;
@@ -1001,7 +1001,7 @@ string_to_dictionary_id(grecs_locus_t * locus,
}
static int
-cb_dictionary(enum grecs_callback_command cmd, grecs_node_t * node,
+cb_dictionary(enum grecs_callback_command cmd, grecs_node_t *node,
void *varptr, void *cb_data)
{
struct dictionary **pmeth, *meth;
@@ -1054,7 +1054,7 @@ cb_dictionary(enum grecs_callback_command cmd, grecs_node_t * node,
}
static int
-cb_url(enum grecs_callback_command cmd, grecs_node_t * node,
+cb_url(enum grecs_callback_command cmd, grecs_node_t *node,
void *varptr, void *cb_data)
{
wy_url_t *purl = varptr, url;
@@ -1144,7 +1144,7 @@ static struct grecs_keyword spool_kw[] = {
};
static int
-cb_spool(enum grecs_callback_command cmd, grecs_node_t * node,
+cb_spool(enum grecs_callback_command cmd, grecs_node_t *node,
void *varptr, void *cb_data)
{
struct spool *spool;
@@ -1226,7 +1226,7 @@ cb_spool(enum grecs_callback_command cmd, grecs_node_t * node,
}
static int
-cb_user(enum grecs_callback_command cmd, grecs_node_t * node,
+cb_user(enum grecs_callback_command cmd, grecs_node_t *node,
void *varptr, void *cb_data)
{
struct passwd *pw;
@@ -1250,7 +1250,7 @@ cb_user(enum grecs_callback_command cmd, grecs_node_t * node,
}
static int
-cb_supp_groups(enum grecs_callback_command cmd, grecs_node_t * node,
+cb_supp_groups(enum grecs_callback_command cmd, grecs_node_t *node,
void *varptr, void *cb_data)
{
grecs_locus_t *locus = &node->locus;
@@ -1297,7 +1297,7 @@ cb_supp_groups(enum grecs_callback_command cmd, grecs_node_t * node,
}
static int
-cb_load_path(enum grecs_callback_command cmd, grecs_node_t * node,
+cb_load_path(enum grecs_callback_command cmd, grecs_node_t *node,
void *varptr, void *cb_data)
{
struct grecs_list **lpp = varptr, *lp;
@@ -1339,7 +1339,7 @@ cb_load_path(enum grecs_callback_command cmd, grecs_node_t * node,
}
static int
-cb_upload_version(enum grecs_callback_command cmd, grecs_node_t * node,
+cb_upload_version(enum grecs_callback_command cmd, grecs_node_t *node,
void *varptr, void *cb_data)
{
unsigned *pversion = varptr, n;
@@ -1368,6 +1368,30 @@ cb_upload_version(enum grecs_callback_command cmd, grecs_node_t * node,
}
static int
+cb_cron(enum grecs_callback_command cmd, grecs_node_t *node,
+ void *varptr, void *cb_data)
+{
+ grecs_locus_t *locus = &node->locus;
+ grecs_value_t *value = node->v.value;
+ struct micronent *entry = varptr;
+ int rc;
+ char *endp;
+
+ if (wy_assert_string_arg(locus, cmd, value))
+ return 1;
+ rc = micron_parse(value->v.string, &endp, entry);
+ if (rc) {
+ grecs_error(&value->locus, 0, "%s near %s",
+ micron_strerror(rc), endp);
+ return 0;
+ }
+ if (endp[strcspn(endp, " \t")])
+ grecs_error(&value->locus, 0, "garbage after cron specification");
+
+ return 0;
+}
+
+static int
cb_daemon_mode(enum grecs_callback_command cmd, grecs_node_t *node,
void *varptr, void *cb_data)
{
@@ -1466,10 +1490,11 @@ static struct grecs_keyword wydawca_kw[] = {
N_("Control implicit signature archivation"),
grecs_type_bool, GRECS_DFLT, &archive_signatures },
- { "stat-report-interval", N_("interval"),
- N_("Interval for periodic statistics reports"),
+ { "stat-report-schedule", N_("spec"),
+ N_("Schedule periodic statistics reports (in crontab format)"),
grecs_type_string, GRECS_DFLT,
- &stat_report_interval, 0, cb_interval },
+ &stat_report_schedule, 0, cb_cron },
+
{ "statistics", N_("items"),
N_("Print these statistic items periodically"),
grecs_type_string, GRECS_CONST, &print_stats, 0, wy_cb_statistics },
@@ -1710,6 +1735,8 @@ config_finish(struct grecs_node *tree)
struct grecs_node *p;
int err;
+ micron_parse("@hourly", NULL, &stat_report_schedule);
+
if (grecs_tree_process(tree, wydawca_kw))
exit(EX_CONFIG);
for (p = tree->down; p; p = p->next) {
@@ -1732,10 +1759,4 @@ config_finish(struct grecs_node *tree)
_("%s too low; reverting to the default %lus"),
"file-sweep-time", (unsigned long)file_sweep_time);
}
- if (stat_report_interval <= 0) {
- stat_report_interval = DEFAULT_STAT_REPORT_INTERVAL;
- wy_log(LOG_NOTICE,
- _("%s too low; reverting to the default %lus"),
- "stat-report-interval", (unsigned long)stat_report_interval);
- }
}
diff --git a/src/micron.c b/src/micron.c
new file mode 100644
index 0000000..2fb6e3b
--- a/dev/null
+++ b/src/micron.c
@@ -0,0 +1,398 @@
+/* micron - a minimal cron implementation
+ Copyright (C) 2020 Sergey Poznyakoff
+
+ Micron 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 3 of the License, or (at your
+ option) any later version.
+
+ Micron 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 micron. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+#include <stdlib.h>
+#include <ctype.h>
+#include <errno.h>
+#include <string.h>
+#include <time.h>
+#include "micron.h"
+
+static char const *mon_names[] = {
+ "jan", "feb", "mar",
+ "apr", "may", "jun",
+ "jul", "aug", "sep",
+ "oct", "nov", "dec"
+};
+
+static char const *dow_names[] = {
+ "sun", "mon", "tue", "wed", "thu", "fri", "sat"
+};
+
+static int
+xlat_name(char const *xlat[], int n, char const *name, char const *allowed)
+{
+ int i;
+ if (strlen(name) < 3 || (name[3] && strchr(allowed, name[3]) == 0))
+ return -1;
+ for (i = 0; i < n; i++)
+ if (strncasecmp(xlat[i], name, 3) == 0)
+ return i;
+ return -1;
+}
+
+static int
+micron_parse_range(char const *spec, char **endp, char *map, int len, int start,
+ char const **xlat)
+{
+ int r_min, r_max, r_step;
+ unsigned long n;
+ char *p;
+ int list_ok;
+
+ if (*spec == '*') {
+ spec++;
+ r_min = 0;
+ r_max = len - 1;
+ list_ok = 0;
+ } else if (isdigit(*spec)) {
+ errno = 0;
+ n = strtoul(spec, &p, 10);
+ if (errno || p == spec || n < start || n - start > len - 1) {
+ *endp = (char*) spec;
+ return MICRON_E_RANGE;
+ }
+ r_min = n - start;
+
+ spec = p;
+
+ if (*spec == '-') {
+ spec++;
+ errno = 0;
+ n = strtoul(spec, &p, 10);
+ if (errno || p == spec || n < start || n - start > len - 1) {
+ *endp = (char*) spec;
+ return MICRON_E_RANGE;
+ }
+ r_max = n - start;
+ spec = p;
+ } else
+ r_max = r_min;
+ list_ok = 1;
+ } else if (xlat) {
+ int d = xlat_name(xlat, len, spec, "-, ");
+ if (d == -1)
+ goto esynt;
+ spec += 3;
+ r_min = d;
+
+ if (*spec == '-') {
+ spec++;
+ d = xlat_name(xlat, len, spec, "/, ");
+ if (d == -1)
+ goto esynt;
+ spec += 3;
+ r_max = d;
+ } else
+ r_max = r_min;
+ list_ok = 1;
+ } else {
+esynt:
+ *endp = (char*) spec;
+ return MICRON_E_SYNT;
+ }
+
+ if (r_max != r_min && *spec == '/') {
+ spec++;
+ errno = 0;
+ n = strtoul(spec, &p, 10);
+ if (errno || p == spec || n >= len) {
+ *endp = (char*) spec;
+ return MICRON_E_RANGE;
+ }
+ r_step = n;
+ spec = p;
+ } else
+ r_step = 1;
+
+ if (r_min > r_max) {
+ for (; r_min < len; r_min += r_step)
+ map[r_min] = 1;
+ r_min = 0;
+ }
+
+ for (; r_min <= r_max; r_min += r_step)
+ map[r_min] = 1;
+
+ *endp = (char*) spec;
+ if (!list_ok && *spec == ',')
+ return MICRON_E_SYNT;
+ return MICRON_E_OK;
+}
+
+static int
+micron_parse_field(char const *spec, char **endp, char *map, int len, int start,
+ char const **xlat)
+{
+ int rc;
+ char *p;
+
+ while (*spec && isspace(*spec))
+ spec++;
+ if (!*spec) {
+ *endp = (char*) spec;
+ return MICRON_E_EOF;
+ }
+ memset(map, 0, len * sizeof(map[0]));
+ do {
+ rc = micron_parse_range(spec, &p, map, len, start, xlat);
+ spec = p;
+ if (*spec != ',')
+ break;
+ spec++;
+ } while (rc == MICRON_E_OK);
+ *endp = (char*) spec;
+ return rc;
+}
+
+#define micron_parse_entry_field(spec,endp,fld,start,xlat) \
+ micron_parse_field(spec, \
+ endp, \
+ fld, \
+ sizeof(fld)/sizeof((fld)[0]), start, xlat)
+
+int
+micron_parse_timespec(char const *spec, char **endp, struct micronent *ent)
+{
+ char *p;
+ int rc;
+
+ rc = micron_parse_entry_field(spec, &p, ent->min, 0, NULL);
+ if (rc == 0) {
+ rc = micron_parse_entry_field(p, &p, ent->hrs, 0, NULL);
+ if (rc == 0) {
+ rc = micron_parse_entry_field(p, &p, ent->day, 1, NULL);
+ if (rc == 0) {
+ rc = micron_parse_entry_field(p, &p, ent->mon, 1, mon_names);
+ if (rc == 0) {
+ rc = micron_parse_entry_field(p, &p, ent->dow, 0,
+ dow_names);
+ if (rc == 0) {
+ if (ent->dow[7]) {
+ ent->dow[0] = 1;
+ ent->dow[7] = 0;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (endp)
+ *endp = p;
+ return rc;
+}
+
+static struct micron_equiv {
+ char const *name;
+ int len;
+ char const *equiv;
+} micron_special[] = {
+#define S(s) #s, sizeof(#s)-1
+ { S(hourly), "0 * * * *" },
+ { S(daily), "0 0 * * *" },
+ { S(midnight), "0 0 * * *" },
+ { S(weekly), "0 0 * * 0" },
+ { S(monthly), "0 0 1 * *" },
+ { S(yearly), "0 0 1 1 *" },
+ { S(annually), "0 0 1 1 *" },
+ { NULL }
+#undef S
+};
+
+int
+micron_parse(char const *spec, char **endp, struct micronent *ent)
+{
+ while (*spec && isspace(*spec))
+ spec++;
+ if (!*spec) {
+ *endp = (char*) spec;
+ return MICRON_E_EOF;
+ }
+
+ if (*spec == '@') {
+ struct micron_equiv *eqv;
+ spec++;
+ for (eqv = micron_special; eqv->name; eqv++) {
+ if (!strncmp(spec, eqv->name, eqv->len)
+ && (spec[eqv->len] == 0|| isspace(spec[eqv->len]))) {
+ micron_parse_timespec(eqv->equiv, NULL, ent);
+ if (endp)
+ *endp = (char*)spec + eqv->len;
+ return MICRON_E_OK;
+ }
+ }
+ if (endp)
+ *endp = (char*) spec;
+ return MICRON_E_SYNT;
+ }
+
+ return micron_parse_timespec(spec, endp, ent);
+}
+
+static char const *micron_error_str[] = {
+ [MICRON_E_OK] = "no error",
+ [MICRON_E_EOF] = "premature end of input",
+ [MICRON_E_RANGE] = "value out of range",
+ [MICRON_E_SYNT] = "syntax error",
+ [MICRON_E_SYS] = "system error",
+ [MICRON_E_BADCRON] = "malformed crontab entry"
+};
+
+char const *
+micron_strerror(int ec)
+{
+ if (ec >= 0 && ec < sizeof(micron_error_str)/sizeof(micron_error_str[0]))
+ return micron_error_str[ec];
+ return "unknown error";
+}
+
+static int
+julianday(struct tm *tm)
+{
+ int a = (13 - tm->tm_mon) / 12;
+ int y = tm->tm_year + 6700 - a;
+ int m = tm->tm_mon + 12*a - 2;
+
+ return tm->tm_mday + (153*m + 2)/5 + 365*y + y/4 - y/100 + y/400 - 32045;
+}
+
+/* Compute day of week (0 - Sunday) */
+static inline int
+dayofweek(struct tm *tm)
+{
+ return (julianday(tm) + 1) % 7;
+}
+
+static int month_start[]=
+ { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 };
+ /* Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec */
+ /* 31 28 31 30 31 30 31 31 30 31 30 31 */
+
+static inline int
+is_leap_year(int y)
+{
+ return (y % 4 == 0 && (y % 100 != 0 || y % 400 == 0));
+}
+
+static inline int
+monthdays(struct tm *tm)
+{
+ return month_start[tm->tm_mon + 1] - month_start[tm->tm_mon]
+ + ((tm->tm_mon == 1) ? is_leap_year(tm->tm_year + 1900) : 0);
+}
+
+static inline void
+next_month(struct tm *tm)
+{
+ if (++tm->tm_mon == 12) {
+ tm->tm_mon = 0;
+ tm->tm_year++;
+ }
+ tm->tm_mday = 1;
+ tm->tm_hour = 0;
+ tm->tm_min = 0;
+ tm->tm_wday = dayofweek(tm);
+}
+
+static inline void
+next_day(struct tm *tm)
+{
+ tm->tm_hour = 0;
+ tm->tm_min = 0;
+ tm->tm_wday = (tm->tm_wday + 1) % 7;
+ if (++tm->tm_mday > monthdays(tm)) {
+ tm->tm_mday = 1;
+ next_month(tm);
+ }
+}
+
+static inline void
+next_hour(struct tm *tm)
+{
+ tm->tm_min = 0;
+ if (++tm->tm_hour == 24) {
+ tm->tm_hour = 0;
+ next_day(tm);
+ }
+}
+
+static inline void
+next_minute(struct tm *tm)
+{
+ if (++tm->tm_min == 60) {
+ tm->tm_min = 0;
+ next_hour(tm);
+ }
+}
+
+void
+micron_next(struct micronent const *ent, struct tm const *now, struct tm *next)
+{
+ *next = *now;
+ next->tm_sec = 0;
+ next_minute(next);
+
+ while (1) {
+ if (!ent->mon[next->tm_mon]) {
+ next_month(next);
+ continue;
+ }
+
+ if (!(ent->day[next->tm_mday-1] == 1
+ && ent->dow[next->tm_wday])) {
+ next_day(next);
+ continue;
+ }
+
+ if (!ent->hrs[next->tm_hour]) {
+ next_hour(next);
+ continue;
+ }
+
+ if (!ent->min[next->tm_min]) {
+ next_minute(next);
+ continue;
+ }
+ break;
+ }
+}
+
+int
+micron_next_time(struct micronent const *ent, struct timespec *ts)
+{
+ struct timespec ts_now;
+ struct tm now, next;
+ time_t t;
+
+ clock_gettime(CLOCK_REALTIME, &ts_now);
+ t = ts_now.tv_sec;
+ if (!localtime_r(&t, &now))
+ return MICRON_E_SYS;
+ micron_next(ent, &now, &next);
+ t = mktime(&next);
+ if (t == (time_t)-1)
+ return MICRON_E_SYS;
+#ifdef HAVE_STRUCT_TM_TM_GMTOFF
+ t += now.tm_gmtoff - next.tm_gmtoff;
+#endif
+ ts->tv_sec = t;
+ ts->tv_nsec = 0;
+ return MICRON_E_OK;
+}
diff --git a/src/micron.h b/src/micron.h
new file mode 100644
index 0000000..c415f50
--- a/dev/null
+++ b/src/micron.h
@@ -0,0 +1,40 @@
+/* micron - a minimal cron implementation
+ Copyright (C) 2020 Sergey Poznyakoff
+
+ Micron 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 3 of the License, or (at your
+ option) any later version.
+
+ Micron 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 micron. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <time.h>
+
+enum {
+ MICRON_E_OK,
+ MICRON_E_EOF,
+ MICRON_E_RANGE,
+ MICRON_E_SYNT,
+ MICRON_E_SYS,
+ MICRON_E_BADCRON
+};
+
+struct micronent {
+ char min[60];
+ char hrs[24];
+ char day[32];
+ char mon[12];
+ char dow[8]; /* 0 or 7 is Sun */
+};
+
+int micron_parse(char const *spec, char **endp, struct micronent *ent);
+char const *micron_strerror(int ec);
+void micron_next(struct micronent const *ent, struct tm const *now,
+ struct tm *next);
+int micron_next_time(struct micronent const *ent, struct timespec *ts);
diff --git a/src/timer.c b/src/timer.c
index 1de7cfb..ff8dfce 100644
--- a/src/timer.c
+++ b/src/timer.c
@@ -258,7 +258,7 @@ wydawca_stat_reset(void)
_timer_reset(&wydawca_global_timer_table[i]);
}
-time_t stat_report_interval = DEFAULT_STAT_REPORT_INTERVAL;
+struct micronent stat_report_schedule;
void *
wy_thr_stat(void *ptr)
@@ -270,8 +270,7 @@ wy_thr_stat(void *ptr)
wydawca_global_timer_table = get_thread_timer_table();
while (!stat_stop) {
struct timespec ts;
- clock_gettime(CLOCK_REALTIME, &ts);
- ts.tv_sec += stat_report_interval;
+ micron_next_time(&stat_report_schedule, &ts);
pthread_cond_timedwait(&global_stats_cond, &global_stats_mutex, &ts);
wydawca_stat_log();
notify_stat();
diff --git a/src/wydawca.h b/src/wydawca.h
index 5fbb106..95f11f0 100644
--- a/src/wydawca.h
+++ b/src/wydawca.h
@@ -54,6 +54,7 @@
#include "grecs.h"
#include "wordsplit.h"
#include "queue.h"
+#include "micron.h"
#ifndef O_SEARCH
# define O_SEARCH 0
@@ -328,7 +329,7 @@ extern char *conffile; /* Configuration file name */
extern int syslog_include_prio; /* Syslog priority indication */
extern time_t file_sweep_time; /* Unlink stale file after this amount of time
*/
-extern time_t stat_report_interval;
+extern struct micronent stat_report_schedule;
extern char *tar_command_name; /* Name of the tar command */
extern unsigned long print_stats;
extern int archive_signatures;

Return to:

Send suggestions and report system problems to the System administrator.