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