1 /* $NetBSD: dict.c,v 1.3 2020/03/18 19:05:21 christos Exp $ */
2
3 /*++
4 /* NAME
5 /* dict 3
6 /* SUMMARY
7 /* dictionary manager
8 /* SYNOPSIS
9 /* #include <dict.h>
10 /*
11 /* void dict_register(dict_name, dict_info)
12 /* const char *dict_name;
13 /* DICT *dict_info;
14 /*
15 /* DICT *dict_handle(dict_name)
16 /* const char *dict_name;
17 /*
18 /* void dict_unregister(dict_name)
19 /* const char *dict_name;
20 /*
21 /* int dict_update(dict_name, member, value)
22 /* const char *dict_name;
23 /* const char *member;
24 /* const char *value;
25 /*
26 /* const char *dict_lookup(dict_name, member)
27 /* const char *dict_name;
28 /* const char *member;
29 /*
30 /* int dict_delete(dict_name, member)
31 /* const char *dict_name;
32 /* const char *member;
33 /*
34 /* int dict_sequence(dict_name, func, member, value)
35 /* const char *dict_name;
36 /* int func;
37 /* const char **member;
38 /* const char **value;
39 /*
40 /* const char *dict_eval(dict_name, string, int recursive)
41 /* const char *dict_name;
42 /* const char *string;
43 /* int recursive;
44 /*
45 /* int dict_walk(action, context)
46 /* void (*action)(dict_name, dict_handle, context)
47 /* void *context;
48 /*
49 /* int dict_error(dict_name)
50 /* const char *dict_name;
51 /*
52 /* const char *dict_changed_name()
53 /*
54 /* void DICT_OWNER_AGGREGATE_INIT(aggregate)
55 /* DICT_OWNER aggregate;
56 /*
57 /* void DICT_OWNER_AGGREGATE_UPDATE(aggregate, source)
58 /* DICT_OWNER aggregate;
59 /* DICT_OWNER source;
60 /* AUXILIARY FUNCTIONS
61 /* int dict_load_file_xt(dict_name, path)
62 /* const char *dict_name;
63 /* const char *path;
64 /*
65 /* void dict_load_fp(dict_name, fp)
66 /* const char *dict_name;
67 /* VSTREAM *fp;
68 /*
69 /* const char *dict_flags_str(dict_flags)
70 /* int dict_flags;
71 /*
72 /* int dict_flags_mask(names)
73 /* const char *names;
74 /* DESCRIPTION
75 /* This module maintains a collection of name-value dictionaries.
76 /* Each dictionary has its own name and has its own methods to read
77 /* or update members. Examples of dictionaries that can be accessed
78 /* in this manner are the global UNIX-style process environment,
79 /* hash tables, NIS maps, DBM files, and so on. Dictionary values
80 /* are not limited to strings but can be arbitrary objects as long
81 /* as they can be represented by character pointers.
82 /* FEATURES
83 /* .fi
84 /* .ad
85 /* Notable features of this module are:
86 /* .IP "macro expansion (string-valued dictionaries only)"
87 /* Macros of the form $\fIname\fR can be expanded to the current
88 /* value of \fIname\fR. The forms $(\fIname\fR) and ${\fIname\fR} are
89 /* also supported.
90 /* .IP "unknown names"
91 /* An update request for an unknown dictionary name will trigger
92 /* the instantiation of an in-memory dictionary with that name.
93 /* A lookup request (including delete and sequence) for an
94 /* unknown dictionary will result in a "not found" and "no
95 /* error" result.
96 /* .PP
97 /* dict_register() adds a new dictionary, including access methods,
98 /* to the list of known dictionaries, or increments the reference
99 /* count for an existing (name, dictionary) pair. Otherwise, it is
100 /* an error to pass an existing name (this would cause a memory leak).
101 /*
102 /* dict_handle() returns the generic dictionary handle of the
103 /* named dictionary, or a null pointer when the named dictionary
104 /* is not found.
105 /*
106 /* dict_unregister() decrements the reference count of the named
107 /* dictionary. When the reference count reaches zero, dict_unregister()
108 /* breaks the (name, dictionary) association and executes the
109 /* dictionary's optional \fIremove\fR method.
110 /*
111 /* dict_update() updates the value of the named dictionary member.
112 /* The dictionary member and the named dictionary are instantiated
113 /* on the fly. The result value is zero (DICT_STAT_SUCCESS)
114 /* when the update was made.
115 /*
116 /* dict_lookup() returns the value of the named member (i.e. without
117 /* expanding macros in the member value). The \fIdict_name\fR argument
118 /* specifies the dictionary to search. The result is a null pointer
119 /* when no value is found, otherwise the result is owned by the
120 /* underlying dictionary method. Make a copy if the result is to be
121 /* modified, or if the result is to survive multiple dict_lookup() calls.
122 /*
123 /* dict_delete() removes the named member from the named dictionary.
124 /* The result value is zero (DICT_STAT_SUCCESS) when the member
125 /* was found.
126 /*
127 /* dict_sequence() steps through the named dictionary and returns
128 /* keys and values in some implementation-defined order. The func
129 /* argument is DICT_SEQ_FUN_FIRST to set the cursor to the first
130 /* entry or DICT_SEQ_FUN_NEXT to select the next entry. The result
131 /* is owned by the underlying dictionary method. Make a copy if the
132 /* result is to be modified, or if the result is to survive multiple
133 /* dict_sequence() calls. The result value is zero (DICT_STAT_SUCCESS)
134 /* when a member was found.
135 /*
136 /* dict_eval() expands macro references in the specified string.
137 /* The result is owned by the dictionary manager. Make a copy if the
138 /* result is to survive multiple dict_eval() calls. When the
139 /* \fIrecursive\fR argument is non-zero, macro references in macro
140 /* lookup results are expanded recursively.
141 /*
142 /* dict_walk() iterates over all registered dictionaries in some
143 /* arbitrary order, and invokes the specified action routine with
144 /* as arguments:
145 /* .IP "const char *dict_name"
146 /* Dictionary name.
147 /* .IP "DICT *dict_handle"
148 /* Generic dictionary handle.
149 /* .IP "char *context"
150 /* Application context from the caller.
151 /* .PP
152 /* dict_changed_name() returns non-zero when any dictionary needs to
153 /* be re-opened because it has changed or because it was unlinked.
154 /* A non-zero result is the name of a changed dictionary.
155 /*
156 /* dict_load_file_xt() reads name-value entries from the named file.
157 /* Lines that begin with whitespace are concatenated to the preceding
158 /* line (the newline is deleted).
159 /* Each entry is stored in the dictionary named by \fIdict_name\fR.
160 /* The result is zero if the file could not be opened.
161 /*
162 /* dict_load_fp() reads name-value entries from an open stream.
163 /* It has the same semantics as the dict_load_file_xt() function.
164 /*
165 /* dict_flags_str() returns a printable representation of the
166 /* specified dictionary flags. The result is overwritten upon
167 /* each call.
168 /*
169 /* dict_flags_mask() returns the bitmask for the specified
170 /* comma/space-separated dictionary flag names.
171 /* TRUST AND PROVENANCE
172 /* .ad
173 /* .fi
174 /* Each dictionary has an owner attribute that contains (status,
175 /* uid) information about the owner of a dictionary. The
176 /* status is one of the following:
177 /* .IP DICT_OWNER_TRUSTED
178 /* The dictionary is owned by a trusted user. The uid is zero,
179 /* and specifies a UNIX user ID.
180 /* .IP DICT_OWNER_UNTRUSTED
181 /* The dictionary is owned by an untrusted user. The uid is
182 /* non-zero, and specifies a UNIX user ID.
183 /* .IP DICT_OWNER_UNKNOWN
184 /* The dictionary is owned by an unspecified user. For example,
185 /* the origin is unauthenticated, or different parts of a
186 /* dictionary aggregate (see below) are owned by different
187 /* untrusted users. The uid is non-zero and does not specify
188 /* a UNIX user ID.
189 /* .PP
190 /* Note that dictionary ownership does not necessarily imply
191 /* ownership of lookup results. For example, a PCRE table may
192 /* be owned by the trusted root user, but the result of $number
193 /* expansion can contain data from an arbitrary remote SMTP
194 /* client. See dict_open(3) for how to disallow $number
195 /* expansions with security-sensitive operations.
196 /*
197 /* Two macros are available to help determine the provenance
198 /* and trustworthiness of a dictionary aggregate. The macros
199 /* are unsafe because they may evaluate arguments more than
200 /* once.
201 /*
202 /* DICT_OWNER_AGGREGATE_INIT() initialize aggregate owner
203 /* attributes to the highest trust level.
204 /*
205 /* DICT_OWNER_AGGREGATE_UPDATE() updates the aggregate owner
206 /* attributes with the attributes of the specified source, and
207 /* reduces the aggregate trust level as appropriate.
208 /* SEE ALSO
209 /* htable(3)
210 /* BUGS
211 /* DIAGNOSTICS
212 /* Fatal errors: out of memory, malformed macro name.
213 /*
214 /* The lookup routine returns non-null when the request is
215 /* satisfied. The update, delete and sequence routines return
216 /* zero (DICT_STAT_SUCCESS) when the request is satisfied.
217 /* The dict_error() function returns non-zero only when the
218 /* last operation was not satisfied due to a dictionary access
219 /* error. The result can have the following values:
220 /* .IP DICT_ERR_NONE(zero)
221 /* There was no dictionary access error. For example, the
222 /* request was satisfied, the requested information did not
223 /* exist in the dictionary, or the information already existed
224 /* when it should not exist (collision).
225 /* .IP DICT_ERR_RETRY(<0)
226 /* The dictionary was temporarily unavailable. This can happen
227 /* with network-based services.
228 /* .IP DICT_ERR_CONFIG(<0)
229 /* The dictionary was unavailable due to a configuration error.
230 /* .PP
231 /* Generally, a program is expected to test the function result
232 /* value for "success" first. If the operation was not successful,
233 /* a program is expected to test for a non-zero dict->error
234 /* status to distinguish between a data notfound/collision
235 /* condition or a dictionary access error.
236 /* LICENSE
237 /* .ad
238 /* .fi
239 /* The Secure Mailer license must be distributed with this software.
240 /* AUTHOR(S)
241 /* Wietse Venema
242 /* IBM T.J. Watson Research
243 /* P.O. Box 704
244 /* Yorktown Heights, NY 10598, USA
245 /*--*/
246
247 /* System libraries. */
248
249 #include "sys_defs.h"
250 #include <sys/stat.h>
251 #include <fcntl.h>
252 #include <ctype.h>
253 #include <string.h>
254 #include <time.h>
255
256 /* Utility library. */
257
258 #include "msg.h"
259 #include "htable.h"
260 #include "mymalloc.h"
261 #include "vstream.h"
262 #include "vstring.h"
263 #include "readlline.h"
264 #include "mac_expand.h"
265 #include "stringops.h"
266 #include "iostuff.h"
267 #include "name_mask.h"
268 #include "dict.h"
269 #include "dict_ht.h"
270 #include "warn_stat.h"
271 #include "line_number.h"
272
273 static HTABLE *dict_table;
274
275 /*
276 * Each (name, dictionary) instance has a reference count. The count is part
277 * of the name, not the dictionary. The same dictionary may be registered
278 * under multiple names. The structure below keeps track of instances and
279 * reference counts.
280 */
281 typedef struct {
282 DICT *dict;
283 int refcount;
284 } DICT_NODE;
285
286 #define dict_node(dict) \
287 (dict_table ? (DICT_NODE *) htable_find(dict_table, dict) : 0)
288
289 /* Find a dictionary handle by name for lookup purposes. */
290
291 #define DICT_FIND_FOR_LOOKUP(dict, dict_name) do { \
292 DICT_NODE *node; \
293 if ((node = dict_node(dict_name)) != 0) \
294 dict = node->dict; \
295 else \
296 dict = 0; \
297 } while (0)
298
299 /* Find a dictionary handle by name for update purposes. */
300
301 #define DICT_FIND_FOR_UPDATE(dict, dict_name) do { \
302 DICT_NODE *node; \
303 if ((node = dict_node(dict_name)) == 0) { \
304 dict = dict_ht_open(dict_name, O_CREAT | O_RDWR, 0); \
305 dict_register(dict_name, dict); \
306 } else \
307 dict = node->dict; \
308 } while (0)
309
310 #define STR(x) vstring_str(x)
311
312 /* dict_register - make association with dictionary */
313
dict_register(const char * dict_name,DICT * dict_info)314 void dict_register(const char *dict_name, DICT *dict_info)
315 {
316 const char *myname = "dict_register";
317 DICT_NODE *node;
318
319 if (dict_table == 0)
320 dict_table = htable_create(0);
321 if ((node = dict_node(dict_name)) == 0) {
322 node = (DICT_NODE *) mymalloc(sizeof(*node));
323 node->dict = dict_info;
324 node->refcount = 0;
325 htable_enter(dict_table, dict_name, (void *) node);
326 } else if (dict_info != node->dict)
327 msg_fatal("%s: dictionary name exists: %s", myname, dict_name);
328 node->refcount++;
329 if (msg_verbose > 1)
330 msg_info("%s: %s %d", myname, dict_name, node->refcount);
331 }
332
333 /* dict_handle - locate generic dictionary handle */
334
dict_handle(const char * dict_name)335 DICT *dict_handle(const char *dict_name)
336 {
337 DICT_NODE *node;
338
339 return ((node = dict_node(dict_name)) != 0 ? node->dict : 0);
340 }
341
342 /* dict_node_free - dict_unregister() callback */
343
dict_node_free(void * ptr)344 static void dict_node_free(void *ptr)
345 {
346 DICT_NODE *node = (DICT_NODE *) ptr;
347 DICT *dict = node->dict;
348
349 if (dict->close)
350 dict->close(dict);
351 myfree((void *) node);
352 }
353
354 /* dict_unregister - break association with named dictionary */
355
dict_unregister(const char * dict_name)356 void dict_unregister(const char *dict_name)
357 {
358 const char *myname = "dict_unregister";
359 DICT_NODE *node;
360
361 if ((node = dict_node(dict_name)) == 0)
362 msg_panic("non-existing dictionary: %s", dict_name);
363 if (msg_verbose > 1)
364 msg_info("%s: %s %d", myname, dict_name, node->refcount);
365 if (--(node->refcount) == 0)
366 htable_delete(dict_table, dict_name, dict_node_free);
367 }
368
369 /* dict_update - replace or add dictionary entry */
370
dict_update(const char * dict_name,const char * member,const char * value)371 int dict_update(const char *dict_name, const char *member, const char *value)
372 {
373 const char *myname = "dict_update";
374 DICT *dict;
375
376 DICT_FIND_FOR_UPDATE(dict, dict_name);
377 if (msg_verbose > 1)
378 msg_info("%s: %s = %s", myname, member, value);
379 return (dict->update(dict, member, value));
380 }
381
382 /* dict_lookup - look up dictionary entry */
383
dict_lookup(const char * dict_name,const char * member)384 const char *dict_lookup(const char *dict_name, const char *member)
385 {
386 const char *myname = "dict_lookup";
387 DICT *dict;
388 const char *ret;
389
390 DICT_FIND_FOR_LOOKUP(dict, dict_name);
391 if (dict != 0) {
392 ret = dict->lookup(dict, member);
393 if (msg_verbose > 1)
394 msg_info("%s: %s = %s", myname, member, ret ? ret :
395 dict->error ? "(error)" : "(notfound)");
396 return (ret);
397 } else {
398 if (msg_verbose > 1)
399 msg_info("%s: %s = %s", myname, member, "(notfound)");
400 return (0);
401 }
402 }
403
404 /* dict_delete - delete dictionary entry */
405
dict_delete(const char * dict_name,const char * member)406 int dict_delete(const char *dict_name, const char *member)
407 {
408 const char *myname = "dict_delete";
409 DICT *dict;
410
411 DICT_FIND_FOR_LOOKUP(dict, dict_name);
412 if (msg_verbose > 1)
413 msg_info("%s: delete %s", myname, member);
414 return (dict ? dict->delete(dict, member) : DICT_STAT_FAIL);
415 }
416
417 /* dict_sequence - traverse dictionary */
418
dict_sequence(const char * dict_name,const int func,const char ** member,const char ** value)419 int dict_sequence(const char *dict_name, const int func,
420 const char **member, const char **value)
421 {
422 const char *myname = "dict_sequence";
423 DICT *dict;
424
425 DICT_FIND_FOR_LOOKUP(dict, dict_name);
426 if (msg_verbose > 1)
427 msg_info("%s: sequence func %d", myname, func);
428 return (dict ? dict->sequence(dict, func, member, value) : DICT_STAT_FAIL);
429 }
430
431 /* dict_error - return last error */
432
dict_error(const char * dict_name)433 int dict_error(const char *dict_name)
434 {
435 DICT *dict;
436
437 DICT_FIND_FOR_LOOKUP(dict, dict_name);
438 return (dict ? dict->error : DICT_ERR_NONE);
439 }
440
441 /* dict_load_file_xt - read entries from text file */
442
dict_load_file_xt(const char * dict_name,const char * path)443 int dict_load_file_xt(const char *dict_name, const char *path)
444 {
445 VSTREAM *fp;
446 struct stat st;
447 time_t before;
448 time_t after;
449
450 /*
451 * Read the file again if it is hot. This may result in reading a partial
452 * parameter name when a file changes in the middle of a read.
453 */
454 for (before = time((time_t *) 0); /* see below */ ; before = after) {
455 if ((fp = vstream_fopen(path, O_RDONLY, 0)) == 0)
456 return (0);
457 dict_load_fp(dict_name, fp);
458 if (fstat(vstream_fileno(fp), &st) < 0)
459 msg_fatal("fstat %s: %m", path);
460 if (vstream_ferror(fp) || vstream_fclose(fp))
461 msg_fatal("read %s: %m", path);
462 after = time((time_t *) 0);
463 if (st.st_mtime < before - 1 || st.st_mtime > after)
464 break;
465 if (msg_verbose > 1)
466 msg_info("pausing to let %s cool down", path);
467 doze(300000);
468 }
469 return (1);
470 }
471
472 /* dict_load_fp - read entries from open stream */
473
dict_load_fp(const char * dict_name,VSTREAM * fp)474 void dict_load_fp(const char *dict_name, VSTREAM *fp)
475 {
476 const char *myname = "dict_load_fp";
477 VSTRING *buf;
478 char *member;
479 char *val;
480 const char *old;
481 int last_line;
482 int lineno;
483 const char *err;
484 struct stat st;
485 DICT *dict;
486
487 /*
488 * Instantiate the dictionary even if the file is empty.
489 */
490 DICT_FIND_FOR_UPDATE(dict, dict_name);
491 buf = vstring_alloc(100);
492 last_line = 0;
493
494 if (fstat(vstream_fileno(fp), &st) < 0)
495 msg_fatal("fstat %s: %m", VSTREAM_PATH(fp));
496 while (readllines(buf, fp, &last_line, &lineno)) {
497 if ((err = split_nameval(STR(buf), &member, &val)) != 0)
498 msg_fatal("%s, line %d: %s: \"%s\"",
499 VSTREAM_PATH(fp),
500 lineno,
501 err, STR(buf));
502 if (msg_verbose > 1)
503 msg_info("%s: %s = %s", myname, member, val);
504 if ((old = dict->lookup(dict, member)) != 0
505 && strcmp(old, val) != 0)
506 msg_warn("%s, line %d: overriding earlier entry: %s=%s",
507 VSTREAM_PATH(fp), lineno, member, old);
508 if (dict->update(dict, member, val) != 0)
509 msg_fatal("%s, line %d: unable to update %s:%s",
510 VSTREAM_PATH(fp), lineno, dict->type, dict->name);
511 }
512 vstring_free(buf);
513 dict->owner.uid = st.st_uid;
514 dict->owner.status = (st.st_uid != 0);
515 }
516
517 /* dict_eval_lookup - macro parser call-back routine */
518
dict_eval_lookup(const char * key,int unused_type,void * context)519 static const char *dict_eval_lookup(const char *key, int unused_type,
520 void *context)
521 {
522 char *dict_name = (char *) context;
523 const char *pp = 0;
524 DICT *dict;
525
526 /*
527 * XXX how would one recover?
528 */
529 DICT_FIND_FOR_LOOKUP(dict, dict_name);
530 if (dict != 0
531 && (pp = dict->lookup(dict, key)) == 0 && dict->error != 0)
532 msg_fatal("dictionary %s: lookup %s: operation failed", dict_name, key);
533 return (pp);
534 }
535
536 /* dict_eval - expand embedded dictionary references */
537
dict_eval(const char * dict_name,const char * value,int recursive)538 const char *dict_eval(const char *dict_name, const char *value, int recursive)
539 {
540 const char *myname = "dict_eval";
541 static VSTRING *buf;
542 int status;
543
544 /*
545 * Initialize.
546 */
547 if (buf == 0)
548 buf = vstring_alloc(10);
549
550 /*
551 * Expand macros, possibly recursively.
552 */
553 #define DONT_FILTER (char *) 0
554
555 status = mac_expand(buf, value,
556 recursive ? MAC_EXP_FLAG_RECURSE : MAC_EXP_FLAG_NONE,
557 DONT_FILTER, dict_eval_lookup, (void *) dict_name);
558 if (status & MAC_PARSE_ERROR)
559 msg_fatal("dictionary %s: macro processing error", dict_name);
560 if (msg_verbose > 1) {
561 if (strcmp(value, STR(buf)) != 0)
562 msg_info("%s: expand %s -> %s", myname, value, STR(buf));
563 else
564 msg_info("%s: const %s", myname, value);
565 }
566 return (STR(buf));
567 }
568
569 /* dict_walk - iterate over all dictionaries in arbitrary order */
570
dict_walk(DICT_WALK_ACTION action,void * ptr)571 void dict_walk(DICT_WALK_ACTION action, void *ptr)
572 {
573 HTABLE_INFO **ht_info_list;
574 HTABLE_INFO **ht;
575 HTABLE_INFO *h;
576
577 ht_info_list = htable_list(dict_table);
578 for (ht = ht_info_list; (h = *ht) != 0; ht++)
579 action(h->key, (DICT *) h->value, ptr);
580 myfree((void *) ht_info_list);
581 }
582
583 /* dict_changed_name - see if any dictionary has changed */
584
dict_changed_name(void)585 const char *dict_changed_name(void)
586 {
587 const char *myname = "dict_changed_name";
588 struct stat st;
589 HTABLE_INFO **ht_info_list;
590 HTABLE_INFO **ht;
591 HTABLE_INFO *h;
592 const char *status;
593 DICT *dict;
594
595 ht_info_list = htable_list(dict_table);
596 for (status = 0, ht = ht_info_list; status == 0 && (h = *ht) != 0; ht++) {
597 dict = ((DICT_NODE *) h->value)->dict;
598 if (dict->stat_fd < 0) /* not file-based */
599 continue;
600 if (dict->mtime == 0) /* not bloody likely */
601 msg_warn("%s: table %s: null time stamp", myname, h->key);
602 if (fstat(dict->stat_fd, &st) < 0)
603 msg_fatal("%s: fstat: %m", myname);
604 if (((dict->flags & DICT_FLAG_MULTI_WRITER) == 0
605 && st.st_mtime != dict->mtime)
606 || st.st_nlink == 0)
607 status = h->key;
608 }
609 myfree((void *) ht_info_list);
610 return (status);
611 }
612
613 /* dict_changed - backwards compatibility */
614
dict_changed(void)615 int dict_changed(void)
616 {
617 return (dict_changed_name() != 0);
618 }
619
620 /*
621 * Mapping between flag names and flag values.
622 */
623 static const NAME_MASK dict_mask[] = {
624 "warn_dup", DICT_FLAG_DUP_WARN, /* if file, warn about dups */
625 "ignore_dup", DICT_FLAG_DUP_IGNORE, /* if file, ignore dups */
626 "try0null", DICT_FLAG_TRY0NULL, /* do not append 0 to key/value */
627 "try1null", DICT_FLAG_TRY1NULL, /* append 0 to key/value */
628 "fixed", DICT_FLAG_FIXED, /* fixed key map */
629 "pattern", DICT_FLAG_PATTERN, /* keys are patterns */
630 "lock", DICT_FLAG_LOCK, /* lock before access */
631 "replace", DICT_FLAG_DUP_REPLACE, /* if file, replace dups */
632 "sync_update", DICT_FLAG_SYNC_UPDATE, /* if file, sync updates */
633 "debug", DICT_FLAG_DEBUG, /* log access */
634 "no_regsub", DICT_FLAG_NO_REGSUB, /* disallow regexp substitution */
635 "no_proxy", DICT_FLAG_NO_PROXY, /* disallow proxy mapping */
636 "no_unauth", DICT_FLAG_NO_UNAUTH, /* disallow unauthenticated data */
637 "fold_fix", DICT_FLAG_FOLD_FIX, /* case-fold with fixed-case key map */
638 "fold_mul", DICT_FLAG_FOLD_MUL, /* case-fold with multi-case key map */
639 "open_lock", DICT_FLAG_OPEN_LOCK, /* permanent lock upon open */
640 "bulk_update", DICT_FLAG_BULK_UPDATE, /* bulk update if supported */
641 "multi_writer", DICT_FLAG_MULTI_WRITER, /* multi-writer safe */
642 "utf8_request", DICT_FLAG_UTF8_REQUEST, /* request UTF-8 activation */
643 "utf8_active", DICT_FLAG_UTF8_ACTIVE, /* UTF-8 is activated */
644 "src_rhs_is_file", DICT_FLAG_SRC_RHS_IS_FILE, /* value from file */
645 0,
646 };
647
648 /* dict_flags_str - convert bitmask to symbolic flag names */
649
dict_flags_str(int dict_flags)650 const char *dict_flags_str(int dict_flags)
651 {
652 static VSTRING *buf = 0;
653
654 if (buf == 0)
655 buf = vstring_alloc(1);
656
657 return (str_name_mask_opt(buf, "dictionary flags", dict_mask, dict_flags,
658 NAME_MASK_NUMBER | NAME_MASK_PIPE));
659 }
660
661 /* dict_flags_mask - convert symbolic flag names to bitmask */
662
dict_flags_mask(const char * names)663 int dict_flags_mask(const char *names)
664 {
665 return (name_mask("dictionary flags", dict_mask, names));
666 }
667