/* lookup.c
 * Support for lookup tables in RainerScript.
 *
 * Copyright 2013-2023 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 <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <json.h>
#include <assert.h>

#include "rsyslog.h"
#include "srUtils.h"
#include "errmsg.h"
#include "lookup.h"
#include "msg.h"
#include "rsconf.h"
#include "dirty.h"
#include "unicode-helper.h"

PRAGMA_IGNORE_Wdeprecated_declarations
/* definitions for objects we access */
DEFobjStaticHelpers
DEFobjCurrIf(glbl)

/* forward definitions */
static rsRetVal lookupReadFile(lookup_t *pThis, const uchar* name, const uchar* filename);
static void lookupDestruct(lookup_t *pThis);

/* static data */
/* tables for interfacing with the v6 config system (as far as we need to) */
static struct cnfparamdescr modpdescr[] = {
	{ "name", eCmdHdlrString, CNFPARAM_REQUIRED },
	{ "file", eCmdHdlrString, CNFPARAM_REQUIRED },
	{ "reloadOnHUP", eCmdHdlrBinary, 0 }
};
static struct cnfparamblk modpblk =
	{ CNFPARAMBLK_VERSION,
	  sizeof(modpdescr)/sizeof(struct cnfparamdescr),
	  modpdescr
	};

/* internal data-types */
typedef struct uint32_index_val_s {
	uint32_t index;
	uchar *val;
} uint32_index_val_t;

const char * reloader_prefix = "lkp_tbl_reloader:";

static void *
lookupTableReloader(void *self);

static void
lookupStopReloader(lookup_ref_t *pThis);


/* create a new lookup table object AND include it in our list of
 * lookup tables.
 */
static rsRetVal
lookupNew(lookup_ref_t **ppThis)
{
	lookup_ref_t *pThis = NULL;
	lookup_t *t = NULL;
	DEFiRet;

	CHKmalloc(pThis = calloc(1, sizeof(lookup_ref_t)));
	CHKmalloc(t = calloc(1, sizeof(lookup_t)));
	pThis->do_reload = pThis->do_stop = 0;
	pThis->reload_on_hup = 1; /*DO reload on HUP (default)*/

	pThis->next = NULL;
	if(loadConf->lu_tabs.root == NULL) {
		loadConf->lu_tabs.root = pThis;
	} else {
		loadConf->lu_tabs.last->next = pThis;
	}
	loadConf->lu_tabs.last = pThis;

	pThis->self = t;

	*ppThis = pThis;
finalize_it:
	if(iRet != RS_RET_OK) {
		LogError(errno, iRet, "a lookup table could not be initialized");
		free(t);
		free(pThis);
	}
	RETiRet;
}

/* activate a lookup table entry once rsyslog is ready to do so */
static rsRetVal
lookupActivateTable(lookup_ref_t *pThis)
{
	DEFiRet;
	int initialized = 0;

	DBGPRINTF("lookupActivateTable called\n");
	CHKiConcCtrl(pthread_rwlock_init(&pThis->rwlock, NULL));
	initialized++; /*1*/
	CHKiConcCtrl(pthread_mutex_init(&pThis->reloader_mut, NULL));
	initialized++; /*2*/
	CHKiConcCtrl(pthread_cond_init(&pThis->run_reloader, NULL));
	initialized++; /*3*/
	CHKiConcCtrl(pthread_attr_init(&pThis->reloader_thd_attr));
	initialized++; /*4*/
	pThis->do_reload = pThis->do_stop = 0;
	CHKiConcCtrl(pthread_create(&pThis->reloader, &pThis->reloader_thd_attr,
		lookupTableReloader, pThis));
	initialized++; /*5*/


finalize_it:
	if(iRet != RS_RET_OK) {
		LogError(errno, iRet, "a lookup table could not be activated: "
			"failed at init-step %d (please enable debug logs for details)",
			initialized);
		/* Can not happen with current code, but might occur in the future when
		 * an error-condition as added after step 5. If we leave it in, Coverity
		 * scan complains. So we comment it out but do not remove the code.
		 * Triggered by CID 185426
		if (initialized > 4) lookupStopReloader(pThis);
		*/
		if (initialized > 3) pthread_attr_destroy(&pThis->reloader_thd_attr);
		if (initialized > 2) pthread_cond_destroy(&pThis->run_reloader);
		if (initialized > 1) pthread_mutex_destroy(&pThis->reloader_mut);
		if (initialized > 0) pthread_rwlock_destroy(&pThis->rwlock);
	}
	RETiRet;
}

/*must be called with reloader_mut acquired*/
static void ATTR_NONNULL()
freeStubValueForReloadFailure(lookup_ref_t *const pThis)
{
	if (pThis->stub_value_for_reload_failure != NULL) {
		free(pThis->stub_value_for_reload_failure);
		pThis->stub_value_for_reload_failure = NULL;
	}
}

