1 /* $NetBSD: dict_db.c,v 1.4 2022/10/08 16:12:50 christos Exp $ */
2
3 /*++
4 /* NAME
5 /* dict_db 3
6 /* SUMMARY
7 /* dictionary manager interface to DB files
8 /* SYNOPSIS
9 /* #include <dict_db.h>
10 /*
11 /* extern int dict_db_cache_size;
12 /*
13 /* DEFINE_DICT_DB_CACHE_SIZE;
14 /*
15 /* DICT *dict_hash_open(path, open_flags, dict_flags)
16 /* const char *path;
17 /* int open_flags;
18 /* int dict_flags;
19 /*
20 /* DICT *dict_btree_open(path, open_flags, dict_flags)
21 /* const char *path;
22 /* int open_flags;
23 /* int dict_flags;
24 /* DESCRIPTION
25 /* dict_XXX_open() opens the specified DB database. The result is
26 /* a pointer to a structure that can be used to access the dictionary
27 /* using the generic methods documented in dict_open(3).
28 /*
29 /* The dict_db_cache_size variable specifies a non-default per-table
30 /* I/O buffer size. The default buffer size is adequate for reading.
31 /* For better performance while creating a large table, specify a large
32 /* buffer size before opening the file.
33 /*
34 /* This variable cannot be exported via the dict(3) API and
35 /* must therefore be defined in the calling program by invoking
36 /* the DEFINE_DICT_DB_CACHE_SIZE macro at the global level.
37 /*
38 /* Arguments:
39 /* .IP path
40 /* The database pathname, not including the ".db" suffix.
41 /* .IP open_flags
42 /* Flags passed to dbopen().
43 /* .IP dict_flags
44 /* Flags used by the dictionary interface.
45 /* SEE ALSO
46 /* dict(3) generic dictionary manager
47 /* DIAGNOSTICS
48 /* Fatal errors: cannot open file, write error, out of memory.
49 /* LICENSE
50 /* .ad
51 /* .fi
52 /* The Secure Mailer license must be distributed with this software.
53 /* AUTHOR(S)
54 /* Wietse Venema
55 /* IBM T.J. Watson Research
56 /* P.O. Box 704
57 /* Yorktown Heights, NY 10598, USA
58 /*
59 /* Wietse Venema
60 /* Google, Inc.
61 /* 111 8th Avenue
62 /* New York, NY 10011, USA
63 /*--*/
64
65 #include "sys_defs.h"
66
67 #ifdef HAS_DB
68
69 /* System library. */
70
71 #include <sys/stat.h>
72 #include <limits.h>
73 #ifdef PATH_DB_H
74 #include PATH_DB_H
75 #else
76 #include <db.h>
77 #endif
78 #include <string.h>
79 #include <unistd.h>
80 #include <errno.h>
81
82 #if defined(_DB_185_H_) && defined(USE_FCNTL_LOCK)
83 #error "Error: this system must not use the db 1.85 compatibility interface"
84 #endif
85
86 #ifndef DB_VERSION_MAJOR
87 #define DB_VERSION_MAJOR 1
88 #define DICT_DB_GET(db, key, val, flag) db->get(db, key, val, flag)
89 #define DICT_DB_PUT(db, key, val, flag) db->put(db, key, val, flag)
90 #define DICT_DB_DEL(db, key, flag) db->del(db, key, flag)
91 #define DICT_DB_SYNC(db, flag) db->sync(db, flag)
92 #define DICT_DB_CLOSE(db) db->close(db)
93 #define DONT_CLOBBER R_NOOVERWRITE
94 #endif
95
96 #if DB_VERSION_MAJOR > 1
97 #define DICT_DB_GET(db, key, val, flag) sanitize(db->get(db, 0, key, val, flag))
98 #define DICT_DB_PUT(db, key, val, flag) sanitize(db->put(db, 0, key, val, flag))
99 #define DICT_DB_DEL(db, key, flag) sanitize(db->del(db, 0, key, flag))
100 #define DICT_DB_SYNC(db, flag) ((errno = db->sync(db, flag)) ? -1 : 0)
101 #define DICT_DB_CLOSE(db) ((errno = db->close(db, 0)) ? -1 : 0)
102 #define DONT_CLOBBER DB_NOOVERWRITE
103 #endif
104
105 #if (DB_VERSION_MAJOR == 2 && DB_VERSION_MINOR < 6)
106 #define DICT_DB_CURSOR(db, curs) (db)->cursor((db), NULL, (curs))
107 #else
108 #define DICT_DB_CURSOR(db, curs) (db)->cursor((db), NULL, (curs), 0)
109 #endif
110
111 #ifndef DB_FCNTL_LOCKING
112 #define DB_FCNTL_LOCKING 0
113 #endif
114
115 /* Utility library. */
116
117 #include "msg.h"
118 #include "mymalloc.h"
119 #include "vstring.h"
120 #include "stringops.h"
121 #include "iostuff.h"
122 #include "myflock.h"
123 #include "dict.h"
124 #include "dict_db.h"
125 #include "warn_stat.h"
126
127 /* Application-specific. */
128
129 typedef struct {
130 DICT dict; /* generic members */
131 DB *db; /* open db file */
132 #if DB_VERSION_MAJOR > 2
133 DB_ENV *dbenv;
134 #endif
135 #if DB_VERSION_MAJOR > 1
136 DBC *cursor; /* dict_db_sequence() */
137 #endif
138 VSTRING *key_buf; /* key result */
139 VSTRING *val_buf; /* value result */
140 } DICT_DB;
141
142 #define SCOPY(buf, data, size) \
143 vstring_str(vstring_strncpy(buf ? buf : (buf = vstring_alloc(10)), data, size))
144
145 #define DICT_DB_NELM 4096
146
147 #if DB_VERSION_MAJOR > 1
148
149 /* sanitize - sanitize db_get/put/del result */
150
sanitize(int status)151 static int sanitize(int status)
152 {
153
154 /*
155 * XXX This is unclean but avoids a lot of clutter elsewhere. Categorize
156 * results into non-fatal errors (i.e., errors that we can deal with),
157 * success, or fatal error (i.e., all other errors).
158 */
159 switch (status) {
160
161 case DB_NOTFOUND: /* get, del */
162 case DB_KEYEXIST: /* put */
163 return (1); /* non-fatal */
164
165 case 0:
166 return (0); /* success */
167
168 case DB_KEYEMPTY: /* get, others? */
169 status = EINVAL;
170 /* FALLTHROUGH */
171 default:
172 errno = status;
173 return (-1); /* fatal */
174 }
175 }
176
177 #endif
178
179 /* dict_db_lookup - find database entry */
180
dict_db_lookup(DICT * dict,const char * name)181 static const char *dict_db_lookup(DICT *dict, const char *name)
182 {
183 DICT_DB *dict_db = (DICT_DB *) dict;
184 DB *db = dict_db->db;
185 DBT db_key;
186 DBT db_value;
187 int status;
188 const char *result = 0;
189
190 dict->error = 0;
191
192 /*
193 * Sanity check.
194 */
195 if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
196 msg_panic("dict_db_lookup: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag");
197
198 memset(&db_key, 0, sizeof(db_key));
199 memset(&db_value, 0, sizeof(db_value));
200
201 /*
202 * Optionally fold the key.
203 */
204 if (dict->flags & DICT_FLAG_FOLD_FIX) {
205 if (dict->fold_buf == 0)
206 dict->fold_buf = vstring_alloc(10);
207 vstring_strcpy(dict->fold_buf, name);
208 name = lowercase(vstring_str(dict->fold_buf));
209 }
210
211 /*
212 * Acquire a shared lock.
213 */
214 if ((dict->flags & DICT_FLAG_LOCK)
215 && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0)
216 msg_fatal("%s: lock dictionary: %m", dict_db->dict.name);
217
218 /*
219 * See if this DB file was written with one null byte appended to key and
220 * value.
221 */
222 if (dict->flags & DICT_FLAG_TRY1NULL) {
223 db_key.data = (void *) name;
224 db_key.size = strlen(name) + 1;
225 if ((status = DICT_DB_GET(db, &db_key, &db_value, 0)) < 0)
226 msg_fatal("error reading %s: %m", dict_db->dict.name);
227 if (status == 0) {
228 dict->flags &= ~DICT_FLAG_TRY0NULL;
229 result = SCOPY(dict_db->val_buf, db_value.data, db_value.size);
230 }
231 }
232
233 /*
234 * See if this DB file was written with no null byte appended to key and
235 * value.
236 */
237 if (result == 0 && (dict->flags & DICT_FLAG_TRY0NULL)) {
238 db_key.data = (void *) name;
239 db_key.size = strlen(name);
240 if ((status = DICT_DB_GET(db, &db_key, &db_value, 0)) < 0)
241 msg_fatal("error reading %s: %m", dict_db->dict.name);
242 if (status == 0) {
243 dict->flags &= ~DICT_FLAG_TRY1NULL;
244 result = SCOPY(dict_db->val_buf, db_value.data, db_value.size);
245 }
246 }
247
248 /*
249 * Release the shared lock.
250 */
251 if ((dict->flags & DICT_FLAG_LOCK)
252 && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
253 msg_fatal("%s: unlock dictionary: %m", dict_db->dict.name);
254
255 return (result);
256 }
257
258 /* dict_db_update - add or update database entry */
259
dict_db_update(DICT * dict,const char * name,const char * value)260 static int dict_db_update(DICT *dict, const char *name, const char *value)
261 {
262 DICT_DB *dict_db = (DICT_DB *) dict;
263 DB *db = dict_db->db;
264 DBT db_key;
265 DBT db_value;
266 int status;
267
268 dict->error = 0;
269
270 /*
271 * Sanity check.
272 */
273 if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
274 msg_panic("dict_db_update: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag");
275
276 /*
277 * Optionally fold the key.
278 */
279 if (dict->flags & DICT_FLAG_FOLD_FIX) {
280 if (dict->fold_buf == 0)
281 dict->fold_buf = vstring_alloc(10);
282 vstring_strcpy(dict->fold_buf, name);
283 name = lowercase(vstring_str(dict->fold_buf));
284 }
285 memset(&db_key, 0, sizeof(db_key));
286 memset(&db_value, 0, sizeof(db_value));
287 db_key.data = (void *) name;
288 db_value.data = (void *) value;
289 db_key.size = strlen(name);
290 db_value.size = strlen(value);
291
292 /*
293 * If undecided about appending a null byte to key and value, choose a
294 * default depending on the platform.
295 */
296 if ((dict->flags & DICT_FLAG_TRY1NULL)
297 && (dict->flags & DICT_FLAG_TRY0NULL)) {
298 #ifdef DB_NO_TRAILING_NULL
299 dict->flags &= ~DICT_FLAG_TRY1NULL;
300 #else
301 dict->flags &= ~DICT_FLAG_TRY0NULL;
302 #endif
303 }
304
305 /*
306 * Optionally append a null byte to key and value.
307 */
308 if (dict->flags & DICT_FLAG_TRY1NULL) {
309 db_key.size++;
310 db_value.size++;
311 }
312
313 /*
314 * Acquire an exclusive lock.
315 */
316 if ((dict->flags & DICT_FLAG_LOCK)
317 && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0)
318 msg_fatal("%s: lock dictionary: %m", dict_db->dict.name);
319
320 /*
321 * Do the update.
322 */
323 if ((status = DICT_DB_PUT(db, &db_key, &db_value,
324 (dict->flags & DICT_FLAG_DUP_REPLACE) ? 0 : DONT_CLOBBER)) < 0)
325 msg_fatal("error writing %s: %m", dict_db->dict.name);
326 if (status) {
327 if (dict->flags & DICT_FLAG_DUP_IGNORE)
328 /* void */ ;
329 else if (dict->flags & DICT_FLAG_DUP_WARN)
330 msg_warn("%s: duplicate entry: \"%s\"", dict_db->dict.name, name);
331 else
332 msg_fatal("%s: duplicate entry: \"%s\"", dict_db->dict.name, name);
333 }
334 if (dict->flags & DICT_FLAG_SYNC_UPDATE)
335 if (DICT_DB_SYNC(db, 0) < 0)
336 msg_fatal("%s: flush dictionary: %m", dict_db->dict.name);
337
338 /*
339 * Release the exclusive lock.
340 */
341 if ((dict->flags & DICT_FLAG_LOCK)
342 && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
343 msg_fatal("%s: unlock dictionary: %m", dict_db->dict.name);
344
345 return (status);
346 }
347
348 /* delete one entry from the dictionary */
349
dict_db_delete(DICT * dict,const char * name)350 static int dict_db_delete(DICT *dict, const char *name)
351 {
352 DICT_DB *dict_db = (DICT_DB *) dict;
353 DB *db = dict_db->db;
354 DBT db_key;
355 int status = 1;
356 int flags = 0;
357
358 dict->error = 0;
359
360 /*
361 * Sanity check.
362 */
363 if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
364 msg_panic("dict_db_delete: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag");
365
366 /*
367 * Optionally fold the key.
368 */
369 if (dict->flags & DICT_FLAG_FOLD_FIX) {
370 if (dict->fold_buf == 0)
371 dict->fold_buf = vstring_alloc(10);
372 vstring_strcpy(dict->fold_buf, name);
373 name = lowercase(vstring_str(dict->fold_buf));
374 }
375 memset(&db_key, 0, sizeof(db_key));
376
377 /*
378 * Acquire an exclusive lock.
379 */
380 if ((dict->flags & DICT_FLAG_LOCK)
381 && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0)
382 msg_fatal("%s: lock dictionary: %m", dict_db->dict.name);
383
384 /*
385 * See if this DB file was written with one null byte appended to key and
386 * value.
387 */
388 if (dict->flags & DICT_FLAG_TRY1NULL) {
389 db_key.data = (void *) name;
390 db_key.size = strlen(name) + 1;
391 if ((status = DICT_DB_DEL(db, &db_key, flags)) < 0)
392 msg_fatal("error deleting from %s: %m", dict_db->dict.name);
393 if (status == 0)
394 dict->flags &= ~DICT_FLAG_TRY0NULL;
395 }
396
397 /*
398 * See if this DB file was written with no null byte appended to key and
399 * value.
400 */
401 if (status > 0 && (dict->flags & DICT_FLAG_TRY0NULL)) {
402 db_key.data = (void *) name;
403 db_key.size = strlen(name);
404 if ((status = DICT_DB_DEL(db, &db_key, flags)) < 0)
405 msg_fatal("error deleting from %s: %m", dict_db->dict.name);
406 if (status == 0)
407 dict->flags &= ~DICT_FLAG_TRY1NULL;
408 }
409 if (dict->flags & DICT_FLAG_SYNC_UPDATE)
410 if (DICT_DB_SYNC(db, 0) < 0)
411 msg_fatal("%s: flush dictionary: %m", dict_db->dict.name);
412
413 /*
414 * Release the exclusive lock.
415 */
416 if ((dict->flags & DICT_FLAG_LOCK)
417 && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
418 msg_fatal("%s: unlock dictionary: %m", dict_db->dict.name);
419
420 return status;
421 }
422
423 /* dict_db_sequence - traverse the dictionary */
424
dict_db_sequence(DICT * dict,int function,const char ** key,const char ** value)425 static int dict_db_sequence(DICT *dict, int function,
426 const char **key, const char **value)
427 {
428 const char *myname = "dict_db_sequence";
429 DICT_DB *dict_db = (DICT_DB *) dict;
430 DB *db = dict_db->db;
431 DBT db_key;
432 DBT db_value;
433 int status = 0;
434 int db_function;
435
436 dict->error = 0;
437
438 #if DB_VERSION_MAJOR > 1
439
440 /*
441 * Initialize.
442 */
443 memset(&db_key, 0, sizeof(db_key));
444 memset(&db_value, 0, sizeof(db_value));
445
446 /*
447 * Determine the function.
448 */
449 switch (function) {
450 case DICT_SEQ_FUN_FIRST:
451 if (dict_db->cursor == 0)
452 DICT_DB_CURSOR(db, &(dict_db->cursor));
453 db_function = DB_FIRST;
454 break;
455 case DICT_SEQ_FUN_NEXT:
456 if (dict_db->cursor == 0)
457 msg_panic("%s: no cursor", myname);
458 db_function = DB_NEXT;
459 break;
460 default:
461 msg_panic("%s: invalid function %d", myname, function);
462 }
463
464 /*
465 * Acquire a shared lock.
466 */
467 if ((dict->flags & DICT_FLAG_LOCK)
468 && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0)
469 msg_fatal("%s: lock dictionary: %m", dict_db->dict.name);
470
471 /*
472 * Database lookup.
473 */
474 status =
475 dict_db->cursor->c_get(dict_db->cursor, &db_key, &db_value, db_function);
476 if (status != 0 && status != DB_NOTFOUND)
477 msg_fatal("error [%d] seeking %s: %m", status, dict_db->dict.name);
478
479 /*
480 * Release the shared lock.
481 */
482 if ((dict->flags & DICT_FLAG_LOCK)
483 && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
484 msg_fatal("%s: unlock dictionary: %m", dict_db->dict.name);
485
486 if (status == 0) {
487
488 /*
489 * Copy the result so it is guaranteed null terminated.
490 */
491 *key = SCOPY(dict_db->key_buf, db_key.data, db_key.size);
492 *value = SCOPY(dict_db->val_buf, db_value.data, db_value.size);
493 }
494 return (status);
495 #else
496
497 /*
498 * determine the function
499 */
500 switch (function) {
501 case DICT_SEQ_FUN_FIRST:
502 db_function = R_FIRST;
503 break;
504 case DICT_SEQ_FUN_NEXT:
505 db_function = R_NEXT;
506 break;
507 default:
508 msg_panic("%s: invalid function %d", myname, function);
509 }
510
511 /*
512 * Acquire a shared lock.
513 */
514 if ((dict->flags & DICT_FLAG_LOCK)
515 && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0)
516 msg_fatal("%s: lock dictionary: %m", dict_db->dict.name);
517
518 if ((status = db->seq(db, &db_key, &db_value, db_function)) < 0)
519 msg_fatal("error seeking %s: %m", dict_db->dict.name);
520
521 /*
522 * Release the shared lock.
523 */
524 if ((dict->flags & DICT_FLAG_LOCK)
525 && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
526 msg_fatal("%s: unlock dictionary: %m", dict_db->dict.name);
527
528 if (status == 0) {
529
530 /*
531 * Copy the result so that it is guaranteed null terminated.
532 */
533 *key = SCOPY(dict_db->key_buf, db_key.data, db_key.size);
534 *value = SCOPY(dict_db->val_buf, db_value.data, db_value.size);
535 }
536 return status;
537 #endif
538 }
539
540 /* dict_db_close - close data base */
541
dict_db_close(DICT * dict)542 static void dict_db_close(DICT *dict)
543 {
544 DICT_DB *dict_db = (DICT_DB *) dict;
545
546 #if DB_VERSION_MAJOR > 1
547 if (dict_db->cursor)
548 dict_db->cursor->c_close(dict_db->cursor);
549 #endif
550 if (DICT_DB_SYNC(dict_db->db, 0) < 0)
551 msg_fatal("flush database %s: %m", dict_db->dict.name);
552
553 /*
554 * With some Berkeley DB implementations, close fails with a bogus ENOENT
555 * error, while it reports no errors with put+sync, no errors with
556 * del+sync, and no errors with the sync operation just before this
557 * comment. This happens in programs that never fork and that never share
558 * the database with other processes. The bogus close error has been
559 * reported for programs that use the first/next iterator. Instead of
560 * making Postfix look bad because it reports errors that other programs
561 * ignore, I'm going to report the bogus error as a non-error.
562 */
563 if (DICT_DB_CLOSE(dict_db->db) < 0)
564 msg_info("close database %s: %m (possible Berkeley DB bug)",
565 dict_db->dict.name);
566 #if DB_VERSION_MAJOR > 2
567 dict_db->dbenv->close(dict_db->dbenv, 0);
568 #endif
569 if (dict_db->key_buf)
570 vstring_free(dict_db->key_buf);
571 if (dict_db->val_buf)
572 vstring_free(dict_db->val_buf);
573 if (dict->fold_buf)
574 vstring_free(dict->fold_buf);
575 dict_free(dict);
576 }
577
578 #if DB_VERSION_MAJOR > 2
579
580 /* dict_db_new_env - workaround for undocumented ./DB_CONFIG read */
581
dict_db_new_env(const char * db_path)582 static DB_ENV *dict_db_new_env(const char *db_path)
583 {
584 VSTRING *db_home_buf;
585 DB_ENV *dbenv;
586 u_int32_t cache_size_gbytes;
587 u_int32_t cache_size_bytes;
588 int ncache;
589
590 if ((errno = db_env_create(&dbenv, 0)) != 0)
591 msg_fatal("create DB environment: %m");
592 #if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 7)
593 if ((errno = dbenv->get_cachesize(dbenv, &cache_size_gbytes,
594 &cache_size_bytes, &ncache)) != 0)
595 msg_fatal("get DB cache size: %m");
596 if (cache_size_gbytes == 0 && cache_size_bytes < dict_db_cache_size) {
597 if ((errno = dbenv->set_cache_max(dbenv, cache_size_gbytes,
598 dict_db_cache_size)) != 0)
599 msg_fatal("set DB max cache size %d: %m", dict_db_cache_size);
600 if ((errno = dbenv->set_cachesize(dbenv, cache_size_gbytes,
601 dict_db_cache_size, ncache)) != 0)
602 msg_fatal("set DB cache size %d: %m", dict_db_cache_size);
603 }
604 #endif
605 /* XXX db_home is also the default directory for the .db file. */
606 db_home_buf = vstring_alloc(100);
607 if ((errno = dbenv->open(dbenv, sane_dirname(db_home_buf, db_path),
608 DB_INIT_MPOOL | DB_CREATE | DB_PRIVATE, 0)) != 0)
609 msg_fatal("open DB environment: %m");
610 vstring_free(db_home_buf);
611 return (dbenv);
612 }
613
614 #endif
615
616 /* dict_db_open - open data base */
617
dict_db_open(const char * class,const char * path,int open_flags,int type,void * tweak,int dict_flags)618 static DICT *dict_db_open(const char *class, const char *path, int open_flags,
619 int type, void *tweak, int dict_flags)
620 {
621 DICT_DB *dict_db;
622 struct stat st;
623 DB *db = 0;
624 char *db_path = 0;
625 VSTRING *db_base_buf = 0;
626 int lock_fd = -1;
627 int dbfd;
628
629 #if DB_VERSION_MAJOR > 1
630 int db_flags;
631
632 #endif
633 #if DB_VERSION_MAJOR > 2
634 DB_ENV *dbenv = 0;
635
636 #endif
637
638 /*
639 * Mismatches between #include file and library are a common cause for
640 * trouble.
641 */
642 #if DB_VERSION_MAJOR > 1
643 int major_version;
644 int minor_version;
645 int patch_version;
646
647 (void) db_version(&major_version, &minor_version, &patch_version);
648 if (major_version != DB_VERSION_MAJOR || minor_version != DB_VERSION_MINOR)
649 return (dict_surrogate(class, path, open_flags, dict_flags,
650 "incorrect version of Berkeley DB: "
651 "compiled against %d.%d.%d, run-time linked against %d.%d.%d",
652 DB_VERSION_MAJOR, DB_VERSION_MINOR, DB_VERSION_PATCH,
653 major_version, minor_version, patch_version));
654 if (msg_verbose) {
655 msg_info("Compiled against Berkeley DB: %d.%d.%d\n",
656 DB_VERSION_MAJOR, DB_VERSION_MINOR, DB_VERSION_PATCH);
657 msg_info("Run-time linked against Berkeley DB: %d.%d.%d\n",
658 major_version, minor_version, patch_version);
659 }
660 #else
661 if (msg_verbose)
662 msg_info("Compiled against Berkeley DB version 1");
663 #endif
664
665 db_path = concatenate(path, ".db", (char *) 0);
666
667 /*
668 * Note: DICT_FLAG_LOCK is used only by programs that do fine-grained (in
669 * the time domain) locking while accessing individual database records.
670 *
671 * Programs such as postmap/postalias use their own large-grained (in the
672 * time domain) locks while rewriting the entire file.
673 *
674 * XXX DB version 4.1 will not open a zero-length file. This means we must
675 * open an existing file without O_CREAT|O_TRUNC, and that we must let
676 * db_open() create a non-existent file for us.
677 */
678 #define LOCK_OPEN_FLAGS(f) ((f) & ~(O_CREAT|O_TRUNC))
679 #if DB_VERSION_MAJOR <= 2
680 #define FREE_RETURN(e) do { \
681 DICT *_dict = (e); if (db) DICT_DB_CLOSE(db); \
682 if (lock_fd >= 0) (void) close(lock_fd); \
683 if (db_base_buf) vstring_free(db_base_buf); \
684 if (db_path) myfree(db_path); return (_dict); \
685 } while (0)
686 #else
687 #define FREE_RETURN(e) do { \
688 DICT *_dict = (e); if (db) DICT_DB_CLOSE(db); \
689 if (dbenv) dbenv->close(dbenv, 0); \
690 if (lock_fd >= 0) (void) close(lock_fd); \
691 if (db_base_buf) vstring_free(db_base_buf); \
692 if (db_path) myfree(db_path); \
693 return (_dict); \
694 } while (0)
695 #endif
696
697 if (dict_flags & DICT_FLAG_LOCK) {
698 if ((lock_fd = open(db_path, LOCK_OPEN_FLAGS(open_flags), 0644)) < 0) {
699 if (errno != ENOENT)
700 FREE_RETURN(dict_surrogate(class, path, open_flags, dict_flags,
701 "open database %s: %m", db_path));
702 } else {
703 if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0)
704 msg_fatal("shared-lock database %s for open: %m", db_path);
705 }
706 }
707
708 /*
709 * Use the DB 1.x programming interface. This is the default interface
710 * with 4.4BSD systems. It is also available via the db_185 compatibility
711 * interface, but that interface does not have the undocumented feature
712 * that we need to make file locking safe with POSIX fcntl() locking.
713 */
714 #if DB_VERSION_MAJOR < 2
715 if ((db = dbopen(db_path, open_flags, 0644, type, tweak)) == 0)
716 FREE_RETURN(dict_surrogate(class, path, open_flags, dict_flags,
717 "open database %s: %m", db_path));
718 dbfd = db->fd(db);
719 #endif
720
721 /*
722 * Use the DB 2.x programming interface. Jump a couple extra hoops.
723 */
724 #if DB_VERSION_MAJOR == 2
725 db_flags = DB_FCNTL_LOCKING;
726 if (open_flags == O_RDONLY)
727 db_flags |= DB_RDONLY;
728 if (open_flags & O_CREAT)
729 db_flags |= DB_CREATE;
730 if (open_flags & O_TRUNC)
731 db_flags |= DB_TRUNCATE;
732 if ((errno = db_open(db_path, type, db_flags, 0644, 0, tweak, &db)) != 0)
733 FREE_RETURN(dict_surrogate(class, path, open_flags, dict_flags,
734 "open database %s: %m", db_path));
735 if (db == 0)
736 msg_panic("db_open null result");
737 if ((errno = db->fd(db, &dbfd)) != 0)
738 msg_fatal("get database file descriptor: %m");
739 #endif
740
741 /*
742 * Use the DB 3.x programming interface. Jump even more hoops.
743 */
744 #if DB_VERSION_MAJOR > 2
745 db_flags = DB_FCNTL_LOCKING;
746 if (open_flags == O_RDONLY)
747 db_flags |= DB_RDONLY;
748 if (open_flags & O_CREAT)
749 db_flags |= DB_CREATE;
750 if (open_flags & O_TRUNC)
751 db_flags |= DB_TRUNCATE;
752 if ((errno = db_create(&db, dbenv = dict_db_new_env(db_path), 0)) != 0)
753 msg_fatal("create DB database: %m");
754 if (db == 0)
755 msg_panic("db_create null result");
756 if (type == DB_HASH && db->set_h_nelem(db, DICT_DB_NELM) != 0)
757 msg_fatal("set DB hash element count %d: %m", DICT_DB_NELM);
758 db_base_buf = vstring_alloc(100);
759 #if DB_VERSION_MAJOR == 18 || DB_VERSION_MAJOR == 6 || DB_VERSION_MAJOR == 5 || \
760 (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR > 0)
761 if ((errno = db->open(db, 0, sane_basename(db_base_buf, db_path),
762 0, type, db_flags, 0644)) != 0)
763 FREE_RETURN(dict_surrogate(class, path, open_flags, dict_flags,
764 "open database %s: %m", db_path));
765 #elif (DB_VERSION_MAJOR == 3 || DB_VERSION_MAJOR == 4)
766 if ((errno = db->open(db, sane_basename(db_base_buf, db_path), 0,
767 type, db_flags, 0644)) != 0)
768 FREE_RETURN(dict_surrogate(class, path, open_flags, dict_flags,
769 "open database %s: %m", db_path));
770 #else
771 #error "Unsupported Berkeley DB version"
772 #endif
773 vstring_free(db_base_buf);
774 if ((errno = db->fd(db, &dbfd)) != 0)
775 msg_fatal("get database file descriptor: %m");
776 #endif
777 if ((dict_flags & DICT_FLAG_LOCK) && lock_fd >= 0) {
778 if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
779 msg_fatal("unlock database %s for open: %m", db_path);
780 if (close(lock_fd) < 0)
781 msg_fatal("close database %s: %m", db_path);
782 lock_fd = -1;
783 }
784 dict_db = (DICT_DB *) dict_alloc(class, db_path, sizeof(*dict_db));
785 dict_db->dict.lookup = dict_db_lookup;
786 dict_db->dict.update = dict_db_update;
787 dict_db->dict.delete = dict_db_delete;
788 dict_db->dict.sequence = dict_db_sequence;
789 dict_db->dict.close = dict_db_close;
790 dict_db->dict.lock_fd = dbfd;
791 dict_db->dict.stat_fd = dbfd;
792 if (fstat(dict_db->dict.stat_fd, &st) < 0)
793 msg_fatal("dict_db_open: fstat: %m");
794 dict_db->dict.mtime = st.st_mtime;
795 dict_db->dict.owner.uid = st.st_uid;
796 dict_db->dict.owner.status = (st.st_uid != 0);
797
798 /*
799 * Warn if the source file is newer than the indexed file, except when
800 * the source file changed only seconds ago.
801 */
802 if ((dict_flags & DICT_FLAG_LOCK) != 0
803 && stat(path, &st) == 0
804 && st.st_mtime > dict_db->dict.mtime
805 && st.st_mtime < time((time_t *) 0) - 100)
806 msg_warn("database %s is older than source file %s", db_path, path);
807
808 close_on_exec(dict_db->dict.lock_fd, CLOSE_ON_EXEC);
809 close_on_exec(dict_db->dict.stat_fd, CLOSE_ON_EXEC);
810 dict_db->dict.flags = dict_flags | DICT_FLAG_FIXED;
811 if ((dict_flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
812 dict_db->dict.flags |= (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL);
813 if (dict_flags & DICT_FLAG_FOLD_FIX)
814 dict_db->dict.fold_buf = vstring_alloc(10);
815 dict_db->db = db;
816 #if DB_VERSION_MAJOR > 2
817 dict_db->dbenv = dbenv;
818 #endif
819 #if DB_VERSION_MAJOR > 1
820 dict_db->cursor = 0;
821 #endif
822 dict_db->key_buf = 0;
823 dict_db->val_buf = 0;
824
825 myfree(db_path);
826 return (DICT_DEBUG (&dict_db->dict));
827 }
828
829 /* dict_hash_open - create association with data base */
830
dict_hash_open(const char * path,int open_flags,int dict_flags)831 DICT *dict_hash_open(const char *path, int open_flags, int dict_flags)
832 {
833 #if DB_VERSION_MAJOR < 2
834 HASHINFO tweak;
835
836 memset((void *) &tweak, 0, sizeof(tweak));
837 tweak.nelem = DICT_DB_NELM;
838 tweak.cachesize = dict_db_cache_size;
839 #endif
840 #if DB_VERSION_MAJOR == 2
841 DB_INFO tweak;
842
843 memset((void *) &tweak, 0, sizeof(tweak));
844 tweak.h_nelem = DICT_DB_NELM;
845 tweak.db_cachesize = dict_db_cache_size;
846 #endif
847 #if DB_VERSION_MAJOR > 2
848 void *tweak;
849
850 tweak = 0;
851 #endif
852 return (dict_db_open(DICT_TYPE_HASH, path, open_flags, DB_HASH,
853 (void *) &tweak, dict_flags));
854 }
855
856 /* dict_btree_open - create association with data base */
857
dict_btree_open(const char * path,int open_flags,int dict_flags)858 DICT *dict_btree_open(const char *path, int open_flags, int dict_flags)
859 {
860 #if DB_VERSION_MAJOR < 2
861 BTREEINFO tweak;
862
863 memset((void *) &tweak, 0, sizeof(tweak));
864 tweak.cachesize = dict_db_cache_size;
865 #endif
866 #if DB_VERSION_MAJOR == 2
867 DB_INFO tweak;
868
869 memset((void *) &tweak, 0, sizeof(tweak));
870 tweak.db_cachesize = dict_db_cache_size;
871 #endif
872 #if DB_VERSION_MAJOR > 2
873 void *tweak;
874
875 tweak = 0;
876 #endif
877
878 return (dict_db_open(DICT_TYPE_BTREE, path, open_flags, DB_BTREE,
879 (void *) &tweak, dict_flags));
880 }
881
882 #endif
883