/* ruleset.c - rsyslog's ruleset object
 *
 * We have a two-way structure of linked lists: one config-specifc linked list
 * (conf->rulesets.llRulesets) hold alls rule sets that we know. Included in each
 * list is a list of rules (which contain a list of actions, but that's
 * a different story).
 *
 * Usually, only a single rule set is executed. However, there exist some
 * situations where all rules must be iterated over, for example on HUP. Thus,
 * we also provide interfaces to do that.
 *
 * Module begun 2009-06-10 by Rainer Gerhards
 *
 * Copyright 2009-2021 Rainer Gerhards and Adiscon GmbH.
 *
 * This file is part of the rsyslog runtime library.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *       -or-
 *       see COPYING.ASL20 in the source distribution
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "config.h"
#include <stdlib.h>
#include <assert.h>
#include <ctype.h>

#include "rsyslog.h"
#include "obj.h"
#include "cfsysline.h"
#include "msg.h"
#include "ruleset.h"
#include "errmsg.h"
#include "parser.h"
#include "batch.h"
#include "unicode-helper.h"
#include "rsconf.h"
#include "action.h"
#include "rainerscript.h"
#include "srUtils.h"
#include "modules.h"
#include "wti.h"
#include "dirty.h" /* for main ruleset queue creation */


/* static data */
DEFobjStaticHelpers
DEFobjCurrIf(parser)

/* tables for interfacing with the v6 config system (as far as we need to) */
static struct cnfparamdescr rspdescr[] = {
	{ "name", eCmdHdlrString, CNFPARAM_REQUIRED },
	{ "parser", eCmdHdlrArray, 0 }
};
static struct cnfparamblk rspblk =
	{ CNFPARAMBLK_VERSION,
	  sizeof(rspdescr)/sizeof(struct cnfparamdescr),
	  rspdescr
	};

/* forward definitions */
static rsRetVal processBatch(batch_t *pBatch, wti_t *pWti);
static rsRetVal scriptExec(struct cnfstmt *root, smsg_t *pMsg, wti_t *pWti);


/* ---------- linked-list key handling functions (ruleset) ---------- */

/* destructor for linked list keys.
 */
rsRetVal
rulesetKeyDestruct(void __attribute__((unused)) *pData)
{
	free(pData);
	return RS_RET_OK;
}
/* ---------- END linked-list key handling functions (ruleset) ---------- */


/* iterate over all actions in a script (stmt subtree) */
static void
scriptIterateAllActions(struct cnfstmt *root, rsRetVal (*pFunc)(void*, void*), void* pParam)
{
	struct cnfstmt *stmt;
	for(stmt = root ; stmt != NULL ; stmt = stmt->next) {
		switch(stmt->nodetype) {
		case S_NOP:
		case S_STOP:
		case S_SET:
		case S_UNSET:
		case S_CALL_INDIRECT:
		case S_CALL:/* call does not need to do anything - done in called ruleset! */
			break;
		case S_ACT:
			DBGPRINTF("iterateAllActions calling into action %p\n", stmt->d.act);
			pFunc(stmt->d.act, pParam);
			break;
		case S_IF:
			if(stmt->d.s_if.t_then != NULL)
				scriptIterateAllActions(stmt->d.s_if.t_then,
							pFunc, pParam);
			if(stmt->d.s_if.t_else != NULL)
				scriptIterateAllActions(stmt->d.s_if.t_else,
							pFunc, pParam);
			break;
		case S_FOREACH:
			if(stmt->d.s_foreach.body != NULL)
				scriptIterateAllActions(stmt->d.s_foreach.body,
							pFunc, pParam);
			break;
		case S_PRIFILT:
			if(stmt->d.s_prifilt.t_then != NULL)
				scriptIterateAllActions(stmt->d.s_prifilt.t_then,
							pFunc, pParam);
			if(stmt->d.s_prifilt.t_else != NULL)
				scriptIterateAllActions(stmt->d.s_prifilt.t_else,
							pFunc, pParam);
			break;
		case S_PROPFILT:
			scriptIterateAllActions(stmt->d.s_propfilt.t_then,
						pFunc, pParam);
			break;
		case S_RELOAD_LOOKUP_TABLE: /* this is a NOP */
			break;
		default:
			dbgprintf("error: unknown stmt type %u during iterateAll\n",
				(unsigned) stmt->nodetype);
			#ifndef NDEBUG
				fprintf(stderr, "error: unknown stmt type %u during iterateAll\n",
					(unsigned) stmt->nodetype);
			#endif
			assert(0); /* abort under debugging */
			break;
		}
	}
}

/* driver to iterate over all of this ruleset actions */
typedef struct iterateAllActions_s {
	rsRetVal (*pFunc)(void*, void*);
	void *pParam;
} iterateAllActions_t;
/* driver to iterate over all actions */
DEFFUNC_llExecFunc(doIterateAllActions)
{
	DEFiRet;
	ruleset_t* pThis = (ruleset_t*) pData;
	iterateAllActions_t *pMyParam = (iterateAllActions_t*) pParam;
	scriptIterateAllActions(pThis->root, pMyParam->pFunc, pMyParam->pParam);
	RETiRet;
}
/* iterate over ALL actions present in the WHOLE system.
 * this is often needed, for example when HUP processing
 * must be done or a shutdown is pending.
 */
static rsRetVal
iterateAllActions(rsconf_t *conf, rsRetVal (*pFunc)(void*, void*), void* pParam)
{
	iterateAllActions_t params;
	DEFiRet;
	assert(pFunc != NULL);

	params.pFunc = pFunc;
	params.pParam = pParam;
	CHKiRet(llExecFunc(&(conf->rulesets.llRulesets), doIterateAllActions, &params));

finalize_it:
	RETiRet;
}

