xref: /netbsd-src/external/ibm-public/postfix/dist/src/util/dict_db.c (revision ca453df649ce9db45b64d73678ba06cbccf9aa11)
1 /*	$NetBSD: dict_db.c,v 1.1.1.3 2011/03/02 19:32:42 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 
115 /* Application-specific. */
116 
117 typedef 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   */
139 int     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 
147 static 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 
177 static 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 
255 static 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 
341 static 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 
414 static 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 
530 static 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 
565 static 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 
741 DICT   *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 
768 DICT   *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