diff options
Diffstat (limited to 'vm.c')
-rw-r--r-- | vm.c | 534 |
1 files changed, 534 insertions, 0 deletions
@@ -0,0 +1,534 @@ +/* vm.c - the arithmetic stack of a virtual machine. + * + * Module begun 2008-02-22 by Rainer Gerhards + * + * Copyright 2008 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of rsyslog. + * + * Rsyslog 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. + * + * Rsyslog 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 Rsyslog. If not, see <http://www.gnu.org/licenses/>. + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + */ + +#include "config.h" +#include <stdlib.h> +#include <assert.h> + +#include "rsyslog.h" +#include "obj.h" +#include "vm.h" +#include "sysvar.h" +#include "stringbuf.h" + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(vmstk) +DEFobjCurrIf(var) +DEFobjCurrIf(sysvar) + + +/* ------------------------------ instruction set implementation ------------------------------ * + * The following functions implement the VM's instruction set. + */ +#define BEGINop(instruction) \ + static rsRetVal op##instruction(vm_t *pThis, __attribute__((unused)) vmop_t *pOp) \ + { \ + DEFiRet; + +#define CODESTARTop(instruction) \ + ISOBJ_TYPE_assert(pThis, vm); + +#define PUSHRESULTop(operand, res) \ + /* we have a result, so let's push it */ \ + var.SetNumber(operand, res); \ + vmstk.Push(pThis->pStk, operand); /* result */ + +#define ENDop(instruction) \ + RETiRet; \ + } + +/* code generator for boolean operations */ +#define BOOLOP(name, OPERATION) \ +BEGINop(name) /* remember to set the instruction also in the ENDop macro! */ \ + var_t *operand1; \ + var_t *operand2; \ +CODESTARTop(name) \ + vmstk.PopBool(pThis->pStk, &operand1); \ + vmstk.PopBool(pThis->pStk, &operand2); \ + if(operand1->val.num OPERATION operand2->val.num) { \ + CHKiRet(var.SetNumber(operand1, 1)); \ + } else { \ + CHKiRet(var.SetNumber(operand1, 0)); \ + } \ + vmstk.Push(pThis->pStk, operand1); /* result */ \ + var.Destruct(&operand2); /* no longer needed */ \ +finalize_it: \ +ENDop(name) +BOOLOP(OR, ||) +BOOLOP(AND, &&) +#undef BOOLOP + + +/* code generator for numerical operations */ +#define NUMOP(name, OPERATION) \ +BEGINop(name) /* remember to set the instruction also in the ENDop macro! */ \ + var_t *operand1; \ + var_t *operand2; \ +CODESTARTop(name) \ + vmstk.PopNumber(pThis->pStk, &operand1); \ + vmstk.PopNumber(pThis->pStk, &operand2); \ + operand1->val.num = operand1->val.num OPERATION operand2->val.num; \ + vmstk.Push(pThis->pStk, operand1); /* result */ \ + var.Destruct(&operand2); /* no longer needed */ \ +ENDop(name) +NUMOP(PLUS, +) +NUMOP(MINUS, -) +NUMOP(TIMES, *) +NUMOP(DIV, /) +NUMOP(MOD, %) +#undef BOOLOP + + +/* code generator for compare operations */ +#define BEGINCMPOP(name) \ +BEGINop(name) \ + var_t *operand1; \ + var_t *operand2; \ + number_t bRes; \ +CODESTARTop(name) \ + CHKiRet(vmstk.Pop2CommOp(pThis->pStk, &operand1, &operand2)); \ + /* data types are equal (so we look only at operand1), but we must \ + * check which type we have to deal with... \ + */ \ + switch(operand1->varType) { +#define ENDCMPOP(name) \ + default: \ + bRes = 0; /* we do not abort just so that we have a value. TODO: reconsider */ \ + break; \ + } \ + \ + /* we have a result, so let's push it */ \ + var.SetNumber(operand1, bRes); \ + vmstk.Push(pThis->pStk, operand1); /* result */ \ + var.Destruct(&operand2); /* no longer needed */ \ +finalize_it: \ +ENDop(name) + +BEGINCMPOP(CMP_EQ) /* remember to change the name also in the END macro! */ + case VARTYPE_NUMBER: + bRes = operand1->val.num == operand2->val.num; + break; + case VARTYPE_STR: + bRes = !rsCStrCStrCmp(operand1->val.pStr, operand2->val.pStr); + break; +ENDCMPOP(CMP_EQ) + +BEGINCMPOP(CMP_NEQ) /* remember to change the name also in the END macro! */ + case VARTYPE_NUMBER: + bRes = operand1->val.num != operand2->val.num; + break; + case VARTYPE_STR: + bRes = rsCStrCStrCmp(operand1->val.pStr, operand2->val.pStr); + break; +ENDCMPOP(CMP_EQ) + +BEGINCMPOP(CMP_LT) /* remember to change the name also in the END macro! */ + case VARTYPE_NUMBER: + bRes = operand1->val.num < operand2->val.num; + break; + case VARTYPE_STR: + bRes = rsCStrCStrCmp(operand1->val.pStr, operand2->val.pStr) < 0; + break; +ENDCMPOP(CMP_LT) + +BEGINCMPOP(CMP_GT) /* remember to change the name also in the END macro! */ + case VARTYPE_NUMBER: + bRes = operand1->val.num > operand2->val.num; + break; + case VARTYPE_STR: + bRes = rsCStrCStrCmp(operand1->val.pStr, operand2->val.pStr) > 0; + break; +ENDCMPOP(CMP_GT) + +BEGINCMPOP(CMP_LTEQ) /* remember to change the name also in the END macro! */ + case VARTYPE_NUMBER: + bRes = operand1->val.num <= operand2->val.num; + break; + case VARTYPE_STR: + bRes = rsCStrCStrCmp(operand1->val.pStr, operand2->val.pStr) <= 0; + break; +ENDCMPOP(CMP_LTEQ) + +BEGINCMPOP(CMP_GTEQ) /* remember to change the name also in the END macro! */ + case VARTYPE_NUMBER: + bRes = operand1->val.num >= operand2->val.num; + break; + case VARTYPE_STR: + bRes = rsCStrCStrCmp(operand1->val.pStr, operand2->val.pStr) >= 0; + break; +ENDCMPOP(CMP_GTEQ) + +#undef BEGINCMPOP +#undef ENDCMPOP +/* end regular compare operations */ + +/* comare operations that work on strings, only */ +BEGINop(CMP_CONTAINS) /* remember to set the instruction also in the ENDop macro! */ + var_t *operand1; + var_t *operand2; + number_t bRes; +CODESTARTop(CMP_CONTAINS) + /* operand2 is on top of stack, so needs to be popped first */ + vmstk.PopString(pThis->pStk, &operand2); + vmstk.PopString(pThis->pStk, &operand1); +var.DebugPrint(operand1); \ +var.DebugPrint(operand2); \ + /* TODO: extend cstr class so that it supports location of cstr inside cstr */ + bRes = (rsCStrLocateInSzStr(operand2->val.pStr, rsCStrGetSzStr(operand1->val.pStr)) == -1) ? 0 : 1; + + /* we have a result, so let's push it */ +RUNLOG_VAR("%lld", bRes); \ + PUSHRESULTop(operand1, bRes); + var.Destruct(&operand2); /* no longer needed */ +ENDop(CMP_CONTAINS) + + +BEGINop(CMP_CONTAINSI) /* remember to set the instruction also in the ENDop macro! */ + var_t *operand1; + var_t *operand2; + number_t bRes; +CODESTARTop(CMP_CONTAINSI) + /* operand2 is on top of stack, so needs to be popped first */ + vmstk.PopString(pThis->pStk, &operand2); + vmstk.PopString(pThis->pStk, &operand1); +var.DebugPrint(operand1); \ +var.DebugPrint(operand2); \ + /* TODO: extend cstr class so that it supports location of cstr inside cstr */ + bRes = (rsCStrCaseInsensitiveLocateInSzStr(operand2->val.pStr, rsCStrGetSzStr(operand1->val.pStr)) == -1) ? 0 : 1; + + /* we have a result, so let's push it */ +RUNLOG_VAR("%lld", bRes); \ + PUSHRESULTop(operand1, bRes); + var.Destruct(&operand2); /* no longer needed */ +ENDop(CMP_CONTAINSI) + + +BEGINop(CMP_STARTSWITH) /* remember to set the instruction also in the ENDop macro! */ + var_t *operand1; + var_t *operand2; + number_t bRes; +CODESTARTop(CMP_STARTSWITH) + /* operand2 is on top of stack, so needs to be popped first */ + vmstk.PopString(pThis->pStk, &operand2); + vmstk.PopString(pThis->pStk, &operand1); +var.DebugPrint(operand1); \ +var.DebugPrint(operand2); \ + /* TODO: extend cstr class so that it supports location of cstr inside cstr */ + bRes = (rsCStrStartsWithSzStr(operand1->val.pStr, rsCStrGetSzStr(operand2->val.pStr), + rsCStrLen(operand2->val.pStr)) == 0) ? 1 : 0; + + /* we have a result, so let's push it */ +RUNLOG_VAR("%lld", bRes); \ + PUSHRESULTop(operand1, bRes); + var.Destruct(&operand2); /* no longer needed */ +ENDop(CMP_STARTSWITH) + + +BEGINop(CMP_STARTSWITHI) /* remember to set the instruction also in the ENDop macro! */ + var_t *operand1; + var_t *operand2; + number_t bRes; +CODESTARTop(CMP_STARTSWITHI) + /* operand2 is on top of stack, so needs to be popped first */ + vmstk.PopString(pThis->pStk, &operand2); + vmstk.PopString(pThis->pStk, &operand1); +var.DebugPrint(operand1); \ +var.DebugPrint(operand2); \ + /* TODO: extend cstr class so that it supports location of cstr inside cstr */ + bRes = (rsCStrCaseInsensitveStartsWithSzStr(operand1->val.pStr, rsCStrGetSzStr(operand2->val.pStr), + rsCStrLen(operand2->val.pStr)) == 0) ? 1 : 0; + + /* we have a result, so let's push it */ +RUNLOG_VAR("%lld", bRes); \ + PUSHRESULTop(operand1, bRes); + var.Destruct(&operand2); /* no longer needed */ +ENDop(CMP_STARTSWITHI) + +/* end comare operations that work on strings, only */ + +BEGINop(STRADD) /* remember to set the instruction also in the ENDop macro! */ + var_t *operand1; + var_t *operand2; +CODESTARTop(STRADD) + vmstk.PopString(pThis->pStk, &operand2); + vmstk.PopString(pThis->pStk, &operand1); + + CHKiRet(rsCStrAppendCStr(operand1->val.pStr, operand2->val.pStr)); + + /* we have a result, so let's push it */ + vmstk.Push(pThis->pStk, operand1); + var.Destruct(&operand2); /* no longer needed */ +finalize_it: +ENDop(STRADD) + +BEGINop(NOT) /* remember to set the instruction also in the ENDop macro! */ + var_t *operand; +CODESTARTop(NOT) + vmstk.PopBool(pThis->pStk, &operand); + PUSHRESULTop(operand, !operand->val.num); +ENDop(NOT) + +BEGINop(UNARY_MINUS) /* remember to set the instruction also in the ENDop macro! */ + var_t *operand; +CODESTARTop(UNARY_MINUS) + vmstk.PopNumber(pThis->pStk, &operand); + PUSHRESULTop(operand, -operand->val.num); +ENDop(UNARY_MINUS) + + +BEGINop(PUSHCONSTANT) /* remember to set the instruction also in the ENDop macro! */ + var_t *pVarDup; /* we need to duplicate the var, as we need to hand it over */ +CODESTARTop(PUSHCONSTANT) + CHKiRet(var.Duplicate(pOp->operand.pVar, &pVarDup)); + vmstk.Push(pThis->pStk, pVarDup); +finalize_it: +ENDop(PUSHCONSTANT) + + +BEGINop(PUSHMSGVAR) /* remember to set the instruction also in the ENDop macro! */ + var_t *pVal; /* the value to push */ + cstr_t *pstrVal; +CODESTARTop(PUSHMSGVAR) + if(pThis->pMsg == NULL) { + /* TODO: flag an error message! As a work-around, we permit + * execution to continue here with an empty string + */ + /* TODO: create a method in var to create a string var? */ + CHKiRet(var.Construct(&pVal)); + CHKiRet(var.ConstructFinalize(pVal)); + CHKiRet(rsCStrConstructFromszStr(&pstrVal, (uchar*)"")); + CHKiRet(var.SetString(pVal, pstrVal)); + } else { + /* we have a message, so pull value from there */ + CHKiRet(msgGetMsgVar(pThis->pMsg, pOp->operand.pVar->val.pStr, &pVal)); + } + + /* if we reach this point, we have a valid pVal and can push it */ + vmstk.Push(pThis->pStk, pVal); +finalize_it: +ENDop(PUSHMSGVAR) + + +BEGINop(PUSHSYSVAR) /* remember to set the instruction also in the ENDop macro! */ + var_t *pVal; /* the value to push */ +CODESTARTop(PUSHSYSVAR) + CHKiRet(sysvar.GetVar(pOp->operand.pVar->val.pStr, &pVal)); + vmstk.Push(pThis->pStk, pVal); +finalize_it: +ENDop(PUSHSYSVAR) + + +/* ------------------------------ end instruction set implementation ------------------------------ */ + + +/* Standard-Constructor + */ +BEGINobjConstruct(vm) /* be sure to specify the object type also in END macro! */ +ENDobjConstruct(vm) + + +/* ConstructionFinalizer + * rgerhards, 2008-01-09 + */ +static rsRetVal +vmConstructFinalize(vm_t __attribute__((unused)) *pThis) +{ + DEFiRet; + ISOBJ_TYPE_assert(pThis, vm); + + CHKiRet(vmstk.Construct(&pThis->pStk)); + CHKiRet(vmstk.ConstructFinalize(pThis->pStk)); + +finalize_it: + RETiRet; +} + + +/* destructor for the vm object */ +BEGINobjDestruct(vm) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDestruct(vm) + if(pThis->pStk != NULL) + vmstk.Destruct(&pThis->pStk); + if(pThis->pMsg != NULL) + msgDestruct(&pThis->pMsg); +ENDobjDestruct(vm) + + +/* debugprint for the vm object */ +BEGINobjDebugPrint(vm) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDebugPrint(vm) + dbgoprint((obj_t*) pThis, "rsyslog virtual machine, currently no state info available\n"); +ENDobjDebugPrint(vm) + + +/* execute a program + */ +static rsRetVal +execProg(vm_t *pThis, vmprg_t *pProg) +{ + DEFiRet; + vmop_t *pCurrOp; /* virtual instruction pointer */ + + ISOBJ_TYPE_assert(pThis, vm); + ISOBJ_TYPE_assert(pProg, vmprg); + +#define doOP(OP) case opcode_##OP: CHKiRet(op##OP(pThis, pCurrOp)); break + pCurrOp = pProg->vmopRoot; /* TODO: do this via a method! */ + while(pCurrOp != NULL && pCurrOp->opcode != opcode_END_PROG) { + switch(pCurrOp->opcode) { + doOP(OR); + doOP(AND); + doOP(CMP_EQ); + doOP(CMP_NEQ); + doOP(CMP_LT); + doOP(CMP_GT); + doOP(CMP_LTEQ); + doOP(CMP_GTEQ); + doOP(CMP_CONTAINS); + doOP(CMP_CONTAINSI); + doOP(CMP_STARTSWITH); + doOP(CMP_STARTSWITHI); + doOP(NOT); + doOP(PUSHCONSTANT); + doOP(PUSHMSGVAR); + doOP(PUSHSYSVAR); + doOP(STRADD); + doOP(PLUS); + doOP(MINUS); + doOP(TIMES); + doOP(DIV); + doOP(MOD); + doOP(UNARY_MINUS); + default: + ABORT_FINALIZE(RS_RET_INVALID_VMOP); + dbgoprint((obj_t*) pThis, "invalid instruction %d in vmprg\n", pCurrOp->opcode); + break; + } + /* so far, we have plain sequential execution, so on to next... */ + pCurrOp = pCurrOp->pNext; + } +#undef doOP + + /* if we reach this point, our program has intintionally terminated + * (no error state). + */ + +finalize_it: + RETiRet; +} + + +/* Set the current message object for the VM. It *is* valid to set a + * NULL message object, what simply means there is none. Message + * objects are properly reference counted. + */ +static rsRetVal +SetMsg(vm_t *pThis, msg_t *pMsg) +{ + DEFiRet; + if(pThis->pMsg != NULL) { + msgDestruct(&pThis->pMsg); + } + + if(pMsg != NULL) { + pThis->pMsg = MsgAddRef(pMsg); + } + + RETiRet; +} + + +/* Pop a var from the stack and return it to caller. The variable type is not + * changed, it is taken from the stack as is. This functionality is + * partly needed. We may (or may not ;)) be able to remove it once we have + * full RainerScript support. -- rgerhards, 2008-02-25 + */ +static rsRetVal +PopVarFromStack(vm_t *pThis, var_t **ppVar) +{ + DEFiRet; + CHKiRet(vmstk.Pop(pThis->pStk, ppVar)); +finalize_it: + RETiRet; +} + + +/* Pop a boolean from the stack and return it to caller. This functionality is + * partly needed. We may (or may not ;)) be able to remove it once we have + * full RainerScript support. -- rgerhards, 2008-02-25 + */ +static rsRetVal +PopBoolFromStack(vm_t *pThis, var_t **ppVar) +{ + DEFiRet; + CHKiRet(vmstk.PopBool(pThis->pStk, ppVar)); +finalize_it: + RETiRet; +} + + +/* queryInterface function + * rgerhards, 2008-02-21 + */ +BEGINobjQueryInterface(vm) +CODESTARTobjQueryInterface(vm) + if(pIf->ifVersion != vmCURR_IF_VERSION) { /* check for current version, increment on each change */ + ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED); + } + + /* ok, we have the right interface, so let's fill it + * Please note that we may also do some backwards-compatibility + * work here (if we can support an older interface version - that, + * of course, also affects the "if" above). + */ + pIf->Construct = vmConstruct; + pIf->ConstructFinalize = vmConstructFinalize; + pIf->Destruct = vmDestruct; + pIf->DebugPrint = vmDebugPrint; + pIf->ExecProg = execProg; + pIf->PopBoolFromStack = PopBoolFromStack; + pIf->PopVarFromStack = PopVarFromStack; + pIf->SetMsg = SetMsg; +finalize_it: +ENDobjQueryInterface(vm) + + +/* Initialize the vm class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-19 + */ +BEGINObjClassInit(vm, 1, OBJ_IS_CORE_MODULE) /* class, version */ + /* request objects we use */ + CHKiRet(objUse(vmstk, CORE_COMPONENT)); + CHKiRet(objUse(var, CORE_COMPONENT)); + CHKiRet(objUse(sysvar, CORE_COMPONENT)); + + /* set our own handlers */ + OBJSetMethodHandler(objMethod_DEBUGPRINT, vmDebugPrint); + OBJSetMethodHandler(objMethod_CONSTRUCTION_FINALIZER, vmConstructFinalize); +ENDObjClassInit(vm) + +/* vi:set ai: + */ |