static void
lookupStopReloader(lookup_ref_t *pThis) {
	pthread_mutex_lock(&pThis->reloader_mut);
	freeStubValueForReloadFailure(pThis);
	pThis->do_reload = 0;
	pThis->do_stop = 1;
	pthread_cond_signal(&pThis->run_reloader);
	pthread_mutex_unlock(&pThis->reloader_mut);
	pthread_join(pThis->reloader, NULL);
}

static void
lookupRefDestruct(lookup_ref_t *pThis)
{
	lookupStopReloader(pThis);
	pthread_mutex_destroy(&pThis->reloader_mut);
	pthread_cond_destroy(&pThis->run_reloader);
	pthread_attr_destroy(&pThis->reloader_thd_attr);

	pthread_rwlock_destroy(&pThis->rwlock);
	lookupDestruct(pThis->self);
	free(pThis->name);
	free(pThis->filename);
	free(pThis);
}

static void
destructTable_str(lookup_t *pThis) {
	uint32_t i = 0;
	lookup_string_tab_entry_t *entries = pThis->table.str->entries;
	for (i = 0; i < pThis->nmemb; i++) {
		free(entries[i].key);
	}
	free(entries);
	free(pThis->table.str);
}


static void
destructTable_arr(lookup_t *pThis) {
	free(pThis->table.arr->interned_val_refs);
	free(pThis->table.arr);
}

static void
destructTable_sparseArr(lookup_t *pThis) {
	free(pThis->table.sprsArr->entries);
	free(pThis->table.sprsArr);
}

static void
lookupDestruct(lookup_t *pThis) {
	uint32_t i;

	if (pThis == NULL) return;

	if (pThis->type == STRING_LOOKUP_TABLE) {
		destructTable_str(pThis);
	} else if (pThis->type == ARRAY_LOOKUP_TABLE) {
		destructTable_arr(pThis);
	} else if (pThis->type == SPARSE_ARRAY_LOOKUP_TABLE) {
		destructTable_sparseArr(pThis);
	} else if (pThis->type == STUBBED_LOOKUP_TABLE) {
		/*nothing to be done*/
	}

	for (i = 0; i < pThis->interned_val_count; i++) {
		free(pThis->interned_vals[i]);
	}
	free(pThis->interned_vals);
	free(pThis->nomatch);
	free(pThis);
}

void
lookupInitCnf(lookup_tables_t *lu_tabs)
{
	lu_tabs->root = NULL;
	lu_tabs->last = NULL;
}

void
lookupDestroyCnf(void)
{
	lookup_ref_t *luref, *luref_next;
	for(luref = runConf->lu_tabs.root ; luref != NULL ; ) {
		luref_next = luref->next;
		lookupRefDestruct(luref);
		luref = luref_next;
	}
}

/* comparison function for qsort() */
static int
qs_arrcmp_strtab(const void *s1, const void *s2)
{
	return ustrcmp(((lookup_string_tab_entry_t*)s1)->key, ((lookup_string_tab_entry_t*)s2)->key);
}

static int
qs_arrcmp_ustrs(const void *s1, const void *s2)
{
	return ustrcmp(*(uchar**)s1, *(uchar**)s2);
}

static int
qs_arrcmp_uint32_index_val(const void *s1, const void *s2)
{
	uint32_t first_value = ((uint32_index_val_t*)s1)->index;
	uint32_t second_value = ((uint32_index_val_t*)s2)->index;
	if (first_value < second_value) {
		return -1;
	}
	return first_value - second_value;
}

static int
qs_arrcmp_sprsArrtab(const void *s1, const void *s2)
{
	uint32_t first_value = ((lookup_sparseArray_tab_entry_t*)s1)->key;
	uint32_t second_value = ((lookup_sparseArray_tab_entry_t*)s2)->key;
	if (first_value < second_value) {
		return -1;
	}
	return first_value - second_value;
}

/* comparison function for bsearch() and string array compare
 * this is for the string lookup table type
 */
static int
bs_arrcmp_strtab(const void *s1, const void *s2)
{
	return strcmp((char*)s1, (char*)((lookup_string_tab_entry_t*)s2)->key);
}

static int
bs_arrcmp_str(const void *s1, const void *s2)
{
	return ustrcmp((uchar*)s1, *(uchar**)s2);
}

static int
bs_arrcmp_sprsArrtab(const void *s1, const void *s2)
{
	uint32_t key = *(uint32_t*)s1;
	uint32_t array_member_value = ((lookup_sparseArray_tab_entry_t*)s2)->key;
	if (key < array_member_value) {
		return -1;
	}
	return key - array_member_value;
}

static inline const char*
defaultVal(lookup_t *pThis) {
	return (pThis->nomatch == NULL) ? "" : (const char*) pThis->nomatch;
}

/* lookup_fn for different types of tables */
static es_str_t*
lookupKey_stub(lookup_t *pThis, lookup_key_t __attribute__((unused)) key) {
	return es_newStrFromCStr((char*) pThis->nomatch, ustrlen(pThis->nomatch));
}

