summaryrefslogtreecommitdiff
path: root/server
diff options
context:
space:
mode:
authorJakub Hrozek <jhrozek@redhat.com>2009-11-03 16:59:42 +0100
committerStephen Gallagher <sgallagh@redhat.com>2009-11-09 10:53:49 -0500
commitb9f94bc83f139df3e143cc020b98e6e652887049 (patch)
treeca0990b2843a852c22cc6c86aa7e3e656148b7bb /server
parentc9bb0b019a811b9d26ddb7c5a037dc19754cf0b3 (diff)
downloadsssd-b9f94bc83f139df3e143cc020b98e6e652887049.tar.gz
sssd-b9f94bc83f139df3e143cc020b98e6e652887049.tar.bz2
sssd-b9f94bc83f139df3e143cc020b98e6e652887049.zip
IPA time rules parsing routines
Diffstat (limited to 'server')
-rw-r--r--server/Makefile.am20
-rw-r--r--server/providers/ipa/ipa_timerules.c1186
-rw-r--r--server/providers/ipa/ipa_timerules.h56
-rw-r--r--server/tests/ipa_timerules-tests.c579
4 files changed, 1841 insertions, 0 deletions
diff --git a/server/Makefile.am b/server/Makefile.am
index 800837c3..0c894a66 100644
--- a/server/Makefile.am
+++ b/server/Makefile.am
@@ -68,6 +68,7 @@ if HAVE_CHECK
resolv-tests \
krb5-utils-tests \
check_and_open-tests \
+ ipa_timerules-tests \
files-tests \
refcount-tests \
fail_over-tests
@@ -294,6 +295,7 @@ dist_noinst_HEADERS = \
providers/ldap/sdap_async_private.h \
providers/ipa/ipa_common.h \
providers/ipa/ipa_access.h \
+ providers/ipa/ipa_timerules.h \
tools/tools_util.h \
tools/sss_sync_ops.h \
resolv/async_resolv.h \
@@ -497,6 +499,24 @@ fail_over_tests_LDADD = \
$(SSSD_LIBS) \
$(CHECK_LIBS) \
$(CARES_LIBS)
+
+ipa_timerules_tests_SOURCES = \
+ providers/ipa/ipa_timerules.c \
+ tests/ipa_timerules-tests.c \
+ tests/common.c \
+ $(SSSD_DEBUG_OBJ)
+ipa_timerules_tests_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(POPT_CFLAGS) \
+ $(TALLOC_CFLAGS) \
+ $(PCRE_CFLAGS) \
+ $(CHECK_CFLAGS)
+ipa_timerules_tests_LDADD = \
+ $(POPT_LIBS) \
+ $(PCRE_LIBS) \
+ $(TALLOC_LIBS) \
+ $(CHECK_LIBS)
+
endif
stress_tests_SOURCES = \
diff --git a/server/providers/ipa/ipa_timerules.c b/server/providers/ipa/ipa_timerules.c
new file mode 100644
index 00000000..1a52eef1
--- /dev/null
+++ b/server/providers/ipa/ipa_timerules.c
@@ -0,0 +1,1186 @@
+/*
+ SSSD
+
+ IPA Provider Time Rules Parsing
+
+ Authors:
+ Jakub Hrozek <jhrozek@redhat.com>
+
+ Copyright (C) Red Hat, Inc 2009
+
+ 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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+#define _XOPEN_SOURCE /* strptime() needs this */
+
+#include <pcre.h>
+#include <talloc.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <time.h>
+#include <stdbool.h>
+#include <limits.h>
+
+#include "providers/ipa/ipa_timerules.h"
+#include "util/util.h"
+
+#define JMP_NEOK(variable) do { \
+ if (variable != EOK) goto done; \
+} while (0)
+
+#define JMP_NEOK_LABEL(variable, label) do { \
+ if (variable != EOK) goto label; \
+} while (0)
+
+#define CHECK_PTR(ptr) do { \
+ if (ptr == NULL) { \
+ return ENOMEM; \
+ } \
+} while (0)
+
+#define CHECK_PTR_JMP(ptr) do { \
+ if (ptr == NULL) { \
+ ret = ENOMEM; \
+ goto done; \
+ } \
+} while (0)
+
+#define BUFFER_OR_JUMP(ctx, ptr, count) do { \
+ ptr = talloc_array(ctx, unsigned char, count); \
+ if (ptr == NULL) { \
+ return ENOMEM; \
+ } \
+ memset(ptr, 0, sizeof(unsigned char)*count); \
+} while (0)
+
+#define TEST_BIT_RANGE(bitfield, index, resptr) do { \
+ if (bitfield) { \
+ if (test_bit(&bitfield, index) == 0) { \
+ *resptr = false; \
+ return EOK; \
+ } \
+ } \
+} while (0)
+
+#define TEST_BIT_RANGE_PTR(bitfield, index, resptr) do { \
+ if (bitfield) { \
+ if (test_bit(bitfield, index) == 0) { \
+ *resptr = false; \
+ return EOK; \
+ } \
+ } \
+} while (0)
+
+/* number of match offsets when matching pcre regexes */
+#define OVEC_SIZE 30
+
+/* regular expressions describing syntax of our HBAC grammar */
+#define RGX_WEEKLY "day (?P<day_of_week>(0|1|2|3|4|5|6|7|Mon|Tue|Wed|Thu|Fri|Sat|Sun|,|-)+)"
+
+#define RGX_MDAY "(?P<mperspec_day>day) (?P<interval_day>[0-9,-]+) "
+#define RGX_MWEEK "(?P<mperspec_week>week) (?P<interval_week>[0-9,-]+) "RGX_WEEKLY
+#define RGX_MONTHLY RGX_MDAY"|"RGX_MWEEK
+
+#define RGX_YDAY "(?P<yperspec_day>day) (?P<day_of_year>[0-9,-]+) "
+#define RGX_YWEEK "(?P<yperspec_week>week) (?P<week_of_year>[0-9,-]+) "RGX_WEEKLY
+#define RGX_YMONTH "(?P<yperspec_month>month) (?P<month_number>[0-9,-]+) (?P<m_period>.*?)$"
+#define RGX_YEARLY RGX_YMONTH"|"RGX_YWEEK"|"RGX_YDAY
+
+#define RGX_TIMESPEC "(?P<timeFrom>[0-9]{4}) ~ (?P<timeTo>[0-9]{4})"
+
+#define RGX_GENERALIZED "(?P<year>[0-9]{4})(?P<month>[0-9]{2})(?P<day>[0-9]{2})(?P<hour>[0-9]{2})?(?P<minute>[0-9]{2})?(?P<second>[0-9]{2})?"
+
+#define RGX_PERIODIC "^periodic (?P<perspec>daily|weekly|monthly|yearly) (?P<period>.*?)"RGX_TIMESPEC"$"
+#define RGX_ABSOLUTE "^absolute (?P<from>\\S+) ~ (?P<to>\\S+)$"
+
+/* limits on various parameters */
+#define DAY_OF_WEEK_MAX 7
+#define DAY_OF_MONTH_MAX 31
+#define WEEK_OF_MONTH_MAX 5
+#define WEEK_OF_YEAR_MAX 54
+#define DAY_OF_YEAR_MAX 366
+#define MONTH_MAX 12
+#define HOUR_MAX 23
+#define MINUTE_MAX 59
+
+/* limits on sizes of buffers for bit arrays */
+#define DAY_OF_MONTH_BUFSIZE 8
+#define DAY_OF_YEAR_BUFSIZE 44
+#define WEEK_OF_YEAR_BUFSIZE 13
+#define MONTH_BUFSIZE 2
+#define HOUR_BUFSIZE 4
+#define MINUTE_BUFSIZE 8
+
+/* Lookup tables for translating names of days and months */
+static const char *names_day_of_week[] =
+ { "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun", NULL };
+static const char *names_months[] =
+ { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Nov", "Dec", NULL };
+
+/*
+ * Timelib knows two types of ranges - periodic and absolute
+ */
+enum rangetypes {
+ TYPE_ABSOLUTE,
+ TYPE_PERIODIC
+};
+
+struct absolute_range {
+ time_t time_from;
+ time_t time_to;
+};
+
+struct periodic_range {
+ unsigned char day_of_week;
+ unsigned char *day_of_month;
+ unsigned char *day_of_year;
+ unsigned char week_of_month;
+ unsigned char *week_of_year;
+ unsigned char *month;
+ unsigned char *hour;
+ unsigned char *minute;
+};
+
+/*
+ * Context of one time rule being analyzed
+ */
+struct range_ctx {
+ /* main context with precompiled patterns */
+ struct time_rules_ctx *trctx;
+ /* enum rangetypes */
+ enum rangetypes type;
+
+ struct absolute_range *abs;
+ struct periodic_range *per;
+};
+
+
+/*
+ * The context of one regular expression
+ */
+struct parse_ctx {
+ /* the regular expression used for one parsing */
+ pcre *re;
+ /* number of matches */
+ int matches;
+ /* vector of matches */
+ int *ovec;
+};
+
+/* indexes to the array of precompiled regexes */
+enum timelib_rgx {
+ LP_RGX_GENERALIZED,
+ LP_RGX_MDAY,
+ LP_RGX_MWEEK,
+ LP_RGX_YEARLY,
+ LP_RGX_WEEKLY,
+ LP_RGX_ABSOLUTE,
+ LP_RGX_PERIODIC,
+ LP_RGX_MAX,
+};
+
+/* matches the indexes */
+static const char *lookup_table[] = {
+ RGX_GENERALIZED,
+ RGX_MDAY,
+ RGX_MWEEK,
+ RGX_YEARLY,
+ RGX_WEEKLY,
+ RGX_ABSOLUTE,
+ RGX_PERIODIC,
+ NULL,
+};
+
+/*
+ * Main struct passed outside
+ * holds precompiled regular expressions
+ */
+struct time_rules_ctx {
+ pcre *re[LP_RGX_MAX];
+};
+
+/*******************************************************************
+ * helper function - bit arrays *
+ *******************************************************************/
+
+/* set a single bit in a bitmap */
+static void set_bit(unsigned char *bitmap, unsigned int bit)
+{
+ bitmap[bit/CHAR_BIT] |= 1 << (bit%CHAR_BIT);
+}
+
+/*
+ * This function is based on bit_nset macro written originally by Paul Vixie,
+ * copyrighted by The Regents of the University of California, as found
+ * in tarball of fcron, file bitstring.h
+ */
+static void set_bit_range(unsigned char *bitmap, unsigned int start,
+ unsigned int stop)
+{
+ int startbyte = start/CHAR_BIT;
+ int stopbyte = stop/CHAR_BIT;
+
+ if (startbyte == stopbyte) {
+ bitmap[startbyte] |= ((0xff << (start & 0x7)) &
+ (0xff >> (CHAR_BIT- 1 - (stop & 0x7))));
+ } else {
+ bitmap[startbyte] |= 0xff << (start & 0x7);
+ while (++startbyte < stopbyte) {
+ bitmap[startbyte] |= 0xff;
+ }
+ bitmap[stopbyte] |= 0xff >> (CHAR_BIT- 1 - (stop & 0x7));
+ }
+}
+
+static int test_bit(unsigned char *bitmap, unsigned int bit)
+{
+ return (int)(bitmap[bit/CHAR_BIT] >> (bit%CHAR_BIT)) & 1;
+}
+
+/*******************************************************************
+ * parsing intervals *
+ *******************************************************************/
+
+/*
+ * Some ranges allow symbolic names, like Mon..Sun for names of day.
+ * This routine takes a list of symbolic names as NAME_ARRAY and the
+ * one we're looking for as KEY and returns its index or -1 when not
+ * found. The last member of NAME_ARRAY must be NULL.
+ */
+static int name_index(const char **name_array, const char *key, int min)
+{
+ int index = 0;
+ const char *one;
+
+ if (name_array == NULL) {
+ return -1;
+ }
+
+ while ((one = name_array[index]) != NULL) {
+ if (strcmp(key,one) == 0) {
+ return index+min;
+ }
+ index++;
+ }
+
+ return -1;
+}
+
+/*
+ * Sets appropriate bits given by an interval in STR (in form of 1,5-7,10) to
+ * a bitfield given in OUT. Does no boundary checking. STR can also contain
+ * symbolic names, these would be given in TRANSLATE.
+ */
+static int interval2bitfield(TALLOC_CTX *mem_ctx,
+ unsigned char *out,
+ const char *str,
+ int min, int max,
+ const char **translate)
+{
+ char *copy;
+ char *next, *token;
+ int tokval, tokmax;
+ char *end_ptr;
+ int ret;
+ char *dash;
+
+ DEBUG(9, ("Converting '%s' to interval\n", str));
+
+ copy = talloc_strdup(mem_ctx, str);
+ CHECK_PTR(copy);
+
+ next = copy;
+ while (next) {
+ token = next;
+ next = strchr(next, ',');
+ if (next) {
+ *next = '\0';
+ next++;
+ }
+
+ errno = 0;
+ tokval = strtol(token, &end_ptr, 10);
+ if (*end_ptr == '\0' && errno == 0) {
+ if (tokval <= max && tokval >= 0) {
+ set_bit(out, tokval);
+ continue;
+ } else {
+ ret = ERANGE;
+ goto done;
+ }
+ } else if ((dash = strchr(token, '-')) != NULL){
+ *dash = '\0';
+ ++dash;
+
+ errno = 0;
+ tokval = strtol(token, &end_ptr, 10);
+ if (*end_ptr != '\0' || errno != 0) {
+ tokval = name_index(translate, token, min);
+ if (tokval == -1) {
+ ret = ERANGE;
+ goto done;
+ }
+ }
+ errno = 0;
+ tokmax = strtol(dash, &end_ptr, 10);
+ if (*end_ptr != '\0' || errno != 0) {
+ tokmax = name_index(translate, dash, min);
+ if (tokmax == -1) {
+ ret = ERANGE;
+ goto done;
+ }
+ }
+
+ if (tokval <= max && tokmax <= max &&
+ tokval >= min && tokmax >= min) {
+ if (tokmax > tokval) {
+ DEBUG(7, ("Setting interval %d-%d\n", tokval, tokmax));
+ DEBUG(9, ("interval: %p\n", out));
+ set_bit_range(out, tokval, tokmax);
+ } else {
+ /* Interval wraps around - i.e. from 18.00 to 06.00 */
+ DEBUG(7, ("Setting inverted interval %d-%d\n", tokval, tokmax));
+ DEBUG(9, ("interval: %p\n", out));
+ set_bit_range(out, min, tokmax);
+ set_bit_range(out, tokval, max);
+ }
+ continue;
+ } else {
+ /* tokval or tokmax are not between <min, max> */
+ ret = ERANGE;
+ goto done;
+ }
+ } else if ((tokval = name_index(translate, token, min)) != -1) {
+ /* Try to translate one token by name */
+ if (tokval <= max) {
+ set_bit(out, tokval);
+ continue;
+ } else {
+ ret = ERANGE;
+ goto done;
+ }
+ } else {
+ ret = EINVAL;
+ goto done;
+ }
+ }
+
+ ret = EOK;
+done:
+ talloc_free(copy);
+ return ret;
+}
+
+/*******************************************************************
+ * wrappers around regexp handling *
+ *******************************************************************/
+
+/*
+ * Copies a named substring SUBSTR_NAME from string STR using the parsing
+ * information from PCTX. The context PCTX is also used as a talloc context.
+ *
+ * The resulting string is stored in OUT.
+ * Return value is EOK on no error or ENOENT on error capturing the substring
+ */
+static int copy_substring(struct parse_ctx *pctx,
+ const char *str,
+ const char *substr_name,
+ char **out)
+{
+ const char *result = NULL;
+ int ret;
+ char *o = NULL;
+
+ result = NULL;
+
+ ret = pcre_get_named_substring(pctx->re, str, pctx->ovec,
+ pctx->matches, substr_name, &result);
+ if (ret < 0 || result == NULL) {
+ DEBUG(5, ("named substring '%s' does not exist in '%s'\n",
+ substr_name, str));
+ return ENOENT;
+ }
+
+ o = talloc_strdup(pctx, result);
+ pcre_free_substring(result);
+ if (o == NULL) {
+ return ENOMEM;
+ }
+
+ DEBUG(9, ("Copied substring named '%s' value '%s'\n", substr_name, o));
+
+ *out = o;
+ return EOK;
+}
+
+/*
+ * Copies a named substring SUBSTR_NAME from string STR using the parsing
+ * information from PCTX and converts it to an integer.
+ * The context PCTX is also used as a talloc context.
+ *
+ * The resulting string is stored in OUT.
+ * Return value is EOK on no error or ENOENT on error capturing the substring
+ */
+static int substring_strtol(struct parse_ctx *pctx,
+ const char *str,
+ const char *substr_name,
+ int *out)
+{
+ char *substr = NULL;
+ int ret;
+ int val;
+ char *err_ptr;
+
+ ret = copy_substring(pctx, str, substr_name, &substr);
+ if (ret != EOK) {
+ DEBUG(5, ("substring '%s' does not exist\n", substr_name));
+ return ret;
+ }
+
+ errno = 0;
+ val = strtol(substr, &err_ptr, 10);
+ if (substr == '\0' || *err_ptr != '\0' || errno != 0) {
+ DEBUG(5, ("substring '%s' does not contain an integerexist\n",
+ substr));
+ talloc_free(substr);
+ return EINVAL;
+ }
+
+ *out = val;
+ talloc_free(substr);
+ return EOK;
+}
+
+/*
+ * Compiles a regular expression REGEXP and tries to match it against the
+ * string STR. Fills in structure _PCTX with info about matching.
+ *
+ * Returns EOK on no error, EFAULT on bad regexp, EINVAL when it cannot
+ * match the regexp.
+ */
+static int matches_regexp(TALLOC_CTX *ctx,
+ struct time_rules_ctx *trctx,
+ const char *str,
+ enum timelib_rgx regex,
+ struct parse_ctx **_pctx)
+{
+ int ret;
+ struct parse_ctx *pctx = NULL;
+
+ pctx = talloc_zero(ctx, struct parse_ctx);
+ CHECK_PTR(pctx);
+ pctx->ovec = talloc_array(pctx, int, OVEC_SIZE);
+ CHECK_PTR_JMP(pctx->ovec);
+ pctx->re = trctx->re[regex];
+
+ ret = pcre_exec(pctx->re, NULL, str, strlen(str), 0, PCRE_NOTEMPTY, pctx->ovec, OVEC_SIZE);
+ if (ret <= 0) {
+ DEBUG(8, ("string '%s' did *NOT* match regexp '%s'\n", str, lookup_table[regex]));
+ ret = EINVAL;
+ goto done;
+ }
+ DEBUG(8, ("string '%s' matched regexp '%s'\n", str, lookup_table[regex]));
+
+ pctx->matches = ret;
+ *_pctx = pctx;
+ return EOK;
+
+done:
+ talloc_free(pctx);
+ return ret;
+}
+
+/*******************************************************************
+ * date/time helper functions *
+ *******************************************************************/
+
+/*
+ * Returns week number as an integer
+ * This may seem ugly, but I think it's actually less error prone
+ * than writing my own routine
+ */
+static int weeknum(const struct tm *t)
+{
+ char buf[3];
+
+ if (!strftime(buf, 3, "%U", t)) {
+ return -1;
+ }
+
+ /* %U returns 0-53, we want 1-54 */
+ return atoi(buf)+1;
+}
+
+/*
+ * Return the week of the month
+ * Range is 1 to 5
+ */
+static int get_week_of_month(const struct tm *t)
+{
+ int fs; /* first sunday */
+
+ fs = (t->tm_mday % 7) - t->tm_wday;
+ if (fs <= 0) {
+ fs += 7;
+ }
+
+ return (t->tm_mday <= fs) ? 1 : (2 + (t->tm_mday - fs - 1) / 7);
+}
+
+/*
+ * Normalize differencies between our HBAC definition and semantics of
+ * struct tm
+ */
+static void abs2tm(struct tm *t)
+{
+ /* tm defines tm_year as num of yrs since 1900, we have absolute number */
+ t->tm_year %= 1900;
+ /* struct tm defines tm_mon as number of month since January */
+ t->tm_mon--;
+}
+
+/*
+ * Normalize differencies between our HBAC definition and semantics of
+ * struct tm
+ */
+static void tm2abs(struct tm *t)
+{
+ /* tm defines tm_year as num of yrs since 1900, we have absolute number */
+ t->tm_year += 1900;
+ /* struct tm defines tm_mon as number of month since January */
+ t->tm_mon++;
+}
+
+/*******************************************************************
+ * parsing of HBAC rules themselves *
+ *******************************************************************/
+
+/*
+ * Parses generalized time string given in STR and fills the
+ * information into OUT.
+ */
+static int parse_generalized_time(struct parse_ctx *pctx,
+ struct time_rules_ctx *trctx,
+ const char *str,
+ time_t *out)
+{
+ int ret;
+ struct parse_ctx *gctx = NULL;
+ struct tm tm;
+
+ memset(&tm, 0, sizeof(tm));
+ tm.tm_isdst = -1;
+
+ ret = matches_regexp(pctx, trctx, str, LP_RGX_GENERALIZED, &gctx);
+ JMP_NEOK(ret);
+
+ /* compulsory */
+ ret = substring_strtol(gctx, str, "year", &tm.tm_year);
+ JMP_NEOK(ret);
+ ret = substring_strtol(gctx, str, "month", &tm.tm_mon);
+ JMP_NEOK(ret);
+ ret = substring_strtol(gctx, str, "day", &tm.tm_mday);
+ JMP_NEOK(ret);
+ /* optional */
+ ret = substring_strtol(gctx, str, "hour", &tm.tm_hour);
+ JMP_NEOK_LABEL(ret, enoent);
+ ret = substring_strtol(gctx, str, "minute", &tm.tm_min);
+ JMP_NEOK_LABEL(ret, enoent);
+ ret = substring_strtol(gctx, str, "second", &tm.tm_sec);
+ JMP_NEOK_LABEL(ret, enoent);
+
+enoent:
+ if (ret == ENOENT) {
+ ret = EOK;
+ }
+
+ abs2tm(&tm);
+
+ *out = mktime(&tm);
+ DEBUG(3, ("converted to time: '%s'\n", ctime(out)));
+ if (*out == -1) {
+ ret = EINVAL;
+ }
+done:
+ talloc_free(gctx);
+ return ret;
+}
+
+/*
+ * Parses absolute timerange string given in STR and fills the
+ * information into ABS.
+ */
+static int parse_absolute(struct absolute_range *absr,
+ struct time_rules_ctx *trctx,
+ struct parse_ctx *pctx,
+ const char *str)
+{
+ char *from = NULL, *to = NULL;
+ int ret;
+
+ ret = copy_substring(pctx, str, "from", &from);
+ if (ret != EOK) {
+ DEBUG(1, ("Missing required part 'from' in absolute timespec\n"));
+ ret = EINVAL;
+ goto done;
+ }
+ ret = copy_substring(pctx, str, "to", &to);
+ if (ret != EOK) {
+ DEBUG(1, ("Missing required part 'to' in absolute timespec\n"));
+ ret = EINVAL;
+ goto done;
+ }
+
+ ret = parse_generalized_time(pctx, trctx, from, &absr->time_from);
+ if (ret != EOK) {
+ DEBUG(1, ("Cannot parse generalized time - first part\n"));
+ goto done;
+ }
+
+ ret = parse_generalized_time(pctx, trctx, to, &absr->time_to);
+ if (ret != EOK) {
+ DEBUG(1, ("Cannot parse generalized time - second part\n"));
+ goto done;
+ }
+
+ if (difftime(absr->time_to, absr->time_from) < 0) {
+ DEBUG(1, ("Not a valid interval\n"));
+ ret = EINVAL;
+ }
+
+ ret = EOK;
+done:
+ talloc_free(from);
+ talloc_free(to);
+ return ret;
+}
+
+static int parse_hhmm(const char *str, int *hour, int *min)
+{
+ struct tm t;
+ char *err;
+
+ err = strptime(str, "%H%M", &t);
+ if (*err != '\0') {
+ return EINVAL;
+ }
+
+ *hour = t.tm_hour;
+ *min = t.tm_min;
+
+ return EOK;
+}
+
+/*
+ * Parses monthly periodic timerange given in STR.
+ * Fills the information into PER.
+ */
+static int parse_periodic_monthly(TALLOC_CTX *ctx,
+ struct time_rules_ctx *trctx,
+ struct periodic_range *per,
+ const char *str)
+{
+ int ret;
+ struct parse_ctx *mpctx = NULL;
+ char *match = NULL;
+ char *mperspec = NULL;
+
+ /* This code would be much less ugly if RHEL5 PCRE knew about PCRE_DUPNAMES */
+ ret = matches_regexp(ctx, trctx, str, LP_RGX_MDAY, &mpctx);
+ if (ret == EOK) {
+ ret = copy_substring(mpctx, str, "mperspec_day", &mperspec);
+ JMP_NEOK(ret);
+ ret = copy_substring(mpctx, str, "interval_day", &match);
+ JMP_NEOK(ret);
+ BUFFER_OR_JUMP(ctx, per->day_of_month, DAY_OF_MONTH_BUFSIZE);
+ ret = interval2bitfield(mpctx, per->day_of_month, match,
+ 1, DAY_OF_MONTH_MAX, NULL);
+ JMP_NEOK(ret);
+ } else {
+ ret = matches_regexp(ctx, trctx, str, LP_RGX_MWEEK, &mpctx);
+ JMP_NEOK(ret);
+ ret = copy_substring(mpctx, str, "mperspec_week", &mperspec);
+ JMP_NEOK(ret);
+
+ ret = copy_substring(mpctx, str, "interval_week", &match);
+ JMP_NEOK(ret);
+ ret = interval2bitfield(mpctx, &per->week_of_month, match,
+ 1, WEEK_OF_MONTH_MAX, NULL);
+ JMP_NEOK(ret);
+
+ ret = copy_substring(mpctx, str, "day_of_week", &match);
+ JMP_NEOK(ret);
+ ret = interval2bitfield(mpctx, &per->day_of_week, match,
+ 1, DAY_OF_WEEK_MAX, names_day_of_week);
+ JMP_NEOK(ret);
+ }
+
+done:
+ talloc_free(mpctx);
+ return ret;
+}
+
+/*
+ * Parses yearly periodic timerange given in STR.
+ * Fills the information into PER.
+ */
+static int parse_periodic_yearly(TALLOC_CTX *ctx,
+ struct time_rules_ctx *trctx,
+ struct periodic_range *per,
+ const char *str)
+{
+ int ret;
+ struct parse_ctx *ypctx = NULL;
+ char *match = NULL;
+ char *yperspec = NULL;
+
+ ret = matches_regexp(ctx, trctx, str, LP_RGX_YEARLY, &ypctx);
+ JMP_NEOK(ret);
+ ret = copy_substring(ypctx, str, "yperspec_day", &yperspec);
+ if (ret == EOK) {
+ ret = copy_substring(ypctx, str, "day_of_year", &match);
+ JMP_NEOK(ret);
+ BUFFER_OR_JUMP(ypctx, per->day_of_year, DAY_OF_YEAR_BUFSIZE);
+ ret = interval2bitfield(ypctx, per->day_of_year, match,
+ 1, DAY_OF_YEAR_MAX, NULL);
+ JMP_NEOK(ret);
+ }
+
+ if (ret != ENOENT) goto done;
+
+ ret = copy_substring(ypctx, str, "yperspec_week", &yperspec);
+ if (ret == EOK) {
+ ret = copy_substring(ypctx, str, "week_of_year", &match);
+ JMP_NEOK(ret);
+ BUFFER_OR_JUMP(ypctx, per->week_of_year, WEEK_OF_YEAR_BUFSIZE);
+ ret = interval2bitfield(ypctx, per->week_of_year, match,
+ 1, WEEK_OF_YEAR_MAX, NULL);
+ JMP_NEOK(ret);
+
+ talloc_free(match);
+ ret = copy_substring(ypctx, str, "day_of_week", &match);
+ JMP_NEOK(ret);
+ ret = interval2bitfield(ypctx, &per->day_of_week, match,
+ 1, DAY_OF_WEEK_MAX, names_day_of_week);
+ JMP_NEOK(ret);
+ }
+
+ if (ret != ENOENT) goto done;
+
+ ret = copy_substring(ypctx, str, "yperspec_month", &yperspec);
+ JMP_NEOK(ret);
+
+ talloc_free(match);
+ ret = copy_substring(ypctx, str, "month_number", &match);
+ JMP_NEOK(ret);
+ BUFFER_OR_JUMP(ypctx, per->month, MONTH_BUFSIZE);
+ ret = interval2bitfield(ypctx, per->month, match,
+ 1, MONTH_MAX, names_months);
+ JMP_NEOK(ret);
+
+ talloc_free(match);
+ ret = copy_substring(ypctx, str, "m_period", &match);
+ JMP_NEOK(ret);
+ DEBUG(7, ("Monthly year period - calling parse_periodic_monthly()\n"));
+ ret = parse_periodic_monthly(ypctx, trctx, per, match);
+ JMP_NEOK(ret);
+
+done:
+ talloc_free(ypctx);
+ return ret;
+}
+
+/*
+ * Parses weekly periodic timerange given in STR.
+ * Fills the information into PER.
+ */
+static int parse_periodic_weekly(TALLOC_CTX *ctx,
+ struct time_rules_ctx *trctx,
+ struct periodic_range *per,
+ const char *str)
+{
+ int ret;
+ struct parse_ctx *wpctx = NULL;
+ char *dow = NULL;
+
+ ret = matches_regexp(ctx, trctx, str, LP_RGX_WEEKLY, &wpctx);
+ JMP_NEOK(ret);
+
+ ret = copy_substring(wpctx, str, "day_of_week", &dow);
+ JMP_NEOK(ret);
+ DEBUG(8, ("day_of_week = '%s'\n", dow));
+
+ ret = interval2bitfield(wpctx, &per->day_of_week, dow,
+ 1, DAY_OF_WEEK_MAX, names_day_of_week);
+
+done:
+ talloc_free(wpctx);
+ return ret;
+}
+
+static int parse_periodic_time(struct periodic_range *per,
+ struct parse_ctx *pctx,
+ const char *str)
+{
+ char *substr = NULL;
+ int ret;
+
+ int hour_from;
+ int hour_to;
+ int min_from;
+ int min_to;
+
+ /* parse out the time */
+ ret = copy_substring(pctx, str, "timeFrom", &substr);
+ JMP_NEOK(ret);
+ parse_hhmm(substr, &hour_from, &min_from);
+ DEBUG(7, ("Parsed timeFrom: %d:%d\n", hour_from, min_from));
+ JMP_NEOK(ret);
+
+ talloc_free(substr);
+ ret = copy_substring(pctx, str, "timeTo", &substr);
+ JMP_NEOK(ret);
+ parse_hhmm(substr, &hour_to, &min_to);
+ DEBUG(7, ("Parsed timeTo: %d:%d\n", hour_to, min_to));
+ JMP_NEOK(ret);
+
+ /* set the interval */
+ if (hour_from > hour_to ) {
+ set_bit_range(per->hour, 0, hour_to);
+ set_bit_range(per->hour, hour_from, HOUR_MAX);
+ } else {
+ set_bit_range(per->hour, hour_from, hour_to);
+ }
+
+ if (min_from > min_to) {
+ set_bit_range(per->minute, 0, min_to);
+ set_bit_range(per->minute, min_from, MINUTE_MAX);
+ } else {
+ set_bit_range(per->minute, min_from, min_to);
+ }
+
+
+ ret = EOK;
+done:
+ talloc_free(substr);
+ return ret;
+}
+
+/*
+ * Parses periodic timerange given in STR.
+ * Fills the information into PER.
+ */
+static int parse_periodic(struct periodic_range *per,
+ struct time_rules_ctx *trctx,
+ struct parse_ctx *pctx,
+ const char *str)
+{
+ char *substr = NULL;
+ char *period = NULL;
+ int ret;
+
+ /* These are mandatory */
+ BUFFER_OR_JUMP(per, per->hour, HOUR_BUFSIZE);
+ BUFFER_OR_JUMP(per, per->minute, MINUTE_BUFSIZE);
+
+ ret = copy_substring(pctx, str, "perspec", &substr);
+ JMP_NEOK(ret);
+ ret = copy_substring(pctx, str, "period", &period);
+ JMP_NEOK(ret);
+
+ if (strcmp(substr, "yearly") == 0) {
+ DEBUG(5, ("periodic yearly\n"));
+ ret = parse_periodic_yearly(pctx, trctx, per, period);
+ JMP_NEOK(ret);
+ } else if (strcmp(substr, "monthly") == 0) {
+ DEBUG(5, ("periodic monthly\n"));
+ ret = parse_periodic_monthly(pctx, trctx, per, period);
+ JMP_NEOK(ret);
+ } else if (strcmp(substr, "weekly") == 0) {
+ DEBUG(5, ("periodic weekly\n"));
+ ret = parse_periodic_weekly(pctx, trctx, per, period);
+ JMP_NEOK(ret);
+ } else if (strcmp(substr, "daily") == 0) {
+ DEBUG(5, ("periodic daily\n"));
+ } else {
+ DEBUG(1, ("Cannot determine periodic rule type"
+ "(perspec = '%s', period = '%s')\n", substr, period));
+ ret = EINVAL;
+ goto done;
+ }
+
+ talloc_free(period);
+
+ ret = parse_periodic_time(per, pctx, str);
+ JMP_NEOK(ret);
+
+ ret = EOK;
+done:
+ talloc_free(substr);
+ return ret;
+}
+
+/*
+ * Parses time specification given in string RULE into range_ctx
+ * context CTX.
+ */
+static int parse_timespec(struct range_ctx *ctx, const char *rule)
+{
+ int ret;
+ struct parse_ctx *pctx = NULL;
+
+ if (matches_regexp(ctx, ctx->trctx, rule, LP_RGX_ABSOLUTE, &pctx) == EOK) {
+ DEBUG(5, ("Matched absolute range\n"));
+ ctx->type = TYPE_ABSOLUTE;
+ ctx->abs = talloc_zero(ctx, struct absolute_range);
+ CHECK_PTR_JMP(ctx->abs);
+
+ ret = parse_absolute(ctx->abs, ctx->trctx, pctx, rule);
+ JMP_NEOK(ret);
+ } else if (matches_regexp(ctx, ctx->trctx, rule, LP_RGX_PERIODIC, &pctx) == EOK) {
+ DEBUG(5, ("Matched periodic range\n"));
+ ctx->type = TYPE_PERIODIC;
+ ctx->per = talloc_zero(ctx, struct periodic_range);
+ CHECK_PTR_JMP(ctx->per);
+
+ ret = parse_periodic(ctx->per, ctx->trctx, pctx, rule);
+ JMP_NEOK(ret);
+ } else {
+ DEBUG(1, ("Cannot determine rule type\n"));
+ ret = EINVAL;
+ goto done;
+ }
+
+ ret = EOK;
+done:
+ talloc_free(pctx);
+ return ret;
+}
+
+/*******************************************************************
+ * validation of rules against time_t *
+ *******************************************************************/
+
+static int absolute_timerange_valid(struct absolute_range *absr,
+ const time_t now,
+ bool *result)
+{
+ if (difftime(absr->time_from, now) > 0) {
+ DEBUG(3, ("Absolute timerange invalid (before interval)\n"));
+ *result = false;
+ return EOK;
+ }
+
+ if (difftime(absr->time_to, now) < 0) {
+ DEBUG(3, ("Absolute timerange invalid (after interval)\n"));
+ *result = false;
+ return EOK;
+ }
+
+ DEBUG(3, ("Absolute timerange valid\n"));
+ *result = true;
+ return EOK;
+}
+
+static int periodic_timerange_valid(struct periodic_range *per,
+ const time_t now,
+ bool *result)
+{
+ struct tm tm_now;
+ int wnum;
+ int wom;
+
+ memset(&tm_now, 0, sizeof(struct tm));
+ if (localtime_r(&now, &tm_now) == NULL) {
+ DEBUG(0, ("Cannot convert time_t to struct tm\n"));
+ return EFAULT;
+ }
+ DEBUG(9, ("Got struct tm value %s", asctime(&tm_now)));
+ tm2abs(&tm_now);
+
+ wnum = weeknum(&tm_now);
+ if (wnum == -1) {
+ DEBUG(7, ("Cannot get week number"));
+ return EINVAL;
+ }
+ DEBUG(9, ("Week number is %d\n", wnum));
+
+ wom = get_week_of_month(&tm_now);
+ if (wnum == -1) {
+ DEBUG(7, ("Cannot get week of number"));
+ return EINVAL;
+ }
+ DEBUG(9, ("Week of month number is %d\n", wom));
+
+ /* The validation itself */
+ TEST_BIT_RANGE(per->day_of_week, tm_now.tm_wday, result);
+ DEBUG(9, ("day of week OK\n"));
+ TEST_BIT_RANGE_PTR(per->day_of_month, tm_now.tm_mday, result);
+ DEBUG(9, ("day of month OK\n"));
+ TEST_BIT_RANGE(per->week_of_month, wom, result);
+ DEBUG(9, ("week of month OK\n"));
+ TEST_BIT_RANGE_PTR(per->week_of_year, wnum, result);
+ DEBUG(9, ("week of year OK\n"));
+ TEST_BIT_RANGE_PTR(per->month, tm_now.tm_mon, result);
+ DEBUG(9, ("month OK\n"));
+ TEST_BIT_RANGE_PTR(per->day_of_year, tm_now.tm_yday, result);
+ DEBUG(9, ("day of year OK\n"));
+ TEST_BIT_RANGE_PTR(per->hour, tm_now.tm_hour, result);
+ DEBUG(9, ("hour OK\n"));
+ TEST_BIT_RANGE_PTR(per->minute, tm_now.tm_min, result);
+ DEBUG(9, ("minute OK\n"));
+
+ DEBUG(3, ("Periodic timerange valid\n"));
+ *result = true;
+ return EOK;
+}
+
+/*
+ * Returns EOK if the timerange in range_ctx context is valid compared against a
+ * given time_t value in NOW, returns ERANGE if the time value is outside the
+ * specified range.
+ */
+static int timerange_valid(struct range_ctx *ctx,
+ const time_t now,
+ bool *result)
+{
+ int ret;
+
+ switch(ctx->type) {
+ case TYPE_ABSOLUTE:
+ DEBUG(7, ("Checking absolute range\n"));
+ ret = absolute_timerange_valid(ctx->abs, now, result);
+ break;
+
+ case TYPE_PERIODIC:
+ DEBUG(7, ("Checking periodic range\n"));
+ ret = periodic_timerange_valid(ctx->per, now, result);
+ break;
+
+ default:
+ DEBUG(1, ("Unknown range type (%d)\n", ctx->type));
+ ret = EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+/*******************************************************************
+ * public interface *
+ *******************************************************************/
+
+/*
+ * This is actually the meat of the library. The function takes a string
+ * representation of a time rule in STR and time to check against (usually that
+ * would be current time) in NOW.
+ *
+ * It returns EOK if the rule is valid in the current time, ERANGE if not and
+ * EINVAL if the rule cannot be parsed
+ */
+int check_time_rule(TALLOC_CTX *mem_ctx,
+ struct time_rules_ctx *trctx,
+ const char *str,
+ const time_t now,
+ bool *result)
+{
+ int ret;
+ struct range_ctx *ctx;
+
+ ctx = talloc_zero(mem_ctx, struct range_ctx);
+ CHECK_PTR_JMP(ctx);
+ ctx->trctx = trctx;
+
+ DEBUG(9, ("Got time_t value %s", ctime(&now)));
+
+ ret = parse_timespec(ctx, str);
+ if (ret != EOK) {
+ DEBUG(1, ("Cannot parse the time specification (%d)\n", ret));
+ goto done;
+ }
+
+ ret = timerange_valid(ctx, now, result);
+ if (ret != EOK) {
+ DEBUG(1, ("Cannot check the time range (%d)\n", ret));
+ goto done;
+ }
+
+ ret = EOK;
+done:
+ talloc_free(ctx);
+ return EOK;
+}
+
+/*
+ * Frees the resources taken by the precompiled rules
+ */
+static int time_rules_parser_destructor(struct time_rules_ctx *ctx)
+{
+ int i;
+
+ for (i = 0; i< LP_RGX_MAX; ++i) {
+ pcre_free(ctx->re[i]);
+ ctx->re[i] = NULL;
+ }
+
+ return 0;
+}
+
+/*
+ * Initializes the parser by precompiling the regular expressions
+ * for later use
+ */
+int init_time_rules_parser(TALLOC_CTX *mem_ctx,
+ struct time_rules_ctx **_out)
+{
+ const char *errstr;
+ int errval;
+ int errpos;
+ int ret;
+ int i;
+ struct time_rules_ctx *ctx = NULL;
+
+ ctx = talloc_zero(mem_ctx, struct time_rules_ctx);
+ CHECK_PTR(ctx);
+ talloc_set_destructor(ctx, time_rules_parser_destructor);
+
+ /* Precompile regular expressions */
+ for (i = LP_RGX_GENERALIZED; i< LP_RGX_MAX; ++i) {
+ ctx->re[i] = pcre_compile2(lookup_table[i],
+ 0,
+ &errval,
+ &errstr,
+ &errpos,
+ NULL);
+
+ if (ctx->re[i] == NULL) {
+ DEBUG(0, ("Invalid Regular Expression pattern '%s' at position %d"
+ " (Error: %d [%s])\n", lookup_table[i],
+ errpos, errval, errstr));
+ ret = EFAULT;
+ goto done;
+ }
+
+ }
+
+ *_out = ctx;
+ return EOK;
+done:
+ talloc_free(ctx);
+ return ret;
+}
+
diff --git a/server/providers/ipa/ipa_timerules.h b/server/providers/ipa/ipa_timerules.h
new file mode 100644
index 00000000..e1beaa22
--- /dev/null
+++ b/server/providers/ipa/ipa_timerules.h
@@ -0,0 +1,56 @@
+/*
+ SSSD
+
+ IPA Provider Time Rules Parsing
+
+ Authors:
+ Jakub Hrozek <jhrozek@redhat.com>
+
+ Copyright (C) Red Hat, Inc 2009
+
+ 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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __IPA_TIMERULES_H_
+#define __IPA_TIMERULES_H_
+
+#include <stdbool.h>
+#include <talloc.h>
+
+/* Opaque structure given after init */
+struct time_rules_ctx;
+
+/*
+ * Init the parser. Destroy the allocated resources by simply
+ * talloc_free()-ing the time_rules_ctx
+ */
+int init_time_rules_parser(TALLOC_CTX *mem_ctx,
+ struct time_rules_ctx **_out);
+
+/*
+ * This is actually the meat of the library. The function takes a string
+ * representation of a time rule in STR and time to check against (usually that
+ * would be current time) in NOW.
+ *
+ * It returns EOK if the rule can be parsed, error code if not. If the time
+ * given in the NOW parameter would be accepted by the rule, it stores true in
+ * RESULT, false otherwise.
+ */
+int check_time_rule(TALLOC_CTX *mem_ctx,
+ struct time_rules_ctx *trctx,
+ const char *str,
+ const time_t now,
+ bool *result);
+
+#endif /* __IPA_TIMERULES_H_ */
diff --git a/server/tests/ipa_timerules-tests.c b/server/tests/ipa_timerules-tests.c
new file mode 100644
index 00000000..12ce6650
--- /dev/null
+++ b/server/tests/ipa_timerules-tests.c
@@ -0,0 +1,579 @@
+/*
+ Timelib
+
+ test_timelib.c
+
+ Copyright (C) Jakub Hrozek <jhrozek@redhat.com> 2009
+
+ 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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+#define _XOPEN_SOURCE /* strptime */
+
+#include <check.h>
+#include <popt.h>
+#include <time.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdbool.h>
+
+#include "providers/ipa/ipa_timerules.h"
+#include "util/util.h"
+#include "tests/common.h"
+
+#define CHECK_TIME_RULE_LEAK(ctx, tctx, str, now, result) do { \
+ check_leaks_push(ctx); \
+ ret = check_time_rule(ctx, tctx, str, now, result); \
+ check_leaks_pop(ctx); \
+} while (0)
+
+static TALLOC_CTX *ctx;
+
+static void usage(poptContext pc, const char *error)
+{
+ poptPrintUsage(pc, stderr, 0);
+ if (error) fprintf(stderr, "%s", error);
+}
+
+int str2time_t(const char *fmt, const char *str, time_t *out)
+{
+ char *err;
+ struct tm stm;
+ memset(&stm, 0, sizeof(struct tm));
+
+ err = strptime(str, fmt, &stm);
+ if(!err || err[0] != '\0')
+ return EINVAL;
+
+ DEBUG(9, ("after strptime: %s", asctime(&stm)));
+ stm.tm_isdst = -1;
+ *out = mktime(&stm);
+ DEBUG(9, ("after mktime: %s", ctime(out)));
+ return (*out == -1) ? EINVAL : EOK;
+}
+
+/* Fixtures - open the time library before every test, close it afterwards */
+void setup(void)
+{
+ leak_check_setup();
+
+ ctx = talloc_new(NULL);
+ fail_if(ctx == NULL);
+}
+
+void teardown(void)
+{
+ leak_check_teardown();
+}
+
+/* Test that timelib detects a time rule inside the absolute range */
+START_TEST(test_timelib_absolute_in_range)
+{
+ time_t now;
+ int ret;
+ bool result;
+ static struct time_rules_ctx *tctx = NULL;
+
+
+ ret = init_time_rules_parser(ctx, &tctx);
+ fail_if(ret != EOK);
+
+ fail_unless(str2time_t("%F", "2000-1-1", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "absolute 199412161032.5 ~ 200512161032,5", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule");
+ fail_unless(result == true, "Range check error");
+
+ talloc_free(tctx);
+}
+END_TEST
+
+/* Test that timelib detects a time rule outside the absolute range */
+START_TEST(test_timelib_absolute_out_of_range)
+{
+ time_t now;
+ int ret;
+ bool result;
+ static struct time_rules_ctx *tctx = NULL;
+
+ ret = init_time_rules_parser(ctx, &tctx);
+ fail_if(ret != EOK);
+
+ fail_unless(str2time_t("%F", "2007-1-1", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "absolute 199412161032.5 ~ 200512161032,5", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule");
+ fail_unless(result == false, "Range check error");
+
+ talloc_free(tctx);
+}
+END_TEST
+
+/* Test that absolute timeranges work OK with only minimal data supplied */
+START_TEST(test_timelib_absolute_minimal)
+{
+ time_t now;
+ int ret;
+ bool result;
+ static struct time_rules_ctx *tctx = NULL;
+
+ ret = init_time_rules_parser(ctx, &tctx);
+ fail_if(ret != EOK);
+
+ fail_unless(str2time_t("%F", "2000-1-1", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "absolute 19941216 ~ 20051216", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule");
+ fail_unless(result == true, "Range check error");
+
+ talloc_free(tctx);
+}
+END_TEST
+
+/* Test a time value "right off the edge" of the time specifier */
+START_TEST(test_timelib_absolute_one_off)
+{
+ time_t now;
+ int ret;
+ bool result;
+ static struct time_rules_ctx *tctx = NULL;
+
+ ret = init_time_rules_parser(ctx, &tctx);
+ fail_if(ret != EOK);
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M-%S", "1994-12-16-10-32-29", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "absolute 19941216103230 ~ 19941216103231", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule");
+ fail_unless(result == false, "Range check error");
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M-%S", "1994-12-16-10-32-32", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "absolute 19941216103230 ~ 19941216103231", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule");
+ fail_unless(result == false, "Range check error");
+
+ talloc_free(tctx);
+}
+END_TEST
+
+
+/* Test a time value "right on the edge" of the time specifier */
+START_TEST(test_timelib_absolute_one_on)
+{
+ time_t now;
+ int ret;
+ bool result;
+ static struct time_rules_ctx *tctx = NULL;
+
+ ret = init_time_rules_parser(ctx, &tctx);
+ fail_if(ret != EOK);
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M-%S", "1994-12-16-10-32-30", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "absolute 19941216103230 ~ 19941216103231", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule");
+ fail_unless(result == true, "Range check error");
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M-%S", "1994-12-16-10-32-31", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "absolute 19941216103230 ~ 19941216103231", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule");
+ fail_unless(result == true, "Range check error");
+
+ talloc_free(tctx);
+}
+END_TEST
+
+START_TEST(test_timelib_periodic_daily_in)
+{
+ time_t now;
+ int ret;
+ bool result;
+ static struct time_rules_ctx *tctx = NULL;
+
+ ret = init_time_rules_parser(ctx, &tctx);
+ fail_if(ret != EOK);
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-03-30-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic daily 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule");
+ fail_unless(result == true, "Range check error");
+
+ /* test edges */
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-03-30-09-30", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic daily 0930 ~ 1830", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule (edge1)");
+ fail_unless(result == true, "Range check error");
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-03-30-18-30", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic daily 0930 ~ 1830", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule (edge2)");
+ fail_unless(result == true, "Range check error");
+
+ /* test wrap around */
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-03-30-16-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic daily 1500 ~ 0600", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule (edge1)");
+ fail_unless(result == true, "Range check error1");
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-03-30-15-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic daily 1500 ~ 0600", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule (edge1)");
+ fail_unless(result == true, "Range check error1");
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-03-30-06-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic daily 1500 ~ 0600", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule (edge1)");
+ fail_unless(result == true, "Range check error1");
+
+ talloc_free(tctx);
+}
+END_TEST
+
+START_TEST(test_timelib_periodic_daily_out)
+{
+ time_t now;
+ int ret;
+ bool result;
+ static struct time_rules_ctx *tctx = NULL;
+
+ ret = init_time_rules_parser(ctx, &tctx);
+ fail_if(ret != EOK);
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-03-30-21-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic daily 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule");
+ fail_unless(result == false, "Range check error");
+
+ /* test one-off errors */
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-03-30-09-29", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic daily 0930 ~ 1830", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule (one-off 1)");
+ fail_unless(result == false, "Range check error");
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-03-30-18-31", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic daily 0930 ~ 1830", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule (one-off 2)");
+ fail_unless(result == false, "Range check error");
+
+ /* test wrap around */
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-03-30-10-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic daily 1500 ~ 0600", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule (edge1)");
+ fail_unless(result == false, "Range check error");
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-03-30-14-59", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic daily 1500 ~ 0600", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule (edge1)");
+ fail_unless(result == false, "Range check error1");
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-03-30-06-01", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic daily 1500 ~ 0600", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule (edge1)");
+ fail_unless(result == false, "Range check error2");
+
+ talloc_free(tctx);
+}
+END_TEST
+
+START_TEST(test_timelib_periodic_weekly_in)
+{
+ time_t now;
+ int ret;
+ bool result;
+ static struct time_rules_ctx *tctx = NULL;
+
+ ret = init_time_rules_parser(ctx, &tctx);
+ fail_if(ret != EOK);
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-04-02-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic weekly day Mon-Fri 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule");
+ fail_unless(result == true, "Range check error1");
+
+ /* test edges - monday */
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-06-22-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic weekly day Mon-Fri 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule");
+ fail_unless(result == true, "Range check error2");
+
+ /* test edges - friday */
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-06-26-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic weekly day Mon-Fri 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule");
+ fail_unless(result == true, "Range check error3");
+
+ /* test wrap around */
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-11-03-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic weekly day Fri-Tue 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule");
+ fail_unless(result == true, "Range check error2");
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-11-06-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic weekly day Fri-Tue 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule");
+ fail_unless(result == true, "Range check error3");
+
+ talloc_free(tctx);
+}
+END_TEST
+
+START_TEST(test_timelib_periodic_weekly_out)
+{
+ time_t now;
+ int ret;
+ bool result;
+ static struct time_rules_ctx *tctx = NULL;
+
+ ret = init_time_rules_parser(ctx, &tctx);
+ fail_if(ret != EOK);
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-04-04-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic weekly day Mon-Fri 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule");
+ fail_unless(result == false, "Range check error");
+
+ /* test one-off error - monday */
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-06-22-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic weekly day Tue-Thu 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule");
+ fail_unless(result == false, "Range check error");
+
+ /* test one-off error - friday */
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-06-26-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic weekly day Tue-Thu 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule");
+ fail_unless(result == false, "Range check error");
+
+ /* test wrap around */
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-11-04-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic weekly day Fri-Tue 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule");
+ fail_unless(result == false, "Range check error2");
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-11-05-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic weekly day Fri-Tue 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule");
+ fail_unless(result == false, "Range check error3");
+
+ talloc_free(tctx);
+}
+END_TEST
+
+START_TEST(test_timelib_periodic_monthly_in)
+{
+ time_t now;
+ int ret;
+ bool result;
+ static struct time_rules_ctx *tctx = NULL;
+
+ ret = init_time_rules_parser(ctx, &tctx);
+ fail_if(ret != EOK);
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-04-07-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic monthly week 1,2 day Mon,Tue 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule1 (ret = %d: %s)", ret, strerror(ret));
+ fail_unless(result == true, "Range check error");
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-04-05-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic monthly day 1-5,10,15,20-25 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule2 (ret = %d: %s)", ret, strerror(ret));
+ fail_unless(result == true, "Range check error");
+
+ /* edges - week in */
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-06-13-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic monthly week 1,2 day Sat 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule (week edge 1)");
+ fail_unless(result == true, "Range check error");
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-06-29-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic monthly week 5 day Mon,Tue 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule (week edge 2)");
+ fail_unless(result == true, "Range check error");
+
+ /* edges - day in */
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-04-01-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic monthly day 1-10 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule (day edge 1)");
+ fail_unless(result == true, "Range check error");
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-04-10-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic monthly day 1-10 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule (day edge 2)");
+ fail_unless(result == true, "Range check error");
+
+ talloc_free(tctx);
+}
+END_TEST
+
+START_TEST(test_timelib_periodic_monthly_out)
+{
+ time_t now;
+ int ret;
+ bool result;
+ static struct time_rules_ctx *tctx = NULL;
+
+ ret = init_time_rules_parser(ctx, &tctx);
+ fail_if(ret != EOK);
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-06-03-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic monthly week 1,2 day Mon,Tue 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule (ret = %d)", ret);
+ fail_unless(result == false, "Range check error");
+
+ /* one-off error - week out */
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-06-15-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic monthly week 1 day Sun-Sat 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule (week edge 1)");
+ fail_unless(result == false, "Range check error");
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-06-28-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic monthly week 5 day Mon,Tue 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule (week edge 2)");
+ fail_unless(result == false, "Range check error");
+
+ /* one-off error - day out */
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-04-01-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic monthly day 2-10 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule (day edge 1)");
+ fail_unless(result == false, "Range check error");
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-04-11-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic monthly day 1-10 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule (day edge 2)");
+ fail_unless(result == false, "Range check error");
+
+ talloc_free(tctx);
+}
+END_TEST
+
+START_TEST(test_timelib_periodic_yearly_in)
+{
+ time_t now;
+ int ret;
+ bool result;
+ static struct time_rules_ctx *tctx = NULL;
+
+ ret = init_time_rules_parser(ctx, &tctx);
+ fail_if(ret != EOK);
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-08-03-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic yearly month 1,7-8 day 1-10 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule (ret = %d)", ret);
+ fail_unless(result == true, "Range check error1");
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-01-01-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic yearly month 1,7-8 day 1-10 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule (ret = %d)", ret);
+ fail_unless(result == true, "Range check error2");
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-01-01-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic yearly week 1 day 1-7 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule (ret = %d)", ret);
+ fail_unless(result == true, "Range check error3");
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-07-10-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic yearly month 1,7-8 day 1-10 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule (ret = %d)", ret);
+ fail_unless(result == true, "Range check error4");
+
+ talloc_free(tctx);
+}
+END_TEST
+
+START_TEST(test_timelib_periodic_yearly_out)
+{
+ time_t now;
+ int ret;
+ bool result;
+ static struct time_rules_ctx *tctx = NULL;
+
+ ret = init_time_rules_parser(ctx, &tctx);
+ fail_if(ret != EOK);
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-06-13-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic yearly month 7-8 day 1-10 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule1 (ret = %d)", ret);
+ fail_unless(result == false, "Range check error");
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-09-13-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic yearly month 7-8 day 1-10 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule2 (ret = %d)", ret);
+ fail_unless(result == false, "Range check error");
+
+ fail_unless(str2time_t("%Y-%m-%d-%H-%M", "2009-01-11-12-00", &now) == 0, "Cannot parse time spec");
+ CHECK_TIME_RULE_LEAK(ctx, tctx, "periodic yearly month 1,7-8 day 1-10 0900 ~ 1800", now, &result);
+ fail_unless(ret == EOK, "Fail to check the time rule3 (ret = %d)", ret);
+ fail_unless(result == false, "Range check error");
+
+ talloc_free(tctx);
+}
+END_TEST
+
+Suite *create_timelib_suite(void)
+{
+ Suite *s = suite_create("timelib");
+
+ TCase *tc_timelib = tcase_create("Timelib Tests");
+
+
+ /* Add setup() and teardown() methods */
+ tcase_add_checked_fixture(tc_timelib, setup, teardown);
+
+ /* Do some testing */
+ tcase_add_test(tc_timelib, test_timelib_absolute_in_range);
+ tcase_add_test(tc_timelib, test_timelib_absolute_out_of_range);
+ tcase_add_test(tc_timelib, test_timelib_absolute_minimal);
+ tcase_add_test(tc_timelib, test_timelib_absolute_one_off);
+ tcase_add_test(tc_timelib, test_timelib_absolute_one_on);
+
+ tcase_add_test(tc_timelib, test_timelib_periodic_daily_in);
+ tcase_add_test(tc_timelib, test_timelib_periodic_daily_out);
+ tcase_add_test(tc_timelib, test_timelib_periodic_weekly_in);
+ tcase_add_test(tc_timelib, test_timelib_periodic_weekly_out);
+ tcase_add_test(tc_timelib, test_timelib_periodic_monthly_in);
+ tcase_add_test(tc_timelib, test_timelib_periodic_monthly_out);
+ tcase_add_test(tc_timelib, test_timelib_periodic_yearly_in);
+ tcase_add_test(tc_timelib, test_timelib_periodic_yearly_out);
+
+ /* Add all test cases to the test suite */
+ suite_add_tcase(s, tc_timelib);
+
+ return s;
+}
+
+int main(int argc, const char *argv[])
+{
+ int ret;
+ poptContext pc;
+ int failure_count;
+ Suite *timelib_suite;
+ SRunner *sr;
+ int debug = 0;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ { "debug-level", 'd', POPT_ARG_INT, &debug, 0, "Set debug level", NULL },
+ POPT_TABLEEND
+ };
+
+ pc = poptGetContext(NULL, argc, (const char **) argv, long_options, 0);
+ if((ret = poptGetNextOpt(pc)) < -1) {
+ usage(pc, poptStrerror(ret));
+ return EXIT_FAILURE;
+ }
+ debug_level = debug;
+
+ timelib_suite = create_timelib_suite();
+ sr = srunner_create(timelib_suite);
+ srunner_run_all(sr, CK_VERBOSE);
+ failure_count = srunner_ntests_failed(sr);
+ srunner_free(sr);
+ return (failure_count==0 ? EXIT_SUCCESS : EXIT_FAILURE);
+}
+