/* 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;
    regexpClassExit();
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:
 */
