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