libclamav/cache.c
6e11bf61
 /*
  *  Copyright (C) 2010 Sourcefire, Inc.
  *
60790424
  *  Authors: aCaB <acab@clamav.net>, Török Edvin <edwin@clamav.net>
6e11bf61
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it 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
  *  along with this program; if not, write to the Free Software
  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  *  MA 02110-1301, USA.
  */
 
60b21251
 #if HAVE_CONFIG_H
 #include "clamav-config.h"
 #endif
 
a99a6040
 #include <string.h>
 #include <stdlib.h>
 #include <pthread.h>
 #include <assert.h>
60b21251
 
511c2e79
 #include "md5.h"
 #include "mpool.h"
 #include "clamav.h"
 #include "cache.h"
ea6f1300
 #include "fmap.h"
511c2e79
 
91505254
 #ifdef CL_THREAD_SAFE
 static pthread_mutex_t pool_mutex = PTHREAD_MUTEX_INITIALIZER;
 #else
 #define pthread_mutex_lock(x) 0
 #define pthread_mutex_unlock(x)
 #define pthread_mutex_init(a, b) 0
 #define pthread_mutex_destroy(a) do { } while(0)
 #endif
52f52931
 
 /* The number of root trees and the chooser function 
    Each tree is protected by a mutex against concurrent access */
 /* #define TREES 1 */
 /* static inline unsigned int getkey(uint8_t *hash) { return 0; } */
 #define TREES 256
 static inline unsigned int getkey(uint8_t *hash) { return *hash; }
 /* #define TREES 4096 */
 /* static inline unsigned int getkey(uint8_t *hash) { return hash[0] | ((unsigned int)(hash[1] & 0xf)<<8) ; } */
 /* #define TREES 65536 */
 /* static inline unsigned int getkey(uint8_t *hash) { return hash[0] | (((unsigned int)hash[1])<<8) ; } */
 
 /* The number of nodes in each tree */
6223810b
 #define NODES 256
5b95022b
 
52f52931
 
 /* The replacement policy algorithm to use */
 /* #define USE_LRUHASHCACHE */
5bf3c763
 #define USE_SPLAY
a99a6040
 
52f52931
 /* LRUHASHCACHE --------------------------------------------------------------------- */
5b95022b
 #ifdef USE_LRUHASHCACHE
a6f7b467
 struct cache_key {
402f4b19
     int64_t digest[2];
a6f7b467
     uint32_t size; /* 0 is used to mark an empty hash slot! */
     struct cache_key *lru_next, *lru_prev;
 };
 
 struct cache_set {
     struct cache_key *data;
     size_t maxelements; /* considering load factor */
402f4b19
     size_t maxdeleted;
a6f7b467
     size_t elements;
402f4b19
     size_t deleted;
a6f7b467
     struct cache_key *lru_head, *lru_tail;
 };
 
 #define CACHE_KEY_DELETED ~0u
 #define CACHE_KEY_EMPTY 0
 
 static void cacheset_lru_remove(struct cache_set *map, size_t howmany)
 {
     while (howmany--) {
 	struct cache_key *old;
 	assert(map->lru_head);
 	assert(!old->lru_prev);
6223810b
 	/* Remove a key from the head of the list */
a6f7b467
 	old = map->lru_head;
 	map->lru_head = old->lru_next;
 	old->size = CACHE_KEY_DELETED;
 	/* This slot is now deleted, it is not empty,
 	 * because previously we could have inserted a key that has seen this
 	 * slot as occupied, to find that key we need to ensure that all keys
 	 * that were occupied when the key was inserted, are seen as occupied
 	 * when searching too.
 	 * Of course when inserting a new value, we treat deleted slots as
 	 * empty.
 	 * We only replace old values with new values, but there is no guarantee
 	 * that the newly inserted value would hash to same place as the value
 	 * we remove due to LRU! */
 	if (old == map->lru_tail)
 	    map->lru_tail = 0;
422faf75
 	map->elements--;
402f4b19
 	map->deleted++;
a6f7b467
     }
 }
 
402f4b19
 static inline int cacheset_lookup_internal(struct cache_set *map,
 					   const char *md5,  size_t size,
 					   uint32_t *insert_pos, int deletedok)
