xref: /netbsd-src/external/ibm-public/postfix/dist/src/util/dict_db.c (revision 2d48ac808c43ea6701ba8f33cfc3645685301f79)
1 /*	$NetBSD: dict_db.c,v 1.1.1.1 2009/06/23 10:08:59 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     if (DICT_DB_CLOSE(dict_db->db) < 0)
541 	msg_fatal("close database %s: %m", dict_db->dict.name);
542     if (dict_db->key_buf)
543 	vstring_free(dict_db->key_buf);
544     if (dict_db->val_buf)
545 	vstring_free(dict_db->val_buf);
546     if (dict->fold_buf)
547 	vstring_free(dict->fold_buf);
548     dict_free(dict);
549 }
550 
551 /* dict_db_open - open data base */
552 
553 static DICT *dict_db_open(const char *class, const char *path, int open_flags,
554 			          int type, void *tweak, int dict_flags)
555 {
556     DICT_DB *dict_db;
557     struct stat st;
558     DB     *db;
559     char   *db_path;
560     int     lock_fd = -1;
561     int     dbfd;
562 
563 #if DB_VERSION_MAJOR > 1
564     int     db_flags;
565 
566 #endif
567 
568     /*
569      * Mismatches between #include file and library are a common cause for
570      * trouble.
571      */
572 #if DB_VERSION_MAJOR > 1
573     int     major_version;
574     int     minor_version;
575     int     patch_version;
576 
577     (void) db_version(&major_version, &minor_version, &patch_version);
578     if (major_version != DB_VERSION_MAJOR || minor_version != DB_VERSION_MINOR)
579 	msg_fatal("incorrect version of Berkeley DB: "
580 	      "compiled against %d.%d.%d, run-time linked against %d.%d.%d",
581 		  DB_VERSION_MAJOR, DB_VERSION_MINOR, DB_VERSION_PATCH,
582 		  major_version, minor_version, patch_version);
583     if (msg_verbose) {
584 	msg_info("Compiled against Berkeley DB: %d.%d.%d\n",
585 		 DB_VERSION_MAJOR, DB_VERSION_MINOR, DB_VERSION_PATCH);
586 	msg_info("Run-time linked against Berkeley DB: %d.%d.%d\n",
587 		 major_version, minor_version, patch_version);
588     }
589 #else
590     if (msg_verbose)
591 	msg_info("Compiled against Berkeley DB version 1");
592 #endif
593 
594     db_path = concatenate(path, ".db", (char *) 0);
595 
596     /*
597      * Note: DICT_FLAG_LOCK is used only by programs that do fine-grained (in
598      * the time domain) locking while accessing individual database records.
599      *
600      * Programs such as postmap/postalias use their own large-grained (in the
601      * time domain) locks while rewriting the entire file.
602      *
603      * XXX DB version 4.1 will not open a zero-length file. This means we must
604      * open an existing file without O_CREAT|O_TRUNC, and that we must let
605      * db_open() create a non-existent file for us.
606      */
607 #define LOCK_OPEN_FLAGS(f) ((f) & ~(O_CREAT|O_TRUNC))
608 
609     if (dict_flags & DICT_FLAG_LOCK) {
610 	if ((lock_fd = open(db_path, LOCK_OPEN_FLAGS(open_flags), 0644)) < 0) {
611 	    if (errno != ENOENT)
612 		msg_fatal("open database %s: %m", db_path);
613 	} else {
614 	    if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0)
615 		msg_fatal("shared-lock database %s for open: %m", db_path);
616 	}
617     }
618 
619     /*
620      * Use the DB 1.x programming interface. This is the default interface
621      * with 4.4BSD systems. It is also available via the db_185 compatibility
622      * interface, but that interface does not have the undocumented feature
623      * that we need to make file locking safe with POSIX fcntl() locking.
624      */
625 #if DB_VERSION_MAJOR < 2
626     if ((db = dbopen(db_path, open_flags, 0644, type, tweak)) == 0)
627 	msg_fatal("open database %s: %m", db_path);
628     dbfd = db->fd(db);
629 #endif
630 
631     /*
632      * Use the DB 2.x programming interface. Jump a couple extra hoops.
633      */
634 #if DB_VERSION_MAJOR == 2
635     db_flags = DB_FCNTL_LOCKING;
636     if (open_flags == O_RDONLY)
637 	db_flags |= DB_RDONLY;
638     if (open_flags & O_CREAT)
639 	db_flags |= DB_CREATE;
640     if (open_flags & O_TRUNC)
641 	db_flags |= DB_TRUNCATE;
642     if ((errno = db_open(db_path, type, db_flags, 0644, 0, tweak, &db)) != 0)
643 	msg_fatal("open database %s: %m", db_path);
644     if (db == 0)
645 	msg_panic("db_open null result");
646     if ((errno = db->fd(db, &dbfd)) != 0)
647 	msg_fatal("get database file descriptor: %m");
648 #endif
649 
650     /*
651      * Use the DB 3.x programming interface. Jump even more hoops.
652      */
653 #if DB_VERSION_MAJOR > 2
654     db_flags = DB_FCNTL_LOCKING;
655     if (open_flags == O_RDONLY)
656 	db_flags |= DB_RDONLY;
657     if (open_flags & O_CREAT)
658 	db_flags |= DB_CREATE;
659     if (open_flags & O_TRUNC)
660 	db_flags |= DB_TRUNCATE;
661     if ((errno = db_create(&db, 0, 0)) != 0)
662 	msg_fatal("create DB database: %m");
663     if (db == 0)
664 	msg_panic("db_create null result");
665     if ((errno = db->set_cachesize(db, 0, dict_db_cache_size, 0)) != 0)
666 	msg_fatal("set DB cache size %d: %m", dict_db_cache_size);
667     if (type == DB_HASH && db->set_h_nelem(db, DICT_DB_NELM) != 0)
668 	msg_fatal("set DB hash element count %d: %m", DICT_DB_NELM);
669 #if (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR > 0)
670     if ((errno = db->open(db, 0, db_path, 0, type, db_flags, 0644)) != 0)
671 	msg_fatal("open database %s: %m", db_path);
672 #elif (DB_VERSION_MAJOR == 3 || DB_VERSION_MAJOR == 4)
673     if ((errno = db->open(db, db_path, 0, type, db_flags, 0644)) != 0)
674 	msg_fatal("open database %s: %m", db_path);
675 #else
676 #error "Unsupported Berkeley DB version"
677 #endif
678     if ((errno = db->fd(db, &dbfd)) != 0)
679 	msg_fatal("get database file descriptor: %m");
680 #endif
681     if ((dict_flags & DICT_FLAG_LOCK) && lock_fd >= 0) {
682 	if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
683 	    msg_fatal("unlock database %s for open: %m", db_path);
684 	if (close(lock_fd) < 0)
685 	    msg_fatal("close database %s: %m", db_path);
686     }
687     dict_db = (DICT_DB *) dict_alloc(class, db_path, sizeof(*dict_db));
688     dict_db->dict.lookup = dict_db_lookup;
689     dict_db->dict.update = dict_db_update;
690     dict_db->dict.delete = dict_db_delete;
691     dict_db->dict.sequence = dict_db_sequence;
692     dict_db->dict.close = dict_db_close;
693     dict_db->dict.lock_fd = dbfd;
694     dict_db->dict.stat_fd = dbfd;
695     if (fstat(dict_db->dict.stat_fd, &st) < 0)
696 	msg_fatal("dict_db_open: fstat: %m");
697     dict_db->dict.mtime = st.st_mtime;
698 
699     /*
700      * Warn if the source file is newer than the indexed file, except when
701      * the source file changed only seconds ago.
702      */
703     if ((dict_flags & DICT_FLAG_LOCK) != 0
704 	&& stat(path, &st) == 0
705 	&& st.st_mtime > dict_db->dict.mtime
706 	&& st.st_mtime < time((time_t *) 0) - 100)
707 	msg_warn("database %s is older than source file %s", db_path, path);
708 
709     close_on_exec(dict_db->dict.lock_fd, CLOSE_ON_EXEC);
710     close_on_exec(dict_db->dict.stat_fd, CLOSE_ON_EXEC);
711     dict_db->dict.flags = dict_flags | DICT_FLAG_FIXED;
712     if ((dict_flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
713 	dict_db->dict.flags |= (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL);
714     if (dict_flags & DICT_FLAG_FOLD_FIX)
715 	dict_db->dict.fold_buf = vstring_alloc(10);
716     dict_db->db = db;
717 #if DB_VERSION_MAJOR > 1
718     dict_db->cursor = 0;
719 #endif
720     dict_db->key_buf = 0;
721     dict_db->val_buf = 0;
722 
723     myfree(db_path);
724     return (DICT_DEBUG (&dict_db->dict));
725 }
726 
727 /* dict_hash_open - create association with data base */
728 
729 DICT   *dict_hash_open(const char *path, int open_flags, int dict_flags)
730 {
731 #if DB_VERSION_MAJOR < 2
732     HASHINFO tweak;
733 
734     memset((char *) &tweak, 0, sizeof(tweak));
735     tweak.nelem = DICT_DB_NELM;
736     tweak.cachesize = dict_db_cache_size;
737 #endif
738 #if DB_VERSION_MAJOR == 2
739     DB_INFO tweak;
740 
741     memset((char *) &tweak, 0, sizeof(tweak));
742     tweak.h_nelem = DICT_DB_NELM;
743     tweak.db_cachesize = dict_db_cache_size;
744 #endif
745 #if DB_VERSION_MAJOR > 2
746     void   *tweak;
747 
748     tweak = 0;
749 #endif
750     return (dict_db_open(DICT_TYPE_HASH, path, open_flags, DB_HASH,
751 			 (void *) &tweak, dict_flags));
752 }
753 
754 /* dict_btree_open - create association with data base */
755 
756 DICT   *dict_btree_open(const char *path, int open_flags, int dict_flags)
757 {
758 #if DB_VERSION_MAJOR < 2
759     BTREEINFO tweak;
760 
761     memset((char *) &tweak, 0, sizeof(tweak));
762     tweak.cachesize = dict_db_cache_size;
763 #endif
764 #if DB_VERSION_MAJOR == 2
765     DB_INFO tweak;
766 
767     memset((char *) &tweak, 0, sizeof(tweak));
768     tweak.db_cachesize = dict_db_cache_size;
769 #endif
770 #if DB_VERSION_MAJOR > 2
771     void   *tweak;
772 
773     tweak = 0;
774 #endif
775 
776     return (dict_db_open(DICT_TYPE_BTREE, path, open_flags, DB_BTREE,
777 			 (void *) &tweak, dict_flags));
778 }
779 
780 #endif
781