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 91typedef 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 135static 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 220static 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 311static 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 396static 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 477static 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 493static 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 502static 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 537static 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 547DICT *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