a6f7b467
 {
402f4b19
     const struct cache_key*data = map->data;
6223810b
     uint32_t capmask = NODES - 1;
402f4b19
     const struct cache_key *k;
     uint32_t idx, tries = 0;
     uint64_t md5_0, md5_1;
     uint64_t md5a[2];
 
     memcpy(&md5a, md5, 16);
     md5_0 = md5a[0];
     md5_1 = md5a[1];
     idx = md5_1 & capmask;
     k = &data[idx];
     while (k->size != CACHE_KEY_EMPTY && tries <= capmask) {
 	if (k->digest[0] == md5_0 &&
 	    k->digest[1] == md5_1 &&
 	    k->size == size) {
a6f7b467
 	    /* found key */
 	    *insert_pos = idx;
 	    return 1;
 	}
402f4b19
 	if (deletedok && k->size == CACHE_KEY_DELETED) {
a6f7b467
            /* treat deleted slot as empty */
            *insert_pos = idx;
            return 0;
402f4b19
 	}
 	idx = (idx + tries++) & capmask;
 	k = &data[idx];
a6f7b467
     }
     /* found empty pos */
     *insert_pos = idx;
     return 0;
 }
 
 static inline void lru_remove(struct cache_set *map, struct cache_key *newkey)
 {
     if (newkey->lru_next)
 	newkey->lru_next->lru_prev = newkey->lru_prev;
     if (newkey->lru_prev)
 	newkey->lru_prev->lru_next = newkey->lru_next;
     if (newkey == map->lru_head)
 	map->lru_head = newkey->lru_next;
 }
 
 static inline void lru_addtail(struct cache_set *map, struct cache_key *newkey)
 {
     if (!map->lru_head)
 	map->lru_head = newkey;
     if (map->lru_tail)
 	map->lru_tail->lru_next = newkey;
     newkey->lru_next = NULL;
     newkey->lru_prev = map->lru_tail;
     map->lru_tail = newkey;
 }
 
402f4b19
 
6223810b
 static void cacheset_add(struct cache_set *map, unsigned char *md5, size_t size, mpool_t *mempool);
 static int cacheset_init(struct cache_set *map, mpool_t *mempool);
402f4b19
 
6223810b
 static void cacheset_rehash(struct cache_set *map, mpool_t *mempool)
402f4b19
 {
     unsigned i;
     int ret;
     struct cache_set tmp_set;
     struct cache_key *key;
     pthread_mutex_lock(&pool_mutex);
6223810b
     ret = cacheset_init(&tmp_set, mempool);
402f4b19
     pthread_mutex_unlock(&pool_mutex);
     if (ret)
 	return;
 
     key = map->lru_head;
     for (i=0;key && i < tmp_set.maxelements/2;i++) {
6223810b
 	cacheset_add(&tmp_set, (unsigned char*)&key->digest, key->size, mempool);
402f4b19
 	key = key->lru_next;
     }
     pthread_mutex_lock(&pool_mutex);
     mpool_free(mempool, map->data);
     pthread_mutex_unlock(&pool_mutex);
     memcpy(map, &tmp_set, sizeof(tmp_set));
 }
 
6223810b
 static void cacheset_add(struct cache_set *map, unsigned char *md5, size_t size, mpool_t *mempool)
a6f7b467
 {
     int ret;
     uint32_t pos;
     struct cache_key *newkey;
402f4b19
 
     if (map->elements >= map->maxelements) {
a6f7b467
 	cacheset_lru_remove(map, 1);
402f4b19
 	if (map->deleted >= map->maxdeleted) {
6223810b
 	    cacheset_rehash(map, mempool);
402f4b19
 	}
     }
a6f7b467
     assert(map->elements < map->maxelements);
 
5b95022b
     ret = cacheset_lookup_internal(map, md5, size, &pos, 1);
a6f7b467
     newkey = &map->data[pos];
402f4b19
     if (newkey->size == CACHE_KEY_DELETED)
 	map->deleted--;
a6f7b467
     if (ret) {
 	/* was already added, remove from LRU list */
 	lru_remove(map, newkey);
     }
     /* add new key to tail of LRU list */
5b95022b
     memcpy(&map->data[pos].digest, md5, sizeof(map->data[pos].digest));
     map->data[pos].size = size;
a6f7b467
     lru_addtail(map, newkey);
 
     map->elements++;
 
     assert(pos < map->maxelements);
 }
 