static es_str_t*
lookupKey_str(lookup_t *pThis, lookup_key_t key) {
	lookup_string_tab_entry_t *entry;
	const char *r;
	if(pThis->nmemb == 0) {
		entry = NULL;
	} else {
		assert(pThis->table.str->entries);
		entry = bsearch(key.k_str, pThis->table.str->entries, pThis->nmemb,
			sizeof(lookup_string_tab_entry_t), bs_arrcmp_strtab);
	}
	if(entry == NULL) {
		r = defaultVal(pThis);
	} else {
		r = (const char*)entry->interned_val_ref;
	}
	return es_newStrFromCStr(r, strlen(r));
}

static es_str_t*
lookupKey_arr(lookup_t *pThis, lookup_key_t key) {
	const char *r;
	uint32_t uint_key = key.k_uint;
	if ((pThis->nmemb == 0) || (uint_key < pThis->table.arr->first_key)) {
		r = defaultVal(pThis);
	} else {
		uint32_t idx = uint_key - pThis->table.arr->first_key;
		if (idx >= pThis->nmemb) {
			r = defaultVal(pThis);
		} else {
		    r = (char*) pThis->table.arr->interned_val_refs[idx];
		}
	}

	return es_newStrFromCStr(r, strlen(r));
}

typedef int (comp_fn_t)(const void *s1, const void *s2);

static void *
bsearch_lte(const void *key, const void *base, size_t nmemb, size_t size, comp_fn_t *comp_fn)
{
	size_t l, u, idx;
	const void *p;
	int comparison;

	l = 0;
	u = nmemb;
	if (l == u) {
		return NULL;
	}
	while (l < u) {
		idx = (l + u) / 2;
		p = (void *) (((const char *) base) + (idx * size));
		comparison = (*comp_fn)(key, p);
		if (comparison < 0)
			u = idx;
		else if (comparison > 0)
			l = idx + 1;
		else
			return (void *) p;
	}
	if (comparison < 0) {
		if (idx == 0) {
			return NULL;
		}
		idx--;
	}
	return (void *) (((const char *) base) + ( idx * size));
}

static es_str_t*
lookupKey_sprsArr(lookup_t *pThis, lookup_key_t key) {
	lookup_sparseArray_tab_entry_t *entry;
	const char *r;
	if (pThis->nmemb == 0) {
		entry = NULL;
	} else {
		entry = bsearch_lte(&key.k_uint, pThis->table.sprsArr->entries, pThis->nmemb,
			sizeof(lookup_sparseArray_tab_entry_t), bs_arrcmp_sprsArrtab);
	}

	if(entry == NULL) {
		r = defaultVal(pThis);
	} else {
		r = (const char*)entry->interned_val_ref;
	}
	return es_newStrFromCStr(r, strlen(r));
}

/* builders for different table-types */

#define NO_INDEX_ERROR(type, name)				\
	LogError(0, RS_RET_INVALID_VALUE, "'%s' lookup table named: '%s' has record(s) without 'index' "\
"field", type, name); \
	ABORT_FINALIZE(RS_RET_INVALID_VALUE);

static rsRetVal
build_StringTable(lookup_t *pThis, struct json_object *jtab, const uchar* name) {
	uint32_t i;
	struct json_object *jrow, *jindex, *jvalue;
	uchar *value, *canonicalValueRef;
	DEFiRet;

	pThis->table.str = NULL;
	CHKmalloc(pThis->table.str = calloc(1, sizeof(lookup_string_tab_t)));
	if (pThis->nmemb > 0) {
		CHKmalloc(pThis->table.str->entries = calloc(pThis->nmemb, sizeof(lookup_string_tab_entry_t)));

		for(i = 0; i < pThis->nmemb; i++) {
			jrow = json_object_array_get_idx(jtab, i);
			jindex = json_object_object_get(jrow, "index");
			jvalue = json_object_object_get(jrow, "value");
			if (jindex == NULL || json_object_is_type(jindex, json_type_null)) {
				NO_INDEX_ERROR("string", name);
			}
			CHKmalloc(pThis->table.str->entries[i].key = ustrdup((uchar*) json_object_get_string(jindex)));
			value = (uchar*) json_object_get_string(jvalue);
			uchar **found  = (uchar**) bsearch(value, pThis->interned_vals,
				pThis->interned_val_count, sizeof(uchar*), bs_arrcmp_str);
			if(found == NULL) {
				LogError(0, RS_RET_INTERNAL_ERROR, "lookup.c:build_StringTable(): "
					"internal error, bsearch returned NULL for '%s'", value);
				ABORT_FINALIZE(RS_RET_INTERNAL_ERROR);
			}
			// I give up, I see no way to remove false positive -- rgerhards, 2017-10-24
			#ifndef __clang_analyzer__
			canonicalValueRef = *found;
			if(canonicalValueRef == NULL) {
				LogError(0, RS_RET_INTERNAL_ERROR, "lookup.c:build_StringTable(): "
					"internal error, canonicalValueRef returned from bsearch "
					"is NULL for '%s'", value);
				ABORT_FINALIZE(RS_RET_INTERNAL_ERROR);
			}
			pThis->table.str->entries[i].interned_val_ref = canonicalValueRef;
			#endif
		}
		qsort(pThis->table.str->entries, pThis->nmemb, sizeof(lookup_string_tab_entry_t), qs_arrcmp_strtab);
	}

	pThis->lookup = lookupKey_str;
	pThis->key_type = LOOKUP_KEY_TYPE_STRING;
finalize_it:
	RETiRet;
}

