Postfix3.3.1
dict_cache.c
[詳解]
1 /*++
2 /* NAME
3 /* dict_cache 3
4 /* SUMMARY
5 /* External cache manager
6 /* SYNOPSIS
7 /* #include <dict_cache.h>
8 /*
9 /* DICT_CACHE *dict_cache_open(dbname, open_flags, dict_flags)
10 /* const char *dbname;
11 /* int open_flags;
12 /* int dict_flags;
13 /*
14 /* void dict_cache_close(cache)
15 /* DICT_CACHE *cache;
16 /*
17 /* const char *dict_cache_lookup(cache, cache_key)
18 /* DICT_CACHE *cache;
19 /* const char *cache_key;
20 /*
21 /* int dict_cache_update(cache, cache_key, cache_val)
22 /* DICT_CACHE *cache;
23 /* const char *cache_key;
24 /* const char *cache_val;
25 /*
26 /* int dict_cache_delete(cache, cache_key)
27 /* DICT_CACHE *cache;
28 /* const char *cache_key;
29 /*
30 /* int dict_cache_sequence(cache, first_next, cache_key, cache_val)
31 /* DICT_CACHE *cache;
32 /* int first_next;
33 /* const char **cache_key;
34 /* const char **cache_val;
35 /* AUXILIARY FUNCTIONS
36 /* void dict_cache_control(cache, name, value, ...)
37 /* DICT_CACHE *cache;
38 /* int name;
39 /*
40 /* typedef int (*DICT_CACHE_VALIDATOR_FN) (const char *cache_key,
41 /* const char *cache_val, void *context);
42 /*
43 /* const char *dict_cache_name(cache)
44 /* DICT_CACHE *cache;
45 /* DESCRIPTION
46 /* This module maintains external cache files with support
47 /* for expiration. The underlying table must implement the
48 /* "lookup", "update", "delete" and "sequence" operations.
49 /*
50 /* Although this API is similar to the one documented in
51 /* dict_open(3), there are subtle differences in the interaction
52 /* between the iterators that access all cache elements, and
53 /* other operations that access individual cache elements.
54 /*
55 /* In particular, when a "sequence" or "cleanup" operation is
56 /* in progress the cache intercepts requests to delete the
57 /* "current" entry, as this would cause some databases to
58 /* mis-behave. Instead, the cache implements a "delete behind"
59 /* strategy, and deletes such an entry after the "sequence"
60 /* or "cleanup" operation moves on to the next cache element.
61 /* The "delete behind" strategy also affects the cache lookup
62 /* and update operations as detailed below.
63 /*
64 /* dict_cache_open() is a wrapper around the dict_open()
65 /* function. It opens the specified cache and returns a handle
66 /* that must be used for subsequent access. This function does
67 /* not return in case of error.
68 /*
69 /* dict_cache_close() closes the specified cache and releases
70 /* memory that was allocated by dict_cache_open(), and terminates
71 /* any thread that was started with dict_cache_control().
72 /*
73 /* dict_cache_lookup() looks up the specified cache entry.
74 /* The result value is a null pointer when the cache entry was
75 /* not found, or when the entry is scheduled for "delete
76 /* behind".
77 /*
78 /* dict_cache_update() updates the specified cache entry. If
79 /* the entry is scheduled for "delete behind", the delete
80 /* operation is canceled (because of this, the cache must be
81 /* opened with DICT_FLAG_DUP_REPLACE). This function does not
82 /* return in case of error.
83 /*
84 /* dict_cache_delete() removes the specified cache entry. If
85 /* this is the "current" entry of a "sequence" operation, the
86 /* entry is scheduled for "delete behind". The result value
87 /* is zero when the entry was found.
88 /*
89 /* dict_cache_sequence() iterates over the specified cache and
90 /* returns each entry in an implementation-defined order. The
91 /* result value is zero when a cache entry was found.
92 /*
93 /* Important: programs must not use both dict_cache_sequence()
94 /* and the built-in cache cleanup feature.
95 /*
96 /* dict_cache_control() provides control over the built-in
97 /* cache cleanup feature and logging. The arguments are a list
98 /* of macros with zero or more arguments, terminated with
99 /* CA_DICT_CACHE_CTL_END which has none. The following lists
100 /* the macros and corresponding argument types.
101 /* .IP "CA_DICT_CACHE_CTL_FLAGS(int flags)"
102 /* The arguments to this command are the bit-wise OR of zero
103 /* or more of the following:
104 /* .RS
105 /* .IP CA_DICT_CACHE_CTL_FLAG_VERBOSE
106 /* Enable verbose logging of cache activity.
107 /* .IP CA_DICT_CACHE_CTL_FLAG_EXP_SUMMARY
108 /* Log cache statistics after each cache cleanup run.
109 /* .RE
110 /* .IP "CA_DICT_CACHE_CTL_INTERVAL(int interval)"
111 /* The interval between cache cleanup runs. Specify a null
112 /* validator or interval to stop cache cleanup.
113 /* .IP "CA_DICT_CACHE_CTL_VALIDATOR(DICT_CACHE_VALIDATOR_FN validator)"
114 /* An application call-back routine that returns non-zero when
115 /* a cache entry should be kept. The call-back function should
116 /* not make changes to the cache. Specify a null validator or
117 /* interval to stop cache cleanup.
118 /* .IP "CA_DICT_CACHE_CTL_CONTEXT(void *context)"
119 /* Application context that is passed to the validator function.
120 /* .RE
121 /* .PP
122 /* dict_cache_name() returns the name of the specified cache.
123 /*
124 /* Arguments:
125 /* .IP "dbname, open_flags, dict_flags"
126 /* These are passed unchanged to dict_open(). The cache must
127 /* be opened with DICT_FLAG_DUP_REPLACE.
128 /* .IP cache
129 /* Cache handle created with dict_cache_open().
130 /* .IP cache_key
131 /* Cache lookup key.
132 /* .IP cache_val
133 /* Information that is stored under a cache lookup key.
134 /* .IP first_next
135 /* One of DICT_SEQ_FUN_FIRST (first cache element) or
136 /* DICT_SEQ_FUN_NEXT (next cache element).
137 /* .sp
138 /* Note: there is no "stop" request. To ensure that the "delete
139 /* behind" strategy does not interfere with database access,
140 /* allow dict_cache_sequence() to run to completion.
141 /* .IP table
142 /* A bare dictonary handle.
143 /* DIAGNOSTICS
144 /* When a request is satisfied, the lookup routine returns
145 /* non-null, and the update, delete and sequence routines
146 /* return zero. The cache->error value is zero when a request
147 /* could not be satisfied because an item did not exist (delete,
148 /* sequence) or if it could not be updated. The cache->error
149 /* value is non-zero only when a request could not be satisfied,
150 /* and the cause was a database error.
151 /*
152 /* Cache access errors are logged with a warning message. To
153 /* avoid spamming the log, each type of operation logs no more
154 /* than one cache access error per second, per cache. Specify
155 /* the DICT_CACHE_FLAG_VERBOSE flag (see above) to log all
156 /* warnings.
157 /* BUGS
158 /* There should be a way to suspend automatic program suicide
159 /* until a cache cleanup run is completed. Some entries may
160 /* never be removed when the process max_idle time is less
161 /* than the time needed to make a full pass over the cache.
162 /*
163 /* The delete-behind strategy assumes that all updates are
164 /* made by a single process. Otherwise, delete-behind may
165 /* remove an entry that was updated after it was scheduled for
166 /* deletion.
167 /* LICENSE
168 /* .ad
169 /* .fi
170 /* The Secure Mailer license must be distributed with this software.
171 /* HISTORY
172 /* .ad
173 /* .fi
174 /* A predecessor of this code was written first for the Postfix
175 /* tlsmgr(8) daemon.
176 /* AUTHOR(S)
177 /* Wietse Venema
178 /* IBM T.J. Watson Research
179 /* P.O. Box 704
180 /* Yorktown Heights, NY 10598, USA
181 /*--*/
182 
183 /* System library. */
184 
185 #include <sys_defs.h>
186 #include <string.h>
187 #include <stdlib.h>
188 
189 /* Utility library. */
190 
191 #include <msg.h>
192 #include <dict.h>
193 #include <mymalloc.h>
194 #include <events.h>
195 #include <dict_cache.h>
196 
197 /* Application-specific. */
198 
199  /*
200  * XXX Deleting entries while enumerating a map can he tricky. Some map
201  * types have a concept of cursor and support a "delete the current element"
202  * operation. Some map types without cursors don't behave well when the
203  * current first/next entry is deleted (example: with Berkeley DB < 2, the
204  * "next" operation produces garbage). To avoid trouble, we delete an entry
205  * after advancing the current first/next position beyond it; we use the
206  * same strategy with application requests to delete the current entry.
207  */
208 
209  /*
210  * Opaque data structure. Use dict_cache_name() to access the name of the
211  * underlying database.
212  */
213 struct DICT_CACHE {
214  char *name; /* full name including proxy: */
215  int cache_flags; /* see below */
216  int user_flags; /* logging */
217  DICT *db; /* database handle */
218  int error; /* last operation only */
219 
220  /* Delete-behind support. */
221  char *saved_curr_key; /* "current" cache lookup key */
222  char *saved_curr_val; /* "current" cache lookup result */
223 
224  /* Cleanup support. */
225  int exp_interval; /* time between cleanup runs */
226  DICT_CACHE_VALIDATOR_FN exp_validator; /* expiration call-back */
227  void *exp_context; /* call-back context */
228  int retained; /* entries retained in cleanup run */
229  int dropped; /* entries removed in cleanup run */
230 
231  /* Rate-limited logging support. */
233  time_t upd_log_stamp; /* last update warning */
234  time_t get_log_stamp; /* last lookup warning */
235  time_t del_log_stamp; /* last delete warning */
236  time_t seq_log_stamp; /* last sequence warning */
237 };
238 
239 #define DC_FLAG_DEL_SAVED_CURRENT_KEY (1<<0) /* delete-behind is scheduled */
240 
241  /*
242  * Don't log cache access errors more than once per second.
243  */
244 #define DC_DEF_LOG_DELAY 1
245 
246  /*
247  * Macros to make obscure code more readable.
248  */
249 #define DC_SCHEDULE_FOR_DELETE_BEHIND(cp) \
250  ((cp)->cache_flags |= DC_FLAG_DEL_SAVED_CURRENT_KEY)
251 
252 #define DC_MATCH_SAVED_CURRENT_KEY(cp, cache_key) \
253  ((cp)->saved_curr_key && strcmp((cp)->saved_curr_key, (cache_key)) == 0)
254 
255 #define DC_IS_SCHEDULED_FOR_DELETE_BEHIND(cp) \
256  (/* NOT: (cp)->saved_curr_key && */ \
257  ((cp)->cache_flags & DC_FLAG_DEL_SAVED_CURRENT_KEY) != 0)
258 
259 #define DC_CANCEL_DELETE_BEHIND(cp) \
260  ((cp)->cache_flags &= ~DC_FLAG_DEL_SAVED_CURRENT_KEY)
261 
262  /*
263  * Special key to store the time of the last cache cleanup run completion.
264  */
265 #define DC_LAST_CACHE_CLEANUP_COMPLETED "_LAST_CACHE_CLEANUP_COMPLETED_"
266 
267 /* dict_cache_lookup - load entry from cache */
268 
269 const char *dict_cache_lookup(DICT_CACHE *cp, const char *cache_key)
270 {
271  const char *myname = "dict_cache_lookup";
272  const char *cache_val;
273  DICT *db = cp->db;
274 
275  /*
276  * Search for the cache entry. Don't return an entry that is scheduled
277  * for delete-behind.
278  */
280  && DC_MATCH_SAVED_CURRENT_KEY(cp, cache_key)) {
282  msg_info("%s: key=%s (pretend not found - scheduled for deletion)",
283  myname, cache_key);
284  DICT_ERR_VAL_RETURN(cp, DICT_ERR_NONE, (char *) 0);
285  } else {
286  cache_val = dict_get(db, cache_key);
287  if (cache_val == 0 && db->error != 0)
289  "%s: cache lookup for '%s' failed due to error",
290  cp->name, cache_key);
292  msg_info("%s: key=%s value=%s", myname, cache_key,
293  cache_val ? cache_val : db->error ?
294  "error" : "(not found)");
295  DICT_ERR_VAL_RETURN(cp, db->error, cache_val);
296  }
297 }
298 
299 /* dict_cache_update - save entry to cache */
300 
301 int dict_cache_update(DICT_CACHE *cp, const char *cache_key,
302  const char *cache_val)
303 {
304  const char *myname = "dict_cache_update";
305  DICT *db = cp->db;
306  int put_res;
307 
308  /*
309  * Store the cache entry and cancel the delete-behind operation.
310  */
312  && DC_MATCH_SAVED_CURRENT_KEY(cp, cache_key)) {
314  msg_info("%s: cancel delete-behind for key=%s", myname, cache_key);
316  }
318  msg_info("%s: key=%s value=%s", myname, cache_key, cache_val);
319  put_res = dict_put(db, cache_key, cache_val);
320  if (put_res != 0)
322  "%s: could not update entry for %s", cp->name, cache_key);
323  DICT_ERR_VAL_RETURN(cp, db->error, put_res);
324 }
325 
326 /* dict_cache_delete - delete entry from cache */
327 
328 int dict_cache_delete(DICT_CACHE *cp, const char *cache_key)
329 {
330  const char *myname = "dict_cache_delete";
331  int del_res;
332  DICT *db = cp->db;
333 
334  /*
335  * Delete the entry, unless we would delete the current first/next entry.
336  * In that case, schedule the "current" entry for delete-behind to avoid
337  * mis-behavior by some databases.
338  */
339  if (DC_MATCH_SAVED_CURRENT_KEY(cp, cache_key)) {
342  msg_info("%s: key=%s (current entry - schedule for delete-behind)",
343  myname, cache_key);
345  } else {
346  del_res = dict_del(db, cache_key);
347  if (del_res != 0)
349  "%s: could not delete entry for %s", cp->name, cache_key);
351  msg_info("%s: key=%s (%s)", myname, cache_key,
352  del_res == 0 ? "found" :
353  db->error ? "error" : "not found");
354  DICT_ERR_VAL_RETURN(cp, db->error, del_res);
355  }
356 }
357 
358 /* dict_cache_sequence - look up the first/next cache entry */
359 
360 int dict_cache_sequence(DICT_CACHE *cp, int first_next,
361  const char **cache_key,
362  const char **cache_val)
363 {
364  const char *myname = "dict_cache_sequence";
365  int seq_res;
366  const char *raw_cache_key;
367  const char *raw_cache_val;
368  char *previous_curr_key;
369  char *previous_curr_val;
370  DICT *db = cp->db;
371 
372  /*
373  * Find the first or next database entry. Hide the record with the cache
374  * cleanup completion time stamp.
375  */
376  seq_res = dict_seq(db, first_next, &raw_cache_key, &raw_cache_val);
377  if (seq_res == 0
378  && strcmp(raw_cache_key, DC_LAST_CACHE_CLEANUP_COMPLETED) == 0)
379  seq_res =
380  dict_seq(db, DICT_SEQ_FUN_NEXT, &raw_cache_key, &raw_cache_val);
382  msg_info("%s: key=%s value=%s", myname,
383  seq_res == 0 ? raw_cache_key : db->error ?
384  "(error)" : "(not found)",
385  seq_res == 0 ? raw_cache_val : db->error ?
386  "(error)" : "(not found)");
387  if (db->error)
389  "%s: sequence error", cp->name);
390 
391  /*
392  * Save the current cache_key and cache_val before they are clobbered by
393  * our own delete operation below. This also prevents surprises when the
394  * application accesses the database after this function returns.
395  *
396  * We also use the saved cache_key to protect the current entry against
397  * application delete requests.
398  */
399  previous_curr_key = cp->saved_curr_key;
400  previous_curr_val = cp->saved_curr_val;
401  if (seq_res == 0) {
402  cp->saved_curr_key = mystrdup(raw_cache_key);
403  cp->saved_curr_val = mystrdup(raw_cache_val);
404  } else {
405  cp->saved_curr_key = 0;
406  cp->saved_curr_val = 0;
407  }
408 
409  /*
410  * Delete behind.
411  */
412  if (db->error == 0 && DC_IS_SCHEDULED_FOR_DELETE_BEHIND(cp)) {
415  msg_info("%s: delete-behind key=%s value=%s",
416  myname, previous_curr_key, previous_curr_val);
417  if (dict_del(db, previous_curr_key) != 0)
419  "%s: could not delete entry for %s",
420  cp->name, previous_curr_key);
421  }
422 
423  /*
424  * Clean up previous iteration key and value.
425  */
426  if (previous_curr_key)
427  myfree(previous_curr_key);
428  if (previous_curr_val)
429  myfree(previous_curr_val);
430 
431  /*
432  * Return the result.
433  */
434  *cache_key = (cp)->saved_curr_key;
435  *cache_val = (cp)->saved_curr_val;
436  DICT_ERR_VAL_RETURN(cp, db->error, seq_res);
437 }
438 
439 /* dict_cache_delete_behind_reset - reset "delete behind" state */
440 
441 static void dict_cache_delete_behind_reset(DICT_CACHE *cp)
442 {
443 #define FREE_AND_WIPE(s) do { if (s) { myfree(s); (s) = 0; } } while (0)
444 
448 }
449 
450 /* dict_cache_clean_stat_log_reset - log and reset cache cleanup statistics */
451 
452 static void dict_cache_clean_stat_log_reset(DICT_CACHE *cp,
453  const char *full_partial)
454 {
456  msg_info("cache %s %s cleanup: retained=%d dropped=%d entries",
457  cp->name, full_partial, cp->retained, cp->dropped);
458  cp->retained = cp->dropped = 0;
459 }
460 
461 /* dict_cache_clean_event - examine one cache entry */
462 
463 static void dict_cache_clean_event(int unused_event, void *cache_context)
464 {
465  const char *myname = "dict_cache_clean_event";
466  DICT_CACHE *cp = (DICT_CACHE *) cache_context;
467  const char *cache_key;
468  const char *cache_val;
469  int next_interval;
470  VSTRING *stamp_buf;
471  int first_next;
472 
473  /*
474  * We interleave cache cleanup with other processing, so that the
475  * application's service remains available, with perhaps increased
476  * latency.
477  */
478 
479  /*
480  * Start a new cache cleanup run.
481  */
482  if (cp->saved_curr_key == 0) {
483  cp->retained = cp->dropped = 0;
484  first_next = DICT_SEQ_FUN_FIRST;
486  msg_info("%s: start %s cache cleanup", myname, cp->name);
487  }
488 
489  /*
490  * Continue a cache cleanup run in progress.
491  */
492  else {
493  first_next = DICT_SEQ_FUN_NEXT;
494  }
495 
496  /*
497  * Examine one cache entry.
498  */
499  if (dict_cache_sequence(cp, first_next, &cache_key, &cache_val) == 0) {
500  if (cp->exp_validator(cache_key, cache_val, cp->exp_context) == 0) {
502  cp->dropped++;
504  msg_info("%s: drop %s cache entry for %s",
505  myname, cp->name, cache_key);
506  } else {
507  cp->retained++;
509  msg_info("%s: keep %s cache entry for %s",
510  myname, cp->name, cache_key);
511  }
512  next_interval = 0;
513  }
514 
515  /*
516  * Cache cleanup completed. Report vital statistics.
517  */
518  else if (cp->error != 0) {
519  msg_warn("%s: cache cleanup scan terminated due to error", cp->name);
520  dict_cache_clean_stat_log_reset(cp, "partial");
521  next_interval = cp->exp_interval;
522  } else {
524  msg_info("%s: done %s cache cleanup scan", myname, cp->name);
525  dict_cache_clean_stat_log_reset(cp, "full");
526  stamp_buf = vstring_alloc(100);
527  vstring_sprintf(stamp_buf, "%ld", (long) event_time());
529  vstring_str(stamp_buf));
530  vstring_free(stamp_buf);
531  next_interval = cp->exp_interval;
532  }
533  event_request_timer(dict_cache_clean_event, cache_context, next_interval);
534 }
535 
536 /* dict_cache_control - schedule or stop the cache cleanup thread */
537 
539 {
540  const char *myname = "dict_cache_control";
541  const char *last_done;
542  time_t next_interval;
543  int cache_cleanup_is_active = (cp->exp_validator && cp->exp_interval);
544  va_list ap;
545  int name;
546 
547  /*
548  * Update the control settings.
549  */
550  va_start(ap, cp);
551  while ((name = va_arg(ap, int)) > 0) {
552  switch (name) {
553  case DICT_CACHE_CTL_END:
554  break;
556  cp->user_flags = va_arg(ap, int);
558  0 : DC_DEF_LOG_DELAY;
559  break;
561  cp->exp_interval = va_arg(ap, int);
562  if (cp->exp_interval < 0)
563  msg_panic("%s: bad %s cache cleanup interval %d",
564  myname, cp->name, cp->exp_interval);
565  break;
567  cp->exp_validator = va_arg(ap, DICT_CACHE_VALIDATOR_FN);
568  break;
570  cp->exp_context = va_arg(ap, void *);
571  break;
572  default:
573  msg_panic("%s: bad command: %d", myname, name);
574  }
575  }
576  va_end(ap);
577 
578  /*
579  * Schedule the cache cleanup thread.
580  */
581  if (cp->exp_interval && cp->exp_validator) {
582 
583  /*
584  * Sanity checks.
585  */
586  if (cache_cleanup_is_active)
587  msg_panic("%s: %s cache cleanup is already scheduled",
588  myname, cp->name);
589 
590  /*
591  * The next start time depends on the last completion time.
592  */
593 #define NEXT_START(last, delta) ((delta) + (unsigned long) atol(last))
594 #define NOW (time((time_t *) 0)) /* NOT: event_time() */
595 
596  if ((last_done = dict_get(cp->db, DC_LAST_CACHE_CLEANUP_COMPLETED)) == 0
597  || (next_interval = (NEXT_START(last_done, cp->exp_interval) - NOW)) < 0)
598  next_interval = 0;
599  if (next_interval > cp->exp_interval)
600  next_interval = cp->exp_interval;
601  if ((cp->user_flags & DICT_CACHE_FLAG_VERBOSE) && next_interval > 0)
602  msg_info("%s cache cleanup will start after %ds",
603  cp->name, (int) next_interval);
604  event_request_timer(dict_cache_clean_event, (void *) cp,
605  (int) next_interval);
606  }
607 
608  /*
609  * Cancel the cache cleanup thread.
610  */
611  else if (cache_cleanup_is_active) {
612  if (cp->retained || cp->dropped)
613  dict_cache_clean_stat_log_reset(cp, "partial");
614  dict_cache_delete_behind_reset(cp);
615  event_cancel_timer(dict_cache_clean_event, (void *) cp);
616  }
617 }
618 
619 /* dict_cache_open - open cache file */
620 
621 DICT_CACHE *dict_cache_open(const char *dbname, int open_flags, int dict_flags)
622 {
623  DICT_CACHE *cp;
624  DICT *dict;
625 
626  /*
627  * Open the database as requested. Don't attempt to second-guess the
628  * application.
629  */
630  dict = dict_open(dbname, open_flags, dict_flags);
631 
632  /*
633  * Create the DICT_CACHE object.
634  */
635  cp = (DICT_CACHE *) mymalloc(sizeof(*cp));
636  cp->name = mystrdup(dbname);
637  cp->cache_flags = 0;
638  cp->user_flags = 0;
639  cp->db = dict;
640  cp->saved_curr_key = 0;
641  cp->saved_curr_val = 0;
642  cp->exp_interval = 0;
643  cp->exp_validator = 0;
644  cp->exp_context = 0;
645  cp->retained = 0;
646  cp->dropped = 0;
648  cp->upd_log_stamp = cp->get_log_stamp =
649  cp->del_log_stamp = cp->seq_log_stamp = 0;
650 
651  return (cp);
652 }
653 
654 /* dict_cache_close - close cache file */
655 
657 {
658 
659  /*
660  * Destroy the DICT_CACHE object.
661  */
662  myfree(cp->name);
664  dict_close(cp->db);
665  if (cp->saved_curr_key)
666  myfree(cp->saved_curr_key);
667  if (cp->saved_curr_val)
668  myfree(cp->saved_curr_val);
669  myfree((void *) cp);
670 }
671 
672 /* dict_cache_name - get the cache name */
673 
674 const char *dict_cache_name(DICT_CACHE *cp)
675 {
676 
677  /*
678  * This is used for verbose logging or warning messages, so the cost of
679  * call is only made where needed (well sort off - code that does not
680  * execute still presents overhead for the processor pipeline, processor
681  * cache, etc).
682  */
683  return (cp->name);
684 }
685 
686  /*
687  * Test driver with support for interleaved access. First, enter a number of
688  * requests to look up, update or delete a sequence of cache entries, then
689  * interleave those sequences with the "run" command.
690  */
691 #ifdef TEST
692 #include <msg_vstream.h>
693 #include <vstring_vstream.h>
694 #include <argv.h>
695 #include <stringops.h>
696 
697 #define DELIMS " "
698 #define USAGE "\n\tTo manage settings:" \
699  "\n\tverbose <level> (verbosity level)" \
700  "\n\telapsed <level> (0=don't show elapsed time)" \
701  "\n\tlmdb_map_size <limit> (initial LMDB size limit)" \
702  "\n\tcache <type>:<name> (switch to named database)" \
703  "\n\tstatus (show map size, cache, pending requests)" \
704  "\n\n\tTo manage pending requests:" \
705  "\n\treset (discard pending requests)" \
706  "\n\trun (execute pending requests in interleaved order)" \
707  "\n\n\tTo add a pending request:" \
708  "\n\tquery <key-suffix> <count> (negative to reverse order)" \
709  "\n\tupdate <key-suffix> <count> (negative to reverse order)" \
710  "\n\tdelete <key-suffix> <count> (negative to reverse order)" \
711  "\n\tpurge <key-suffix>" \
712  "\n\tcount <key-suffix>"
713 
714  /*
715  * For realism, open the cache with the same flags as postscreen(8) and
716  * verify(8).
717  */
718 #define DICT_CACHE_OPEN_FLAGS (DICT_FLAG_DUP_REPLACE | DICT_FLAG_SYNC_UPDATE | \
719  DICT_FLAG_OPEN_LOCK)
720 
721  /*
722  * Storage for one request to access a sequence of cache entries.
723  */
724 typedef struct DICT_CACHE_SREQ {
725  int flags; /* per-request: reverse, purge */
726  char *cmd; /* command for status report */
727  void (*action) (struct DICT_CACHE_SREQ *, DICT_CACHE *, VSTRING *);
728  char *suffix; /* key suffix */
729  int done; /* progress indicator */
730  int todo; /* number of entries to process */
731  int first_next; /* first/next */
732 } DICT_CACHE_SREQ;
733 
734 #define DICT_CACHE_SREQ_FLAG_PURGE (1<<1) /* purge instead of count */
735 #define DICT_CACHE_SREQ_FLAG_REVERSE (1<<2) /* reverse instead of forward */
736 
737 #define DICT_CACHE_SREQ_LIMIT 10
738 
739  /*
740  * All test requests combined.
741  */
742 typedef struct DICT_CACHE_TEST {
743  int flags; /* exclusion flags */
744  int size; /* allocated slots */
745  int used; /* used slots */
746  DICT_CACHE_SREQ job_list[1]; /* actually, a bunch */
747 } DICT_CACHE_TEST;
748 
749 #define DICT_CACHE_TEST_FLAG_ITER (1<<0) /* count or purge */
750 
751 #define STR(x) vstring_str(x)
752 
753 int show_elapsed = 1; /* show elapsed time */
754 
755 #ifdef HAS_LMDB
756 extern size_t dict_lmdb_map_size; /* LMDB-specific */
757 
758 #endif
759 
760 /* usage - command-line usage message */
761 
762 static NORETURN usage(const char *progname)
763 {
764  msg_fatal("usage: %s (no argument)", progname);
765 }
766 
767 /* make_tagged_key - make tagged search key */
768 
769 static void make_tagged_key(VSTRING *bp, DICT_CACHE_SREQ *cp)
770 {
771  if (cp->done < 0)
772  msg_panic("make_tagged_key: bad done count: %d", cp->done);
773  if (cp->todo < 1)
774  msg_panic("make_tagged_key: bad todo count: %d", cp->todo);
775  vstring_sprintf(bp, "%d-%s",
776  (cp->flags & DICT_CACHE_SREQ_FLAG_REVERSE) ?
777  cp->todo - cp->done - 1 : cp->done, cp->suffix);
778 }
779 
780 /* create_requests - create request list */
781 
782 static DICT_CACHE_TEST *create_requests(int count)
783 {
784  DICT_CACHE_TEST *tp;
785  DICT_CACHE_SREQ *cp;
786 
787  tp = (DICT_CACHE_TEST *) mymalloc(sizeof(DICT_CACHE_TEST) +
788  (count - 1) *sizeof(DICT_CACHE_SREQ));
789  tp->flags = 0;
790  tp->size = count;
791  tp->used = 0;
792  for (cp = tp->job_list; cp < tp->job_list + count; cp++) {
793  cp->flags = 0;
794  cp->cmd = 0;
795  cp->action = 0;
796  cp->suffix = 0;
797  cp->todo = 0;
798  cp->first_next = DICT_SEQ_FUN_FIRST;
799  }
800  return (tp);
801 }
802 
803 /* reset_requests - reset request list */
804 
805 static void reset_requests(DICT_CACHE_TEST *tp)
806 {
807  DICT_CACHE_SREQ *cp;
808 
809  tp->flags = 0;
810  tp->used = 0;
811  for (cp = tp->job_list; cp < tp->job_list + tp->size; cp++) {
812  cp->flags = 0;
813  if (cp->cmd) {
814  myfree(cp->cmd);
815  cp->cmd = 0;
816  }
817  cp->action = 0;
818  if (cp->suffix) {
819  myfree(cp->suffix);
820  cp->suffix = 0;
821  }
822  cp->todo = 0;
823  cp->first_next = DICT_SEQ_FUN_FIRST;
824  }
825 }
826 
827 /* free_requests - destroy request list */
828 
829 static void free_requests(DICT_CACHE_TEST *tp)
830 {
831  reset_requests(tp);
832  myfree((void *) tp);
833 }
834 
835 /* run_requests - execute pending requests in interleaved order */
836 
837 static void run_requests(DICT_CACHE_TEST *tp, DICT_CACHE *dp, VSTRING *bp)
838 {
839  DICT_CACHE_SREQ *cp;
840  int todo;
841  struct timeval start;
842  struct timeval finish;
843  struct timeval elapsed;
844 
845  if (dp == 0) {
846  msg_warn("no cache");
847  return;
848  }
849  GETTIMEOFDAY(&start);
850  do {
851  todo = 0;
852  for (cp = tp->job_list; cp < tp->job_list + tp->used; cp++) {
853  if (cp->done < cp->todo) {
854  todo = 1;
855  cp->action(cp, dp, bp);
856  }
857  }
858  } while (todo);
859  GETTIMEOFDAY(&finish);
860  timersub(&finish, &start, &elapsed);
861  if (show_elapsed)
862  vstream_printf("Elapsed: %g\n",
863  elapsed.tv_sec + elapsed.tv_usec / 1000000.0);
864 
865  reset_requests(tp);
866 }
867 
868 /* show_status - show settings and pending requests */
869 
870 static void show_status(DICT_CACHE_TEST *tp, DICT_CACHE *dp)
871 {
872  DICT_CACHE_SREQ *cp;
873 
874 #ifdef HAS_LMDB
875  vstream_printf("lmdb_map_size\t%ld\n", (long) dict_lmdb_map_size);
876 #endif
877  vstream_printf("cache\t%s\n", dp ? dp->name : "(none)");
878 
879  if (tp->used == 0)
880  vstream_printf("No pending requests\n");
881  else
882  vstream_printf("%s\t%s\t%s\t%s\t%s\t%s\n",
883  "cmd", "dir", "suffix", "count", "done", "first/next");
884 
885  for (cp = tp->job_list; cp < tp->job_list + tp->used; cp++)
886  if (cp->todo > 0)
887  vstream_printf("%s\t%s\t%s\t%d\t%d\t%d\n",
888  cp->cmd,
889  (cp->flags & DICT_CACHE_SREQ_FLAG_REVERSE) ?
890  "reverse" : "forward",
891  cp->suffix ? cp->suffix : "(null)", cp->todo,
892  cp->done, cp->first_next);
893 }
894 
895 /* query_action - lookup cache entry */
896 
897 static void query_action(DICT_CACHE_SREQ *cp, DICT_CACHE *dp, VSTRING *bp)
898 {
899  const char *lookup;
900 
901  make_tagged_key(bp, cp);
902  if ((lookup = dict_cache_lookup(dp, STR(bp))) == 0) {
903  if (dp->error)
904  msg_warn("query_action: query failed: %s: %m", STR(bp));
905  else
906  msg_warn("query_action: query failed: %s", STR(bp));
907  } else if (strcmp(STR(bp), lookup) != 0) {
908  msg_warn("lookup result \"%s\" differs from key \"%s\"",
909  lookup, STR(bp));
910  }
911  cp->done += 1;
912 }
913 
914 /* update_action - update cache entry */
915 
916 static void update_action(DICT_CACHE_SREQ *cp, DICT_CACHE *dp, VSTRING *bp)
917 {
918  make_tagged_key(bp, cp);
919  if (dict_cache_update(dp, STR(bp), STR(bp)) != 0) {
920  if (dp->error)
921  msg_warn("update_action: update failed: %s: %m", STR(bp));
922  else
923  msg_warn("update_action: update failed: %s", STR(bp));
924  }
925  cp->done += 1;
926 }
927 
928 /* delete_action - delete cache entry */
929 
930 static void delete_action(DICT_CACHE_SREQ *cp, DICT_CACHE *dp, VSTRING *bp)
931 {
932  make_tagged_key(bp, cp);
933  if (dict_cache_delete(dp, STR(bp)) != 0) {
934  if (dp->error)
935  msg_warn("delete_action: delete failed: %s: %m", STR(bp));
936  else
937  msg_warn("delete_action: delete failed: %s", STR(bp));
938  }
939  cp->done += 1;
940 }
941 
942 /* iter_action - iterate over cache and act on entries with given suffix */
943 
944 static void iter_action(DICT_CACHE_SREQ *cp, DICT_CACHE *dp, VSTRING *bp)
945 {
946  const char *cache_key;
947  const char *cache_val;
948  const char *what;
949  const char *suffix;
950 
951  if (dict_cache_sequence(dp, cp->first_next, &cache_key, &cache_val) == 0) {
952  if (strcmp(cache_key, cache_val) != 0)
953  msg_warn("value \"%s\" differs from key \"%s\"",
954  cache_val, cache_key);
955  suffix = cache_key + strspn(cache_key, "0123456789");
956  if (suffix[0] == '-' && strcmp(suffix + 1, cp->suffix) == 0) {
957  cp->done += 1;
958  cp->todo = cp->done + 1; /* XXX */
959  if ((cp->flags & DICT_CACHE_SREQ_FLAG_PURGE)
960  && dict_cache_delete(dp, cache_key) != 0) {
961  if (dp->error)
962  msg_warn("purge_action: delete failed: %s: %m", STR(bp));
963  else
964  msg_warn("purge_action: delete failed: %s", STR(bp));
965  }
966  }
967  cp->first_next = DICT_SEQ_FUN_NEXT;
968  } else {
969  what = (cp->flags & DICT_CACHE_SREQ_FLAG_PURGE) ? "purge" : "count";
970  if (dp->error)
971  msg_warn("%s error after %d: %m", what, cp->done);
972  else
973  vstream_printf("suffix=%s %s=%d\n", cp->suffix, what, cp->done);
974  cp->todo = 0;
975  }
976 }
977 
978  /*
979  * Table-driven support.
980  */
981 typedef struct DICT_CACHE_SREQ_INFO {
982  const char *name;
983  int argc;
984  void (*action) (DICT_CACHE_SREQ *, DICT_CACHE *, VSTRING *);
985  int test_flags;
986  int req_flags;
987 } DICT_CACHE_SREQ_INFO;
988 
989 static DICT_CACHE_SREQ_INFO req_info[] = {
990  {"query", 3, query_action},
991  {"update", 3, update_action},
992  {"delete", 3, delete_action},
993  {"count", 2, iter_action, DICT_CACHE_TEST_FLAG_ITER},
994  {"purge", 2, iter_action, DICT_CACHE_TEST_FLAG_ITER, DICT_CACHE_SREQ_FLAG_PURGE},
995  0,
996 };
997 
998 /* add_request - add a request to the list */
999 
1000 static void add_request(DICT_CACHE_TEST *tp, ARGV *argv)
1001 {
1002  DICT_CACHE_SREQ_INFO *rp;
1003  DICT_CACHE_SREQ *cp;
1004  int req_flags;
1005  int count;
1006  char *cmd = argv->argv[0];
1007  char *suffix = (argv->argc > 1 ? argv->argv[1] : 0);
1008  char *todo = (argv->argc > 2 ? argv->argv[2] : "1"); /* XXX */
1009 
1010  if (tp->used >= tp->size) {
1011  msg_warn("%s: request list is full", cmd);
1012  return;
1013  }
1014  for (rp = req_info; /* See below */ ; rp++) {
1015  if (rp->name == 0) {
1016  vstream_printf("usage: %s\n", USAGE);
1017  return;
1018  }
1019  if (strcmp(rp->name, argv->argv[0]) == 0
1020  && rp->argc == argv->argc)
1021  break;
1022  }
1023  req_flags = rp->req_flags;
1024  if (todo[0] == '-') {
1025  req_flags |= DICT_CACHE_SREQ_FLAG_REVERSE;
1026  todo += 1;
1027  }
1028  if (!alldig(todo) || (count = atoi(todo)) == 0) {
1029  msg_warn("%s: bad count: %s", cmd, todo);
1030  return;
1031  }
1032  if (tp->flags & rp->test_flags) {
1033  msg_warn("%s: command conflicts with other command", cmd);
1034  return;
1035  }
1036  tp->flags |= rp->test_flags;
1037  cp = tp->job_list + tp->used;
1038  cp->cmd = mystrdup(cmd);
1039  cp->action = rp->action;
1040  if (suffix)
1041  cp->suffix = mystrdup(suffix);
1042  cp->done = 0;
1043  cp->flags = req_flags;
1044  cp->todo = count;
1045  tp->used += 1;
1046 }
1047 
1048 /* main - main program */
1049 
1050 int main(int argc, char **argv)
1051 {
1052  DICT_CACHE_TEST *test_job;
1053  VSTRING *inbuf = vstring_alloc(100);
1054  char *bufp;
1055  ARGV *args;
1056  DICT_CACHE *cache = 0;
1057  int stdin_is_tty;
1058 
1059  msg_vstream_init(argv[0], VSTREAM_ERR);
1060  if (argc != 1)
1061  usage(argv[0]);
1062 
1063 
1064  test_job = create_requests(DICT_CACHE_SREQ_LIMIT);
1065 
1066  stdin_is_tty = isatty(0);
1067 
1068  for (;;) {
1069  if (stdin_is_tty) {
1070  vstream_printf("> ");
1072  }
1073  if (vstring_fgets_nonl(inbuf, VSTREAM_IN) == 0)
1074  break;
1075  bufp = vstring_str(inbuf);
1076  if (!stdin_is_tty) {
1077  vstream_printf("> %s\n", bufp);
1079  }
1080  if (*bufp == '#')
1081  continue;
1082  args = argv_split(bufp, DELIMS);
1083  if (argc == 0) {
1084  vstream_printf("usage: %s\n", USAGE);
1086  continue;
1087  }
1088  if (strcmp(args->argv[0], "verbose") == 0 && args->argc == 2) {
1089  msg_verbose = atoi(args->argv[1]);
1090  } else if (strcmp(args->argv[0], "elapsed") == 0 && args->argc == 2) {
1091  show_elapsed = atoi(args->argv[1]);
1092 #ifdef HAS_LMDB
1093  } else if (strcmp(args->argv[0], "lmdb_map_size") == 0 && args->argc == 2) {
1094  dict_lmdb_map_size = atol(args->argv[1]);
1095 #endif
1096  } else if (strcmp(args->argv[0], "cache") == 0 && args->argc == 2) {
1097  if (cache)
1098  dict_cache_close(cache);
1099  cache = dict_cache_open(args->argv[1], O_CREAT | O_RDWR,
1100  DICT_CACHE_OPEN_FLAGS);
1101  } else if (strcmp(args->argv[0], "reset") == 0 && args->argc == 1) {
1102  reset_requests(test_job);
1103  } else if (strcmp(args->argv[0], "run") == 0 && args->argc == 1) {
1104  run_requests(test_job, cache, inbuf);
1105  } else if (strcmp(args->argv[0], "status") == 0 && args->argc == 1) {
1106  show_status(test_job, cache);
1107  } else {
1108  add_request(test_job, args);
1109  }
1111  argv_free(args);
1112  }
1113 
1114  vstring_free(inbuf);
1115  free_requests(test_job);
1116  if (cache)
1117  dict_cache_close(cache);
1118  return (0);
1119 }
1120 
1121 #endif
int msg_verbose
Definition: msg.c:177
#define vstring_fgets_nonl(s, p)
time_t del_log_stamp
Definition: dict_cache.c:235
void myfree(void *ptr)
Definition: mymalloc.c:207
#define DC_DEF_LOG_DELAY
Definition: dict_cache.c:244
#define DC_IS_SCHEDULED_FOR_DELETE_BEHIND(cp)
Definition: dict_cache.c:255
char * mystrdup(const char *str)
Definition: mymalloc.c:225
#define dict_put(dp, key, val)
Definition: dict.h:237
#define DC_MATCH_SAVED_CURRENT_KEY(cp, cache_key)
Definition: dict_cache.c:252
ARGV * argv_free(ARGV *argvp)
Definition: argv.c:136
#define DC_SCHEDULE_FOR_DELETE_BEHIND(cp)
Definition: dict_cache.c:249
#define DICT_SEQ_FUN_FIRST
Definition: dict.h:200
#define NORETURN
Definition: sys_defs.h:1583
Definition: argv.h:17
NORETURN msg_panic(const char *fmt,...)
Definition: msg.c:295
#define vstring_str(vp)
Definition: vstring.h:71
#define VSTREAM_OUT
Definition: vstream.h:67
const char * dict_cache_lookup(DICT_CACHE *cp, const char *cache_key)
Definition: dict_cache.c:269
#define DICT_SEQ_FUN_NEXT
Definition: dict.h:201
int main(int argc, char **argv)
Definition: anvil.c:1010
#define USAGE
char ** argv
Definition: argv.h:20
void msg_rate_delay(time_t *stamp, int delay, void(*log_fn)(const char *,...), const char *fmt,...)
DICT * dict_open(const char *, int, int)
Definition: dict_open.c:421
#define FREE_AND_WIPE(s)
size_t dict_lmdb_map_size
#define VSTREAM_IN
Definition: vstream.h:66
int alldig(const char *string)
Definition: alldig.c:38
int(* DICT_CACHE_VALIDATOR_FN)(const char *, const char *, void *)
Definition: dict_cache.h:24
#define DICT_ERR_NONE
Definition: dict.h:177
Definition: dict.h:78
#define DICT_CACHE_CTL_VALIDATOR
Definition: dict_cache.h:42
#define DICT_STAT_SUCCESS
Definition: dict.h:186
#define DICT_CACHE_FLAG_VERBOSE
Definition: dict_cache.h:35
int user_flags
Definition: dict_cache.c:216
#define dict_get(dp, key)
Definition: dict.h:236
char * saved_curr_val
Definition: dict_cache.c:222
int dict_cache_sequence(DICT_CACHE *cp, int first_next, const char **cache_key, const char **cache_val)
Definition: dict_cache.c:360
#define DICT_CACHE_CTL_END
Definition: dict_cache.h:39
#define dict_seq(dp, f, key, val)
Definition: dict.h:239
VSTREAM * vstream_printf(const char *fmt,...)
Definition: vstream.c:1335
time_t seq_log_stamp
Definition: dict_cache.c:236
#define STR(x)
Definition: anvil.c:518
void msg_warn(const char *fmt,...)
Definition: msg.c:215
int dict_cache_delete(DICT_CACHE *cp, const char *cache_key)
Definition: dict_cache.c:328
VSTRING * vstring_alloc(ssize_t len)
Definition: vstring.c:353
#define DICT_CACHE_CTL_CONTEXT
Definition: dict_cache.h:43
char * name
Definition: dict_cache.c:214
int error
Definition: dict.h:94
void dict_cache_close(DICT_CACHE *cp)
Definition: dict_cache.c:656
VSTRING * vstring_sprintf(VSTRING *vp, const char *format,...)
Definition: vstring.c:602
const char * dict_cache_name(DICT_CACHE *cp)
Definition: dict_cache.c:674
#define DC_LAST_CACHE_CLEANUP_COMPLETED
Definition: dict_cache.c:265
#define DC_CANCEL_DELETE_BEHIND(cp)
Definition: dict_cache.c:259
NORETURN msg_fatal(const char *fmt,...)
Definition: msg.c:249
#define DICT_ERR_VAL_RETURN(dict, err, val)
Definition: dict.h:192
int log_delay
Definition: dict_cache.c:232
int vstream_fflush(VSTREAM *stream)
Definition: vstream.c:1257
DICT_CACHE_VALIDATOR_FN exp_validator
Definition: dict_cache.c:226
time_t event_time(void)
Definition: events.c:647
void * exp_context
Definition: dict_cache.c:227
#define timersub(a, b, res)
Definition: tls_bio_ops.c:128
ARGV * argv_split(const char *, const char *)
Definition: argv_split.c:63
time_t get_log_stamp
Definition: dict_cache.c:234
#define NEXT_START(last, delta)
#define DICT_CACHE_FLAG_STATISTICS
Definition: dict_cache.h:36
VSTRING * vstring_free(VSTRING *vp)
Definition: vstring.c:380
time_t event_request_timer(EVENT_NOTIFY_TIME_FN callback, void *context, int delay)
Definition: events.c:894
void msg_vstream_init(const char *name, VSTREAM *vp)
Definition: msg_vstream.c:77
int dict_cache_update(DICT_CACHE *cp, const char *cache_key, const char *cache_val)
Definition: dict_cache.c:301
ssize_t argc
Definition: argv.h:19
DICT_CACHE * dict_cache_open(const char *dbname, int open_flags, int dict_flags)
Definition: dict_cache.c:621
#define NOW
time_t upd_log_stamp
Definition: dict_cache.c:233
DICT * db
Definition: dict_cache.c:217
#define DICT_CACHE_CTL_INTERVAL
Definition: dict_cache.h:41
#define dict_del(dp, key)
Definition: dict.h:238
int exp_interval
Definition: dict_cache.c:225
#define DICT_CACHE_CTL_FLAGS
Definition: dict_cache.h:40
int event_cancel_timer(EVENT_NOTIFY_TIME_FN callback, void *context)
Definition: events.c:965
char * saved_curr_key
Definition: dict_cache.c:221
#define VSTREAM_ERR
Definition: vstream.h:68
#define dict_close(dp)
Definition: dict.h:240
int cache_flags
Definition: dict_cache.c:215
void * mymalloc(ssize_t len)
Definition: mymalloc.c:150
void msg_info(const char *fmt,...)
Definition: msg.c:199
void dict_cache_control(DICT_CACHE *cp,...)
Definition: dict_cache.c:538
int retained
Definition: dict_cache.c:228