/* 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 #include #include #include #include #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: */