/* driver to iterate over all rulesets */
DEFFUNC_llExecFunc(doActivateRulesetQueues)
{
	DEFiRet;
	ruleset_t* pThis = (ruleset_t*) pData;
	dbgprintf("Activating Ruleset Queue[%p] for Ruleset %s\n",
		  pThis->pQueue, pThis->pszName);
	if(pThis->pQueue != NULL)
		startMainQueue(runConf, pThis->pQueue);
	RETiRet;
}
/* activate all ruleset queues */
rsRetVal
activateRulesetQueues(void)
{
	llExecFunc(&(runConf->rulesets.llRulesets), doActivateRulesetQueues, NULL);
	return RS_RET_OK;
}


static rsRetVal
execAct(struct cnfstmt *stmt, smsg_t *pMsg, wti_t *pWti)
{
	DEFiRet;
	if(stmt->d.act->bDisabled) {
		DBGPRINTF("action %d died, do NOT execute\n", stmt->d.act->iActionNbr);
		FINALIZE;
	}

	DBGPRINTF("executing action %d\n", stmt->d.act->iActionNbr);
	stmt->d.act->submitToActQ(stmt->d.act, pWti, pMsg);
	if(iRet != RS_RET_DISCARDMSG) {
		/* note: we ignore the error code here, as we do NEVER want to
		 * stop script execution due to action return code
		 */
		iRet = RS_RET_OK;
	}
finalize_it:
	RETiRet;
}

static rsRetVal ATTR_NONNULL()
execSet(const struct cnfstmt *const stmt,
	smsg_t *const pMsg,
	wti_t *const __restrict__ pWti)
{
	struct svar result;
	DEFiRet;
	cnfexprEval(stmt->d.s_set.expr, &result, pMsg, pWti);
	msgSetJSONFromVar(pMsg, stmt->d.s_set.varname, &result, stmt->d.s_set.force_reset);
	varDelete(&result);
	RETiRet;
}

static rsRetVal
execUnset(struct cnfstmt *stmt, smsg_t *pMsg)
{
	DEFiRet;
	msgDelJSON(pMsg, stmt->d.s_unset.varname);
	RETiRet;
}

static rsRetVal
execCallIndirect(struct cnfstmt *const __restrict__ stmt,
	smsg_t *pMsg,
	wti_t *const __restrict__ pWti)
{
	ruleset_t *pRuleset;
	struct svar result;
	int bMustFree; /* dummy parameter */
	DEFiRet;

	assert(stmt->d.s_call_ind.expr != NULL);

	cnfexprEval(stmt->d.s_call_ind.expr, &result, pMsg, pWti);
	uchar *const rsName = (uchar*) var2CString(&result, &bMustFree);
	const rsRetVal localRet = rulesetGetRuleset(runConf, &pRuleset, rsName);
	if(localRet != RS_RET_OK) {
		/* in that case, we accept that a NOP will "survive" */
		LogError(0, RS_RET_RULESET_NOT_FOUND, "error: CALL_INDIRECT: "
			"ruleset '%s' cannot be found, treating as NOP\n", rsName);
		FINALIZE;
	}
	DBGPRINTF("CALL_INDIRECT obtained ruleset ptr %p for ruleset '%s' [hasQueue:%d]\n",
		  pRuleset, rsName, rulesetHasQueue(pRuleset));
	if(rulesetHasQueue(pRuleset)) {
		CHKmalloc(pMsg = MsgDup((smsg_t*) pMsg));
		DBGPRINTF("CALL_INDIRECT: forwarding message to async ruleset %p\n",
			  pRuleset->pQueue);
		MsgSetFlowControlType(pMsg, eFLOWCTL_NO_DELAY);
		MsgSetRuleset(pMsg, pRuleset);
		/* Note: we intentionally use submitMsg2() here, as we process messages
		 * that were already run through the rate-limiter.
		 */
		submitMsg2(pMsg);
	} else {
		CHKiRet(scriptExec(pRuleset->root, pMsg, pWti));
	}
finalize_it:
	varDelete(&result);
	free(rsName);
	RETiRet;
}

static rsRetVal
execCall(struct cnfstmt *stmt, smsg_t *pMsg, wti_t *pWti)
{
	DEFiRet;
	if(stmt->d.s_call.ruleset == NULL) {
		CHKiRet(scriptExec(stmt->d.s_call.stmt, pMsg, pWti));
	} else {
		CHKmalloc(pMsg = MsgDup((smsg_t*) pMsg));
		DBGPRINTF("CALL: forwarding message to async ruleset %p\n",
			  stmt->d.s_call.ruleset->pQueue);
		MsgSetFlowControlType(pMsg, eFLOWCTL_NO_DELAY);
		MsgSetRuleset(pMsg, stmt->d.s_call.ruleset);
		/* Note: we intentionally use submitMsg2() here, as we process messages
		 * that were already run through the rate-limiter.
		 */
		submitMsg2(pMsg);
	}
finalize_it:
	RETiRet;
}

static rsRetVal
execIf(struct cnfstmt *const stmt, smsg_t *const pMsg, wti_t *const pWti)
{
	sbool bRet;
	DEFiRet;
	bRet = cnfexprEvalBool(stmt->d.s_if.expr, pMsg, pWti);
	DBGPRINTF("if condition result is %d\n", bRet);
	if(bRet) {
		if(stmt->d.s_if.t_then != NULL)
			CHKiRet(scriptExec(stmt->d.s_if.t_then, pMsg, pWti));
	} else {
		if(stmt->d.s_if.t_else != NULL)
			CHKiRet(scriptExec(stmt->d.s_if.t_else, pMsg, pWti));
	}
finalize_it:
	RETiRet;
}

