xref: /netbsd-src/external/ibm-public/postfix/dist/src/util/dict_lmdb.c (revision b5c47949a45ac972130c38cf13dfd8afb1f09285)
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 
87 typedef 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 
131 static 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 
216 static 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 
307 static 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 
392 static 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 
473 static 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 
489 static 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 
498 static 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 
533 static 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 
543 DICT   *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