5b95022b
 static int cacheset_lookup(struct cache_set *map, unsigned char *md5, size_t size)
a6f7b467
 {
     struct cache_key *newkey;
     int ret;
     uint32_t pos;
402f4b19
 
5b95022b
     ret = cacheset_lookup_internal(map, md5, size, &pos, 0);
a6f7b467
     if (!ret)
6223810b
 	return 0;
a6f7b467
     newkey = &map->data[pos];
     /* update LRU position: move to tail */
     lru_remove(map, newkey);
     lru_addtail(map, newkey);
6223810b
     return 1;
a6f7b467
 }
 
6223810b
 static int cacheset_init(struct cache_set *map, mpool_t *mempool) {
     map->data = mpool_calloc(mempool, NODES, sizeof(*map->data));
5b95022b
     if (!map->data)
 	return CL_EMEM;
6223810b
     map->maxelements = 80 * NODES / 100;
     map->maxdeleted = NODES - map->maxelements - 1;
5b95022b
     map->elements = 0;
     map->lru_head = map->lru_tail = NULL;
     return 0;
 }
6223810b
 
 static inline void cacheset_destroy(struct cache_set *cs, mpool_t *mempool) {
     mpool_free(mempool, cs->data);
     cs->data = NULL;
 }
 
a2c45a6b
 #endif /* USE_LRUHASHCACHE */
5b95022b
 
52f52931
 /* SPLAY --------------------------------------------------------------------- */
5b95022b
 #ifdef USE_SPLAY
52f52931
 
 struct node { /* a node */
a2c45a6b
     int64_t digest[2];
5b95022b
     struct node *left;
     struct node *right;
5bf3c763
     struct node *up;
08605d2f
     struct node *next;
     struct node *prev;
1e4d5656
     uint32_t size;
2b70f02a
     uint32_t minrec;
5b95022b
 };
 
52f52931
 struct cache_set { /* a tree */
5b95022b
     struct node *data;
     struct node *root;
08605d2f
     struct node *first;
     struct node *last;
5b95022b
 };
 
52f52931
 /* Allocates all the nodes and sets up the replacement chain */
6223810b
 static int cacheset_init(struct cache_set *cs, mpool_t *mempool) {
08605d2f
     unsigned int i;
6223810b
     cs->data = mpool_calloc(mempool, NODES,  sizeof(*cs->data));
08605d2f
     cs->root = NULL;
5b95022b
 
08605d2f
     if(!cs->data)
6223810b
 	return 1;
08605d2f
 
6223810b
     for(i=1; i<NODES; i++) {
08605d2f
 	cs->data[i-1].next = &cs->data[i];
 	cs->data[i].prev = &cs->data[i-1];
     }
 
     cs->first = cs->data;
6223810b
     cs->last = &cs->data[NODES-1];
08605d2f
 
5b95022b
     return 0;
 }
 
52f52931
 /* Frees all the nodes */
6223810b
 static inline void cacheset_destroy(struct cache_set *cs, mpool_t *mempool) {
     mpool_free(mempool, cs->data);
     cs->data = NULL;
 }
 
52f52931
 /* The left/right cooser for the splay tree */
 static inline int cmp(int64_t *a, ssize_t sa, int64_t *b, ssize_t sb) {
402f4b19
     if(a[1] < b[1]) return -1;
     if(a[1] > b[1]) return 1;
     if(a[0] < b[0]) return -1;
52f52931
     if(a[0] > b[0]) return 1;
     if(sa < sb) return -1;
     if(sa > sb) return 1;
     return 0;
a2c45a6b
 }
5b95022b
 
402f4b19
 
52f52931
 /* #define PRINT_TREE */
402f4b19
 #ifdef PRINT_TREE
 #define ptree printf
 #else
52f52931
 #define ptree(...)
