summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--doc/rainerscript.html34
-rw-r--r--grammar/lexer.l2
-rw-r--r--runtime/msg.c64
-rw-r--r--runtime/msg.h2
-rw-r--r--runtime/rsconf.c4
-rw-r--r--runtime/rsyslog.h1
-rw-r--r--runtime/ruleset.c5
-rw-r--r--template.c26
8 files changed, 114 insertions, 24 deletions
diff --git a/doc/rainerscript.html b/doc/rainerscript.html
index 0faa38b5..3eb08eff 100644
--- a/doc/rainerscript.html
+++ b/doc/rainerscript.html
@@ -37,22 +37,44 @@ script interpreter when there is need to do so.<br>
<h2>Variable (Property) types</h2>
<p>All rsyslog properties (see the <a href="property_replacer.html">property
replacer</a> page for a list) can be used in RainerScript. In addition, it also
-supports local variables. These are local to the current message, but are
+supports local and global variables. Local variables are local to the current message, but are
NOT message properties (e.g. the "$!" all JSON property does not contain
-them). Future releases will probably introduce rsyslog global variables which
-exists outside the scope of a single message.
+them). Global variables have a truely global scope and are NOT bound to
+a specifc message. Thus they can be used to persist values across
+multiple messages (for things like counters). Please note that rsyslog
+ensures proper synchronization for global variables (which also means
+they are slower than the others). HOWEVER, in a highly multithreaded
+configuration operations like<br>
+set $/var = $/var + 1;</br>
+are <b>not</b> atomic, so some updates to the counter variable may be missing. The
+classical sample for this is in a two-thread environment: Variable $/var is set
+to 1 at the start. Now the following happens in the following order:
+<ol>
+<li>Thread A reads 1 from $/var and adds 1, result is 2, but not yet stored
+<li>Thread B reads 1 from $/var and adds 1, result is 2, but not yet stored
+<li>Thread A stores its result of 2
+<li>Thread B stores its result of 2
+</ol>
+After this sequence, $/var contains the value two, which is probably not what was
+expected. Rsyslog does <b>not</b> provide looking primitives for individual variables,
+as this can lead to serious configuration problems if not used 100% correctly.
+However, rsyslog provides (or will in the future provide) special function which
+provide guaranteed atomic updates (in the sample, the end result would be three
+no matter what the scheduling order is).
<p>Only message json (CEE/Lumberjack) properties can be modified by
the "set" and "unset" statements, not any other message property. Obviously,
-local variables are also modifieable.
+local and global variables are also modifieable.
<p>Message JSON property names start with "$!" where the bang character
represents the root.
-<p>Local variables names start with "$.", where the dot denotes the root.
+<p>Local variables names start with "$.", where the dot denotes the root. Similarly,
+global variables start with "$/".
<p>Both JSON properties as well as local variables may contain an arbitrary
deep path before the final element. The bang character is always used as path
separator, no matter if it is a message property or a local variable. For example
"$!path1!path2!varname" is a three-level deep message property where as
the very similar looking "$.path1!path2!varname" specifies a three-level
-deep local variable. The bang or dot character immediately following the
+deep local variable. The similar global variables is named "$/path1!path2!varname".
+The bang, slash, or dot character immediately following the
dollar sign is used by rsyslog to separate the different types.
<h2>configuration objects</h2>
<h3>main_queue()</h3>
diff --git a/grammar/lexer.l b/grammar/lexer.l
index 44606977..438ccdd8 100644
--- a/grammar/lexer.l
+++ b/grammar/lexer.l
@@ -129,7 +129,7 @@ int fileno(FILE *stream);
<EXPR>0[0-7]+ | /* octal number */
<EXPR>0x[0-7a-f] | /* hex number, following rule is dec; strtoll handles all! */
<EXPR>([1-9][0-9]*|0) { yylval.n = strtoll(yytext, NULL, 0); return NUMBER; }
-<EXPR>\$[$!.]{0,1}[a-z][!a-z0-9\-_\.]* { yylval.s = strdup(yytext); return VAR; }
+<EXPR>\$[$!./]{0,1}[a-z][!a-z0-9\-_\.]* { yylval.s = strdup(yytext); return VAR; }
<EXPR>\'([^'\\]|\\['"\\$bntr]|\\x[0-9a-f][0-9a-f]|\\[0-7][0-7][0-7])*\' {
yytext[yyleng-1] = '\0';
unescapeStr((uchar*)yytext+1, yyleng-2);
diff --git a/runtime/msg.c b/runtime/msg.c
index 73fa0367..c7cd9406 100644
--- a/runtime/msg.c
+++ b/runtime/msg.c
@@ -66,6 +66,12 @@
#include "var.h"
#include "rsconf.h"
+/* TODO: move the global variable root to the config object - had no time to to it
+ * right now before vacation -- rgerhards, 2013-07-22
+ */
+static pthread_rwlock_t glblVars_rwlock;
+struct json_object *global_var_root = NULL;
+
/* static data */
DEFobjStaticHelpers
DEFobjCurrIf(datetime)
@@ -539,11 +545,14 @@ propNameStrToID(uchar *pName, propid_t *pPropID)
*pPropID = PROP_CEE;
} else if(!strncmp((char*) pName, "$.", 2)) {
*pPropID = PROP_LOCAL_VAR;
+ } else if(!strncmp((char*) pName, "$/", 2)) {
+ *pPropID = PROP_GLOBAL_VAR;
} else if(!strcmp((char*) pName, "$bom")) {
*pPropID = PROP_SYS_BOM;
} else if(!strcmp((char*) pName, "$uptime")) {
*pPropID = PROP_SYS_UPTIME;
} else {
+ DBGPRINTF("PROP_INVALID for name '%s'\n", pName);
*pPropID = PROP_INVALID;
iRet = RS_RET_VAR_NOT_FOUND;
}
@@ -639,6 +648,8 @@ uchar *propIDToName(propid_t propID)
return UCHAR_CONSTANT("*CEE-based property*");
case PROP_LOCAL_VAR:
return UCHAR_CONSTANT("*LOCAL_VARIABLE*");
+ case PROP_GLOBAL_VAR:
+ return UCHAR_CONSTANT("*GLOBAL_VARIABLE*");
case PROP_CEE_ALL_JSON:
return UCHAR_CONSTANT("$!all-json");
case PROP_SYS_BOM:
@@ -2584,6 +2595,16 @@ getLocalVarPropVal(msg_t *pM, es_str_t *propName, uchar **pRes, rs_size_t *bufle
return getJSONPropVal(pM->localvars, propName, pRes, buflen, pbMustBeFreed);
}
+rsRetVal
+getGlobalVarPropVal( es_str_t *propName, uchar **pRes, rs_size_t *buflen, unsigned short *pbMustBeFreed)
+{
+ DEFiRet;
+ pthread_rwlock_rdlock(&glblVars_rwlock);
+ iRet = getJSONPropVal(global_var_root, propName, pRes, buflen, pbMustBeFreed);
+ pthread_rwlock_unlock(&glblVars_rwlock);
+ RETiRet;
+}
+
/* Get a JSON-based-variable as native json object */
rsRetVal
@@ -2628,6 +2649,16 @@ msgGetLocalVarJSON(msg_t *pM, es_str_t *propName, struct json_object **pjson)
return msgGetJSONPropJSON(pM->localvars, propName, pjson);
}
+rsRetVal
+msgGetGlobalVarJSON(es_str_t *propName, struct json_object **pjson)
+{
+ DEFiRet;
+ pthread_rwlock_rdlock(&glblVars_rwlock);
+ iRet = msgGetJSONPropJSON(global_var_root, propName, pjson);
+ pthread_rwlock_unlock(&glblVars_rwlock);
+ RETiRet;
+}
+
/* Encode a JSON value and add it to provided string. Note that
* the string object may be NULL. In this case, it is created
* if and only if escaping is needed.
@@ -3028,6 +3059,9 @@ uchar *MsgGetProp(msg_t *pMsg, struct templateEntry *pTpe,
case PROP_LOCAL_VAR:
getLocalVarPropVal(pMsg, propName, &pRes, &bufLen, pbMustBeFreed);
break;
+ case PROP_GLOBAL_VAR:
+ getGlobalVarPropVal(propName, &pRes, &bufLen, pbMustBeFreed);
+ break;
case PROP_SYS_BOM:
if(*pbMustBeFreed == 1)
free(pRes);
@@ -3750,13 +3784,16 @@ msgGetMsgVarNew(msg_t *pThis, uchar *name)
propid_t propid;
unsigned short bMustBeFreed = 0;
es_str_t *estr;
+ es_str_t *propName;
ISOBJ_TYPE_assert(pThis, msg);
/* always call MsgGetProp() without a template specifier */
/* TODO: optimize propNameToID() call -- rgerhards, 2009-06-26 */
propNameStrToID(name, &propid);
- pszProp = (uchar*) MsgGetProp(pThis, NULL, propid, NULL, &propLen, &bMustBeFreed, NULL);
+ propName = es_newStrFromCStr((char*)name+1, ustrlen(name)-1); // TODO: optimize!
+ pszProp = (uchar*) MsgGetProp(pThis, NULL, propid, propName, &propLen, &bMustBeFreed, NULL);
+ es_deleteStr(propName);
estr = es_newStrFromCStr((char*)pszProp, propLen);
if(bMustBeFreed)
@@ -3869,13 +3906,13 @@ jsonPathGetLeaf(uchar *name, int lenName)
int i;
for(i = lenName ; i >= 0 ; --i)
if(i == 0) {
- if(name[0] == '.' || name[0] == '!')
+ if(name[0] == '!' || name[0] == '.' || name[0] == '/')
break;
} else {
if(name[i] == '!')
break;
}
- if(name[i] == '!' || name[i] == '.')
+ if(name[i] == '!' || name[i] == '.' || name[i] == '/')
++i;
return name + i;
}
@@ -3891,9 +3928,9 @@ jsonPathFindNext(struct json_object *root, uchar *namestart, uchar **name, uchar
uchar *p = *name;
DEFiRet;
- if(*p == '!' || (*name == namestart && *p == '.'))
+ if(*p == '!' || (*name == namestart && (*p == '.' || *p == '/')))
++p;
- for(i = 0 ; *p && !(p == namestart && *p == '.') && *p != '!' && p != leaf && i < sizeof(namebuf)-1 ; ++i, ++p)
+ for(i = 0 ; *p && !(p == namestart && (*p == '.' || *p == '/')) && *p != '!' && p != leaf && i < sizeof(namebuf)-1 ; ++i, ++p)
namebuf[i] = *p;
if(i > 0) {
namebuf[i] = '\0';
@@ -3988,7 +4025,7 @@ msgAddJSONObj(msg_t *pM, uchar *name, struct json_object *json, struct json_obje
DEFiRet;
MsgLock(pM);
- if((name[0] == '!' || name[0] == '.') && name[1] == '\0') {
+ if((name[0] == '!' || name[0] == '.' || name[0] == '/') && name[1] == '\0') {
if(*pjroot == NULL)
*pjroot = json;
else
@@ -4049,7 +4086,7 @@ msgDelJSONVar(msg_t *pM, struct json_object **jroot, uchar *name)
dbgprintf("AAAA: unset variable '%s'\n", name);
MsgLock(pM);
- if((name[0] == '!' || name[0] == '.') && name[1] == '\0') {
+ if((name[0] == '!' || name[0] == '.' || name[0] == '/') && name[1] == '\0') {
/* strange, but I think we should permit this. After all,
* we trust rsyslog.conf to be written by the admin.
*/
@@ -4157,10 +4194,15 @@ msgSetJSONFromVar(msg_t *pMsg, uchar *varname, struct var *v)
ABORT_FINALIZE(RS_RET_ERR);
}
/* we always know strlen(varname) > 2 */
- if(varname[1] == '.')
- msgAddJSONObj(pMsg, varname+1, json, &pMsg->localvars);
- else
+ if(varname[1] == '!')
msgAddJSONObj(pMsg, varname+1, json, &pMsg->json);
+ else if(varname[1] == '.')
+ msgAddJSONObj(pMsg, varname+1, json, &pMsg->localvars);
+ else { /* global - '/' */
+ pthread_rwlock_wrlock(&glblVars_rwlock);
+ msgAddJSONObj(pMsg, varname+1, json, &global_var_root);
+ pthread_rwlock_unlock(&glblVars_rwlock);
+ }
finalize_it:
RETiRet;
}
@@ -4173,6 +4215,8 @@ rsRetVal msgQueryInterface(void) { return RS_RET_NOT_IMPLEMENTED; }
* rgerhards, 2008-01-04
*/
BEGINObjClassInit(msg, 1, OBJ_IS_CORE_MODULE)
+ pthread_rwlock_init(&glblVars_rwlock, NULL);
+
/* request objects we use */
CHKiRet(objUse(datetime, CORE_COMPONENT));
CHKiRet(objUse(glbl, CORE_COMPONENT));
diff --git a/runtime/msg.h b/runtime/msg.h
index f9ffc5e3..6b85042d 100644
--- a/runtime/msg.h
+++ b/runtime/msg.h
@@ -208,7 +208,9 @@ uchar *getRcvFrom(msg_t *pM);
rsRetVal propNameToID(cstr_t *pCSPropName, propid_t *pPropID);
uchar *propIDToName(propid_t propID);
rsRetVal msgGetCEEPropJSON(msg_t *pM, es_str_t *propName, struct json_object **pjson);
+rsRetVal getGlobalVarPropVal( es_str_t *propName, uchar **pRes, rs_size_t *buflen, unsigned short *pbMustBeFreed);
rsRetVal msgGetLocalVarJSON(msg_t *pM, es_str_t *propName, struct json_object **pjson);
+rsRetVal msgGetGlobalVarJSON(es_str_t *propName, struct json_object **pjson);
rsRetVal msgSetJSONFromVar(msg_t *pMsg, uchar *varname, struct var *var);
rsRetVal msgDelJSON(msg_t *pMsg, uchar *varname);
rsRetVal jsonFind(struct json_object *jroot, es_str_t *propName, struct json_object **jsonres);
diff --git a/runtime/rsconf.c b/runtime/rsconf.c
index b13e5cc7..ffe0d1d1 100644
--- a/runtime/rsconf.c
+++ b/runtime/rsconf.c
@@ -486,10 +486,10 @@ cnfGetVar(char *name, void *usrptr)
else if(name[1] == '!')
estr = msgGetCEEVarNew((msg_t*) usrptr, name+2);
else
- estr = msgGetMsgVarNew((msg_t*) usrptr, (uchar*)name+1);
+ estr = msgGetMsgVarNew((msg_t*) usrptr, (uchar*)name);
} else { /* if this happens, we have a program logic error */
estr = es_newStrFromCStr("err: var must start with $",
- strlen("err: var must start with $"));
+ sizeof("err: var must start with $")-1);
}
if(Debug) {
char *s;
diff --git a/runtime/rsyslog.h b/runtime/rsyslog.h
index 9fd527fd..e51f11bd 100644
--- a/runtime/rsyslog.h
+++ b/runtime/rsyslog.h
@@ -146,6 +146,7 @@ typedef uintTiny propid_t;
#define PROP_CEE 200
#define PROP_CEE_ALL_JSON 201
#define PROP_LOCAL_VAR 202
+#define PROP_GLOBAL_VAR 203
/* The error codes below are orginally "borrowed" from
diff --git a/runtime/ruleset.c b/runtime/ruleset.c
index 6c4eae26..dae5bbaa 100644
--- a/runtime/ruleset.c
+++ b/runtime/ruleset.c
@@ -452,6 +452,11 @@ evalPROPFILT(struct cnfstmt *stmt, msg_t *pMsg)
DBGPRINTF("Filter: check for local var '%s' (value '%s') ",
cstr, pszPropVal);
free(cstr);
+ } else if(stmt->d.s_propfilt.propID == PROP_GLOBAL_VAR) {
+ cstr = es_str2cstr(stmt->d.s_propfilt.propName, NULL);
+ DBGPRINTF("Filter: check for global var '%s' (value '%s') ",
+ cstr, pszPropVal);
+ free(cstr);
} else {
DBGPRINTF("Filter: check for property '%s' (value '%s') ",
propIDToName(stmt->d.s_propfilt.propID), pszPropVal);
diff --git a/template.c b/template.c
index 77e23863..8cb7bc27 100644
--- a/template.c
+++ b/template.c
@@ -367,6 +367,17 @@ tplToJSON(struct template *pTpl, msg_t *pMsg, struct json_object **pjson, struct
json_object_object_add(json, (char*)pTpe->fieldName, NULL);
}
}
+ } else if(pTpe->data.field.propid == PROP_GLOBAL_VAR) {
+ localRet = msgGetGlobalVarJSON(pTpe->data.field.propName, &jsonf);
+ if(localRet == RS_RET_OK) {
+ json_object_object_add(json, (char*)pTpe->fieldName, json_object_get(jsonf));
+ } else {
+ DBGPRINTF("tplToJSON: error %d looking up local variable %s\n",
+ localRet, pTpe->fieldName);
+ if(pTpe->data.field.options.bMandatory) {
+ json_object_object_add(json, (char*)pTpe->fieldName, NULL);
+ }
+ }
} else {
pVal = (uchar*) MsgGetProp(pMsg, pTpe, pTpe->data.field.propid,
pTpe->data.field.propName, &propLen,
@@ -808,8 +819,8 @@ do_Parameter(uchar **pp, struct template *pTpl)
cstrDestruct(&pStrProp);
ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
}
- } else if(pTpe->data.field.propid == PROP_LOCAL_VAR) {
- /* in CEE case, we need to preserve the actual property name, but correct the root ID (bang vs. dot) */
+ } else if(pTpe->data.field.propid == PROP_LOCAL_VAR || pTpe->data.field.propid == PROP_GLOBAL_VAR) {
+ /* in these cases, we need to preserve the actual property name, but correct the root ID (bang vs. dot) */
if((pTpe->data.field.propName = es_newStrFromCStr((char*)cstrGetSzStrNoNULL(pStrProp)+1, cstrLen(pStrProp)-1)) == NULL) {
cstrDestruct(&pStrProp);
ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
@@ -1112,7 +1123,8 @@ do_Parameter(uchar **pp, struct template *pTpl)
/* save field name - if none was given, use the property name instead */
if(pStrField == NULL) {
- if(pTpe->data.field.propid == PROP_CEE ||pTpe->data.field.propid == PROP_LOCAL_VAR) {
+ if(pTpe->data.field.propid == PROP_CEE || pTpe->data.field.propid == PROP_LOCAL_VAR ||
+ pTpe->data.field.propid == PROP_GLOBAL_VAR) {
/* in CEE case, we remove "$!"/"$." from the fieldname - it's just our indicator */
pTpe->fieldName = ustrdup(cstrGetSzStrNoNULL(pStrProp)+2);
pTpe->lenFieldName = cstrLen(pStrProp)-2;
@@ -1598,8 +1610,8 @@ createPropertyTpe(struct template *pTpl, struct cnfobj *o)
/* in CEE case, we need to preserve the actual property name */
pTpe->data.field.propName = es_newStrFromCStr((char*)cstrGetSzStrNoNULL(name)+1,
cstrLen(name)-1);
- } else if(pTpe->data.field.propid == PROP_LOCAL_VAR) {
- /* in this case, we need to preserve the actual property name, but correct the root ID (bang vs. dot) */
+ } else if(pTpe->data.field.propid == PROP_LOCAL_VAR || pTpe->data.field.propid == PROP_GLOBAL_VAR) {
+ /* in these case, we need to preserve the actual property name, but correct the root ID (bang vs. dot) */
pTpe->data.field.propName = es_newStrFromCStr((char*)cstrGetSzStrNoNULL(name)+1, cstrLen(name)-1);
es_getBufAddr(pTpe->data.field.propName)[0] = '!'; /* patch root name */
}
@@ -2114,6 +2126,10 @@ void tplPrintList(rsconf_t *conf)
char *cstr = es_str2cstr(pTpe->data.field.propName, NULL);
dbgprintf("[Local Var: '%s'] ", cstr);
free(cstr);
+ } else if(pTpe->data.field.propid == PROP_GLOBAL_VAR) {
+ char *cstr = es_str2cstr(pTpe->data.field.propName, NULL);
+ dbgprintf("[Global Var: '%s'] ", cstr);
+ free(cstr);
}
switch(pTpe->data.field.eDateFormat) {
case tplFmtDefault: