2 * Copyright (c) 1997 - 2006 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
4 * Copyright (c) 2011 - Howard Chu, Symas Corp.
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
18 * 3. Neither the name of the Institute nor the names of its contributors
19 * may be used to endorse or promote products derived from this software
20 * without specific prior written permission.
22 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
23 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
26 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
45 #define E(sym, kret) case sym: ret = kret; ename = #sym; break
47 /* Note: calls krb5_set_error_message() */
48 static krb5_error_code
49 mdb2krb5_code(krb5_context context
, int code
)
51 krb5_error_code ret
= 0;
52 const char *ename
= "UNKNOWN";
53 const char *estr
= mdb_strerror(code
);
56 case MDB_SUCCESS
: return 0;
57 E(MDB_KEYEXIST
, HDB_ERR_EXISTS
);
58 E(MDB_NOTFOUND
, HDB_ERR_NOENTRY
);
59 E(MDB_PAGE_NOTFOUND
, HDB_ERR_UK_SERROR
);
60 E(MDB_CORRUPTED
, HDB_ERR_UK_SERROR
);
61 E(MDB_PANIC
, HDB_ERR_UK_SERROR
);
62 E(MDB_VERSION_MISMATCH
, HDB_ERR_UK_SERROR
);
63 E(MDB_INVALID
, HDB_ERR_UK_SERROR
);
64 E(MDB_MAP_FULL
, HDB_ERR_UK_SERROR
);
65 E(MDB_DBS_FULL
, HDB_ERR_UK_SERROR
);
66 E(MDB_READERS_FULL
, HDB_ERR_UK_SERROR
);
67 E(MDB_TLS_FULL
, HDB_ERR_UK_SERROR
);
68 E(MDB_TXN_FULL
, HDB_ERR_UK_SERROR
);
69 E(MDB_CURSOR_FULL
, HDB_ERR_UK_SERROR
);
70 E(MDB_PAGE_FULL
, HDB_ERR_UK_SERROR
);
71 E(MDB_MAP_RESIZED
, HDB_ERR_UK_SERROR
);
72 E(MDB_INCOMPATIBLE
, HDB_ERR_UK_SERROR
);
73 E(MDB_BAD_RSLOT
, HDB_ERR_UK_SERROR
);
74 E(MDB_BAD_TXN
, HDB_ERR_UK_SERROR
);
75 E(MDB_BAD_VALSIZE
, HDB_ERR_UK_SERROR
);
76 E(MDB_BAD_DBI
, HDB_ERR_UK_SERROR
);
78 if (code
> 0 && code
< 100)
81 ret
= HDB_ERR_UK_SERROR
;
85 krb5_set_error_message(context
, ret
, "MDB error %s (%d): %s",
90 typedef struct mdb_info
{
102 struct keep_it_open
{
109 unsigned int valid
:1;
110 struct keep_it_open
*next
;
112 HEIMDAL_MUTEX keep_them_open_lock
= HEIMDAL_MUTEX_INITIALIZER
;
115 * On Unix LMDB uses fcntl() byte-range locks, and unlike SQLite3 (which also
116 * uses fcntl() byte-range locks) LMDB takes no precautions to avoid early
117 * first-close()s that cause other threads' locks to get dropped. No, LMDB
118 * requires the caller to take such precautions. For us that means opening one
119 * mdb env per-{HDB, mode} (where mode is read-write or read-only), never
120 * closing it, and sharing it with all threads.
122 * Sharing an MDB_env * across multiple threads is documented to be safe, and
123 * internally LMDB uses pread(2), pwrite(2), and mmap(2) for I/O, using
124 * read(2)/write(2) only in the DB copy routines that we don't use.
126 * On WIN32 we don't have to do any of this, however, to avoid ifdef spaghetti,
127 * we share this code on all platforms, even if it isn't strictly needed.
129 * Also, one must call mdb_open() (aka mdb_dbi_open()) only once per call to
130 * mdb_env_open() and per B-tree. We only use one B-tree in each LMDB: the
133 * On success this outputs an `MDB_env *' (the handle for the LMDB) and an
134 * `MDB_dbi' (the handle for the main B-tree in the LMDB).
136 * ALSO, LMDB requires that we re-open the `MDB_env' when the database grows
137 * larger than the mmap size. We handle this by finding in `keep_them_open'
138 * the env we already have, marking it unusable, and the finding some other
139 * better one or opening a new one and adding it to the list.
141 static krb5_error_code
142 my_mdb_env_create_and_open(krb5_context context
,
147 struct keep_it_open
*p
, *n
;
149 unsigned int flags
= MDB_NOSUBDIR
;
156 mi
->oflags
&= O_ACCMODE
;
157 flags
|= (mi
->oflags
== O_RDONLY
) ? MDB_RDONLY
: 0;
162 * Allocate a new object, in case we don't already have one in
163 * `keep_them_open'; if we don't need it, we'll free it. This way we do
164 * some of the work of creating one while not holding a lock.
166 if ((n
= calloc(1, sizeof(*n
))) == NULL
||
167 (n
->path
= strdup(path
)) == NULL
) {
169 return krb5_enomem(context
);
171 n
->oflags
= mi
->oflags
;
173 max_readers
= krb5_config_get_int_default(context
, NULL
, 0, "kdc",
174 "hdb-mdb-maxreaders", NULL
);
175 mapsize
= krb5_config_get_int_default(context
, NULL
, 0, "kdc", "hdb-mdb-mapsize",
177 if (mapsize
> INT_MAX
)
180 memset(&st
, 0, sizeof(st
));
181 if (stat(path
, &st
) == 0 && st
.st_size
> mapsize
* KILO
)
182 mapsize
+= (st
.st_size
+ (st
.st_size
>> 2)) / KILO
;
183 if (mapsize
< 100 * 1024)
184 mapsize
= 100 * 1024; /* 100MB */
185 if (mapsize
< mi
->mapsize
)
186 mapsize
= mi
->mapsize
;
188 mapsize
+= 10 * 1024;
189 if ((code
= mdb_env_create(&n
->env
)) ||
190 (max_readers
&& (code
= mdb_env_set_maxreaders(n
->env
, max_readers
))))
193 /* Look for an existing env */
194 HEIMDAL_MUTEX_lock(&keep_them_open_lock
);
196 for (p
= keep_them_open
; p
; p
= p
->next
) {
197 if (strcmp(p
->path
, path
))
199 if (p
->mapsize
> mapsize
)
200 /* Always increase mapsize */
201 mapsize
= p
->mapsize
+ (p
->mapsize
>> 1);
202 if (!p
->valid
|| p
->oflags
!= mi
->oflags
)
204 /* Found one; output it and get out */
211 /* Did not find one, so open and add this one to the list */
213 /* Open the LMDB itself */
216 krb5_debug(context
, 5, "Opening HDB LMDB %s with mapsize %llu",
217 path
, (unsigned long long)mapsize
* KILO
);
218 code
= mdb_env_set_mapsize(n
->env
, mapsize
* KILO
);
220 code
= mdb_env_open(n
->env
, path
, flags
, mi
->mode
);
222 /* Open a transaction so we can resolve the main B-tree */
223 code
= mdb_txn_begin(n
->env
, NULL
, MDB_RDONLY
, &txn
);
225 /* Resolve the main B-tree */
226 code
= mdb_open(txn
, NULL
, 0, &n
->d
);
230 /* Successfully opened the LMDB; output the two handles */
231 mi
->mapsize
= n
->mapsize
= mapsize
;
235 /* Add this keep_it_open to the front of the list */
236 n
->next
= keep_them_open
;
242 HEIMDAL_MUTEX_unlock(&keep_them_open_lock
);
245 mdb_env_close(n
->env
);
249 (void) mdb_txn_commit(txn
); /* Safe when `txn == NULL' */
250 return mdb2krb5_code(context
, code
);
254 my_mdb_env_close(krb5_context context
,
258 struct keep_it_open
**prev
;
259 struct keep_it_open
*p
, *old
;
260 size_t refs_seen
= 0;
261 size_t slen
= strlen(db_name
);
262 MDB_env
*env
= *envp
;
267 HEIMDAL_MUTEX_lock(&keep_them_open_lock
);
268 for (p
= keep_them_open
; p
; p
= p
->next
) {
270 * We can have multiple open ones and we need to know if this is the
271 * last one, so we can't break out early.
274 refs_seen
+= (--(p
->refs
));
275 else if (strncmp(db_name
, p
->path
, slen
) == 0 &&
276 strcmp(p
->path
+ slen
, ".mdb") == 0)
277 refs_seen
+= p
->refs
;
279 krb5_debug(context
, 6, "Closing HDB LMDB %s / %p; refs %llu", db_name
, env
,
280 (unsigned long long)refs_seen
);
281 prev
= &keep_them_open
;
282 for (p
= keep_them_open
; !refs_seen
&& p
; ) {
283 /* We're the last close */
285 strncmp(db_name
, p
->path
, slen
) ||
286 strcmp(p
->path
+ slen
, ".mdb")) {
288 /* Not us; this keep_it_open stays */
294 /* Close and remove this one */
295 krb5_debug(context
, 6, "Closing HDB LMDB %s (mapsize was %llu)",
296 db_name
, (unsigned long long)p
->mapsize
* KILO
);
298 *prev
= (p
= p
->next
); /* prev stays */
299 mdb_env_close(old
->env
);
303 HEIMDAL_MUTEX_unlock(&keep_them_open_lock
);
307 * This is a wrapper around my_mdb_env_create_and_open(). It may close an
308 * existing MDB_env in mi->e if it's there. If we need to reopen because the
309 * MDB grew too much, then we call this.
311 static krb5_error_code
312 my_reopen_mdb(krb5_context context
, HDB
*db
, int mapfull
)
314 mdb_info
*mi
= (mdb_info
*)db
->hdb_db
;
316 krb5_error_code ret
= 0;
318 /* No-op if we don't have an open one */
319 my_mdb_env_close(context
, db
->hdb_name
, &mi
->e
);
320 if (asprintf(&fn
, "%s.mdb", db
->hdb_name
) == -1)
321 ret
= krb5_enomem(context
);
323 ret
= my_mdb_env_create_and_open(context
, mi
, fn
, mapfull
);
328 static krb5_error_code
329 DB_close(krb5_context context
, HDB
*db
)
331 mdb_info
*mi
= (mdb_info
*)db
->hdb_db
;
333 mdb_cursor_close(mi
->c
);
334 mdb_txn_abort(mi
->t
);
335 my_mdb_env_close(context
, db
->hdb_name
, &mi
->e
);
342 static krb5_error_code
343 DB_destroy(krb5_context context
, HDB
*db
)
347 ret
= hdb_clear_master_key(context
, db
);
348 krb5_config_free_strings(db
->virtual_hostbased_princ_svcs
);
355 static krb5_error_code
356 DB_set_sync(krb5_context context
, HDB
*db
, int on
)
358 mdb_info
*mi
= (mdb_info
*)db
->hdb_db
;
360 mdb_env_set_flags(mi
->e
, MDB_NOSYNC
, !on
);
361 return mdb_env_sync(mi
->e
, 0);
364 static krb5_error_code
365 DB_lock(krb5_context context
, HDB
*db
, int operation
)
371 static krb5_error_code
372 DB_unlock(krb5_context context
, HDB
*db
)
374 if (db
->lock_count
> 1) {
378 heim_assert(db
->lock_count
== 1, "HDB lock/unlock sequence does not match");
384 static krb5_error_code
385 DB_seq(krb5_context context
, HDB
*db
,
386 unsigned flags
, hdb_entry_ex
*entry
, int flag
)
388 mdb_info
*mi
= db
->hdb_db
;
390 krb5_data key_data
, data
;
394 * No need to worry about MDB_MAP_FULL when we're scanning the DB since we
395 * have snapshot semantics, and any DB growth from other transactions
396 * should not affect us.
400 code
= mdb_cursor_get(mi
->c
, &key
, &value
, flag
);
402 return mdb2krb5_code(context
, code
);
404 key_data
.data
= key
.mv_data
;
405 key_data
.length
= key
.mv_size
;
406 data
.data
= value
.mv_data
;
407 data
.length
= value
.mv_size
;
408 memset(entry
, 0, sizeof(*entry
));
409 if (hdb_value2entry(context
, &data
, &entry
->entry
))
410 return DB_seq(context
, db
, flags
, entry
, MDB_NEXT
);
411 if (db
->hdb_master_key_set
&& (flags
& HDB_F_DECRYPT
)) {
412 code
= hdb_unseal_keys (context
, db
, &entry
->entry
);
414 hdb_free_entry (context
, entry
);
416 if (entry
->entry
.principal
== NULL
) {
417 entry
->entry
.principal
= malloc(sizeof(*entry
->entry
.principal
));
418 if (entry
->entry
.principal
== NULL
) {
419 hdb_free_entry (context
, entry
);
420 krb5_set_error_message(context
, ENOMEM
, "malloc: out of memory");
423 hdb_key2principal(context
, &key_data
, entry
->entry
.principal
);
430 static krb5_error_code
431 DB_firstkey(krb5_context context
, HDB
*db
, unsigned flags
, hdb_entry_ex
*entry
)
433 krb5_error_code ret
= 0;
434 mdb_info
*mi
= db
->hdb_db
;
438 /* Always start with a fresh cursor to pick up latest DB state */
442 mdb_txn_abort(mi
->t
);
445 code
= my_reopen_mdb(context
, db
, 1);
447 code
= mdb_txn_begin(mi
->e
, NULL
, MDB_RDONLY
, &mi
->t
);
449 code
= mdb_cursor_open(mi
->t
, mi
->d
, &mi
->c
);
451 ret
= DB_seq(context
, db
, flags
, entry
, MDB_FIRST
);
454 } while (code
== MDB_MAP_FULL
&& --tries
> 0);
457 mdb_txn_abort(mi
->t
);
460 return ret
? ret
: mdb2krb5_code(context
, code
);
464 static krb5_error_code
465 DB_nextkey(krb5_context context
, HDB
*db
, unsigned flags
, hdb_entry_ex
*entry
)
467 return DB_seq(context
, db
, flags
, entry
, MDB_NEXT
);
470 static krb5_error_code
471 DB_rename(krb5_context context
, HDB
*db
, const char *new_name
)
476 if (strncmp(new_name
, "mdb:", sizeof("mdb:") - 1) == 0)
477 new_name
+= sizeof("mdb:") - 1;
478 else if (strncmp(new_name
, "lmdb:", sizeof("lmdb:") - 1) == 0)
479 new_name
+= sizeof("lmdb:") - 1;
480 if (asprintf(&old
, "%s.mdb", db
->hdb_name
) == -1)
482 if (asprintf(&new, "%s.mdb", new_name
) == -1) {
486 ret
= rename(old
, new);
493 db
->hdb_name
= strdup(new_name
);
497 static krb5_error_code
498 DB__get(krb5_context context
, HDB
*db
, krb5_data key
, krb5_data
*reply
)
500 mdb_info
*mi
= (mdb_info
*)db
->hdb_db
;
506 k
.mv_data
= key
.data
;
507 k
.mv_size
= key
.length
;
515 code
= my_reopen_mdb(context
, db
, 1);
517 code
= mdb_txn_begin(mi
->e
, NULL
, MDB_RDONLY
, &txn
);
519 code
= mdb_get(txn
, mi
->d
, &k
, &v
);
521 krb5_data_copy(reply
, v
.mv_data
, v
.mv_size
);
522 } while (code
== MDB_MAP_FULL
&& --tries
> 0);
527 (void) mdb_txn_commit(txn
); /* Empty transaction? -> commit */
528 return mdb2krb5_code(context
, code
);
531 static krb5_error_code
532 DB__put(krb5_context context
, HDB
*db
, int replace
,
533 krb5_data key
, krb5_data value
)
535 mdb_info
*mi
= (mdb_info
*)db
->hdb_db
;
541 k
.mv_data
= key
.data
;
542 k
.mv_size
= key
.length
;
543 v
.mv_data
= value
.data
;
544 v
.mv_size
= value
.length
;
552 code
= my_reopen_mdb(context
, db
, 1);
554 code
= mdb_txn_begin(mi
->e
, NULL
, 0, &txn
);
556 code
= mdb_put(txn
, mi
->d
, &k
, &v
, replace
? 0 : MDB_NOOVERWRITE
);
559 * No need to call mdb_env_sync(); it's done automatically if
560 * MDB_NOSYNC is not set.
562 code
= mdb_txn_commit(txn
);
565 } while (code
== MDB_MAP_FULL
&& --tries
> 0);
568 return mdb2krb5_code(context
, code
);
571 static krb5_error_code
572 DB__del(krb5_context context
, HDB
*db
, krb5_data key
)
574 mdb_info
*mi
= (mdb_info
*)db
->hdb_db
;
580 k
.mv_data
= key
.data
;
581 k
.mv_size
= key
.length
;
589 code
= my_reopen_mdb(context
, db
, 1);
591 code
= mdb_txn_begin(mi
->e
, NULL
, 0, &txn
);
593 code
= mdb_del(txn
, mi
->d
, &k
, NULL
);
596 * No need to call mdb_env_sync(); it's done automatically if
597 * MDB_NOSYNC is not set.
599 code
= mdb_txn_commit(txn
);
602 } while (code
== MDB_MAP_FULL
&& --tries
> 0);
606 return mdb2krb5_code(context
, code
);
609 static krb5_error_code
610 DB_open(krb5_context context
, HDB
*db
, int oflags
, mode_t mode
)
612 mdb_info
*mi
= (mdb_info
*)db
->hdb_db
;
617 mi
->oflags
= oflags
& O_ACCMODE
;
618 ret
= my_reopen_mdb(context
, db
, 0);
620 krb5_prepend_error_message(context
, ret
, "opening %s:", db
->hdb_name
);
624 if ((oflags
& O_ACCMODE
) == O_RDONLY
) {
625 ret
= hdb_check_db_format(context
, db
);
627 * Dubious: if the DB is not initialized, shouldn't we tell the
630 if (ret
== HDB_ERR_NOENTRY
)
633 /* hdb_init_db() calls hdb_check_db_format() */
634 ret
= hdb_init_db(context
, db
);
637 DB_close(context
, db
);
638 krb5_set_error_message(context
, ret
, "hdb_open: failed %s database %s",
639 (oflags
& O_ACCMODE
) == O_RDONLY
?
640 "checking format of" : "initialize",
648 hdb_mdb_create(krb5_context context
, HDB
**db
,
649 const char *filename
)
651 *db
= calloc(1, sizeof(**db
));
653 krb5_set_error_message(context
, ENOMEM
, "malloc: out of memory");
657 (*db
)->hdb_db
= calloc(1, sizeof(mdb_info
));
658 if ((*db
)->hdb_db
== NULL
) {
661 krb5_set_error_message(context
, ENOMEM
, "malloc: out of memory");
664 (*db
)->hdb_name
= strdup(filename
);
665 if ((*db
)->hdb_name
== NULL
) {
669 krb5_set_error_message(context
, ENOMEM
, "malloc: out of memory");
672 (*db
)->hdb_master_key_set
= 0;
673 (*db
)->hdb_openp
= 0;
674 (*db
)->hdb_capability_flags
= HDB_CAP_F_HANDLE_ENTERPRISE_PRINCIPAL
;
675 (*db
)->hdb_open
= DB_open
;
676 (*db
)->hdb_close
= DB_close
;
677 (*db
)->hdb_fetch_kvno
= _hdb_fetch_kvno
;
678 (*db
)->hdb_store
= _hdb_store
;
679 (*db
)->hdb_remove
= _hdb_remove
;
680 (*db
)->hdb_firstkey
= DB_firstkey
;
681 (*db
)->hdb_nextkey
= DB_nextkey
;
682 (*db
)->hdb_lock
= DB_lock
;
683 (*db
)->hdb_unlock
= DB_unlock
;
684 (*db
)->hdb_rename
= DB_rename
;
685 (*db
)->hdb__get
= DB__get
;
686 (*db
)->hdb__put
= DB__put
;
687 (*db
)->hdb__del
= DB__del
;
688 (*db
)->hdb_destroy
= DB_destroy
;
689 (*db
)->hdb_set_sync
= DB_set_sync
;
692 #endif /* HAVE_LMDB */