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