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