402f4b19
 #endif
 
52f52931
 /* Debug function to print the tree and check its consistency */
 /* #define CHECK_TREE */
402f4b19
 #ifdef CHECK_TREE
 static int printtree(struct cache_set *cs, struct node *n, int d) {
     int i;
     int ab = 0;
     if (n == NULL) return 0;
52f52931
     if(n == cs->root) { ptree("--------------------------\n"); }
402f4b19
     ab |= printtree(cs, n->right, d+1);
     if(n->right) {
52f52931
 	if(cmp(n->digest, n->size, n->right->digest, n->right->size) >= 0) {
402f4b19
 	    for (i=0; i<d; i++) ptree("        ");
52f52931
 	    ptree("^^^^ %lld >= %lld\n", n->digest[1], n->right->digest[1]);
402f4b19
 	    ab = 1;
 	}
     }
     for (i=0; i<d; i++) ptree("        ");
     ptree("%08x(%02u)\n", n->digest[1]>>48, n - cs->data);
     if(n->left) {
52f52931
 	if(cmp(n->digest, n->size, n->left->digest, n->left->size) <= 0) {
402f4b19
 	    for (i=0; i<d; i++) ptree("        ");
52f52931
 	    ptree("vvvv %lld <= %lld\n", n->digest[1], n->left->digest[1]);
402f4b19
 	    ab = 1;
 	}
     }
     if(d){
 	if(!n->up) {
 	    ptree("no parent!\n");
 	    ab = 1;
 	} else {
 	    if(n->up->left != n && n->up->right != n) {
 		ptree("broken parent\n");
 		ab = 1;
 	    }
 	}
     } else {
 	if(n->up) {
 	    ptree("root with a parent!\n");
 	    ab = 1;
 	}
     }
     ab |= printtree(cs, n->left, d+1);
     return ab;
 }
 #else
6223810b
 #define printtree(a,b,c) (0)
402f4b19
 #endif
 
52f52931
 /* Looks up a node and splays it up to the root of the tree */
 static int splay(int64_t *md5, size_t len, struct cache_set *cs) {
2b70f02a
     struct node next = {{0, 0}, NULL, NULL, NULL, NULL, NULL, 0, 0}, *right = &next, *left = &next, *temp, *root = cs->root;
402f4b19
     int comp, found = 0;
5bf3c763
 
5b95022b
     if(!root)
5bf3c763
 	return 0;
5b95022b
 
     while(1) {
52f52931
 	comp = cmp(md5, len, root->digest, root->size);
a2c45a6b
 	if(comp < 0) {
5b95022b
 	    if(!root->left) break;
52f52931
 	    if(cmp(md5, len, root->left->digest, root->left->size) < 0) {
5b95022b
 		temp = root->left;
                 root->left = temp->right;
211fc872
 		if(temp->right) temp->right->up = root;
5b95022b
                 temp->right = root;
211fc872
 		root->up = temp;
5b95022b
                 root = temp;
5bf3c763
                 if(!root->left) break;
5b95022b
 	    }
             right->left = root;
211fc872
 	    root->up = right;
5b95022b
             right = root;
             root = root->left;
a2c45a6b
 	} else if(comp > 0) {
5b95022b
 	    if(!root->right) break;
52f52931
 	    if(cmp(md5, len, root->right->digest, root->right->size) > 0) {
5b95022b
 		temp = root->right;
                 root->right = temp->left;
211fc872
 		if(temp->left) temp->left->up = root;
5b95022b
                 temp->left = root;
211fc872
 		root->up = temp;
5b95022b
                 root = temp;
 		if(!root->right) break;
 	    }
 	    left->right = root;
211fc872
 	    root->up = left;
5b95022b
             left = root;
             root = root->right;
5bf3c763
 	} else {
402f4b19
 	    found = 1;
5bf3c763
 	    break;
 	}
5b95022b
     }
a2c45a6b
 
5b95022b
     left->right = root->left;
211fc872
     if(root->left) root->left->up = left;
5b95022b
     right->left = root->right;
211fc872
     if(root->right) root->right->up = right;
5b95022b
     root->left = next.right;
211fc872
     if(next.right) next.right->up = root;
5b95022b
     root->right = next.left;
211fc872
     if(next.left) next.left->up = root;
     root->up = NULL;
5b95022b
     cs->root = root;
402f4b19
     return found;
5b95022b
 }
 
