diff options
Diffstat (limited to 'runtime/regexp.c')
-rw-r--r-- | runtime/regexp.c | 384 |
1 files changed, 384 insertions, 0 deletions
diff --git a/runtime/regexp.c b/runtime/regexp.c new file mode 100644 index 0000000..433c9c2 --- /dev/null +++ b/runtime/regexp.c @@ -0,0 +1,384 @@ +/* The regexp object. + * + * Module begun 2008-03-05 by Rainer Gerhards, based on some code + * from syslogd.c + * + * Copyright 2008-2012 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 <pthread.h> +#include <regex.h> +#include <string.h> +#include <stdint.h> +#include <assert.h> + +#include "rsyslog.h" +#include "module-template.h" +#include "obj.h" +#include "regexp.h" +#include "errmsg.h" +#include "hashtable.h" +#include "hashtable_itr.h" + +MODULE_TYPE_LIB +MODULE_TYPE_NOKEEP + +/* static data */ +DEFobjStaticHelpers + +/* When using glibc, we enable per-thread regex to avoid lock contention. + * See: + * - https://github.com/rsyslog/rsyslog/issues/2759 + * - https://github.com/rsyslog/rsyslog/pull/2786 + * - https://sourceware.org/bugzilla/show_bug.cgi?id=11159 + * + * This should not affect BSD as they don't seem to take a lock in regexec. + */ +#ifdef __GLIBC__ +#define USE_PERTHREAD_REGEX 1 +#else +#define USE_PERTHREAD_REGEX 0 +#endif + +static pthread_mutex_t mut_regexp; + +// Map a regex_t to its associated uncompiled parameters. +static struct hashtable *regex_to_uncomp = NULL; + +// Map a (regexp_t, pthead_t) to a perthread_regex. +static struct hashtable *perthread_regexs = NULL; + + +/* + * This stores un-compiled regex to allow further + * call to regexec to re-compile a new regex dedicated + * to the calling thread. + */ +typedef struct uncomp_regex { + char *regex; + int cflags; + regex_t *preg; +} uncomp_regex_t; + +/* + * This stores a regex dedicated to a single thread. + */ +typedef struct perthread_regex { + const regex_t *original_preg; + regex_t preg; + int ret; + pthread_mutex_t lock; + pthread_t thread; +} perthread_regex_t; + + +static unsigned __attribute__((nonnull(1))) int hash_from_regex(void *k) { + return (uintptr_t)*(regex_t **)k; +} + +static int key_equals_regex(void *key1, void *key2) { + return *(regex_t **)key1 == *(regex_t **)key2; +} + +static unsigned __attribute__((nonnull(1))) int hash_from_tregex(void *k) { + perthread_regex_t *entry = k; + // Cast to (void*) is ok here because already used in other parts of the code. + uintptr_t thread_id = (uintptr_t)(void *)entry->thread; + + return thread_id ^ (uintptr_t)entry->original_preg; +} + +static int key_equals_tregex(void *key1, void *key2) { + perthread_regex_t *entry1 = key1; + perthread_regex_t *entry2 = key2; + + return (pthread_equal(entry1->thread, entry2->thread) && + entry1->original_preg == entry2->original_preg); +} + + +/* ------------------------------ methods ------------------------------ */ + + +// Create a copy of preg to be used by this thread only. +static perthread_regex_t *create_perthread_regex(const regex_t *preg, uncomp_regex_t *uncomp) { + perthread_regex_t *entry = NULL; + + if (Debug) { + DBGPRINTF("Creating new regex_t for thread %p original regexp_t %p (pattern: %s, cflags: %x)\n", + (void *)pthread_self(), preg, + uncomp->regex, uncomp->cflags); + } + entry = calloc(1, sizeof(*entry)); + if (!entry) + return entry; + entry->original_preg = preg; + DBGPRINTF("regexp: regcomp %p %p\n", entry, &entry->preg); + entry->ret = regcomp(&entry->preg, uncomp->regex, uncomp->cflags); + pthread_mutex_init(&entry->lock, NULL); + entry->thread = pthread_self(); + return entry; +} + +// Get (or create) a regex_t to be used by the current thread. +static perthread_regex_t *get_perthread_regex(const regex_t *preg) { + perthread_regex_t *entry = NULL; + perthread_regex_t key = { .original_preg = preg, .thread = pthread_self() }; + + pthread_mutex_lock(&mut_regexp); + entry = hashtable_search(perthread_regexs, (void *)&key); + if (!entry) { + uncomp_regex_t *uncomp = hashtable_search(regex_to_uncomp, (void *)&preg); + + if (uncomp) { + entry = create_perthread_regex(preg, uncomp); + if(!hashtable_insert(perthread_regexs, (void *)entry, entry)) { + LogError(0, RS_RET_INTERNAL_ERROR, + "error trying to insert thread-regexp into hash-table - things " + "will not work 100%% correctly (mostly probably out of memory issue)"); + } + } + } + if (entry) { + pthread_mutex_lock(&entry->lock); + } + pthread_mutex_unlock(&mut_regexp); + return entry; +} + +static void remove_uncomp_regexp(regex_t *preg) { + uncomp_regex_t *uncomp = NULL; + + pthread_mutex_lock(&mut_regexp); + uncomp = hashtable_remove(regex_to_uncomp, (void *)&preg); + + if (uncomp) { + if (Debug) { + DBGPRINTF("Removing everything linked to regexp_t %p (pattern: %s, cflags: %x)\n", + preg, uncomp->regex, uncomp->cflags); + } + free(uncomp->regex); + free(uncomp); + } + pthread_mutex_unlock(&mut_regexp); +} + +static void _regfree(regex_t *preg) { + int ret = 0; + struct hashtable_itr *itr = NULL; + + if (!preg) + return; + + regfree(preg); + remove_uncomp_regexp(preg); + + pthread_mutex_lock(&mut_regexp); + if (!hashtable_count(perthread_regexs)) { + pthread_mutex_unlock(&mut_regexp); + return; + } + + // This can be long to iterate other all regexps, but regfree doesn't get called + // a lot during processing. + itr = hashtable_iterator(perthread_regexs); + do { + perthread_regex_t *entry = (perthread_regex_t *)hashtable_iterator_value(itr); + + // Do it before freeing the entry. + ret = hashtable_iterator_advance(itr); + + if (entry->original_preg == preg) { + // This allows us to avoid freeing this while somebody is still using it. + pthread_mutex_lock(&entry->lock); + // We can unlock immediately after because mut_regexp is locked. + pthread_mutex_unlock(&entry->lock); + pthread_mutex_destroy(&entry->lock); + regfree(&entry->preg); + + // Do it last because it will free entry. + hashtable_remove(perthread_regexs, (void *)entry); + } + } while (ret); + free(itr); + + pthread_mutex_unlock(&mut_regexp); +} + +static int _regcomp(regex_t *preg, const char *regex, int cflags) { + int ret = 0; + regex_t **ppreg = NULL; + uncomp_regex_t *uncomp; + + // Remove previous data if caller forgot to call regfree(). + remove_uncomp_regexp(preg); + + // Make sure preg itself it correctly initalized. + ret = regcomp(preg, regex, cflags); + if (ret != 0) + return ret; + + uncomp = calloc(1, sizeof(*uncomp)); + if (!uncomp) + return REG_ESPACE; + + uncomp->preg = preg; + uncomp->regex = strdup(regex); + uncomp->cflags = cflags; + pthread_mutex_lock(&mut_regexp); + + // We need to allocate the key because hashtable will free it on remove. + ppreg = malloc(sizeof(regex_t *)); + *ppreg = preg; + ret = hashtable_insert(regex_to_uncomp, (void *)ppreg, uncomp); + pthread_mutex_unlock(&mut_regexp); + if (ret == 0) { + free(uncomp->regex); + free(uncomp); + return REG_ESPACE; + } + + perthread_regex_t *entry = get_perthread_regex(preg); + if (entry) { + ret = entry->ret; + pthread_mutex_unlock(&entry->lock); + } else { + ret = REG_ESPACE; + } + return ret; +} + +static int _regexec(const regex_t *preg, const char *string, size_t nmatch, regmatch_t pmatch[], int eflags) { + perthread_regex_t *entry = get_perthread_regex(preg); + int ret = REG_NOMATCH; + if(entry != NULL) { + ret = regexec(&entry->preg, string, nmatch, pmatch, eflags); + pthread_mutex_unlock(&entry->lock); + } + return ret; +} + +static size_t _regerror(int errcode, const regex_t *preg, char *errbuf, size_t errbuf_size) { + perthread_regex_t *entry = get_perthread_regex(preg); + + if (entry) + preg = &entry->preg; + + size_t ret = regerror(errcode, preg, errbuf, errbuf_size); + + if (entry) + pthread_mutex_unlock(&entry->lock); + + return ret; +} + +/* queryInterface function + * rgerhards, 2008-03-05 + */ +BEGINobjQueryInterface(regexp) +CODESTARTobjQueryInterface(regexp) + if(pIf->ifVersion != regexpCURR_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). + */ + if (USE_PERTHREAD_REGEX) { + pIf->regcomp = _regcomp; + pIf->regexec = _regexec; + pIf->regerror = _regerror; + pIf->regfree = _regfree; + } else { + pIf->regcomp = regcomp; + pIf->regexec = regexec; + pIf->regerror = regerror; + pIf->regfree = regfree; + } + +finalize_it: +ENDobjQueryInterface(regexp) + + +/* Initialize the regexp class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-19 + */ +BEGINAbstractObjClassInit(regexp, 1, OBJ_IS_LOADABLE_MODULE) /* class, version */ + /* request objects we use */ + + if (USE_PERTHREAD_REGEX) { + pthread_mutex_init(&mut_regexp, NULL); + + regex_to_uncomp = create_hashtable(100, hash_from_regex, key_equals_regex, NULL); + perthread_regexs = create_hashtable(100, hash_from_tregex, key_equals_tregex, NULL); + if(regex_to_uncomp == NULL || perthread_regexs == NULL) { + LogError(0, RS_RET_INTERNAL_ERROR, "error trying to initialize hash-table " + "for regexp table. regexp will be disabled."); + if (regex_to_uncomp) hashtable_destroy(regex_to_uncomp, 1); + if (perthread_regexs) hashtable_destroy(perthread_regexs, 1); + regex_to_uncomp = NULL; + perthread_regexs = NULL; + ABORT_FINALIZE(RS_RET_INTERNAL_ERROR); + } + } + +ENDObjClassInit(regexp) + + +/* Exit the class. + */ +BEGINObjClassExit(regexp, OBJ_IS_LOADABLE_MODULE) /* class, version */ + if (USE_PERTHREAD_REGEX) { + /* release objects we no longer need */ + pthread_mutex_destroy(&mut_regexp); + if (regex_to_uncomp) + hashtable_destroy(regex_to_uncomp, 1); + if (perthread_regexs) + hashtable_destroy(perthread_regexs, 1); + } +ENDObjClassExit(regexp) + + +/* --------------- here now comes the plumbing that makes as a library module --------------- */ + + +BEGINmodExit +CODESTARTmodExit +ENDmodExit + + +BEGINqueryEtryPt +CODESTARTqueryEtryPt +CODEqueryEtryPt_STD_LIB_QUERIES +ENDqueryEtryPt + + +BEGINmodInit() +CODESTARTmodInit + *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */ + + CHKiRet(regexpClassInit(pModInfo)); /* must be done after tcps_sess, as we use it */ + /* Initialize all classes that are in our module - this includes ourselfs */ +ENDmodInit +/* vi:set ai: + */ |