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 
91 typedef 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 
dict_lmdb_lookup(DICT * dict,const char * name)135 static 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 
dict_lmdb_update(DICT * dict,const char * name,const char * value)220 static 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 
dict_lmdb_delete(DICT * dict,const char * name)311 static 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 
dict_lmdb_sequence(DICT * dict,int function,const char ** key,const char ** value)396 static 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 
dict_lmdb_close(DICT * dict)477 static 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 
dict_lmdb_longjmp(void * context,int val)493 static 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 
dict_lmdb_notify(void * context,int error_code,...)502 static 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 
dict_lmdb_assert(void * context,const char * text)537 static 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 
dict_lmdb_open(const char * path,int open_flags,int dict_flags)547 DICT   *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