static rsRetVal
build_ArrayTable(lookup_t *pThis, struct json_object *jtab, const uchar *name) {
	uint32_t i;
	struct json_object *jrow, *jindex, *jvalue;
	uchar *canonicalValueRef;
	uint32_t prev_index, _index;
	uint8_t prev_index_set;
	uint32_index_val_t *indexes = NULL;
	DEFiRet;

	prev_index_set = 0;

	pThis->table.arr = NULL;
	CHKmalloc(pThis->table.arr = calloc(1, sizeof(lookup_array_tab_t)));
	if (pThis->nmemb > 0) {
		CHKmalloc(indexes = calloc(pThis->nmemb, sizeof(uint32_index_val_t)));
		CHKmalloc(pThis->table.arr->interned_val_refs = calloc(pThis->nmemb, sizeof(uchar*)));

		for(i = 0; i < pThis->nmemb; i++) {
			jrow = json_object_array_get_idx(jtab, i);
			jindex = json_object_object_get(jrow, "index");
			jvalue = json_object_object_get(jrow, "value");
			if (jindex == NULL || json_object_is_type(jindex, json_type_null)) {
				NO_INDEX_ERROR("array", name);
			}
			indexes[i].index = (uint32_t) json_object_get_int(jindex);
			indexes[i].val = (uchar*) json_object_get_string(jvalue);
		}
		qsort(indexes, pThis->nmemb, sizeof(uint32_index_val_t), qs_arrcmp_uint32_index_val);
		for(i = 0; i < pThis->nmemb; i++) {
			_index = indexes[i].index;
			if (prev_index_set == 0) {
				prev_index = _index;
				prev_index_set = 1;
				pThis->table.arr->first_key = _index;
			} else {
				if (_index != ++prev_index) {
					LogError(0, RS_RET_INVALID_VALUE, "'array' lookup table name: '%s' "
					"has non-contiguous members between index '%d' and '%d'",
									name, prev_index, _index);
					ABORT_FINALIZE(RS_RET_INVALID_VALUE);
				}
			}
			uchar *const *const canonicalValueRef_ptr = bsearch(indexes[i].val, pThis->interned_vals,
				pThis->interned_val_count, sizeof(uchar*), bs_arrcmp_str);
			if(canonicalValueRef_ptr == NULL) {
				LogError(0, RS_RET_ERR, "BUG: canonicalValueRef not found in "
					"build_ArrayTable(), %s:%d", __FILE__, __LINE__);
				ABORT_FINALIZE(RS_RET_ERR);
			}
			canonicalValueRef = *canonicalValueRef_ptr;
			assert(canonicalValueRef != NULL);
			pThis->table.arr->interned_val_refs[i] = canonicalValueRef;
		}
	}

	pThis->lookup = lookupKey_arr;
	pThis->key_type = LOOKUP_KEY_TYPE_UINT;

finalize_it:
	free(indexes);
	RETiRet;
}

static rsRetVal
build_SparseArrayTable(lookup_t *pThis, struct json_object *jtab, const uchar* name) {
	uint32_t i;
	struct json_object *jrow, *jindex, *jvalue;
	uchar *value, *canonicalValueRef;
	DEFiRet;

	pThis->table.str = NULL;
	CHKmalloc(pThis->table.sprsArr = calloc(1, sizeof(lookup_sparseArray_tab_t)));
	if (pThis->nmemb > 0) {
		CHKmalloc(pThis->table.sprsArr->entries = calloc(pThis->nmemb, sizeof(lookup_sparseArray_tab_entry_t)));

		for(i = 0; i < pThis->nmemb; i++) {
			jrow = json_object_array_get_idx(jtab, i);
			jindex = json_object_object_get(jrow, "index");
			jvalue = json_object_object_get(jrow, "value");
			if (jindex == NULL || json_object_is_type(jindex, json_type_null)) {
				NO_INDEX_ERROR("sparseArray", name);
			}
			pThis->table.sprsArr->entries[i].key = (uint32_t) json_object_get_int(jindex);
			value = (uchar*) json_object_get_string(jvalue);
			uchar *const *const canonicalValueRef_ptr = bsearch(value, pThis->interned_vals,
				pThis->interned_val_count, sizeof(uchar*), bs_arrcmp_str);
			if(canonicalValueRef_ptr == NULL) {
				LogError(0, RS_RET_ERR, "BUG: canonicalValueRef not found in "
					"build_SparseArrayTable(), %s:%d", __FILE__, __LINE__);
				ABORT_FINALIZE(RS_RET_ERR);
			}
			canonicalValueRef = *canonicalValueRef_ptr;
			assert(canonicalValueRef != NULL);
			pThis->table.sprsArr->entries[i].interned_val_ref = canonicalValueRef;
		}
		qsort(pThis->table.sprsArr->entries, pThis->nmemb, sizeof(lookup_sparseArray_tab_entry_t),
				qs_arrcmp_sprsArrtab);
	}

	pThis->lookup = lookupKey_sprsArr;
	pThis->key_type = LOOKUP_KEY_TYPE_UINT;

finalize_it:
	RETiRet;
}

