1 /*	$NetBSD: dict_cache.c,v 1.1.1.2 2013/01/02 18:59:12 tron Exp $	*/
2 
3 /*++
4 /* NAME
5 /*	dict_cache 3
6 /* SUMMARY
7 /*	External cache manager
8 /* SYNOPSIS
9 /*	#include <dict_cache.h>
10 /*
11 /*	DICT_CACHE *dict_cache_open(dbname, open_flags, dict_flags)
12 /*	const char *dbname;
13 /*	int	open_flags;
14 /*	int	dict_flags;
15 /*
16 /*	void	dict_cache_close(cache)
17 /*	DICT_CACHE *cache;
18 /*
19 /*	const char *dict_cache_lookup(cache, cache_key)
20 /*	DICT_CACHE *cache;
21 /*	const char *cache_key;
22 /*
23 /*	int	dict_cache_update(cache, cache_key, cache_val)
24 /*	DICT_CACHE *cache;
25 /*	const char *cache_key;
26 /*	const char *cache_val;
27 /*
28 /*	int	dict_cache_delete(cache, cache_key)
29 /*	DICT_CACHE *cache;
30 /*	const char *cache_key;
31 /*
32 /*	int	dict_cache_sequence(cache, first_next, cache_key, cache_val)
33 /*	DICT_CACHE *cache;
34 /*	int	first_next;
35 /*	const char **cache_key;
36 /*	const char **cache_val;
37 /* AUXILIARY FUNCTIONS
38 /*	void	dict_cache_control(cache, name, value, ...)
39 /*	DICT_CACHE *cache;
40 /*	int	name;
41 /*
42 /*	typedef int (*DICT_CACHE_VALIDATOR_FN) (const char *cache_key,
43 /*		const char *cache_val, char *context);
44 /*
45 /*	const char *dict_cache_name(cache)
46 /*	DICT_CACHE	*cache;
47 /* DESCRIPTION
48 /*	This module maintains external cache files with support
49 /*	for expiration. The underlying table must implement the
50 /*	"lookup", "update", "delete" and "sequence" operations.
51 /*
52 /*	Although this API is similar to the one documented in
53 /*	dict_open(3), there are subtle differences in the interaction
54 /*	between the iterators that access all cache elements, and
55 /*	other operations that access individual cache elements.
56 /*
57 /*	In particular, when a "sequence" or "cleanup" operation is
58 /*	in progress the cache intercepts requests to delete the
59 /*	"current" entry, as this would cause some databases to
60 /*	mis-behave. Instead, the cache implements a "delete behind"
61 /*	strategy, and deletes such an entry after the "sequence"
62 /*	or "cleanup" operation moves on to the next cache element.
63 /*	The "delete behind" strategy also affects the cache lookup
64 /*	and update operations as detailed below.
65 /*
66 /*	dict_cache_open() is a wrapper around the dict_open()
67 /*	function.  It opens the specified cache and returns a handle
68 /*	that must be used for subsequent access. This function does
69 /*	not return in case of error.
70 /*
71 /*	dict_cache_close() closes the specified cache and releases
72 /*	memory that was allocated by dict_cache_open(), and terminates
73 /*	any thread that was started with dict_cache_control().
74 /*
75 /*	dict_cache_lookup() looks up the specified cache entry.
76 /*	The result value is a null pointer when the cache entry was
77 /*	not found, or when the entry is scheduled for "delete
78 /*	behind".
79 /*
80 /*	dict_cache_update() updates the specified cache entry. If
81 /*	the entry is scheduled for "delete behind", the delete
82 /*	operation is canceled (because of this, the cache must be
83 /*	opened with DICT_FLAG_DUP_REPLACE). This function does not
84 /*	return in case of error.
85 /*
86 /*	dict_cache_delete() removes the specified cache entry.  If
87 /*	this is the "current" entry of a "sequence" operation, the
88 /*	entry is scheduled for "delete behind". The result value
89 /*	is zero when the entry was found.
90 /*
91 /*	dict_cache_sequence() iterates over the specified cache and
92 /*	returns each entry in an implementation-defined order.  The
93 /*	result value is zero when a cache entry was found.
94 /*
95 /*	Important: programs must not use both dict_cache_sequence()
96 /*	and the built-in cache cleanup feature.
97 /*
98 /*	dict_cache_control() provides control over the built-in
99 /*	cache cleanup feature and logging. The arguments are a list
100 /*	of (name, value) pairs, terminated with DICT_CACHE_CTL_END.
101 /*	The following lists the names and the types of the corresponding
102 /*	value arguments.
103 /* .IP "DICT_CACHE_FLAGS (int flags)"
104 /*	The arguments to this command are the bit-wise OR of zero
105 /*	or more of the following:
106 /* .RS
107 /* .IP DICT_CACHE_FLAG_VERBOSE
108 /*	Enable verbose logging of cache activity.
109 /* .IP DICT_CACHE_FLAG_EXP_SUMMARY
110 /*	Log cache statistics after each cache cleanup run.
111 /* .RE
112 /* .IP "DICT_CACHE_CTL_INTERVAL (int interval)"
113 /*	The interval between cache cleanup runs.  Specify a null
114 /*	validator or interval to stop cache cleanup.
115 /* .IP "DICT_CACHE_CTL_VALIDATOR (DICT_CACHE_VALIDATOR_FN validator)"
116 /*	An application call-back routine that returns non-zero when
117 /*	a cache entry should be kept. The call-back function should
118 /*	not make changes to the cache. Specify a null validator or
119 /*	interval to stop cache cleanup.
120 /* .IP "DICT_CACHE_CTL_CONTEXT (char *context)"
121 /*	Application context that is passed to the validator function.
122 /* .RE
123 /* .PP
124 /*	dict_cache_name() returns the name of the specified cache.
125 /*
126 /*	Arguments:
127 /* .IP "dbname, open_flags, dict_flags"
128 /*	These are passed unchanged to dict_open(). The cache must
129 /*	be opened with DICT_FLAG_DUP_REPLACE.
130 /* .IP cache
131 /*	Cache handle created with dict_cache_open().
132 /* .IP cache_key
133 /*	Cache lookup key.
134 /* .IP cache_val
135 /*	Information that is stored under a cache lookup key.
136 /* .IP first_next
137 /*	One of DICT_SEQ_FUN_FIRST (first cache element) or
138 /*	DICT_SEQ_FUN_NEXT (next cache element).
139 /* .sp
140 /*	Note: there is no "stop" request. To ensure that the "delete
141 /*	behind" strategy does not interfere with database access,
142 /*	allow dict_cache_sequence() to run to completion.
143 /* .IP table
144 /*	A bare dictonary handle.
145 /* DIAGNOSTICS
146 /*	When a request is satisfied, the lookup routine returns
147 /*	non-null, and the update, delete and sequence routines
148 /*	return zero.  The cache->error value is zero when a request
149 /*	could not be satisfied because an item did not exist (delete,
150 /*	sequence) or if it could not be updated. The cache->error
151 /*	value is non-zero only when a request could not be satisfied,
152 /*	and the cause was a database error.
153 /*
154 /*	Cache access errors are logged with a warning message. To
155 /*	avoid spamming the log, each type of operation logs no more
156 /*	than one cache access error per second, per cache. Specify
157 /*	the DICT_CACHE_FLAG_VERBOSE flag (see above) to log all
158 /*	warnings.
159 /* BUGS
160 /*	There should be a way to suspend automatic program suicide
161 /*	until a cache cleanup run is completed. Some entries may
162 /*	never be removed when the process max_idle time is less
163 /*	than the time needed to make a full pass over the cache.
164 /* LICENSE
165 /* .ad
166 /* .fi
167 /*	The Secure Mailer license must be distributed with this software.
168 /* HISTORY
169 /* .ad
170 /* .fi
171 /*	A predecessor of this code was written first for the Postfix
172 /*	tlsmgr(8) daemon.
173 /* AUTHOR(S)
174 /*	Wietse Venema
175 /*	IBM T.J. Watson Research
176 /*	P.O. Box 704
177 /*	Yorktown Heights, NY 10598, USA
178 /*--*/
179 
180 /* System library. */
181 
182 #include <sys_defs.h>
183 #include <string.h>
184 #include <stdlib.h>
185 
186 /* Utility library. */
187 
188 #include <msg.h>
189 #include <dict.h>
190 #include <mymalloc.h>
191 #include <events.h>
192 #include <dict_cache.h>
193 
194 /* Application-specific. */
195 
196  /*
197   * XXX Deleting entries while enumerating a map can he tricky. Some map
198   * types have a concept of cursor and support a "delete the current element"
199   * operation. Some map types without cursors don't behave well when the
200   * current first/next entry is deleted (example: with Berkeley DB < 2, the
201   * "next" operation produces garbage). To avoid trouble, we delete an entry
202   * after advancing the current first/next position beyond it; we use the
203   * same strategy with application requests to delete the current entry.
204   */
205 
206  /*
207   * Opaque data structure. Use dict_cache_name() to access the name of the
208   * underlying database.
209   */
210 struct DICT_CACHE {
211     char   *name;			/* full name including proxy: */
212     int     cache_flags;		/* see below */
213     int     user_flags;			/* logging */
214     DICT   *db;				/* database handle */
215     int     error;			/* last operation only */
216 
217     /* Delete-behind support. */
218     char   *saved_curr_key;		/* "current" cache lookup key */
219     char   *saved_curr_val;		/* "current" cache lookup result */
220 
221     /* Cleanup support. */
222     int     exp_interval;		/* time between cleanup runs */
223     DICT_CACHE_VALIDATOR_FN exp_validator;	/* expiration call-back */
224     char   *exp_context;		/* call-back context */
225     int     retained;			/* entries retained in cleanup run */
226     int     dropped;			/* entries removed in cleanup run */
227 
228     /* Rate-limited logging support. */
229     int     log_delay;
230     time_t  upd_log_stamp;		/* last update warning */
231     time_t  get_log_stamp;		/* last lookup warning */
232     time_t  del_log_stamp;		/* last delete warning */
233     time_t  seq_log_stamp;		/* last sequence warning */
234 };
235 
236 #define DC_FLAG_DEL_SAVED_CURRENT_KEY	(1<<0)	/* delete-behind is scheduled */
237 
238  /*
239   * Don't log cache access errors more than once per second.
240   */
241 #define DC_DEF_LOG_DELAY	1
242 
243  /*
244   * Macros to make obscure code more readable.
245   */
246 #define DC_SCHEDULE_FOR_DELETE_BEHIND(cp) \
247     ((cp)->cache_flags |= DC_FLAG_DEL_SAVED_CURRENT_KEY)
248 
249 #define DC_MATCH_SAVED_CURRENT_KEY(cp, cache_key) \
250     ((cp)->saved_curr_key && strcmp((cp)->saved_curr_key, (cache_key)) == 0)
251 
252 #define DC_IS_SCHEDULED_FOR_DELETE_BEHIND(cp) \
253     (/* NOT: (cp)->saved_curr_key && */ \
254 	((cp)->cache_flags & DC_FLAG_DEL_SAVED_CURRENT_KEY) != 0)
255 
256 #define DC_CANCEL_DELETE_BEHIND(cp) \
257     ((cp)->cache_flags &= ~DC_FLAG_DEL_SAVED_CURRENT_KEY)
258 
259  /*
260   * Special key to store the time of the last cache cleanup run completion.
261   */
262 #define DC_LAST_CACHE_CLEANUP_COMPLETED "_LAST_CACHE_CLEANUP_COMPLETED_"
263 
264 /* dict_cache_lookup - load entry from cache */
265 
266 const char *dict_cache_lookup(DICT_CACHE *cp, const char *cache_key)
267 {
268     const char *myname = "dict_cache_lookup";
269     const char *cache_val;
270     DICT   *db = cp->db;
271 
272     /*
273      * Search for the cache entry. Don't return an entry that is scheduled
274      * for delete-behind.
275      */
276     if (DC_IS_SCHEDULED_FOR_DELETE_BEHIND(cp)
277 	&& DC_MATCH_SAVED_CURRENT_KEY(cp, cache_key)) {
278 	if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE)
279 	    msg_info("%s: key=%s (pretend not found  - scheduled for deletion)",
280 		     myname, cache_key);
281 	DICT_ERR_VAL_RETURN(cp, DICT_ERR_NONE, (char *) 0);
282     } else {
283 	cache_val = dict_get(db, cache_key);
284 	if (cache_val == 0 && db->error != 0)
285 	    msg_rate_delay(&cp->get_log_stamp, cp->log_delay, msg_warn,
286 			   "%s: cache lookup for '%s' failed due to error",
287 			   cp->name, cache_key);
288 	if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE)
289 	    msg_info("%s: key=%s value=%s", myname, cache_key,
290 		     cache_val ? cache_val : db->error ?
291 		     "error" : "(not found)");
292 	DICT_ERR_VAL_RETURN(cp, db->error, cache_val);
293     }
294 }
295 
296 /* dict_cache_update - save entry to cache */
297 
298 int     dict_cache_update(DICT_CACHE *cp, const char *cache_key,
299 			          const char *cache_val)
300 {
301     const char *myname = "dict_cache_update";
302     DICT   *db = cp->db;
303     int     put_res;
304 
305     /*
306      * Store the cache entry and cancel the delete-behind operation.
307      */
308     if (DC_IS_SCHEDULED_FOR_DELETE_BEHIND(cp)
309 	&& DC_MATCH_SAVED_CURRENT_KEY(cp, cache_key)) {
310 	if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE)
311 	    msg_info("%s: cancel delete-behind for key=%s", myname, cache_key);
312 	DC_CANCEL_DELETE_BEHIND(cp);
313     }
314     if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE)
315 	msg_info("%s: key=%s value=%s", myname, cache_key, cache_val);
316     put_res = dict_put(db, cache_key, cache_val);
317     if (put_res != 0)
318 	msg_rate_delay(&cp->upd_log_stamp, cp->log_delay, msg_warn,
319 		  "%s: could not update entry for %s", cp->name, cache_key);
320     DICT_ERR_VAL_RETURN(cp, db->error, put_res);
321 }
322 
323 /* dict_cache_delete - delete entry from cache */
324 
325 int     dict_cache_delete(DICT_CACHE *cp, const char *cache_key)
326 {
327     const char *myname = "dict_cache_delete";
328     int     del_res;
329     DICT   *db = cp->db;
330 
331     /*
332      * Delete the entry, unless we would delete the current first/next entry.
333      * In that case, schedule the "current" entry for delete-behind to avoid
334      * mis-behavior by some databases.
335      */
336     if (DC_MATCH_SAVED_CURRENT_KEY(cp, cache_key)) {
337 	DC_SCHEDULE_FOR_DELETE_BEHIND(cp);
338 	if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE)
339 	    msg_info("%s: key=%s (current entry - schedule for delete-behind)",
340 		     myname, cache_key);
341 	DICT_ERR_VAL_RETURN(cp, DICT_ERR_NONE, DICT_STAT_SUCCESS);
342     } else {
343 	del_res = dict_del(db, cache_key);
344 	if (del_res != 0)
345 	    msg_rate_delay(&cp->del_log_stamp, cp->log_delay, msg_warn,
346 		  "%s: could not delete entry for %s", cp->name, cache_key);
347 	if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE)
348 	    msg_info("%s: key=%s (%s)", myname, cache_key,
349 		     del_res == 0 ? "found" :
350 		     db->error ? "error" : "not found");
351 	DICT_ERR_VAL_RETURN(cp, db->error, del_res);
352     }
353 }
354 
355 /* dict_cache_sequence - look up the first/next cache entry */
356 
357 int     dict_cache_sequence(DICT_CACHE *cp, int first_next,
358 			            const char **cache_key,
359 			            const char **cache_val)
360 {
361     const char *myname = "dict_cache_sequence";
362     int     seq_res;
363     const char *raw_cache_key;
364     const char *raw_cache_val;
365     char   *previous_curr_key;
366     char   *previous_curr_val;
367     DICT   *db = cp->db;
368 
369     /*
370      * Find the first or next database entry. Hide the record with the cache
371      * cleanup completion time stamp.
372      */
373     seq_res = dict_seq(db, first_next, &raw_cache_key, &raw_cache_val);
374     if (seq_res == 0
375 	&& strcmp(raw_cache_key, DC_LAST_CACHE_CLEANUP_COMPLETED) == 0)
376 	seq_res =
377 	    dict_seq(db, DICT_SEQ_FUN_NEXT, &raw_cache_key, &raw_cache_val);
378     if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE)
379 	msg_info("%s: key=%s value=%s", myname,
380 		 seq_res == 0 ? raw_cache_key : db->error ?
381 		 "(error)" : "(not found)",
382 		 seq_res == 0 ? raw_cache_val : db->error ?
383 		 "(error)" : "(not found)");
384     if (db->error)
385 	msg_rate_delay(&cp->seq_log_stamp, cp->log_delay, msg_warn,
386 		       "%s: sequence error", cp->name);
387 
388     /*
389      * Save the current cache_key and cache_val before they are clobbered by
390      * our own delete operation below. This also prevents surprises when the
391      * application accesses the database after this function returns.
392      *
393      * We also use the saved cache_key to protect the current entry against
394      * application delete requests.
395      */
396     previous_curr_key = cp->saved_curr_key;
397     previous_curr_val = cp->saved_curr_val;
398     if (seq_res == 0) {
399 	cp->saved_curr_key = mystrdup(raw_cache_key);
400 	cp->saved_curr_val = mystrdup(raw_cache_val);
401     } else {
402 	cp->saved_curr_key = 0;
403 	cp->saved_curr_val = 0;
404     }
405 
406     /*
407      * Delete behind.
408      */
409     if (db->error == 0 && DC_IS_SCHEDULED_FOR_DELETE_BEHIND(cp)) {
410 	DC_CANCEL_DELETE_BEHIND(cp);
411 	if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE)
412 	    msg_info("%s: delete-behind key=%s value=%s",
413 		     myname, previous_curr_key, previous_curr_val);
414 	if (dict_del(db, previous_curr_key) != 0)
415 	    msg_rate_delay(&cp->del_log_stamp, cp->log_delay, msg_warn,
416 			   "%s: could not delete entry for %s",
417 			   cp->name, previous_curr_key);
418     }
419 
420     /*
421      * Clean up previous iteration key and value.
422      */
423     if (previous_curr_key)
424 	myfree(previous_curr_key);
425     if (previous_curr_val)
426 	myfree(previous_curr_val);
427 
428     /*
429      * Return the result.
430      */
431     *cache_key = (cp)->saved_curr_key;
432     *cache_val = (cp)->saved_curr_val;
433     DICT_ERR_VAL_RETURN(cp, db->error, seq_res);
434 }
435 
436 /* dict_cache_delete_behind_reset - reset "delete behind" state */
437 
438 static void dict_cache_delete_behind_reset(DICT_CACHE *cp)
439 {
440 #define FREE_AND_WIPE(s) do { if (s) { myfree(s); (s) = 0; } } while (0)
441 
442     DC_CANCEL_DELETE_BEHIND(cp);
443     FREE_AND_WIPE(cp->saved_curr_key);
444     FREE_AND_WIPE(cp->saved_curr_val);
445 }
446 
447 /* dict_cache_clean_stat_log_reset - log and reset cache cleanup statistics */
448 
449 static void dict_cache_clean_stat_log_reset(DICT_CACHE *cp,
450 					            const char *full_partial)
451 {
452     if (cp->user_flags & DICT_CACHE_FLAG_STATISTICS)
453 	msg_info("cache %s %s cleanup: retained=%d dropped=%d entries",
454 		 cp->name, full_partial, cp->retained, cp->dropped);
455     cp->retained = cp->dropped = 0;
456 }
457 
458 /* dict_cache_clean_event - examine one cache entry */
459 
460 static void dict_cache_clean_event(int unused_event, char *cache_context)
461 {
462     const char *myname = "dict_cache_clean_event";
463     DICT_CACHE *cp = (DICT_CACHE *) cache_context;
464     const char *cache_key;
465     const char *cache_val;
466     int     next_interval;
467     VSTRING *stamp_buf;
468     int     first_next;
469 
470     /*
471      * We interleave cache cleanup with other processing, so that the
472      * application's service remains available, with perhaps increased
473      * latency.
474      */
475 
476     /*
477      * Start a new cache cleanup run.
478      */
479     if (cp->saved_curr_key == 0) {
480 	cp->retained = cp->dropped = 0;
481 	first_next = DICT_SEQ_FUN_FIRST;
482 	if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE)
483 	    msg_info("%s: start %s cache cleanup", myname, cp->name);
484     }
485 
486     /*
487      * Continue a cache cleanup run in progress.
488      */
489     else {
490 	first_next = DICT_SEQ_FUN_NEXT;
491     }
492 
493     /*
494      * Examine one cache entry.
495      */
496     if (dict_cache_sequence(cp, first_next, &cache_key, &cache_val) == 0) {
497 	if (cp->exp_validator(cache_key, cache_val, cp->exp_context) == 0) {
498 	    DC_SCHEDULE_FOR_DELETE_BEHIND(cp);
499 	    cp->dropped++;
500 	    if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE)
501 		msg_info("%s: drop %s cache entry for %s",
502 			 myname, cp->name, cache_key);
503 	} else {
504 	    cp->retained++;
505 	    if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE)
506 		msg_info("%s: keep %s cache entry for %s",
507 			 myname, cp->name, cache_key);
508 	}
509 	next_interval = 0;
510     }
511 
512     /*
513      * Cache cleanup completed. Report vital statistics.
514      */
515     else if (cp->error != 0) {
516 	msg_warn("%s: cache cleanup scan terminated due to error", cp->name);
517 	dict_cache_clean_stat_log_reset(cp, "partial");
518 	next_interval = cp->exp_interval;
519     } else {
520 	if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE)
521 	    msg_info("%s: done %s cache cleanup scan", myname, cp->name);
522 	dict_cache_clean_stat_log_reset(cp, "full");
523 	stamp_buf = vstring_alloc(100);
524 	vstring_sprintf(stamp_buf, "%ld", (long) event_time());
525 	dict_put(cp->db, DC_LAST_CACHE_CLEANUP_COMPLETED,
526 		 vstring_str(stamp_buf));
527 	vstring_free(stamp_buf);
528 	next_interval = cp->exp_interval;
529     }
530     event_request_timer(dict_cache_clean_event, cache_context, next_interval);
531 }
532 
533 /* dict_cache_control - schedule or stop the cache cleanup thread */
534 
535 void    dict_cache_control(DICT_CACHE *cp,...)
536 {
537     const char *myname = "dict_cache_control";
538     const char *last_done;
539     time_t  next_interval;
540     int     cache_cleanup_is_active = (cp->exp_validator && cp->exp_interval);
541     va_list ap;
542     int     name;
543 
544     /*
545      * Update the control settings.
546      */
547     va_start(ap, cp);
548     while ((name = va_arg(ap, int)) > 0) {
549 	switch (name) {
550 	case DICT_CACHE_CTL_END:
551 	    break;
552 	case DICT_CACHE_CTL_FLAGS:
553 	    cp->user_flags = va_arg(ap, int);
554 	    cp->log_delay = (cp->user_flags & DICT_CACHE_FLAG_VERBOSE) ?
555 		0 : DC_DEF_LOG_DELAY;
556 	    break;
557 	case DICT_CACHE_CTL_INTERVAL:
558 	    cp->exp_interval = va_arg(ap, int);
559 	    if (cp->exp_interval < 0)
560 		msg_panic("%s: bad %s cache cleanup interval %d",
561 			  myname, cp->name, cp->exp_interval);
562 	    break;
563 	case DICT_CACHE_CTL_VALIDATOR:
564 	    cp->exp_validator = va_arg(ap, DICT_CACHE_VALIDATOR_FN);
565 	    break;
566 	case DICT_CACHE_CTL_CONTEXT:
567 	    cp->exp_context = va_arg(ap, char *);
568 	    break;
569 	default:
570 	    msg_panic("%s: bad command: %d", myname, name);
571 	}
572     }
573     va_end(ap);
574 
575     /*
576      * Schedule the cache cleanup thread.
577      */
578     if (cp->exp_interval && cp->exp_validator) {
579 
580 	/*
581 	 * Sanity checks.
582 	 */
583 	if (cache_cleanup_is_active)
584 	    msg_panic("%s: %s cache cleanup is already scheduled",
585 		      myname, cp->name);
586 
587 	/*
588 	 * The next start time depends on the last completion time.
589 	 */
590 #define NEXT_START(last, delta) ((delta) + (unsigned long) atol(last))
591 #define NOW	(time((time_t *) 0))		/* NOT: event_time() */
592 
593 	if ((last_done = dict_get(cp->db, DC_LAST_CACHE_CLEANUP_COMPLETED)) == 0
594 	    || (next_interval = (NEXT_START(last_done, cp->exp_interval) - NOW)) < 0)
595 	    next_interval = 0;
596 	if (next_interval > cp->exp_interval)
597 	    next_interval = cp->exp_interval;
598 	if ((cp->user_flags & DICT_CACHE_FLAG_VERBOSE) && next_interval > 0)
599 	    msg_info("%s cache cleanup will start after %ds",
600 		     cp->name, (int) next_interval);
601 	event_request_timer(dict_cache_clean_event, (char *) cp,
602 			    (int) next_interval);
603     }
604 
605     /*
606      * Cancel the cache cleanup thread.
607      */
608     else if (cache_cleanup_is_active) {
609 	if (cp->retained || cp->dropped)
610 	    dict_cache_clean_stat_log_reset(cp, "partial");
611 	dict_cache_delete_behind_reset(cp);
612 	event_cancel_timer(dict_cache_clean_event, (char *) cp);
613     }
614 }
615 
616 /* dict_cache_open - open cache file */
617 
618 DICT_CACHE *dict_cache_open(const char *dbname, int open_flags, int dict_flags)
619 {
620     DICT_CACHE *cp;
621     DICT   *dict;
622 
623     /*
624      * Open the database as requested. Don't attempt to second-guess the
625      * application.
626      */
627     dict = dict_open(dbname, open_flags, dict_flags);
628 
629     /*
630      * Create the DICT_CACHE object.
631      */
632     cp = (DICT_CACHE *) mymalloc(sizeof(*cp));
633     cp->name = mystrdup(dbname);
634     cp->cache_flags = 0;
635     cp->user_flags = 0;
636     cp->db = dict;
637     cp->saved_curr_key = 0;
638     cp->saved_curr_val = 0;
639     cp->exp_interval = 0;
640     cp->exp_validator = 0;
641     cp->exp_context = 0;
642     cp->retained = 0;
643     cp->dropped = 0;
644     cp->log_delay = DC_DEF_LOG_DELAY;
645     cp->upd_log_stamp = cp->get_log_stamp =
646 	cp->del_log_stamp = cp->seq_log_stamp = 0;
647 
648     return (cp);
649 }
650 
651 /* dict_cache_close - close cache file */
652 
653 void    dict_cache_close(DICT_CACHE *cp)
654 {
655 
656     /*
657      * Destroy the DICT_CACHE object.
658      */
659     myfree(cp->name);
660     dict_cache_control(cp, DICT_CACHE_CTL_INTERVAL, 0, DICT_CACHE_CTL_END);
661     dict_close(cp->db);
662     if (cp->saved_curr_key)
663 	myfree(cp->saved_curr_key);
664     if (cp->saved_curr_val)
665 	myfree(cp->saved_curr_val);
666     myfree((char *) cp);
667 }
668 
669 /* dict_cache_name - get the cache name */
670 
671 const char *dict_cache_name(DICT_CACHE *cp)
672 {
673 
674     /*
675      * This is used for verbose logging or warning messages, so the cost of
676      * call is only made where needed (well sort off - code that does not
677      * execute still presents overhead for the processor pipeline, processor
678      * cache, etc).
679      */
680     return (cp->name);
681 }
682