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