Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add 'exists()' script function to check if variable exists #4435

Merged
merged 1 commit into from
Oct 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 4 additions & 1 deletion grammar/grammar.y
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* PRI filter) are very hard to beat in ease of use, at least for simpler
* cases.
*
* Copyright 2011-2016 Rainer Gerhards and Adiscon GmbH.
* Copyright 2011-2020 Rainer Gerhards and Adiscon GmbH.
*
* This file is part of the rsyslog runtime library.
*
Expand Down Expand Up @@ -55,6 +55,7 @@ extern int yyerror(const char*);
struct cnfexpr *expr;
struct cnfarray *arr;
struct cnffunc *func;
struct cnffuncexists *exists;
struct cnffparamlst *fparams;
struct cnfitr *itr;
}
Expand All @@ -74,6 +75,7 @@ extern int yyerror(const char*);
%token RESET
%token UNSET
%token CONTINUE
%token EXISTS
%token <cnfstmt> CALL
%token <cnfstmt> CALL_INDIRECT
%token <s> LEGACY_ACTION
Expand Down Expand Up @@ -217,6 +219,7 @@ expr: expr AND expr { $$ = cnfexprNew(AND, $1, $3); }
| expr '%' expr { $$ = cnfexprNew('%', $1, $3); }
| '(' expr ')' { $$ = $2; }
| '-' expr %prec UMINUS { $$ = cnfexprNew('M', NULL, $2); }
| EXISTS '(' VAR ')' { $$ = (struct cnfexpr*) cnffuncexistsNew($3); }
| FUNC '(' ')' { $$ = (struct cnfexpr*) cnffuncNew($1, NULL); }
| FUNC '(' fparams ')' { $$ = (struct cnfexpr*) cnffuncNew($1, $3); }
| NUMBER { $$ = (struct cnfexpr*) cnfnumvalNew($1); }
Expand Down
1 change: 1 addition & 0 deletions grammar/lexer.l
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,7 @@ int fileno(FILE *stream);
"escaped, problem string is: %s",
yytext); }
<EXPR>[ \t\n] { cnfPrintToken(yytext); }
<EXPR>"exists" { cnfPrintToken(yytext); return EXISTS; } /* special case function (see rainerscript.c) */
<EXPR>[a-z][a-z0-9_]* { cnfPrintToken(yytext); yylval.estr = es_newStrFromCStr(yytext, yyleng);
return FUNC; }
<EXPR>. { cnfPrintToken(yytext); parser_errmsg("invalid character '%s' in expression "
Expand Down
47 changes: 47 additions & 0 deletions grammar/rainerscript.c
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ tokenToString(const int token)
case 'V': tokstr ="V"; break;
case 'F': tokstr ="F"; break;
case 'A': tokstr ="A"; break;
case S_FUNC_EXISTS: tokstr ="exists()"; break;
default: snprintf(tokbuf, sizeof(tokbuf), "%c[%d]", token, token);
tokstr = tokbuf; break;
}
Expand Down Expand Up @@ -2750,6 +2751,27 @@ doFuncCall(struct cnffunc *__restrict__ const func, struct svar *__restrict__ co
}
}


/* Perform the special "exists()" function to check presence of a variable.
*/
static int ATTR_NONNULL()
evalFuncExists(struct cnffuncexists *__restrict__ const fexists, void *__restrict__ const usrptr)
{
int r = 0;
rsRetVal localRet;

if(fexists->prop.id == PROP_CEE ||
fexists->prop.id == PROP_LOCAL_VAR ||
fexists->prop.id == PROP_GLOBAL_VAR ) {
localRet = msgCheckVarExists((smsg_t*)usrptr, &fexists->prop);
if(localRet == RS_RET_OK) {
r = 1;
}
}

return r;
}