static rsRetVal
lookupBuildStubbedTable(lookup_t *pThis, const uchar* stub_val) {
	DEFiRet;

	CHKmalloc(pThis->nomatch = ustrdup(stub_val));
	pThis->lookup = lookupKey_stub;
	pThis->type = STUBBED_LOOKUP_TABLE;
	pThis->key_type = LOOKUP_KEY_TYPE_NONE;

finalize_it:
	RETiRet;
}

static rsRetVal
lookupBuildTable_v1(lookup_t *pThis, struct json_object *jroot, const uchar* name) {
	struct json_object *jnomatch, *jtype, *jtab;
	struct json_object *jrow, *jvalue;
	const char *table_type, *nomatch_value;
	const uchar **all_values;
	const uchar *curr, *prev;
	uint32_t i, j;
	uint32_t uniq_values;

	DEFiRet;
	all_values = NULL;

	jnomatch = json_object_object_get(jroot, "nomatch");
	jtype = json_object_object_get(jroot, "type");
	jtab = json_object_object_get(jroot, "table");
	if (jtab == NULL || !json_object_is_type(jtab, json_type_array)) {
		LogError(0, RS_RET_INVALID_VALUE, "lookup table named: '%s' has invalid table definition", name);
		ABORT_FINALIZE(RS_RET_INVALID_VALUE);
	}
	pThis->nmemb = json_object_array_length(jtab);
	table_type = json_object_get_string(jtype);
	if (table_type == NULL) {
		table_type = "string";
	}

	CHKmalloc(all_values = malloc(pThis->nmemb * sizeof(uchar*)));

	/* before actual table can be loaded, prepare all-value list and remove duplicates*/
	for(i = 0; i < pThis->nmemb; i++) {
		jrow = json_object_array_get_idx(jtab, i);
		jvalue = json_object_object_get(jrow, "value");
		if (jvalue == NULL || json_object_is_type(jvalue, json_type_null)) {
			LogError(0, RS_RET_INVALID_VALUE, "'%s' lookup table named: '%s' has record(s) "
			"without 'value' field", table_type, name);
			ABORT_FINALIZE(RS_RET_INVALID_VALUE);
		}
		all_values[i] = (const uchar*) json_object_get_string(jvalue);
	}
	qsort(all_values, pThis->nmemb, sizeof(uchar*), qs_arrcmp_ustrs);
	uniq_values = 1;
	for(i = 1; i < pThis->nmemb; i++) {
		curr = all_values[i];
		prev = all_values[i - 1];
		if (ustrcmp(prev, curr) != 0) {
			uniq_values++;
		}
	}

	if (pThis->nmemb > 0)  {
		CHKmalloc(pThis->interned_vals = malloc(uniq_values * sizeof(uchar*)));
		j = 0;
		CHKmalloc(pThis->interned_vals[j++] = ustrdup(all_values[0]));
		for(i = 1; i < pThis->nmemb ; ++i) {
			curr = all_values[i];
			prev = all_values[i - 1];
			if (ustrcmp(prev, curr) != 0) {
				CHKmalloc(pThis->interned_vals[j++] = ustrdup(all_values[i]));
			}
		}
		pThis->interned_val_count = uniq_values;
	}
	/* uniq values captured (sorted) */

	nomatch_value = json_object_get_string(jnomatch);
	if (nomatch_value != NULL) {
		CHKmalloc(pThis->nomatch = (uchar*) strdup(nomatch_value));
	}

	if (strcmp(table_type, "array") == 0) {
		pThis->type = ARRAY_LOOKUP_TABLE;
		CHKiRet(build_ArrayTable(pThis, jtab, name));
	} else if (strcmp(table_type, "sparseArray") == 0) {
		pThis->type = SPARSE_ARRAY_LOOKUP_TABLE;
		CHKiRet(build_SparseArrayTable(pThis, jtab, name));
	} else if (strcmp(table_type, "string") == 0) {
		pThis->type = STRING_LOOKUP_TABLE;
		CHKiRet(build_StringTable(pThis, jtab, name));
	} else {
		LogError(0, RS_RET_INVALID_VALUE, "lookup table named: '%s' uses unupported "
				"type: '%s'", name, table_type);
		ABORT_FINALIZE(RS_RET_INVALID_VALUE);
	}
finalize_it:
	if (all_values != NULL) free(all_values);
	RETiRet;
}

