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