static rsRetVal
invokeForeachBodyWith(struct cnfstmt *stmt, json_object *o, smsg_t *pMsg, wti_t *pWti) {
	struct svar v;
	v.datatype = 'J';
	v.d.json = o;
	DEFiRet;
	CHKiRet(msgSetJSONFromVar(pMsg, (uchar*)stmt->d.s_foreach.iter->var, &v, 1));
	CHKiRet(scriptExec(stmt->d.s_foreach.body, pMsg, pWti));
finalize_it:
	RETiRet;
}

static rsRetVal
callForeachArray(struct cnfstmt *stmt, json_object *arr, smsg_t *pMsg, wti_t *pWti) {
	DEFiRet;
	int len = json_object_array_length(arr);
	json_object *curr;
	for (int i = 0; i < len; i++) {
		curr = json_object_array_get_idx(arr, i);
		CHKiRet(invokeForeachBodyWith(stmt, curr, pMsg, pWti));
	}
finalize_it:
	RETiRet;
}


static rsRetVal
callForeachObject(struct cnfstmt *stmt, json_object *arr, smsg_t *pMsg, wti_t *pWti) {
	json_object *entry = NULL;
	json_object *key = NULL;
	const char **keys = NULL;
	json_object *curr = NULL;
	const char **curr_key;
	struct json_object_iterator it;
	struct json_object_iterator itEnd;
	DEFiRet;

	int len = json_object_object_length(arr);
	CHKmalloc(keys = calloc(len, sizeof(char*)));
	curr_key = keys;
	it = json_object_iter_begin(arr);
	itEnd = json_object_iter_end(arr);
	while (!json_object_iter_equal(&it, &itEnd)) {
		*curr_key = json_object_iter_peek_name(&it);
		curr_key++;
		json_object_iter_next(&it);
	}
	CHKmalloc(entry = json_object_new_object());
	for (int i = 0; i < len; i++) {
		if (json_object_object_get_ex(arr, keys[i], &curr)) {
			CHKmalloc(key = json_object_new_string(keys[i]));
			json_object_object_add(entry, "key", key);
			key = NULL;
			json_object_object_add(entry, "value", json_object_get(curr));
			CHKiRet(invokeForeachBodyWith(stmt, entry, pMsg, pWti));
		}
	}
finalize_it:
	if (keys != NULL) free(keys);
	if (entry != NULL) json_object_put(entry);
	/* "fix" Coverity scan issue CID 185393: key currently can NOT be NULL
	 * However, instead of just removing the
	 *   if (key != NULL) json_object_put(key);
	 * we put an assertion in its place.
	 */
	assert(key == NULL);

	RETiRet;
}

static rsRetVal ATTR_NONNULL()
execForeach(struct cnfstmt *const stmt, smsg_t *const pMsg, wti_t *const pWti)
{
	json_object *arr = NULL;
	DEFiRet;

	/* arr can either be an array or an associative-array (obj) */
	arr = cnfexprEvalCollection(stmt->d.s_foreach.iter->collection, pMsg, pWti);

	if (arr == NULL) {
		DBGPRINTF("foreach loop skipped, as object to iterate upon is empty\n");
		FINALIZE;
	} else if (json_object_is_type(arr, json_type_array) && json_object_array_length(arr) > 0) {
		CHKiRet(callForeachArray(stmt, arr, pMsg, pWti));
	} else if (json_object_is_type(arr, json_type_object) && json_object_object_length(arr) > 0) {
		CHKiRet(callForeachObject(stmt, arr, pMsg, pWti));
	} else {
		DBGPRINTF("foreach loop skipped, as object to iterate upon is empty or is not an array\n");
		FINALIZE;
	}
	CHKiRet(msgDelJSON(pMsg, (uchar*)stmt->d.s_foreach.iter->var));

finalize_it:
	if (arr != NULL) json_object_put(arr);

	RETiRet;
}

static rsRetVal
execPRIFILT(struct cnfstmt *stmt, smsg_t *pMsg, wti_t *pWti)
{
	int bRet;
	DEFiRet;
	if( (stmt->d.s_prifilt.pmask[pMsg->iFacility] == TABLE_NOPRI) ||
	   ((stmt->d.s_prifilt.pmask[pMsg->iFacility]
		    & (1<<pMsg->iSeverity)) == 0) )
		bRet = 0;
	else
		bRet = 1;

	DBGPRINTF("PRIFILT condition result is %d\n", bRet);
	if(bRet) {
		if(stmt->d.s_prifilt.t_then != NULL)
			CHKiRet(scriptExec(stmt->d.s_prifilt.t_then, pMsg, pWti));
	} else {
		if(stmt->d.s_prifilt.t_else != NULL)
			CHKiRet(scriptExec(stmt->d.s_prifilt.t_else, pMsg, pWti));
	}
finalize_it:
	RETiRet;
}