static rsRetVal
lookupBuildTable(lookup_t *pThis, struct json_object *jroot, const uchar* name)
{
	struct json_object *jversion;
	int version = 1;

	DEFiRet;

	jversion = json_object_object_get(jroot, "version");
	if (jversion != NULL && !json_object_is_type(jversion, json_type_null)) {
		version = json_object_get_int(jversion);
	} else {
		LogError(0, RS_RET_INVALID_VALUE, "lookup table named: '%s' doesn't specify version "
		"(will use default value: %d)", name, version);
	}
	if (version == 1) {
		CHKiRet(lookupBuildTable_v1(pThis, jroot, name));
	} else {
		LogError(0, RS_RET_INVALID_VALUE, "lookup table named: '%s' uses unsupported "
				"version: %d", name, version);
		ABORT_FINALIZE(RS_RET_INVALID_VALUE);
	}

finalize_it:
	RETiRet;
}


/* find a lookup table. This is a naive O(n) algo, but this really
 * doesn't matter as it is called only a few times during config
 * load. The function returns either a pointer to the requested
 * table or NULL, if not found.
 */
lookup_ref_t * ATTR_NONNULL()
lookupFindTable(uchar *name)
{
	lookup_ref_t *curr;

	for(curr = loadConf->lu_tabs.root ; curr != NULL ; curr = curr->next) {
		if(!ustrcmp(curr->name, name))
			break;
	}
	return curr;
}


/* this reloads a lookup table. This is done while the engine is running,
 * as such the function must ensure proper locking and proper order of
 * operations (so that nothing can interfere). If the table cannot be loaded,
 * the old table is continued to be used.
 */
static rsRetVal
lookupReloadOrStub(lookup_ref_t *pThis, const uchar* stub_val) {
	lookup_t *newlu, *oldlu; /* dummy to be able to use support functions without
								affecting current settings. */
	DEFiRet;

	oldlu = pThis->self;
	newlu = NULL;

	DBGPRINTF("reload requested for lookup table '%s'\n", pThis->name);
	CHKmalloc(newlu = calloc(1, sizeof(lookup_t)));
	if (stub_val == NULL) {
		CHKiRet(lookupReadFile(newlu, pThis->name, pThis->filename));
	} else {
		CHKiRet(lookupBuildStubbedTable(newlu, stub_val));
	}
	/* all went well, copy over data members */
	pthread_rwlock_wrlock(&pThis->rwlock);
	pThis->self = newlu;
	pthread_rwlock_unlock(&pThis->rwlock);
finalize_it:
	if (iRet != RS_RET_OK) {
		if (stub_val == NULL) {
			LogError(0, RS_RET_INTERNAL_ERROR,
					"lookup table '%s' could not be reloaded from file '%s'",
					pThis->name, pThis->filename);
		} else {
			LogError(0, RS_RET_INTERNAL_ERROR,
					"lookup table '%s' could not be stubbed with value '%s'",
					pThis->name, stub_val);
		}
		lookupDestruct(newlu);
	} else {
		if (stub_val == NULL) {
			LogMsg(0, RS_RET_OK, LOG_INFO, "lookup table '%s' reloaded from file '%s'",
					pThis->name, pThis->filename);
		} else {
			LogError(0, RS_RET_OK, "lookup table '%s' stubbed with value '%s'",
					pThis->name, stub_val);
		}
		lookupDestruct(oldlu);
	}
	RETiRet;
}

static rsRetVal
lookupDoStub(lookup_ref_t *pThis, const uchar* stub_val)
{
	int already_stubbed = 0;
	DEFiRet;
	pthread_rwlock_rdlock(&pThis->rwlock);
	if (pThis->self->type == STUBBED_LOOKUP_TABLE &&
		ustrcmp(pThis->self->nomatch, stub_val) == 0)
		already_stubbed = 1;
	pthread_rwlock_unlock(&pThis->rwlock);
	if (! already_stubbed) {
		LogError(0, RS_RET_OK, "stubbing lookup table '%s' with value '%s'",
			pThis->name, stub_val);
		CHKiRet(lookupReloadOrStub(pThis, stub_val));
	} else {
		LogError(0, RS_RET_OK, "lookup table '%s' is already stubbed with value '%s'",
			pThis->name, stub_val);
	}
finalize_it:
	RETiRet;
}

static uint8_t
lookupIsReloadPending(lookup_ref_t *pThis) {
	uint8_t reload_pending;
	pthread_mutex_lock(&pThis->reloader_mut);
	reload_pending = pThis->do_reload;
	pthread_mutex_unlock(&pThis->reloader_mut);
	return reload_pending;
}

