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