From 965c09147c8f87e4ed6ece46b9d54e612e35cf11 Mon Sep 17 00:00:00 2001 From: Rainer Gerhards Date: Sun, 24 Feb 2008 17:53:16 +0000 Subject: - added some thoughts on RainerScript - worked a bit on conversion functions --- doc/Makefile.am | 1 + doc/expression.html | 8 +---- doc/rainerscript.html | 37 +++++++++++++++++++++ rsyslog.h | 4 ++- stringbuf.c | 90 ++++++++++++++++++++++++++++++++++++++++++++------- stringbuf.h | 3 +- var.c | 60 +++++++++++++++++++++++++++------- var.h | 2 -- 8 files changed, 170 insertions(+), 35 deletions(-) create mode 100644 doc/rainerscript.html diff --git a/doc/Makefile.am b/doc/Makefile.am index f03c9120..31481a96 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -37,6 +37,7 @@ html_files = \ queueWorkerLogic.dia \ queueWorkerLogic.jpg \ queueWorkerLogic_small.jpg \ + rainerscript.html \ rsconf1_actionexeconlywhenpreviousissuspended.html \ rsconf1_actionresumeinterval.html \ rsconf1_allowedsender.html \ diff --git a/doc/expression.html b/doc/expression.html index 6d187413..e7eb7842 100644 --- a/doc/expression.html +++ b/doc/expression.html @@ -4,13 +4,7 @@

Expressions

Rsyslog supports expressions at a growing number of places. So -far, they are supported for filtering messages.

C-like comments (/* some comment */) are supported inside the expression, but not yet in the rest of the configuration file.

-

-

Formal Definition

- -

Below is the formal definition of expression format (in ABNF, RFC 2234):
-

; The stuff immediately below here is a quick shot at how the config
; file ABNF *at whole* may look like. That is not really related to
; expressions, but for the time being I put it here. It will later be
; moved to a more appropriate place. -- rgerhards, 2008-02-22
?line? := cfsysline / cfli
cfsysline:= BOL "$" *char EOL ; how to handle the first line? (no EOL in front!)
BOL := ; Begin of Line - implicitely set on file beginning and after each EOL
EOL := 0x0a ;LF
if_stmt := "if" expr "then"
old_filter:= BOL facility "." severity ; no whitespace allowed between BOL and facility!
facility := "*" / "auth" / "authpriv" / "cron" / "daemon" / "kern" / "lpr" /
"mail" / "mark" / "news" / "security" / "syslog" / "user" / "uucp" /
"local0" .. "local7" / "mark"
; The keyword security should not be used anymore
; mark is just internal
severity := TBD ; not really relevant in this context

; and now the actual expression
expr := e_and *("or" e_and)
e_and := e_cmp *("and" e_cmp)
e_cmp := val 0*1(cmp_op val)
val := term *(("+" / "-") term)
term := factor *(("*" / "/" / "%") factor)
factor := ["not"] ["-"] terminal
terminal := var / constant / function / ( "(" expr ")" )
function := name "(" *("," expr) ")"
var := "$" varname
varname := msgvar / sysvar
msgvar := name
sysvar := "$" name
name := alpha *(alnum)
constant := string / number
string := simpstr / tplstr ; tplstr will be implemented in next phase
simpstr := "'" *char "'" ; use your imagination for char ;)
tplstr := '"' template '"' ; not initially implemented
number := ["-"] 1*digit ; 0nn = octal, 0xnn = hex, nn = decimal
cmp_op := "==" / "!=" / "<>" / "<" / ">" / "<=" / ">=" / "contains" / "startswith"
digit := %x30-39
alpha := "a" ... "z" # all letters
alnum :* alpha / digit / "_"
-

[rsyslog.conf overview] +far, they are supported for filtering messages.

Expression support is provided by RainerScript. For now, please see the formal expression definition in RainerScript ABNF. It is the "expr" node.