static void
evalVar(struct cnfvar *__restrict__ const var, void *__restrict__ const usrptr,
struct svar *__restrict__ const ret)
Expand Down Expand Up @@ -3352,6 +3374,10 @@ cnfexprEval(const struct cnfexpr *__restrict__ const expr,
case 'F':
doFuncCall((struct cnffunc*) expr, ret, usrptr, pWti);
break;
case S_FUNC_EXISTS:
ret->datatype = 'N';
ret->d.n = evalFuncExists((struct cnffuncexists*) expr, usrptr);
break;
default:
ret->datatype = 'N';
ret->d.n = 0ll;
Expand Down Expand Up @@ -3887,6 +3913,10 @@ cnfexprPrint(struct cnfexpr *expr, int indent)
dbgprintf("NOT\n");
cnfexprPrint(expr->r, indent+1);
break;
case S_FUNC_EXISTS:
doIndent(indent);
dbgprintf("exists(%s)\n", ((struct cnffuncexists*)expr)->varname);
break;
case 'S':
doIndent(indent);
cstrPrint("string '", ((struct cnfstringval*)expr)->estr);
Expand Down Expand Up @@ -5197,6 +5227,23 @@ cnffuncNew_prifilt(int fac)
}


/* The check-if-variable exists "exists($!var)" is a special beast and as such
* also needs special code (we must not evaluate the var but need its name).
*/
struct cnffuncexists * ATTR_NONNULL()
cnffuncexistsNew(const char *const varname)
{
struct cnffuncexists* f_exists;

if((f_exists = malloc(sizeof(struct cnffuncexists))) != NULL) {
f_exists->nodetype = S_FUNC_EXISTS;
f_exists->varname = varname;
msgPropDescrFill(&f_exists->prop, (uchar*)varname, strlen(varname));
}
return f_exists;
}


