1 /* $NetBSD: dict_db.c,v 1.5 2026/05/09 18:49:22 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 && (status = DICT_DB_CURSOR(db, &(dict_db->cursor))) != 0) 453 msg_fatal("error [%d] initializing cursor for %s: %m", 454 status, dict_db->dict.name); 455 db_function = DB_FIRST; 456 break; 457 case DICT_SEQ_FUN_NEXT: 458 if (dict_db->cursor == 0) 459 msg_panic("%s: no cursor", myname); 460 db_function = DB_NEXT; 461 break; 462 default: 463 msg_panic("%s: invalid function %d", myname, function); 464 } 465 466 /* 467 * Acquire a shared lock. 468 */ 469 if ((dict->flags & DICT_FLAG_LOCK) 470 && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0) 471 msg_fatal("%s: lock dictionary: %m", dict_db->dict.name); 472 473 /* 474 * Database lookup. 475 */ 476 status = 477 dict_db->cursor->c_get(dict_db->cursor, &db_key, &db_value, db_function); 478 if (status != 0 && status != DB_NOTFOUND) 479 msg_fatal("error [%d] seeking %s: %m", status, dict_db->dict.name); 480 481 /* 482 * Release the shared lock. 483 */ 484 if ((dict->flags & DICT_FLAG_LOCK) 485 && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) 486 msg_fatal("%s: unlock dictionary: %m", dict_db->dict.name); 487 488 if (status == 0) { 489 490 /* 491 * Copy the result so it is guaranteed null terminated. 492 */ 493 *key = SCOPY(dict_db->key_buf, db_key.data, db_key.size); 494 *value = SCOPY(dict_db->val_buf, db_value.data, db_value.size); 495 } 496 return (status); 497 #else 498 499 /* 500 * determine the function 501 */ 502 switch (function) { 503 case DICT_SEQ_FUN_FIRST: 504 db_function = R_FIRST; 505 break; 506 case DICT_SEQ_FUN_NEXT: 507 db_function = R_NEXT; 508 break; 509 default: 510 msg_panic("%s: invalid function %d", myname, function); 511 } 512 513 /* 514 * Acquire a shared lock. 515 */ 516 if ((dict->flags & DICT_FLAG_LOCK) 517 && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0) 518 msg_fatal("%s: lock dictionary: %m", dict_db->dict.name); 519 520 if ((status = db->seq(db, &db_key, &db_value, db_function)) < 0) 521 msg_fatal("error seeking %s: %m", dict_db->dict.name); 522 523 /* 524 * Release the shared lock. 525 */ 526 if ((dict->flags & DICT_FLAG_LOCK) 527 && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) 528 msg_fatal("%s: unlock dictionary: %m", dict_db->dict.name); 529 530 if (status == 0) { 531 532 /* 533 * Copy the result so that it is guaranteed null terminated. 534 */ 535 *key = SCOPY(dict_db->key_buf, db_key.data, db_key.size); 536 *value = SCOPY(dict_db->val_buf, db_value.data, db_value.size); 537 } 538 return status; 539 #endif 540 } 541 542 /* dict_db_close - close data base */ 543 544 static void dict_db_close(DICT *dict) 545 { 546 DICT_DB *dict_db = (DICT_DB *) dict; 547 548 #if DB_VERSION_MAJOR > 1 549 if (dict_db->cursor) 550 dict_db->cursor->c_close(dict_db->cursor); 551 #endif 552 if (DICT_DB_SYNC(dict_db->db, 0) < 0) 553 msg_fatal("flush database %s: %m", dict_db->dict.name); 554 555 /* 556 * With some Berkeley DB implementations, close fails with a bogus ENOENT 557 * error, while it reports no errors with put+sync, no errors with 558 * del+sync, and no errors with the sync operation just before this 559 * comment. This happens in programs that never fork and that never share 560 * the database with other processes. The bogus close error has been 561 * reported for programs that use the first/next iterator. Instead of 562 * making Postfix look bad because it reports errors that other programs 563 * ignore, I'm going to report the bogus error as a non-error. 564 */ 565 if (DICT_DB_CLOSE(dict_db->db) < 0) 566 msg_info("close database %s: %m (possible Berkeley DB bug)", 567 dict_db->dict.name); 568 #if DB_VERSION_MAJOR > 2 569 dict_db->dbenv->close(dict_db->dbenv, 0); 570 #endif 571 if (dict_db->key_buf) 572 vstring_free(dict_db->key_buf); 573 if (dict_db->val_buf) 574 vstring_free(dict_db->val_buf); 575 if (dict->fold_buf) 576 vstring_free(dict->fold_buf); 577 dict_free(dict); 578 } 579 580 #if DB_VERSION_MAJOR > 2 581 582 /* dict_db_new_env - workaround for undocumented ./DB_CONFIG read */ 583 584 static DB_ENV *dict_db_new_env(const char *db_path) 585 { 586 VSTRING *db_home_buf; 587 DB_ENV *dbenv; 588 u_int32_t cache_size_gbytes; 589 u_int32_t cache_size_bytes; 590 int ncache; 591 592 if ((errno = db_env_create(&dbenv, 0)) != 0) 593 msg_fatal("create DB environment: %m"); 594 #if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 7) 595 if ((errno = dbenv->get_cachesize(dbenv, &cache_size_gbytes, 596 &cache_size_bytes, &ncache)) != 0) 597 msg_fatal("get DB cache size: %m"); 598 if (cache_size_gbytes == 0 && cache_size_bytes < dict_db_cache_size) { 599 if ((errno = dbenv->set_cache_max(dbenv, cache_size_gbytes, 600 dict_db_cache_size)) != 0) 601 msg_fatal("set DB max cache size %d: %m", dict_db_cache_size); 602 if ((errno = dbenv->set_cachesize(dbenv, cache_size_gbytes, 603 dict_db_cache_size, ncache)) != 0) 604 msg_fatal("set DB cache size %d: %m", dict_db_cache_size); 605 } 606 #endif 607 /* XXX db_home is also the default directory for the .db file. */ 608 db_home_buf = vstring_alloc(100); 609 if ((errno = dbenv->open(dbenv, sane_dirname(db_home_buf, db_path), 610 DB_INIT_MPOOL | DB_CREATE | DB_PRIVATE, 0)) != 0) 611 msg_fatal("open DB environment: %m"); 612 vstring_free(db_home_buf); 613 return (dbenv); 614 } 615 616 #endif 617 618 /* dict_db_open - open data base */ 619 620 static DICT *dict_db_open(const char *class, const char *path, int open_flags, 621 int type, void *tweak, int dict_flags) 622 { 623 DICT_DB *dict_db; 624 struct stat st; 625 DB *db = 0; 626 char *db_path = 0; 627 VSTRING *db_base_buf = 0; 628 int lock_fd = -1; 629 int dbfd; 630 631 #if DB_VERSION_MAJOR > 1 632 int db_flags; 633 634 #endif 635 #if DB_VERSION_MAJOR > 2 636 DB_ENV *dbenv = 0; 637 638 #endif 639 640 /* 641 * Mismatches between #include file and library are a common cause for 642 * trouble. 643 */ 644 #if DB_VERSION_MAJOR > 1 645 int major_version; 646 int minor_version; 647 int patch_version; 648 649 (void) db_version(&major_version, &minor_version, &patch_version); 650 if (major_version != DB_VERSION_MAJOR || minor_version != DB_VERSION_MINOR) 651 return (dict_surrogate(class, path, open_flags, dict_flags, 652 "incorrect version of Berkeley DB: " 653 "compiled against %d.%d.%d, run-time linked against %d.%d.%d", 654 DB_VERSION_MAJOR, DB_VERSION_MINOR, DB_VERSION_PATCH, 655 major_version, minor_version, patch_version)); 656 if (msg_verbose) { 657 msg_info("Compiled against Berkeley DB: %d.%d.%d", 658 DB_VERSION_MAJOR, DB_VERSION_MINOR, DB_VERSION_PATCH); 659 msg_info("Run-time linked against Berkeley DB: %d.%d.%d", 660 major_version, minor_version, patch_version); 661 } 662 #else 663 if (msg_verbose) 664 msg_info("Compiled against Berkeley DB version 1"); 665 #endif 666 667 db_path = concatenate(path, ".db", (char *) 0); 668 669 /* 670 * Note: DICT_FLAG_LOCK is used only by programs that do fine-grained (in 671 * the time domain) locking while accessing individual database records. 672 * 673 * Programs such as postmap/postalias use their own large-grained (in the 674 * time domain) locks while rewriting the entire file. 675 * 676 * XXX DB version 4.1 will not open a zero-length file. This means we must 677 * open an existing file without O_CREAT|O_TRUNC, and that we must let 678 * db_open() create a non-existent file for us. 679 */ 680 #define LOCK_OPEN_FLAGS(f) ((f) & ~(O_CREAT|O_TRUNC)) 681 #if DB_VERSION_MAJOR <= 2 682 #define FREE_RETURN(e) do { \ 683 DICT *_dict = (e); if (db) DICT_DB_CLOSE(db); \ 684 if (lock_fd >= 0) (void) close(lock_fd); \ 685 if (db_base_buf) vstring_free(db_base_buf); \ 686 if (db_path) myfree(db_path); return (_dict); \ 687 } while (0) 688 #else 689 #define FREE_RETURN(e) do { \ 690 DICT *_dict = (e); if (db) DICT_DB_CLOSE(db); \ 691 if (dbenv) dbenv->close(dbenv, 0); \ 692 if (lock_fd >= 0) (void) close(lock_fd); \ 693 if (db_base_buf) vstring_free(db_base_buf); \ 694 if (db_path) myfree(db_path); \ 695 return (_dict); \ 696 } while (0) 697 #endif 698 699 if (dict_flags & DICT_FLAG_LOCK) { 700 if ((lock_fd = open(db_path, LOCK_OPEN_FLAGS(open_flags), 0644)) < 0) { 701 if (errno != ENOENT) 702 FREE_RETURN(dict_surrogate(class, path, open_flags, dict_flags, 703 "open database %s: %m", db_path)); 704 } else { 705 if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0) 706 msg_fatal("shared-lock database %s for open: %m", db_path); 707 } 708 } 709 710 /* 711 * Use the DB 1.x programming interface. This is the default interface 712 * with 4.4BSD systems. It is also available via the db_185 compatibility 713 * interface, but that interface does not have the undocumented feature 714 * that we need to make file locking safe with POSIX fcntl() locking. 715 */ 716 #if DB_VERSION_MAJOR < 2 717 if ((db = dbopen(db_path, open_flags, 0644, type, tweak)) == 0) 718 FREE_RETURN(dict_surrogate(class, path, open_flags, dict_flags, 719 "open database %s: %m", db_path)); 720 dbfd = db->fd(db); 721 #endif 722 723 /* 724 * Use the DB 2.x programming interface. Jump a couple extra hoops. 725 */ 726 #if DB_VERSION_MAJOR == 2 727 db_flags = DB_FCNTL_LOCKING; 728 if (open_flags == O_RDONLY) 729 db_flags |= DB_RDONLY; 730 if (open_flags & O_CREAT) 731 db_flags |= DB_CREATE; 732 if (open_flags & O_TRUNC) 733 db_flags |= DB_TRUNCATE; 734 if ((errno = db_open(db_path, type, db_flags, 0644, 0, tweak, &db)) != 0) 735 FREE_RETURN(dict_surrogate(class, path, open_flags, dict_flags, 736 "open database %s: %m", db_path)); 737 if (db == 0) 738 msg_panic("db_open null result"); 739 if ((errno = db->fd(db, &dbfd)) != 0) 740 msg_fatal("get database file descriptor: %m"); 741 #endif 742 743 /* 744 * Use the DB 3.x programming interface. Jump even more hoops. 745 */ 746 #if DB_VERSION_MAJOR > 2 747 db_flags = DB_FCNTL_LOCKING; 748 if (open_flags == O_RDONLY) 749 db_flags |= DB_RDONLY; 750 if (open_flags & O_CREAT) 751 db_flags |= DB_CREATE; 752 if (open_flags & O_TRUNC) 753 db_flags |= DB_TRUNCATE; 754 if ((errno = db_create(&db, dbenv = dict_db_new_env(db_path), 0)) != 0) 755 msg_fatal("create DB database: %m"); 756 if (db == 0) 757 msg_panic("db_create null result"); 758 if (type == DB_HASH && db->set_h_nelem(db, DICT_DB_NELM) != 0) 759 msg_fatal("set DB hash element count %d: %m", DICT_DB_NELM); 760 db_base_buf = vstring_alloc(100); 761 #if DB_VERSION_MAJOR == 18 || DB_VERSION_MAJOR == 6 || DB_VERSION_MAJOR == 5 || \ 762 (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR > 0) 763 if ((errno = db->open(db, 0, sane_basename(db_base_buf, db_path), 764 0, type, db_flags, 0644)) != 0) 765 FREE_RETURN(dict_surrogate(class, path, open_flags, dict_flags, 766 "open database %s: %m", db_path)); 767 #elif (DB_VERSION_MAJOR == 3 || DB_VERSION_MAJOR == 4) 768 if ((errno = db->open(db, sane_basename(db_base_buf, db_path), 0, 769 type, db_flags, 0644)) != 0) 770 FREE_RETURN(dict_surrogate(class, path, open_flags, dict_flags, 771 "open database %s: %m", db_path)); 772 #else 773 #error "Unsupported Berkeley DB version" 774 #endif 775 vstring_free(db_base_buf); 776 if ((errno = db->fd(db, &dbfd)) != 0) 777 msg_fatal("get database file descriptor: %m"); 778 #endif 779 if ((dict_flags & DICT_FLAG_LOCK) && lock_fd >= 0) { 780 if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) 781 msg_fatal("unlock database %s for open: %m", db_path); 782 if (close(lock_fd) < 0) 783 msg_fatal("close database %s: %m", db_path); 784 lock_fd = -1; 785 } 786 dict_db = (DICT_DB *) dict_alloc(class, db_path, sizeof(*dict_db)); 787 dict_db->dict.lookup = dict_db_lookup; 788 dict_db->dict.update = dict_db_update; 789 dict_db->dict.delete = dict_db_delete; 790 dict_db->dict.sequence = dict_db_sequence; 791 dict_db->dict.close = dict_db_close; 792 dict_db->dict.lock_fd = dbfd; 793 dict_db->dict.stat_fd = dbfd; 794 if (fstat(dict_db->dict.stat_fd, &st) < 0) 795 msg_fatal("dict_db_open: fstat: %m"); 796 if (open_flags == O_RDONLY) 797 dict_db->dict.mtime = st.st_mtime; 798 dict_db->dict.owner.uid = st.st_uid; 799 dict_db->dict.owner.status = (st.st_uid != 0); 800 801 /* 802 * Warn if the source file is newer than the indexed file, except when 803 * the source file changed only seconds ago. 804 */ 805 if ((dict_flags & DICT_FLAG_LOCK) != 0 806 && open_flags == O_RDONLY 807 && stat(path, &st) == 0 808 && st.st_mtime > dict_db->dict.mtime 809 && st.st_mtime < time((time_t *) 0) - 100) 810 msg_warn("database %s is older than source file %s", db_path, path); 811 812 close_on_exec(dict_db->dict.lock_fd, CLOSE_ON_EXEC); 813 close_on_exec(dict_db->dict.stat_fd, CLOSE_ON_EXEC); 814 dict_db->dict.flags = dict_flags | DICT_FLAG_FIXED; 815 if ((dict_flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) 816 dict_db->dict.flags |= (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL); 817 if (dict_flags & DICT_FLAG_FOLD_FIX) 818 dict_db->dict.fold_buf = vstring_alloc(10); 819 dict_db->db = db; 820 #if DB_VERSION_MAJOR > 2 821 dict_db->dbenv = dbenv; 822 #endif 823 #if DB_VERSION_MAJOR > 1 824 dict_db->cursor = 0; 825 #endif 826 dict_db->key_buf = 0; 827 dict_db->val_buf = 0; 828 829 myfree(db_path); 830 return (&dict_db->dict); 831 } 832 833 /* dict_hash_open - create association with data base */ 834 835 DICT *dict_hash_open(const char *path, int open_flags, int dict_flags) 836 { 837 #if DB_VERSION_MAJOR < 2 838 HASHINFO tweak; 839 840 memset((void *) &tweak, 0, sizeof(tweak)); 841 tweak.nelem = DICT_DB_NELM; 842 tweak.cachesize = dict_db_cache_size; 843 #endif 844 #if DB_VERSION_MAJOR == 2 845 DB_INFO tweak; 846 847 memset((void *) &tweak, 0, sizeof(tweak)); 848 tweak.h_nelem = DICT_DB_NELM; 849 tweak.db_cachesize = dict_db_cache_size; 850 #endif 851 #if DB_VERSION_MAJOR > 2 852 void *tweak; 853 854 tweak = 0; 855 #endif 856 return (dict_db_open(DICT_TYPE_HASH, path, open_flags, DB_HASH, 857 (void *) &tweak, dict_flags)); 858 } 859 860 /* dict_btree_open - create association with data base */ 861 862 DICT *dict_btree_open(const char *path, int open_flags, int dict_flags) 863 { 864 #if DB_VERSION_MAJOR < 2 865 BTREEINFO tweak; 866 867 memset((void *) &tweak, 0, sizeof(tweak)); 868 tweak.cachesize = dict_db_cache_size; 869 #endif 870 #if DB_VERSION_MAJOR == 2 871 DB_INFO tweak; 872 873 memset((void *) &tweak, 0, sizeof(tweak)); 874 tweak.db_cachesize = dict_db_cache_size; 875 #endif 876 #if DB_VERSION_MAJOR > 2 877 void *tweak; 878 879 tweak = 0; 880 #endif 881 882 return (dict_db_open(DICT_TYPE_BTREE, path, open_flags, DB_BTREE, 883 (void *) &tweak, dict_flags)); 884 } 885 886 #endif 887