C-like comments (/* some comment */) are supported inside the expression, but not yet in the rest of the configuration file.

[rsyslog.conf overview] [manual index] [rsyslog site]

This documentation is part of the rsyslog diff --git a/doc/rainerscript.html b/doc/rainerscript.html new file mode 100644 index 00000000..22415258 --- /dev/null +++ b/doc/rainerscript.html @@ -0,0 +1,37 @@ + + +RainerScript ABNF + + + +

RainerScript ABNF

+

This is the formal definition of RainerScript, as supported by +rsyslog configuration. Please note that this currently is working +document and the actual implementation may be quite different.

+

The +first glimpse of RainerScript will be available as part of rsyslog +3.12.0 expression support. However, the 3.12. series of rsyslog will +have a partial script implementaiton, which will not necessariy be +compatible with the later full implementation. So if you use it, be +prepared for some config file changes as RainerScript evolves.

+

C-like comments (/* some comment */) are supported in all pure +RainerScript lines. However, legacy-mapped lines do not support them. +All lines support the hash mark "#" as a comment initiator. Everything +between the hash and the end of line is a comment (just like // in C++ +and many other languages).

+

Formal Definition

+

Below is the formal language definitionin ABNF (RFC 2234) +format:
+

+
; all of this is a working document and may change! -- rgerhards, 2008-02-24

script := *stmt
stmt := (if_stmt / block / vardef / run_s / load_s)
vardef := "var" ["scope" = ("global" / "event")]
block := "begin" stmt "end"
load_s := "load" constraint ("module") modpath params ; load mod only if expr is true
run_s := "run" constraint ("input") name
constraint:= "if" expr ; constrains some one-time commands
modpath := expr
params := ["params" *1param *("," param) "endparams"]
param := paramname) "=" expr
paramname := [*(obqualifier ".") name]
modpath:= ; path to module
?line? := cfsysline / cfli
cfsysline:= BOL "$" *char EOL ; how to handle the first line? (no EOL in front!)
BOL := ; Begin of Line - implicitely set on file beginning and after each EOL
EOL := 0x0a ;LF
if_stmt := "if" expr "then"
old_filter:= BOL facility "." severity ; no whitespace allowed between BOL and facility!
facility := "*" / "auth" / "authpriv" / "cron" / "daemon" / "kern" / "lpr" /
"mail" / "mark" / "news" / "security" / "syslog" / "user" / "uucp" /
"local0" .. "local7" / "mark"
; The keyword security should not be used anymore
; mark is just internal
severity := TBD ; not really relevant in this context

; and now the actual expression
expr := e_and *("or" e_and)
e_and := e_cmp *("and" e_cmp)
e_cmp := val 0*1(cmp_op val)
val := term *(("+" / "-") term)
term := factor *(("*" / "/" / "%") factor)
factor := ["not"] ["-"] terminal
terminal := var / constant / function / ( "(" expr ")" )
function := name "(" *("," expr) ")"
var := "$" varname
varname := msgvar / sysvar
msgvar := name
sysvar := "$" name
name := alpha *(alnum)
constant := string / number
string := simpstr / tplstr ; tplstr will be implemented in next phase
simpstr := "'" *char "'" ; use your imagination for char ;)
tplstr := '"' template '"' ; not initially implemented
number := ["-"] 1*digit ; 0nn = octal, 0xnn = hex, nn = decimal
cmp_op := "==" / "!=" / "<>" / "<" / ">" / "<=" / ">=" / "contains" / "startswith"
digit := %x30-39
alpha := "a" ... "z" # all letters
alnum :* alpha / digit / "_"
+

Samples

+

Some samples of RainerScript:

define function IsLinux
begin
    if $environ contains "linux" then return true else return false
end

load if IsLinux() 'imklog.so' params name='klog' endparams /* load klog under linux only */
run if IsLinux() input 'klog'
load 'ommysql.so'

