1 /* $NetBSD: dict_db.c,v 1.4 2022/10/08 16:12:50 christos Exp $ */ 2 3 /*++ 4 /* NAME 5 /* dict_db 3 6 /* SUMMARY 7 /* dictionary manager interface to DB files 8 /* SYNOPSIS 9 /* #include <dict_db.h> 10 /* 11 /* extern int dict_db_cache_size; 12 /* 13 /* DEFINE_DICT_DB_CACHE_SIZE; 14 /* 15 /* DICT *dict_hash_open(path, open_flags, dict_flags) 16 /* const char *path; 17 /* int open_flags; 18 /* int dict_flags; 19 /* 20 /* DICT *dict_btree_open(path, open_flags, dict_flags) 21 /* const char *path; 22 /* int open_flags; 23 /* int dict_flags; 24 /* DESCRIPTION 25 /* dict_XXX_open() opens the specified DB database. The result is 26 /* a pointer to a structure that can be used to access the dictionary 27 /* using the generic methods documented in dict_open(3). 28 /* 29 /* The dict_db_cache_size variable specifies a non-default per-table 30 /* I/O buffer size. The default buffer size is adequate for reading. 31 /* For better performance while creating a large table, specify a large 32 /* buffer size before opening the file. 33 /* 34 /* This variable cannot be exported via the dict(3) API and 35 /* must therefore be defined in the calling program by invoking 36 /* the DEFINE_DICT_DB_CACHE_SIZE macro at the global level. 37 /* 38 /* Arguments: 39 /* .IP path 40 /* The database pathname, not including the ".db" suffix. 41 /* .IP open_flags 42 /* Flags passed to dbopen(). 43 /* .IP dict_flags 44 /* Flags used by the dictionary interface. 45 /* SEE ALSO 46 /* dict(3) generic dictionary manager 47 /* DIAGNOSTICS 48 /* Fatal errors: cannot open file, write error, out of memory. 49 /* LICENSE 50 /* .ad 51 /* .fi 52 /* The Secure Mailer license must be distributed with this software. 53 /* AUTHOR(S) 54 /* Wietse Venema 55 /* IBM T.J. Watson Research 56 /* P.O. Box 704 57 /* Yorktown Heights, NY 10598, USA 58 /* 59 /* Wietse Venema 60 /* Google, Inc. 61 /* 111 8th Avenue 62 /* New York, NY 10011, USA 63 /*--*/ 64 65 #include "sys_defs.h" 66 67 #ifdef HAS_DB 68 69 /* System library. */ 70 71 #include <sys/stat.h> 72 #include <limits.h> 73 #ifdef PATH_DB_H 74 #include PATH_DB_H 75 #else 76 #include <db.h> 77 #endif 78 #include <string.h> 79 #include <unistd.h> 80 #include <errno.h> 81 82 #if defined(_DB_185_H_) && defined(USE_FCNTL_LOCK) 83 #error "Error: this system must not use the db 1.85 compatibility interface" 84 #endif 85 86 #ifndef DB_VERSION_MAJOR 87 #define DB_VERSION_MAJOR 1 88 #define DICT_DB_GET(db, key, val, flag) db->get(db, key, val, flag) 89 #define DICT_DB_PUT(db, key, val, flag) db->put(db, key, val, flag) 90 #define DICT_DB_DEL(db, key, flag) db->del(db, key, flag) 91 #define DICT_DB_SYNC(db, flag) db->sync(db, flag) 92 #define DICT_DB_CLOSE(db) db->close(db) 93 #define DONT_CLOBBER R_NOOVERWRITE 94 #endif 95 96 #if DB_VERSION_MAJOR > 1 97 #define DICT_DB_GET(db, key, val, flag) sanitize(db->get(db, 0, key, val, flag)) 98 #define DICT_DB_PUT(db, key, val, flag) sanitize(db->put(db, 0, key, val, flag)) 99 #define DICT_DB_DEL(db, key, flag) sanitize(db->del(db, 0, key, flag)) 100 #define DICT_DB_SYNC(db, flag) ((errno = db->sync(db, flag)) ? -1 : 0) 101 #define DICT_DB_CLOSE(db) ((errno = db->close(db, 0)) ? -1 : 0) 102 #define DONT_CLOBBER DB_NOOVERWRITE 103 #endif 104 105 #if (DB_VERSION_MAJOR == 2 && DB_VERSION_MINOR < 6) 106 #define DICT_DB_CURSOR(db, curs) (db)->cursor((db), NULL, (curs)) 107 #else 108 #define DICT_DB_CURSOR(db, curs) (db)->cursor((db), NULL, (curs), 0) 109 #endif 110 111 #ifndef DB_FCNTL_LOCKING 112 #define DB_FCNTL_LOCKING 0 113 #endif 114 115 /* Utility library. */ 116 117 #include "msg.h" 118 #include "mymalloc.h" 119 #include "vstring.h" 120 #include "stringops.h" 121 #include "iostuff.h" 122 #include "myflock.h" 123 #include "dict.h" 124 #include "dict_db.h" 125 #include "warn_stat.h" 126 127 /* Application-specific. */ 128 129 typedef struct { 130 DICT dict; /* generic members */ 131 DB *db; /* open db file */ 132 #if DB_VERSION_MAJOR > 2 133 DB_ENV *dbenv; 134 #endif 135 #if DB_VERSION_MAJOR > 1 136 DBC *cursor; /* dict_db_sequence() */ 137 #endif 138 VSTRING *key_buf; /* key result */ 139 VSTRING *val_buf; /* value result */ 140 } DICT_DB; 141 142 #define SCOPY(buf, data, size) \ 143 vstring_str(vstring_strncpy(buf ? buf : (buf = vstring_alloc(10)), data, size)) 144 145 #define DICT_DB_NELM 4096 146 147 #if DB_VERSION_MAJOR > 1 148 149 /* sanitize - sanitize db_get/put/del result */ 150 151 static int sanitize(int status) 152 { 153 154 /* 155 * XXX This is unclean but avoids a lot of clutter elsewhere. Categorize 156 * results into non-fatal errors (i.e., errors that we can deal with), 157 * success, or fatal error (i.e., all other errors). 158 */ 159 switch (status) { 160 161 case DB_NOTFOUND: /* get, del */ 162 case DB_KEYEXIST: /* put */ 163 return (1); /* non-fatal */ 164 165 case 0: 166 return (0); /* success */ 167 168 case DB_KEYEMPTY: /* get, others? */ 169 status = EINVAL; 170 /* FALLTHROUGH */ 171 default: 172 errno = status; 173 return (-1); /* fatal */ 174 } 175 } 176 177 #endif 178 179 /* dict_db_lookup - find database entry */ 180 181 static const char *dict_db_lookup(DICT *dict, const char *name) 182 { 183 DICT_DB *dict_db = (DICT_DB *) dict; 184 DB *db = dict_db->db; 185 DBT db_key; 186 DBT db_value; 187 int status; 188 const char *result = 0; 189 190 dict->error = 0; 191 192 /* 193 * Sanity check. 194 */ 195 if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) 196 msg_panic("dict_db_lookup: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag"); 197 198 memset(&db_key, 0, sizeof(db_key)); 199 memset(&db_value, 0, sizeof(db_value)); 200 201 /* 202 * Optionally fold the key. 203 */ 204 if (dict->flags & DICT_FLAG_FOLD_FIX) { 205 if (dict->fold_buf == 0) 206 dict->fold_buf = vstring_alloc(10); 207 vstring_strcpy(dict->fold_buf, name); 208 name = lowercase(vstring_str(dict->fold_buf)); 209 } 210 211 /* 212 * Acquire a shared lock. 213 */ 214 if ((dict->flags & DICT_FLAG_LOCK) 215 && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0) 216 msg_fatal("%s: lock dictionary: %m", dict_db->dict.name); 217 218 /* 219 * See if this DB file was written with one null byte appended to key and 220 * value. 221 */ 222 if (dict->flags & DICT_FLAG_TRY1NULL) { 223 db_key.data = (void *) name; 224 db_key.size = strlen(name) + 1; 225 if ((status = DICT_DB_GET(db, &db_key, &db_value, 0)) < 0) 226 msg_fatal("error reading %s: %m", dict_db->dict.name); 227 if (status == 0) { 228 dict->flags &= ~DICT_FLAG_TRY0NULL; 229 result = SCOPY(dict_db->val_buf, db_value.data, db_value.size); 230 } 231 } 232 233 /* 234 * See if this DB file was written with no null byte appended to key and 235 * value. 236 */ 237 if (result == 0 && (dict->flags & DICT_FLAG_TRY0NULL)) { 238 db_key.data = (void *) name; 239 db_key.size = strlen(name); 240 if ((status = DICT_DB_GET(db, &db_key, &db_value, 0)) < 0) 241 msg_fatal("error reading %s: %m", dict_db->dict.name); 242 if (status == 0) { 243 dict->flags &= ~DICT_FLAG_TRY1NULL; 244 result = SCOPY(dict_db->val_buf, db_value.data, db_value.size); 245 } 246 } 247 248 /* 249 * Release the shared lock. 250 */ 251 if ((dict->flags & DICT_FLAG_LOCK) 252 && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) 253 msg_fatal("%s: unlock dictionary: %m", dict_db->dict.name); 254 255 return (result); 256 } 257 258 /* dict_db_update - add or update database entry */ 259 260 static int dict_db_update(DICT *dict, const char *name, const char *value) 261 { 262 DICT_DB *dict_db = (DICT_DB *) dict; 263 DB *db = dict_db->db; 264 DBT db_key; 265 DBT db_value; 266 int status; 267 268 dict->error = 0; 269 270 /* 271 * Sanity check. 272 */ 273 if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) 274 msg_panic("dict_db_update: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag"); 275 276 /* 277 * Optionally fold the key. 278 */ 279 if (dict->flags & DICT_FLAG_FOLD_FIX) { 280 if (dict->fold_buf == 0) 281 dict->fold_buf = vstring_alloc(10); 282 vstring_strcpy(dict->fold_buf, name); 283 name = lowercase(vstring_str(dict->fold_buf)); 284 } 285 memset(&db_key, 0, sizeof(db_key)); 286 memset(&db_value, 0, sizeof(db_value)); 287 db_key.data = (void *) name; 288 db_value.data = (void *) value; 289 db_key.size = strlen(name); 290 db_value.size = strlen(value); 291 292 /* 293 * If undecided about appending a null byte to key and value, choose a 294 * default depending on the platform. 295 */ 296 if ((dict->flags & DICT_FLAG_TRY1NULL) 297 && (dict->flags & DICT_FLAG_TRY0NULL)) { 298 #ifdef DB_NO_TRAILING_NULL 299 dict->flags &= ~DICT_FLAG_TRY1NULL; 300 #else 301 dict->flags &= ~DICT_FLAG_TRY0NULL; 302 #endif 303 } 304 305 /* 306 * Optionally append a null byte to key and value. 307 */ 308 if (dict->flags & DICT_FLAG_TRY1NULL) { 309 db_key.size++; 310 db_value.size++; 311 } 312 313 /* 314 * Acquire an exclusive lock. 315 */ 316 if ((dict->flags & DICT_FLAG_LOCK) 317 && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0) 318 msg_fatal("%s: lock dictionary: %m", dict_db->dict.name); 319 320 /* 321 * Do the update. 322 */ 323 if ((status = DICT_DB_PUT(db, &db_key, &db_value, 324 (dict->flags & DICT_FLAG_DUP_REPLACE) ? 0 : DONT_CLOBBER)) < 0) 325 msg_fatal("error writing %s: %m", dict_db->dict.name); 326 if (status) { 327 if (dict->flags & DICT_FLAG_DUP_IGNORE) 328 /* void */ ; 329 else if (dict->flags & DICT_FLAG_DUP_WARN) 330 msg_warn("%s: duplicate entry: \"%s\"", dict_db->dict.name, name); 331 else 332 msg_fatal("%s: duplicate entry: \"%s\"", dict_db->dict.name, name); 333 } 334 if (dict->flags & DICT_FLAG_SYNC_UPDATE) 335 if (DICT_DB_SYNC(db, 0) < 0) 336 msg_fatal("%s: flush dictionary: %m", dict_db->dict.name); 337 338 /* 339 * Release the exclusive lock. 340 */ 341 if ((dict->flags & DICT_FLAG_LOCK) 342 && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) 343 msg_fatal("%s: unlock dictionary: %m", dict_db->dict.name); 344 345 return (status); 346 } 347 348 /* delete one entry from the dictionary */ 349 350 static int dict_db_delete(DICT *dict, const char *name) 351 { 352 DICT_DB *dict_db = (DICT_DB *) dict; 353 DB *db = dict_db->db; 354 DBT db_key; 355 int status = 1; 356 int flags = 0; 357 358 dict->error = 0; 359 360 /* 361 * Sanity check. 362 */ 363 if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) 364 msg_panic("dict_db_delete: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag"); 365 366 /* 367 * Optionally fold the key. 368 */ 369 if (dict->flags & DICT_FLAG_FOLD_FIX) { 370 if (dict->fold_buf == 0) 371 dict->fold_buf = vstring_alloc(10); 372 vstring_strcpy(dict->fold_buf, name); 373 name = lowercase(vstring_str(dict->fold_buf)); 374 } 375 memset(&db_key, 0, sizeof(db_key)); 376 377 /* 378 * Acquire an exclusive lock. 379 */ 380 if ((dict->flags & DICT_FLAG_LOCK) 381 && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0) 382 msg_fatal("%s: lock dictionary: %m", dict_db->dict.name); 383 384 /* 385 * See if this DB file was written with one null byte appended to key and 386 * value. 387 */ 388 if (dict->flags & DICT_FLAG_TRY1NULL) { 389 db_key.data = (void *) name; 390 db_key.size = strlen(name) + 1; 391 if ((status = DICT_DB_DEL(db, &db_key, flags)) < 0) 392 msg_fatal("error deleting from %s: %m", dict_db->dict.name); 393 if (status == 0) 394 dict->flags &= ~DICT_FLAG_TRY0NULL; 395 } 396 397 /* 398 * See if this DB file was written with no null byte appended to key and 399 * value. 400 */ 401 if (status > 0 && (dict->flags & DICT_FLAG_TRY0NULL)) { 402 db_key.data = (void *) name; 403 db_key.size = strlen(name); 404 if ((status = DICT_DB_DEL(db, &db_key, flags)) < 0) 405 msg_fatal("error deleting from %s: %m", dict_db->dict.name); 406 if (status == 0) 407 dict->flags &= ~DICT_FLAG_TRY1NULL; 408 } 409 if (dict->flags & DICT_FLAG_SYNC_UPDATE) 410 if (DICT_DB_SYNC(db, 0) < 0) 411 msg_fatal("%s: flush dictionary: %m", dict_db->dict.name); 412 413 /* 414 * Release the exclusive lock. 415 */ 416 if ((dict->flags & DICT_FLAG_LOCK) 417 && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) 418 msg_fatal("%s: unlock dictionary: %m", dict_db->dict.name); 419 420 return status; 421 } 422 423 /* dict_db_sequence - traverse the dictionary */ 424 425 static int dict_db_sequence(DICT *dict, int function, 426 const char **key, const char **value) 427 { 428 const char *myname = "dict_db_sequence"; 429 DICT_DB *dict_db = (DICT_DB *) dict; 430 DB *db = dict_db->db; 431 DBT db_key; 432 DBT db_value; 433 int status = 0; 434 int db_function; 435 436 dict->error = 0; 437 438 #if DB_VERSION_MAJOR > 1 439 440 /* 441 * Initialize. 442 */ 443 memset(&db_key, 0, sizeof(db_key)); 444 memset(&db_value, 0, sizeof(db_value)); 445 446 /* 447 * Determine the function. 448 */ 449 switch (function) { 450 case DICT_SEQ_FUN_FIRST: 451 if (dict_db->cursor == 0) 452 DICT_DB_CURSOR(db, &(dict_db->cursor)); 453 db_function = DB_FIRST; 454 break; 455 case DICT_SEQ_FUN_NEXT: 456 if (dict_db->cursor == 0) 457 msg_panic("%s: no cursor", myname); 458 db_function = DB_NEXT; 459 break; 460 default: 461 msg_panic("%s: invalid function %d", myname, function); 462 } 463 464 /* 465 * Acquire a shared lock. 466 */ 467 if ((dict->flags & DICT_FLAG_LOCK) 468 && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0) 469 msg_fatal("%s: lock dictionary: %m", dict_db->dict.name); 470 471 /* 472 * Database lookup. 473 */ 474 status = 475 dict_db->cursor->c_get(dict_db->cursor, &db_key, &db_value, db_function); 476 if (status != 0 && status != DB_NOTFOUND) 477 msg_fatal("error [%d] seeking %s: %m", status, dict_db->dict.name); 478 479 /* 480 * Release the shared lock. 481 */ 482 if ((dict->flags & DICT_FLAG_LOCK) 483 && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) 484 msg_fatal("%s: unlock dictionary: %m", dict_db->dict.name); 485 486 if (status == 0) { 487 488 /* 489 * Copy the result so it is guaranteed null terminated. 490 */ 491 *key = SCOPY(dict_db->key_buf, db_key.data, db_key.size); 492 *value = SCOPY(dict_db->val_buf, db_value.data, db_value.size); 493 } 494 return (status); 495 #else 496 497 /* 498 * determine the function 499 */ 500 switch (function) { 501 case DICT_SEQ_FUN_FIRST: 502 db_function = R_FIRST; 503 break; 504 case DICT_SEQ_FUN_NEXT: 505 db_function = R_NEXT; 506 break; 507 default: 508 msg_panic("%s: invalid function %d", myname, function); 509 } 510 511 /* 512 * Acquire a shared lock. 513 */ 514 if ((dict->flags & DICT_FLAG_LOCK) 515 && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0) 516 msg_fatal("%s: lock dictionary: %m", dict_db->dict.name); 517 518 if ((status = db->seq(db, &db_key, &db_value, db_function)) < 0) 519 msg_fatal("error seeking %s: %m", dict_db->dict.name); 520 521 /* 522 * Release the shared lock. 523 */ 524 if ((dict->flags & DICT_FLAG_LOCK) 525 && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) 526 msg_fatal("%s: unlock dictionary: %m", dict_db->dict.name); 527 528 if (status == 0) { 529 530 /* 531 * Copy the result so that it is guaranteed null terminated. 532 */ 533 *key = SCOPY(dict_db->key_buf, db_key.data, db_key.size); 534 *value = SCOPY(dict_db->val_buf, db_value.data, db_value.size); 535 } 536 return status; 537 #endif 538 } 539 540 /* dict_db_close - close data base */ 541 542 static void dict_db_close(DICT *dict) 543 { 544 DICT_DB *dict_db = (DICT_DB *) dict; 545 546 #if DB_VERSION_MAJOR > 1 547 if (dict_db->cursor) 548 dict_db->cursor->c_close(dict_db->cursor); 549 #endif 550 if (DICT_DB_SYNC(dict_db->db, 0) < 0) 551 msg_fatal("flush database %s: %m", dict_db->dict.name); 552 553 /* 554 * With some Berkeley DB implementations, close fails with a bogus ENOENT 555 * error, while it reports no errors with put+sync, no errors with 556 * del+sync, and no errors with the sync operation just before this 557 * comment. This happens in programs that never fork and that never share 558 * the database with other processes. The bogus close error has been 559 * reported for programs that use the first/next iterator. Instead of 560 * making Postfix look bad because it reports errors that other programs 561 * ignore, I'm going to report the bogus error as a non-error. 562 */ 563 if (DICT_DB_CLOSE(dict_db->db) < 0) 564 msg_info("close database %s: %m (possible Berkeley DB bug)", 565 dict_db->dict.name); 566 #if DB_VERSION_MAJOR > 2 567 dict_db->dbenv->close(dict_db->dbenv, 0); 568 #endif 569 if (dict_db->key_buf) 570 vstring_free(dict_db->key_buf); 571 if (dict_db->val_buf) 572 vstring_free(dict_db->val_buf); 573 if (dict->fold_buf) 574 vstring_free(dict->fold_buf); 575 dict_free(dict); 576 } 577 578 #if DB_VERSION_MAJOR > 2 579 580 /* dict_db_new_env - workaround for undocumented ./DB_CONFIG read */ 581 582 static DB_ENV *dict_db_new_env(const char *db_path) 583 { 584 VSTRING *db_home_buf; 585 DB_ENV *dbenv; 586 u_int32_t cache_size_gbytes; 587 u_int32_t cache_size_bytes; 588 int ncache; 589 590 if ((errno = db_env_create(&dbenv, 0)) != 0) 591 msg_fatal("create DB environment: %m"); 592 #if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 7) 593 if ((errno = dbenv->get_cachesize(dbenv, &cache_size_gbytes, 594 &cache_size_bytes, &ncache)) != 0) 595 msg_fatal("get DB cache size: %m"); 596 if (cache_size_gbytes == 0 && cache_size_bytes < dict_db_cache_size) { 597 if ((errno = dbenv->set_cache_max(dbenv, cache_size_gbytes, 598 dict_db_cache_size)) != 0) 599 msg_fatal("set DB max cache size %d: %m", dict_db_cache_size); 600 if ((errno = dbenv->set_cachesize(dbenv, cache_size_gbytes, 601 dict_db_cache_size, ncache)) != 0) 602 msg_fatal("set DB cache size %d: %m", dict_db_cache_size); 603 } 604 #endif 605 /* XXX db_home is also the default directory for the .db file. */ 606 db_home_buf = vstring_alloc(100); 607 if ((errno = dbenv->open(dbenv, sane_dirname(db_home_buf, db_path), 608 DB_INIT_MPOOL | DB_CREATE | DB_PRIVATE, 0)) != 0) 609 msg_fatal("open DB environment: %m"); 610 vstring_free(db_home_buf); 611 return (dbenv); 612 } 613 614 #endif 615 616 /* dict_db_open - open data base */ 617 618 static DICT *dict_db_open(const char *class, const char *path, int open_flags, 619 int type, void *tweak, int dict_flags) 620 { 621 DICT_DB *dict_db; 622 struct stat st; 623 DB *db = 0; 624 char *db_path = 0; 625 VSTRING *db_base_buf = 0; 626 int lock_fd = -1; 627 int dbfd; 628 629 #if DB_VERSION_MAJOR > 1 630 int db_flags; 631 632 #endif 633 #if DB_VERSION_MAJOR > 2 634 DB_ENV *dbenv = 0; 635 636 #endif 637 638 /* 639 * Mismatches between #include file and library are a common cause for 640 * trouble. 641 */ 642 #if DB_VERSION_MAJOR > 1 643 int major_version; 644 int minor_version; 645 int patch_version; 646 647 (void) db_version(&major_version, &minor_version, &patch_version); 648 if (major_version != DB_VERSION_MAJOR || minor_version != DB_VERSION_MINOR) 649 return (dict_surrogate(class, path, open_flags, dict_flags, 650 "incorrect version of Berkeley DB: " 651 "compiled against %d.%d.%d, run-time linked against %d.%d.%d", 652 DB_VERSION_MAJOR, DB_VERSION_MINOR, DB_VERSION_PATCH, 653 major_version, minor_version, patch_version)); 654 if (msg_verbose) { 655 msg_info("Compiled against Berkeley DB: %d.%d.%d\n", 656 DB_VERSION_MAJOR, DB_VERSION_MINOR, DB_VERSION_PATCH); 657 msg_info("Run-time linked against Berkeley DB: %d.%d.%d\n", 658 major_version, minor_version, patch_version); 659 } 660 #else 661 if (msg_verbose) 662 msg_info("Compiled against Berkeley DB version 1"); 663 #endif 664 665 db_path = concatenate(path, ".db", (char *) 0); 666 667 /* 668 * Note: DICT_FLAG_LOCK is used only by programs that do fine-grained (in 669 * the time domain) locking while accessing individual database records. 670 * 671 * Programs such as postmap/postalias use their own large-grained (in the 672 * time domain) locks while rewriting the entire file. 673 * 674 * XXX DB version 4.1 will not open a zero-length file. This means we must 675 * open an existing file without O_CREAT|O_TRUNC, and that we must let 676 * db_open() create a non-existent file for us. 677 */ 678 #define LOCK_OPEN_FLAGS(f) ((f) & ~(O_CREAT|O_TRUNC)) 679 #if DB_VERSION_MAJOR <= 2 680 #define FREE_RETURN(e) do { \ 681 DICT *_dict = (e); if (db) DICT_DB_CLOSE(db); \ 682 if (lock_fd >= 0) (void) close(lock_fd); \ 683 if (db_base_buf) vstring_free(db_base_buf); \ 684 if (db_path) myfree(db_path); return (_dict); \ 685 } while (0) 686 #else 687 #define FREE_RETURN(e) do { \ 688 DICT *_dict = (e); if (db) DICT_DB_CLOSE(db); \ 689 if (dbenv) dbenv->close(dbenv, 0); \ 690 if (lock_fd >= 0) (void) close(lock_fd); \ 691 if (db_base_buf) vstring_free(db_base_buf); \ 692 if (db_path) myfree(db_path); \ 693 return (_dict); \ 694 } while (0) 695 #endif 696 697 if (dict_flags & DICT_FLAG_LOCK) { 698 if ((lock_fd = open(db_path, LOCK_OPEN_FLAGS(open_flags), 0644)) < 0) { 699 if (errno != ENOENT) 700 FREE_RETURN(dict_surrogate(class, path, open_flags, dict_flags, 701 "open database %s: %m", db_path)); 702 } else { 703 if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0) 704 msg_fatal("shared-lock database %s for open: %m", db_path); 705 } 706 } 707 708 /* 709 * Use the DB 1.x programming interface. This is the default interface 710 * with 4.4BSD systems. It is also available via the db_185 compatibility 711 * interface, but that interface does not have the undocumented feature 712 * that we need to make file locking safe with POSIX fcntl() locking. 713 */ 714 #if DB_VERSION_MAJOR < 2 715 if ((db = dbopen(db_path, open_flags, 0644, type, tweak)) == 0) 716 FREE_RETURN(dict_surrogate(class, path, open_flags, dict_flags, 717 "open database %s: %m", db_path)); 718 dbfd = db->fd(db); 719 #endif 720 721 /* 722 * Use the DB 2.x programming interface. Jump a couple extra hoops. 723 */ 724 #if DB_VERSION_MAJOR == 2 725 db_flags = DB_FCNTL_LOCKING; 726 if (open_flags == O_RDONLY) 727 db_flags |= DB_RDONLY; 728 if (open_flags & O_CREAT) 729 db_flags |= DB_CREATE; 730 if (open_flags & O_TRUNC) 731 db_flags |= DB_TRUNCATE; 732 if ((errno = db_open(db_path, type, db_flags, 0644, 0, tweak, &db)) != 0) 733 FREE_RETURN(dict_surrogate(class, path, open_flags, dict_flags, 734 "open database %s: %m", db_path)); 735 if (db == 0) 736 msg_panic("db_open null result"); 737 if ((errno = db->fd(db, &dbfd)) != 0) 738 msg_fatal("get database file descriptor: %m"); 739 #endif 740 741 /* 742 * Use the DB 3.x programming interface. Jump even more hoops. 743 */ 744 #if DB_VERSION_MAJOR > 2 745 db_flags = DB_FCNTL_LOCKING; 746 if (open_flags == O_RDONLY) 747 db_flags |= DB_RDONLY; 748 if (open_flags & O_CREAT) 749 db_flags |= DB_CREATE; 750 if (open_flags & O_TRUNC) 751 db_flags |= DB_TRUNCATE; 752 if ((errno = db_create(&db, dbenv = dict_db_new_env(db_path), 0)) != 0) 753 msg_fatal("create DB database: %m"); 754 if (db == 0) 755 msg_panic("db_create null result"); 756 if (type == DB_HASH && db->set_h_nelem(db, DICT_DB_NELM) != 0) 757 msg_fatal("set DB hash element count %d: %m", DICT_DB_NELM); 758 db_base_buf = vstring_alloc(100); 759 #if DB_VERSION_MAJOR == 18 || DB_VERSION_MAJOR == 6 || DB_VERSION_MAJOR == 5 || \ 760 (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR > 0) 761 if ((errno = db->open(db, 0, sane_basename(db_base_buf, db_path), 762 0, type, db_flags, 0644)) != 0) 763 FREE_RETURN(dict_surrogate(class, path, open_flags, dict_flags, 764 "open database %s: %m", db_path)); 765 #elif (DB_VERSION_MAJOR == 3 || DB_VERSION_MAJOR == 4) 766 if ((errno = db->open(db, sane_basename(db_base_buf, db_path), 0, 767 type, db_flags, 0644)) != 0) 768 FREE_RETURN(dict_surrogate(class, path, open_flags, dict_flags, 769 "open database %s: %m", db_path)); 770 #else 771 #error "Unsupported Berkeley DB version" 772 #endif 773 vstring_free(db_base_buf); 774 if ((errno = db->fd(db, &dbfd)) != 0) 775 msg_fatal("get database file descriptor: %m"); 776 #endif 777 if ((dict_flags & DICT_FLAG_LOCK) && lock_fd >= 0) { 778 if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) 779 msg_fatal("unlock database %s for open: %m", db_path); 780 if (close(lock_fd) < 0) 781 msg_fatal("close database %s: %m", db_path); 782 lock_fd = -1; 783 } 784 dict_db = (DICT_DB *) dict_alloc(class, db_path, sizeof(*dict_db)); 785 dict_db->dict.lookup = dict_db_lookup; 786 dict_db->dict.update = dict_db_update; 787 dict_db->dict.delete = dict_db_delete; 788 dict_db->dict.sequence = dict_db_sequence; 789 dict_db->dict.close = dict_db_close; 790 dict_db->dict.lock_fd = dbfd; 791 dict_db->dict.stat_fd = dbfd; 792 if (fstat(dict_db->dict.stat_fd, &st) < 0) 793 msg_fatal("dict_db_open: fstat: %m"); 794 dict_db->dict.mtime = st.st_mtime; 795 dict_db->dict.owner.uid = st.st_uid; 796 dict_db->dict.owner.status = (st.st_uid != 0); 797 798 /* 799 * Warn if the source file is newer than the indexed file, except when 800 * the source file changed only seconds ago. 801 */ 802 if ((dict_flags & DICT_FLAG_LOCK) != 0 803 && stat(path, &st) == 0 804 && st.st_mtime > dict_db->dict.mtime 805 && st.st_mtime < time((time_t *) 0) - 100) 806 msg_warn("database %s is older than source file %s", db_path, path); 807 808 close_on_exec(dict_db->dict.lock_fd, CLOSE_ON_EXEC); 809 close_on_exec(dict_db->dict.stat_fd, CLOSE_ON_EXEC); 810 dict_db->dict.flags = dict_flags | DICT_FLAG_FIXED; 811 if ((dict_flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) 812 dict_db->dict.flags |= (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL); 813 if (dict_flags & DICT_FLAG_FOLD_FIX) 814 dict_db->dict.fold_buf = vstring_alloc(10); 815 dict_db->db = db; 816 #if DB_VERSION_MAJOR > 2 817 dict_db->dbenv = dbenv; 818 #endif 819 #if DB_VERSION_MAJOR > 1 820 dict_db->cursor = 0; 821 #endif 822 dict_db->key_buf = 0; 823 dict_db->val_buf = 0; 824 825 myfree(db_path); 826 return (DICT_DEBUG (&dict_db->dict)); 827 } 828 829 /* dict_hash_open - create association with data base */ 830 831 DICT *dict_hash_open(const char *path, int open_flags, int dict_flags) 832 { 833 #if DB_VERSION_MAJOR < 2 834 HASHINFO tweak; 835 836 memset((void *) &tweak, 0, sizeof(tweak)); 837 tweak.nelem = DICT_DB_NELM; 838 tweak.cachesize = dict_db_cache_size; 839 #endif 840 #if DB_VERSION_MAJOR == 2 841 DB_INFO tweak; 842 843 memset((void *) &tweak, 0, sizeof(tweak)); 844 tweak.h_nelem = DICT_DB_NELM; 845 tweak.db_cachesize = dict_db_cache_size; 846 #endif 847 #if DB_VERSION_MAJOR > 2 848 void *tweak; 849 850 tweak = 0; 851 #endif 852 return (dict_db_open(DICT_TYPE_HASH, path, open_flags, DB_HASH, 853 (void *) &tweak, dict_flags)); 854 } 855 856 /* dict_btree_open - create association with data base */ 857 858 DICT *dict_btree_open(const char *path, int open_flags, int dict_flags) 859 { 860 #if DB_VERSION_MAJOR < 2 861 BTREEINFO tweak; 862 863 memset((void *) &tweak, 0, sizeof(tweak)); 864 tweak.cachesize = dict_db_cache_size; 865 #endif 866 #if DB_VERSION_MAJOR == 2 867 DB_INFO tweak; 868 869 memset((void *) &tweak, 0, sizeof(tweak)); 870 tweak.db_cachesize = dict_db_cache_size; 871 #endif 872 #if DB_VERSION_MAJOR > 2 873 void *tweak; 874 875 tweak = 0; 876 #endif 877 878 return (dict_db_open(DICT_TYPE_BTREE, path, open_flags, DB_BTREE, 879 (void *) &tweak, dict_flags)); 880 } 881 882 #endif 883