/* helper to execPROPFILT(), as the evaluation itself is quite lengthy */
static int
evalPROPFILT(struct cnfstmt *stmt, smsg_t *pMsg)
{
	unsigned short pbMustBeFreed;
	uchar *pszPropVal;
	int bRet = 0;
	rs_size_t propLen;

	if(stmt->d.s_propfilt.prop.id == PROP_INVALID)
		goto done;

	pszPropVal = MsgGetProp(pMsg, NULL, &stmt->d.s_propfilt.prop,
				&propLen, &pbMustBeFreed, NULL);

	/* Now do the compares (short list currently ;)) */
	switch(stmt->d.s_propfilt.operation ) {
	case FIOP_CONTAINS:
		if(rsCStrLocateInSzStr(stmt->d.s_propfilt.pCSCompValue, (uchar*) pszPropVal) != -1)
			bRet = 1;
		break;
	case FIOP_ISEMPTY:
		if(propLen == 0)
			bRet = 1; /* process message! */
		break;
	case FIOP_ISEQUAL:
		if(rsCStrSzStrCmp(stmt->d.s_propfilt.pCSCompValue,
				  pszPropVal, propLen) == 0)
			bRet = 1; /* process message! */
		break;
	case FIOP_STARTSWITH:
		if(rsCStrSzStrStartsWithCStr(stmt->d.s_propfilt.pCSCompValue,
				  pszPropVal, propLen) == 0)
			bRet = 1; /* process message! */
		break;
	case FIOP_REGEX:
		if(rsCStrSzStrMatchRegex(stmt->d.s_propfilt.pCSCompValue,
				(unsigned char*) pszPropVal, 0, &stmt->d.s_propfilt.regex_cache) == RS_RET_OK)
			bRet = 1;
		break;
	case FIOP_EREREGEX:
		if(rsCStrSzStrMatchRegex(stmt->d.s_propfilt.pCSCompValue,
				  (unsigned char*) pszPropVal, 1, &stmt->d.s_propfilt.regex_cache) == RS_RET_OK)
			bRet = 1;
		break;
	case FIOP_NOP:
	default:
		/* here, it handles NOP (for performance reasons) */
		assert(stmt->d.s_propfilt.operation == FIOP_NOP);
		bRet = 1; /* as good as any other default ;) */
		break;
	}

	/* now check if the value must be negated */
	if(stmt->d.s_propfilt.isNegated)
		bRet = (bRet == 1) ?  0 : 1;

	if(Debug) {
		if(stmt->d.s_propfilt.prop.id == PROP_CEE) {
			DBGPRINTF("Filter: check for CEE property '%s' (value '%s') ",
				stmt->d.s_propfilt.prop.name, pszPropVal);
		} else if(stmt->d.s_propfilt.prop.id == PROP_LOCAL_VAR) {
			DBGPRINTF("Filter: check for local var '%s' (value '%s') ",
				stmt->d.s_propfilt.prop.name, pszPropVal);
		} else if(stmt->d.s_propfilt.prop.id == PROP_GLOBAL_VAR) {
			DBGPRINTF("Filter: check for global var '%s' (value '%s') ",
				stmt->d.s_propfilt.prop.name, pszPropVal);
		} else {
			DBGPRINTF("Filter: check for property '%s' (value '%s') ",
				propIDToName(stmt->d.s_propfilt.prop.id), pszPropVal);
		}
		if(stmt->d.s_propfilt.isNegated)
			DBGPRINTF("NOT ");
		if(stmt->d.s_propfilt.operation == FIOP_ISEMPTY) {
			DBGPRINTF("%s : %s\n",
			       getFIOPName(stmt->d.s_propfilt.operation),
			       bRet ? "TRUE" : "FALSE");
		} else {
			DBGPRINTF("%s '%s': %s\n",
			       getFIOPName(stmt->d.s_propfilt.operation),
			       rsCStrGetSzStrNoNULL(stmt->d.s_propfilt.pCSCompValue),
			       bRet ? "TRUE" : "FALSE");
		}
	}

	/* cleanup */
	if(pbMustBeFreed)
		free(pszPropVal);
done:
	return bRet;
}

static rsRetVal
execPROPFILT(struct cnfstmt *stmt, smsg_t *pMsg, wti_t *pWti)
{
	sbool bRet;
	DEFiRet;

	bRet = evalPROPFILT(stmt, pMsg);
	DBGPRINTF("PROPFILT condition result is %d\n", bRet);
	if(bRet)
		CHKiRet(scriptExec(stmt->d.s_propfilt.t_then, pMsg, pWti));
finalize_it:
	RETiRet;
}

static rsRetVal ATTR_NONNULL()
execReloadLookupTable(struct cnfstmt *stmt)
{
	assert(stmt != NULL);
	lookup_ref_t *t;
	DEFiRet;
	t = stmt->d.s_reload_lookup_table.table;
	if (t == NULL) {
		ABORT_FINALIZE(RS_RET_NONE);
	}

	iRet = lookupReload(t, stmt->d.s_reload_lookup_table.stub_value);
	/* Note that reload dispatched above is performed asynchronously,
	   on a different thread. So rsRetVal it returns means it was triggered
	   successfully, and not that it was reloaded successfully. */

finalize_it:
	RETiRet;
}

/* The rainerscript execution engine. It is debatable if that would be better
 * contained in grammer/rainerscript.c, HOWEVER, that file focusses primarily
 * on the parsing and object creation part. So as an actual executor, it is
 * better suited here.
 * rgerhards, 2012-09-04
 */
