Home | History | Annotate | Line # | Download | only in util
      1 /*	$NetBSD: dict_open.c,v 1.5 2026/05/09 18:49:22 christos Exp $	*/
      2 
      3 /*++
      4 /* NAME
      5 /*	dict_open 3
      6 /* SUMMARY
      7 /*	low-level dictionary interface
      8 /* SYNOPSIS
      9 /*	#include <dict.h>
     10 /*
     11 /*	DICT	*dict_open(dict_spec, open_flags, dict_flags)
     12 /*	const char *dict_spec;
     13 /*	int	open_flags;
     14 /*	int	dict_flags;
     15 /*
     16 /*	DICT	*dict_open3(dict_type, dict_name, open_flags, dict_flags)
     17 /*	const char *dict_type;
     18 /*	const char *dict_name;
     19 /*	int	open_flags;
     20 /*	int	dict_flags;
     21 /*
     22 /*	int	dict_put(dict, key, value)
     23 /*	DICT	*dict;
     24 /*	const char *key;
     25 /*	const char *value;
     26 /*
     27 /*	const char *dict_get(dict, key)
     28 /*	DICT	*dict;
     29 /*	const char *key;
     30 /*
     31 /*	int	dict_del(dict, key)
     32 /*	DICT	*dict;
     33 /*	const char *key;
     34 /*
     35 /*	int	dict_seq(dict, func, key, value)
     36 /*	DICT	*dict;
     37 /*	int	func;
     38 /*	const char **key;
     39 /*	const char **value;
     40 /*
     41 /*	void	dict_close(dict)
     42 /*	DICT	*dict;
     43 /*
     44 /*	typedef struct {
     45 /* .in +4
     46 /*	    char   *type;
     47 /*	    DICT_OPEN_FN dict_fn;
     48 /*	    MKMAP_OPEN_FN mkmap_fn;	/* See <mkmap.h> */
     49 /* .in -4
     50 /*	} DICT_OPEN_INFO;
     51 /*
     52 /*	typedef DICT *(*DICT_OPEN_FN) (const char *, int, int);
     53 /*
     54 /*	void	dict_open_register(open_info)
     55 /*	DICT_OPEN_INFO *open_info;
     56 /*
     57 /*	void	dict_open_unregister(dict_type)
     58 /*	const char *dict_type;
     59 /*
     60 /*	const DICT_OPEN_INFO *dict_open_lookup(dict_type)
     61 /*	const char *dict_type;
     62 /*
     63 /*	typedef DICT_OPEN_INFO (*DICT_OPEN_EXTEND_FN)(char *);
     64 /*
     65 /*	DICT_OPEN_EXTEND_FN dict_open_extend(call_back)
     66 /*	DICT_OPEN_EXTEND_FN call_back;
     67 /*
     68 /*	ARGV	*dict_mapnames()
     69 /*
     70 /*	typedef ARGV *(*DICT_MAPNAMES_EXTEND_FN)(ARGV *names);
     71 /*
     72 /*	DICT_MAPNAMES_EXTEND_FN dict_mapnames_extend(call_back)
     73 /*	DICT_MAPNAMES_EXTEND_FN call_back;
     74 /*
     75 /*	int	dict_isjmp(dict)
     76 /*	DICT	*dict;
     77 /*
     78 /*	int	dict_setjmp(dict)
     79 /*	DICT	*dict;
     80 /*
     81 /*	int	dict_longjmp(dict, val)
     82 /*	DICT	*dict;
     83 /*	int	val;
     84 /*
     85 /*	void	dict_type_override(dict, type)
     86 /*	DICT	*dict;
     87 /*	const char *type;
     88 /* DESCRIPTION
     89 /*	This module implements a low-level interface to multiple
     90 /*	dictionary types.
     91 /*
     92 /*	In addition to providing a mapping from type names to
     93 /*	implementations, this module deduplicates requests to open a
     94 /*	dictionary with the same fingerprint (type, name, and initial
     95 /*	flags), and manages the dictionary life cycle using reference
     96 /*	counts maintained with dict_(un)register().
     97 /*
     98 /*	The fingerprint, generated with dict_make_registered_name()
     99 /*	and available as DICT.reg_name, may be used in dict_handle()
    100 /*	calls.
    101 /*
    102 /*	dict_open() takes a type:name pair that specifies a dictionary type
    103 /*	and dictionary name, opens the dictionary, and returns a dictionary
    104 /*	handle.  The \fIopen_flags\fR arguments are as in open(2). The
    105 /*	\fIdict_flags\fR are the bit-wise OR of zero or more of the following:
    106 /* .IP DICT_FLAG_DUP_WARN
    107 /*	Warn about duplicate keys, if the underlying database does not
    108 /*	support duplicate keys. The default is to terminate with a fatal
    109 /*	error.
    110 /* .IP DICT_FLAG_DUP_IGNORE
    111 /*	Ignore duplicate keys if the underlying database does not
    112 /*	support duplicate keys. The default is to terminate with a fatal
    113 /*	error.
    114 /* .IP DICT_FLAG_DUP_REPLACE
    115 /*	Replace duplicate keys if the underlying database supports such
    116 /*	an operation. The default is to terminate with a fatal error.
    117 /* .IP DICT_FLAG_TRY0NULL
    118 /*	With maps where this is appropriate, append no null byte to
    119 /*	keys and values.
    120 /*	When neither DICT_FLAG_TRY0NULL nor DICT_FLAG_TRY1NULL are
    121 /*	specified, the software guesses what format to use for reading;
    122 /*	and in the absence of definite information, a system-dependent
    123 /*	default is chosen for writing.
    124 /* .IP DICT_FLAG_TRY1NULL
    125 /*	With maps where this is appropriate, append one null byte to
    126 /*	keys and values.
    127 /*	When neither DICT_FLAG_TRY0NULL nor DICT_FLAG_TRY1NULL are
    128 /*	specified, the software guesses what format to use for reading;
    129 /*	and in the absence of definite information, a system-dependent
    130 /*	default is chosen for writing.
    131 /* .IP DICT_FLAG_LOCK
    132 /*	With maps where this is appropriate, acquire an exclusive lock
    133 /*	before writing, and acquire a shared lock before reading.
    134 /*	Release the lock when the operation completes.
    135 /* .IP DICT_FLAG_OPEN_LOCK
    136 /*	The behavior of this flag depends on whether a database
    137 /*	sets the DICT_FLAG_MULTI_WRITER flag to indicate that it
    138 /*	is multi-writer safe.
    139 /*
    140 /*	With databases that are not multi-writer safe, dict_open()
    141 /*	acquires a persistent exclusive lock, or it terminates with
    142 /*	a fatal run-time error.
    143 /*
    144 /*	With databases that are multi-writer safe, dict_open()
    145 /*	downgrades the DICT_FLAG_OPEN_LOCK flag (persistent lock)
    146 /*	to DICT_FLAG_LOCK (temporary lock).
    147 /* .IP DICT_FLAG_FOLD_FIX
    148 /*	With databases whose lookup fields are fixed-case strings,
    149 /*	fold the search string to lower case before accessing the
    150 /*	database.  This includes hash:, cdb:, dbm:. nis:, ldap:,
    151 /*	*sql. WARNING: case folding is supported only for ASCII or
    152 /*	valid UTF-8.
    153 /* .IP DICT_FLAG_FOLD_MUL
    154 /*	With databases where one lookup field can match both upper
    155 /*	and lower case, fold the search key to lower case before
    156 /*	accessing the database. This includes regexp: and pcre:.
    157 /*	WARNING: case folding is supported only for ASCII or valid
    158 /*	UTF-8.
    159 /* .IP DICT_FLAG_FOLD_ANY
    160 /*	Short-hand for (DICT_FLAG_FOLD_FIX | DICT_FLAG_FOLD_MUL).
    161 /* .IP DICT_FLAG_SYNC_UPDATE
    162 /*	With file-based maps, flush I/O buffers to file after each update.
    163 /*	Thus feature is not supported with some file-based dictionaries.
    164 /* .IP DICT_FLAG_NO_REGSUB
    165 /*	Disallow regular expression substitution from the lookup string
    166 /*	into the lookup result, to block data injection attacks.
    167 /* .IP DICT_FLAG_NO_PROXY
    168 /*	Disallow access through the unprivileged \fBproxymap\fR
    169 /*	service, to block privilege escalation attacks.
    170 /* .IP DICT_FLAG_NO_UNAUTH
    171 /*	Disallow lookup mechanisms that lack any form of authentication,
    172 /*	to block privilege escalation attacks (example: tcp_table;
    173 /*	even NIS can be secured to some extent by requiring that
    174 /*	the server binds to a privileged port).
    175 /* .IP DICT_FLAG_PARANOID
    176 /*	A combination of all the paranoia flags: DICT_FLAG_NO_REGSUB,
    177 /*	DICT_FLAG_NO_PROXY and DICT_FLAG_NO_UNAUTH.
    178 /* .IP DICT_FLAG_BULK_UPDATE
    179 /*	Enable preliminary code for bulk-mode database updates.
    180 /*	The caller must create an exception handler with dict_jmp_alloc()
    181 /*	and must trap exceptions from the database client with dict_setjmp().
    182 /* .IP DICT_FLAG_UTF8_REQUEST
    183 /*	With util_utf8_enable != 0, require that lookup/update/delete
    184 /*	keys and values are valid UTF-8. Skip a lookup/update/delete
    185 /*	request with a non-UTF-8 key, skip an update request with
    186 /*	a non-UTF-8 value, and fail a lookup request with a non-UTF-8
    187 /*	value.
    188 /* .IP DICT_FLAG_SRC_RHS_IS_FILE
    189 /*	With dictionaries that are created from source text, each
    190 /*	value in the source of a dictionary specifies a list of
    191 /*	file names separated by comma and/or whitespace. The file
    192 /*	contents are concatenated with a newline inserted between
    193 /*	files, and the base64-encoded result is stored under the
    194 /*	key.
    195 /* .sp
    196 /*	NOTE 1: it is up to the application to decode lookup results
    197 /*	with dict_file_lookup() or equivalent (this requires that
    198 /*	the dictionary is opened with DICT_FLAG_SRC_RHS_IS_FILE).
    199 /*	Decoding is not built into the normal dictionary lookup
    200 /*	method, because that would complicate dictionary nesting,
    201 /*	pipelining, and proxying.
    202 /* .sp
    203 /*	NOTE 2: it is up to the application to convert file names
    204 /*	into base64-encoded file content before calling the dictionary
    205 /*	update method (see dict_file(3) for support). Automatic
    206 /*	file content encoding is available only when a dictionary
    207 /*	is created from source text.
    208 /* .PP
    209 /*	Specify DICT_FLAG_NONE for no special processing.
    210 /*
    211 /*	The dictionary types are as follows:
    212 /* .IP environ
    213 /*	The process environment array. The \fIdict_name\fR argument is ignored.
    214 /* .IP dbm
    215 /*	DBM file.
    216 /* .IP hash
    217 /*	Berkeley DB file in hash format.
    218 /* .IP btree
    219 /*	Berkeley DB file in btree format.
    220 /* .IP nis
    221 /*	NIS map. Only read access is supported.
    222 /* .IP nisplus
    223 /*	NIS+ map. Only read access is supported.
    224 /* .IP netinfo
    225 /*	NetInfo table. Only read access is supported.
    226 /* .IP ldap
    227 /*	LDAP ("light-weight" directory access protocol) database access.
    228 /* .IP pcre
    229 /*	PERL-compatible regular expressions.
    230 /* .IP regexp
    231 /*	POSIX-compatible regular expressions.
    232 /* .IP texthash
    233 /*	Flat text in postmap(1) input format.
    234 /* .PP
    235 /*	dict_open3() takes separate arguments for dictionary type and
    236 /*	name, but otherwise performs the same functions as dict_open().
    237 /*
    238 /*	The dict_get(), dict_put(), dict_del(), and dict_seq()
    239 /*	macros evaluate their first argument multiple times.
    240 /*	These names should have been in uppercase.
    241 /*
    242 /*	dict_get() retrieves the value stored in the named dictionary
    243 /*	under the given key. A null pointer means the value was not found.
    244 /*	As with dict_lookup(), the result is owned by the lookup table
    245 /*	implementation. Make a copy if the result is to be modified,
    246 /*	or if the result is to survive multiple table lookups.
    247 /*
    248 /*	dict_put() stores the specified key and value into the named
    249 /*	dictionary. A zero (DICT_STAT_SUCCESS) result means the
    250 /*	update was made.
    251 /*
    252 /*	dict_del() removes a dictionary entry, and returns
    253 /*	DICT_STAT_SUCCESS in case of success.
    254 /*
    255 /*	dict_seq() iterates over all members in the named dictionary.
    256 /*	func is define DICT_SEQ_FUN_FIRST (select first member) or
    257 /*	DICT_SEQ_FUN_NEXT (select next member). A zero (DICT_STAT_SUCCESS)
    258 /*	result means that an entry was found.
    259 /*
    260 /*	dict_close() closes the specified dictionary and cleans up the
    261 /*	associated data structures.
    262 /*
    263 /*	dict_open_register() adds support for a new dictionary type.
    264 /*	NOTE: this function does not copy its argument. It is an error
    265 /*	to add an existing type.
    266 /*
    267 /*	dict_open_unregister() removes support for a dictionary type.
    268 /*	NOTE: it is an error to delete a non-existent type.
    269 /*
    270 /*	dict_open_lookup() returns a pointer to the DICT_OPEN_INFO
    271 /*	for the specified dictionary type, or a null pointer if the
    272 /*	requested information is not found.
    273 /*
    274 /*	dict_open_extend() registers a call-back function that looks
    275 /*	up the dictionary open() function for a type that is not
    276 /*	registered, or null in case of error. The result value is
    277 /*	the last previously-registered call-back or null.
    278 /*
    279 /*	dict_mapnames() returns a sorted list with the names of all available
    280 /*	dictionary types.
    281 /*
    282 /*	dict_mapnames_extend() registers a call-back function that
    283 /*	enumerates additional dictionary type names. The result
    284 /*	will be sorted by dict_mapnames().  The result value
    285 /*	is the last previously-registered call-back or null.
    286 /*
    287 /*	dict_setjmp() saves processing context and makes that context
    288 /*	available for use with dict_longjmp().  Normally, dict_setjmp()
    289 /*	returns zero.  A non-zero result means that dict_setjmp()
    290 /*	returned through a dict_longjmp() call; the result is the
    291 /*	\fIval\fR argument given to dict_longjmp(). dict_isjmp()
    292 /*	returns non-zero when dict_setjmp() and dict_longjmp()
    293 /*	are enabled for a given dictionary.
    294 /*
    295 /*	NB: non-local jumps such as dict_longjmp() are not safe for
    296 /*	jumping out of any routine that manipulates DICT data.
    297 /*	longjmp() like calls are best avoided in signal handlers.
    298 /*
    299 /*	dict_type_override() changes the symbolic dictionary type.
    300 /*	This is used by dictionaries whose internals are based on
    301 /*	some other dictionary type. dict_type_override() requires that
    302 /*	the dictionary is not already registered with dict_register(),
    303 /*	i.e., it must be the result from dict_xxx_open(), not from
    304 /*	dict_open(). If needed in the future, this limitation may
    305 /*	be lifted.
    306 /* DIAGNOSTICS
    307 /*	Fatal error: open error, unsupported dictionary type, attempt to
    308 /*	update non-writable dictionary, attempt to unregister a type
    309 /*	that is not registered.
    310 /*
    311 /*	The lookup routine returns non-null when the request is
    312 /*	satisfied. The update, delete and sequence routines return
    313 /*	zero (DICT_STAT_SUCCESS) when the request is satisfied.
    314 /*	The dict->errno value is non-zero only when the last operation
    315 /*	was not satisfied due to a dictionary access error. This
    316 /*	can have the following values:
    317 /* .IP DICT_ERR_NONE(zero)
    318 /*	There was no dictionary access error. For example, the
    319 /*	request was satisfied, the requested information did not
    320 /*	exist in the dictionary, or the information already existed
    321 /*	when it should not exist (collision).
    322 /* .IP DICT_ERR_RETRY(<0)
    323 /*	The dictionary was temporarily unavailable. This can happen
    324 /*	with network-based services.
    325 /* .IP DICT_ERR_CONFIG(<0)
    326 /*	The dictionary was unavailable due to a configuration error.
    327 /* .PP
    328 /*	Generally, a program is expected to test the function result
    329 /*	value for "success" first. If the operation was not successful,
    330 /*	a program is expected to test for a non-zero dict->error
    331 /*	status to distinguish between a data notfound/collision
    332 /*	condition or a dictionary access error.
    333 /* LICENSE
    334 /* .ad
    335 /* .fi
    336 /*	The Secure Mailer license must be distributed with this software.
    337 /* AUTHOR(S)
    338 /*	Wietse Venema
    339 /*	IBM T.J. Watson Research
    340 /*	P.O. Box 704
    341 /*	Yorktown Heights, NY 10598, USA
    342 /*
    343 /*	Wietse Venema
    344 /*	Google, Inc.
    345 /*	111 8th Avenue
    346 /*	New York, NY 10011, USA
    347 /*--*/
    348 
    349 /* System library. */
    350 
    351 #include <sys_defs.h>
    352 #include <string.h>
    353 #include <stdlib.h>
    354 
    355 /* Utility library. */
    356 
    357 #include <argv.h>
    358 #include <mymalloc.h>
    359 #include <msg.h>
    360 #include <dict.h>
    361 #include <dict_cdb.h>
    362 #include <dict_debug.h>
    363 #include <dict_env.h>
    364 #include <dict_unix.h>
    365 #include <dict_tcp.h>
    366 #include <dict_sdbm.h>
    367 #include <dict_dbm.h>
    368 #include <dict_db.h>
    369 #include <dict_lmdb.h>
    370 #include <dict_nis.h>
    371 #include <dict_nisplus.h>
    372 #include <dict_ni.h>
    373 #include <dict_pcre.h>
    374 #include <dict_regexp.h>
    375 #include <dict_static.h>
    376 #include <dict_cidr.h>
    377 #include <dict_ht.h>
    378 #include <dict_thash.h>
    379 #include <dict_sockmap.h>
    380 #include <dict_fail.h>
    381 #include <dict_pipe.h>
    382 #include <dict_random.h>
    383 #include <dict_union.h>
    384 #include <dict_inline.h>
    385 #include <stringops.h>
    386 #include <split_at.h>
    387 #include <htable.h>
    388 #include <myflock.h>
    389 #include <mkmap.h>
    390 
    391  /*
    392   * lookup table for available map types.
    393   */
    394 static const DICT_OPEN_INFO dict_open_info[] = {
    395     DICT_TYPE_ENVIRON, dict_env_open, 0,
    396     DICT_TYPE_HT, dict_ht_open, 0,
    397     DICT_TYPE_UNIX, dict_unix_open, 0,
    398     DICT_TYPE_TCP, dict_tcp_open, 0,
    399 #ifdef HAS_DBM
    400     DICT_TYPE_DBM, dict_dbm_open, mkmap_dbm_open,
    401 #endif
    402 #ifdef HAS_DB
    403     DICT_TYPE_HASH, dict_hash_open, mkmap_hash_open,
    404     DICT_TYPE_BTREE, dict_btree_open, mkmap_btree_open,
    405 #endif
    406 #ifdef HAS_NIS
    407     DICT_TYPE_NIS, dict_nis_open, 0,
    408 #endif
    409 #ifdef HAS_NISPLUS
    410     DICT_TYPE_NISPLUS, dict_nisplus_open, 0,
    411 #endif
    412 #ifdef HAS_NETINFO
    413     DICT_TYPE_NETINFO, dict_ni_open, 0,
    414 #endif
    415 #ifdef HAS_POSIX_REGEXP
    416     DICT_TYPE_REGEXP, dict_regexp_open, 0,
    417 #endif
    418     DICT_TYPE_STATIC, dict_static_open, 0,
    419     DICT_TYPE_CIDR, dict_cidr_open, 0,
    420     DICT_TYPE_THASH, dict_thash_open, 0,
    421     DICT_TYPE_SOCKMAP, dict_sockmap_open, 0,
    422     DICT_TYPE_FAIL, dict_fail_open, mkmap_fail_open,
    423     DICT_TYPE_PIPE, dict_pipe_open, 0,
    424     DICT_TYPE_RANDOM, dict_random_open, 0,
    425     DICT_TYPE_UNION, dict_union_open, 0,
    426     DICT_TYPE_INLINE, dict_inline_open, 0,
    427 #ifndef USE_DYNAMIC_MAPS
    428 #ifdef HAS_PCRE
    429     DICT_TYPE_PCRE, dict_pcre_open, 0,
    430 #endif
    431 #ifdef HAS_CDB
    432     DICT_TYPE_CDB, dict_cdb_open, mkmap_cdb_open,
    433 #endif
    434 #ifdef HAS_SDBM
    435     DICT_TYPE_SDBM, dict_sdbm_open, mkmap_sdbm_open,
    436 #endif
    437 #ifdef HAS_LMDB
    438     DICT_TYPE_LMDB, dict_lmdb_open, mkmap_lmdb_open,
    439 #endif
    440 #endif					/* !USE_DYNAMIC_MAPS */
    441     DICT_TYPE_DEBUG, dict_debug_open, 0,
    442     0,
    443 };
    444 
    445 static HTABLE *dict_open_hash;
    446 
    447  /*
    448   * Extension hooks.
    449   */
    450 static DICT_OPEN_EXTEND_FN dict_open_extend_hook;
    451 static DICT_MAPNAMES_EXTEND_FN dict_mapnames_extend_hook;
    452 
    453  /*
    454   * Workaround: define global variables here to control database cache sizes.
    455   * When a database driver is dynamically loaded, global control variables
    456   * cannot simply be owned by the loadable objects because that would result
    457   * in build-time linker errors.
    458   */
    459 DEFINE_DICT_LMDB_MAP_SIZE;
    460 DEFINE_DICT_DB_CACHE_SIZE;
    461 
    462  /*
    463   * Replace obscure code with a more readable expression.
    464   */
    465 #define NEED_DICT_OPEN_INIT()	(dict_open_hash == 0)
    466 
    467 /* dict_open_init - one-off initialization */
    468 
    469 static void dict_open_init(void)
    470 {
    471     const char *myname = "dict_open_init";
    472     const DICT_OPEN_INFO *dp;
    473 
    474     if (!NEED_DICT_OPEN_INIT())
    475 	msg_panic("%s: multiple initialization", myname);
    476     dict_open_hash = htable_create(10);
    477 
    478     for (dp = dict_open_info; dp->type; dp++)
    479 	htable_enter(dict_open_hash, dp->type, (void *) dp);
    480 }
    481 
    482 /* dict_open - open dictionary */
    483 
    484 DICT   *dict_open(const char *dict_spec, int open_flags, int dict_flags)
    485 {
    486     char   *saved_dict_spec = mystrdup(dict_spec);
    487     char   *dict_name;
    488     DICT   *dict;
    489 
    490     if ((dict_name = split_at(saved_dict_spec, ':')) == 0)
    491 	msg_fatal("open dictionary: expecting \"type:name\" form instead of \"%s\"",
    492 		  dict_spec);
    493 
    494     dict = dict_open3(saved_dict_spec, dict_name, open_flags, dict_flags);
    495     myfree(saved_dict_spec);
    496     return (dict);
    497 }
    498 
    499 /* dict_open3 - open dictionary */
    500 
    501 DICT   *dict_open3(const char *dict_type, const char *dict_name,
    502 		           int open_flags, int dict_flags)
    503 {
    504     const char *myname = "dict_open";
    505     const DICT_OPEN_INFO *dp;
    506     VSTRING *reg_name = vstring_alloc(100);
    507     DICT   *dict;
    508 
    509     /* Workaround for dict_proxy_open() with DICT_FLAG_NO_FILE. */
    510 #define DICT_OPEN3_RETURN(d) do { \
    511 	DICT *_d = (d); \
    512 	dict_register(_d->reg_name? _d->reg_name : vstring_str(reg_name), _d); \
    513 	vstring_free(reg_name); \
    514 	return (_d); \
    515     } while (0)
    516 
    517     /*
    518      * If the dictionary is already open, simply increase the reference count
    519      * to update an existing life cycle.
    520      */
    521     dict_make_registered_name4(reg_name, dict_type, dict_name,
    522 			       open_flags, dict_flags);
    523     if ((dict = dict_handle(vstring_str(reg_name))) != 0)
    524 	DICT_OPEN3_RETURN(dict);
    525 
    526     if (*dict_type == 0 || *dict_name == 0)
    527 	msg_fatal("open dictionary: expecting \"type:name\" form instead of \"%s:%s\"",
    528 		  dict_type, dict_name);
    529     if (NEED_DICT_OPEN_INIT())
    530 	dict_open_init();
    531     if ((dp = dict_open_lookup(dict_type)) == 0)
    532 	DICT_OPEN3_RETURN(dict_surrogate(dict_type, dict_name, open_flags,
    533 		 dict_flags, "unsupported dictionary type: %s", dict_type));
    534     if ((dict = dp->dict_fn(dict_name, open_flags, dict_flags)) == 0)
    535 	DICT_OPEN3_RETURN(dict_surrogate(dict_type, dict_name, open_flags,
    536 		dict_flags, "cannot open %s:%s: %m", dict_type, dict_name));
    537     if (msg_verbose)
    538 	msg_info("%s: %s:%s", myname, dict_type, dict_name);
    539     /* XXX The choice between wait-for-lock or no-wait is hard-coded. */
    540     if (dict->flags & DICT_FLAG_OPEN_LOCK) {
    541 	if (dict->flags & DICT_FLAG_LOCK)
    542 	    msg_panic("%s: attempt to open %s:%s with both \"open\" lock and \"access\" lock",
    543 		      myname, dict_type, dict_name);
    544 	/* Multi-writer safe map: downgrade persistent lock to temporary. */
    545 	if (dict->flags & DICT_FLAG_MULTI_WRITER) {
    546 	    dict->flags &= ~DICT_FLAG_OPEN_LOCK;
    547 	    dict->flags |= DICT_FLAG_LOCK;
    548 	}
    549 	/* Multi-writer unsafe map: acquire exclusive lock or bust. */
    550 	else if (dict->lock(dict, MYFLOCK_OP_EXCLUSIVE | MYFLOCK_OP_NOWAIT) < 0)
    551 	    msg_fatal("%s:%s: unable to get exclusive lock: %m",
    552 		      dict_type, dict_name);
    553     }
    554     /* Last step: insert proxy for UTF-8 syntax checks and casefolding. */
    555     if ((dict->flags & DICT_FLAG_UTF8_ACTIVE) == 0
    556 	&& DICT_NEED_UTF8_ACTIVATION(util_utf8_enable, dict_flags))
    557 	dict = dict_utf8_activate(dict);
    558     /* Register the result. */
    559     DICT_OPEN3_RETURN(dict);
    560 }
    561 
    562 /* dict_open_register - register dictionary type */
    563 
    564 void    dict_open_register(const DICT_OPEN_INFO *dp)
    565 {
    566     const char *myname = "dict_open_register";
    567 
    568     if (msg_verbose > 1)
    569 	msg_info("%s: %s", myname, dp->type);
    570     if (NEED_DICT_OPEN_INIT())
    571 	dict_open_init();
    572     if (htable_find(dict_open_hash, dp->type))
    573 	msg_panic("%s: dictionary type exists: %s", myname, dp->type);
    574     (void) htable_enter(dict_open_hash, dp->type, (void *) dp);
    575 }
    576 
    577 /* dict_open_unregister - unregister dictionary type */
    578 
    579 void    dict_open_unregister(const char *dict_type)
    580 {
    581     const char *myname = "dict_open_unregister";
    582 
    583     if (msg_verbose > 1)
    584 	msg_info("%s: %s", myname, dict_type);
    585     if (NEED_DICT_OPEN_INIT())
    586 	dict_open_init();
    587     htable_delete(dict_open_hash, dict_type, (void (*) (void *)) 0);
    588 }
    589 
    590 /* dict_open_lookup - look up DICT_OPEN_INFO for dictionary type */
    591 
    592 const DICT_OPEN_INFO *dict_open_lookup(const char *dict_type)
    593 {
    594     const char myname[] = "dict_open_lookup";
    595     const DICT_OPEN_INFO *dp;
    596 
    597     if (msg_verbose > 1)
    598 	msg_info("%s: %s", myname, dict_type);
    599     if (NEED_DICT_OPEN_INIT())
    600 	dict_open_init();
    601     if ((dp = (DICT_OPEN_INFO *) htable_find(dict_open_hash, dict_type)) == 0
    602 	&& dict_open_extend_hook != 0
    603 	&& (dp = dict_open_extend_hook(dict_type)) != 0)
    604 	dict_open_register(dp);
    605     return (dp);
    606 }
    607 
    608 /* dict_open_extend - register alternate dictionary search routine */
    609 
    610 DICT_OPEN_EXTEND_FN dict_open_extend(DICT_OPEN_EXTEND_FN new_cb)
    611 {
    612     DICT_OPEN_EXTEND_FN old_cb;
    613 
    614     old_cb = dict_open_extend_hook;
    615     dict_open_extend_hook = new_cb;
    616     return (old_cb);
    617 }
    618 
    619 /* dict_mapnames - return an ARGV of available map_names */
    620 
    621 ARGV   *dict_mapnames()
    622 {
    623     HTABLE_INFO **ht_info;
    624     HTABLE_INFO **ht;
    625     DICT_OPEN_INFO *dp;
    626     ARGV   *mapnames;
    627 
    628     if (NEED_DICT_OPEN_INIT())
    629 	dict_open_init();
    630     mapnames = argv_alloc(dict_open_hash->used + 1);
    631     for (ht_info = ht = htable_list(dict_open_hash); *ht; ht++) {
    632 	dp = (DICT_OPEN_INFO *) ht[0]->value;
    633 	argv_add(mapnames, dp->type, ARGV_END);
    634     }
    635     if (dict_mapnames_extend_hook != 0)
    636 	(void) dict_mapnames_extend_hook(mapnames);
    637     argv_qsort(mapnames, (ARGV_COMPAR_FN) 0);
    638     /* In case some drivers have been loaded dynamically. */
    639     argv_uniq(mapnames, (ARGV_COMPAR_FN) 0);
    640     myfree((void *) ht_info);
    641     argv_terminate(mapnames);
    642     return mapnames;
    643 }
    644 
    645 /* dict_mapnames_extend - register alternate dictionary type list routine */
    646 
    647 DICT_MAPNAMES_EXTEND_FN dict_mapnames_extend(DICT_MAPNAMES_EXTEND_FN new_cb)
    648 {
    649     DICT_MAPNAMES_EXTEND_FN old_cb;
    650 
    651     old_cb = dict_mapnames_extend_hook;
    652     dict_mapnames_extend_hook = new_cb;
    653     return (old_cb);
    654 }
    655 
    656 /* dict_type_override - disguise a dictionary type */
    657 
    658 void    dict_type_override(DICT *dict, const char *type)
    659 {
    660 
    661     /*
    662      * To lift this limitation, compute a new reg_name, and implement a move
    663      * (copy+delete) operation from the old reg_name to the new one. Also
    664      * handle the case that the new destination name is already in use. The
    665      * above should be encapsulated in code adjacent to dict_register().
    666      */
    667     if (dict->reg_name)
    668 	msg_panic("%s: %s:%s is already registered",
    669 		  __func__, dict->type, dict->name);
    670     myfree(dict->type);
    671     dict->type = mystrdup(type);
    672 }
    673 
    674 #ifdef TEST
    675 
    676  /*
    677   * Proof-of-concept test program.
    678   */
    679 int     main(int argc, char **argv)
    680 {
    681     dict_test(argc, argv);
    682     return (0);
    683 }
    684 
    685 #endif
    686