mirror of https://github.com/OISF/suricata
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
821 lines
21 KiB
C
821 lines
21 KiB
C
/* Copyright (C) 2007-2016 Open Information Security Foundation
|
|
*
|
|
* You can copy, redistribute or modify this Program under the terms of
|
|
* the GNU General Public License version 2 as published by the Free
|
|
* Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* version 2 along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
* 02110-1301, USA.
|
|
*/
|
|
|
|
/**
|
|
* \file
|
|
*
|
|
* \author Victor Julien <victor@inliniac.net>
|
|
*
|
|
*/
|
|
|
|
#include "suricata-common.h"
|
|
#include "conf.h"
|
|
|
|
#include "util-debug.h"
|
|
#include "util-thash.h"
|
|
|
|
#include "util-random.h"
|
|
#include "util-misc.h"
|
|
#include "util-byte.h"
|
|
|
|
#include "util-hash-lookup3.h"
|
|
#include "util-validate.h"
|
|
|
|
static THashData *THashGetUsed(THashTableContext *ctx);
|
|
static void THashDataEnqueue (THashDataQueue *q, THashData *h);
|
|
|
|
void THashDataMoveToSpare(THashTableContext *ctx, THashData *h)
|
|
{
|
|
THashDataEnqueue(&ctx->spare_q, h);
|
|
(void) SC_ATOMIC_SUB(ctx->counter, 1);
|
|
}
|
|
|
|
static THashDataQueue *THashDataQueueInit (THashDataQueue *q)
|
|
{
|
|
if (q != NULL) {
|
|
memset(q, 0, sizeof(THashDataQueue));
|
|
HQLOCK_INIT(q);
|
|
}
|
|
return q;
|
|
}
|
|
|
|
THashDataQueue *THashDataQueueNew(void)
|
|
{
|
|
THashDataQueue *q = (THashDataQueue *)SCMalloc(sizeof(THashDataQueue));
|
|
if (q == NULL) {
|
|
SCLogError("Fatal error encountered in THashDataQueueNew. Exiting...");
|
|
exit(EXIT_SUCCESS);
|
|
}
|
|
q = THashDataQueueInit(q);
|
|
return q;
|
|
}
|
|
|
|
/**
|
|
* \brief Destroy a queue
|
|
*
|
|
* \param q the queue to destroy
|
|
*/
|
|
static void THashDataQueueDestroy (THashDataQueue *q)
|
|
{
|
|
HQLOCK_DESTROY(q);
|
|
}
|
|
|
|
/**
|
|
* \brief add to queue
|
|
*
|
|
* \param q queue
|
|
* \param h data
|
|
*/
|
|
static void THashDataEnqueue (THashDataQueue *q, THashData *h)
|
|
{
|
|
#ifdef DEBUG
|
|
BUG_ON(q == NULL || h == NULL);
|
|
#endif
|
|
|
|
HQLOCK_LOCK(q);
|
|
|
|
/* more data in queue */
|
|
if (q->top != NULL) {
|
|
h->next = q->top;
|
|
q->top->prev = h;
|
|
q->top = h;
|
|
/* only data */
|
|
} else {
|
|
q->top = h;
|
|
q->bot = h;
|
|
}
|
|
q->len++;
|
|
#ifdef DBG_PERF
|
|
if (q->len > q->dbg_maxlen)
|
|
q->dbg_maxlen = q->len;
|
|
#endif /* DBG_PERF */
|
|
HQLOCK_UNLOCK(q);
|
|
}
|
|
|
|
/**
|
|
* \brief remove data from the queue
|
|
*
|
|
* \param q queue
|
|
*
|
|
* \retval h data or NULL if empty list.
|
|
*/
|
|
static THashData *THashDataDequeue (THashDataQueue *q)
|
|
{
|
|
HQLOCK_LOCK(q);
|
|
|
|
THashData *h = q->bot;
|
|
if (h == NULL) {
|
|
HQLOCK_UNLOCK(q);
|
|
return NULL;
|
|
}
|
|
|
|
/* more packets in queue */
|
|
if (q->bot->prev != NULL) {
|
|
q->bot = q->bot->prev;
|
|
q->bot->next = NULL;
|
|
/* just the one we remove, so now empty */
|
|
} else {
|
|
q->top = NULL;
|
|
q->bot = NULL;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
BUG_ON(q->len == 0);
|
|
#endif
|
|
if (q->len > 0)
|
|
q->len--;
|
|
|
|
h->next = NULL;
|
|
h->prev = NULL;
|
|
|
|
HQLOCK_UNLOCK(q);
|
|
return h;
|
|
}
|
|
|
|
#if 0
|
|
static uint32_t THashDataQueueLen(THashDataQueue *q)
|
|
{
|
|
uint32_t len;
|
|
HQLOCK_LOCK(q);
|
|
len = q->len;
|
|
HQLOCK_UNLOCK(q);
|
|
return len;
|
|
}
|
|
#endif
|
|
|
|
static THashData *THashDataAlloc(THashTableContext *ctx)
|
|
{
|
|
const size_t data_size = THASH_DATA_SIZE(ctx);
|
|
|
|
if (!(THASH_CHECK_MEMCAP(ctx, data_size))) {
|
|
return NULL;
|
|
}
|
|
|
|
(void) SC_ATOMIC_ADD(ctx->memuse, data_size);
|
|
|
|
THashData *h = SCCalloc(1, data_size);
|
|
if (unlikely(h == NULL))
|
|
goto error;
|
|
|
|
/* points to data right after THashData block */
|
|
h->data = (uint8_t *)h + sizeof(THashData);
|
|
|
|
// memset(h, 0x00, data_size);
|
|
|
|
SCMutexInit(&h->m, NULL);
|
|
SC_ATOMIC_INIT(h->use_cnt);
|
|
return h;
|
|
|
|
error:
|
|
return NULL;
|
|
}
|
|
|
|
static void THashDataFree(THashTableContext *ctx, THashData *h)
|
|
{
|
|
if (h != NULL) {
|
|
DEBUG_VALIDATE_BUG_ON(SC_ATOMIC_GET(h->use_cnt) != 0);
|
|
|
|
if (h->data != NULL) {
|
|
ctx->config.DataFree(h->data);
|
|
}
|
|
SCMutexDestroy(&h->m);
|
|
SCFree(h);
|
|
(void) SC_ATOMIC_SUB(ctx->memuse, THASH_DATA_SIZE(ctx));
|
|
}
|
|
}
|
|
|
|
#define THASH_DEFAULT_HASHSIZE 4096
|
|
#define THASH_DEFAULT_MEMCAP 16777216
|
|
#define THASH_DEFAULT_PREALLOC 1000
|
|
|
|
#define GET_VAR(prefix,name) \
|
|
snprintf(varname, sizeof(varname), "%s.%s", (prefix), (name))
|
|
|
|
/** \brief initialize the configuration
|
|
* \warning Not thread safe */
|
|
static int THashInitConfig(THashTableContext *ctx, const char *cnf_prefix)
|
|
{
|
|
char varname[256];
|
|
|
|
SCLogDebug("initializing thash engine...");
|
|
|
|
/* Check if we have memcap and hash_size defined at config */
|
|
const char *conf_val;
|
|
uint32_t configval = 0;
|
|
|
|
/** set config values for memcap, prealloc and hash_size */
|
|
GET_VAR(cnf_prefix, "memcap");
|
|
if ((ConfGet(varname, &conf_val)) == 1)
|
|
{
|
|
if (ParseSizeStringU64(conf_val, &ctx->config.memcap) < 0) {
|
|
SCLogError("Error parsing %s "
|
|
"from conf file - %s. Killing engine",
|
|
varname, conf_val);
|
|
return -1;
|
|
}
|
|
}
|
|
GET_VAR(cnf_prefix, "hash-size");
|
|
if ((ConfGet(varname, &conf_val)) == 1)
|
|
{
|
|
if (StringParseUint32(&configval, 10, (uint16_t)strlen(conf_val), conf_val) > 0) {
|
|
ctx->config.hash_size = configval;
|
|
}
|
|
}
|
|
|
|
GET_VAR(cnf_prefix, "prealloc");
|
|
if ((ConfGet(varname, &conf_val)) == 1)
|
|
{
|
|
if (StringParseUint32(&configval, 10, (uint16_t)strlen(conf_val), conf_val) > 0) {
|
|
ctx->config.prealloc = configval;
|
|
} else {
|
|
WarnInvalidConfEntry(varname, "%"PRIu32, ctx->config.prealloc);
|
|
}
|
|
}
|
|
|
|
/* alloc hash memory */
|
|
uint64_t hash_size = ctx->config.hash_size * sizeof(THashHashRow);
|
|
if (!(THASH_CHECK_MEMCAP(ctx, hash_size))) {
|
|
SCLogError("allocating hash failed: "
|
|
"max hash memcap is smaller than projected hash size. "
|
|
"Memcap: %" PRIu64 ", Hash table size %" PRIu64 ". Calculate "
|
|
"total hash size by multiplying \"hash-size\" with %" PRIuMAX ", "
|
|
"which is the hash bucket size.",
|
|
ctx->config.memcap, hash_size, (uintmax_t)sizeof(THashHashRow));
|
|
return -1;
|
|
}
|
|
ctx->array = SCMallocAligned(ctx->config.hash_size * sizeof(THashHashRow), CLS);
|
|
if (unlikely(ctx->array == NULL)) {
|
|
SCLogError("Fatal error encountered in THashInitConfig. Exiting...");
|
|
return -1;
|
|
}
|
|
memset(ctx->array, 0, ctx->config.hash_size * sizeof(THashHashRow));
|
|
|
|
uint32_t i = 0;
|
|
for (i = 0; i < ctx->config.hash_size; i++) {
|
|
HRLOCK_INIT(&ctx->array[i]);
|
|
}
|
|
(void) SC_ATOMIC_ADD(ctx->memuse, (ctx->config.hash_size * sizeof(THashHashRow)));
|
|
|
|
/* pre allocate prealloc */
|
|
for (i = 0; i < ctx->config.prealloc; i++) {
|
|
if (!(THASH_CHECK_MEMCAP(ctx, THASH_DATA_SIZE(ctx)))) {
|
|
SCLogError("preallocating data failed: "
|
|
"max thash memcap reached. Memcap %" PRIu64 ", "
|
|
"Memuse %" PRIu64 ".",
|
|
ctx->config.memcap,
|
|
((uint64_t)SC_ATOMIC_GET(ctx->memuse) + THASH_DATA_SIZE(ctx)));
|
|
return -1;
|
|
}
|
|
|
|
THashData *h = THashDataAlloc(ctx);
|
|
if (h == NULL) {
|
|
SCLogError("preallocating data failed: %s", strerror(errno));
|
|
return -1;
|
|
}
|
|
THashDataEnqueue(&ctx->spare_q,h);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
THashTableContext *THashInit(const char *cnf_prefix, size_t data_size,
|
|
int (*DataSet)(void *, void *), void (*DataFree)(void *), uint32_t (*DataHash)(void *),
|
|
bool (*DataCompare)(void *, void *), bool reset_memcap, uint64_t memcap, uint32_t hashsize)
|
|
{
|
|
THashTableContext *ctx = SCCalloc(1, sizeof(*ctx));
|
|
BUG_ON(!ctx);
|
|
|
|
ctx->config.data_size = data_size;
|
|
ctx->config.DataSet = DataSet;
|
|
ctx->config.DataFree = DataFree;
|
|
ctx->config.DataHash = DataHash;
|
|
ctx->config.DataCompare = DataCompare;
|
|
|
|
/* set defaults */
|
|
ctx->config.hash_rand = (uint32_t)RandomGet();
|
|
ctx->config.hash_size = hashsize > 0 ? hashsize : THASH_DEFAULT_HASHSIZE;
|
|
/* Reset memcap in case of loading from file to the highest possible value
|
|
unless defined by the rule keyword */
|
|
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
|
|
// limit memcap size to default when fuzzing
|
|
ctx->config.memcap = THASH_DEFAULT_MEMCAP;
|
|
#else
|
|
if (memcap > 0) {
|
|
ctx->config.memcap = memcap;
|
|
} else {
|
|
ctx->config.memcap = reset_memcap ? UINT64_MAX : THASH_DEFAULT_MEMCAP;
|
|
}
|
|
#endif
|
|
ctx->config.prealloc = THASH_DEFAULT_PREALLOC;
|
|
|
|
SC_ATOMIC_INIT(ctx->counter);
|
|
SC_ATOMIC_INIT(ctx->memuse);
|
|
SC_ATOMIC_INIT(ctx->prune_idx);
|
|
THashDataQueueInit(&ctx->spare_q);
|
|
|
|
if (THashInitConfig(ctx, cnf_prefix) < 0) {
|
|
THashShutdown(ctx);
|
|
ctx = NULL;
|
|
}
|
|
return ctx;
|
|
}
|
|
|
|
/* \brief Set memcap to current memuse
|
|
* */
|
|
void THashConsolidateMemcap(THashTableContext *ctx)
|
|
{
|
|
ctx->config.memcap = MAX(SC_ATOMIC_GET(ctx->memuse), ctx->config.memcap);
|
|
SCLogDebug("memcap after load set to: %" PRIu64, ctx->config.memcap);
|
|
}
|
|
|
|
/** \brief shutdown the flow engine
|
|
* \warning Not thread safe */
|
|
void THashShutdown(THashTableContext *ctx)
|
|
{
|
|
THashData *h;
|
|
|
|
/* free spare queue */
|
|
while ((h = THashDataDequeue(&ctx->spare_q))) {
|
|
BUG_ON(SC_ATOMIC_GET(h->use_cnt) > 0);
|
|
THashDataFree(ctx, h);
|
|
}
|
|
|
|
/* clear and free the hash */
|
|
if (ctx->array != NULL) {
|
|
for (uint32_t u = 0; u < ctx->config.hash_size; u++) {
|
|
h = ctx->array[u].head;
|
|
while (h) {
|
|
THashData *n = h->next;
|
|
THashDataFree(ctx, h);
|
|
h = n;
|
|
}
|
|
|
|
HRLOCK_DESTROY(&ctx->array[u]);
|
|
}
|
|
SCFreeAligned(ctx->array);
|
|
ctx->array = NULL;
|
|
}
|
|
(void) SC_ATOMIC_SUB(ctx->memuse, ctx->config.hash_size * sizeof(THashHashRow));
|
|
THashDataQueueDestroy(&ctx->spare_q);
|
|
SCFree(ctx);
|
|
return;
|
|
}
|
|
|
|
/** \brief Walk the hash
|
|
*
|
|
*/
|
|
int THashWalk(THashTableContext *ctx, THashFormatFunc FormatterFunc, THashOutputFunc OutputterFunc, void *output_ctx)
|
|
{
|
|
uint32_t u;
|
|
|
|
if (ctx->array == NULL)
|
|
return -1;
|
|
|
|
bool err = false;
|
|
for (u = 0; u < ctx->config.hash_size; u++) {
|
|
THashHashRow *hb = &ctx->array[u];
|
|
HRLOCK_LOCK(hb);
|
|
THashData *h = hb->head;
|
|
while (h) {
|
|
char output_string[1024] = "";
|
|
int size = FormatterFunc(h->data, output_string, sizeof(output_string));
|
|
if (size > 0) {
|
|
if (OutputterFunc(output_ctx, (const uint8_t *)output_string, size) < 0) {
|
|
err = true;
|
|
break;
|
|
}
|
|
}
|
|
h = h->next;
|
|
}
|
|
HRLOCK_UNLOCK(hb);
|
|
if (err == true)
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/** \brief Cleanup the thash engine
|
|
*
|
|
* Cleanup the thash engine from tag and threshold.
|
|
*
|
|
*/
|
|
void THashCleanup(THashTableContext *ctx)
|
|
{
|
|
uint32_t u;
|
|
|
|
if (ctx->array == NULL)
|
|
return;
|
|
|
|
for (u = 0; u < ctx->config.hash_size; u++) {
|
|
THashHashRow *hb = &ctx->array[u];
|
|
HRLOCK_LOCK(hb);
|
|
THashData *h = hb->head;
|
|
while (h) {
|
|
if ((SC_ATOMIC_GET(h->use_cnt) > 0)) {
|
|
h = h->next;
|
|
} else {
|
|
THashData *n = h->next;
|
|
/* remove from the hash */
|
|
if (h->prev != NULL)
|
|
h->prev->next = h->next;
|
|
if (h->next != NULL)
|
|
h->next->prev = h->prev;
|
|
if (hb->head == h)
|
|
hb->head = h->next;
|
|
if (hb->tail == h)
|
|
hb->tail = h->prev;
|
|
h->next = NULL;
|
|
h->prev = NULL;
|
|
THashDataMoveToSpare(ctx, h);
|
|
h = n;
|
|
}
|
|
}
|
|
HRLOCK_UNLOCK(hb);
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* calculate the hash key for this packet
|
|
*
|
|
* we're using:
|
|
* hash_rand -- set at init time
|
|
* source address
|
|
*/
|
|
static uint32_t THashGetKey(const THashConfig *cnf, void *data)
|
|
{
|
|
uint32_t key;
|
|
|
|
key = cnf->DataHash(data);
|
|
key %= cnf->hash_size;
|
|
|
|
return key;
|
|
}
|
|
|
|
static inline int THashCompare(const THashConfig *cnf, void *a, void *b)
|
|
{
|
|
if (cnf->DataCompare(a, b))
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* \brief Get new data
|
|
*
|
|
* Get new data. We're checking memcap first and will try to make room
|
|
* if the memcap is reached.
|
|
*
|
|
* \retval h *LOCKED* data on succes, NULL on error.
|
|
*/
|
|
static THashData *THashDataGetNew(THashTableContext *ctx, void *data)
|
|
{
|
|
THashData *h = NULL;
|
|
|
|
/* get data from the spare queue */
|
|
h = THashDataDequeue(&ctx->spare_q);
|
|
if (h == NULL) {
|
|
/* If we reached the max memcap, we get used data */
|
|
if (!(THASH_CHECK_MEMCAP(ctx, THASH_DATA_SIZE(ctx)))) {
|
|
h = THashGetUsed(ctx);
|
|
if (h == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
if (!SC_ATOMIC_GET(ctx->memcap_reached)) {
|
|
SC_ATOMIC_SET(ctx->memcap_reached, true);
|
|
}
|
|
|
|
/* freed data, but it's unlocked */
|
|
} else {
|
|
/* now see if we can alloc a new data */
|
|
h = THashDataAlloc(ctx);
|
|
if (h == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
/* data is initialized but *unlocked* */
|
|
}
|
|
} else {
|
|
/* data has been recycled before it went into the spare queue */
|
|
|
|
/* data is initialized (recycled) but *unlocked* */
|
|
}
|
|
|
|
// setup the data
|
|
BUG_ON(ctx->config.DataSet(h->data, data) != 0);
|
|
|
|
(void) SC_ATOMIC_ADD(ctx->counter, 1);
|
|
SCMutexLock(&h->m);
|
|
return h;
|
|
}
|
|
|
|
/*
|
|
* returns a *LOCKED* data or NULL
|
|
*/
|
|
|
|
struct THashDataGetResult
|
|
THashGetFromHash (THashTableContext *ctx, void *data)
|
|
{
|
|
struct THashDataGetResult res = { .data = NULL, .is_new = false, };
|
|
THashData *h = NULL;
|
|
|
|
/* get the key to our bucket */
|
|
uint32_t key = THashGetKey(&ctx->config, data);
|
|
/* get our hash bucket and lock it */
|
|
THashHashRow *hb = &ctx->array[key];
|
|
HRLOCK_LOCK(hb);
|
|
|
|
/* see if the bucket already has data */
|
|
if (hb->head == NULL) {
|
|
h = THashDataGetNew(ctx, data);
|
|
if (h == NULL) {
|
|
HRLOCK_UNLOCK(hb);
|
|
return res;
|
|
}
|
|
|
|
/* data is locked */
|
|
hb->head = h;
|
|
hb->tail = h;
|
|
|
|
/* initialize and return */
|
|
(void) THashIncrUsecnt(h);
|
|
|
|
HRLOCK_UNLOCK(hb);
|
|
res.data = h;
|
|
res.is_new = true;
|
|
return res;
|
|
}
|
|
|
|
/* ok, we have data in the bucket. Let's find out if it is our data */
|
|
h = hb->head;
|
|
|
|
/* see if this is the data we are looking for */
|
|
if (THashCompare(&ctx->config, h->data, data) == 0) {
|
|
THashData *ph = NULL; /* previous data */
|
|
|
|
while (h) {
|
|
ph = h;
|
|
h = h->next;
|
|
|
|
if (h == NULL) {
|
|
h = ph->next = THashDataGetNew(ctx, data);
|
|
if (h == NULL) {
|
|
HRLOCK_UNLOCK(hb);
|
|
return res;
|
|
}
|
|
hb->tail = h;
|
|
|
|
/* data is locked */
|
|
|
|
h->prev = ph;
|
|
|
|
/* initialize and return */
|
|
(void) THashIncrUsecnt(h);
|
|
|
|
HRLOCK_UNLOCK(hb);
|
|
res.data = h;
|
|
res.is_new = true;
|
|
return res;
|
|
}
|
|
|
|
if (THashCompare(&ctx->config, h->data, data) != 0) {
|
|
/* we found our data, lets put it on top of the
|
|
* hash list -- this rewards active data */
|
|
if (h->next) {
|
|
h->next->prev = h->prev;
|
|
}
|
|
if (h->prev) {
|
|
h->prev->next = h->next;
|
|
}
|
|
if (h == hb->tail) {
|
|
hb->tail = h->prev;
|
|
}
|
|
|
|
h->next = hb->head;
|
|
h->prev = NULL;
|
|
hb->head->prev = h;
|
|
hb->head = h;
|
|
|
|
/* found our data, lock & return */
|
|
SCMutexLock(&h->m);
|
|
(void) THashIncrUsecnt(h);
|
|
HRLOCK_UNLOCK(hb);
|
|
res.data = h;
|
|
res.is_new = false;
|
|
/* coverity[missing_unlock : FALSE] */
|
|
return res;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* lock & return */
|
|
SCMutexLock(&h->m);
|
|
(void) THashIncrUsecnt(h);
|
|
HRLOCK_UNLOCK(hb);
|
|
res.data = h;
|
|
res.is_new = false;
|
|
/* coverity[missing_unlock : FALSE] */
|
|
return res;
|
|
}
|
|
|
|
/** \brief look up data in the hash
|
|
*
|
|
* \param data data to look up
|
|
*
|
|
* \retval h *LOCKED* data or NULL
|
|
*/
|
|
THashData *THashLookupFromHash (THashTableContext *ctx, void *data)
|
|
{
|
|
THashData *h = NULL;
|
|
|
|
/* get the key to our bucket */
|
|
uint32_t key = THashGetKey(&ctx->config, data);
|
|
/* get our hash bucket and lock it */
|
|
THashHashRow *hb = &ctx->array[key];
|
|
HRLOCK_LOCK(hb);
|
|
|
|
if (hb->head == NULL) {
|
|
HRLOCK_UNLOCK(hb);
|
|
return h;
|
|
}
|
|
|
|
/* ok, we have data in the bucket. Let's find out if it is our data */
|
|
h = hb->head;
|
|
|
|
/* see if this is the data we are looking for */
|
|
if (THashCompare(&ctx->config, h->data, data) == 0) {
|
|
while (h) {
|
|
h = h->next;
|
|
if (h == NULL) {
|
|
HRLOCK_UNLOCK(hb);
|
|
return h;
|
|
}
|
|
|
|
if (THashCompare(&ctx->config, h->data, data) != 0) {
|
|
/* we found our data, lets put it on top of the
|
|
* hash list -- this rewards active data */
|
|
if (h->next) {
|
|
h->next->prev = h->prev;
|
|
}
|
|
if (h->prev) {
|
|
h->prev->next = h->next;
|
|
}
|
|
if (h == hb->tail) {
|
|
hb->tail = h->prev;
|
|
}
|
|
|
|
h->next = hb->head;
|
|
h->prev = NULL;
|
|
hb->head->prev = h;
|
|
hb->head = h;
|
|
|
|
/* found our data, lock & return */
|
|
SCMutexLock(&h->m);
|
|
(void) THashIncrUsecnt(h);
|
|
HRLOCK_UNLOCK(hb);
|
|
return h;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* lock & return */
|
|
SCMutexLock(&h->m);
|
|
(void) THashIncrUsecnt(h);
|
|
HRLOCK_UNLOCK(hb);
|
|
return h;
|
|
}
|
|
|
|
/** \internal
|
|
* \brief Get data from the hash directly.
|
|
*
|
|
* Called in conditions where the spare queue is empty and memcap is
|
|
* reached.
|
|
*
|
|
* Walks the hash until data can be freed. "prune_idx" atomic int makes
|
|
* sure we don't start at the top each time since that would clear the top
|
|
* of the hash leading to longer and longer search times under high
|
|
* pressure (observed).
|
|
*
|
|
* \retval h data or NULL
|
|
*/
|
|
static THashData *THashGetUsed(THashTableContext *ctx)
|
|
{
|
|
uint32_t idx = SC_ATOMIC_GET(ctx->prune_idx) % ctx->config.hash_size;
|
|
uint32_t cnt = ctx->config.hash_size;
|
|
|
|
while (cnt--) {
|
|
if (++idx >= ctx->config.hash_size)
|
|
idx = 0;
|
|
|
|
THashHashRow *hb = &ctx->array[idx];
|
|
|
|
if (HRLOCK_TRYLOCK(hb) != 0)
|
|
continue;
|
|
|
|
THashData *h = hb->tail;
|
|
if (h == NULL) {
|
|
HRLOCK_UNLOCK(hb);
|
|
continue;
|
|
}
|
|
|
|
if (SCMutexTrylock(&h->m) != 0) {
|
|
HRLOCK_UNLOCK(hb);
|
|
continue;
|
|
}
|
|
|
|
if (SC_ATOMIC_GET(h->use_cnt) > 0) {
|
|
HRLOCK_UNLOCK(hb);
|
|
SCMutexUnlock(&h->m);
|
|
continue;
|
|
}
|
|
|
|
/* remove from the hash */
|
|
if (h->prev != NULL)
|
|
h->prev->next = h->next;
|
|
if (h->next != NULL)
|
|
h->next->prev = h->prev;
|
|
if (hb->head == h)
|
|
hb->head = h->next;
|
|
if (hb->tail == h)
|
|
hb->tail = h->prev;
|
|
|
|
h->next = NULL;
|
|
h->prev = NULL;
|
|
HRLOCK_UNLOCK(hb);
|
|
|
|
if (h->data != NULL) {
|
|
ctx->config.DataFree(h->data);
|
|
}
|
|
SCMutexUnlock(&h->m);
|
|
|
|
(void) SC_ATOMIC_ADD(ctx->prune_idx, (ctx->config.hash_size - cnt));
|
|
return h;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* \retval int -1 not found
|
|
* \retval int 0 found, but it was busy (ref cnt)
|
|
* \retval int 1 found and removed */
|
|
int THashRemoveFromHash (THashTableContext *ctx, void *data)
|
|
{
|
|
/* get the key to our bucket */
|
|
uint32_t key = THashGetKey(&ctx->config, data);
|
|
/* get our hash bucket and lock it */
|
|
THashHashRow *hb = &ctx->array[key];
|
|
|
|
HRLOCK_LOCK(hb);
|
|
THashData *h = hb->head;
|
|
while (h != NULL) {
|
|
/* see if this is the data we are looking for */
|
|
if (THashCompare(&ctx->config, h->data, data) == 0) {
|
|
h = h->next;
|
|
continue;
|
|
}
|
|
|
|
SCMutexLock(&h->m);
|
|
if (SC_ATOMIC_GET(h->use_cnt) > 0) {
|
|
SCMutexUnlock(&h->m);
|
|
HRLOCK_UNLOCK(hb);
|
|
return 0;
|
|
}
|
|
|
|
/* remove from the hash */
|
|
if (h->prev != NULL)
|
|
h->prev->next = h->next;
|
|
if (h->next != NULL)
|
|
h->next->prev = h->prev;
|
|
if (hb->head == h)
|
|
hb->head = h->next;
|
|
if (hb->tail == h)
|
|
hb->tail = h->prev;
|
|
|
|
h->next = NULL;
|
|
h->prev = NULL;
|
|
SCMutexUnlock(&h->m);
|
|
HRLOCK_UNLOCK(hb);
|
|
THashDataFree(ctx, h);
|
|
SCLogDebug("found and removed");
|
|
return 1;
|
|
}
|
|
|
|
HRLOCK_UNLOCK(hb);
|
|
SCLogDebug("data not found");
|
|
return -1;
|
|
}
|