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