dict_lmdb.c revision 1.3
1/*	$NetBSD: dict_lmdb.c,v 1.3 2020/03/18 19:05:21 christos Exp $	*/
2
3/*++
4/* NAME
5/*	dict_lmdb 3
6/* SUMMARY
7/*	dictionary manager interface to OpenLDAP LMDB files
8/* SYNOPSIS
9/*	#include <dict_lmdb.h>
10/*
11/*	extern size_t dict_lmdb_map_size;
12/*
13/*	DEFINE_DICT_LMDB_MAP_SIZE;
14/*
15/*	DICT	*dict_lmdb_open(path, open_flags, dict_flags)
16/*	const char *name;
17/*	const char *path;
18/*	int	open_flags;
19/*	int	dict_flags;
20/* DESCRIPTION
21/*	dict_lmdb_open() opens the named LMDB database and makes
22/*	it available via the generic interface described in
23/*	dict_open(3).
24/*
25/*	The dict_lmdb_map_size variable specifies the initial
26/*	database memory map size.  When a map becomes full its size
27/*	is doubled, and other programs pick up the size change.
28/*
29/*	This variable cannot be exported via the dict(3) API and
30/*	must therefore be defined in the calling program by invoking
31/*	the DEFINE_DICT_LMDB_MAP_SIZE macro at the global level.
32/* DIAGNOSTICS
33/*	Fatal errors: cannot open file, file write error, out of
34/*	memory.
35/* BUGS
36/*	The on-the-fly map resize operations require no concurrent
37/*	activity in the same database by other threads in the same
38/*	memory address space.
39/* SEE ALSO
40/*	dict(3) generic dictionary manager
41/* LICENSE
42/* .ad
43/* .fi
44/*	The Secure Mailer license must be distributed with this software.
45/* AUTHOR(S)
46/*	Howard Chu
47/*	Symas Corporation
48/*
49/*	Wietse Venema
50/*	IBM T.J. Watson Research
51/*	P.O. Box 704
52/*	Yorktown Heights, NY 10598, USA
53/*
54/*	Wietse Venema
55/*	Google, Inc.
56/*	111 8th Avenue
57/*	New York, NY 10011, USA
58/*--*/
59
60#include <sys_defs.h>
61
62#ifdef HAS_LMDB
63
64/* System library. */
65
66#include <sys/stat.h>
67#include <string.h>
68#include <unistd.h>
69#include <limits.h>
70
71/* Utility library. */
72
73#include <msg.h>
74#include <mymalloc.h>
75#include <htable.h>
76#include <iostuff.h>
77#include <vstring.h>
78#include <myflock.h>
79#include <stringops.h>
80#include <slmdb.h>
81#include <dict.h>
82#include <dict_lmdb.h>
83#include <warn_stat.h>
84
85/* Application-specific. */
86
87typedef struct {
88    DICT    dict;			/* generic members */
89    SLMDB   slmdb;			/* sane LMDB API */
90    VSTRING *key_buf;			/* key buffer */
91    VSTRING *val_buf;			/* value buffer */
92} DICT_LMDB;
93
94 /*
95  * The LMDB database filename suffix happens to equal our DICT_TYPE_LMDB
96  * prefix, but that doesn't mean it is kosher to use DICT_TYPE_LMDB where a
97  * suffix is needed, so we define an explicit suffix here.
98  */
99#define DICT_LMDB_SUFFIX	"lmdb"
100
101 /*
102  * Make a safe string copy that is guaranteed to be null-terminated.
103  */
104#define SCOPY(buf, data, size) \
105    vstring_str(vstring_strncpy(buf ? buf : (buf = vstring_alloc(10)), data, size))
106
107 /*
108  * Postfix writers recover from a "map full" error by increasing the memory
109  * map size with a factor DICT_LMDB_SIZE_INCR (up to some limit) and
110  * retrying the transaction.
111  *
112  * Each dict(3) API call is retried no more than a few times. For bulk-mode
113  * transactions the number of retries is proportional to the size of the
114  * address space.
115  *
116  * We do not expose these details to the Postfix user interface. The purpose of
117  * Postfix is to solve problems, not punt them to the user.
118  */
119#define DICT_LMDB_SIZE_INCR	2	/* Increase size by 1 bit on retry */
120#define DICT_LMDB_SIZE_MAX	SSIZE_T_MAX
121
122#define DICT_LMDB_API_RETRY_LIMIT 2	/* Retries per dict(3) API call */
123#define DICT_LMDB_BULK_RETRY_LIMIT \
124	((int) (2 * sizeof(size_t) * CHAR_BIT))	/* Retries per bulk-mode
125						 * transaction */
126
127/* #define msg_verbose 1 */
128
129/* dict_lmdb_lookup - find database entry */
130
131static const char *dict_lmdb_lookup(DICT *dict, const char *name)
132{
133    DICT_LMDB *dict_lmdb = (DICT_LMDB *) dict;
134    MDB_val mdb_key;
135    MDB_val mdb_value;
136    const char *result = 0;
137    int     status;
138    ssize_t klen;
139
140    dict->error = 0;
141    klen = strlen(name);
142
143    /*
144     * Sanity check.
145     */
146    if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
147	msg_panic("dict_lmdb_lookup: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag");
148
149    /*
150     * Optionally fold the key.
151     */
152    if (dict->flags & DICT_FLAG_FOLD_FIX) {
153	if (dict->fold_buf == 0)
154	    dict->fold_buf = vstring_alloc(10);
155	vstring_strcpy(dict->fold_buf, name);
156	name = lowercase(vstring_str(dict->fold_buf));
157    }
158
159    /*
160     * Acquire a shared lock.
161     */
162    if ((dict->flags & DICT_FLAG_LOCK)
163      && myflock(dict->lock_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_SHARED) < 0)
164	msg_fatal("%s: lock dictionary: %m", dict->name);
165
166    /*
167     * See if this LMDB file was written with one null byte appended to key
168     * and value.
169     */
170    if (dict->flags & DICT_FLAG_TRY1NULL) {
171	mdb_key.mv_data = (void *) name;
172	mdb_key.mv_size = klen + 1;
173	status = slmdb_get(&dict_lmdb->slmdb, &mdb_key, &mdb_value);
174	if (status == 0) {
175	    dict->flags &= ~DICT_FLAG_TRY0NULL;
176	    result = SCOPY(dict_lmdb->val_buf, mdb_value.mv_data,
177			   mdb_value.mv_size);
178	} else if (status != MDB_NOTFOUND) {
179	    msg_fatal("error reading %s:%s: %s",
180		      dict_lmdb->dict.type, dict_lmdb->dict.name,
181		      mdb_strerror(status));
182	}
183    }
184
185    /*
186     * See if this LMDB file was written with no null byte appended to key
187     * and value.
188     */
189    if (result == 0 && (dict->flags & DICT_FLAG_TRY0NULL)) {
190	mdb_key.mv_data = (void *) name;
191	mdb_key.mv_size = klen;
192	status = slmdb_get(&dict_lmdb->slmdb, &mdb_key, &mdb_value);
193	if (status == 0) {
194	    dict->flags &= ~DICT_FLAG_TRY1NULL;
195	    result = SCOPY(dict_lmdb->val_buf, mdb_value.mv_data,
196			   mdb_value.mv_size);
197	} else if (status != MDB_NOTFOUND) {
198	    msg_fatal("error reading %s:%s: %s",
199		      dict_lmdb->dict.type, dict_lmdb->dict.name,
200		      mdb_strerror(status));
201	}
202    }
203
204    /*
205     * Release the shared lock.
206     */
207    if ((dict->flags & DICT_FLAG_LOCK)
208	&& myflock(dict->lock_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_NONE) < 0)
209	msg_fatal("%s: unlock dictionary: %m", dict->name);
210
211    return (result);
212}
213
214/* dict_lmdb_update - add or update database entry */
215
216static int dict_lmdb_update(DICT *dict, const char *name, const char *value)
217{
218    DICT_LMDB *dict_lmdb = (DICT_LMDB *) dict;
219    MDB_val mdb_key;
220    MDB_val mdb_value;
221    int     status;
222
223    dict->error = 0;
224
225    /*
226     * Sanity check.
227     */
228    if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
229	msg_panic("dict_lmdb_update: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag");
230
231    /*
232     * Optionally fold the key.
233     */
234    if (dict->flags & DICT_FLAG_FOLD_FIX) {
235	if (dict->fold_buf == 0)
236	    dict->fold_buf = vstring_alloc(10);
237	vstring_strcpy(dict->fold_buf, name);
238	name = lowercase(vstring_str(dict->fold_buf));
239    }
240    mdb_key.mv_data = (void *) name;
241    mdb_value.mv_data = (void *) value;
242    mdb_key.mv_size = strlen(name);
243    mdb_value.mv_size = strlen(value);
244
245    /*
246     * If undecided about appending a null byte to key and value, choose a
247     * default depending on the platform.
248     */
249    if ((dict->flags & DICT_FLAG_TRY1NULL)
250	&& (dict->flags & DICT_FLAG_TRY0NULL)) {
251#ifdef LMDB_NO_TRAILING_NULL
252	dict->flags &= ~DICT_FLAG_TRY1NULL;
253#else
254	dict->flags &= ~DICT_FLAG_TRY0NULL;
255#endif
256    }
257
258    /*
259     * Optionally append a null byte to key and value.
260     */
261    if (dict->flags & DICT_FLAG_TRY1NULL) {
262	mdb_key.mv_size++;
263	mdb_value.mv_size++;
264    }
265
266    /*
267     * Acquire an exclusive lock.
268     */
269    if ((dict->flags & DICT_FLAG_LOCK)
270    && myflock(dict->lock_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_EXCLUSIVE) < 0)
271	msg_fatal("%s: lock dictionary: %m", dict->name);
272
273    /*
274     * Do the update.
275     */
276    status = slmdb_put(&dict_lmdb->slmdb, &mdb_key, &mdb_value,
277	       (dict->flags & DICT_FLAG_DUP_REPLACE) ? 0 : MDB_NOOVERWRITE);
278    if (status != 0) {
279	if (status == MDB_KEYEXIST) {
280	    if (dict->flags & DICT_FLAG_DUP_IGNORE)
281		 /* void */ ;
282	    else if (dict->flags & DICT_FLAG_DUP_WARN)
283		msg_warn("%s:%s: duplicate entry: \"%s\"",
284			 dict_lmdb->dict.type, dict_lmdb->dict.name, name);
285	    else
286		msg_fatal("%s:%s: duplicate entry: \"%s\"",
287			  dict_lmdb->dict.type, dict_lmdb->dict.name, name);
288	} else {
289	    msg_fatal("error updating %s:%s: %s",
290		      dict_lmdb->dict.type, dict_lmdb->dict.name,
291		      mdb_strerror(status));
292	}
293    }
294
295    /*
296     * Release the exclusive lock.
297     */
298    if ((dict->flags & DICT_FLAG_LOCK)
299	&& myflock(dict->lock_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_NONE) < 0)
300	msg_fatal("%s: unlock dictionary: %m", dict->name);
301
302    return (status);
303}
304
305/* dict_lmdb_delete - delete one entry from the dictionary */
306
307static int dict_lmdb_delete(DICT *dict, const char *name)
308{
309    DICT_LMDB *dict_lmdb = (DICT_LMDB *) dict;
310    MDB_val mdb_key;
311    int     status = 1;
312    ssize_t klen;
313
314    dict->error = 0;
315    klen = strlen(name);
316
317    /*
318     * Sanity check.
319     */
320    if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0)
321	msg_panic("dict_lmdb_delete: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag");
322
323    /*
324     * Optionally fold the key.
325     */
326    if (dict->flags & DICT_FLAG_FOLD_FIX) {
327	if (dict->fold_buf == 0)
328	    dict->fold_buf = vstring_alloc(10);
329	vstring_strcpy(dict->fold_buf, name);
330	name = lowercase(vstring_str(dict->fold_buf));
331    }
332
333    /*
334     * Acquire an exclusive lock.
335     */
336    if ((dict->flags & DICT_FLAG_LOCK)
337    && myflock(dict->lock_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_EXCLUSIVE) < 0)
338	msg_fatal("%s: lock dictionary: %m", dict->name);
339
340    /*
341     * See if this LMDB file was written with one null byte appended to key
342     * and value.
343     */
344    if (dict->flags & DICT_FLAG_TRY1NULL) {
345	mdb_key.mv_data = (void *) name;
346	mdb_key.mv_size = klen + 1;
347	status = slmdb_del(&dict_lmdb->slmdb, &mdb_key);
348	if (status != 0) {
349	    if (status == MDB_NOTFOUND)
350		status = 1;
351	    else
352		msg_fatal("error deleting from %s:%s: %s",
353			  dict_lmdb->dict.type, dict_lmdb->dict.name,
354			  mdb_strerror(status));
355	} else {
356	    dict->flags &= ~DICT_FLAG_TRY0NULL;	/* found */
357	}
358    }
359
360    /*
361     * See if this LMDB file was written with no null byte appended to key
362     * and value.
363     */
364    if (status > 0 && (dict->flags & DICT_FLAG_TRY0NULL)) {
365	mdb_key.mv_data = (void *) name;
366	mdb_key.mv_size = klen;
367	status = slmdb_del(&dict_lmdb->slmdb, &mdb_key);
368	if (status != 0) {
369	    if (status == MDB_NOTFOUND)
370		status = 1;
371	    else
372		msg_fatal("error deleting from %s:%s: %s",
373			  dict_lmdb->dict.type, dict_lmdb->dict.name,
374			  mdb_strerror(status));
375	} else {
376	    dict->flags &= ~DICT_FLAG_TRY1NULL;	/* found */
377	}
378    }
379
380    /*
381     * Release the exclusive lock.
382     */
383    if ((dict->flags & DICT_FLAG_LOCK)
384	&& myflock(dict->lock_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_NONE) < 0)
385	msg_fatal("%s: unlock dictionary: %m", dict->name);
386
387    return (status);
388}
389
390/* dict_lmdb_sequence - traverse the dictionary */
391
392static int dict_lmdb_sequence(DICT *dict, int function,
393			              const char **key, const char **value)
394{
395    const char *myname = "dict_lmdb_sequence";
396    DICT_LMDB *dict_lmdb = (DICT_LMDB *) dict;
397    MDB_val mdb_key;
398    MDB_val mdb_value;
399    MDB_cursor_op op;
400    int     status;
401
402    dict->error = 0;
403
404    /*
405     * Determine the seek function.
406     */
407    switch (function) {
408    case DICT_SEQ_FUN_FIRST:
409	op = MDB_FIRST;
410	break;
411    case DICT_SEQ_FUN_NEXT:
412	op = MDB_NEXT;
413	break;
414    default:
415	msg_panic("%s: invalid function: %d", myname, function);
416    }
417
418    /*
419     * Acquire a shared lock.
420     */
421    if ((dict->flags & DICT_FLAG_LOCK)
422      && myflock(dict->lock_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_SHARED) < 0)
423	msg_fatal("%s: lock dictionary: %m", dict->name);
424
425    /*
426     * Database lookup.
427     */
428    status = slmdb_cursor_get(&dict_lmdb->slmdb, &mdb_key, &mdb_value, op);
429
430    switch (status) {
431
432	/*
433	 * Copy the key and value so they are guaranteed null terminated.
434	 */
435    case 0:
436	*key = SCOPY(dict_lmdb->key_buf, mdb_key.mv_data, mdb_key.mv_size);
437	if (mdb_value.mv_data != 0 && mdb_value.mv_size > 0)
438	    *value = SCOPY(dict_lmdb->val_buf, mdb_value.mv_data,
439			   mdb_value.mv_size);
440	else
441	    *value = "";			/* XXX */
442	break;
443
444	/*
445	 * End-of-database.
446	 */
447    case MDB_NOTFOUND:
448	status = 1;
449	/* Not: mdb_cursor_close(). Wrong abstraction level. */
450	break;
451
452	/*
453	 * Bust.
454	 */
455    default:
456	msg_fatal("error seeking %s:%s: %s",
457		  dict_lmdb->dict.type, dict_lmdb->dict.name,
458		  mdb_strerror(status));
459    }
460
461    /*
462     * Release the shared lock.
463     */
464    if ((dict->flags & DICT_FLAG_LOCK)
465	&& myflock(dict->lock_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_NONE) < 0)
466	msg_fatal("%s: unlock dictionary: %m", dict->name);
467
468    return (status);
469}
470
471/* dict_lmdb_close - disassociate from data base */
472
473static void dict_lmdb_close(DICT *dict)
474{
475    DICT_LMDB *dict_lmdb = (DICT_LMDB *) dict;
476
477    slmdb_close(&dict_lmdb->slmdb);
478    if (dict_lmdb->key_buf)
479	vstring_free(dict_lmdb->key_buf);
480    if (dict_lmdb->val_buf)
481	vstring_free(dict_lmdb->val_buf);
482    if (dict->fold_buf)
483	vstring_free(dict->fold_buf);
484    dict_free(dict);
485}
486
487/* dict_lmdb_longjmp - repeat bulk transaction */
488
489static void dict_lmdb_longjmp(void *context, int val)
490{
491    DICT_LMDB *dict_lmdb = (DICT_LMDB *) context;
492
493    dict_longjmp(&dict_lmdb->dict, val);
494}
495
496/* dict_lmdb_notify - debug logging */
497
498static void dict_lmdb_notify(void *context, int error_code,...)
499{
500    DICT_LMDB *dict_lmdb = (DICT_LMDB *) context;
501    va_list ap;
502
503    va_start(ap, error_code);
504    switch (error_code) {
505    case MDB_SUCCESS:
506	msg_info("database %s:%s: using size limit %lu during open",
507		 dict_lmdb->dict.type, dict_lmdb->dict.name,
508		 (unsigned long) va_arg(ap, size_t));
509	break;
510    case MDB_MAP_FULL:
511	msg_info("database %s:%s: using size limit %lu after MDB_MAP_FULL",
512		 dict_lmdb->dict.type, dict_lmdb->dict.name,
513		 (unsigned long) va_arg(ap, size_t));
514	break;
515    case MDB_MAP_RESIZED:
516	msg_info("database %s:%s: using size limit %lu after MDB_MAP_RESIZED",
517		 dict_lmdb->dict.type, dict_lmdb->dict.name,
518		 (unsigned long) va_arg(ap, size_t));
519	break;
520    case MDB_READERS_FULL:
521	msg_info("database %s:%s: pausing after MDB_READERS_FULL",
522		 dict_lmdb->dict.type, dict_lmdb->dict.name);
523	break;
524    default:
525	msg_warn("unknown MDB error code: %d", error_code);
526	break;
527    }
528    va_end(ap);
529}
530
531/* dict_lmdb_assert - report LMDB internal assertion failure */
532
533static void dict_lmdb_assert(void *context, const char *text)
534{
535    DICT_LMDB *dict_lmdb = (DICT_LMDB *) context;
536
537    msg_fatal("%s:%s: internal error: %s",
538	      dict_lmdb->dict.type, dict_lmdb->dict.name, text);
539}
540
541/* dict_lmdb_open - open LMDB data base */
542
543DICT   *dict_lmdb_open(const char *path, int open_flags, int dict_flags)
544{
545    DICT_LMDB *dict_lmdb;
546    DICT   *dict;
547    struct stat st;
548    SLMDB   slmdb;
549    char   *mdb_path;
550    int     mdb_flags, slmdb_flags, status;
551    int     db_fd;
552
553    /*
554     * Let the optimizer worry about eliminating redundant code.
555     */
556#define DICT_LMDB_OPEN_RETURN(d) do { \
557	DICT *__d = (d); \
558	myfree(mdb_path); \
559	return (__d); \
560    } while (0)
561
562    mdb_path = concatenate(path, "." DICT_TYPE_LMDB, (char *) 0);
563
564    /*
565     * Impedance adapters.
566     */
567    mdb_flags = MDB_NOSUBDIR | MDB_NOLOCK;
568    if (open_flags == O_RDONLY)
569	mdb_flags |= MDB_RDONLY;
570
571    slmdb_flags = 0;
572    if (dict_flags & DICT_FLAG_BULK_UPDATE)
573	slmdb_flags |= SLMDB_FLAG_BULK;
574
575    /*
576     * Security violation.
577     *
578     * By default, LMDB 0.9.9 writes uninitialized heap memory to a
579     * world-readable database file, as chunks of up to 4096 bytes. This is a
580     * huge memory disclosure vulnerability: memory content that a program
581     * does not intend to share ends up in a world-readable file. The content
582     * of uninitialized heap memory depends on program execution history.
583     * That history includes code execution in other libraries that are
584     * linked into the program.
585     *
586     * This is a problem whenever the user who writes the database file differs
587     * from the user who reads the database file. For example, a privileged
588     * writer and an unprivileged reader. In the case of Postfix, the
589     * postmap(1) and postalias(1) commands would leak uninitialized heap
590     * memory, as chunks of up to 4096 bytes, from a root-privileged process
591     * that writes to a database file, to unprivileged processes that read
592     * from that database file.
593     *
594     * As a workaround the postmap(1) and postalias(1) commands turn on
595     * MDB_WRITEMAP which disables the use of malloc() in LMDB. However, that
596     * does not address several disclosures of stack memory. We don't enable
597     * this workaround for Postfix databases are maintained by Postfix daemon
598     * processes, because those are accessible only by the postfix user.
599     *
600     * LMDB 0.9.10 by default does not write uninitialized heap memory to file
601     * (specify MDB_NOMEMINIT to revert that change). We use the MDB_WRITEMAP
602     * workaround for older LMDB versions.
603     */
604#ifndef MDB_NOMEMINIT
605    if (dict_flags & DICT_FLAG_BULK_UPDATE)	/* XXX Good enough */
606	mdb_flags |= MDB_WRITEMAP;
607#endif
608
609    /*
610     * Gracefully handle most database open errors.
611     */
612    if ((status = slmdb_init(&slmdb, dict_lmdb_map_size, DICT_LMDB_SIZE_INCR,
613			     DICT_LMDB_SIZE_MAX)) != 0
614	|| (status = slmdb_open(&slmdb, mdb_path, open_flags, mdb_flags,
615				slmdb_flags)) != 0) {
616	/* This leaks a little memory that would have been used otherwise. */
617	dict = dict_surrogate(DICT_TYPE_LMDB, path, open_flags, dict_flags,
618		    "open database %s: %s", mdb_path, mdb_strerror(status));
619	DICT_LMDB_OPEN_RETURN(dict);
620    }
621
622    /*
623     * XXX Persistent locking belongs in mkmap_lmdb.
624     *
625     * We just need to acquire exclusive access momentarily. This establishes
626     * that no readers are accessing old (obsoleted by copy-on-write) txn
627     * snapshots, so we are free to reuse all eligible old pages. Downgrade
628     * the lock right after acquiring it. This is sufficient to keep out
629     * other writers until we are done.
630     */
631    db_fd = slmdb_fd(&slmdb);
632    if (dict_flags & DICT_FLAG_BULK_UPDATE) {
633	if (myflock(db_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_EXCLUSIVE) < 0)
634	    msg_fatal("%s: lock dictionary: %m", mdb_path);
635	if (myflock(db_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_SHARED) < 0)
636	    msg_fatal("%s: unlock dictionary: %m", mdb_path);
637    }
638
639    /*
640     * Bundle up. From here on no more assignments to slmdb.
641     */
642    dict_lmdb = (DICT_LMDB *) dict_alloc(DICT_TYPE_LMDB, path, sizeof(*dict_lmdb));
643    dict_lmdb->slmdb = slmdb;
644    dict_lmdb->dict.lookup = dict_lmdb_lookup;
645    dict_lmdb->dict.update = dict_lmdb_update;
646    dict_lmdb->dict.delete = dict_lmdb_delete;
647    dict_lmdb->dict.sequence = dict_lmdb_sequence;
648    dict_lmdb->dict.close = dict_lmdb_close;
649
650    if (fstat(db_fd, &st) < 0)
651	msg_fatal("dict_lmdb_open: fstat: %m");
652    dict_lmdb->dict.lock_fd = dict_lmdb->dict.stat_fd = db_fd;
653    dict_lmdb->dict.lock_type = MYFLOCK_STYLE_FCNTL;
654    dict_lmdb->dict.mtime = st.st_mtime;
655    dict_lmdb->dict.owner.uid = st.st_uid;
656    dict_lmdb->dict.owner.status = (st.st_uid != 0);
657
658    dict_lmdb->key_buf = 0;
659    dict_lmdb->val_buf = 0;
660
661    /*
662     * Warn if the source file is newer than the indexed file, except when
663     * the source file changed only seconds ago.
664     */
665    if ((dict_flags & DICT_FLAG_LOCK) != 0
666	&& stat(path, &st) == 0
667	&& st.st_mtime > dict_lmdb->dict.mtime
668	&& st.st_mtime < time((time_t *) 0) - 100)
669	msg_warn("database %s is older than source file %s", mdb_path, path);
670
671#define DICT_LMDB_IMPL_FLAGS	(DICT_FLAG_FIXED | DICT_FLAG_MULTI_WRITER)
672
673    dict_lmdb->dict.flags = dict_flags | DICT_LMDB_IMPL_FLAGS;
674    if ((dict_flags & (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL)) == 0)
675	dict_lmdb->dict.flags |= (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL);
676    if (dict_flags & DICT_FLAG_FOLD_FIX)
677	dict_lmdb->dict.fold_buf = vstring_alloc(10);
678
679    if (dict_flags & DICT_FLAG_BULK_UPDATE)
680	dict_jmp_alloc(&dict_lmdb->dict);
681
682    /*
683     * The following requests return an error result only if we have serious
684     * memory corruption problem.
685     */
686    if (slmdb_control(&dict_lmdb->slmdb,
687		    CA_SLMDB_CTL_API_RETRY_LIMIT(DICT_LMDB_API_RETRY_LIMIT),
688		  CA_SLMDB_CTL_BULK_RETRY_LIMIT(DICT_LMDB_BULK_RETRY_LIMIT),
689		      CA_SLMDB_CTL_LONGJMP_FN(dict_lmdb_longjmp),
690		      CA_SLMDB_CTL_NOTIFY_FN(msg_verbose ?
691				    dict_lmdb_notify : (SLMDB_NOTIFY_FN) 0),
692		      CA_SLMDB_CTL_ASSERT_FN(dict_lmdb_assert),
693		      CA_SLMDB_CTL_CB_CONTEXT((void *) dict_lmdb),
694		      CA_SLMDB_CTL_END) != 0)
695	msg_panic("dict_lmdb_open: slmdb_control: %m");
696
697    if (msg_verbose)
698	dict_lmdb_notify((void *) dict_lmdb, MDB_SUCCESS,
699			 slmdb_curr_limit(&dict_lmdb->slmdb));
700
701    DICT_LMDB_OPEN_RETURN(DICT_DEBUG (&dict_lmdb->dict));
702}
703
704#endif
705