static rsRetVal ATTR_NONNULL(2, 3)
scriptExec(struct cnfstmt *const root, smsg_t *const pMsg, wti_t *const pWti)
{
	struct cnfstmt *stmt;
	DEFiRet;

	for(stmt = root ; stmt != NULL ; stmt = stmt->next) {
		if(*pWti->pbShutdownImmediate) {
			DBGPRINTF("scriptExec: ShutdownImmediate set, "
				  "force terminating\n");
			ABORT_FINALIZE(RS_RET_FORCE_TERM);
		}
		if(Debug) {
			cnfstmtPrintOnly(stmt, 2, 0);
		}
		switch(stmt->nodetype) {
		case S_NOP:
			break;
		case S_STOP:
			ABORT_FINALIZE(RS_RET_DISCARDMSG);
			break;
		case S_ACT:
			CHKiRet(execAct(stmt, pMsg, pWti));
			break;
		case S_SET:
			CHKiRet(execSet(stmt, pMsg, pWti));
			break;
		case S_UNSET:
			CHKiRet(execUnset(stmt, pMsg));
			break;
		case S_CALL:
			CHKiRet(execCall(stmt, pMsg, pWti));
			break;
		case S_CALL_INDIRECT:
			CHKiRet(execCallIndirect(stmt, pMsg, pWti));
			break;
		case S_IF:
			CHKiRet(execIf(stmt, pMsg, pWti));
			break;
		case S_FOREACH:
			CHKiRet(execForeach(stmt, pMsg, pWti));
			break;
		case S_PRIFILT:
			CHKiRet(execPRIFILT(stmt, pMsg, pWti));
			break;
		case S_PROPFILT:
			CHKiRet(execPROPFILT(stmt, pMsg, pWti));
			break;
		case S_RELOAD_LOOKUP_TABLE:
			CHKiRet(execReloadLookupTable(stmt));
			break;
		default:
			dbgprintf("error: unknown stmt type %u during exec\n",
				(unsigned) stmt->nodetype);
			break;
		}
	}
finalize_it:
	RETiRet;
}


/* Process (consume) a batch of messages. Calls the actions configured.
 * This is called by MAIN queues.
 */
static rsRetVal
processBatch(batch_t *pBatch, wti_t *pWti)
{
	int i;
	smsg_t *pMsg;
	ruleset_t *pRuleset;
	rsRetVal localRet;
	DEFiRet;

	DBGPRINTF("processBATCH: batch of %d elements must be processed\n", pBatch->nElem);

	wtiResetExecState(pWti, pBatch);

	/* execution phase */
	for(i = 0 ; i < batchNumMsgs(pBatch) && !*(pWti->pbShutdownImmediate) ; ++i) {
		pMsg = pBatch->pElem[i].pMsg;
		DBGPRINTF("processBATCH: next msg %d: %.128s\n", i, pMsg->pszRawMsg);
		pRuleset = (pMsg->pRuleset == NULL) ? runConf->rulesets.pDflt : pMsg->pRuleset;
		localRet = scriptExec(pRuleset->root, pMsg, pWti);
		/* the most important case here is that processing may be aborted
		 * due to pbShutdownImmediate, in which case we MUST NOT flag this
		 * message as committed. If we would do so, the message would
		 * potentially be lost.
		 */
		if(localRet == RS_RET_OK)
			batchSetElemState(pBatch, i, BATCH_STATE_COMM);
		else if(localRet == RS_RET_SUSPENDED)
			--i;
	}

	/* commit phase */
	DBGPRINTF("END batch execution phase, entering to commit phase "
		"[processed %d of %d messages]\n", i, batchNumMsgs(pBatch));
	actionCommitAllDirect(pWti);

	DBGPRINTF("processBATCH: batch of %d elements has been processed\n", pBatch->nElem);
	RETiRet;
}


/* return the ruleset-assigned parser list. NULL means use the default
 * parser list.
 * rgerhards, 2009-11-04
 */
static parserList_t*
GetParserList(rsconf_t *conf, smsg_t *pMsg)
{
	return (pMsg->pRuleset == NULL) ? conf->rulesets.pDflt->pParserLst : pMsg->pRuleset->pParserLst;
}


/* Add a script block to the current ruleset */
static void ATTR_NONNULL(1)
addScript(ruleset_t *const pThis, struct cnfstmt *const script)
{
	if(script == NULL) /* happens for include() */
		return;
	if(pThis->last == NULL)
		pThis->root = pThis->last = script;
	else {
		pThis->last->next = script;
		pThis->last = script;
	}
}


/* set name for ruleset */
static rsRetVal rulesetSetName(ruleset_t *pThis, uchar *pszName)
{
	DEFiRet;
	free(pThis->pszName);
	CHKmalloc(pThis->pszName = ustrdup(pszName));

finalize_it:
	RETiRet;
}


/* get current ruleset
 * We use a non-standard calling interface, as nothing can go wrong and it
 * is really much more natural to return the pointer directly.
 */
static ruleset_t*
GetCurrent(rsconf_t *conf)
{
	return conf->rulesets.pCurr;
}


/* get main queue associated with ruleset. If no ruleset-specifc main queue
 * is set, the primary main message queue is returned.
 * We use a non-standard calling interface, as nothing can go wrong and it
 * is really much more natural to return the pointer directly.
 */
static qqueue_t*
GetRulesetQueue(ruleset_t *pThis)
{
	ISOBJ_TYPE_assert(pThis, ruleset);
	return (pThis->pQueue == NULL) ? runConf->pMsgQueue : pThis->pQueue;
}


/* Find the ruleset with the given name and return a pointer to its object.
 */
rsRetVal
rulesetGetRuleset(rsconf_t *conf, ruleset_t **ppRuleset, uchar *pszName)
{
	DEFiRet;
	assert(ppRuleset != NULL);
	assert(pszName != NULL);

	CHKiRet(llFind(&(conf->rulesets.llRulesets), pszName, (void*) ppRuleset));

finalize_it:
	RETiRet;
}


/* Set a new default rule set. If the default can not be found, no change happens.
 */
