1/*	$NetBSD$	*/
2
3/*++
4/* NAME
5/*	ctable 3
6/* SUMMARY
7/*	cache manager
8/* SYNOPSIS
9/*	#include <ctable.h>
10/*
11/*	CTABLE	*ctable_create(limit, create, delete, context)
12/*	int	limit;
13/*	void	*(*create)(const char *key, void *context);
14/*	void	(*delete)(void *value, void *context);
15/*	void	*context;
16/*
17/*	const void *ctable_locate(cache, key)
18/*	CTABLE	*cache;
19/*	const char *key;
20/*
21/*	void	ctable_free(cache)
22/*	CTABLE	*cache;
23/*
24/*	void	ctable_walk(cache, action)
25/*	CTABLE	*cache;
26/*	void	(*action)(const char *key, const void *value);
27/* DESCRIPTION
28/*	This module maintains multiple caches. Cache items are purged
29/*	automatically when the number of items exceeds a configurable
30/*	limit. Caches never shrink. Each cache entry consists of a
31/*	string-valued lookup key and a generic data pointer value.
32/*
33/*	ctable_create() creates a cache with the specified size limit, and
34/*	returns a pointer to the result. The create and delete arguments
35/*	specify pointers to call-back functions that create a value, given
36/*	a key, and delete a given value, respectively. The context argument
37/*	is passed on to the call-back routines.
38/*
39/*	ctable_locate() looks up or generates the value that corresponds to
40/*	the specified key, and returns that value.
41/*
42/*	ctable_free() destroys the specified cache, including its contents.
43/*
44/*	ctable_walk() iterates over all elements in the cache, and invokes
45/*	the action function for each cache element with the corresponding
46/*	key and value as arguments. This function is useful mainly for
47/*	cache performance debugging.
48/* DIAGNOSTICS
49/*	Fatal errors: out of memory. Panic: interface violation.
50/* LICENSE
51/* .ad
52/* .fi
53/*	The Secure Mailer license must be distributed with this software.
54/* AUTHOR(S)
55/*	Wietse Venema
56/*	IBM T.J. Watson Research
57/*	P.O. Box 704
58/*	Yorktown Heights, NY 10598, USA
59/*--*/
60
61/* System library. */
62
63#include <sys_defs.h>
64#include <stdlib.h>
65#include <stddef.h>
66
67/* Utility library. */
68
69#include <msg.h>
70#include <mymalloc.h>
71#include <ring.h>
72#include <htable.h>
73#include <ctable.h>
74
75 /*
76  * Cache entries are kept in most-recently used order. We use a hash table
77  * to quickly locate cache entries.
78  */
79#define	CTABLE_ENTRY struct ctable_entry
80
81struct ctable_entry {
82    RING    ring;			/* MRU linkage */
83    const char *key;			/* lookup key */
84    void   *value;			/* corresponding value */
85};
86
87#define RING_TO_CTABLE_ENTRY(ring_ptr)	\
88	RING_TO_APPL(ring_ptr, CTABLE_ENTRY, ring)
89#define RING_PTR_OF(x)	(&((x)->ring))
90
91struct ctable {
92    HTABLE *table;			/* table with key, ctable_entry pairs */
93    unsigned limit;			/* max nr of entries */
94    unsigned used;			/* current nr of entries */
95    CTABLE_CREATE_FN create;		/* constructor */
96    CTABLE_DELETE_FN delete;		/* destructor */
97    RING    ring;			/* MRU linkage */
98    void   *context;			/* application context */
99};
100
101#define CTABLE_MIN_SIZE	5
102
103/* ctable_create - create empty cache */
104
105CTABLE *ctable_create(int limit, CTABLE_CREATE_FN create,
106		              CTABLE_DELETE_FN delete, void *context)
107{
108    CTABLE *cache = (CTABLE *) mymalloc(sizeof(CTABLE));
109    const char *myname = "ctable_create";
110
111    if (limit < 1)
112	msg_panic("%s: bad cache limit: %d", myname, limit);
113
114    cache->table = htable_create(limit);
115    cache->limit = (limit < CTABLE_MIN_SIZE ? CTABLE_MIN_SIZE : limit);
116    cache->used = 0;
117    cache->create = create;
118    cache->delete = delete;
119    ring_init(RING_PTR_OF(cache));
120    cache->context = context;
121    return (cache);
122}
123
124/* ctable_locate - look up or create cache item */
125
126const void *ctable_locate(CTABLE *cache, const char *key)
127{
128    const char *myname = "ctable_locate";
129    CTABLE_ENTRY *entry;
130
131    /*
132     * If the entry is not in the cache, make sure there is room for a new
133     * entry and install it at the front of the MRU chain. Otherwise, move
134     * the entry to the front of the MRU chain if it is not already there.
135     * All this means that the cache never shrinks.
136     */
137    if ((entry = (CTABLE_ENTRY *) htable_find(cache->table, key)) == 0) {
138	if (cache->used >= cache->limit) {
139	    entry = RING_TO_CTABLE_ENTRY(ring_pred(RING_PTR_OF(cache)));
140	    if (msg_verbose)
141		msg_info("%s: purge entry key %s", myname, entry->key);
142	    ring_detach(RING_PTR_OF(entry));
143	    cache->delete(entry->value, cache->context);
144	    htable_delete(cache->table, entry->key, (void (*) (char *)) 0);
145	} else {
146	    entry = (CTABLE_ENTRY *) mymalloc(sizeof(CTABLE_ENTRY));
147	    cache->used++;
148	}
149	entry->value = cache->create(key, cache->context);
150	entry->key = htable_enter(cache->table, key, (char *) entry)->key;
151	ring_append(RING_PTR_OF(cache), RING_PTR_OF(entry));
152	if (msg_verbose)
153	    msg_info("%s: install entry key %s", myname, entry->key);
154    } else if (entry == RING_TO_CTABLE_ENTRY(ring_succ(RING_PTR_OF(cache)))) {
155	if (msg_verbose)
156	    msg_info("%s: leave existing entry key %s", myname, entry->key);
157    } else {
158	ring_detach(RING_PTR_OF(entry));
159	ring_append(RING_PTR_OF(cache), RING_PTR_OF(entry));
160	if (msg_verbose)
161	    msg_info("%s: move existing entry key %s", myname, entry->key);
162    }
163    return (entry->value);
164}
165
166static CTABLE *ctable_free_cache;
167
168/* ctable_free_callback - callback function */
169
170static void ctable_free_callback(char *ptr)
171{
172    CTABLE_ENTRY *entry = (CTABLE_ENTRY *) ptr;
173
174    ctable_free_cache->delete(entry->value, ctable_free_cache->context);
175    myfree((char *) entry);
176}
177
178/* ctable_free - destroy cache and contents */
179
180void    ctable_free(CTABLE *cache)
181{
182    CTABLE *saved_cache = ctable_free_cache;
183
184    /*
185     * XXX the hash table does not pass application context so we have to
186     * store it in a global variable.
187     */
188    ctable_free_cache = cache;
189    htable_free(cache->table, ctable_free_callback);
190    myfree((char *) cache);
191    ctable_free_cache = saved_cache;
192}
193
194/* ctable_walk - iterate over all cache entries */
195
196void    ctable_walk(CTABLE *cache, void (*action) (const char *, const void *))
197{
198    RING   *entry = RING_PTR_OF(cache);
199
200    /* Walking down the MRU chain is less work than using ht_walk(). */
201
202    while ((entry = ring_succ(entry)) != RING_PTR_OF(cache))
203	action((RING_TO_CTABLE_ENTRY(entry)->key),
204	       (RING_TO_CTABLE_ENTRY(entry)->value));
205}
206
207#ifdef TEST
208
209 /*
210  * Proof-of-concept test program. Read keys from stdin, ask for values not
211  * in cache.
212  */
213#include <unistd.h>
214#include <vstream.h>
215#include <vstring.h>
216#include <vstring_vstream.h>
217#include <msg_vstream.h>
218
219#define STR(x)	vstring_str(x)
220
221static void *ask(const char *key, void *context)
222{
223    VSTRING *data_buf = (VSTRING *) context;
224
225    vstream_printf("ask: %s = ", key);
226    vstream_fflush(VSTREAM_OUT);
227    if (vstring_get_nonl(data_buf, VSTREAM_IN) == VSTREAM_EOF)
228	vstream_longjmp(VSTREAM_IN, 1);
229    if (!isatty(0)) {
230	vstream_printf("%s\n", STR(data_buf));
231	vstream_fflush(VSTREAM_OUT);
232    }
233    return (mystrdup(STR(data_buf)));
234}
235
236static void drop(void *data, void *unused_context)
237{
238    myfree(data);
239}
240
241int     main(int unused_argc, char **argv)
242{
243    VSTRING *key_buf;
244    VSTRING *data_buf;
245    CTABLE *cache;
246    const char *value;
247
248    msg_vstream_init(argv[0], VSTREAM_ERR);
249    key_buf = vstring_alloc(100);
250    data_buf = vstring_alloc(100);
251    cache = ctable_create(1, ask, drop, (void *) data_buf);
252    msg_verbose = 1;
253    vstream_control(VSTREAM_IN, VSTREAM_CTL_EXCEPT, VSTREAM_CTL_END);
254
255    if (vstream_setjmp(VSTREAM_IN) == 0) {
256	for (;;) {
257	    vstream_printf("key = ");
258	    vstream_fflush(VSTREAM_OUT);
259	    if (vstring_get_nonl(key_buf, VSTREAM_IN) == VSTREAM_EOF)
260		vstream_longjmp(VSTREAM_IN, 1);
261	    if (!isatty(0)) {
262		vstream_printf("%s\n", STR(key_buf));
263		vstream_fflush(VSTREAM_OUT);
264	    }
265	    value = ctable_locate(cache, STR(key_buf));
266	    vstream_printf("result: %s\n", value);
267	}
268    }
269    ctable_free(cache);
270    vstring_free(key_buf);
271    vstring_free(data_buf);
272    return (0);
273}
274
275#endif
276