diff options
Diffstat (limited to 'runtime/lookup.c')
-rw-r--r-- | runtime/lookup.c | 1093 |
1 files changed, 1093 insertions, 0 deletions
diff --git a/runtime/lookup.c b/runtime/lookup.c new file mode 100644 index 0000000..4408581 --- /dev/null +++ b/runtime/lookup.c @@ -0,0 +1,1093 @@ +/* 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; +} |