static rsRetVal
SetDefaultRuleset(rsconf_t *conf, uchar *pszName)
{
	ruleset_t *pRuleset;
	DEFiRet;
	assert(pszName != NULL);

	CHKiRet(rulesetGetRuleset(conf, &pRuleset, pszName));
	conf->rulesets.pDflt = pRuleset;
	DBGPRINTF("default rule set changed to %p: '%s'\n", pRuleset, pszName);

finalize_it:
	RETiRet;
}


/* Set a new current rule set. If the ruleset can not be found, no change happens */
static rsRetVal
SetCurrRuleset(rsconf_t *conf, uchar *pszName)
{
	ruleset_t *pRuleset;
	DEFiRet;
	assert(pszName != NULL);

	CHKiRet(rulesetGetRuleset(conf, &pRuleset, pszName));
	conf->rulesets.pCurr = pRuleset;
	DBGPRINTF("current rule set changed to %p: '%s'\n", pRuleset, pszName);

finalize_it:
	RETiRet;
}


/* Standard-Constructor
 */
BEGINobjConstruct(ruleset) /* be sure to specify the object type also in END macro! */
	pThis->root = NULL;
	pThis->last = NULL;
ENDobjConstruct(ruleset)


/* ConstructionFinalizer
 * This also adds the rule set to the list of all known rulesets.
 */
static rsRetVal
rulesetConstructFinalize(rsconf_t *conf, ruleset_t *pThis)
{
	uchar *keyName;
	DEFiRet;
	ISOBJ_TYPE_assert(pThis, ruleset);

	/* we must duplicate our name, as the key destructer would also
	 * free it, resulting in a double-free. It's also cleaner to have
	 * two separate copies.
	 */
	CHKmalloc(keyName = ustrdup(pThis->pszName));
	CHKiRet(llAppend(&(conf->rulesets.llRulesets), keyName, pThis));

	/* and also the default, if so far none has been set */
	if(conf->rulesets.pDflt == NULL)
		conf->rulesets.pDflt = pThis;

finalize_it:
	RETiRet;
}


/* destructor for the ruleset object */
BEGINobjDestruct(ruleset) /* be sure to specify the object type also in END and CODESTART macros! */
CODESTARTobjDestruct(ruleset)
	DBGPRINTF("destructing ruleset %p, name %p\n", pThis, pThis->pszName);
	if(pThis->pQueue != NULL) {
		qqueueDestruct(&pThis->pQueue);
	}
	if(pThis->pParserLst != NULL) {
		parser.DestructParserList(&pThis->pParserLst);
	}
	free(pThis->pszName);
ENDobjDestruct(ruleset)


/* helper for Destructor, shut down queue workers */
DEFFUNC_llExecFunc(doShutdownQueueWorkers)
{
	DEFiRet;
	ruleset_t *const pThis = (ruleset_t*) pData;
	DBGPRINTF("shutting down queue workers for ruleset %p, name %s, queue %p\n",
		pThis, pThis->pszName, pThis->pQueue);
	ISOBJ_TYPE_assert(pThis, ruleset);
	if(pThis->pQueue != NULL) {
		qqueueShutdownWorkers(pThis->pQueue);
	}
	RETiRet;
}
/* helper for Destructor, shut down actions (cnfstmt's in general) */
DEFFUNC_llExecFunc(doDestructCnfStmt)
{
	DEFiRet;
	ruleset_t *const pThis = (ruleset_t*) pData;
	DBGPRINTF("shutting down actions and conf stmts for ruleset %p, name %s\n",
		pThis, pThis->pszName);
	ISOBJ_TYPE_assert(pThis, ruleset);
	cnfstmtDestructLst(pThis->root);
	RETiRet;
}
/* destruct ALL rule sets that reside in the system. This must
 * be callable before unloading this module as the module may
 * not be unloaded before unload of the actions is required. This is
 * kind of a left-over from previous logic and may be optimized one
 * everything runs stable again. -- rgerhards, 2009-06-10
 */
static rsRetVal
destructAllActions(rsconf_t *conf)
{
	DEFiRet;

	DBGPRINTF("rulesetDestructAllActions\n");
	/* we first need to stop all queue workers, else we
	 * may run into trouble with "call" statements calling
	 * into then-destroyed rulesets.
	 * see: https://github.com/rsyslog/rsyslog/issues/1122
	 */
	DBGPRINTF("destructAllActions: queue shutdown\n");
	llExecFunc(&(conf->rulesets.llRulesets), doShutdownQueueWorkers, NULL);
	DBGPRINTF("destructAllActions: action and conf stmt shutdown\n");
	llExecFunc(&(conf->rulesets.llRulesets), doDestructCnfStmt, NULL);

	CHKiRet(llDestroy(&(conf->rulesets.llRulesets)));
	CHKiRet(llInit(&(conf->rulesets.llRulesets), rulesetDestructForLinkedList,
		rulesetKeyDestruct, strcasecmp));
	conf->rulesets.pDflt = NULL;

finalize_it:
	RETiRet;
}

/* this is a special destructor for the linkedList class. LinkedList does NOT
 * provide a pointer to the pointer, but rather the raw pointer itself. So we
 * must map this, otherwise the destructor will abort.
 */
rsRetVal
rulesetDestructForLinkedList(void *pData)
{
	ruleset_t *pThis = (ruleset_t*) pData;
	return rulesetDestruct(&pThis);
}