/* note: stub_val_if_reload_fails may or may not be NULL */
rsRetVal ATTR_NONNULL(1)
lookupReload(lookup_ref_t *const pThis, const uchar *const stub_val_if_reload_fails)
{
	uint8_t locked = 0;
	int lock_errno = 0;
	DEFiRet;
	assert(pThis != NULL);
	if ((lock_errno = pthread_mutex_trylock(&pThis->reloader_mut)) == 0) {
		locked = 1;
		/*so it doesn't leak memory in situation where 2 reload requests are issued back to back*/
		freeStubValueForReloadFailure(pThis);
		if (stub_val_if_reload_fails != NULL) {
			CHKmalloc(pThis->stub_value_for_reload_failure = ustrdup(stub_val_if_reload_fails));
		}
		pThis->do_reload = 1;
		pthread_cond_signal(&pThis->run_reloader);
	} else {
		LogError(lock_errno, RS_RET_INTERNAL_ERROR, "attempt to trigger "
			"reload of lookup table '%s' failed (not stubbing)", pThis->name);
		ABORT_FINALIZE(RS_RET_INTERNAL_ERROR);
		/* we can choose to stub the table here, but it'll hurt because
		   the table reloader may take time to complete the reload
		   and stubbing because of a concurrent reload message may
		   not be desirable (except in very tightly controled environments
		   where reload-triggering messages pushed are timed accurately
		   and an idempotency-filter is used to reject re-deliveries) */
	}
finalize_it:
	if (locked) {
		pthread_mutex_unlock(&pThis->reloader_mut);
	}
	RETiRet;
}

static rsRetVal ATTR_NONNULL()
lookupDoReload(lookup_ref_t *pThis)
{
	DEFiRet;
	iRet = lookupReloadOrStub(pThis, NULL);
	if ((iRet != RS_RET_OK) &&
		(pThis->stub_value_for_reload_failure != NULL)) {
		iRet = lookupDoStub(pThis, pThis->stub_value_for_reload_failure);
	}
	freeStubValueForReloadFailure(pThis);
	RETiRet;
}

void *
lookupTableReloader(void *self)
{
	lookup_ref_t *pThis = (lookup_ref_t*) self;
	pthread_mutex_lock(&pThis->reloader_mut);
	while(1) {
		if (pThis->do_stop) {
			break;
		} else if (pThis->do_reload) {
			lookupDoReload(pThis);
			pThis->do_reload = 0;
		} else {
			pthread_cond_wait(&pThis->run_reloader, &pThis->reloader_mut);
		}
	}
	pthread_mutex_unlock(&pThis->reloader_mut);
	return NULL;
}

/* reload all lookup tables on HUP */
void
lookupDoHUP(void)
{
	lookup_ref_t *luref;
	for(luref = runConf->lu_tabs.root ; luref != NULL ; luref = luref->next) {
		if (luref->reload_on_hup) {
			lookupReload(luref, NULL);
		}
	}
}

/* activate lookup table system config
 * most importantly, this means tarting the lookup table reloader thread in the
 * right process space - it is a difference if we fork or not!
 */
void
lookupActivateConf(void)
{
	DBGPRINTF("lookup tables: activate config \n");
	lookup_ref_t *luref;
	for(luref = runConf->lu_tabs.root ; luref != NULL ; luref = luref->next) {
		DBGPRINTF("lookup actiate: processing %p\n", luref);
		lookupActivateTable(luref);
	}
	DBGPRINTF("lookup tables: activate done\n");
}

uint
lookupPendingReloadCount(void)
{
	uint pending_reload_count = 0;
	lookup_ref_t *luref;
	for(luref = runConf->lu_tabs.root ; luref != NULL ; luref = luref->next) {
		if (lookupIsReloadPending(luref)) {
			pending_reload_count++;
		}
	}
	return pending_reload_count;
}


/* returns either a pointer to the value (read only!) or NULL
 * if either the key could not be found or an error occurred.
 * Note that an estr_t object is returned. The caller is
 * responsible for freeing it.
 */
es_str_t *
lookupKey(lookup_ref_t *pThis, lookup_key_t key)
{
	es_str_t *estr;
	lookup_t *t;
	pthread_rwlock_rdlock(&pThis->rwlock);
	t = pThis->self;
	estr = t->lookup(t, key);
	pthread_rwlock_unlock(&pThis->rwlock);
	return estr;
}


/* note: widely-deployed json_c 0.9 does NOT support incremental
 * parsing. In order to keep compatible with e.g. Ubuntu 12.04LTS,
 * we read the file into one big memory buffer and parse it at once.
 * While this is not very elegant, it will not pose any real issue
 * for "reasonable" lookup tables (and "unreasonably" large ones
 * will probably have other issues as well...).
 */
