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);
 }
 
59d02e32
 static void cacheset_remove(struct cache_set *map, unsigned char *md5, size_t size, mpool_t *mempool)
 {
     int ret;
     uint32_t pos;
     struct cache_key *newkey;
     ret = cacheset_lookup_internal(map, md5, size, &pos, 1);
     newkey = &map->data[pos];
     if (!ret || (newkey->size == CACHE_KEY_DELETED)) {
         /* already deleted */
         return;
     }
     /* remove from list */
     lru_remove(map, newkey);
     newkey->size = CACHE_KEY_DELETED;
     map->deleted++;
     map->elements--;
     if (map->deleted >= map->maxdeleted) {
         cacheset_rehash(map, mempool);
     }
 }
 
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;
f2e50833
     if ((n == NULL) || (cs == NULL) || (cs->data == 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) {
f2e50833
 	    printf("no parent, [node %02u]!\n", n - cs->data);
402f4b19
 	    ab = 1;
 	} else {
 	    if(n->up->left != n && n->up->right != n) {
f2e50833
 		printf("broken parent [node %02u, parent node %02u]\n", n - cs->data, n->up - cs->data);
402f4b19
 		ab = 1;
 	    }
 	}
     } else {
 	if(n->up) {
f2e50833
 	    printf("root with a parent, [node %02u]!\n", n - cs->data);
402f4b19
 	    ab = 1;
 	}
     }
     ab |= printtree(cs, n->left, d+1);
     return ab;
 }
 #else
6223810b
 #define printtree(a,b,c) (0)
402f4b19
 #endif
 
f2e50833
 /* For troubleshooting only; prints out one specific node */
 /* #define PRINT_NODE */
 #ifdef PRINT_NODE
 static void printnode(const char *prefix, struct cache_set *cs, struct node *n) {
     if (!prefix || !cs || !cs->data) {
         printf("bad args!\n");
         return;
     }
     if (!n) {
         printf("no node!\n");
         return;
     }
     printf("%s node [%02u]:", prefix, n - cs->data);
     printf(" size=%lu digest=%llx,%llx\n", (unsigned long)(n->size), n->digest[0], n->digest[1]);
     printf("\tleft=");
     if(n->left)
         printf("%02u ", n->left - cs->data);
     else
         printf("NULL ");
     printf("right=");
     if(n->right)
         printf("%02u ", n->right - cs->data);
     else
         printf("NULL ");
     printf("up=");
     if(n->up)
         printf("%02u ", n->up - cs->data);
     else
         printf("NULL ");
 
     printf("\tprev=");
     if(n->prev)
         printf("%02u ", n->prev - cs->data);
     else
         printf("NULL ");
     printf("next=");
     if(n->next)
         printf("%02u\n", n->next - cs->data);
     else
         printf("NULL\n");
 }
 #else
 #define printnode(a,b,c) (0)
 #endif
 
 /* #define PRINT_CHAINS */
 #ifdef PRINT_CHAINS
 /* For troubleshooting only, print the chain forwards and back */
 static inline void printchain(const char *prefix, struct cache_set *cs) {
     if (!cs || !cs->data) return;
     if (prefix) printf("%s: ", prefix);
     printf("chain by next: ");
     {
         unsigned int i = 0;
         struct node *x = cs->first;
         while(x) {
             printf("%02d,", x - cs->data);
             x=x->next;
             i++;
         }
         printf(" [count=%u]\nchain by prev: ", i);
         x=cs->last;
         i=0;
         while(x) {
             printf("%02d,", x - cs->data);
             x=x->prev;
             i++;
         }
         printf(" [count=%u]\n", i);
     }
 }
 #else
 #define printchain(a,b) (0)
 #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);
f2e50833
 	printchain("before", cs);
402f4b19
 #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
f2e50833
 	printchain("after", cs);
402f4b19
 #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) {
b89dc676
         if(!newnode->right && !newnode->left)
             break;
f2e50833
         if(newnode->next) {
             if(newnode == newnode->next) {
                 cli_errmsg("cacheset_add: cache chain in a bad state\n");
                 return;
             }
             newnode = newnode->next;
         }
         else {
 	    cli_warnmsg("cacheset_add: end of chain reached\n");
 	    return;
b89dc676
         }
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
     }
f2e50833
     printnode("newnode", cs, newnode);
5b95022b
 }
f2e50833
 