/* returns 0 if everything is OK and config parsing shall continue,
* and 1 if things are so wrong that config parsing shall be aborted.
*/
Expand Down
10 changes: 9 additions & 1 deletion grammar/rainerscript.h
Original file line number Diff line number Diff line change
Expand Up @@ -97,13 +97,13 @@ struct nvlst {
* be the sole foundation for the AST.
*
* nodetypes (list not yet complete)
* A - (string) array
* F - function
* N - number
* P - fparamlst
* R - rule
* S - string
* V - var
* A - (string) array
* ... plus the S_* #define's below:
*/
#define S_STOP 4000
Expand All @@ -118,6 +118,7 @@ struct nvlst {
#define S_FOREACH 4009
#define S_RELOAD_LOOKUP_TABLE 4010
#define S_CALL_INDIRECT 4011
#define S_FUNC_EXISTS 4012 /* special case function which must get varname only */

enum cnfFiltType { CNFFILT_NONE, CNFFILT_PRI, CNFFILT_PROP, CNFFILT_SCRIPT };
const char* cnfFiltType2str(const enum cnfFiltType filttype);
Expand Down Expand Up @@ -262,6 +263,12 @@ struct cnffunc {
} __attribute__((aligned (8)));


struct cnffuncexists {
unsigned nodetype;
const char *varname;
msgPropDescr_t prop;
} __attribute__((aligned (8)));

struct scriptFunct {
const char *fname;
unsigned short minParams;
Expand Down Expand Up @@ -354,6 +361,7 @@ struct cnfnumval* cnfnumvalNew(long long val);
struct cnfstringval* cnfstringvalNew(es_str_t *estr);
struct cnfvar* cnfvarNew(char *name);
struct cnffunc * cnffuncNew(es_str_t *fname, struct cnffparamlst* paramlst);
struct cnffuncexists * cnffuncexistsNew(const char *varname);
struct cnffparamlst * cnffparamlstNew(struct cnfexpr *expr, struct cnffparamlst *next);
int cnfDoInclude(const char *name, const int optional);
int cnfparamGetIdx(struct cnfparamblk *params, const char *name);
Expand Down
38 changes: 32 additions & 6 deletions runtime/msg.c
Original file line number Diff line number Diff line change
Expand Up @@ -567,7 +567,7 @@ getRcvFromIP(smsg_t * const pM)

/* map a property name (string) to a property ID */
rsRetVal
propNameToID(uchar *pName, propid_t *pPropID)
propNameToID(const uchar *const pName, propid_t *const pPropID)
{
DEFiRet;

Expand Down Expand Up @@ -4838,7 +4838,8 @@ jsonPathFindNext(struct json_object *root, uchar *namestart, uchar **name, uchar
}

static rsRetVal
jsonPathFindParent(struct json_object *jroot, uchar *name, uchar *leaf, struct json_object **parent, int bCreate)
jsonPathFindParent(struct json_object *jroot, uchar *name, uchar *leaf, struct json_object **parent,
const int bCreate)
{
uchar *namestart;
DEFiRet;
Expand Down Expand Up @@ -4876,28 +4877,53 @@ jsonMerge(struct json_object *existing, struct json_object *json)

/* find a JSON structure element (field or container doesn't matter). */
rsRetVal
jsonFind(struct json_object *jroot, msgPropDescr_t *pProp, struct json_object **jsonres)
jsonFind(smsg_t *const pMsg, msgPropDescr_t *pProp, struct json_object **jsonres)
{
uchar *leaf;
struct json_object *parent;
struct json_object *field;
struct json_object **jroot = NULL;
pthread_mutex_t *mut = NULL;
DEFiRet;

if(jroot == NULL) {
CHKiRet(getJSONRootAndMutex(pMsg, pProp->id, &jroot, &mut));
pthread_mutex_lock(mut);

if(*jroot == NULL) {
field = NULL;
goto finalize_it;
}

if(!strcmp((char*)pProp->name, "!")) {
field = jroot;
field = *jroot;
} else if(!strcmp((char*)pProp->name, ".")) {
field = *jroot;
} else {
leaf = jsonPathGetLeaf(pProp->name, pProp->nameLen);
CHKiRet(jsonPathFindParent(jroot, pProp->name, leaf, &parent, 0));
CHKiRet(jsonPathFindParent(*jroot, pProp->name, leaf, &parent, 0));
if(jsonVarExtract(parent, (char*)leaf, &field) == FALSE)
field = NULL;
}
*jsonres = field;

finalize_it:
if(mut != NULL)
pthread_mutex_unlock(mut);
RETiRet;
}

/* check if JSON variable exists (works on terminal var and container) */
rsRetVal ATTR_NONNULL()
msgCheckVarExists(smsg_t *const pMsg, msgPropDescr_t *pProp)
{
struct json_object *jsonres = NULL;
DEFiRet;

CHKiRet(jsonFind(pMsg, pProp, &jsonres));
if(jsonres == NULL) {
iRet = RS_RET_NOT_FOUND;
}

finalize_it:
RETiRet;
}
Expand Down
5 changes: 3 additions & 2 deletions runtime/msg.h
Original file line number Diff line number Diff line change
Expand Up @@ -229,16 +229,17 @@ void getInputName(const smsg_t * const pM, uchar **ppsz, int *const plen);
int getHOSTNAMELen(smsg_t *pM);
uchar *getProgramName(smsg_t *pM, sbool bLockMutex);
uchar *getRcvFrom(smsg_t *pM);
rsRetVal propNameToID(uchar *pName, propid_t *pPropID);
rsRetVal propNameToID(const uchar *pName, propid_t *pPropID);
uchar *propIDToName(propid_t propID);
rsRetVal ATTR_NONNULL() msgCheckVarExists(smsg_t *const pMsg, msgPropDescr_t *pProp);
rsRetVal msgGetJSONPropJSON(smsg_t *pMsg, msgPropDescr_t *pProp, struct json_object **pjson);
rsRetVal msgGetJSONPropJSONorString(smsg_t * const pMsg, msgPropDescr_t *pProp, struct json_object **pjson,
uchar **pcstr);
rsRetVal getJSONPropVal(smsg_t *pMsg, msgPropDescr_t *pProp, uchar **pRes, rs_size_t *buflen,
unsigned short *pbMustBeFreed);
rsRetVal msgSetJSONFromVar(smsg_t *pMsg, uchar *varname, struct svar *var, int force_reset);
rsRetVal msgDelJSON(smsg_t *pMsg, uchar *varname);
rsRetVal jsonFind(struct json_object *jroot, msgPropDescr_t *pProp, struct json_object **jsonres);
rsRetVal jsonFind(smsg_t *const pMsg, msgPropDescr_t *pProp, struct json_object **jsonres);

rsRetVal msgPropDescrFill(msgPropDescr_t *pProp, uchar *name, int nameLen);
void msgPropDescrDestruct(msgPropDescr_t *pProp);
Expand Down
2 changes: 1 addition & 1 deletion template.c
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ tplToJSON(struct template *pTpl, smsg_t *pMsg, struct json_object **pjson, struc
DEFiRet;

if(pTpl->bHaveSubtree){
if(jsonFind(pMsg->json, &pTpl->subtree, pjson) != RS_RET_OK)
if(jsonFind(pMsg, &pTpl->subtree, pjson) != RS_RET_OK)
*pjson = NULL;
if(*pjson == NULL) {
/* we need to have a root object! */
Expand Down
12 changes: 12 additions & 0 deletions tests/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,12 @@ TESTS += \
rscript_parse_json.sh \
rscript_previous_action_suspended.sh \
rscript_str2num_negative.sh \
rscript_exists-yes.sh \
rscript_exists-yes2.sh \
rscript_exists-not1.sh \
rscript_exists-not2.sh \
rscript_exists-not3.sh \
rscript_exists-not4.sh \
rscript-config_enable-on.sh \
config_output-o-option.sh \
rs-cnum.sh \
Expand Down Expand Up @@ -1715,6 +1721,12 @@ EXTRA_DIST= \
rscript_backticks_empty_envvar-vg.sh \
rscript_previous_action_suspended.sh \
rscript_str2num_negative.sh \
rscript_exists-yes.sh \
rscript_exists-yes2.sh \
rscript_exists-not1.sh \
rscript_exists-not2.sh \
rscript_exists-not3.sh \
rscript_exists-not4.sh \
rs-cnum.sh \
rs-int2hex.sh \
rs-substring.sh \
Expand Down
22 changes: 22 additions & 0 deletions tests/rscript_exists-not1.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/bin/bash
# add 2020-10-02 by Rainer Gerhards, released under ASL 2.0
. ${srcdir:=.}/diag.sh init
generate_conf
add_conf '
template(name="outfmt" type="string" string="%!result%\n")
# ensure that in this test there is NO variable define at all
if $msg contains "msgnum" then {
if exists($!p1!p2!val) then
set $!result = "on";
else
set $!result = "off";
action(type="omfile" file="'$RSYSLOG_OUT_LOG'" template="outfmt")
}
'
startup
injectmsg 0 1
shutdown_when_empty
wait_shutdown
export EXPECTED='off'
cmp_exact
exit_test
22 changes: 22 additions & 0 deletions tests/rscript_exists-not2.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/bin/bash
# add 2020-10-02 by Rainer Gerhards, released under ASL 2.0
. ${srcdir:=.}/diag.sh init
generate_conf
add_conf '
template(name="outfmt" type="string" string="%!result%\n")
set $!somevar = "test"; # this makes matters a bit more complicated
if $msg contains "msgnum" then {
if exists($!p1!p2!val) then
set $!result = "on";
else
set $!result = "off";
action(type="omfile" file="'$RSYSLOG_OUT_LOG'" template="outfmt")
}
'
startup
injectmsg 0 1
shutdown_when_empty
wait_shutdown
export EXPECTED='off'
cmp_exact
exit_test
22 changes: 22 additions & 0 deletions tests/rscript_exists-not3.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/bin/bash
# add 2020-10-02 by Rainer Gerhards, released under ASL 2.0
. ${srcdir:=.}/diag.sh init
generate_conf
add_conf '
template(name="outfmt" type="string" string="%!result%\n")
# ensure that in this test there is NO variable define at all
if $msg contains "msgnum" then {
if exists($.p1!p2!val) then
set $!result = "on";
else
set $!result = "off";
action(type="omfile" file="'$RSYSLOG_OUT_LOG'" template="outfmt")
}
'
startup
injectmsg 0 1
shutdown_when_empty
wait_shutdown
export EXPECTED='off'
cmp_exact
exit_test
22 changes: 22 additions & 0 deletions tests/rscript_exists-not4.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/bin/bash
# add 2020-10-02 by Rainer Gerhards, released under ASL 2.0
. ${srcdir:=.}/diag.sh init
generate_conf
add_conf '
template(name="outfmt" type="string" string="%!result%\n")
set $.somevar = "test"; # this makes matters a bit more complicated
if $msg contains "msgnum" then {
if not exists($.p1!p2!val) then
set $!result = "off";
else
set $!result = "on";
action(type="omfile" file="'$RSYSLOG_OUT_LOG'" template="outfmt")
}
'
startup
injectmsg 0 1
shutdown_when_empty
wait_shutdown
export EXPECTED='off'
cmp_exact
exit_test