Home | History | Annotate | Line # | Download | only in util
      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