/* debugprint for the ruleset object */
BEGINobjDebugPrint(ruleset) /* be sure to specify the object type also in END and CODESTART macros! */
CODESTARTobjDebugPrint(ruleset)
	dbgoprint((obj_t*) pThis, "rsyslog ruleset %s:\n", pThis->pszName);
	cnfstmtPrint(pThis->root, 0);
	dbgoprint((obj_t*) pThis, "ruleset %s assigned parser list:\n", pThis->pszName);
	printParserList(pThis->pParserLst);
ENDobjDebugPrint(ruleset)


/* helper for debugPrintAll(), prints a single ruleset */
DEFFUNC_llExecFunc(doDebugPrintAll)
{
	return rulesetDebugPrint((ruleset_t*) pData);
}
/* debug print all rulesets
 */
static rsRetVal
debugPrintAll(rsconf_t *conf)
{
	DEFiRet;
	dbgprintf("All Rulesets:\n");
	llExecFunc(&(conf->rulesets.llRulesets), doDebugPrintAll, NULL);
	dbgprintf("End of Rulesets.\n");
	RETiRet;
}

struct cnfstmt * removeNOPs(struct cnfstmt *root);
static void
rulesetOptimize(ruleset_t *pRuleset)
{
	if(Debug) {
		dbgprintf("ruleset '%s' before optimization:\n",
			  pRuleset->pszName);
		rulesetDebugPrint((ruleset_t*) pRuleset);
	}
	pRuleset->root = cnfstmtOptimize(pRuleset->root);
	if(Debug) {
		dbgprintf("ruleset '%s' after optimization:\n",
			  pRuleset->pszName);
		rulesetDebugPrint((ruleset_t*) pRuleset);
	}
}

/* helper for rulsetOptimizeAll(), optimizes a single ruleset */
DEFFUNC_llExecFunc(doRulesetOptimizeAll)
{
	rulesetOptimize((ruleset_t*) pData);
	return RS_RET_OK;
}
/* optimize all rulesets
 */
rsRetVal
rulesetOptimizeAll(rsconf_t *conf)
{
	DEFiRet;
	dbgprintf("begin ruleset optimization phase\n");
	llExecFunc(&(conf->rulesets.llRulesets), doRulesetOptimizeAll, NULL);
	dbgprintf("ruleset optimization phase finished.\n");
	RETiRet;
}


/* Create a ruleset-specific "main" queue for this ruleset. If one is already
 * defined, an error message is emitted but nothing else is done.
 * Note: we use the main message queue parameters for queue creation and access
 * syslogd.c directly to obtain these. This is far from being perfect, but
 * considered acceptable for the time being.
 * rgerhards, 2009-10-27
 */
static rsRetVal
doRulesetCreateQueue(rsconf_t *conf, int *pNewVal)
{
	uchar *rsname;
	DEFiRet;

	if(conf->rulesets.pCurr == NULL) {
		LogError(0, RS_RET_NO_CURR_RULESET, "error: currently no specific ruleset specified, thus a "
				"queue can not be added to it");
		ABORT_FINALIZE(RS_RET_NO_CURR_RULESET);
	}

	if(conf->rulesets.pCurr->pQueue != NULL) {
		LogError(0, RS_RET_RULES_QUEUE_EXISTS, "error: ruleset already has a main queue, can not "
				"add another one");
		ABORT_FINALIZE(RS_RET_RULES_QUEUE_EXISTS);
	}

	if(pNewVal == 0)
		FINALIZE; /* if it is turned off, we do not need to change anything ;) */

	rsname = (conf->rulesets.pCurr->pszName == NULL) ? (uchar*) "[ruleset]" : conf->rulesets.pCurr->pszName;
	DBGPRINTF("adding a ruleset-specific \"main\" queue for ruleset '%s'\n", rsname);
	CHKiRet(createMainQueue(&conf->rulesets.pCurr->pQueue, rsname, NULL));

finalize_it:
	RETiRet;
}

static rsRetVal
rulesetCreateQueue(void __attribute__((unused)) *pVal, int *pNewVal)
{
	return doRulesetCreateQueue(ourConf, pNewVal);
}

/* Add a ruleset specific parser to the ruleset. Note that adding the first
 * parser automatically disables the default parsers. If they are needed as well,
 * the must be added via explicit config directives.
 * Note: this is the only spot in the code that requires the parser object. In order
 * to solve some class init bootstrap sequence problems, we get the object handle here
 * instead of during module initialization. Note that objUse() is capable of being
 * called multiple times.
 * rgerhards, 2009-11-04
 */
static rsRetVal
doRulesetAddParser(ruleset_t *pRuleset, uchar *pName)
{
	parser_t *pParser;
	DEFiRet;

	CHKiRet(objUse(parser, CORE_COMPONENT));
	iRet = parser.FindParser(loadConf->parsers.pParsLstRoot, &pParser, pName);
	if(iRet == RS_RET_PARSER_NOT_FOUND) {
		LogError(0, RS_RET_PARSER_NOT_FOUND, "error: parser '%s' unknown at this time "
			  	"(maybe defined too late in rsyslog.conf?)", pName);
		ABORT_FINALIZE(RS_RET_NO_CURR_RULESET);
	} else if(iRet != RS_RET_OK) {
		LogError(0, iRet, "error trying to find parser '%s'\n", pName);
		FINALIZE;
	}

	CHKiRet(parser.AddParserToList(&pRuleset->pParserLst, pParser));

	DBGPRINTF("added parser '%s' to ruleset '%s'\n", pName, pRuleset->pszName);

finalize_it:
	free(pName); /* no longer needed */

	RETiRet;
}

static rsRetVal
rulesetAddParser(void __attribute__((unused)) *pVal, uchar *pName)
{
	return doRulesetAddParser(loadConf->rulesets.pCurr, pName);
}