52f52931
 
 /* Looks up an hash in the tree and maintains the replacement chain */
2b70f02a
 static inline int cacheset_lookup(struct cache_set *cs, unsigned char *md5, size_t size, uint32_t reclevel) {
a2c45a6b
     int64_t hash[2];
5b95022b
 
     memcpy(hash, md5, 16);
52f52931
     if(splay(hash, size, cs)) {
402f4b19
 	struct node *o = cs->root->prev, *p = cs->root, *q = cs->root->next;
 #ifdef PRINT_CHAINS
 	printf("promoting %02d\n", p - cs->data);
 	{
 	    struct node *x = cs->first;
 	    printf("before: ");
 	    while(x) {
 		printf("%02d,", x - cs->data);
 		x=x->next;
 	    }
 	    printf(" --- ");
 	    x=cs->last;
 	    while(x) {
 		printf("%02d,", x - cs->data);
 		x=x->prev;
 	    }
 	    printf("\n");
 	}
 #endif
     	if(q) {
 	    if(o)
 		o->next = q;
 	    else
 		cs->first = q;
 	    q->prev = o;
 	    cs->last->next = p;
 	    p->prev = cs->last;
 	    p->next = NULL;
 	    cs->last = p;
 	}
 #ifdef PRINT_CHAINS
 	{
 	    struct node *x = cs->first;
 	    printf("after : ");
 	    while(x) {
 		printf("%02d,", x - cs->data);
 		x=x->next;
 	    }
 	    printf(" --- ");
 	    x=cs->last;
 	    while(x) {
 		printf("%02d,", x - cs->data);
 		x=x->prev;
 	    }
 	    printf("\n");
 	}
 #endif
2b70f02a
 	if(reclevel >= p->minrec)
 	    return 1;
402f4b19
     }
     return 0;
5b95022b
 }
 
52f52931
 /* If the hash is present nothing happens.
    Otherwise a new node is created for the hash picking one from the begin of the chain.
    Used nodes are moved to the end of the chain */
2b70f02a
 static inline void cacheset_add(struct cache_set *cs, unsigned char *md5, size_t size, uint32_t reclevel) {
5b95022b
     struct node *newnode;
a2c45a6b
     int64_t hash[2];
5b95022b
 
     memcpy(hash, md5, 16);
2b70f02a
     if(splay(hash, size, cs)) {
 	if(cs->root->minrec > reclevel)
 	    cs->root->minrec = reclevel;
08605d2f
 	return; /* Already there */
2b70f02a
     }
08605d2f
 
402f4b19
     ptree("1:\n");
     if(printtree(cs, cs->root, 0)) {
37002fee
 	cli_errmsg("cacheset_add: inconsistent tree before choosing newnode, good luck\n");
 	return;
402f4b19
     }
 
08605d2f
     newnode = cs->first;
     while(newnode) {
402f4b19
     	if(!newnode->right && !newnode->left)
     	    break;
     	newnode = newnode->next;
08605d2f
     }
     if(!newnode) {
37002fee
 	cli_errmsg("cacheset_add: tree has got no end nodes\n");
 	return;
08605d2f
     }
     if(newnode->up) {
402f4b19
     	if(newnode->up->left == newnode)
     	    newnode->up->left = NULL;
     	else
     	    newnode->up->right = NULL;
08605d2f
     }
     if(newnode->prev)
402f4b19
     	newnode->prev->next = newnode->next;
08605d2f
     if(newnode->next)
402f4b19
     	newnode->next->prev = newnode->prev;
08605d2f
     if(cs->first == newnode)
402f4b19
     	cs->first = newnode->next;
08605d2f
 
     newnode->prev = cs->last;
     newnode->next = NULL;
     cs->last->next = newnode;
     cs->last = newnode;
402f4b19
 
     ptree("2:\n");
     if(printtree(cs, cs->root, 0)) {
37002fee
 	cli_errmsg("cacheset_add: inconsistent tree before adding newnode, good luck\n");
 	return;
402f4b19
     }
5b95022b
 
     if(!cs->root) {
 	newnode->left = NULL;
 	newnode->right = NULL;
     } else {
52f52931
 	if(cmp(hash, size, cs->root->digest, cs->root->size) < 0) {
211fc872
 	    newnode->left = cs->root->left;
 	    newnode->right = cs->root;
 	    cs->root->left = NULL;
 	} else {
 	    newnode->right = cs->root->right;
 	    newnode->left = cs->root;
 	    cs->root->right = NULL;
 	}
 	if(newnode->left) newnode->left->up = newnode;
 	if(newnode->right) newnode->right->up = newnode;
5b95022b
     }
     newnode->digest[0] = hash[0];
     newnode->digest[1] = hash[1];
211fc872
     newnode->up = NULL;
52f52931
     newnode->size = size;
2b70f02a
     newnode->minrec = reclevel;
5b95022b
     cs->root = newnode;
402f4b19
 
     ptree("3: %lld\n", hash[1]);
     if(printtree(cs, cs->root, 0)) {
37002fee
 	cli_errmsg("cacheset_add: inconsistent tree after adding newnode, good luck\n");
 	return;
402f4b19
     }
5b95022b
 }
 #endif /* USE_SPLAY */