if $message contains "error" then
  action
    type='ommysql.so', queue.mode='disk', queue.highwatermark = 300,
    action.dbname='events', action.dbuser='uid',
    [?action.template='templatename'?] or [?action.sql='insert into table... values('+$facility+','+$severity+...?]
  endaction

... or ...

define action writeMySQL
    type='ommysql.so', queue.mode='disk', queue.highwatermark = 300,
    action.dbname='events', action.dbuser='uid',
    [?action.template='templatename'?] or [?action.sql='insert into table... values('+$facility+','+$severity+...?]
   endaction

if $message contains "error" then action writeMySQL

ALTERNATE APPROACH

define function IsLinux(
    if $environ contains "linux" then return true else return false
)

load if IsLinux() 'imklog.so' params name='klog' endparams /* load klog under linux only */
run if IsLinux() input 'klog'
load 'ommysql.so'

if $message contains "error" then
  action(
    type='ommysql.so', queue.mode='disk', queue.highwatermark = 300,
    action.dbname='events', action.dbuser='uid',
    [?action.template='templatename'?] or [?action.sql='insert into table... values('+$facility+','+$severity+...?]
  )

... or ...

define action writeMySQL(
    type='ommysql.so', queue.mode='disk', queue.highwatermark = 300,
    action.dbname='events', action.dbuser='uid',
    [?action.template='templatename'?] or [?action.sql='insert into table... values('+$facility+','+$severity+...?]
   )

if $message contains "error" then action writeMySQL(action.dbname='differentDB')

[rsyslog.conf overview] +[manual index] [rsyslog site]

+

This documentation is part of the +rsyslog +project.
+Copyright © 2008 by Rainer +Gerhards and +Adiscon. +Released under the GNU GPL version 3 or higher.