/* Process ruleset() objects */
rsRetVal
rulesetProcessCnf(struct cnfobj *o)
{
	struct cnfparamvals *pvals;
	rsRetVal localRet;
	uchar *rsName = NULL;
	uchar *parserName;
	int nameIdx, parserIdx;
	ruleset_t *pRuleset;
	struct cnfarray *ar;
	int i;
	int qtype;
	uchar *rsname;
	DEFiRet;

	pvals = nvlstGetParams(o->nvlst, &rspblk, NULL);
	if(pvals == NULL) {
		ABORT_FINALIZE(RS_RET_CONFIG_ERROR);
	}
	DBGPRINTF("ruleset param blk after rulesetProcessCnf:\n");
	cnfparamsPrint(&rspblk, pvals);
	nameIdx = cnfparamGetIdx(&rspblk, "name");
	rsName = (uchar*)es_str2cstr(pvals[nameIdx].val.d.estr, NULL);

	localRet = rulesetGetRuleset(loadConf, &pRuleset, rsName);
	if(localRet == RS_RET_OK) {
		LogError(0, RS_RET_RULESET_EXISTS,
			"error: ruleset '%s' specified more than once",
			rsName);
		cnfstmtDestructLst(o->script);
		ABORT_FINALIZE(RS_RET_RULESET_EXISTS);
	} else if(localRet != RS_RET_NOT_FOUND) {
		ABORT_FINALIZE(localRet);
	}

	CHKiRet(rulesetConstruct(&pRuleset));
	if((localRet = rulesetSetName(pRuleset, rsName)) != RS_RET_OK) {
		rulesetDestruct(&pRuleset);
		ABORT_FINALIZE(localRet);
	}
	if((localRet = rulesetConstructFinalize(loadConf, pRuleset)) != RS_RET_OK) {
		rulesetDestruct(&pRuleset);
		ABORT_FINALIZE(localRet);
	}
	addScript(pRuleset, o->script);

	/* we have only two params, so we do NOT do the usual param loop */
	parserIdx = cnfparamGetIdx(&rspblk, "parser");
	if(parserIdx != -1  && pvals[parserIdx].bUsed) {
		ar = pvals[parserIdx].val.d.ar;
		for(i = 0 ; i <  ar->nmemb ; ++i) {
			parserName = (uchar*)es_str2cstr(ar->arr[i], NULL);
			doRulesetAddParser(pRuleset, parserName);
			/* note parserName is freed in doRulesetAddParser()! */
		}
	}

	/* pick up ruleset queue parameters */
	if(queueCnfParamsSet(o->nvlst)) {
		if(pRuleset->pszName == NULL) {
			rsname = (uchar*) "[ruleset]";
			qtype = pRuleset->pQueue->qType;
		} else {
			rsname = pRuleset->pszName;
			qtype = 3;
		}
		DBGPRINTF("adding a ruleset-specific \"main\" queue for ruleset '%s', mode %d\n",
			rsname, qtype);
		CHKiRet(createMainQueue(&pRuleset->pQueue, rsname, o->nvlst));
	}

finalize_it:
	free(rsName);
	cnfparamvalsDestruct(pvals, &rspblk);
	RETiRet;
}


/* queryInterface function
 * rgerhards, 2008-02-21
 */
BEGINobjQueryInterface(ruleset)
CODESTARTobjQueryInterface(ruleset)
	if(pIf->ifVersion != rulesetCURR_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 = rulesetConstruct;
	pIf->ConstructFinalize = rulesetConstructFinalize;
	pIf->Destruct = rulesetDestruct;
	pIf->DebugPrint = rulesetDebugPrint;

	pIf->IterateAllActions = iterateAllActions;
	pIf->DestructAllActions = destructAllActions;
	pIf->AddScript = addScript;
	pIf->ProcessBatch = processBatch;
	pIf->SetName = rulesetSetName;
	pIf->DebugPrintAll = debugPrintAll;
	pIf->GetCurrent = GetCurrent;
	pIf->GetRuleset = rulesetGetRuleset;
	pIf->SetDefaultRuleset = SetDefaultRuleset;
	pIf->SetCurrRuleset = SetCurrRuleset;
	pIf->GetRulesetQueue = GetRulesetQueue;
	pIf->GetParserList = GetParserList;
finalize_it:
ENDobjQueryInterface(ruleset)


/* Exit the ruleset class.
 * rgerhards, 2009-04-06
 */
BEGINObjClassExit(ruleset, OBJ_IS_CORE_MODULE) /* class, version */
	objRelease(parser, CORE_COMPONENT);
ENDObjClassExit(ruleset)


/* Initialize the ruleset class. Must be called as the very first method
 * before anything else is called inside this class.
 * rgerhards, 2008-02-19
 */
BEGINObjClassInit(ruleset, 1, OBJ_IS_CORE_MODULE) /* class, version */
	/* request objects we use */

	/* set our own handlers */
	OBJSetMethodHandler(objMethod_DEBUGPRINT, rulesetDebugPrint);
	OBJSetMethodHandler(objMethod_CONSTRUCTION_FINALIZER, rulesetConstructFinalize);

	/* config file handlers */
	CHKiRet(regCfSysLineHdlr((uchar *)"rulesetparser", 0, eCmdHdlrGetWord, rulesetAddParser, NULL, NULL));
	CHKiRet(regCfSysLineHdlr((uchar *)"rulesetcreatemainqueue", 0, eCmdHdlrBinary, rulesetCreateQueue,
		NULL, NULL));
ENDObjClassInit(ruleset)