64e6c97f
 
 
52f52931
 /* COMMON STUFF --------------------------------------------------------------------- */
64e6c97f
 
6223810b
 struct CACHE {
5b95022b
     struct cache_set cacheset;
fa9612e6
 #ifdef CL_THREAD_SAFE
5b95022b
     pthread_mutex_t mutex;
fa9612e6
 #endif
6223810b
 };
08605d2f
 
52f52931
 /* Allocates the trees for the engine cache */
6223810b
 int cli_cache_init(struct cl_engine *engine) {
     static struct CACHE *cache;
     unsigned int i, j;
5bf3c763
 
6223810b
     if(!engine) {
 	cli_errmsg("cli_cache_init: mpool malloc fail\n");
64e6c97f
 	return 1;
     }
6223810b
 
     if(!(cache = mpool_malloc(engine->mempool, sizeof(struct CACHE) * TREES))) {
 	cli_errmsg("cli_cache_init: mpool malloc fail\n");
64e6c97f
 	return 1;
     }
 
     for(i=0; i<TREES; i++) {
 	if(pthread_mutex_init(&cache[i].mutex, NULL)) {
6223810b
 	    cli_errmsg("cli_cache_init: mutex init fail\n");
 	    for(j=0; j<i; j++) cacheset_destroy(&cache[j].cacheset, engine->mempool);
 	    for(j=0; j<i; j++) pthread_mutex_destroy(&cache[j].mutex);
 	    mpool_free(engine->mempool, cache);
64e6c97f
 	    return 1;
 	}
6223810b
 	if(cacheset_init(&cache[i].cacheset, engine->mempool)) {
 	    for(j=0; j<i; j++) cacheset_destroy(&cache[j].cacheset, engine->mempool);
 	    for(j=0; j<=i; j++) pthread_mutex_destroy(&cache[j].mutex);
 	    mpool_free(engine->mempool, cache);
5b95022b
 	    return 1;
 	}
64e6c97f
     }
6223810b
     engine->cache = cache;
64e6c97f
     return 0;
 }
 
52f52931
 /* Frees the engine cache */
6223810b
 void cli_cache_destroy(struct cl_engine *engine) {
     static struct CACHE *cache;
     unsigned int i;
5b95022b
 
6223810b
     if(!engine || !(cache = engine->cache))
 	return;
 
     for(i=0; i<TREES; i++) {
 	cacheset_destroy(&cache[i].cacheset, engine->mempool);
 	pthread_mutex_destroy(&cache[i].mutex);
     }
     mpool_free(engine->mempool, cache);
 }
 
52f52931
 /* Looks up an hash in the proper tree */