59d02e32
 /* If the hash is not present nothing happens other than splaying the tree.
    Otherwise the identified node is removed from the tree and then placed back at 
    the front of the chain. */
 static inline void cacheset_remove(struct cache_set *cs, unsigned char *md5, size_t size) {
     struct node *targetnode;
     struct node *reattachnode;
     int64_t hash[2];
 
     memcpy(hash, md5, 16);
     if(splay(hash, size, cs) != 1) {
1f765cf9
 	cli_dbgmsg("cacheset_remove: node not found in tree\n");
59d02e32
 	return; /* No op */
     }
 
     ptree("cacheset_remove: node found and splayed to root\n");
     targetnode = cs->root;
f2e50833
     printnode("targetnode", cs, targetnode);
59d02e32
 
     /* First fix the tree */
     if(targetnode->left == NULL) {
         /* At left edge so prune */
         cs->root = targetnode->right;
         if(cs->root)
             cs->root->up = NULL;
     }
     else {
         /* new root will come from leftside tree */
         cs->root = targetnode->left;
         cs->root->up = NULL;
         /* splay tree, expecting not found, bringing rightmost member to root */
         splay(hash, size, cs);
f2e50833
 
59d02e32
         if (targetnode->right) {
             /* reattach right tree to clean right-side attach point */
             reattachnode = cs->root;
             while (reattachnode->right) 
                 reattachnode = reattachnode->right; /* shouldn't happen, but safer in case of dupe */
             reattachnode->right = targetnode->right;
f2e50833
             targetnode->right->up = reattachnode;
59d02e32
         }
     }
f2e50833
     targetnode->size = (size_t)0;
     targetnode->digest[0] = 0;
     targetnode->digest[1] = 0;
59d02e32
     targetnode->up = NULL;
     targetnode->left = NULL;
     targetnode->right = NULL;
 
     /* Tree is fixed, so now fix chain around targetnode */
     if(targetnode->prev) 
         targetnode->prev->next = targetnode->next;
     if(targetnode->next) 
         targetnode->next->prev = targetnode->prev;
     if(cs->last == targetnode)
         cs->last = targetnode->prev;
 
b89dc676
     /* Put targetnode at front of chain, if not there already */
     if(cs->first != targetnode) {
         targetnode->next = cs->first;
         if(cs->first)
             cs->first->prev = targetnode;
         cs->first = targetnode;
59d02e32
     }
     targetnode->prev = NULL;
f2e50833
 
     printnode("root", cs, cs->root);
     printnode("first", cs, cs->first);
     printnode("last", cs, cs->last);
 
     printchain("remove (after)", cs);
59d02e32
 }
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) {
a7cf187a
     struct CACHE *cache;
6223810b
     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) {
a7cf187a
     struct CACHE *cache;
6223810b
     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
 
f2e50833
     /* cli_warnmsg("cache_lookup_hash: key is %u\n", key); */
 
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;
a3dcc429
     if (SCAN_ALL && (ctx->num_viruses > 0)) {
 	cli_dbgmsg("cache_add: alert found within same topfile, skipping cache\n");
 	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
 
f2e50833
     /* cli_warnmsg("cache_add: key is %u\n", key); */
 
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;
 }
 
59d02e32
 /* Removes a hash from the cache */
f129bc1f
 void cache_remove(unsigned char *md5, size_t size, const struct cl_engine *engine) {
59d02e32
     unsigned int key = getkey(md5);
     struct CACHE *c;
 
f703e46a
     if(!engine || !engine->cache)
59d02e32
        return;
 
f2e50833
     /* cli_warnmsg("cache_remove: key is %u\n", key); */
 
f129bc1f
     c = &engine->cache[key];
59d02e32
     if(pthread_mutex_lock(&c->mutex)) {
 	cli_errmsg("cli_add: mutex lock fail\n");
 	return;
     }
 
 #ifdef USE_LRUHASHCACHE
f129bc1f
     cacheset_remove(&c->cacheset, md5, size, engine->mempool);
59d02e32
 #else
 #ifdef USE_SPLAY
     cacheset_remove(&c->cacheset, md5, size);
 #else
 #error #define USE_SPLAY or USE_LRUHASHCACHE
 #endif
 #endif
 
     pthread_mutex_unlock(&c->mutex);
     cli_dbgmsg("cache_remove: %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\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]);
     return;
 }
 
52f52931
 /* Hashes a file onto the provided buffer and looks it up the cache.
84c35b01
    Returns CL_VIRUS if found, CL_CLEAN if not FIXME or a recoverable error,
    and returns CL_EREAD if unrecoverable */
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) {
f304dc68
 	const void *buf;
64e6c97f
 	size_t readme = todo < FILEBUFF ? todo : FILEBUFF;
 	if(!(buf = fmap_need_off_once(map, at, readme)))
ed98fae7
 	    return CL_EREAD;
64e6c97f
 	todo -= readme;
 	at += readme;
84c35b01
 	if (cli_md5_update(&md5, buf, readme)) {
 	    cli_errmsg("cache_check: error reading while generating hash!\n");
 	    return CL_EREAD;
 	}
64e6c97f
     }
     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
 }