1 /* $NetBSD: dict_memcache.c,v 1.1.1.2 2014/07/06 19:27:50 tron Exp $ */ 2 3 /*++ 4 /* NAME 5 /* dict_memcache 3 6 /* SUMMARY 7 /* dictionary interface to memcaches 8 /* SYNOPSIS 9 /* #include <dict_memcache.h> 10 /* 11 /* DICT *dict_memcache_open(name, open_flags, dict_flags) 12 /* const char *name; 13 /* int open_flags; 14 /* int dict_flags; 15 /* DESCRIPTION 16 /* dict_memcache_open() opens a memcache, providing 17 /* a dictionary interface for Postfix key->value mappings. 18 /* The result is a pointer to the installed dictionary. 19 /* 20 /* Configuration parameters are described in memcache_table(5). 21 /* 22 /* Arguments: 23 /* .IP name 24 /* The path to the Postfix memcache configuration file. 25 /* .IP open_flags 26 /* O_RDONLY or O_RDWR. This function ignores flags that don't 27 /* specify a read, write or append mode. 28 /* .IP dict_flags 29 /* See dict_open(3). 30 /* SEE ALSO 31 /* dict(3) generic dictionary manager 32 /* HISTORY 33 /* .ad 34 /* .fi 35 /* The first memcache client for Postfix was written by Omar 36 /* Kilani, and was based on libmemcache. The current 37 /* implementation implements the memcache protocol directly, 38 /* and bears no resemblance to earlier work. 39 /* AUTHOR(S) 40 /* Wietse Venema 41 /* IBM T.J. Watson Research 42 /* P.O. Box 704 43 /* Yorktown Heights, NY 10598, USA 44 /*--*/ 45 46 /* System library. */ 47 48 #include <sys_defs.h> 49 #include <errno.h> 50 #include <string.h> 51 #include <ctype.h> 52 #include <stdio.h> /* XXX sscanf() */ 53 54 /* Utility library. */ 55 56 #include <msg.h> 57 #include <mymalloc.h> 58 #include <dict.h> 59 #include <vstring.h> 60 #include <stringops.h> 61 #include <auto_clnt.h> 62 #include <vstream.h> 63 64 /* Global library. */ 65 66 #include <cfg_parser.h> 67 #include <db_common.h> 68 #include <memcache_proto.h> 69 70 /* Application-specific. */ 71 72 #include <dict_memcache.h> 73 74 /* 75 * Structure of one memcache dictionary handle. 76 */ 77 typedef struct { 78 DICT dict; /* parent class */ 79 CFG_PARSER *parser; /* common parameter parser */ 80 void *dbc_ctxt; /* db_common context */ 81 char *key_format; /* query key translation */ 82 int timeout; /* client timeout */ 83 int mc_ttl; /* memcache update expiration */ 84 int mc_flags; /* memcache update flags */ 85 int err_pause; /* delay between errors */ 86 int max_tries; /* number of tries */ 87 int max_line; /* reply line limit */ 88 int max_data; /* reply data limit */ 89 char *memcache; /* memcache server spec */ 90 AUTO_CLNT *clnt; /* memcache client stream */ 91 VSTRING *clnt_buf; /* memcache client buffer */ 92 VSTRING *key_buf; /* lookup key */ 93 VSTRING *res_buf; /* lookup result */ 94 int error; /* memcache dict_errno */ 95 DICT *backup; /* persistent backup */ 96 } DICT_MC; 97 98 /* 99 * Memcache option defaults and names. 100 */ 101 #define DICT_MC_DEF_HOST "localhost" 102 #define DICT_MC_DEF_PORT "11211" 103 #define DICT_MC_DEF_MEMCACHE "inet:" DICT_MC_DEF_HOST ":" DICT_MC_DEF_PORT 104 #define DICT_MC_DEF_KEY_FMT "%s" 105 #define DICT_MC_DEF_MC_TTL 3600 106 #define DICT_MC_DEF_MC_TIMEOUT 2 107 #define DICT_MC_DEF_MC_FLAGS 0 108 #define DICT_MC_DEF_MAX_TRY 2 109 #define DICT_MC_DEF_MAX_LINE 1024 110 #define DICT_MC_DEF_MAX_DATA 10240 111 #define DICT_MC_DEF_ERR_PAUSE 1 112 113 #define DICT_MC_NAME_MEMCACHE "memcache" 114 #define DICT_MC_NAME_BACKUP "backup" 115 #define DICT_MC_NAME_KEY_FMT "key_format" 116 #define DICT_MC_NAME_MC_TTL "ttl" 117 #define DICT_MC_NAME_MC_TIMEOUT "timeout" 118 #define DICT_MC_NAME_MC_FLAGS "flags" 119 #define DICT_MC_NAME_MAX_TRY "max_try" 120 #define DICT_MC_NAME_MAX_LINE "line_size_limit" 121 #define DICT_MC_NAME_MAX_DATA "data_size_limit" 122 #define DICT_MC_NAME_ERR_PAUSE "retry_pause" 123 124 /* 125 * SLMs. 126 */ 127 #define STR(x) vstring_str(x) 128 #define LEN(x) VSTRING_LEN(x) 129 130 /*#define msg_verbose 1*/ 131 132 /* dict_memcache_set - set memcache key/value */ 133 134 static int dict_memcache_set(DICT_MC *dict_mc, const char *value, int ttl) 135 { 136 VSTREAM *fp; 137 int count; 138 int data_len = strlen(value); 139 140 /* 141 * Return a permanent error if we can't store this data. This results in 142 * loss of information. 143 */ 144 if (data_len > dict_mc->max_data) { 145 msg_warn("database %s:%s: data for key %s is too long (%s=%d) " 146 "-- not stored", DICT_TYPE_MEMCACHE, dict_mc->dict.name, 147 STR(dict_mc->key_buf), DICT_MC_NAME_MAX_DATA, 148 dict_mc->max_data); 149 /* Not stored! */ 150 DICT_ERR_VAL_RETURN(dict_mc, DICT_ERR_NONE, DICT_STAT_FAIL); 151 } 152 for (count = 0; count < dict_mc->max_tries; count++) { 153 if (count > 0) 154 sleep(dict_mc->err_pause); 155 if ((fp = auto_clnt_access(dict_mc->clnt)) == 0) { 156 break; 157 } else if (memcache_printf(fp, "set %s %d %d %ld", 158 STR(dict_mc->key_buf), dict_mc->mc_flags, ttl, data_len) < 0 159 || memcache_fwrite(fp, value, strlen(value)) < 0 160 || memcache_get(fp, dict_mc->clnt_buf, 161 dict_mc->max_line) < 0) { 162 if (count > 0) 163 msg_warn(errno ? "database %s:%s: I/O error: %m" : 164 "database %s:%s: I/O error", 165 DICT_TYPE_MEMCACHE, dict_mc->dict.name); 166 } else if (strcmp(STR(dict_mc->clnt_buf), "STORED") != 0) { 167 if (count > 0) 168 msg_warn("database %s:%s: update failed: %.30s", 169 DICT_TYPE_MEMCACHE, dict_mc->dict.name, 170 STR(dict_mc->clnt_buf)); 171 } else { 172 /* Victory! */ 173 DICT_ERR_VAL_RETURN(dict_mc, DICT_ERR_NONE, DICT_STAT_SUCCESS); 174 } 175 auto_clnt_recover(dict_mc->clnt); 176 } 177 DICT_ERR_VAL_RETURN(dict_mc, DICT_ERR_RETRY, DICT_STAT_ERROR); 178 } 179 180 /* dict_memcache_get - get memcache key/value */ 181 182 static const char *dict_memcache_get(DICT_MC *dict_mc) 183 { 184 VSTREAM *fp; 185 long todo; 186 int count; 187 188 for (count = 0; count < dict_mc->max_tries; count++) { 189 if (count > 0) 190 sleep(dict_mc->err_pause); 191 if ((fp = auto_clnt_access(dict_mc->clnt)) == 0) { 192 break; 193 } else if (memcache_printf(fp, "get %s", STR(dict_mc->key_buf)) < 0 194 || memcache_get(fp, dict_mc->clnt_buf, dict_mc->max_line) < 0) { 195 if (count > 0) 196 msg_warn(errno ? "database %s:%s: I/O error: %m" : 197 "database %s:%s: I/O error", 198 DICT_TYPE_MEMCACHE, dict_mc->dict.name); 199 } else if (strcmp(STR(dict_mc->clnt_buf), "END") == 0) { 200 /* Not found. */ 201 DICT_ERR_VAL_RETURN(dict_mc, DICT_ERR_NONE, (char *) 0); 202 } else if (sscanf(STR(dict_mc->clnt_buf), 203 "VALUE %*s %*s %ld", &todo) != 1 204 || todo < 0 || todo > dict_mc->max_data) { 205 if (count > 0) 206 msg_warn("%s: unexpected memcache server reply: %.30s", 207 dict_mc->dict.name, STR(dict_mc->clnt_buf)); 208 } else if (memcache_fread(fp, dict_mc->res_buf, todo) < 0) { 209 if (count > 0) 210 msg_warn("%s: EOF receiving memcache server reply", 211 dict_mc->dict.name); 212 } else { 213 /* Victory! */ 214 if (memcache_get(fp, dict_mc->clnt_buf, dict_mc->max_line) < 0 215 || strcmp(STR(dict_mc->clnt_buf), "END") != 0) 216 auto_clnt_recover(dict_mc->clnt); 217 DICT_ERR_VAL_RETURN(dict_mc, DICT_ERR_NONE, STR(dict_mc->res_buf)); 218 } 219 auto_clnt_recover(dict_mc->clnt); 220 } 221 DICT_ERR_VAL_RETURN(dict_mc, DICT_ERR_RETRY, (char *) 0); 222 } 223 224 /* dict_memcache_del - delete memcache key/value */ 225 226 static int dict_memcache_del(DICT_MC *dict_mc) 227 { 228 VSTREAM *fp; 229 int count; 230 231 for (count = 0; count < dict_mc->max_tries; count++) { 232 if (count > 0) 233 sleep(dict_mc->err_pause); 234 if ((fp = auto_clnt_access(dict_mc->clnt)) == 0) { 235 break; 236 } else if (memcache_printf(fp, "delete %s", STR(dict_mc->key_buf)) < 0 237 || memcache_get(fp, dict_mc->clnt_buf, dict_mc->max_line) < 0) { 238 if (count > 0) 239 msg_warn(errno ? "database %s:%s: I/O error: %m" : 240 "database %s:%s: I/O error", 241 DICT_TYPE_MEMCACHE, dict_mc->dict.name); 242 } else if (strcmp(STR(dict_mc->clnt_buf), "DELETED") == 0) { 243 /* Victory! */ 244 DICT_ERR_VAL_RETURN(dict_mc, DICT_ERR_NONE, DICT_STAT_SUCCESS); 245 } else if (strcmp(STR(dict_mc->clnt_buf), "NOT_FOUND") == 0) { 246 /* Not found! */ 247 DICT_ERR_VAL_RETURN(dict_mc, DICT_ERR_NONE, DICT_STAT_FAIL); 248 } else { 249 if (count > 0) 250 msg_warn("database %s:%s: delete failed: %.30s", 251 DICT_TYPE_MEMCACHE, dict_mc->dict.name, 252 STR(dict_mc->clnt_buf)); 253 } 254 auto_clnt_recover(dict_mc->clnt); 255 } 256 DICT_ERR_VAL_RETURN(dict_mc, DICT_ERR_RETRY, DICT_STAT_ERROR); 257 } 258 259 /* dict_memcache_prepare_key - prepare lookup key */ 260 261 static int dict_memcache_prepare_key(DICT_MC *dict_mc, const char *name) 262 { 263 264 /* 265 * Optionally case-fold the search string. 266 */ 267 if (dict_mc->dict.flags & DICT_FLAG_FOLD_FIX) { 268 if (dict_mc->dict.fold_buf == 0) 269 dict_mc->dict.fold_buf = vstring_alloc(10); 270 vstring_strcpy(dict_mc->dict.fold_buf, name); 271 name = lowercase(STR(dict_mc->dict.fold_buf)); 272 } 273 274 /* 275 * Optionally expand the query key format. 276 */ 277 #define DICT_MC_NO_KEY (0) 278 #define DICT_MC_NO_QUOTING ((void (*)(DICT *, const char *, VSTRING *)) 0) 279 280 if (dict_mc->key_format != 0 281 && strcmp(dict_mc->key_format, DICT_MC_DEF_KEY_FMT) != 0) { 282 VSTRING_RESET(dict_mc->key_buf); 283 if (db_common_expand(dict_mc->dbc_ctxt, dict_mc->key_format, 284 name, DICT_MC_NO_KEY, dict_mc->key_buf, 285 DICT_MC_NO_QUOTING) == 0) 286 return (0); 287 } else { 288 vstring_strcpy(dict_mc->key_buf, name); 289 } 290 291 /* 292 * The length indicates whether the expansion is empty or not. 293 */ 294 return (LEN(dict_mc->key_buf)); 295 } 296 297 /* dict_memcache_valid_key - validate key */ 298 299 static int dict_memcache_valid_key(DICT_MC *dict_mc, 300 const char *name, 301 const char *operation, 302 void (*log_func) (const char *,...)) 303 { 304 unsigned char *cp; 305 int rc; 306 307 #define DICT_MC_SKIP(why) do { \ 308 if (msg_verbose || log_func != msg_info) \ 309 log_func("%s: skipping %s for name \"%s\": %s", \ 310 dict_mc->dict.name, operation, name, (why)); \ 311 DICT_ERR_VAL_RETURN(dict_mc, DICT_ERR_NONE, 0); \ 312 } while (0) 313 314 if (*name == 0) 315 DICT_MC_SKIP("empty lookup key"); 316 if ((rc = db_common_check_domain(dict_mc->dbc_ctxt, name)) == 0) 317 DICT_MC_SKIP("domain mismatch"); 318 if (rc < 0) 319 DICT_ERR_VAL_RETURN(dict_mc, rc, 0); 320 if (dict_memcache_prepare_key(dict_mc, name) == 0) 321 DICT_MC_SKIP("empty lookup key expansion"); 322 for (cp = (unsigned char *) STR(dict_mc->key_buf); *cp; cp++) 323 if (isascii(*cp) && isspace(*cp)) 324 DICT_MC_SKIP("name contains space"); 325 326 DICT_ERR_VAL_RETURN(dict_mc, DICT_ERR_NONE, 1); 327 } 328 329 /* dict_memcache_update - update memcache */ 330 331 static int dict_memcache_update(DICT *dict, const char *name, 332 const char *value) 333 { 334 const char *myname = "dict_memcache_update"; 335 DICT_MC *dict_mc = (DICT_MC *) dict; 336 DICT *backup = dict_mc->backup; 337 int upd_res; 338 339 /* 340 * Skip updates with an inapplicable key, noisily. This results in loss 341 * of information. 342 */ 343 if (dict_memcache_valid_key(dict_mc, name, "update", msg_warn) == 0) 344 DICT_ERR_VAL_RETURN(dict, dict_mc->error, DICT_STAT_FAIL); 345 346 /* 347 * Update the memcache first. 348 */ 349 upd_res = dict_memcache_set(dict_mc, value, dict_mc->mc_ttl); 350 dict->error = dict_mc->error; 351 352 /* 353 * Update the backup database last. 354 */ 355 if (backup) { 356 upd_res = backup->update(backup, name, value); 357 dict->error = backup->error; 358 } 359 if (msg_verbose) 360 msg_info("%s: %s: update key \"%s\"(%s) => \"%s\" %s", 361 myname, dict_mc->dict.name, name, STR(dict_mc->key_buf), 362 value, dict_mc->error ? "(memcache error)" : (backup 363 && backup->error) ? "(backup error)" : "(no error)"); 364 365 return (upd_res); 366 } 367 368 /* dict_memcache_lookup - lookup memcache */ 369 370 static const char *dict_memcache_lookup(DICT *dict, const char *name) 371 { 372 const char *myname = "dict_memcache_lookup"; 373 DICT_MC *dict_mc = (DICT_MC *) dict; 374 DICT *backup = dict_mc->backup; 375 const char *retval; 376 377 /* 378 * Skip lookups with an inapplicable key, silently. This is just asking 379 * for information that cannot exist. 380 */ 381 if (dict_memcache_valid_key(dict_mc, name, "lookup", msg_info) == 0) 382 DICT_ERR_VAL_RETURN(dict, dict_mc->error, (char *) 0); 383 384 /* 385 * Search the memcache first. 386 */ 387 retval = dict_memcache_get(dict_mc); 388 dict->error = dict_mc->error; 389 390 /* 391 * Search the backup database last. Update the memcache if the data is 392 * found. 393 */ 394 if (backup) { 395 backup->error = 0; 396 if (retval == 0) { 397 retval = backup->lookup(backup, name); 398 dict->error = backup->error; 399 /* Update the cache. */ 400 if (retval != 0) 401 dict_memcache_set(dict_mc, retval, dict_mc->mc_ttl); 402 } 403 } 404 if (msg_verbose) 405 msg_info("%s: %s: key \"%s\"(%s) => %s", 406 myname, dict_mc->dict.name, name, STR(dict_mc->key_buf), 407 retval ? retval : dict_mc->error ? "(memcache error)" : 408 (backup && backup->error) ? "(backup error)" : "(not found)"); 409 410 return (retval); 411 } 412 413 /* dict_memcache_delete - delete memcache entry */ 414 415 static int dict_memcache_delete(DICT *dict, const char *name) 416 { 417 const char *myname = "dict_memcache_delete"; 418 DICT_MC *dict_mc = (DICT_MC *) dict; 419 DICT *backup = dict_mc->backup; 420 int del_res; 421 422 /* 423 * Skip lookups with an inapplicable key, noisily. This is just deleting 424 * information that cannot exist. 425 */ 426 if (dict_memcache_valid_key(dict_mc, name, "delete", msg_info) == 0) 427 DICT_ERR_VAL_RETURN(dict, dict_mc->error, dict_mc->error ? 428 DICT_STAT_ERROR : DICT_STAT_FAIL); 429 430 /* 431 * Update the memcache first. 432 */ 433 del_res = dict_memcache_del(dict_mc); 434 dict->error = dict_mc->error; 435 436 /* 437 * Update the persistent database last. 438 */ 439 if (backup) { 440 del_res = backup->delete(backup, name); 441 dict->error = backup->error; 442 } 443 if (msg_verbose) 444 msg_info("%s: %s: delete key \"%s\"(%s) => %s", 445 myname, dict_mc->dict.name, name, STR(dict_mc->key_buf), 446 dict_mc->error ? "(memcache error)" : (backup 447 && backup->error) ? "(backup error)" : "(no error)"); 448 449 return (del_res); 450 } 451 452 /* dict_memcache_sequence - first/next lookup */ 453 454 static int dict_memcache_sequence(DICT *dict, int function, const char **key, 455 const char **value) 456 { 457 const char *myname = "dict_memcache_sequence"; 458 DICT_MC *dict_mc = (DICT_MC *) dict; 459 DICT *backup = dict_mc->backup; 460 int seq_res; 461 462 if (backup == 0) { 463 msg_warn("database %s:%s: first/next support requires backup database", 464 DICT_TYPE_MEMCACHE, dict_mc->dict.name); 465 DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, DICT_STAT_FAIL); 466 } else { 467 seq_res = backup->sequence(backup, function, key, value); 468 if (msg_verbose) 469 msg_info("%s: %s: key \"%s\" => %s", 470 myname, dict_mc->dict.name, *key ? *key : "(not found)", 471 *value ? *value : backup->error ? "(backup error)" : 472 "(not found)"); 473 DICT_ERR_VAL_RETURN(dict, backup->error, seq_res); 474 } 475 } 476 477 /* dict_memcache_close - close memcache */ 478 479 static void dict_memcache_close(DICT *dict) 480 { 481 DICT_MC *dict_mc = (DICT_MC *) dict; 482 483 cfg_parser_free(dict_mc->parser); 484 db_common_free_ctx(dict_mc->dbc_ctxt); 485 if (dict_mc->key_format) 486 myfree(dict_mc->key_format); 487 myfree(dict_mc->memcache); 488 auto_clnt_free(dict_mc->clnt); 489 vstring_free(dict_mc->clnt_buf); 490 vstring_free(dict_mc->key_buf); 491 vstring_free(dict_mc->res_buf); 492 if (dict->fold_buf) 493 vstring_free(dict->fold_buf); 494 if (dict_mc->backup) 495 dict_close(dict_mc->backup); 496 dict_free(dict); 497 } 498 499 /* dict_memcache_open - open memcache */ 500 501 DICT *dict_memcache_open(const char *name, int open_flags, int dict_flags) 502 { 503 DICT_MC *dict_mc; 504 char *backup; 505 CFG_PARSER *parser; 506 507 /* 508 * Sanity checks. 509 */ 510 if (dict_flags & DICT_FLAG_NO_UNAUTH) 511 return (dict_surrogate(DICT_TYPE_MEMCACHE, name, open_flags, dict_flags, 512 "%s:%s map is not allowed for security-sensitive data", 513 DICT_TYPE_MEMCACHE, name)); 514 open_flags &= (O_RDONLY | O_RDWR | O_WRONLY | O_APPEND); 515 if (open_flags != O_RDONLY && open_flags != O_RDWR) 516 return (dict_surrogate(DICT_TYPE_MEMCACHE, name, open_flags, dict_flags, 517 "%s:%s map requires O_RDONLY or O_RDWR access mode", 518 DICT_TYPE_MEMCACHE, name)); 519 520 /* 521 * Open the configuration file. 522 */ 523 if ((parser = cfg_parser_alloc(name)) == 0) 524 return (dict_surrogate(DICT_TYPE_MEMCACHE, name, open_flags, dict_flags, 525 "open %s: %m", name)); 526 527 /* 528 * Create the dictionary object. 529 */ 530 dict_mc = (DICT_MC *) dict_alloc(DICT_TYPE_MEMCACHE, name, 531 sizeof(*dict_mc)); 532 dict_mc->dict.lookup = dict_memcache_lookup; 533 if (open_flags == O_RDWR) { 534 dict_mc->dict.update = dict_memcache_update; 535 dict_mc->dict.delete = dict_memcache_delete; 536 } 537 dict_mc->dict.sequence = dict_memcache_sequence; 538 dict_mc->dict.close = dict_memcache_close; 539 dict_mc->dict.flags = dict_flags; 540 dict_mc->key_buf = vstring_alloc(10); 541 dict_mc->res_buf = vstring_alloc(10); 542 543 /* 544 * Parse the configuration file. 545 */ 546 dict_mc->parser = parser; 547 dict_mc->key_format = cfg_get_str(dict_mc->parser, DICT_MC_NAME_KEY_FMT, 548 DICT_MC_DEF_KEY_FMT, 0, 0); 549 dict_mc->timeout = cfg_get_int(dict_mc->parser, DICT_MC_NAME_MC_TIMEOUT, 550 DICT_MC_DEF_MC_TIMEOUT, 0, 0); 551 dict_mc->mc_ttl = cfg_get_int(dict_mc->parser, DICT_MC_NAME_MC_TTL, 552 DICT_MC_DEF_MC_TTL, 0, 0); 553 dict_mc->mc_flags = cfg_get_int(dict_mc->parser, DICT_MC_NAME_MC_FLAGS, 554 DICT_MC_DEF_MC_FLAGS, 0, 0); 555 dict_mc->err_pause = cfg_get_int(dict_mc->parser, DICT_MC_NAME_ERR_PAUSE, 556 DICT_MC_DEF_ERR_PAUSE, 1, 0); 557 dict_mc->max_tries = cfg_get_int(dict_mc->parser, DICT_MC_NAME_MAX_TRY, 558 DICT_MC_DEF_MAX_TRY, 1, 0); 559 dict_mc->max_line = cfg_get_int(dict_mc->parser, DICT_MC_NAME_MAX_LINE, 560 DICT_MC_DEF_MAX_LINE, 1, 0); 561 dict_mc->max_data = cfg_get_int(dict_mc->parser, DICT_MC_NAME_MAX_DATA, 562 DICT_MC_DEF_MAX_DATA, 1, 0); 563 dict_mc->memcache = cfg_get_str(dict_mc->parser, DICT_MC_NAME_MEMCACHE, 564 DICT_MC_DEF_MEMCACHE, 0, 0); 565 566 /* 567 * Initialize the memcache client. 568 */ 569 dict_mc->clnt = auto_clnt_create(dict_mc->memcache, dict_mc->timeout, 0, 0); 570 dict_mc->clnt_buf = vstring_alloc(100); 571 572 /* 573 * Open the optional backup database. 574 */ 575 backup = cfg_get_str(dict_mc->parser, DICT_MC_NAME_BACKUP, 576 (char *) 0, 0, 0); 577 if (backup) { 578 dict_mc->backup = dict_open(backup, open_flags, dict_flags); 579 myfree(backup); 580 } else 581 dict_mc->backup = 0; 582 583 /* 584 * Parse templates and common database parameters. Maps that use 585 * substring keys should only be used with the full input key. 586 */ 587 dict_mc->dbc_ctxt = 0; 588 db_common_parse(&dict_mc->dict, &dict_mc->dbc_ctxt, 589 dict_mc->key_format, 1); 590 db_common_parse_domain(dict_mc->parser, dict_mc->dbc_ctxt); 591 if (db_common_dict_partial(dict_mc->dbc_ctxt)) 592 /* Breaks recipient delimiters */ 593 dict_mc->dict.flags |= DICT_FLAG_PATTERN; 594 else 595 dict_mc->dict.flags |= DICT_FLAG_FIXED; 596 597 dict_mc->dict.flags |= DICT_FLAG_MULTI_WRITER; 598 599 return (&dict_mc->dict); 600 } 601