2b70f02a
 static int cache_lookup_hash(unsigned char *md5, size_t len, struct CACHE *cache, uint32_t reclevel) {
64e6c97f
     unsigned int key = getkey(md5);
6223810b
     int ret = CL_VIRUS;
64e6c97f
     struct CACHE *c;
 
     c = &cache[key];
     if(pthread_mutex_lock(&c->mutex)) {
6223810b
 	cli_errmsg("cache_lookup_hash: cache_lookup_hash: mutex lock fail\n");
64e6c97f
 	return ret;
     }
5b95022b
 
2b70f02a
     ret = (cacheset_lookup(&c->cacheset, md5, len, reclevel)) ? CL_CLEAN : CL_VIRUS;
5b95022b
     pthread_mutex_unlock(&c->mutex);
52f52931
     /* if(ret == CL_CLEAN) cli_warnmsg("cached\n"); */
64e6c97f
     return ret;
 }
 
52f52931
 /* Adds an hash to the cache */
 void cache_add(unsigned char *md5, size_t size, cli_ctx *ctx) {
64e6c97f
     unsigned int key = getkey(md5);
2b70f02a
     uint32_t level;
64e6c97f
     struct CACHE *c;
 
4933f344
     if(!ctx || !ctx->engine || !ctx->engine->cache)
6223810b
        return;
64e6c97f
 
c5ed82ba
     level =  (*ctx->fmap && (*ctx->fmap)->dont_cache_flag) ? ctx->recursion : 0;
65cc5979
     if (ctx->found_possibly_unwanted && (level || !ctx->recursion))
 	return;
6223810b
     c = &ctx->engine->cache[key];
64e6c97f
     if(pthread_mutex_lock(&c->mutex)) {
6223810b
 	cli_errmsg("cli_add: mutex lock fail\n");
64e6c97f
 	return;
     }
5b95022b
 
6223810b
 #ifdef USE_LRUHASHCACHE
52f52931
     cacheset_add(&c->cacheset, md5, size, ctx->engine->mempool);
6223810b
 #else
 #ifdef USE_SPLAY
2b70f02a
     cacheset_add(&c->cacheset, md5, size, level);
6223810b
 #else
 #error #define USE_SPLAY or USE_LRUHASHCACHE
 #endif
 #endif
5b95022b
 
64e6c97f
     pthread_mutex_unlock(&c->mutex);
2b70f02a
     cli_dbgmsg("cache_add: %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x (level %u)\n", md5[0], md5[1], md5[2], md5[3], md5[4], md5[5], md5[6], md5[7], md5[8], md5[9], md5[10], md5[11], md5[12], md5[13], md5[14], md5[15], level);
64e6c97f
     return;
 }
 
52f52931
 /* Hashes a file onto the provided buffer and looks it up the cache.
    Returns CL_VIRUS if found, CL_CLEAN if not FIXME or an error */
64e6c97f
 int cache_check(unsigned char *hash, cli_ctx *ctx) {
f6798708
     fmap_t *map;
     size_t todo, at = 0;
64e6c97f
     cli_md5_ctx md5;
aef8d4ac
     int ret;
64e6c97f
 
6223810b
     if(!ctx || !ctx->engine || !ctx->engine->cache)
        return CL_VIRUS;
64e6c97f
 
f6798708
     map = *ctx->fmap;
     todo = map->len;
 
64e6c97f
     cli_md5_init(&md5);
     while(todo) {
 	void *buf;
 	size_t readme = todo < FILEBUFF ? todo : FILEBUFF;
 	if(!(buf = fmap_need_off_once(map, at, readme)))
 	    return CL_VIRUS;
 	todo -= readme;
 	at += readme;
 	cli_md5_update(&md5, buf, readme);
     }
     cli_md5_final(hash, &md5);
2b70f02a
     ret = cache_lookup_hash(hash, map->len, ctx->engine->cache, ctx->recursion);
aef8d4ac
     cli_dbgmsg("cache_check: %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x is %s\n", hash[0], hash[1], hash[2], hash[3], hash[4], hash[5], hash[6], hash[7], hash[8], hash[9], hash[10], hash[11], hash[12], hash[13], hash[14], hash[15], (ret == CL_VIRUS) ? "negative" : "positive");
     return ret;
64e6c97f
 }