+ \ No newline at end of file diff --git a/rsyslog.h b/rsyslog.h index 1d0bb3ce..a64a13d9 100644 --- a/rsyslog.h +++ b/rsyslog.h @@ -47,6 +47,7 @@ /* some universal 64 bit define... */ typedef long long int64; typedef long long unsigned uint64; +typedef int64 number_t; /* type to use for numbers - TODO: maybe an autoconf option? */ /* The error codes below are orginally "borrowed" from * liblogging. As such, we reserve values up to -2999 @@ -131,7 +132,8 @@ enum rsRetVal_ /** return value. All methods return this if not specified oth RS_RET_OUT_OF_STACKSPACE = -2055, /**< a stack data structure is exhausted and can not be grown */ RS_RET_STACK_EMPTY = -2056, /**< a pop was requested on a stack, but the stack was already empty */ RS_RET_INVALID_VMOP = -2057, /**< invalid virtual machine instruction */ - RS_RET_INVALID_VAR = -2057, /**< a var_t or its content is unsuitable, eg. VARTYPE_NONE */ + RS_RET_INVALID_VAR = -2058, /**< a var_t or its content is unsuitable, eg. VARTYPE_NONE */ + RS_RET_NOT_A_NUMBER = -2059, /**< e.g. conversion impossible because the string is not a number */ RS_RET_OK_DELETE_LISTENTRY = 1, /**< operation successful, but callee requested the deletion of an entry (special state) */ RS_RET_TERMINATE_NOW = 2, /**< operation successful, function is requested to terminate (mostly used with threads) */ RS_RET_NO_RUN = 3, /**< operation successful, but function does not like to be executed */ diff --git a/stringbuf.c b/stringbuf.c index d1e69abf..80f1bb97 100755 --- a/stringbuf.c +++ b/stringbuf.c @@ -718,34 +718,100 @@ int rsCStrOffsetSzStrCmp(cstr_t *pCS1, size_t iOffset, uchar *psz, size_t iLenSz } -/* check if the string can be converted to a number. Returns 1 if that's possible - * and 0 otherwise. +/* Converts a string to a number. If the string dos not contain a number, + * RS_RET_NOT_A_NUMBER is returned and the contents of pNumber is undefined. + * If all goes well, pNumber contains the number that the string was converted + * to. */ -int rsCStrCanConvertToNumber(cstr_t *pStr) +rsRetVal +rsCStrConvertToNumber(cstr_t *pStr, number_t *pNumber) { - int i; - int ret = 1; + DEFiRet; + number_t n; + int bIsNegative; + size_t i; + + ASSERT(pStr != NULL); + ASSERT(pNumber != NULL); if(pStr->iStrLen == 0) { /* can be converted to 0! (by convention) */ - goto finalize_it; + pNumber = 0; + FINALIZE; + } + + /* first skip whitespace (if present) */ + for(i = 0 ; i < pStr->iStrLen && isspace(pStr->pBuf[i]) ; ++i) { + /*DO NOTHING*/ } /* we have a string, so let's check its syntax */ - if(pStr->pBuf[0] == '+' || pStr->pBuf[0] == '-') { - i = 1; /* skip that char */ + if(pStr->pBuf[i] == '+') { + ++i; /* skip that char */ + bIsNegative = 0; + } else if(pStr->pBuf[0] == '-') { + ++i; /* skip that char */ + bIsNegative = 1; } else { - i = 0; /* start from the beginning */ + bIsNegative = 0; } - while(i < pStr->iStrLen && isdigit(pStr->pBuf[i])) + /* TODO: octal? hex? */ + n = 0; + while(i < pStr->iStrLen && isdigit(pStr->pBuf[i])) { + n = n * 10 + pStr->pBuf[i] * 10; ++i; + } if(i < pStr->iStrLen) /* non-digits before end of string? */ - ret = 0; /* than we can not convert */ + ABORT_FINALIZE(RS_RET_NOT_A_NUMBER); + + if(bIsNegative) + n *= -1; + + /* we got it, so return the number */ + *pNumber = n; + +finalize_it: + RETiRet; +} + + +/* Converts a string to a boolen. First tries to convert to a number. If + * that succeeds, we are done (number is then used as boolean value). If + * that fails, we look if the string is "yes" or "true". If so, a value + * of 1 is returned. In all other cases, a value of 0 is returned. Please + * note that we do not have a specific boolean type, so we return a number. + * so, these are + * RS_RET_NOT_A_NUMBER is returned and the contents of pNumber is undefined. + * If all goes well, pNumber contains the number that the string was converted + * to. + */ +rsRetVal +rsCStrConvertToBool(cstr_t *pStr, number_t *pBool) +{ + DEFiRet; + + ASSERT(pStr != NULL); + ASSERT(pBool != NULL); + + iRet = rsCStrConvertToNumber(pStr, pBool); + + if(iRet != RS_RET_NOT_A_NUMBER) { + FINALIZE; /* in any case, we have nothing left to do */ + } + + /* TODO: maybe we can do better than strcasecmp ;) -- overhead! */ + if(!strcasecmp((char*)rsCStrGetSzStr(pStr), "true")) { + *pBool = 1; + } else if(!strcasecmp((char*)rsCStrGetSzStr(pStr), "yes")) { + *pBool = 1; + } else { + *pBool = 0; + } finalize_it: - return ret; + RETiRet; } diff --git a/stringbuf.h b/stringbuf.h index 02720c2a..d8e72999 100755 --- a/stringbuf.h +++ b/stringbuf.h @@ -138,7 +138,8 @@ int rsCStrLocateInSzStr(cstr_t *pThis, uchar *sz); int rsCStrStartsWithSzStr(cstr_t *pCS1, uchar *psz, size_t iLenSz); int rsCStrSzStrStartsWithCStr(cstr_t *pCS1, uchar *psz, size_t iLenSz); int rsCStrSzStrMatchRegex(cstr_t *pCS1, uchar *psz); -int rsCStrCanConvertToNumber(cstr_t *pStr); +rsRetVal rsCStrConvertToNumber(cstr_t *pStr, number_t *pNumber); +rsRetVal rsCStrConvertToBool(cstr_t *pStr, number_t *pBool); /* now come inline-like functions */ #ifdef NDEBUG diff --git a/var.c b/var.c index 1f4ed1cb..6910986e 100644 --- a/var.c +++ b/var.c @@ -148,23 +148,50 @@ finalize_it: * conversion request on the unchanged object is guaranteed to succeed. * rgerhards, 2008-02-22 */ -int canConvToNumber(var_t *pThis) +rsRetVal +ConvToNumber(var_t *pThis) { - int ret = 0; - - BEGINfunc + DEFiRet; + number_t n; if(pThis->varType == VARTYPE_NUMBER) { - ret = 1; + FINALIZE; } else if(pThis->varType == VARTYPE_STR) { - ret = rsCStrCanConvertToNumber(pThis->val.pStr); // TODO: implement the same method in str_t object, then call that */ + CHKiRet(rsCStrConvertToNumber(pThis->val.pStr, &n)); + pThis->val.num = n; + pThis->varType = VARTYPE_NUMBER; } - ENDfunc - return ret; +finalize_it: + RETiRet; } +/* convert the provided var to type string. This is always possible + * (except, of course, for things like out of memory...) + * TODO: finish implementation!!!!!!!!! + * rgerhards, 2008-02-24 + */ +rsRetVal +ConvToString(var_t *pThis) +{ + DEFiRet; + + if(pThis->varType == VARTYPE_STR) { + FINALIZE; + } else if(pThis->varType == VARTYPE_NUMBER) { + //CHKiRet(rsCStrConvertToNumber(pThis->val.pStr, &n)); + //pThis->val.num = n; + // TODO: ADD CODE!!!! + pThis->varType = VARTYPE_STR; + } + +finalize_it: + RETiRet; +} + + + /* This function is used to prepare two var_t objects for a common operation, * e.g before they are added, multiplied or compared. The function looks at * the data types of both operands and finds the best data type suitable for @@ -217,11 +244,16 @@ ConvForOperation(var_t *pThis, var_t *pOther) ABORT_FINALIZE(RS_RET_INVALID_VAR); break; case VARTYPE_STR: - commonType = VARTYPE_STR; + /* two strings, we are all set */ break; case VARTYPE_NUMBER: /* check if we can convert pThis to a number, if so use number format. */ - commonType = canConvToNumber(pThis) ? VARTYPE_NUMBER : VARTYPE_STR; + iRet = ConvToNumber(pThis); + if(iRet != RS_RET_NOT_A_NUMBER) { + CHKiRet(ConvToString(pOther)); + } else { + FINALIZE; /* OK or error */ + } break; case VARTYPE_SYSLOGTIME: ABORT_FINALIZE(RS_RET_NOT_IMPLEMENTED); @@ -234,8 +266,12 @@ ConvForOperation(var_t *pThis, var_t *pOther) ABORT_FINALIZE(RS_RET_INVALID_VAR); break; case VARTYPE_STR: - /* check if we can convert pOther to a number, if so use number format. */ - commonType = canConvToNumber(pOther) ? VARTYPE_NUMBER : VARTYPE_STR; + iRet = ConvToNumber(pOther); + if(iRet != RS_RET_NOT_A_NUMBER) { + CHKiRet(ConvToString(pThis)); + } else { + FINALIZE; /* OK or error */ + } break; case VARTYPE_NUMBER: commonType = VARTYPE_NUMBER; diff --git a/var.h b/var.h index 40b33037..77ccb8c6 100644 --- a/var.h +++ b/var.h @@ -32,8 +32,6 @@ typedef enum { VARTYPE_SYSLOGTIME = 3 } varType_t; -typedef int64 number_t; /* type to use for numbers */ - /* the var object */ typedef struct var_s { BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */ -- cgit