static rsRetVal ATTR_NONNULL()
lookupReadFile(lookup_t *const pThis, const uchar *const name, const uchar *const filename)
{
	struct json_tokener *tokener = NULL;
	struct json_object *json = NULL;
	char *iobuf = NULL;
	int fd = -1;
	ssize_t nread;
	struct stat sb;
	DEFiRet;


	if((fd = open((const char*) filename, O_RDONLY)) == -1) {
		LogError(errno, RS_RET_FILE_NOT_FOUND,
			"lookup table file '%s' could not be opened", filename);
		ABORT_FINALIZE(RS_RET_FILE_NOT_FOUND);
	}

	if(fstat(fd, &sb) == -1) {
		LogError(errno, RS_RET_FILE_NOT_FOUND,
			"lookup table file '%s' stat failed", filename);
		ABORT_FINALIZE(RS_RET_FILE_NOT_FOUND);
	}

	CHKmalloc(iobuf = malloc(sb.st_size));

	tokener = json_tokener_new();
	nread = read(fd, iobuf, sb.st_size);
	if(nread != (ssize_t) sb.st_size) {
		LogError(errno, RS_RET_READ_ERR,
			"lookup table file '%s' read error", filename);
		ABORT_FINALIZE(RS_RET_READ_ERR);
	}

	json = json_tokener_parse_ex(tokener, iobuf, sb.st_size);
	if(json == NULL) {
		LogError(0, RS_RET_JSON_PARSE_ERR,
			"lookup table file '%s' json parsing error",
			filename);
		ABORT_FINALIZE(RS_RET_JSON_PARSE_ERR);
	}
	free(iobuf); /* early free to sever resources*/
	iobuf = NULL; /* make sure no double-free */

	/* got json object, now populate our own in-memory structure */
	CHKiRet(lookupBuildTable(pThis, json, name));

finalize_it:
	if (fd != -1) {
		close(fd);
	}
	free(iobuf);
	if(tokener != NULL)
		json_tokener_free(tokener);
	if(json != NULL)
		json_object_put(json);
	RETiRet;
}


rsRetVal
lookupTableDefProcessCnf(struct cnfobj *o)
{
	struct cnfparamvals *pvals;
	lookup_ref_t *lu;
	short i;
#ifdef HAVE_PTHREAD_SETNAME_NP
	char *reloader_thd_name = NULL;
	int thd_name_len = 0;
#endif
	DEFiRet;
	lu = NULL;

	pvals = nvlstGetParams(o->nvlst, &modpblk, NULL);
	if(pvals == NULL) {
		ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS);
	}
	DBGPRINTF("lookupTableDefProcessCnf params:\n");
	cnfparamsPrint(&modpblk, pvals);

	CHKiRet(lookupNew(&lu));

	for(i = 0 ; i < modpblk.nParams ; ++i) {
		if(!pvals[i].bUsed)
			continue;
		if(!strcmp(modpblk.descr[i].name, "file")) {
			CHKmalloc(lu->filename = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL));
		} else if(!strcmp(modpblk.descr[i].name, "name")) {
			CHKmalloc(lu->name = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL));
		} else if(!strcmp(modpblk.descr[i].name, "reloadOnHUP")) {
			lu->reload_on_hup = (pvals[i].val.d.n != 0);
		} else {
			dbgprintf("lookup_table: program error, non-handled "
			  "param '%s'\n", modpblk.descr[i].name);
		}
	}
	const uchar *const lu_name = lu->name; /* we need a const to keep TSAN happy :-( */
	const uchar *const lu_filename = lu->filename; /* we need a const to keep TSAN happy :-( */
	if(lu_name == NULL || lu_filename == NULL) {
		iRet = RS_RET_INTERNAL_ERROR;
		LogError(0, iRet, "internal error: lookup table name not set albeit being mandatory");
		ABORT_FINALIZE(iRet);
	}
#ifdef HAVE_PTHREAD_SETNAME_NP
	thd_name_len = ustrlen(lu_name) + strlen(reloader_prefix) + 1;
	CHKmalloc(reloader_thd_name = malloc(thd_name_len));
	strcpy(reloader_thd_name, reloader_prefix);
	strcpy(reloader_thd_name + strlen(reloader_prefix), (char*) lu_name);
	reloader_thd_name[thd_name_len - 1] = '\0';
#if defined(__NetBSD__)
	pthread_setname_np(lu->reloader, "%s", reloader_thd_name);
#elif defined(__APPLE__)
	pthread_setname_np(reloader_thd_name); // must check
#else
	pthread_setname_np(lu->reloader, reloader_thd_name);
#endif
#endif
	CHKiRet(lookupReadFile(lu->self, lu_name, lu_filename));
	LogMsg(0, RS_RET_OK, LOG_INFO, "lookup table '%s' loaded from file '%s'",
		lu_name, lu->filename);

finalize_it:
#ifdef HAVE_PTHREAD_SETNAME_NP
	free(reloader_thd_name);
#endif
	cnfparamvalsDestruct(pvals, &modpblk);
	if (iRet != RS_RET_OK) {
		if (lu != NULL) {
			lookupDestruct(lu->self);
			lu->self = NULL;
		}
	}
	RETiRet;
}

void
lookupClassExit(void)
{
	objRelease(glbl, CORE_COMPONENT);
}

rsRetVal
lookupClassInit(void)
{
	DEFiRet;
	CHKiRet(objGetObjInterface(&obj));
	CHKiRet(objUse(glbl, CORE_COMPONENT));
finalize_it:
	RETiRet;
}
