diff options
author | Sergey Poznyakoff <gray@gnu.org.ua> | 2012-02-17 15:19:58 +0200 |
---|---|---|
committer | Sergey Poznyakoff <gray@gnu.org.ua> | 2012-02-17 15:29:01 +0200 |
commit | 73b4e38d693e80c8e2f8c8f990e063c9e919894a (patch) | |
tree | 6c3bb78aa39d030a7a680c179edb6bc81efbab4f | |
parent | cc8e335a68ab8232837515ba3f91b42f7dc7b9e9 (diff) | |
download | slb-73b4e38d693e80c8e2f8c8f990e063c9e919894a.tar.gz slb-73b4e38d693e80c8e2f8c8f990e063c9e919894a.tar.bz2 |
Extend the functionality of assertions.
* NEWS: Update.
* doc/config.texi: Update.
* src/Makefile.am (slb_SOURCES): Add wildmat.c.
* src/wildmat.c: New file.
* src/config.c (cb_server_assert): Take a single string as
argument, optionally allowing three distinct arguments as
a kind of syntax sugar. Support various string and arithmetical
comparisons.
* src/slb.h (SLB_ASSERT_NEG,SLB_ASSERT_ICASE): New flags.
(SLB_ASSERT_EQ,SLB_ASSERT_PREFIX,SLB_ASSERT_SUFFIX)
(SLB_ASSERT_GLOB,SLB_ASSERT_EQUAL,SLB_ASSERT_LT)
(SLB_ASSERT_LE): New opcodes.
(slb_assertion) <opcode,vallen>: New members.
(wildmatch): New proto. (SLB_ASSERT_NEG,SLB_ASSERT_ICASE): New flags.
(SLB_ASSERT_EQ,SLB_ASSERT_PREFIX,SLB_ASSERT_SUFFIX)
(SLB_ASSERT_GLOB,SLB_ASSERT_EQUAL,SLB_ASSERT_LT)
(SLB_ASSERT_LE): New opcodes.
(slb_assertion) <opcode,vallen>: New members.
(wildmatch): New proto.
* src/snmploop.c (assertion_test): Handle new opcodes. Revert the
meaning of the return code. All uses updated.
-rw-r--r-- | NEWS | 24 | ||||
-rw-r--r-- | doc/config.texi | 24 | ||||
-rw-r--r-- | src/Makefile.am | 3 | ||||
-rw-r--r-- | src/config.c | 162 | ||||
-rw-r--r-- | src/slb.h | 20 | ||||
-rw-r--r-- | src/snmploop.c | 37 | ||||
-rw-r--r-- | src/wildmat.c | 132 |
7 files changed, 357 insertions, 45 deletions
@@ -1,4 +1,4 @@ -SLB NEWS -- history of user-visible changes. 2012-02-16 +SLB NEWS -- history of user-visible changes. 2012-02-17 Copyright (C) 2011, 2012 Sergey Poznyakoff See the end of file for copying conditions. @@ -30,7 +30,29 @@ the value "eth1". For example, if the SNMP tree has the following MIB IF-MIB::ifDescr.10: eth0 then the expression "$iftable[eth]" yields "10" + +* Assertion syntax changed. + +The assertion statement takes a single argument, which must be a +string consisting of the following three parts: + + <oid: string> [!]<opcode>[/i] <value: string> + +The <opcode> part can be either an arithmetical operator (=, <, <=, >, +>=), or any of the following string operators: + + eq string equality + ne string inequality + prefix oid value must begin with <value> + suffix oid value must end with <value> + glob <value> is a glob(7) pattern that oid value must match +Each of these can be suffixed with "/i" to request case-insensitive comparison. + +A "!" in front of opcode reverts its meaning. + +The <value> part must not include the type prefix. + Version 1.0, 2011-04-26 diff --git a/doc/config.texi b/doc/config.texi index e39819f..12c9525 100644 --- a/doc/config.texi +++ b/doc/config.texi @@ -1091,9 +1091,10 @@ Defines a macro @var{name} to expand to @var{expansion}. Macros allow to customize output formats on a per-server basis. @xref{macro}. @end deffn -@deffn {Config: server} assert oid op pattern +@deffn {Config: server} assert "oid op pattern" Ensures that the value of SNMP variable @var{oid} matches -@var{pattern}. The type of match is given by the @var{op} argument: +@var{pattern}. The type of match is given by the @var{op} part. +The following @var{op} values indicate string comparisons: @table @asis @item eq @@ -1101,8 +1102,25 @@ The value must match @var{pattern} exactly. @item ne The value must not match @var{pattern}. + +@item prefix +The value must begin with @var{pattern}. + +@item suffix +The value must end with @var{pattern}. + +@item glob +The @var{pattern} is a glob(7) pattern which the value must match. @end table +These values can be suffixed with @samp{/i} to indicate +case-insensitive comparison. + +A @samp{!} appearing before @var{op} reverses its meaning. + +The following @var{op} codes specify numeric comparisons: +@samp{=}, @samp{!=}, @samp{<}, @samp{<=}, @samp{>}, @samp{>=}. + If the assertion fails, the server is excluded from the load table. Use this statement to ensure that a variable used in the computation @@ -1112,7 +1130,7 @@ interface 3), it would be wise to ensure that the 3rd row refers to the interface in question (say @samp{eth1}): @example -assert "IF-MIB::ifDescr.3" eq "eth1"; +assert "IF-MIB::ifDescr.3 eq eth1"; @end example See also @ref{table, table slices}, for a more elegant way of diff --git a/src/Makefile.am b/src/Makefile.am index 827270c..0c38f64 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -28,7 +28,8 @@ slb_SOURCES =\ slb.h\ test.c\ snmploop.c\ - symimp.c + symimp.c\ + wildmat.c BUILT_SOURCES=cmdline.h EXTRA_DIST=cmdline.opt expr.output diff --git a/src/config.c b/src/config.c index b7566b5..93683f0 100644 --- a/src/config.c +++ b/src/config.c @@ -381,36 +381,71 @@ cb_server_constant(enum grecs_callback_command cmd, return 0; } -int -cb_server_assert(enum grecs_callback_command cmd, - grecs_locus_t *locus, - void *varptr, - grecs_value_t *value, - void *cb_data) +struct asrtop { + char *id; + int opcode; + int flags; +}; + +static struct asrtop asrtop[] = { + { "eq", SLB_ASSERT_EQ, 0 }, + { "eq/i", SLB_ASSERT_EQ, SLB_ASSERT_ICASE }, + { "ne", SLB_ASSERT_EQ, SLB_ASSERT_NEG }, + { "ne/i", SLB_ASSERT_EQ, SLB_ASSERT_NEG|SLB_ASSERT_ICASE }, + { "prefix", SLB_ASSERT_PREFIX, 0 }, + { "prefix/i", SLB_ASSERT_PREFIX, SLB_ASSERT_ICASE }, + { "suffix", SLB_ASSERT_SUFFIX, 0 }, + { "suffix/i", SLB_ASSERT_SUFFIX, SLB_ASSERT_ICASE }, + { "glob", SLB_ASSERT_GLOB, 0 }, + { "glob/i", SLB_ASSERT_GLOB, SLB_ASSERT_ICASE }, + { "=", SLB_ASSERT_EQUAL }, + { "<=", SLB_ASSERT_LE }, + { "<", SLB_ASSERT_LT }, + { ">=", SLB_ASSERT_LT, SLB_ASSERT_NEG }, + { ">", SLB_ASSERT_LE, SLB_ASSERT_NEG }, + { NULL } +}; + + +static struct asrtop * +find_asrtop(char *s) +{ + struct asrtop *op; + for (op = asrtop; op->id; op++) + if (strcmp(s, op->id) == 0) + return op; + return NULL; +} + +static int +parse_assertion(char **argv, struct grecs_symtab *at, grecs_locus_t *locus) { - struct grecs_symtab *at = *(struct grecs_symtab **)varptr; struct slb_assertion *ap; int install; - int flags; + int flags = 0; + int opcode; oid oid[MAX_OID_LEN]; size_t oidlen; size_t len; + char *s; + struct asrtop *op; - if (assert_n_strings(locus, cmd, value, 3, 3)) - return 1; - - if (strcmp(value->v.arg.v[1]->v.string, "eq") == 0) - flags = 0; - else if (strcmp(value->v.arg.v[1]->v.string, "ne") == 0) - flags = SLB_ASSERT_NE; - else { - grecs_error(locus, 0, _("invalid operation: %s"), - value->v.arg.v[1]->v.string); + s = argv[1]; + if (*s == '!') { + ++s; + flags |= SLB_ASSERT_NEG; + } + + op = find_asrtop(s); + if (!op) { + grecs_error(locus, 0, _("invalid operation: %s"), s); return 1; } + opcode = op->opcode; + flags ^= op->flags; oidlen = MAX_OID_LEN; - if (!read_objid(value->v.arg.v[0]->v.string, oid, &oidlen)) { + if (!read_objid(argv[0], oid, &oidlen)) { grecs_error(locus, 0, _("cannot parse oid")); return 1; } @@ -418,27 +453,74 @@ cb_server_assert(enum grecs_callback_command cmd, install = 1; ap = assertion_lookup(at, oid, oidlen, &install); if (!install) - grecs_error(locus, 0, - _("redefinition of %s"), - value->v.arg.v[0]->v.string); + grecs_error(locus, 0, _("redefinition of %s"), argv); - len = strlen(value->v.arg.v[0]->v.string) + 1 + - strlen(value->v.arg.v[1]->v.string) + 1 + - strlen(value->v.arg.v[2]->v.string) + 1; + ap->opcode = opcode; + ap->flags = flags; + ap->value = grecs_strdup(argv[2]); + ap->vallen = strlen(ap->value); + clone_locus(&ap->locus, locus); + + len = strlen(argv[0]) + 1 + + strlen(argv[1]) + 1 + + strlen(argv[2]) + 1; ap->text = grecs_malloc(len); - strcpy(ap->text, value->v.arg.v[0]->v.string); + strcpy(ap->text, argv[0]); strcat(ap->text, " "); - strcat(ap->text, value->v.arg.v[1]->v.string); + strcat(ap->text, argv[1]); strcat(ap->text, " "); - strcat(ap->text, value->v.arg.v[2]->v.string); - - ap->value = grecs_strdup(value->v.arg.v[2]->v.string); - ap->flags = flags; - clone_locus(&ap->locus, locus); + strcat(ap->text, argv[2]); return 0; } int +cb_server_assert(enum grecs_callback_command cmd, + grecs_locus_t *locus, + void *varptr, + grecs_value_t *value, + void *cb_data) +{ + struct grecs_symtab *at = *(struct grecs_symtab **)varptr; + int rc; + + if (assert_n_strings(locus, cmd, value, 1, 3)) + return 1; + + if (value->type == GRECS_TYPE_STRING) { + struct wordsplit ws; + + if (wordsplit(value->v.string, &ws, WRDSF_DEFFLAGS)) { + grecs_error(locus, 0, + _("can't split argument: %s"), + wordsplit_strerror(&ws)); + return 1; + } + if (ws.ws_wordc > 3) { + grecs_error(locus, 0, + _("too many arguments to assertion")); + wordsplit_free(&ws); + return 1; + } else if (ws.ws_wordc < 3) { + grecs_error(locus, 0, + _("too few arguments to assertion")); + wordsplit_free(&ws); + return 1; + } + rc = parse_assertion(ws.ws_wordv, at, locus); + wordsplit_free(&ws); + } else { + char *argv[4]; + argv[0] = value->v.arg.v[0]->v.string; + argv[1] = value->v.arg.v[1]->v.string; + argv[2] = value->v.arg.v[2]->v.string; + argv[3] = NULL; + rc = parse_assertion(argv, at, locus); + } + + return rc; +} + +int cb_server_macro(enum grecs_callback_command cmd, grecs_locus_t *locus, void *varptr, @@ -771,8 +853,20 @@ static struct grecs_keyword server_kw[] = { grecs_type_string, GRECS_MULT, NULL, offsetof(struct slb_server, macros), cb_server_macro }, - { "assert", N_("<oid: string> {eq|ne} <value: string>"), - N_("Ensure that <oid> has the given <value>"), + { "assert", N_("<arg: <oid: string> [!]<opcode>[/i] <value: string>>"), + N_("Ensure that <oid> matches the given <value> as per <opcode>.\n" + "Valid opcodes are:\n" + "Arithmetical operations: =, <, <=, >, >=\n" + "String operations:\n" + " eq string equality\n" + " ne string inequality\n" + " prefix oid value must begin with <value>\n" + " suffix oid value must end with <value>\n" + " glob <value> is a glob(7) pattern that oid value must match\n" + "Each of these can be suffixed with \"/i\" to request case-insensitive\n" + "comparison.\n" + "\n" + "A \"!\" in front of opcode reverts its meaning."), grecs_type_string, GRECS_MULT, NULL, offsetof(struct slb_server, assertions), cb_server_assert }, @@ -311,13 +311,25 @@ char *slb_format_oid_value(oid *oid, size_t len, struct variable_list *vp, -#define SLB_ASSERT_NE 0x01 +#define SLB_ASSERT_NEG 0x01 +#define SLB_ASSERT_ICASE 0x02 + +/* Opcodes */ +#define SLB_ASSERT_EQ 1 +#define SLB_ASSERT_PREFIX 2 +#define SLB_ASSERT_SUFFIX 3 +#define SLB_ASSERT_GLOB 4 +#define SLB_ASSERT_EQUAL 5 +#define SLB_ASSERT_LT 6 +#define SLB_ASSERT_LE 7 struct slb_assertion { oid name[MAX_OID_LEN]; size_t length; + int opcode; int flags; char *value; + size_t vallen; char *text; grecs_locus_t locus; struct slb_format_cache cache; @@ -406,8 +418,8 @@ extern int syslog_include_prio; void logger_setup(void); void logmsg(int prio, const char *fmt, ...) SLB_PRINTFLIKE(2,3); -void logmsg_at_locus(int prio, grecs_locus_t const *locus, const char *fmt, ...) - SLB_PRINTFLIKE(3,4); +void logmsg_at_locus(int prio, grecs_locus_t const *locus, + const char *fmt, ...) SLB_PRINTFLIKE(3,4); void debug_printf(const char *fmt, ...) SLB_PRINTFLIKE(1,2); int string_to_syslog_facility(const char *str, int *pfacility); @@ -438,4 +450,6 @@ void close_fds(fd_set *fdset); int slb_test(const char *name); +int wildmatch(char *expr, char *name, int icase); + diff --git a/src/snmploop.c b/src/snmploop.c index 84e30fe..5d1e98e 100644 --- a/src/snmploop.c +++ b/src/snmploop.c @@ -134,8 +134,39 @@ assertion_test(struct slb_assertion *ap, struct variable_list *vp) buf = slb_format_oid_value(ap->name, ap->length, vp, &ap->cache); if (!buf) return 1; - rc = strcmp(ap->value, buf); - if (ap->flags & SLB_ASSERT_NE) + switch (ap->opcode) { + case SLB_ASSERT_EQ: + rc = ((ap->flags & SLB_ASSERT_ICASE) ? strcasecmp : strcmp) + (ap->value, buf)== 0; + break; + case SLB_ASSERT_PREFIX: + rc = strlen(buf) >= ap->vallen && + ((ap->flags & SLB_ASSERT_ICASE) ? + strncasecmp : strncmp) (buf, ap->value, ap->vallen) + == 0; + break; + case SLB_ASSERT_SUFFIX: + rc = strlen(buf) >= ap->vallen && + ((ap->flags & SLB_ASSERT_ICASE) ? + strncasecmp : strncmp) + (buf + strlen(buf) - ap->vallen, + ap->value, ap->vallen) == 0; + break; + case SLB_ASSERT_GLOB: + rc = wildmatch(ap->value, buf, + ap->flags & SLB_ASSERT_ICASE) == 0; + break; + case SLB_ASSERT_EQUAL: + /* FIXME */ + rc = strtoul(buf, NULL, 10) == strtoul(ap->value, NULL, 10); + break; + case SLB_ASSERT_LT: + rc = strtoul(buf, NULL, 10) < strtoul(ap->value, NULL, 10); + break; + case SLB_ASSERT_LE: + rc = strtoul(buf, NULL, 10) <= strtoul(ap->value, NULL, 10); + } + if (ap->flags & SLB_ASSERT_NEG) rc = !rc; return rc; } @@ -153,7 +184,7 @@ process_result(struct slb_server *srv, struct snmp_pdu *pdu) ap = assertion_lookup(srv->assertions, vp->name, vp->name_length, NULL); - if (ap && assertion_test(ap, vp)) { + if (ap && !assertion_test(ap, vp)) { logmsg(LOG_NOTICE, "%s:%d: %s: assertion \"%s\" failed, got %s", ap->locus.beg.file, ap->locus.beg.line, diff --git a/src/wildmat.c b/src/wildmat.c new file mode 100644 index 0000000..f1b31be --- /dev/null +++ b/src/wildmat.c @@ -0,0 +1,132 @@ +/* This file is part of SLB + Copyright (C) 2011, 2012 Sergey Poznyakoff + + SLB 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, or (at your option) + any later version. + + SLB 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 SLB. If not, see <http://www.gnu.org/licenses/>. */ + +#include "slb.h" +#include "wordsplit.h" + +#define WILD_FALSE 0 +#define WILD_TRUE 1 +#define WILD_ABORT 2 + +int +match_char_class(char **pexpr, char c, int icase) +{ + int res; + int rc; + char *expr = *pexpr; + + if (icase) + c = toupper(c); + + expr++; + if (*expr == '^') { + res = 0; + expr++; + } else + res = 1; + + if (*expr == '-' || *expr == ']') + rc = c == *expr++; + else + rc = !res; + + for (; *expr && *expr != ']'; expr++) { + if (rc == res) { + if (*expr == '\\' && expr[1] == ']') + expr++; + } else if (expr[1] == '-') { + if (*expr == '\\') + rc = *++expr == c; + else { + if (icase) + rc = toupper(*expr) <= c && + c <= toupper(expr[2]); + else + rc = *expr <= c && c <= expr[2]; + expr += 2; + } + } else if (*expr == '\\' && expr[1] == ']') + rc = *++expr == c; + else if (icase) + rc = toupper(*expr) == c; + else + rc = *expr == c; + } + *pexpr = *expr ? expr + 1 : expr; + return rc == res; +} + +int +_wild_match(char *expr, char *name, int icase) +{ + int c; + + while (expr && *expr) { + if (*name == 0 && *expr != '*') + return WILD_ABORT; + switch (*expr) { + case '*': + while (*++expr == '*') + ; + if (*expr == 0) + return WILD_TRUE; + while (*name) { + int res = _wild_match(expr, name++, icase); + if (res != WILD_FALSE) + return res; + } + return WILD_ABORT; + + case '?': + expr++; + if (*name == 0) + return WILD_FALSE; + name++; + break; + + case '[': + if (!match_char_class(&expr, *name, icase)) + return WILD_FALSE; + name++; + break; + + case '\\': + if (expr[1]) { + c = *++expr; expr++; + if (*name != wordsplit_c_unquote_char(c)) + return WILD_FALSE; + name++; + break; + } + /* fall through */ + default: + if (icase) { + if (toupper(*expr) != toupper(*name)) + return WILD_FALSE; + } else if (*expr != *name) + return WILD_FALSE; + expr++; + name++; + } + } + return *name == 0; +} + +int +wildmatch(char *expr, char *name, int icase) +{ + return _wild_match(expr, name, icase) != WILD_TRUE; +} |