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