dict_lmdb.c revision 1.3
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 87typedef 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 131static 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 216static 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 307static 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 392static 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 473static 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 489static 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 498static 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 533static 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 543DICT *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