summaryrefslogtreecommitdiffstats
path: root/src/contrib
diff options
context:
space:
mode:
Diffstat (limited to 'src/contrib')
-rw-r--r--src/contrib/Makefile.inc103
-rw-r--r--src/contrib/asan.h37
-rw-r--r--src/contrib/base32hex.c353
-rw-r--r--src/contrib/base32hex.h103
-rw-r--r--src/contrib/base64.c272
-rw-r--r--src/contrib/base64.h102
-rw-r--r--src/contrib/ctype.h192
-rw-r--r--src/contrib/dnstap/convert.c142
-rw-r--r--src/contrib/dnstap/convert.h59
-rw-r--r--src/contrib/dnstap/dnstap.c40
-rw-r--r--src/contrib/dnstap/dnstap.h46
-rw-r--r--src/contrib/dnstap/dnstap.proto270
-rw-r--r--src/contrib/dnstap/message.c130
-rw-r--r--src/contrib/dnstap/message.h62
-rw-r--r--src/contrib/dnstap/reader.c105
-rw-r--r--src/contrib/dnstap/reader.h72
-rw-r--r--src/contrib/dnstap/writer.c120
-rw-r--r--src/contrib/dnstap/writer.h70
-rw-r--r--src/contrib/dynarray.h123
-rw-r--r--src/contrib/files.c134
-rw-r--r--src/contrib/files.h43
-rw-r--r--src/contrib/getline.c60
-rw-r--r--src/contrib/getline.h38
-rw-r--r--src/contrib/licenses/0BSD12
-rw-r--r--src/contrib/licenses/BSD-3-Clause21
-rw-r--r--src/contrib/licenses/LGPL-2.0339
-rw-r--r--src/contrib/licenses/OLDAP-2.847
-rw-r--r--src/contrib/lmdb/LICENSE1
-rw-r--r--src/contrib/lmdb/lmdb.h1604
-rw-r--r--src/contrib/lmdb/mdb.c10266
-rw-r--r--src/contrib/lmdb/midl.c359
-rw-r--r--src/contrib/lmdb/midl.h186
-rw-r--r--src/contrib/macros.h43
-rw-r--r--src/contrib/mempattern.c122
-rw-r--r--src/contrib/mempattern.h46
-rw-r--r--src/contrib/net.c598
-rw-r--r--src/contrib/net.h185
-rw-r--r--src/contrib/openbsd/LICENSE2
-rw-r--r--src/contrib/openbsd/siphash.c176
-rw-r--r--src/contrib/openbsd/siphash.h83
-rw-r--r--src/contrib/openbsd/strlcat.c48
-rw-r--r--src/contrib/openbsd/strlcat.h31
-rw-r--r--src/contrib/openbsd/strlcpy.c46
-rw-r--r--src/contrib/openbsd/strlcpy.h29
-rw-r--r--src/contrib/qp-trie/trie.c835
-rw-r--r--src/contrib/qp-trie/trie.h112
-rw-r--r--src/contrib/sockaddr.c351
-rw-r--r--src/contrib/sockaddr.h160
-rw-r--r--src/contrib/string.c112
-rw-r--r--src/contrib/string.h82
-rw-r--r--src/contrib/strtonum.h120
-rw-r--r--src/contrib/time.c405
-rw-r--r--src/contrib/time.h171
-rw-r--r--src/contrib/tolower.h51
-rw-r--r--src/contrib/trim.h35
-rw-r--r--src/contrib/ucw/LICENSE1
-rw-r--r--src/contrib/ucw/array-sort.h195
-rw-r--r--src/contrib/ucw/binsearch.h50
-rw-r--r--src/contrib/ucw/heap.c166
-rw-r--r--src/contrib/ucw/heap.h46
-rw-r--r--src/contrib/ucw/lists.c235
-rw-r--r--src/contrib/ucw/lists.h84
-rw-r--r--src/contrib/ucw/mempool.c322
-rw-r--r--src/contrib/ucw/mempool.h124
-rw-r--r--src/contrib/wire_ctx.h361
65 files changed, 20938 insertions, 0 deletions
diff --git a/src/contrib/Makefile.inc b/src/contrib/Makefile.inc
new file mode 100644
index 0000000..a6b6576
--- /dev/null
+++ b/src/contrib/Makefile.inc
@@ -0,0 +1,103 @@
+noinst_LTLIBRARIES += libcontrib.la
+
+libcontrib_la_CPPFLAGS = $(AM_CPPFLAGS) $(CFLAG_VISIBILITY)
+libcontrib_la_LDFLAGS = $(AM_LDFLAGS) $(LDFLAG_EXCLUDE_LIBS)
+if !HAVE_LMDB
+libcontrib_la_LDFLAGS += $(pthread_LIBS)
+endif !HAVE_LMDB
+
+EXTRA_DIST += \
+ contrib/licenses/0BSD \
+ contrib/licenses/BSD-3-Clause \
+ contrib/licenses/LGPL-2.0 \
+ contrib/licenses/OLDAP-2.8 \
+ contrib/lmdb/LICENSE \
+ contrib/openbsd/LICENSE \
+ contrib/ucw/LICENSE \
+ contrib/dnstap/dnstap.proto
+
+libcontrib_la_SOURCES = \
+ contrib/asan.h \
+ contrib/base32hex.c \
+ contrib/base32hex.h \
+ contrib/base64.c \
+ contrib/base64.h \
+ contrib/ctype.h \
+ contrib/dynarray.h \
+ contrib/files.c \
+ contrib/files.h \
+ contrib/getline.c \
+ contrib/getline.h \
+ contrib/macros.h \
+ contrib/mempattern.c \
+ contrib/mempattern.h \
+ contrib/net.c \
+ contrib/net.h \
+ contrib/qp-trie/trie.c \
+ contrib/qp-trie/trie.h \
+ contrib/sockaddr.c \
+ contrib/sockaddr.h \
+ contrib/string.c \
+ contrib/string.h \
+ contrib/strtonum.h \
+ contrib/time.c \
+ contrib/time.h \
+ contrib/tolower.h \
+ contrib/trim.h \
+ contrib/wire_ctx.h \
+ contrib/openbsd/siphash.c \
+ contrib/openbsd/siphash.h \
+ contrib/openbsd/strlcat.c \
+ contrib/openbsd/strlcat.h \
+ contrib/openbsd/strlcpy.c \
+ contrib/openbsd/strlcpy.h \
+ contrib/ucw/array-sort.h \
+ contrib/ucw/binsearch.h \
+ contrib/ucw/heap.c \
+ contrib/ucw/heap.h \
+ contrib/ucw/lists.c \
+ contrib/ucw/lists.h \
+ contrib/ucw/mempool.c \
+ contrib/ucw/mempool.h
+
+if !HAVE_LMDB
+libcontrib_la_SOURCES += \
+ contrib/lmdb/lmdb.h \
+ contrib/lmdb/mdb.c \
+ contrib/lmdb/midl.c \
+ contrib/lmdb/midl.h
+endif !HAVE_LMDB
+
+if HAVE_LIBDNSTAP
+noinst_LTLIBRARIES += libdnstap.la
+
+libdnstap_la_CPPFLAGS = $(AM_CPPFLAGS) $(DNSTAP_CFLAGS)
+libdnstap_la_LDFLAGS = $(AM_LDFLAGS) $(DNSTAP_LIBS)
+
+SUFFIXES = .proto .pb-c.c .pb-c.h
+
+.proto.pb-c.c:
+ $(AM_V_GEN)@PROTOC_C@ --c_out=. -I$(srcdir) $<
+
+.proto.pb-c.h:
+ $(AM_V_GEN)@PROTOC_C@ --c_out=. -I$(srcdir) $<
+
+libdnstap_la_SOURCES = \
+ contrib/dnstap/convert.c \
+ contrib/dnstap/convert.h \
+ contrib/dnstap/dnstap.c \
+ contrib/dnstap/dnstap.h \
+ contrib/dnstap/message.c \
+ contrib/dnstap/message.h \
+ contrib/dnstap/reader.c \
+ contrib/dnstap/reader.h \
+ contrib/dnstap/writer.c \
+ contrib/dnstap/writer.h
+
+nodist_libdnstap_la_SOURCES = \
+ contrib/dnstap/dnstap.pb-c.c \
+ contrib/dnstap/dnstap.pb-c.h
+
+BUILT_SOURCES += $(nodist_libdnstap_la_SOURCES)
+CLEANFILES += $(nodist_libdnstap_la_SOURCES)
+endif HAVE_LIBDNSTAP
diff --git a/src/contrib/asan.h b/src/contrib/asan.h
new file mode 100644
index 0000000..112f81e
--- /dev/null
+++ b/src/contrib/asan.h
@@ -0,0 +1,37 @@
+/* Copyright (C) 2015 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#pragma once
+
+/*
+ * see sanitizer/asan_interface.h in compiler-rt (LLVM)
+ */
+#ifndef __has_feature
+ #define __has_feature(feature) 0
+#endif
+#if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__)
+ void __asan_poison_memory_region(void const volatile *addr, size_t size);
+ void __asan_unpoison_memory_region(void const volatile *addr, size_t size);
+ #define ASAN_POISON_MEMORY_REGION(addr, size) \
+ __asan_poison_memory_region((addr), (size))
+ #define ASAN_UNPOISON_MEMORY_REGION(addr, size) \
+ __asan_unpoison_memory_region((addr), (size))
+#else
+ #define ASAN_POISON_MEMORY_REGION(addr, size) \
+ ((void)(addr), (void)(size))
+ #define ASAN_UNPOISON_MEMORY_REGION(addr, size) \
+ ((void)(addr), (void)(size))
+#endif
diff --git a/src/contrib/base32hex.c b/src/contrib/base32hex.c
new file mode 100644
index 0000000..56fb427
--- /dev/null
+++ b/src/contrib/base32hex.c
@@ -0,0 +1,353 @@
+/* Copyright (C) 2017 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "contrib/base32hex.h"
+#include "libknot/errcode.h"
+
+#include <stdlib.h>
+#include <stdint.h>
+
+/*! \brief Maximal length of binary input to Base32hex encoding. */
+#define MAX_BIN_DATA_LEN ((INT32_MAX / 8) * 5)
+
+/*! \brief Base32hex padding character. */
+static const uint8_t base32hex_pad = '=';
+/*! \brief Base32hex alphabet. */
+static const uint8_t base32hex_enc[] = "0123456789abcdefghijklmnopqrstuv";
+
+/*! \brief Indicates bad Base32hex character. */
+#define KO 255
+/*! \brief Indicates Base32hex padding character. */
+#define PD 32
+
+/*! \brief Transformation and validation table for decoding Base32hex. */
+static const uint8_t base32hex_dec[256] = {
+ [ 0] = KO, [ 43] = KO, ['V'] = 31, [129] = KO, [172] = KO, [215] = KO,
+ [ 1] = KO, [ 44] = KO, ['W'] = KO, [130] = KO, [173] = KO, [216] = KO,
+ [ 2] = KO, [ 45] = KO, ['X'] = KO, [131] = KO, [174] = KO, [217] = KO,
+ [ 3] = KO, [ 46] = KO, ['Y'] = KO, [132] = KO, [175] = KO, [218] = KO,
+ [ 4] = KO, [ 47] = KO, ['Z'] = KO, [133] = KO, [176] = KO, [219] = KO,
+ [ 5] = KO, ['0'] = 0, [ 91] = KO, [134] = KO, [177] = KO, [220] = KO,
+ [ 6] = KO, ['1'] = 1, [ 92] = KO, [135] = KO, [178] = KO, [221] = KO,
+ [ 7] = KO, ['2'] = 2, [ 93] = KO, [136] = KO, [179] = KO, [222] = KO,
+ [ 8] = KO, ['3'] = 3, [ 94] = KO, [137] = KO, [180] = KO, [223] = KO,
+ [ 9] = KO, ['4'] = 4, [ 95] = KO, [138] = KO, [181] = KO, [224] = KO,
+ [ 10] = KO, ['5'] = 5, [ 96] = KO, [139] = KO, [182] = KO, [225] = KO,
+ [ 11] = KO, ['6'] = 6, ['a'] = 10, [140] = KO, [183] = KO, [226] = KO,
+ [ 12] = KO, ['7'] = 7, ['b'] = 11, [141] = KO, [184] = KO, [227] = KO,
+ [ 13] = KO, ['8'] = 8, ['c'] = 12, [142] = KO, [185] = KO, [228] = KO,
+ [ 14] = KO, ['9'] = 9, ['d'] = 13, [143] = KO, [186] = KO, [229] = KO,
+ [ 15] = KO, [ 58] = KO, ['e'] = 14, [144] = KO, [187] = KO, [230] = KO,
+ [ 16] = KO, [ 59] = KO, ['f'] = 15, [145] = KO, [188] = KO, [231] = KO,
+ [ 17] = KO, [ 60] = KO, ['g'] = 16, [146] = KO, [189] = KO, [232] = KO,
+ [ 18] = KO, ['='] = PD, ['h'] = 17, [147] = KO, [190] = KO, [233] = KO,
+ [ 19] = KO, [ 62] = KO, ['i'] = 18, [148] = KO, [191] = KO, [234] = KO,
+ [ 20] = KO, [ 63] = KO, ['j'] = 19, [149] = KO, [192] = KO, [235] = KO,
+ [ 21] = KO, [ 64] = KO, ['k'] = 20, [150] = KO, [193] = KO, [236] = KO,
+ [ 22] = KO, ['A'] = 10, ['l'] = 21, [151] = KO, [194] = KO, [237] = KO,
+ [ 23] = KO, ['B'] = 11, ['m'] = 22, [152] = KO, [195] = KO, [238] = KO,
+ [ 24] = KO, ['C'] = 12, ['n'] = 23, [153] = KO, [196] = KO, [239] = KO,
+ [ 25] = KO, ['D'] = 13, ['o'] = 24, [154] = KO, [197] = KO, [240] = KO,
+ [ 26] = KO, ['E'] = 14, ['p'] = 25, [155] = KO, [198] = KO, [241] = KO,
+ [ 27] = KO, ['F'] = 15, ['q'] = 26, [156] = KO, [199] = KO, [242] = KO,
+ [ 28] = KO, ['G'] = 16, ['r'] = 27, [157] = KO, [200] = KO, [243] = KO,
+ [ 29] = KO, ['H'] = 17, ['s'] = 28, [158] = KO, [201] = KO, [244] = KO,
+ [ 30] = KO, ['I'] = 18, ['t'] = 29, [159] = KO, [202] = KO, [245] = KO,
+ [ 31] = KO, ['J'] = 19, ['u'] = 30, [160] = KO, [203] = KO, [246] = KO,
+ [ 32] = KO, ['K'] = 20, ['v'] = 31, [161] = KO, [204] = KO, [247] = KO,
+ [ 33] = KO, ['L'] = 21, ['w'] = KO, [162] = KO, [205] = KO, [248] = KO,
+ [ 34] = KO, ['M'] = 22, ['x'] = KO, [163] = KO, [206] = KO, [249] = KO,
+ [ 35] = KO, ['N'] = 23, ['y'] = KO, [164] = KO, [207] = KO, [250] = KO,
+ [ 36] = KO, ['O'] = 24, ['z'] = KO, [165] = KO, [208] = KO, [251] = KO,
+ [ 37] = KO, ['P'] = 25, [123] = KO, [166] = KO, [209] = KO, [252] = KO,
+ [ 38] = KO, ['Q'] = 26, [124] = KO, [167] = KO, [210] = KO, [253] = KO,
+ [ 39] = KO, ['R'] = 27, [125] = KO, [168] = KO, [211] = KO, [254] = KO,
+ [ 40] = KO, ['S'] = 28, [126] = KO, [169] = KO, [212] = KO, [255] = KO,
+ [ 41] = KO, ['T'] = 29, [127] = KO, [170] = KO, [213] = KO,
+ [ 42] = KO, ['U'] = 30, [128] = KO, [171] = KO, [214] = KO,
+};
+
+int32_t base32hex_encode(const uint8_t *in,
+ const uint32_t in_len,
+ uint8_t *out,
+ const uint32_t out_len)
+{
+ // Checking inputs.
+ if (in == NULL || out == NULL) {
+ return KNOT_EINVAL;
+ }
+ if (in_len > MAX_BIN_DATA_LEN || out_len < ((in_len + 4) / 5) * 8) {
+ return KNOT_ERANGE;
+ }
+
+ uint8_t rest_len = in_len % 5;
+ const uint8_t *stop = in + in_len - rest_len;
+ uint8_t *text = out;
+
+ // Encoding loop takes 5 bytes and creates 8 characters.
+ while (in < stop) {
+ text[0] = base32hex_enc[in[0] >> 3];
+ text[1] = base32hex_enc[(in[0] & 0x07) << 2 | in[1] >> 6];
+ text[2] = base32hex_enc[(in[1] & 0x3E) >> 1];
+ text[3] = base32hex_enc[(in[1] & 0x01) << 4 | in[2] >> 4];
+ text[4] = base32hex_enc[(in[2] & 0x0F) << 1 | in[3] >> 7];
+ text[5] = base32hex_enc[(in[3] & 0x7C) >> 2];
+ text[6] = base32hex_enc[(in[3] & 0x03) << 3 | in[4] >> 5];
+ text[7] = base32hex_enc[in[4] & 0x1F];
+ text += 8;
+ in += 5;
+ }
+
+ // Processing of padding, if any.
+ switch (rest_len) {
+ case 4:
+ text[0] = base32hex_enc[in[0] >> 3];
+ text[1] = base32hex_enc[(in[0] & 0x07) << 2 | in[1] >> 6];
+ text[2] = base32hex_enc[(in[1] & 0x3E) >> 1];
+ text[3] = base32hex_enc[(in[1] & 0x01) << 4 | in[2] >> 4];
+ text[4] = base32hex_enc[(in[2] & 0x0F) << 1 | in[3] >> 7];
+ text[5] = base32hex_enc[(in[3] & 0x7C) >> 2];
+ text[6] = base32hex_enc[(in[3] & 0x03) << 3];
+ text[7] = base32hex_pad;
+ text += 8;
+ break;
+ case 3:
+ text[0] = base32hex_enc[in[0] >> 3];
+ text[1] = base32hex_enc[(in[0] & 0x07) << 2 | in[1] >> 6];
+ text[2] = base32hex_enc[(in[1] & 0x3E) >> 1];
+ text[3] = base32hex_enc[(in[1] & 0x01) << 4 | in[2] >> 4];
+ text[4] = base32hex_enc[(in[2] & 0x0F) << 1];
+ text[5] = base32hex_pad;
+ text[6] = base32hex_pad;
+ text[7] = base32hex_pad;
+ text += 8;
+ break;
+ case 2:
+ text[0] = base32hex_enc[in[0] >> 3];
+ text[1] = base32hex_enc[(in[0] & 0x07) << 2 | in[1] >> 6];
+ text[2] = base32hex_enc[(in[1] & 0x3E) >> 1];
+ text[3] = base32hex_enc[(in[1] & 0x01) << 4];
+ text[4] = base32hex_pad;
+ text[5] = base32hex_pad;
+ text[6] = base32hex_pad;
+ text[7] = base32hex_pad;
+ text += 8;
+ break;
+ case 1:
+ text[0] = base32hex_enc[in[0] >> 3];
+ text[1] = base32hex_enc[(in[0] & 0x07) << 2];
+ text[2] = base32hex_pad;
+ text[3] = base32hex_pad;
+ text[4] = base32hex_pad;
+ text[5] = base32hex_pad;
+ text[6] = base32hex_pad;
+ text[7] = base32hex_pad;
+ text += 8;
+ break;
+ }
+
+ return (text - out);
+}
+
+int32_t base32hex_encode_alloc(const uint8_t *in,
+ const uint32_t in_len,
+ uint8_t **out)
+{
+ // Checking inputs.
+ if (out == NULL) {
+ return KNOT_EINVAL;
+ }
+ if (in_len > MAX_BIN_DATA_LEN) {
+ return KNOT_ERANGE;
+ }
+
+ // Compute output buffer length.
+ uint32_t out_len = ((in_len + 4) / 5) * 8;
+
+ // Allocate output buffer.
+ *out = malloc(out_len);
+ if (*out == NULL) {
+ return KNOT_ENOMEM;
+ }
+
+ // Encode data.
+ int32_t ret = base32hex_encode(in, in_len, *out, out_len);
+ if (ret < 0) {
+ free(*out);
+ *out = NULL;
+ }
+
+ return ret;
+}
+
+int32_t base32hex_decode(const uint8_t *in,
+ const uint32_t in_len,
+ uint8_t *out,
+ const uint32_t out_len)
+{
+ // Checking inputs.
+ if (in == NULL || out == NULL) {
+ return KNOT_EINVAL;
+ }
+ if (in_len > INT32_MAX || out_len < ((in_len + 7) / 8) * 5) {
+ return KNOT_ERANGE;
+ }
+ if ((in_len % 8) != 0) {
+ return KNOT_BASE32HEX_ESIZE;
+ }
+
+ const uint8_t *stop = in + in_len;
+ uint8_t *bin = out;
+ uint8_t pad_len = 0;
+ uint8_t c1, c2, c3, c4, c5, c6, c7, c8;
+
+ // Decoding loop takes 8 characters and creates 5 bytes.
+ while (in < stop) {
+ // Filling and transforming 8 Base32hex chars.
+ c1 = base32hex_dec[in[0]];
+ c2 = base32hex_dec[in[1]];
+ c3 = base32hex_dec[in[2]];
+ c4 = base32hex_dec[in[3]];
+ c5 = base32hex_dec[in[4]];
+ c6 = base32hex_dec[in[5]];
+ c7 = base32hex_dec[in[6]];
+ c8 = base32hex_dec[in[7]];
+
+ // Check 8. char if is bad or padding.
+ if (c8 >= PD) {
+ if (c8 == PD && pad_len == 0) {
+ pad_len = 1;
+ } else {
+ return KNOT_BASE32HEX_ECHAR;
+ }
+ }
+
+ // Check 7. char if is bad or padding (if so, 6. must be too).
+ if (c7 >= PD) {
+ if (c7 == PD && c6 == PD && pad_len == 1) {
+ pad_len = 3;
+ } else {
+ return KNOT_BASE32HEX_ECHAR;
+ }
+ }
+
+ // Check 6. char if is bad or padding.
+ if (c6 >= PD) {
+ if (!(c6 == PD && pad_len == 3)) {
+ return KNOT_BASE32HEX_ECHAR;
+ }
+ }
+
+ // Check 5. char if is bad or padding.
+ if (c5 >= PD) {
+ if (c5 == PD && pad_len == 3) {
+ pad_len = 4;
+ } else {
+ return KNOT_BASE32HEX_ECHAR;
+ }
+ }
+
+ // Check 4. char if is bad or padding (if so, 3. must be too).
+ if (c4 >= PD) {
+ if (c4 == PD && c3 == PD && pad_len == 4) {
+ pad_len = 6;
+ } else {
+ return KNOT_BASE32HEX_ECHAR;
+ }
+ }
+
+ // Check 3. char if is bad or padding.
+ if (c3 >= PD) {
+ if (!(c3 == PD && pad_len == 6)) {
+ return KNOT_BASE32HEX_ECHAR;
+ }
+ }
+
+ // 1. and 2. chars must not be padding.
+ if (c2 >= PD || c1 >= PD) {
+ return KNOT_BASE32HEX_ECHAR;
+ }
+
+ // Computing of output data based on padding length.
+ switch (pad_len) {
+ case 0:
+ bin[4] = (c7 << 5) + c8;
+ // FALLTHROUGH
+ case 1:
+ bin[3] = (c5 << 7) + (c6 << 2) + (c7 >> 3);
+ // FALLTHROUGH
+ case 3:
+ bin[2] = (c4 << 4) + (c5 >> 1);
+ // FALLTHROUGH
+ case 4:
+ bin[1] = (c2 << 6) + (c3 << 1) + (c4 >> 4);
+ // FALLTHROUGH
+ case 6:
+ bin[0] = (c1 << 3) + (c2 >> 2);
+ }
+
+ // Update output end.
+ switch (pad_len) {
+ case 0:
+ bin += 5;
+ break;
+ case 1:
+ bin += 4;
+ break;
+ case 3:
+ bin += 3;
+ break;
+ case 4:
+ bin += 2;
+ break;
+ case 6:
+ bin += 1;
+ break;
+ }
+
+ in += 8;
+ }
+
+ return (bin - out);
+}
+
+int32_t base32hex_decode_alloc(const uint8_t *in,
+ const uint32_t in_len,
+ uint8_t **out)
+{
+ // Checking inputs.
+ if (out == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ // Compute output buffer length.
+ uint32_t out_len = ((in_len + 7) / 8) * 5;
+
+ // Allocate output buffer.
+ *out = malloc(out_len);
+ if (*out == NULL) {
+ return KNOT_ENOMEM;
+ }
+
+ // Decode data.
+ int32_t ret = base32hex_decode(in, in_len, *out, out_len);
+ if (ret < 0) {
+ free(*out);
+ *out = NULL;
+ }
+
+ return ret;
+}
diff --git a/src/contrib/base32hex.h b/src/contrib/base32hex.h
new file mode 100644
index 0000000..027acd8
--- /dev/null
+++ b/src/contrib/base32hex.h
@@ -0,0 +1,103 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*!
+ * \brief Base32hex implementation (RFC 4648).
+ *
+ * \note Input Base32hex string can contain a-v characters. These characters
+ * are considered as A-V equivalent. Lower-case variant is used for encoding!
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+/*!
+ * \brief Encodes binary data using Base32hex.
+ *
+ * \note Output data buffer contains Base32hex text string which isn't
+ * terminated with '\0'!
+ *
+ * \param in Input binary data.
+ * \param in_len Length of input data.
+ * \param out Output data buffer.
+ * \param out_len Size of output buffer.
+ *
+ * \retval >=0 length of output string.
+ * \retval KNOT_E* if error.
+ */
+int32_t base32hex_encode(const uint8_t *in,
+ const uint32_t in_len,
+ uint8_t *out,
+ const uint32_t out_len);
+
+/*!
+ * \brief Encodes binary data using Base32hex and output stores to own buffer.
+ *
+ * \note Output data buffer contains Base32hex text string which isn't
+ * terminated with '\0'!
+ *
+ * \note Output buffer should be deallocated after use.
+ *
+ * \param in Input binary data.
+ * \param in_len Length of input data.
+ * \param out Output data buffer.
+ *
+ * \retval >=0 length of output string.
+ * \retval KNOT_E* if error.
+ */
+int32_t base32hex_encode_alloc(const uint8_t *in,
+ const uint32_t in_len,
+ uint8_t **out);
+
+/*!
+ * \brief Decodes text data using Base32hex.
+ *
+ * \note Input data needn't be terminated with '\0'.
+ *
+ * \note Input data must be continuous Base32hex string!
+ *
+ * \param in Input text data.
+ * \param in_len Length of input string.
+ * \param out Output data buffer.
+ * \param out_len Size of output buffer.
+ *
+ * \retval >=0 length of output data.
+ * \retval KNOT_E* if error.
+ */
+int32_t base32hex_decode(const uint8_t *in,
+ const uint32_t in_len,
+ uint8_t *out,
+ const uint32_t out_len);
+
+/*!
+ * \brief Decodes text data using Base32hex and output stores to own buffer.
+ *
+ * \note Input data needn't be terminated with '\0'.
+ *
+ * \note Input data must be continuous Base32hex string!
+ *
+ * \note Output buffer should be deallocated after use.
+ *
+ * \param in Input text data.
+ * \param in_len Length of input string.
+ * \param out Output data buffer.
+ *
+ * \retval >=0 length of output data.
+ * \retval KNOT_E* if error.
+ */
+int32_t base32hex_decode_alloc(const uint8_t *in,
+ const uint32_t in_len,
+ uint8_t **out);
diff --git a/src/contrib/base64.c b/src/contrib/base64.c
new file mode 100644
index 0000000..a0d83a4
--- /dev/null
+++ b/src/contrib/base64.c
@@ -0,0 +1,272 @@
+/* Copyright (C) 2017 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "contrib/base64.h"
+#include "libknot/errcode.h"
+
+#include <stdlib.h>
+#include <stdint.h>
+
+/*! \brief Maximal length of binary input to Base64 encoding. */
+#define MAX_BIN_DATA_LEN ((INT32_MAX / 4) * 3)
+
+/*! \brief Base64 padding character. */
+static const uint8_t base64_pad = '=';
+/*! \brief Base64 alphabet. */
+static const uint8_t base64_enc[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+/*! \brief Indicates bad Base64 character. */
+#define KO 255
+/*! \brief Indicates Base64 padding character. */
+#define PD 64
+
+/*! \brief Transformation and validation table for decoding Base64. */
+static const uint8_t base64_dec[256] = {
+ [ 0] = KO, ['+'] = 62, ['V'] = 21, [129] = KO, [172] = KO, [215] = KO,
+ [ 1] = KO, [ 44] = KO, ['W'] = 22, [130] = KO, [173] = KO, [216] = KO,
+ [ 2] = KO, [ 45] = KO, ['X'] = 23, [131] = KO, [174] = KO, [217] = KO,
+ [ 3] = KO, [ 46] = KO, ['Y'] = 24, [132] = KO, [175] = KO, [218] = KO,
+ [ 4] = KO, ['/'] = 63, ['Z'] = 25, [133] = KO, [176] = KO, [219] = KO,
+ [ 5] = KO, ['0'] = 52, [ 91] = KO, [134] = KO, [177] = KO, [220] = KO,
+ [ 6] = KO, ['1'] = 53, [ 92] = KO, [135] = KO, [178] = KO, [221] = KO,
+ [ 7] = KO, ['2'] = 54, [ 93] = KO, [136] = KO, [179] = KO, [222] = KO,
+ [ 8] = KO, ['3'] = 55, [ 94] = KO, [137] = KO, [180] = KO, [223] = KO,
+ [ 9] = KO, ['4'] = 56, [ 95] = KO, [138] = KO, [181] = KO, [224] = KO,
+ [ 10] = KO, ['5'] = 57, [ 96] = KO, [139] = KO, [182] = KO, [225] = KO,
+ [ 11] = KO, ['6'] = 58, ['a'] = 26, [140] = KO, [183] = KO, [226] = KO,
+ [ 12] = KO, ['7'] = 59, ['b'] = 27, [141] = KO, [184] = KO, [227] = KO,
+ [ 13] = KO, ['8'] = 60, ['c'] = 28, [142] = KO, [185] = KO, [228] = KO,
+ [ 14] = KO, ['9'] = 61, ['d'] = 29, [143] = KO, [186] = KO, [229] = KO,
+ [ 15] = KO, [ 58] = KO, ['e'] = 30, [144] = KO, [187] = KO, [230] = KO,
+ [ 16] = KO, [ 59] = KO, ['f'] = 31, [145] = KO, [188] = KO, [231] = KO,
+ [ 17] = KO, [ 60] = KO, ['g'] = 32, [146] = KO, [189] = KO, [232] = KO,
+ [ 18] = KO, ['='] = PD, ['h'] = 33, [147] = KO, [190] = KO, [233] = KO,
+ [ 19] = KO, [ 62] = KO, ['i'] = 34, [148] = KO, [191] = KO, [234] = KO,
+ [ 20] = KO, [ 63] = KO, ['j'] = 35, [149] = KO, [192] = KO, [235] = KO,
+ [ 21] = KO, [ 64] = KO, ['k'] = 36, [150] = KO, [193] = KO, [236] = KO,
+ [ 22] = KO, ['A'] = 0, ['l'] = 37, [151] = KO, [194] = KO, [237] = KO,
+ [ 23] = KO, ['B'] = 1, ['m'] = 38, [152] = KO, [195] = KO, [238] = KO,
+ [ 24] = KO, ['C'] = 2, ['n'] = 39, [153] = KO, [196] = KO, [239] = KO,
+ [ 25] = KO, ['D'] = 3, ['o'] = 40, [154] = KO, [197] = KO, [240] = KO,
+ [ 26] = KO, ['E'] = 4, ['p'] = 41, [155] = KO, [198] = KO, [241] = KO,
+ [ 27] = KO, ['F'] = 5, ['q'] = 42, [156] = KO, [199] = KO, [242] = KO,
+ [ 28] = KO, ['G'] = 6, ['r'] = 43, [157] = KO, [200] = KO, [243] = KO,
+ [ 29] = KO, ['H'] = 7, ['s'] = 44, [158] = KO, [201] = KO, [244] = KO,
+ [ 30] = KO, ['I'] = 8, ['t'] = 45, [159] = KO, [202] = KO, [245] = KO,
+ [ 31] = KO, ['J'] = 9, ['u'] = 46, [160] = KO, [203] = KO, [246] = KO,
+ [ 32] = KO, ['K'] = 10, ['v'] = 47, [161] = KO, [204] = KO, [247] = KO,
+ [ 33] = KO, ['L'] = 11, ['w'] = 48, [162] = KO, [205] = KO, [248] = KO,
+ [ 34] = KO, ['M'] = 12, ['x'] = 49, [163] = KO, [206] = KO, [249] = KO,
+ [ 35] = KO, ['N'] = 13, ['y'] = 50, [164] = KO, [207] = KO, [250] = KO,
+ [ 36] = KO, ['O'] = 14, ['z'] = 51, [165] = KO, [208] = KO, [251] = KO,
+ [ 37] = KO, ['P'] = 15, [123] = KO, [166] = KO, [209] = KO, [252] = KO,
+ [ 38] = KO, ['Q'] = 16, [124] = KO, [167] = KO, [210] = KO, [253] = KO,
+ [ 39] = KO, ['R'] = 17, [125] = KO, [168] = KO, [211] = KO, [254] = KO,
+ [ 40] = KO, ['S'] = 18, [126] = KO, [169] = KO, [212] = KO, [255] = KO,
+ [ 41] = KO, ['T'] = 19, [127] = KO, [170] = KO, [213] = KO,
+ [ 42] = KO, ['U'] = 20, [128] = KO, [171] = KO, [214] = KO,
+};
+
+int32_t base64_encode(const uint8_t *in,
+ const uint32_t in_len,
+ uint8_t *out,
+ const uint32_t out_len)
+{
+ // Checking inputs.
+ if (in == NULL || out == NULL) {
+ return KNOT_EINVAL;
+ }
+ if (in_len > MAX_BIN_DATA_LEN || out_len < ((in_len + 2) / 3) * 4) {
+ return KNOT_ERANGE;
+ }
+
+ uint8_t rest_len = in_len % 3;
+ const uint8_t *stop = in + in_len - rest_len;
+ uint8_t *text = out;
+
+ // Encoding loop takes 3 bytes and creates 4 characters.
+ while (in < stop) {
+ text[0] = base64_enc[in[0] >> 2];
+ text[1] = base64_enc[(in[0] & 0x03) << 4 | in[1] >> 4];
+ text[2] = base64_enc[(in[1] & 0x0F) << 2 | in[2] >> 6];
+ text[3] = base64_enc[in[2] & 0x3F];
+ text += 4;
+ in += 3;
+ }
+
+ // Processing of padding, if any.
+ switch (rest_len) {
+ case 2:
+ text[0] = base64_enc[in[0] >> 2];
+ text[1] = base64_enc[(in[0] & 0x03) << 4 | in[1] >> 4];
+ text[2] = base64_enc[(in[1] & 0x0F) << 2];
+ text[3] = base64_pad;
+ text += 4;
+ break;
+ case 1:
+ text[0] = base64_enc[in[0] >> 2];
+ text[1] = base64_enc[(in[0] & 0x03) << 4];
+ text[2] = base64_pad;
+ text[3] = base64_pad;
+ text += 4;
+ break;
+ }
+
+ return (text - out);
+}
+
+int32_t base64_encode_alloc(const uint8_t *in,
+ const uint32_t in_len,
+ uint8_t **out)
+{
+ // Checking inputs.
+ if (out == NULL) {
+ return KNOT_EINVAL;
+ }
+ if (in_len > MAX_BIN_DATA_LEN) {
+ return KNOT_ERANGE;
+ }
+
+ // Compute output buffer length.
+ uint32_t out_len = ((in_len + 2) / 3) * 4;
+
+ // Allocate output buffer.
+ *out = malloc(out_len);
+ if (*out == NULL) {
+ return KNOT_ENOMEM;
+ }
+
+ // Encode data.
+ int32_t ret = base64_encode(in, in_len, *out, out_len);
+ if (ret < 0) {
+ free(*out);
+ *out = NULL;
+ }
+
+ return ret;
+}
+
+int32_t base64_decode(const uint8_t *in,
+ const uint32_t in_len,
+ uint8_t *out,
+ const uint32_t out_len)
+{
+ // Checking inputs.
+ if (in == NULL || out == NULL) {
+ return KNOT_EINVAL;
+ }
+ if (in_len > INT32_MAX || out_len < ((in_len + 3) / 4) * 3) {
+ return KNOT_ERANGE;
+ }
+ if ((in_len % 4) != 0) {
+ return KNOT_BASE64_ESIZE;
+ }
+
+ const uint8_t *stop = in + in_len;
+ uint8_t *bin = out;
+ uint8_t pad_len = 0;
+ uint8_t c1, c2, c3, c4;
+
+ // Decoding loop takes 4 characters and creates 3 bytes.
+ while (in < stop) {
+ // Filling and transforming 4 Base64 chars.
+ c1 = base64_dec[in[0]];
+ c2 = base64_dec[in[1]];
+ c3 = base64_dec[in[2]];
+ c4 = base64_dec[in[3]];
+
+ // Check 4. char if is bad or padding.
+ if (c4 >= PD) {
+ if (c4 == PD && pad_len == 0) {
+ pad_len = 1;
+ } else {
+ return KNOT_BASE64_ECHAR;
+ }
+ }
+
+ // Check 3. char if is bad or padding.
+ if (c3 >= PD) {
+ if (c3 == PD && pad_len == 1) {
+ pad_len = 2;
+ } else {
+ return KNOT_BASE64_ECHAR;
+ }
+ }
+
+ // Check 1. and 2. chars if are not padding.
+ if (c2 >= PD || c1 >= PD) {
+ return KNOT_BASE64_ECHAR;
+ }
+
+ // Computing of output data based on padding length.
+ switch (pad_len) {
+ case 0:
+ bin[2] = (c3 << 6) + c4;
+ // FALLTHROUGH
+ case 1:
+ bin[1] = (c2 << 4) + (c3 >> 2);
+ // FALLTHROUGH
+ case 2:
+ bin[0] = (c1 << 2) + (c2 >> 4);
+ }
+
+ // Update output end.
+ switch (pad_len) {
+ case 0:
+ bin += 3;
+ break;
+ case 1:
+ bin += 2;
+ break;
+ case 2:
+ bin += 1;
+ break;
+ }
+
+ in += 4;
+ }
+
+ return (bin - out);
+}
+
+int32_t base64_decode_alloc(const uint8_t *in,
+ const uint32_t in_len,
+ uint8_t **out)
+{
+ // Checking inputs.
+ if (out == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ // Compute output buffer length.
+ uint32_t out_len = ((in_len + 3) / 4) * 3;
+
+ // Allocate output buffer.
+ *out = malloc(out_len);
+ if (*out == NULL) {
+ return KNOT_ENOMEM;
+ }
+
+ // Decode data.
+ int32_t ret = base64_decode(in, in_len, *out, out_len);
+ if (ret < 0) {
+ free(*out);
+ *out = NULL;
+ }
+
+ return ret;
+}
diff --git a/src/contrib/base64.h b/src/contrib/base64.h
new file mode 100644
index 0000000..611574e
--- /dev/null
+++ b/src/contrib/base64.h
@@ -0,0 +1,102 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*!
+ * \brief Base64 implementation (RFC 4648).
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+/*!
+ * \brief Encodes binary data using Base64.
+ *
+ * \note Output data buffer contains Base64 text string which isn't
+ * terminated with '\0'!
+ *
+ * \param in Input binary data.
+ * \param in_len Length of input data.
+ * \param out Output data buffer.
+ * \param out_len Size of output buffer.
+ *
+ * \retval >=0 length of output string.
+ * \retval KNOT_E* if error.
+ */
+int32_t base64_encode(const uint8_t *in,
+ const uint32_t in_len,
+ uint8_t *out,
+ const uint32_t out_len);
+
+/*!
+ * \brief Encodes binary data using Base64 and output stores to own buffer.
+ *
+ * \note Output data buffer contains Base64 text string which isn't
+ * terminated with '\0'!
+ *
+ * \note Output buffer should be deallocated after use.
+ *
+ * \param in Input binary data.
+ * \param in_len Length of input data.
+ * \param out Output data buffer.
+ *
+ * \retval >=0 length of output string.
+ * \retval KNOT_E* if error.
+ */
+int32_t base64_encode_alloc(const uint8_t *in,
+ const uint32_t in_len,
+ uint8_t **out);
+
+/*!
+ * \brief Decodes text data using Base64.
+ *
+ * \note Input data needn't be terminated with '\0'.
+ *
+ * \note Input data must be continuous Base64 string!
+ *
+ * \param in Input text data.
+ * \param in_len Length of input string.
+ * \param out Output data buffer.
+ * \param out_len Size of output buffer.
+ *
+ * \retval >=0 length of output data.
+ * \retval KNOT_E* if error.
+ */
+int32_t base64_decode(const uint8_t *in,
+ const uint32_t in_len,
+ uint8_t *out,
+ const uint32_t out_len);
+
+/*!
+ * \brief Decodes text data using Base64 and output stores to own buffer.
+ *
+ * \note Input data needn't be terminated with '\0'.
+ *
+ * \note Input data must be continuous Base64 string!
+ *
+ * \note Output buffer should be deallocated after use.
+ *
+ * \param in Input text data.
+ * \param in_len Length of input string.
+ * \param out Output data buffer.
+ *
+ * \retval >=0 length of output data.
+ * \retval KNOT_E* if error.
+ */
+int32_t base64_decode_alloc(const uint8_t *in,
+ const uint32_t in_len,
+ uint8_t **out);
+
+/*! @} */
diff --git a/src/contrib/ctype.h b/src/contrib/ctype.h
new file mode 100644
index 0000000..93b85db
--- /dev/null
+++ b/src/contrib/ctype.h
@@ -0,0 +1,192 @@
+/* Copyright (C) 2017 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*!
+ * \brief Locale-independent ctype functions.
+ */
+
+#pragma once
+
+#include <ctype.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+enum {
+ CT_DIGIT = 1 << 0,
+ CT_UPPER = 1 << 1,
+ CT_LOWER = 1 << 2,
+ CT_XDIGT = 1 << 3,
+ CT_PUNCT = 1 << 4,
+ CT_PRINT = 1 << 5,
+ CT_SPACE = 1 << 6,
+};
+
+static const uint8_t char_mask[256] = {
+ // 0 - 8
+ ['\t'] = CT_SPACE,
+ ['\n'] = CT_SPACE,
+ ['\v'] = CT_SPACE,
+ ['\f'] = CT_SPACE,
+ ['\r'] = CT_SPACE,
+ // 14 - 31
+ [' '] = CT_PRINT | CT_SPACE,
+
+ ['!'] = CT_PRINT | CT_PUNCT,
+ ['"'] = CT_PRINT | CT_PUNCT,
+ ['#'] = CT_PRINT | CT_PUNCT,
+ ['$'] = CT_PRINT | CT_PUNCT,
+ ['%'] = CT_PRINT | CT_PUNCT,
+ ['&'] = CT_PRINT | CT_PUNCT,
+ ['\''] = CT_PRINT | CT_PUNCT,
+ ['('] = CT_PRINT | CT_PUNCT,
+ [')'] = CT_PRINT | CT_PUNCT,
+ ['*'] = CT_PRINT | CT_PUNCT,
+ ['+'] = CT_PRINT | CT_PUNCT,
+ [','] = CT_PRINT | CT_PUNCT,
+ ['-'] = CT_PRINT | CT_PUNCT,
+ ['.'] = CT_PRINT | CT_PUNCT,
+ ['/'] = CT_PRINT | CT_PUNCT,
+
+ ['0'] = CT_PRINT | CT_DIGIT | CT_XDIGT,
+ ['1'] = CT_PRINT | CT_DIGIT | CT_XDIGT,
+ ['2'] = CT_PRINT | CT_DIGIT | CT_XDIGT,
+ ['3'] = CT_PRINT | CT_DIGIT | CT_XDIGT,
+ ['4'] = CT_PRINT | CT_DIGIT | CT_XDIGT,
+ ['5'] = CT_PRINT | CT_DIGIT | CT_XDIGT,
+ ['6'] = CT_PRINT | CT_DIGIT | CT_XDIGT,
+ ['7'] = CT_PRINT | CT_DIGIT | CT_XDIGT,
+ ['8'] = CT_PRINT | CT_DIGIT | CT_XDIGT,
+ ['9'] = CT_PRINT | CT_DIGIT | CT_XDIGT,
+
+ [':'] = CT_PRINT | CT_PUNCT,
+ [';'] = CT_PRINT | CT_PUNCT,
+ ['<'] = CT_PRINT | CT_PUNCT,
+ ['='] = CT_PRINT | CT_PUNCT,
+ ['>'] = CT_PRINT | CT_PUNCT,
+ ['?'] = CT_PRINT | CT_PUNCT,
+ ['@'] = CT_PRINT | CT_PUNCT,
+
+ ['A'] = CT_PRINT | CT_UPPER | CT_XDIGT,
+ ['B'] = CT_PRINT | CT_UPPER | CT_XDIGT,
+ ['C'] = CT_PRINT | CT_UPPER | CT_XDIGT,
+ ['D'] = CT_PRINT | CT_UPPER | CT_XDIGT,
+ ['E'] = CT_PRINT | CT_UPPER | CT_XDIGT,
+ ['F'] = CT_PRINT | CT_UPPER | CT_XDIGT,
+ ['G'] = CT_PRINT | CT_UPPER,
+ ['H'] = CT_PRINT | CT_UPPER,
+ ['I'] = CT_PRINT | CT_UPPER,
+ ['J'] = CT_PRINT | CT_UPPER,
+ ['K'] = CT_PRINT | CT_UPPER,
+ ['L'] = CT_PRINT | CT_UPPER,
+ ['M'] = CT_PRINT | CT_UPPER,
+ ['N'] = CT_PRINT | CT_UPPER,
+ ['O'] = CT_PRINT | CT_UPPER,
+ ['P'] = CT_PRINT | CT_UPPER,
+ ['Q'] = CT_PRINT | CT_UPPER,
+ ['R'] = CT_PRINT | CT_UPPER,
+ ['S'] = CT_PRINT | CT_UPPER,
+ ['T'] = CT_PRINT | CT_UPPER,
+ ['U'] = CT_PRINT | CT_UPPER,
+ ['V'] = CT_PRINT | CT_UPPER,
+ ['W'] = CT_PRINT | CT_UPPER,
+ ['X'] = CT_PRINT | CT_UPPER,
+ ['Y'] = CT_PRINT | CT_UPPER,
+ ['Z'] = CT_PRINT | CT_UPPER,
+
+ ['['] = CT_PRINT | CT_PUNCT,
+ ['\\'] = CT_PRINT | CT_PUNCT,
+ [']'] = CT_PRINT | CT_PUNCT,
+ ['^'] = CT_PRINT | CT_PUNCT,
+ ['_'] = CT_PRINT | CT_PUNCT,
+ ['`'] = CT_PRINT | CT_PUNCT,
+
+ ['a'] = CT_PRINT | CT_LOWER | CT_XDIGT,
+ ['b'] = CT_PRINT | CT_LOWER | CT_XDIGT,
+ ['c'] = CT_PRINT | CT_LOWER | CT_XDIGT,
+ ['d'] = CT_PRINT | CT_LOWER | CT_XDIGT,
+ ['e'] = CT_PRINT | CT_LOWER | CT_XDIGT,
+ ['f'] = CT_PRINT | CT_LOWER | CT_XDIGT,
+ ['g'] = CT_PRINT | CT_LOWER,
+ ['h'] = CT_PRINT | CT_LOWER,
+ ['i'] = CT_PRINT | CT_LOWER,
+ ['j'] = CT_PRINT | CT_LOWER,
+ ['k'] = CT_PRINT | CT_LOWER,
+ ['l'] = CT_PRINT | CT_LOWER,
+ ['m'] = CT_PRINT | CT_LOWER,
+ ['n'] = CT_PRINT | CT_LOWER,
+ ['o'] = CT_PRINT | CT_LOWER,
+ ['p'] = CT_PRINT | CT_LOWER,
+ ['q'] = CT_PRINT | CT_LOWER,
+ ['r'] = CT_PRINT | CT_LOWER,
+ ['s'] = CT_PRINT | CT_LOWER,
+ ['t'] = CT_PRINT | CT_LOWER,
+ ['u'] = CT_PRINT | CT_LOWER,
+ ['v'] = CT_PRINT | CT_LOWER,
+ ['w'] = CT_PRINT | CT_LOWER,
+ ['x'] = CT_PRINT | CT_LOWER,
+ ['y'] = CT_PRINT | CT_LOWER,
+ ['z'] = CT_PRINT | CT_LOWER,
+
+ ['{'] = CT_PRINT | CT_PUNCT,
+ ['|'] = CT_PRINT | CT_PUNCT,
+ ['}'] = CT_PRINT | CT_PUNCT,
+ ['~'] = CT_PRINT | CT_PUNCT,
+ // 127 - 255
+};
+
+static inline bool is_alnum(uint8_t c)
+{
+ return char_mask[c] & (CT_DIGIT | CT_UPPER | CT_LOWER);
+}
+
+static inline bool is_alpha(uint8_t c)
+{
+ return char_mask[c] & (CT_UPPER | CT_LOWER);
+}
+
+static inline bool is_digit(uint8_t c)
+{
+ return char_mask[c] & CT_DIGIT;
+}
+
+static inline bool is_xdigit(uint8_t c)
+{
+ return char_mask[c] & CT_XDIGT;
+}
+
+static inline bool is_lower(uint8_t c)
+{
+ return char_mask[c] & CT_LOWER;
+}
+
+static inline bool is_upper(uint8_t c)
+{
+ return char_mask[c] & CT_UPPER;
+}
+
+static inline bool is_print(uint8_t c)
+{
+ return char_mask[c] & CT_PRINT;
+}
+
+static inline bool is_punct(uint8_t c)
+{
+ return char_mask[c] & CT_PUNCT;
+}
+
+static inline bool is_space(uint8_t c)
+{
+ return char_mask[c] & CT_SPACE;
+}
diff --git a/src/contrib/dnstap/convert.c b/src/contrib/dnstap/convert.c
new file mode 100644
index 0000000..f59bacd
--- /dev/null
+++ b/src/contrib/dnstap/convert.c
@@ -0,0 +1,142 @@
+/* Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+
+#include "contrib/dnstap/convert.h"
+#include "contrib/dnstap/dnstap.pb-c.h"
+
+/*!
+ * \brief Translation between real and Dnstap value.
+ */
+typedef struct mapping {
+ int real;
+ int dnstap;
+} mapping_t;
+
+/*!
+ * \brief Mapping for network family.
+ */
+static const mapping_t SOCKET_FAMILY_MAPPING[] = {
+ { AF_INET, DNSTAP__SOCKET_FAMILY__INET },
+ { AF_INET6, DNSTAP__SOCKET_FAMILY__INET6 },
+ { 0 }
+};
+
+/*!
+ * \brief Mapping from network protocol.
+ */
+static const mapping_t SOCKET_PROTOCOL_MAPPING[] = {
+ { IPPROTO_UDP, DNSTAP__SOCKET_PROTOCOL__UDP },
+ { IPPROTO_TCP, DNSTAP__SOCKET_PROTOCOL__TCP },
+ { 0 }
+};
+
+/*!
+ * \brief Get Dnstap value for a given real value.
+ */
+static int encode(const mapping_t *mapping, int real)
+{
+ for (const mapping_t *m = mapping; m->real != 0; m += 1) {
+ if (m->real == real) {
+ return m->dnstap;
+ }
+ }
+
+ return 0;
+}
+
+/*!
+ * \brief Get real value for a given Dnstap value.
+ */
+static int decode(const mapping_t *mapping, int dnstap)
+{
+ for (const mapping_t *m = mapping; m->real != 0; m += 1) {
+ if (m->dnstap == dnstap) {
+ return m->real;
+ }
+ }
+
+ return 0;
+}
+
+/* -- public API ----------------------------------------------------------- */
+
+Dnstap__SocketFamily dt_family_encode(int family)
+{
+ return encode(SOCKET_FAMILY_MAPPING, family);
+}
+
+int dt_family_decode(Dnstap__SocketFamily dnstap_family)
+{
+ return decode(SOCKET_FAMILY_MAPPING, dnstap_family);
+}
+
+Dnstap__SocketProtocol dt_protocol_encode(int protocol)
+{
+ return encode(SOCKET_PROTOCOL_MAPPING, protocol);
+}
+
+int dt_protocol_decode(Dnstap__SocketProtocol dnstap_protocol)
+{
+ return decode(SOCKET_PROTOCOL_MAPPING, dnstap_protocol);
+}
+
+bool dt_message_type_is_query(Dnstap__Message__Type type)
+{
+ switch (type) {
+ case DNSTAP__MESSAGE__TYPE__AUTH_QUERY:
+ case DNSTAP__MESSAGE__TYPE__CLIENT_QUERY:
+ case DNSTAP__MESSAGE__TYPE__FORWARDER_QUERY:
+ case DNSTAP__MESSAGE__TYPE__RESOLVER_QUERY:
+ case DNSTAP__MESSAGE__TYPE__STUB_QUERY:
+ case DNSTAP__MESSAGE__TYPE__TOOL_QUERY:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool dt_message_type_is_response(Dnstap__Message__Type type)
+{
+ switch (type) {
+ case DNSTAP__MESSAGE__TYPE__AUTH_RESPONSE:
+ case DNSTAP__MESSAGE__TYPE__CLIENT_RESPONSE:
+ case DNSTAP__MESSAGE__TYPE__FORWARDER_RESPONSE:
+ case DNSTAP__MESSAGE__TYPE__RESOLVER_RESPONSE:
+ case DNSTAP__MESSAGE__TYPE__STUB_RESPONSE:
+ case DNSTAP__MESSAGE__TYPE__TOOL_RESPONSE:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool dt_message_role_is_initiator(Dnstap__Message__Type type)
+{
+ switch (type) {
+ case DNSTAP__MESSAGE__TYPE__AUTH_QUERY:
+ case DNSTAP__MESSAGE__TYPE__AUTH_RESPONSE:
+ case DNSTAP__MESSAGE__TYPE__CLIENT_QUERY:
+ case DNSTAP__MESSAGE__TYPE__CLIENT_RESPONSE:
+ return false;
+ default:
+ return true;
+ }
+}
diff --git a/src/contrib/dnstap/convert.h b/src/contrib/dnstap/convert.h
new file mode 100644
index 0000000..bf00ffb
--- /dev/null
+++ b/src/contrib/dnstap/convert.h
@@ -0,0 +1,59 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*!
+ * \brief Dnstap identifiers conversions.
+ */
+
+#pragma once
+
+#include <stdbool.h>
+
+#include "contrib/dnstap/dnstap.pb-c.h"
+
+/*!
+ * \brief Get Dnstap socket family from the real one.
+ */
+Dnstap__SocketFamily dt_family_encode(int family);
+
+/*!
+ * \brief Get real socket family from the Dnstap one.
+ */
+int dt_family_decode(Dnstap__SocketFamily dnstap_family);
+
+/*!
+ * \brief Get Dnstap protocol from a real one.
+ */
+Dnstap__SocketProtocol dt_protocol_encode(int protocol);
+
+/*!
+ * \brief Get real protocol from the Dnstap one.
+ */
+int dt_protocol_decode(Dnstap__SocketProtocol dnstap_protocol);
+
+/*!
+ * Check if a message type is any type of a query.
+ */
+bool dt_message_type_is_query(Dnstap__Message__Type type);
+
+/*!
+ * Check if a message type is any type of a response.
+ */
+bool dt_message_type_is_response(Dnstap__Message__Type type);
+
+/*!
+ * Check if a message role is any type of an initiator.
+ */
+bool dt_message_role_is_initiator(Dnstap__Message__Type type);
diff --git a/src/contrib/dnstap/dnstap.c b/src/contrib/dnstap/dnstap.c
new file mode 100644
index 0000000..9e24eac
--- /dev/null
+++ b/src/contrib/dnstap/dnstap.c
@@ -0,0 +1,40 @@
+/* Copyright (C) 2014 Farsight Security, Inc. <software@farsightsecurity.com>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "contrib/dnstap/dnstap.pb-c.h"
+
+#define DNSTAP_INITIAL_BUF_SIZE 256
+
+uint8_t* dt_pack(const Dnstap__Dnstap *d, uint8_t **buf, size_t *sz)
+{
+ ProtobufCBufferSimple sbuf = { { NULL } };
+
+ sbuf.base.append = protobuf_c_buffer_simple_append;
+ sbuf.len = 0;
+ sbuf.alloced = DNSTAP_INITIAL_BUF_SIZE;
+ sbuf.data = malloc(sbuf.alloced);
+ if (sbuf.data == NULL) {
+ return NULL;
+ }
+ sbuf.must_free_data = 1;
+
+ *sz = dnstap__dnstap__pack_to_buffer(d, (ProtobufCBuffer *) &sbuf);
+ *buf = sbuf.data;
+ return *buf;
+}
diff --git a/src/contrib/dnstap/dnstap.h b/src/contrib/dnstap/dnstap.h
new file mode 100644
index 0000000..41e5f65
--- /dev/null
+++ b/src/contrib/dnstap/dnstap.h
@@ -0,0 +1,46 @@
+/* Copyright (C) 2014 Farsight Security, Inc. <software@farsightsecurity.com>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*!
+ * \author Robert Edmonds <edmonds@fsi.io>
+ *
+ * \brief Public interface for dnstap.
+ */
+
+#pragma once
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "contrib/dnstap/dnstap.pb-c.h"
+
+/*! \brief Frame Streams "Content Type" value for dnstap. */
+#define DNSTAP_CONTENT_TYPE "protobuf:dnstap.Dnstap"
+
+/*!
+ * \brief Serializes a filled out dnstap protobuf struct. Dynamically allocates
+ * storage for the serialized frame.
+ *
+ * \note This function returns a copy of its parameter return value 'buf' to
+ * make error checking slightly easier.
+ *
+ * \param d dnstap protobuf struct.
+ * \param[out] buf Serialized frame.
+ * \param[out] sz Size in bytes of the serialized frame.
+ *
+ * \return Serialized frame.
+ * \retval NULL if error.
+ */
+uint8_t* dt_pack(const Dnstap__Dnstap *d, uint8_t **buf, size_t *sz);
diff --git a/src/contrib/dnstap/dnstap.proto b/src/contrib/dnstap/dnstap.proto
new file mode 100644
index 0000000..ea5c77a
--- /dev/null
+++ b/src/contrib/dnstap/dnstap.proto
@@ -0,0 +1,270 @@
+// dnstap: flexible, structured event replication format for DNS software
+//
+// This file contains the protobuf schemas for the "dnstap" structured event
+// replication format for DNS software.
+
+// Written in 2013-2014 by Farsight Security, Inc.
+//
+// To the extent possible under law, the author(s) have dedicated all
+// copyright and related and neighboring rights to this file to the public
+// domain worldwide. This file is distributed without any warranty.
+//
+// You should have received a copy of the CC0 Public Domain Dedication along
+// with this file. If not, see:
+//
+// <http://creativecommons.org/publicdomain/zero/1.0/>.
+
+syntax = "proto2";
+
+package dnstap;
+
+// "Dnstap": this is the top-level dnstap type, which is a "union" type that
+// contains other kinds of dnstap payloads, although currently only one type
+// of dnstap payload is defined.
+// See: https://developers.google.com/protocol-buffers/docs/techniques#union
+message Dnstap {
+ // DNS server identity.
+ // If enabled, this is the identity string of the DNS server which generated
+ // this message. Typically this would be the same string as returned by an
+ // "NSID" (RFC 5001) query.
+ optional bytes identity = 1;
+
+ // DNS server version.
+ // If enabled, this is the version string of the DNS server which generated
+ // this message. Typically this would be the same string as returned by a
+ // "version.bind" query.
+ optional bytes version = 2;
+
+ // Extra data for this payload.
+ // This field can be used for adding an arbitrary byte-string annotation to
+ // the payload. No encoding or interpretation is applied or enforced.
+ optional bytes extra = 3;
+
+ // Identifies which field below is filled in.
+ enum Type {
+ MESSAGE = 1;
+ }
+ required Type type = 15;
+
+ // One of the following will be filled in.
+ optional Message message = 14;
+}
+
+// SocketFamily: the network protocol family of a socket. This specifies how
+// to interpret "network address" fields.
+enum SocketFamily {
+ INET = 1; // IPv4 (RFC 791)
+ INET6 = 2; // IPv6 (RFC 2460)
+}
+
+// SocketProtocol: the transport protocol of a socket. This specifies how to
+// interpret "transport port" fields.
+enum SocketProtocol {
+ UDP = 1; // User Datagram Protocol (RFC 768)
+ TCP = 2; // Transmission Control Protocol (RFC 793)
+}
+
+// Message: a wire-format (RFC 1035 section 4) DNS message and associated
+// metadata. Applications generating "Message" payloads should follow
+// certain requirements based on the MessageType, see below.
+message Message {
+
+ // There are eight types of "Message" defined that correspond to the
+ // four arrows in the following diagram, slightly modified from RFC 1035
+ // section 2:
+
+ // +---------+ +----------+ +--------+
+ // | | query | | query | |
+ // | Stub |-SQ--------CQ->| Recursive|-RQ----AQ->| Auth. |
+ // | Resolver| | Server | | Name |
+ // | |<-SR--------CR-| |<-RR----AR-| Server |
+ // +---------+ response | | response | |
+ // +----------+ +--------+
+
+ // Each arrow has two Type values each, one for each "end" of each arrow,
+ // because these are considered to be distinct events. Each end of each
+ // arrow on the diagram above has been marked with a two-letter Type
+ // mnemonic. Clockwise from upper left, these mnemonic values are:
+ //
+ // SQ: STUB_QUERY
+ // CQ: CLIENT_QUERY
+ // RQ: RESOLVER_QUERY
+ // AQ: AUTH_QUERY
+ // AR: AUTH_RESPONSE
+ // RR: RESOLVER_RESPONSE
+ // CR: CLIENT_RESPONSE
+ // SR: STUB_RESPONSE
+
+ // Two additional types of "Message" have been defined for the
+ // "forwarding" case where an upstream DNS server is responsible for
+ // further recursion. These are not shown on the diagram above, but have
+ // the following mnemonic values:
+
+ // FQ: FORWARDER_QUERY
+ // FR: FORWARDER_RESPONSE
+
+ // The "Message" Type values are defined below.
+
+ enum Type {
+ // AUTH_QUERY is a DNS query message received from a resolver by an
+ // authoritative name server, from the perspective of the authoritative
+ // name server.
+ AUTH_QUERY = 1;
+
+ // AUTH_RESPONSE is a DNS response message sent from an authoritative
+ // name server to a resolver, from the perspective of the authoritative
+ // name server.
+ AUTH_RESPONSE = 2;
+
+ // RESOLVER_QUERY is a DNS query message sent from a resolver to an
+ // authoritative name server, from the perspective of the resolver.
+ // Resolvers typically clear the RD (recursion desired) bit when
+ // sending queries.
+ RESOLVER_QUERY = 3;
+
+ // RESOLVER_RESPONSE is a DNS response message received from an
+ // authoritative name server by a resolver, from the perspective of
+ // the resolver.
+ RESOLVER_RESPONSE = 4;
+
+ // CLIENT_QUERY is a DNS query message sent from a client to a DNS
+ // server which is expected to perform further recursion, from the
+ // perspective of the DNS server. The client may be a stub resolver or
+ // forwarder or some other type of software which typically sets the RD
+ // (recursion desired) bit when querying the DNS server. The DNS server
+ // may be a simple forwarding proxy or it may be a full recursive
+ // resolver.
+ CLIENT_QUERY = 5;
+
+ // CLIENT_RESPONSE is a DNS response message sent from a DNS server to
+ // a client, from the perspective of the DNS server. The DNS server
+ // typically sets the RA (recursion available) bit when responding.
+ CLIENT_RESPONSE = 6;
+
+ // FORWARDER_QUERY is a DNS query message sent from a downstream DNS
+ // server to an upstream DNS server which is expected to perform
+ // further recursion, from the perspective of the downstream DNS
+ // server.
+ FORWARDER_QUERY = 7;
+
+ // FORWARDER_RESPONSE is a DNS response message sent from an upstream
+ // DNS server performing recursion to a downstream DNS server, from the
+ // perspective of the downstream DNS server.
+ FORWARDER_RESPONSE = 8;
+
+ // STUB_QUERY is a DNS query message sent from a stub resolver to a DNS
+ // server, from the perspective of the stub resolver.
+ STUB_QUERY = 9;
+
+ // STUB_RESPONSE is a DNS response message sent from a DNS server to a
+ // stub resolver, from the perspective of the stub resolver.
+ STUB_RESPONSE = 10;
+
+ // TOOL_QUERY is a DNS query message sent from a DNS software tool to a
+ // DNS server, from the perspective of the tool.
+ TOOL_QUERY = 11;
+
+ // TOOL_RESPONSE is a DNS response message received by a DNS software
+ // tool from a DNS server, from the perspective of the tool.
+ TOOL_RESPONSE = 12;
+ }
+
+ // One of the Type values described above.
+ required Type type = 1;
+
+ // One of the SocketFamily values described above.
+ optional SocketFamily socket_family = 2;
+
+ // One of the SocketProtocol values described above.
+ optional SocketProtocol socket_protocol = 3;
+
+ // The network address of the message initiator.
+ // For SocketFamily INET, this field is 4 octets (IPv4 address).
+ // For SocketFamily INET6, this field is 16 octets (IPv6 address).
+ optional bytes query_address = 4;
+
+ // The network address of the message responder.
+ // For SocketFamily INET, this field is 4 octets (IPv4 address).
+ // For SocketFamily INET6, this field is 16 octets (IPv6 address).
+ optional bytes response_address = 5;
+
+ // The transport port of the message initiator.
+ // This is a 16-bit UDP or TCP port number, depending on SocketProtocol.
+ optional uint32 query_port = 6;
+
+ // The transport port of the message responder.
+ // This is a 16-bit UDP or TCP port number, depending on SocketProtocol.
+ optional uint32 response_port = 7;
+
+ // The time at which the DNS query message was sent or received, depending
+ // on whether this is an AUTH_QUERY, RESOLVER_QUERY, or CLIENT_QUERY.
+ // This is the number of seconds since the UNIX epoch.
+ optional uint64 query_time_sec = 8;
+
+ // The time at which the DNS query message was sent or received.
+ // This is the seconds fraction, expressed as a count of nanoseconds.
+ optional fixed32 query_time_nsec = 9;
+
+ // The initiator's original wire-format DNS query message, verbatim.
+ optional bytes query_message = 10;
+
+ // The "zone" or "bailiwick" pertaining to the DNS query message.
+ // This is a wire-format DNS domain name.
+ optional bytes query_zone = 11;
+
+ // The time at which the DNS response message was sent or received,
+ // depending on whether this is an AUTH_RESPONSE, RESOLVER_RESPONSE, or
+ // CLIENT_RESPONSE.
+ // This is the number of seconds since the UNIX epoch.
+ optional uint64 response_time_sec = 12;
+
+ // The time at which the DNS response message was sent or received.
+ // This is the seconds fraction, expressed as a count of nanoseconds.
+ optional fixed32 response_time_nsec = 13;
+
+ // The responder's original wire-format DNS response message, verbatim.
+ optional bytes response_message = 14;
+}
+
+// All fields except for 'type' in the Message schema are optional.
+// It is recommended that at least the following fields be filled in for
+// particular types of Messages.
+
+// AUTH_QUERY:
+// socket_family, socket_protocol
+// query_address, query_port
+// query_message
+// query_time_sec, query_time_nsec
+
+// AUTH_RESPONSE:
+// socket_family, socket_protocol
+// query_address, query_port
+// query_time_sec, query_time_nsec
+// response_message
+// response_time_sec, response_time_nsec
+
+// RESOLVER_QUERY:
+// socket_family, socket_protocol
+// query_message
+// query_time_sec, query_time_nsec
+// query_zone
+// response_address, response_port
+
+// RESOLVER_RESPONSE:
+// socket_family, socket_protocol
+// query_time_sec, query_time_nsec
+// query_zone
+// response_address, response_port
+// response_message
+// response_time_sec, response_time_nsec
+
+// CLIENT_QUERY:
+// socket_family, socket_protocol
+// query_message
+// query_time_sec, query_time_nsec
+
+// CLIENT_RESPONSE:
+// socket_family, socket_protocol
+// query_time_sec, query_time_nsec
+// response_message
+// response_time_sec, response_time_nsec
diff --git a/src/contrib/dnstap/message.c b/src/contrib/dnstap/message.c
new file mode 100644
index 0000000..0b41e9d
--- /dev/null
+++ b/src/contrib/dnstap/message.c
@@ -0,0 +1,130 @@
+/* Copyright (C) 2017 Farsight Security, Inc. <software@farsightsecurity.com>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <netinet/in.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "libknot/errcode.h"
+
+#include "contrib/dnstap/convert.h"
+#include "contrib/dnstap/message.h"
+
+static void set_address(const struct sockaddr *sockaddr,
+ ProtobufCBinaryData *addr,
+ protobuf_c_boolean *has_addr,
+ uint32_t *port,
+ protobuf_c_boolean *has_port)
+{
+ if (sockaddr == NULL) {
+ *has_addr = 0;
+ *has_port = 0;
+ return;
+ }
+
+ *has_addr = 1;
+ *has_port = 1;
+
+ if (sockaddr->sa_family == AF_INET) {
+ const struct sockaddr_in *sai;
+ sai = (const struct sockaddr_in *)sockaddr;
+ addr->len = sizeof(sai->sin_addr);
+ addr->data = (uint8_t *)&sai->sin_addr.s_addr;
+ *port = ntohs(sai->sin_port);
+ } else if (sockaddr->sa_family == AF_INET6) {
+ const struct sockaddr_in6 *sai6;
+ sai6 = (const struct sockaddr_in6 *)sockaddr;
+ addr->len = sizeof(sai6->sin6_addr);
+ addr->data = (uint8_t *)&sai6->sin6_addr.s6_addr;
+ *port = ntohs(sai6->sin6_port);
+ }
+}
+
+static int get_family(const struct sockaddr *query_sa,
+ const struct sockaddr *response_sa)
+{
+ const struct sockaddr *source = query_sa ? query_sa : response_sa;
+ if (source == NULL) {
+ return 0;
+ }
+
+ return dt_family_encode(source->sa_family);
+}
+
+int dt_message_fill(Dnstap__Message *m,
+ const Dnstap__Message__Type type,
+ const struct sockaddr *query_sa,
+ const struct sockaddr *response_sa,
+ const int protocol,
+ const void *wire,
+ const size_t len_wire,
+ const struct timespec *mtime)
+{
+ if (m == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ memset(m, 0, sizeof(*m));
+
+ m->base.descriptor = &dnstap__message__descriptor;
+
+ // Message.type
+ m->type = type;
+
+ // Message.socket_family
+ m->socket_family = get_family(query_sa, response_sa);
+ m->has_socket_family = m->socket_family != 0;
+
+ // Message.socket_protocol
+ m->socket_protocol = dt_protocol_encode(protocol);
+ m->has_socket_protocol = m->socket_protocol != 0;
+
+ // Message addresses
+ set_address(query_sa, &m->query_address, &m->has_query_address,
+ &m->query_port, &m->has_query_port);
+ set_address(response_sa, &m->response_address, &m->has_response_address,
+ &m->response_port, &m->has_response_port);
+
+ if (dt_message_type_is_query(type)) {
+ // Message.query_message
+ m->query_message.len = len_wire;
+ m->query_message.data = (uint8_t *)wire;
+ m->has_query_message = 1;
+ // Message.query_time_sec, Message.query_time_nsec
+ if (mtime != NULL) {
+ m->query_time_sec = mtime->tv_sec;
+ m->query_time_nsec = mtime->tv_nsec;
+ m->has_query_time_sec = 1;
+ m->has_query_time_nsec = 1;
+ }
+ } else if (dt_message_type_is_response(type)) {
+ // Message.response_message
+ m->response_message.len = len_wire;
+ m->response_message.data = (uint8_t *)wire;
+ m->has_response_message = 1;
+ // Message.response_time_sec, Message.response_time_nsec
+ if (mtime != NULL) {
+ m->response_time_sec = mtime->tv_sec;
+ m->response_time_nsec = mtime->tv_nsec;
+ m->has_response_time_sec = 1;
+ m->has_response_time_nsec = 1;
+ }
+ }
+
+ return KNOT_EOK;
+}
diff --git a/src/contrib/dnstap/message.h b/src/contrib/dnstap/message.h
new file mode 100644
index 0000000..11a967d
--- /dev/null
+++ b/src/contrib/dnstap/message.h
@@ -0,0 +1,62 @@
+/* Copyright (C) 2017 Farsight Security, Inc. <software@farsightsecurity.com>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*!
+ * \author Robert Edmonds <edmonds@fsi.io>
+ *
+ * \brief Dnstap message interface.
+ */
+
+#pragma once
+
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <stddef.h>
+
+#include "contrib/dnstap/dnstap.pb-c.h"
+
+/*!
+ * \brief Fill a Dnstap__Message structure with the given parameters.
+ *
+ * \param[out] m
+ * Dnstap__Message structure to fill. Will be zeroed first.
+ * \param type
+ * One of the DNSTAP__MESSAGE__TYPE__* values.
+ * \param query_sa
+ * sockaddr_in or sockaddr_in6 to use when filling the 'socket_family',
+ * 'query_address', 'query_port' fields.
+ * \param response_sa
+ * sockaddr_in or sockaddr_in6 to use when filling the 'socket_family',
+ * 'response_address', 'response_port' fields.
+ * \param protocol
+ * \c IPPROTO_UDP or \c IPPROTO_TCP.
+ * \param wire
+ * Wire-format query message or response message (depending on 'type').
+ * \param len_wire
+ * Length in bytes of 'wire'.
+ * \param mtime
+ * Message time. May be NULL.
+ *
+ * \retval KNOT_EOK
+ * \retval KNOT_EINVAL
+ */
+int dt_message_fill(Dnstap__Message *m,
+ const Dnstap__Message__Type type,
+ const struct sockaddr *query_sa,
+ const struct sockaddr *response_sa,
+ const int protocol,
+ const void *wire,
+ const size_t len_wire,
+ const struct timespec *mtime);
diff --git a/src/contrib/dnstap/reader.c b/src/contrib/dnstap/reader.c
new file mode 100644
index 0000000..593c6ec
--- /dev/null
+++ b/src/contrib/dnstap/reader.c
@@ -0,0 +1,105 @@
+/* Copyright (C) 2014 Farsight Security, Inc. <software@farsightsecurity.com>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "libknot/errcode.h"
+
+#include "contrib/macros.h"
+#include "contrib/dnstap/dnstap.h"
+#include "contrib/dnstap/reader.h"
+
+dt_reader_t* dt_reader_create(const char *file_path)
+{
+ struct fstrm_file_options *fopt = NULL;
+ struct fstrm_reader_options *ropt = NULL;
+ dt_reader_t *reader = NULL;
+ fstrm_res res;
+
+ reader = calloc(1, sizeof(dt_reader_t));
+ if (reader == NULL) {
+ goto fail;
+ }
+
+ // Open reader.
+ fopt = fstrm_file_options_init();
+ fstrm_file_options_set_file_path(fopt, file_path);
+ ropt = fstrm_reader_options_init();
+ fstrm_reader_options_add_content_type(ropt,
+ (const uint8_t *) DNSTAP_CONTENT_TYPE,
+ strlen(DNSTAP_CONTENT_TYPE));
+ reader->fr = fstrm_file_reader_init(fopt, ropt);
+ fstrm_file_options_destroy(&fopt);
+ fstrm_reader_options_destroy(&ropt);
+ if (reader->fr == NULL) {
+ goto fail;
+ }
+ res = fstrm_reader_open(reader->fr);
+ if (res != fstrm_res_success) {
+ goto fail;
+ }
+
+ return reader;
+fail:
+ dt_reader_free(reader);
+ return NULL;
+}
+
+void dt_reader_free(dt_reader_t *reader)
+{
+ if (reader == NULL) {
+ return;
+ }
+
+ fstrm_reader_destroy(&reader->fr);
+ free(reader);
+}
+
+int dt_reader_read(dt_reader_t *reader, Dnstap__Dnstap **d)
+{
+ fstrm_res res;
+ const uint8_t *data = NULL;
+ size_t len = 0;
+
+ res = fstrm_reader_read(reader->fr, &data, &len);
+ if (res == fstrm_res_success) {
+ *d = dnstap__dnstap__unpack(NULL, len, data);
+ if (*d == NULL) {
+ return KNOT_ENOMEM;
+ }
+ } else if (res == fstrm_res_failure) {
+ return KNOT_ERROR;
+ } else if (res == fstrm_res_stop) {
+ return KNOT_EOF;
+ }
+
+ return KNOT_EOK;
+}
+
+void dt_reader_free_frame(dt_reader_t *reader, Dnstap__Dnstap **frame_ptr)
+{
+ if (!*frame_ptr) {
+ return;
+ }
+
+ UNUSED(reader);
+
+ dnstap__dnstap__free_unpacked(*frame_ptr, NULL);
+ *frame_ptr = NULL;
+}
diff --git a/src/contrib/dnstap/reader.h b/src/contrib/dnstap/reader.h
new file mode 100644
index 0000000..c8cda26
--- /dev/null
+++ b/src/contrib/dnstap/reader.h
@@ -0,0 +1,72 @@
+/* Copyright (C) 2017 Farsight Security, Inc. <software@farsightsecurity.com>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*!
+ * \brief Dnstap file reader.
+ */
+
+#pragma once
+
+#include <fstrm.h>
+#include <protobuf-c/protobuf-c.h>
+
+#include "contrib/dnstap/dnstap.pb-c.h"
+
+/*! \brief Structure for dnstap file reader. */
+typedef struct {
+ /*!< Input reader. */
+ struct fstrm_reader *fr;
+} dt_reader_t;
+
+/*!
+ * \brief Creates dnstap file reader structure.
+ *
+ * \param file_path Name of file to read input from.
+ *
+ * \retval reader if success.
+ * \retval NULL if error.
+ */
+dt_reader_t* dt_reader_create(const char *file_path);
+
+/*!
+ * \brief Close dnstap file reader.
+ *
+ * \param reader dnstap file reader structure.
+ */
+void dt_reader_free(dt_reader_t *reader);
+
+/*!
+ * \brief Read a dnstap protobuf from a dnstap file reader.
+ *
+ * Caller must deallocate the returned protobuf with the
+ * dnstap__dnstap__free_unpacked() function.
+ *
+ * \param[in] reader dnstap file reader structure.
+ * \param[out] d Unpacked dnstap protobuf.
+ *
+ * \retval KNOT_EOK
+ * \retval KNOT_ERROR
+ * \retval KNOT_EOF
+ * \retval KNOT_ENOMEM
+ */
+int dt_reader_read(dt_reader_t *reader, Dnstap__Dnstap **d);
+
+/*!
+ * \brief free the frame allocated by dt_read_data.
+ *
+ * \param reader Dnstap reader context.
+ * \param d The frame to be freed.
+ */
+void dt_reader_free_frame(dt_reader_t *reader, Dnstap__Dnstap **d);
diff --git a/src/contrib/dnstap/writer.c b/src/contrib/dnstap/writer.c
new file mode 100644
index 0000000..08f4519
--- /dev/null
+++ b/src/contrib/dnstap/writer.c
@@ -0,0 +1,120 @@
+/* Copyright (C) 2014 Farsight Security, Inc. <software@farsightsecurity.com>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "libknot/errcode.h"
+
+#include "contrib/dnstap/dnstap.h"
+#include "contrib/dnstap/writer.h"
+
+dt_writer_t* dt_writer_create(const char *file_path, const char *version)
+{
+ struct fstrm_file_options *fopt = NULL;
+ struct fstrm_writer_options *wopt = NULL;
+ dt_writer_t *writer = NULL;
+ fstrm_res res;
+
+ writer = calloc(1, sizeof(dt_writer_t));
+ if (writer == NULL) {
+ goto fail;
+ }
+
+ // Set "version".
+ if (version != NULL) {
+ writer->len_version = strlen(version);
+ writer->version = strdup(version);
+ if (!writer->version) {
+ goto fail;
+ }
+ }
+
+ // Open writer.
+ fopt = fstrm_file_options_init();
+ fstrm_file_options_set_file_path(fopt, file_path);
+ wopt = fstrm_writer_options_init();
+ fstrm_writer_options_add_content_type(wopt,
+ (const uint8_t *) DNSTAP_CONTENT_TYPE,
+ strlen(DNSTAP_CONTENT_TYPE));
+ writer->fw = fstrm_file_writer_init(fopt, wopt);
+ fstrm_file_options_destroy(&fopt);
+ fstrm_writer_options_destroy(&wopt);
+ if (writer->fw == NULL) {
+ goto fail;
+ }
+
+ res = fstrm_writer_open(writer->fw);
+ if (res != fstrm_res_success) {
+ goto fail;
+ }
+
+ return writer;
+fail:
+ dt_writer_free(writer);
+ return NULL;
+}
+
+void dt_writer_free(dt_writer_t *writer)
+{
+ if (writer == NULL) {
+ return;
+ }
+
+ fstrm_writer_destroy(&writer->fw);
+ free(writer->version);
+ free(writer);
+}
+
+int dt_writer_write(dt_writer_t *writer, const ProtobufCMessage *msg)
+{
+ Dnstap__Dnstap dnstap = DNSTAP__DNSTAP__INIT;
+ size_t len;
+ uint8_t *data;
+
+ if (writer->fw == NULL) {
+ return KNOT_EOK;
+ }
+
+ // Only handle dnstap/Message.
+ assert(msg->descriptor == &dnstap__message__descriptor);
+
+ // Fill out 'dnstap'.
+ if (writer->version) {
+ dnstap.version.data = writer->version;
+ dnstap.version.len = writer->len_version;
+ dnstap.has_version = 1;
+ }
+ dnstap.type = DNSTAP__DNSTAP__TYPE__MESSAGE;
+ dnstap.message = (Dnstap__Message *)msg;
+
+ // Serialize the dnstap frame.
+ if (!dt_pack(&dnstap, &data, &len)) {
+ return KNOT_ENOMEM;
+ }
+
+ // Write the dnstap frame to the output stream.
+ if (fstrm_writer_write(writer->fw, data, len) != fstrm_res_success) {
+ return KNOT_ERROR;
+ }
+
+ // Cleanup.
+ free(data);
+
+ return KNOT_EOK;
+}
diff --git a/src/contrib/dnstap/writer.h b/src/contrib/dnstap/writer.h
new file mode 100644
index 0000000..4fe7326
--- /dev/null
+++ b/src/contrib/dnstap/writer.h
@@ -0,0 +1,70 @@
+/* Copyright (C) 2014 Farsight Security, Inc. <software@farsightsecurity.com>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*!
+ * \author Robert Edmonds <edmonds@fsi.io>
+ *
+ * \brief Dnstap file writer.
+ */
+
+#pragma once
+
+#include <fstrm.h>
+#include <protobuf-c/protobuf-c.h>
+
+/*! \brief Structure for dnstap file writer. */
+typedef struct {
+ /*!< Output writer. */
+ struct fstrm_writer *fw;
+
+ /*!< dnstap "version" field. */
+ void *version;
+
+ /*!< length of dnstap "version" field. */
+ size_t len_version;
+} dt_writer_t;
+
+/*!
+ * \brief Creates dnstap file writer structure.
+ *
+ * \param file_path Name of file to write output to.
+ * \param version Version string of software. May be NULL.
+ *
+ * \retval writer if success.
+ * \retval NULL if error.
+ */
+dt_writer_t* dt_writer_create(const char *file_path, const char *version);
+
+/*!
+ * \brief Finish writing dnstap file writer and free resources.
+ *
+ * \param writer dnstap file writer structure.
+ */
+void dt_writer_free(dt_writer_t *writer);
+
+/*!
+ * \brief Write a protobuf to the dnstap file writer.
+ *
+ * Supported protobuf types for the 'msg' parameter:
+ * \c Dnstap__Message
+ *
+ * \param writer dnstap file writer structure.
+ * \param msg dnstap protobuf. Must be a supported type.
+ *
+ * \retval KNOT_EOK
+ * \retval KNOT_EINVAL
+ * \retval KNOT_ENOMEM
+ */
+int dt_writer_write(dt_writer_t *writer, const ProtobufCMessage *msg);
diff --git a/src/contrib/dynarray.h b/src/contrib/dynarray.h
new file mode 100644
index 0000000..79eab31
--- /dev/null
+++ b/src/contrib/dynarray.h
@@ -0,0 +1,123 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*!
+ * \brief Simple write-once allocation-optimal dynamic array.
+ *
+ * Include it into your .c file
+ *
+ * prefix - identifier prefix, e.g. ptr -> struct ptr_dynarray, ptr_dynarray_add(), ...
+ * ntype - data type to be stored. Let it be a number, pointer or small struct
+ * initial_capacity - how many data items will be allocated on stac and copied with assignment
+ *
+ * prefix_dynarray_add() - add a data item
+ * prefix_dynarray_fix() - call EVERYTIME the array is copied from some already invalid stack
+ * prefix_dynarray_free() - call EVERYTIME you dismiss all copies of the array
+ *
+ */
+
+#include <stdlib.h>
+#include <assert.h>
+
+#pragma once
+
+#define DYNARRAY_VISIBILITY_STATIC static
+#define DYNARRAY_VISIBILITY_PUBLIC
+#define DYNARRAY_VISIBILITY_LIBRARY __public__
+
+#define dynarray_declare(prefix, ntype, visibility, initial_capacity) \
+ typedef struct prefix ## _dynarray { \
+ ssize_t capacity; \
+ ssize_t size; \
+ ntype *(*arr)(struct prefix ## _dynarray *dynarray); \
+ ntype init[initial_capacity]; \
+ ntype *_arr; \
+ } prefix ## _dynarray_t; \
+ \
+ visibility ntype *prefix ## _dynarray_arr(prefix ## _dynarray_t *dynarray); \
+ visibility void prefix ## _dynarray_add(prefix ## _dynarray_t *dynarray, \
+ ntype const *to_add); \
+ visibility void prefix ## _dynarray_free(prefix ## _dynarray_t *dynarray);
+
+#define dynarray_foreach(prefix, ntype, ptr, array) \
+ for (ntype *ptr = prefix ## _dynarray_arr(&(array)); \
+ ptr < prefix ## _dynarray_arr(&(array)) + (array).size; ptr++)
+
+#define dynarray_define(prefix, ntype, visibility) \
+ \
+ static void prefix ## _dynarray_free__(struct prefix ## _dynarray *dynarray) \
+ { \
+ if (dynarray->capacity > sizeof(dynarray->init) / sizeof(*dynarray->init)) { \
+ free(dynarray->_arr); \
+ } \
+ } \
+ \
+ __attribute__((unused)) \
+ visibility ntype *prefix ## _dynarray_arr(struct prefix ## _dynarray *dynarray) \
+ { \
+ assert(dynarray->size <= dynarray->capacity); \
+ return (dynarray->capacity <= sizeof(dynarray->init) / sizeof(*dynarray->init) ? \
+ dynarray->init : dynarray->_arr); \
+ } \
+ \
+ static ntype *prefix ## _dynarray_arr_init__(struct prefix ## _dynarray *dynarray) \
+ { \
+ assert(dynarray->capacity == sizeof(dynarray->init) / sizeof(*dynarray->init)); \
+ return dynarray->init; \
+ } \
+ \
+ static ntype *prefix ## _dynarray_arr_arr__(struct prefix ## _dynarray *dynarray) \
+ { \
+ assert(dynarray->capacity > sizeof(dynarray->init) / sizeof(*dynarray->init)); \
+ return dynarray->_arr; \
+ } \
+ \
+ __attribute__((unused)) \
+ visibility void prefix ## _dynarray_add(struct prefix ## _dynarray *dynarray, \
+ ntype const *to_add) \
+ { \
+ if (dynarray->capacity < 0) { \
+ return; \
+ } \
+ if (dynarray->capacity == 0) { \
+ dynarray->capacity = sizeof(dynarray->init) / sizeof(*dynarray->init); \
+ dynarray->arr = prefix ## _dynarray_arr_init__; \
+ } \
+ if (dynarray->size >= dynarray->capacity) { \
+ ssize_t new_capacity = dynarray->capacity * 2 + 1; \
+ ntype *new_arr = calloc(new_capacity, sizeof(ntype)); \
+ if (new_arr == NULL) { \
+ prefix ## _dynarray_free__(dynarray); \
+ dynarray->capacity = dynarray->size = -1; \
+ return; \
+ } \
+ if (dynarray->capacity > 0) { \
+ memcpy(new_arr, prefix ## _dynarray_arr(dynarray), \
+ dynarray->capacity * sizeof(ntype)); \
+ } \
+ prefix ## _dynarray_free__(dynarray); \
+ dynarray->_arr = new_arr; \
+ dynarray->capacity = new_capacity; \
+ dynarray->arr = prefix ## _dynarray_arr_arr__; \
+ } \
+ prefix ## _dynarray_arr(dynarray)[dynarray->size++] = *to_add; \
+ } \
+ \
+ __attribute__((unused)) \
+ visibility void prefix ## _dynarray_free(struct prefix ## _dynarray *dynarray) \
+ { \
+ prefix ## _dynarray_free__(dynarray); \
+ memset(dynarray, 0, sizeof(*dynarray)); \
+ }
diff --git a/src/contrib/files.c b/src/contrib/files.c
new file mode 100644
index 0000000..3181386
--- /dev/null
+++ b/src/contrib/files.c
@@ -0,0 +1,134 @@
+/* Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <assert.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <ftw.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "contrib/files.h"
+#include "contrib/string.h"
+#include "libknot/errcode.h"
+
+static int remove_file(const char *path, const struct stat *stat, int type, struct FTW *ftw)
+{
+ (void)stat;
+ (void)ftw;
+ if (type == FTW_DP) {
+ return rmdir(path);
+ } else {
+ return unlink(path);
+ }
+}
+
+bool remove_path(const char *path)
+{
+ return (0 == nftw(path, remove_file, 1, FTW_DEPTH | FTW_PHYS));
+}
+
+int make_dir(const char *path, mode_t mode, bool ignore_existing)
+{
+ if (mkdir(path, mode) == 0) {
+ return KNOT_EOK;
+ }
+
+ if (!ignore_existing || errno != EEXIST) {
+ return knot_map_errno();
+ }
+
+ assert(errno == EEXIST);
+
+ struct stat st = { 0 };
+ if (stat(path, &st) != 0) {
+ return knot_map_errno();
+ }
+
+ if (!S_ISDIR(st.st_mode)) {
+ return knot_map_errno_code(ENOTDIR);
+ }
+
+ return KNOT_EOK;
+}
+
+int make_path(const char *path, mode_t mode)
+{
+ if (path == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ char *dir = strdup(path);
+ if (dir == NULL) {
+ return KNOT_ENOMEM;
+ }
+
+ for (char *p = strchr(dir + 1, '/'); p != NULL; p = strchr(p + 1, '/')) {
+ *p = '\0';
+ if (mkdir(dir, mode) == -1 && errno != EEXIST) {
+ free(dir);
+ return knot_map_errno();
+ }
+ *p = '/';
+ }
+
+ free(dir);
+
+ return KNOT_EOK;
+}
+
+int open_tmp_file(const char *path, char **tmp_name, FILE **file, mode_t mode)
+{
+ int ret;
+
+ *tmp_name = sprintf_alloc("%s.XXXXXX", path);
+ if (*tmp_name == NULL) {
+ ret = KNOT_ENOMEM;
+ goto open_tmp_failed;
+ }
+
+ int fd = mkstemp(*tmp_name);
+ if (fd < 0) {
+ ret = knot_map_errno();
+ goto open_tmp_failed;
+ }
+
+ if (fchmod(fd, mode) != 0) {
+ ret = knot_map_errno();
+ close(fd);
+ unlink(*tmp_name);
+ goto open_tmp_failed;
+ }
+
+ *file = fdopen(fd, "w");
+ if (*file == NULL) {
+ ret = knot_map_errno();
+ close(fd);
+ unlink(*tmp_name);
+ goto open_tmp_failed;
+ }
+
+ return KNOT_EOK;
+open_tmp_failed:
+ free(*tmp_name);
+ *tmp_name = NULL;
+ *file = NULL;
+
+ assert(ret != KNOT_EOK);
+ return ret;
+}
diff --git a/src/contrib/files.h b/src/contrib/files.h
new file mode 100644
index 0000000..c4c681f
--- /dev/null
+++ b/src/contrib/files.h
@@ -0,0 +1,43 @@
+/* Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#pragma once
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <sys/types.h>
+
+/*!
+ * \brief Delete file or directory (recursive).
+ *
+ * \return true on success, false when one or more files failed to be removed.
+ */
+bool remove_path(const char *path);
+
+/*!
+ * Equivalent to mkdir(2), can succeed if the directory already exists.
+ */
+int make_dir(const char *path, mode_t mode, bool ignore_existing);
+
+/*!
+ * Makes a directory part of the path with all parent directories if not exist.
+ */
+int make_path(const char *path, mode_t mode);
+
+/*!
+ * Creates and opens for writing a temporary file based on given path.
+ */
+int open_tmp_file(const char *path, char **tmp_name, FILE **file, mode_t mode);
diff --git a/src/contrib/getline.c b/src/contrib/getline.c
new file mode 100644
index 0000000..074c511
--- /dev/null
+++ b/src/contrib/getline.c
@@ -0,0 +1,60 @@
+/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+// FreeBSD POSIX2008 getline
+#ifndef _WITH_GETLINE
+#define _WITH_GETLINE
+#endif
+
+#include "contrib/getline.h"
+
+#include <stdio.h> // getline or fgetln
+#include <stdlib.h> // free
+#include <string.h> // memcpy
+
+ssize_t knot_getline(char **lineptr, size_t *n, FILE *stream)
+{
+#ifdef HAVE_GETLINE
+ return getline(lineptr, n, stream);
+#else
+#ifdef HAVE_FGETLN
+ size_t length = 0;
+ char *buffer = fgetln(stream, &length);
+ if (buffer == NULL) {
+ return -1;
+ }
+
+ /* NOTE: Function fgetln doesn't return terminated string!
+ * Output buffer from the fgetln can't be freed.
+ */
+
+ // If the output buffer is not specified or is small, extend it.
+ if (*lineptr == NULL || *n <= length) {
+ char *tmp = realloc(*lineptr, length + 1);
+ if (tmp == NULL) {
+ return -1;
+ }
+ *lineptr = tmp;
+ *n = length + 1;
+ }
+
+ memcpy(*lineptr, buffer, length);
+ (*lineptr)[length] = '\0';
+
+ return length;
+#endif
+#endif
+}
diff --git a/src/contrib/getline.h b/src/contrib/getline.h
new file mode 100644
index 0000000..11c7fe3
--- /dev/null
+++ b/src/contrib/getline.h
@@ -0,0 +1,38 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*!
+ * \brief Multiplatform getline wrapper.
+ */
+
+#pragma once
+
+#include <stdio.h>
+#include <sys/types.h>
+
+/*!
+ * \brief Reads a line from a stream.
+ *
+ * This function has the same semantics as POSIX.1-2008 getline().
+ * If necessary, the output buffer will be allocated/reallocated.
+ *
+ * \param lineptr Output buffer.
+ * \param n Output buffer size.
+ * \param stream Input stream.
+ *
+ * \retval Number of characters read, including new line delimiter,
+ * not including terminating. -1 on error or EOF.
+ */
+ssize_t knot_getline(char **lineptr, size_t *n, FILE *stream);
diff --git a/src/contrib/licenses/0BSD b/src/contrib/licenses/0BSD
new file mode 100644
index 0000000..56c5528
--- /dev/null
+++ b/src/contrib/licenses/0BSD
@@ -0,0 +1,12 @@
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
diff --git a/src/contrib/licenses/BSD-3-Clause b/src/contrib/licenses/BSD-3-Clause
new file mode 100644
index 0000000..8041f21
--- /dev/null
+++ b/src/contrib/licenses/BSD-3-Clause
@@ -0,0 +1,21 @@
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+3. Neither the name of the copyright holder nor the names of its contributors
+ may be used to endorse or promote products derived from this software without
+ specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/src/contrib/licenses/LGPL-2.0 b/src/contrib/licenses/LGPL-2.0
new file mode 100644
index 0000000..d159169
--- /dev/null
+++ b/src/contrib/licenses/LGPL-2.0
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/src/contrib/licenses/OLDAP-2.8 b/src/contrib/licenses/OLDAP-2.8
new file mode 100644
index 0000000..05ad757
--- /dev/null
+++ b/src/contrib/licenses/OLDAP-2.8
@@ -0,0 +1,47 @@
+The OpenLDAP Public License
+ Version 2.8, 17 August 2003
+
+Redistribution and use of this software and associated documentation
+("Software"), with or without modification, are permitted provided
+that the following conditions are met:
+
+1. Redistributions in source form must retain copyright statements
+ and notices,
+
+2. Redistributions in binary form must reproduce applicable copyright
+ statements and notices, this list of conditions, and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution, and
+
+3. Redistributions must contain a verbatim copy of this document.
+
+The OpenLDAP Foundation may revise this license from time to time.
+Each revision is distinguished by a version number. You may use
+this Software under terms of this license revision or under the
+terms of any subsequent revision of the license.
+
+THIS SOFTWARE IS PROVIDED BY THE OPENLDAP FOUNDATION AND ITS
+CONTRIBUTORS ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
+INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+SHALL THE OPENLDAP FOUNDATION, ITS CONTRIBUTORS, OR THE AUTHOR(S)
+OR OWNER(S) OF THE SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+
+The names of the authors and copyright holders must not be used in
+advertising or otherwise to promote the sale, use or other dealing
+in this Software without specific, written prior permission. Title
+to copyright in this Software shall at all times remain with copyright
+holders.
+
+OpenLDAP is a registered trademark of the OpenLDAP Foundation.
+
+Copyright 1999-2003 The OpenLDAP Foundation, Redwood City,
+California, USA. All Rights Reserved. Permission to copy and
+distribute verbatim copies of this document is granted.
diff --git a/src/contrib/lmdb/LICENSE b/src/contrib/lmdb/LICENSE
new file mode 100644
index 0000000..66210f0
--- /dev/null
+++ b/src/contrib/lmdb/LICENSE
@@ -0,0 +1 @@
+../licenses/OLDAP-2.8 \ No newline at end of file
diff --git a/src/contrib/lmdb/lmdb.h b/src/contrib/lmdb/lmdb.h
new file mode 100644
index 0000000..32a278e
--- /dev/null
+++ b/src/contrib/lmdb/lmdb.h
@@ -0,0 +1,1604 @@
+/** @file lmdb.h
+ * @brief Lightning memory-mapped database library
+ *
+ * @mainpage Lightning Memory-Mapped Database Manager (LMDB)
+ *
+ * @section intro_sec Introduction
+ * LMDB is a Btree-based database management library modeled loosely on the
+ * BerkeleyDB API, but much simplified. The entire database is exposed
+ * in a memory map, and all data fetches return data directly
+ * from the mapped memory, so no malloc's or memcpy's occur during
+ * data fetches. As such, the library is extremely simple because it
+ * requires no page caching layer of its own, and it is extremely high
+ * performance and memory-efficient. It is also fully transactional with
+ * full ACID semantics, and when the memory map is read-only, the
+ * database integrity cannot be corrupted by stray pointer writes from
+ * application code.
+ *
+ * The library is fully thread-aware and supports concurrent read/write
+ * access from multiple processes and threads. Data pages use a copy-on-
+ * write strategy so no active data pages are ever overwritten, which
+ * also provides resistance to corruption and eliminates the need of any
+ * special recovery procedures after a system crash. Writes are fully
+ * serialized; only one write transaction may be active at a time, which
+ * guarantees that writers can never deadlock. The database structure is
+ * multi-versioned so readers run with no locks; writers cannot block
+ * readers, and readers don't block writers.
+ *
+ * Unlike other well-known database mechanisms which use either write-ahead
+ * transaction logs or append-only data writes, LMDB requires no maintenance
+ * during operation. Both write-ahead loggers and append-only databases
+ * require periodic checkpointing and/or compaction of their log or database
+ * files otherwise they grow without bound. LMDB tracks free pages within
+ * the database and re-uses them for new write operations, so the database
+ * size does not grow without bound in normal use.
+ *
+ * The memory map can be used as a read-only or read-write map. It is
+ * read-only by default as this provides total immunity to corruption.
+ * Using read-write mode offers much higher write performance, but adds
+ * the possibility for stray application writes thru pointers to silently
+ * corrupt the database. Of course if your application code is known to
+ * be bug-free (...) then this is not an issue.
+ *
+ * If this is your first time using a transactional embedded key/value
+ * store, you may find the \ref starting page to be helpful.
+ *
+ * @section caveats_sec Caveats
+ * Troubleshooting the lock file, plus semaphores on BSD systems:
+ *
+ * - A broken lockfile can cause sync issues.
+ * Stale reader transactions left behind by an aborted program
+ * cause further writes to grow the database quickly, and
+ * stale locks can block further operation.
+ *
+ * Fix: Check for stale readers periodically, using the
+ * #mdb_reader_check function or the \ref mdb_stat_1 "mdb_stat" tool.
+ * Stale writers will be cleared automatically on some systems:
+ * - Windows - automatic
+ * - Linux, systems using POSIX mutexes with Robust option - automatic
+ * - not on BSD, systems using POSIX semaphores.
+ * Otherwise just make all programs using the database close it;
+ * the lockfile is always reset on first open of the environment.
+ *
+ * - On BSD systems or others configured with MDB_USE_POSIX_SEM,
+ * startup can fail due to semaphores owned by another userid.
+ *
+ * Fix: Open and close the database as the user which owns the
+ * semaphores (likely last user) or as root, while no other
+ * process is using the database.
+ *
+ * Restrictions/caveats (in addition to those listed for some functions):
+ *
+ * - Only the database owner should normally use the database on
+ * BSD systems or when otherwise configured with MDB_USE_POSIX_SEM.
+ * Multiple users can cause startup to fail later, as noted above.
+ *
+ * - There is normally no pure read-only mode, since readers need write
+ * access to locks and lock file. Exceptions: On read-only filesystems
+ * or with the #MDB_NOLOCK flag described under #mdb_env_open().
+ *
+ * - An LMDB configuration will often reserve considerable \b unused
+ * memory address space and maybe file size for future growth.
+ * This does not use actual memory or disk space, but users may need
+ * to understand the difference so they won't be scared off.
+ *
+ * - By default, in versions before 0.9.10, unused portions of the data
+ * file might receive garbage data from memory freed by other code.
+ * (This does not happen when using the #MDB_WRITEMAP flag.) As of
+ * 0.9.10 the default behavior is to initialize such memory before
+ * writing to the data file. Since there may be a slight performance
+ * cost due to this initialization, applications may disable it using
+ * the #MDB_NOMEMINIT flag. Applications handling sensitive data
+ * which must not be written should not use this flag. This flag is
+ * irrelevant when using #MDB_WRITEMAP.
+ *
+ * - A thread can only use one transaction at a time, plus any child
+ * transactions. Each transaction belongs to one thread. See below.
+ * The #MDB_NOTLS flag changes this for read-only transactions.
+ *
+ * - Use an MDB_env* in the process which opened it, not after fork().
+ *
+ * - Do not have open an LMDB database twice in the same process at
+ * the same time. Not even from a plain open() call - close()ing it
+ * breaks fcntl() advisory locking. (It is OK to reopen it after
+ * fork() - exec*(), since the lockfile has FD_CLOEXEC set.)
+ *
+ * - Avoid long-lived transactions. Read transactions prevent
+ * reuse of pages freed by newer write transactions, thus the
+ * database can grow quickly. Write transactions prevent
+ * other write transactions, since writes are serialized.
+ *
+ * - Avoid suspending a process with active transactions. These
+ * would then be "long-lived" as above. Also read transactions
+ * suspended when writers commit could sometimes see wrong data.
+ *
+ * ...when several processes can use a database concurrently:
+ *
+ * - Avoid aborting a process with an active transaction.
+ * The transaction becomes "long-lived" as above until a check
+ * for stale readers is performed or the lockfile is reset,
+ * since the process may not remove it from the lockfile.
+ *
+ * This does not apply to write transactions if the system clears
+ * stale writers, see above.
+ *
+ * - If you do that anyway, do a periodic check for stale readers. Or
+ * close the environment once in a while, so the lockfile can get reset.
+ *
+ * - Do not use LMDB databases on remote filesystems, even between
+ * processes on the same host. This breaks flock() on some OSes,
+ * possibly memory map sync, and certainly sync between programs
+ * on different hosts.
+ *
+ * - Opening a database can fail if another process is opening or
+ * closing it at exactly the same time.
+ *
+ * @author Howard Chu, Symas Corporation.
+ *
+ * @copyright Copyright 2011-2018 Howard Chu, Symas Corp. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ *
+ * @par Derived From:
+ * This code is derived from btree.c written by Martin Hedenfalk.
+ *
+ * Copyright (c) 2009, 2010 Martin Hedenfalk <martin@bzero.se>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifndef _LMDB_H_
+#define _LMDB_H_
+
+#include <sys/types.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** Unix permissions for creating files, or dummy definition for Windows */
+#ifdef _MSC_VER
+typedef int mdb_mode_t;
+#else
+typedef mode_t mdb_mode_t;
+#endif
+
+/** An abstraction for a file handle.
+ * On POSIX systems file handles are small integers. On Windows
+ * they're opaque pointers.
+ */
+#ifdef _WIN32
+typedef void *mdb_filehandle_t;
+#else
+typedef int mdb_filehandle_t;
+#endif
+
+/** @defgroup mdb LMDB API
+ * @{
+ * @brief OpenLDAP Lightning Memory-Mapped Database Manager
+ */
+/** @defgroup Version Version Macros
+ * @{
+ */
+/** Library major version */
+#define MDB_VERSION_MAJOR 0
+/** Library minor version */
+#define MDB_VERSION_MINOR 9
+/** Library patch version */
+#define MDB_VERSION_PATCH 22
+
+/** Combine args a,b,c into a single integer for easy version comparisons */
+#define MDB_VERINT(a,b,c) (((a) << 24) | ((b) << 16) | (c))
+
+/** The full library version as a single integer */
+#define MDB_VERSION_FULL \
+ MDB_VERINT(MDB_VERSION_MAJOR,MDB_VERSION_MINOR,MDB_VERSION_PATCH)
+
+/** The release date of this library version */
+#define MDB_VERSION_DATE "March 21, 2018"
+
+/** A stringifier for the version info */
+#define MDB_VERSTR(a,b,c,d) "LMDB " #a "." #b "." #c ": (" d ")"
+
+/** A helper for the stringifier macro */
+#define MDB_VERFOO(a,b,c,d) MDB_VERSTR(a,b,c,d)
+
+/** The full library version as a C string */
+#define MDB_VERSION_STRING \
+ MDB_VERFOO(MDB_VERSION_MAJOR,MDB_VERSION_MINOR,MDB_VERSION_PATCH,MDB_VERSION_DATE)
+/** @} */
+
+/** @brief Opaque structure for a database environment.
+ *
+ * A DB environment supports multiple databases, all residing in the same
+ * shared-memory map.
+ */
+typedef struct MDB_env MDB_env;
+
+/** @brief Opaque structure for a transaction handle.
+ *
+ * All database operations require a transaction handle. Transactions may be
+ * read-only or read-write.
+ */
+typedef struct MDB_txn MDB_txn;
+
+/** @brief A handle for an individual database in the DB environment. */
+typedef unsigned int MDB_dbi;
+
+/** @brief Opaque structure for navigating through a database */
+typedef struct MDB_cursor MDB_cursor;
+
+/** @brief Generic structure used for passing keys and data in and out
+ * of the database.
+ *
+ * Values returned from the database are valid only until a subsequent
+ * update operation, or the end of the transaction. Do not modify or
+ * free them, they commonly point into the database itself.
+ *
+ * Key sizes must be between 1 and #mdb_env_get_maxkeysize() inclusive.
+ * The same applies to data sizes in databases with the #MDB_DUPSORT flag.
+ * Other data items can in theory be from 0 to 0xffffffff bytes long.
+ */
+typedef struct MDB_val {
+ size_t mv_size; /**< size of the data item */
+ void *mv_data; /**< address of the data item */
+} MDB_val;
+
+/** @brief A callback function used to compare two keys in a database */
+typedef int (MDB_cmp_func)(const MDB_val *a, const MDB_val *b);
+
+/** @brief A callback function used to relocate a position-dependent data item
+ * in a fixed-address database.
+ *
+ * The \b newptr gives the item's desired address in
+ * the memory map, and \b oldptr gives its previous address. The item's actual
+ * data resides at the address in \b item. This callback is expected to walk
+ * through the fields of the record in \b item and modify any
+ * values based at the \b oldptr address to be relative to the \b newptr address.
+ * @param[in,out] item The item that is to be relocated.
+ * @param[in] oldptr The previous address.
+ * @param[in] newptr The new address to relocate to.
+ * @param[in] relctx An application-provided context, set by #mdb_set_relctx().
+ * @todo This feature is currently unimplemented.
+ */
+typedef void (MDB_rel_func)(MDB_val *item, void *oldptr, void *newptr, void *relctx);
+
+/** @defgroup mdb_env Environment Flags
+ * @{
+ */
+ /** mmap at a fixed address (experimental) */
+#define MDB_FIXEDMAP 0x01
+ /** no environment directory */
+#define MDB_NOSUBDIR 0x4000
+ /** don't fsync after commit */
+#define MDB_NOSYNC 0x10000
+ /** read only */
+#define MDB_RDONLY 0x20000
+ /** don't fsync metapage after commit */
+#define MDB_NOMETASYNC 0x40000
+ /** use writable mmap */
+#define MDB_WRITEMAP 0x80000
+ /** use asynchronous msync when #MDB_WRITEMAP is used */
+#define MDB_MAPASYNC 0x100000
+ /** tie reader locktable slots to #MDB_txn objects instead of to threads */
+#define MDB_NOTLS 0x200000
+ /** don't do any locking, caller must manage their own locks */
+#define MDB_NOLOCK 0x400000
+ /** don't do readahead (no effect on Windows) */
+#define MDB_NORDAHEAD 0x800000
+ /** don't initialize malloc'd memory before writing to datafile */
+#define MDB_NOMEMINIT 0x1000000
+/** @} */
+
+/** @defgroup mdb_dbi_open Database Flags
+ * @{
+ */
+ /** use reverse string keys */
+#define MDB_REVERSEKEY 0x02
+ /** use sorted duplicates */
+#define MDB_DUPSORT 0x04
+ /** numeric keys in native byte order: either unsigned int or size_t.
+ * The keys must all be of the same size. */
+#define MDB_INTEGERKEY 0x08
+ /** with #MDB_DUPSORT, sorted dup items have fixed size */
+#define MDB_DUPFIXED 0x10
+ /** with #MDB_DUPSORT, dups are #MDB_INTEGERKEY-style integers */
+#define MDB_INTEGERDUP 0x20
+ /** with #MDB_DUPSORT, use reverse string dups */
+#define MDB_REVERSEDUP 0x40
+ /** create DB if not already existing */
+#define MDB_CREATE 0x40000
+/** @} */
+
+/** @defgroup mdb_put Write Flags
+ * @{
+ */
+/** For put: Don't write if the key already exists. */
+#define MDB_NOOVERWRITE 0x10
+/** Only for #MDB_DUPSORT<br>
+ * For put: don't write if the key and data pair already exist.<br>
+ * For mdb_cursor_del: remove all duplicate data items.
+ */
+#define MDB_NODUPDATA 0x20
+/** For mdb_cursor_put: overwrite the current key/data pair */
+#define MDB_CURRENT 0x40
+/** For put: Just reserve space for data, don't copy it. Return a
+ * pointer to the reserved space.
+ */
+#define MDB_RESERVE 0x10000
+/** Data is being appended, don't split full pages. */
+#define MDB_APPEND 0x20000
+/** Duplicate data is being appended, don't split full pages. */
+#define MDB_APPENDDUP 0x40000
+/** Store multiple data items in one call. Only for #MDB_DUPFIXED. */
+#define MDB_MULTIPLE 0x80000
+/* @} */
+
+/** @defgroup mdb_copy Copy Flags
+ * @{
+ */
+/** Compacting copy: Omit free space from copy, and renumber all
+ * pages sequentially.
+ */
+#define MDB_CP_COMPACT 0x01
+/* @} */
+
+/** @brief Cursor Get operations.
+ *
+ * This is the set of all operations for retrieving data
+ * using a cursor.
+ */
+typedef enum MDB_cursor_op {
+ MDB_FIRST, /**< Position at first key/data item */
+ MDB_FIRST_DUP, /**< Position at first data item of current key.
+ Only for #MDB_DUPSORT */
+ MDB_GET_BOTH, /**< Position at key/data pair. Only for #MDB_DUPSORT */
+ MDB_GET_BOTH_RANGE, /**< position at key, nearest data. Only for #MDB_DUPSORT */
+ MDB_GET_CURRENT, /**< Return key/data at current cursor position */
+ MDB_GET_MULTIPLE, /**< Return key and up to a page of duplicate data items
+ from current cursor position. Move cursor to prepare
+ for #MDB_NEXT_MULTIPLE. Only for #MDB_DUPFIXED */
+ MDB_LAST, /**< Position at last key/data item */
+ MDB_LAST_DUP, /**< Position at last data item of current key.
+ Only for #MDB_DUPSORT */
+ MDB_NEXT, /**< Position at next data item */
+ MDB_NEXT_DUP, /**< Position at next data item of current key.
+ Only for #MDB_DUPSORT */
+ MDB_NEXT_MULTIPLE, /**< Return key and up to a page of duplicate data items
+ from next cursor position. Move cursor to prepare
+ for #MDB_NEXT_MULTIPLE. Only for #MDB_DUPFIXED */
+ MDB_NEXT_NODUP, /**< Position at first data item of next key */
+ MDB_PREV, /**< Position at previous data item */
+ MDB_PREV_DUP, /**< Position at previous data item of current key.
+ Only for #MDB_DUPSORT */
+ MDB_PREV_NODUP, /**< Position at last data item of previous key */
+ MDB_SET, /**< Position at specified key */
+ MDB_SET_KEY, /**< Position at specified key, return key + data */
+ MDB_SET_RANGE, /**< Position at first key greater than or equal to specified key. */
+ MDB_PREV_MULTIPLE /**< Position at previous page and return key and up to
+ a page of duplicate data items. Only for #MDB_DUPFIXED */
+} MDB_cursor_op;
+
+/** @defgroup errors Return Codes
+ *
+ * BerkeleyDB uses -30800 to -30999, we'll go under them
+ * @{
+ */
+ /** Successful result */
+#define MDB_SUCCESS 0
+ /** key/data pair already exists */
+#define MDB_KEYEXIST (-30799)
+ /** key/data pair not found (EOF) */
+#define MDB_NOTFOUND (-30798)
+ /** Requested page not found - this usually indicates corruption */
+#define MDB_PAGE_NOTFOUND (-30797)
+ /** Located page was wrong type */
+#define MDB_CORRUPTED (-30796)
+ /** Update of meta page failed or environment had fatal error */
+#define MDB_PANIC (-30795)
+ /** Environment version mismatch */
+#define MDB_VERSION_MISMATCH (-30794)
+ /** File is not a valid LMDB file */
+#define MDB_INVALID (-30793)
+ /** Environment mapsize reached */
+#define MDB_MAP_FULL (-30792)
+ /** Environment maxdbs reached */
+#define MDB_DBS_FULL (-30791)
+ /** Environment maxreaders reached */
+#define MDB_READERS_FULL (-30790)
+ /** Too many TLS keys in use - Windows only */
+#define MDB_TLS_FULL (-30789)
+ /** Txn has too many dirty pages */
+#define MDB_TXN_FULL (-30788)
+ /** Cursor stack too deep - internal error */
+#define MDB_CURSOR_FULL (-30787)
+ /** Page has not enough space - internal error */
+#define MDB_PAGE_FULL (-30786)
+ /** Database contents grew beyond environment mapsize */
+#define MDB_MAP_RESIZED (-30785)
+ /** Operation and DB incompatible, or DB type changed. This can mean:
+ * <ul>
+ * <li>The operation expects an #MDB_DUPSORT / #MDB_DUPFIXED database.
+ * <li>Opening a named DB when the unnamed DB has #MDB_DUPSORT / #MDB_INTEGERKEY.
+ * <li>Accessing a data record as a database, or vice versa.
+ * <li>The database was dropped and recreated with different flags.
+ * </ul>
+ */
+#define MDB_INCOMPATIBLE (-30784)
+ /** Invalid reuse of reader locktable slot */
+#define MDB_BAD_RSLOT (-30783)
+ /** Transaction must abort, has a child, or is invalid */
+#define MDB_BAD_TXN (-30782)
+ /** Unsupported size of key/DB name/data, or wrong DUPFIXED size */
+#define MDB_BAD_VALSIZE (-30781)
+ /** The specified DBI was changed unexpectedly */
+#define MDB_BAD_DBI (-30780)
+ /** The last defined error code */
+#define MDB_LAST_ERRCODE MDB_BAD_DBI
+/** @} */
+
+/** @brief Statistics for a database in the environment */
+typedef struct MDB_stat {
+ unsigned int ms_psize; /**< Size of a database page.
+ This is currently the same for all databases. */
+ unsigned int ms_depth; /**< Depth (height) of the B-tree */
+ size_t ms_branch_pages; /**< Number of internal (non-leaf) pages */
+ size_t ms_leaf_pages; /**< Number of leaf pages */
+ size_t ms_overflow_pages; /**< Number of overflow pages */
+ size_t ms_entries; /**< Number of data items */
+} MDB_stat;
+
+/** @brief Information about the environment */
+typedef struct MDB_envinfo {
+ void *me_mapaddr; /**< Address of map, if fixed */
+ size_t me_mapsize; /**< Size of the data memory map */
+ size_t me_last_pgno; /**< ID of the last used page */
+ size_t me_last_txnid; /**< ID of the last committed transaction */
+ unsigned int me_maxreaders; /**< max reader slots in the environment */
+ unsigned int me_numreaders; /**< max reader slots used in the environment */
+} MDB_envinfo;
+
+ /** @brief Return the LMDB library version information.
+ *
+ * @param[out] major if non-NULL, the library major version number is copied here
+ * @param[out] minor if non-NULL, the library minor version number is copied here
+ * @param[out] patch if non-NULL, the library patch version number is copied here
+ * @retval "version string" The library version as a string
+ */
+char *mdb_version(int *major, int *minor, int *patch);
+
+ /** @brief Return a string describing a given error code.
+ *
+ * This function is a superset of the ANSI C X3.159-1989 (ANSI C) strerror(3)
+ * function. If the error code is greater than or equal to 0, then the string
+ * returned by the system function strerror(3) is returned. If the error code
+ * is less than 0, an error string corresponding to the LMDB library error is
+ * returned. See @ref errors for a list of LMDB-specific error codes.
+ * @param[in] err The error code
+ * @retval "error message" The description of the error
+ */
+char *mdb_strerror(int err);
+
+ /** @brief Create an LMDB environment handle.
+ *
+ * This function allocates memory for a #MDB_env structure. To release
+ * the allocated memory and discard the handle, call #mdb_env_close().
+ * Before the handle may be used, it must be opened using #mdb_env_open().
+ * Various other options may also need to be set before opening the handle,
+ * e.g. #mdb_env_set_mapsize(), #mdb_env_set_maxreaders(), #mdb_env_set_maxdbs(),
+ * depending on usage requirements.
+ * @param[out] env The address where the new handle will be stored
+ * @return A non-zero error value on failure and 0 on success.
+ */
+int mdb_env_create(MDB_env **env);
+
+ /** @brief Open an environment handle.
+ *
+ * If this function fails, #mdb_env_close() must be called to discard the #MDB_env handle.
+ * @param[in] env An environment handle returned by #mdb_env_create()
+ * @param[in] path The directory in which the database files reside. This
+ * directory must already exist and be writable.
+ * @param[in] flags Special options for this environment. This parameter
+ * must be set to 0 or by bitwise OR'ing together one or more of the
+ * values described here.
+ * Flags set by mdb_env_set_flags() are also used.
+ * <ul>
+ * <li>#MDB_FIXEDMAP
+ * use a fixed address for the mmap region. This flag must be specified
+ * when creating the environment, and is stored persistently in the environment.
+ * If successful, the memory map will always reside at the same virtual address
+ * and pointers used to reference data items in the database will be constant
+ * across multiple invocations. This option may not always work, depending on
+ * how the operating system has allocated memory to shared libraries and other uses.
+ * The feature is highly experimental.
+ * <li>#MDB_NOSUBDIR
+ * By default, LMDB creates its environment in a directory whose
+ * pathname is given in \b path, and creates its data and lock files
+ * under that directory. With this option, \b path is used as-is for
+ * the database main data file. The database lock file is the \b path
+ * with "-lock" appended.
+ * <li>#MDB_RDONLY
+ * Open the environment in read-only mode. No write operations will be
+ * allowed. LMDB will still modify the lock file - except on read-only
+ * filesystems, where LMDB does not use locks.
+ * <li>#MDB_WRITEMAP
+ * Use a writeable memory map unless MDB_RDONLY is set. This uses
+ * fewer mallocs but loses protection from application bugs
+ * like wild pointer writes and other bad updates into the database.
+ * This may be slightly faster for DBs that fit entirely in RAM, but
+ * is slower for DBs larger than RAM.
+ * Incompatible with nested transactions.
+ * Do not mix processes with and without MDB_WRITEMAP on the same
+ * environment. This can defeat durability (#mdb_env_sync etc).
+ * <li>#MDB_NOMETASYNC
+ * Flush system buffers to disk only once per transaction, omit the
+ * metadata flush. Defer that until the system flushes files to disk,
+ * or next non-MDB_RDONLY commit or #mdb_env_sync(). This optimization
+ * maintains database integrity, but a system crash may undo the last
+ * committed transaction. I.e. it preserves the ACI (atomicity,
+ * consistency, isolation) but not D (durability) database property.
+ * This flag may be changed at any time using #mdb_env_set_flags().
+ * <li>#MDB_NOSYNC
+ * Don't flush system buffers to disk when committing a transaction.
+ * This optimization means a system crash can corrupt the database or
+ * lose the last transactions if buffers are not yet flushed to disk.
+ * The risk is governed by how often the system flushes dirty buffers
+ * to disk and how often #mdb_env_sync() is called. However, if the
+ * filesystem preserves write order and the #MDB_WRITEMAP flag is not
+ * used, transactions exhibit ACI (atomicity, consistency, isolation)
+ * properties and only lose D (durability). I.e. database integrity
+ * is maintained, but a system crash may undo the final transactions.
+ * Note that (#MDB_NOSYNC | #MDB_WRITEMAP) leaves the system with no
+ * hint for when to write transactions to disk, unless #mdb_env_sync()
+ * is called. (#MDB_MAPASYNC | #MDB_WRITEMAP) may be preferable.
+ * This flag may be changed at any time using #mdb_env_set_flags().
+ * <li>#MDB_MAPASYNC
+ * When using #MDB_WRITEMAP, use asynchronous flushes to disk.
+ * As with #MDB_NOSYNC, a system crash can then corrupt the
+ * database or lose the last transactions. Calling #mdb_env_sync()
+ * ensures on-disk database integrity until next commit.
+ * This flag may be changed at any time using #mdb_env_set_flags().
+ * <li>#MDB_NOTLS
+ * Don't use Thread-Local Storage. Tie reader locktable slots to
+ * #MDB_txn objects instead of to threads. I.e. #mdb_txn_reset() keeps
+ * the slot reseved for the #MDB_txn object. A thread may use parallel
+ * read-only transactions. A read-only transaction may span threads if
+ * the user synchronizes its use. Applications that multiplex many
+ * user threads over individual OS threads need this option. Such an
+ * application must also serialize the write transactions in an OS
+ * thread, since LMDB's write locking is unaware of the user threads.
+ * <li>#MDB_NOLOCK
+ * Don't do any locking. If concurrent access is anticipated, the
+ * caller must manage all concurrency itself. For proper operation
+ * the caller must enforce single-writer semantics, and must ensure
+ * that no readers are using old transactions while a writer is
+ * active. The simplest approach is to use an exclusive lock so that
+ * no readers may be active at all when a writer begins.
+ * <li>#MDB_NORDAHEAD
+ * Turn off readahead. Most operating systems perform readahead on
+ * read requests by default. This option turns it off if the OS
+ * supports it. Turning it off may help random read performance
+ * when the DB is larger than RAM and system RAM is full.
+ * The option is not implemented on Windows.
+ * <li>#MDB_NOMEMINIT
+ * Don't initialize malloc'd memory before writing to unused spaces
+ * in the data file. By default, memory for pages written to the data
+ * file is obtained using malloc. While these pages may be reused in
+ * subsequent transactions, freshly malloc'd pages will be initialized
+ * to zeroes before use. This avoids persisting leftover data from other
+ * code (that used the heap and subsequently freed the memory) into the
+ * data file. Note that many other system libraries may allocate
+ * and free memory from the heap for arbitrary uses. E.g., stdio may
+ * use the heap for file I/O buffers. This initialization step has a
+ * modest performance cost so some applications may want to disable
+ * it using this flag. This option can be a problem for applications
+ * which handle sensitive data like passwords, and it makes memory
+ * checkers like Valgrind noisy. This flag is not needed with #MDB_WRITEMAP,
+ * which writes directly to the mmap instead of using malloc for pages. The
+ * initialization is also skipped if #MDB_RESERVE is used; the
+ * caller is expected to overwrite all of the memory that was
+ * reserved in that case.
+ * This flag may be changed at any time using #mdb_env_set_flags().
+ * </ul>
+ * @param[in] mode The UNIX permissions to set on created files and semaphores.
+ * This parameter is ignored on Windows.
+ * @return A non-zero error value on failure and 0 on success. Some possible
+ * errors are:
+ * <ul>
+ * <li>#MDB_VERSION_MISMATCH - the version of the LMDB library doesn't match the
+ * version that created the database environment.
+ * <li>#MDB_INVALID - the environment file headers are corrupted.
+ * <li>ENOENT - the directory specified by the path parameter doesn't exist.
+ * <li>EACCES - the user didn't have permission to access the environment files.
+ * <li>EAGAIN - the environment was locked by another process.
+ * </ul>
+ */
+int mdb_env_open(MDB_env *env, const char *path, unsigned int flags, mdb_mode_t mode);
+
+ /** @brief Copy an LMDB environment to the specified path.
+ *
+ * This function may be used to make a backup of an existing environment.
+ * No lockfile is created, since it gets recreated at need.
+ * @note This call can trigger significant file size growth if run in
+ * parallel with write transactions, because it employs a read-only
+ * transaction. See long-lived transactions under @ref caveats_sec.
+ * @param[in] env An environment handle returned by #mdb_env_create(). It
+ * must have already been opened successfully.
+ * @param[in] path The directory in which the copy will reside. This
+ * directory must already exist and be writable but must otherwise be
+ * empty.
+ * @return A non-zero error value on failure and 0 on success.
+ */
+int mdb_env_copy(MDB_env *env, const char *path);
+
+ /** @brief Copy an LMDB environment to the specified file descriptor.
+ *
+ * This function may be used to make a backup of an existing environment.
+ * No lockfile is created, since it gets recreated at need.
+ * @note This call can trigger significant file size growth if run in
+ * parallel with write transactions, because it employs a read-only
+ * transaction. See long-lived transactions under @ref caveats_sec.
+ * @param[in] env An environment handle returned by #mdb_env_create(). It
+ * must have already been opened successfully.
+ * @param[in] fd The filedescriptor to write the copy to. It must
+ * have already been opened for Write access.
+ * @return A non-zero error value on failure and 0 on success.
+ */
+int mdb_env_copyfd(MDB_env *env, mdb_filehandle_t fd);
+
+ /** @brief Copy an LMDB environment to the specified path, with options.
+ *
+ * This function may be used to make a backup of an existing environment.
+ * No lockfile is created, since it gets recreated at need.
+ * @note This call can trigger significant file size growth if run in
+ * parallel with write transactions, because it employs a read-only
+ * transaction. See long-lived transactions under @ref caveats_sec.
+ * @param[in] env An environment handle returned by #mdb_env_create(). It
+ * must have already been opened successfully.
+ * @param[in] path The directory in which the copy will reside. This
+ * directory must already exist and be writable but must otherwise be
+ * empty.
+ * @param[in] flags Special options for this operation. This parameter
+ * must be set to 0 or by bitwise OR'ing together one or more of the
+ * values described here.
+ * <ul>
+ * <li>#MDB_CP_COMPACT - Perform compaction while copying: omit free
+ * pages and sequentially renumber all pages in output. This option
+ * consumes more CPU and runs more slowly than the default.
+ * Currently it fails if the environment has suffered a page leak.
+ * </ul>
+ * @return A non-zero error value on failure and 0 on success.
+ */
+int mdb_env_copy2(MDB_env *env, const char *path, unsigned int flags);
+
+ /** @brief Copy an LMDB environment to the specified file descriptor,
+ * with options.
+ *
+ * This function may be used to make a backup of an existing environment.
+ * No lockfile is created, since it gets recreated at need. See
+ * #mdb_env_copy2() for further details.
+ * @note This call can trigger significant file size growth if run in
+ * parallel with write transactions, because it employs a read-only
+ * transaction. See long-lived transactions under @ref caveats_sec.
+ * @param[in] env An environment handle returned by #mdb_env_create(). It
+ * must have already been opened successfully.
+ * @param[in] fd The filedescriptor to write the copy to. It must
+ * have already been opened for Write access.
+ * @param[in] flags Special options for this operation.
+ * See #mdb_env_copy2() for options.
+ * @return A non-zero error value on failure and 0 on success.
+ */
+int mdb_env_copyfd2(MDB_env *env, mdb_filehandle_t fd, unsigned int flags);
+
+ /** @brief Return statistics about the LMDB environment.
+ *
+ * @param[in] env An environment handle returned by #mdb_env_create()
+ * @param[out] stat The address of an #MDB_stat structure
+ * where the statistics will be copied
+ */
+int mdb_env_stat(MDB_env *env, MDB_stat *stat);
+
+ /** @brief Return information about the LMDB environment.
+ *
+ * @param[in] env An environment handle returned by #mdb_env_create()
+ * @param[out] stat The address of an #MDB_envinfo structure
+ * where the information will be copied
+ */
+int mdb_env_info(MDB_env *env, MDB_envinfo *stat);
+
+ /** @brief Flush the data buffers to disk.
+ *
+ * Data is always written to disk when #mdb_txn_commit() is called,
+ * but the operating system may keep it buffered. LMDB always flushes
+ * the OS buffers upon commit as well, unless the environment was
+ * opened with #MDB_NOSYNC or in part #MDB_NOMETASYNC. This call is
+ * not valid if the environment was opened with #MDB_RDONLY.
+ * @param[in] env An environment handle returned by #mdb_env_create()
+ * @param[in] force If non-zero, force a synchronous flush. Otherwise
+ * if the environment has the #MDB_NOSYNC flag set the flushes
+ * will be omitted, and with #MDB_MAPASYNC they will be asynchronous.
+ * @return A non-zero error value on failure and 0 on success. Some possible
+ * errors are:
+ * <ul>
+ * <li>EACCES - the environment is read-only.
+ * <li>EINVAL - an invalid parameter was specified.
+ * <li>EIO - an error occurred during synchronization.
+ * </ul>
+ */
+int mdb_env_sync(MDB_env *env, int force);
+
+ /** @brief Close the environment and release the memory map.
+ *
+ * Only a single thread may call this function. All transactions, databases,
+ * and cursors must already be closed before calling this function. Attempts to
+ * use any such handles after calling this function will cause a SIGSEGV.
+ * The environment handle will be freed and must not be used again after this call.
+ * @param[in] env An environment handle returned by #mdb_env_create()
+ */
+void mdb_env_close(MDB_env *env);
+
+ /** @brief Set environment flags.
+ *
+ * This may be used to set some flags in addition to those from
+ * #mdb_env_open(), or to unset these flags. If several threads
+ * change the flags at the same time, the result is undefined.
+ * @param[in] env An environment handle returned by #mdb_env_create()
+ * @param[in] flags The flags to change, bitwise OR'ed together
+ * @param[in] onoff A non-zero value sets the flags, zero clears them.
+ * @return A non-zero error value on failure and 0 on success. Some possible
+ * errors are:
+ * <ul>
+ * <li>EINVAL - an invalid parameter was specified.
+ * </ul>
+ */
+int mdb_env_set_flags(MDB_env *env, unsigned int flags, int onoff);
+
+ /** @brief Get environment flags.
+ *
+ * @param[in] env An environment handle returned by #mdb_env_create()
+ * @param[out] flags The address of an integer to store the flags
+ * @return A non-zero error value on failure and 0 on success. Some possible
+ * errors are:
+ * <ul>
+ * <li>EINVAL - an invalid parameter was specified.
+ * </ul>
+ */
+int mdb_env_get_flags(MDB_env *env, unsigned int *flags);
+
+ /** @brief Return the path that was used in #mdb_env_open().
+ *
+ * @param[in] env An environment handle returned by #mdb_env_create()
+ * @param[out] path Address of a string pointer to contain the path. This
+ * is the actual string in the environment, not a copy. It should not be
+ * altered in any way.
+ * @return A non-zero error value on failure and 0 on success. Some possible
+ * errors are:
+ * <ul>
+ * <li>EINVAL - an invalid parameter was specified.
+ * </ul>
+ */
+int mdb_env_get_path(MDB_env *env, const char **path);
+
+ /** @brief Return the filedescriptor for the given environment.
+ *
+ * This function may be called after fork(), so the descriptor can be
+ * closed before exec*(). Other LMDB file descriptors have FD_CLOEXEC.
+ * (Until LMDB 0.9.18, only the lockfile had that.)
+ *
+ * @param[in] env An environment handle returned by #mdb_env_create()
+ * @param[out] fd Address of a mdb_filehandle_t to contain the descriptor.
+ * @return A non-zero error value on failure and 0 on success. Some possible
+ * errors are:
+ * <ul>
+ * <li>EINVAL - an invalid parameter was specified.
+ * </ul>
+ */
+int mdb_env_get_fd(MDB_env *env, mdb_filehandle_t *fd);
+
+ /** @brief Set the size of the memory map to use for this environment.
+ *
+ * The size should be a multiple of the OS page size. The default is
+ * 10485760 bytes. The size of the memory map is also the maximum size
+ * of the database. The value should be chosen as large as possible,
+ * to accommodate future growth of the database.
+ * This function should be called after #mdb_env_create() and before #mdb_env_open().
+ * It may be called at later times if no transactions are active in
+ * this process. Note that the library does not check for this condition,
+ * the caller must ensure it explicitly.
+ *
+ * The new size takes effect immediately for the current process but
+ * will not be persisted to any others until a write transaction has been
+ * committed by the current process. Also, only mapsize increases are
+ * persisted into the environment.
+ *
+ * If the mapsize is increased by another process, and data has grown
+ * beyond the range of the current mapsize, #mdb_txn_begin() will
+ * return #MDB_MAP_RESIZED. This function may be called with a size
+ * of zero to adopt the new size.
+ *
+ * Any attempt to set a size smaller than the space already consumed
+ * by the environment will be silently changed to the current size of the used space.
+ * @param[in] env An environment handle returned by #mdb_env_create()
+ * @param[in] size The size in bytes
+ * @return A non-zero error value on failure and 0 on success. Some possible
+ * errors are:
+ * <ul>
+ * <li>EINVAL - an invalid parameter was specified, or the environment has
+ * an active write transaction.
+ * </ul>
+ */
+int mdb_env_set_mapsize(MDB_env *env, size_t size);
+
+ /** @brief Set the maximum number of threads/reader slots for the environment.
+ *
+ * This defines the number of slots in the lock table that is used to track readers in the
+ * the environment. The default is 126.
+ * Starting a read-only transaction normally ties a lock table slot to the
+ * current thread until the environment closes or the thread exits. If
+ * MDB_NOTLS is in use, #mdb_txn_begin() instead ties the slot to the
+ * MDB_txn object until it or the #MDB_env object is destroyed.
+ * This function may only be called after #mdb_env_create() and before #mdb_env_open().
+ * @param[in] env An environment handle returned by #mdb_env_create()
+ * @param[in] readers The maximum number of reader lock table slots
+ * @return A non-zero error value on failure and 0 on success. Some possible
+ * errors are:
+ * <ul>
+ * <li>EINVAL - an invalid parameter was specified, or the environment is already open.
+ * </ul>
+ */
+int mdb_env_set_maxreaders(MDB_env *env, unsigned int readers);
+
+ /** @brief Get the maximum number of threads/reader slots for the environment.
+ *
+ * @param[in] env An environment handle returned by #mdb_env_create()
+ * @param[out] readers Address of an integer to store the number of readers
+ * @return A non-zero error value on failure and 0 on success. Some possible
+ * errors are:
+ * <ul>
+ * <li>EINVAL - an invalid parameter was specified.
+ * </ul>
+ */
+int mdb_env_get_maxreaders(MDB_env *env, unsigned int *readers);
+
+ /** @brief Set the maximum number of named databases for the environment.
+ *
+ * This function is only needed if multiple databases will be used in the
+ * environment. Simpler applications that use the environment as a single
+ * unnamed database can ignore this option.
+ * This function may only be called after #mdb_env_create() and before #mdb_env_open().
+ *
+ * Currently a moderate number of slots are cheap but a huge number gets
+ * expensive: 7-120 words per transaction, and every #mdb_dbi_open()
+ * does a linear search of the opened slots.
+ * @param[in] env An environment handle returned by #mdb_env_create()
+ * @param[in] dbs The maximum number of databases
+ * @return A non-zero error value on failure and 0 on success. Some possible
+ * errors are:
+ * <ul>
+ * <li>EINVAL - an invalid parameter was specified, or the environment is already open.
+ * </ul>
+ */
+int mdb_env_set_maxdbs(MDB_env *env, MDB_dbi dbs);
+
+ /** @brief Get the maximum size of keys and #MDB_DUPSORT data we can write.
+ *
+ * Depends on the compile-time constant #MDB_MAXKEYSIZE. Default 511.
+ * See @ref MDB_val.
+ * @param[in] env An environment handle returned by #mdb_env_create()
+ * @return The maximum size of a key we can write
+ */
+int mdb_env_get_maxkeysize(MDB_env *env);
+
+ /** @brief Set application information associated with the #MDB_env.
+ *
+ * @param[in] env An environment handle returned by #mdb_env_create()
+ * @param[in] ctx An arbitrary pointer for whatever the application needs.
+ * @return A non-zero error value on failure and 0 on success.
+ */
+int mdb_env_set_userctx(MDB_env *env, void *ctx);
+
+ /** @brief Get the application information associated with the #MDB_env.
+ *
+ * @param[in] env An environment handle returned by #mdb_env_create()
+ * @return The pointer set by #mdb_env_set_userctx().
+ */
+void *mdb_env_get_userctx(MDB_env *env);
+
+ /** @brief A callback function for most LMDB assert() failures,
+ * called before printing the message and aborting.
+ *
+ * @param[in] env An environment handle returned by #mdb_env_create().
+ * @param[in] msg The assertion message, not including newline.
+ */
+typedef void MDB_assert_func(MDB_env *env, const char *msg);
+
+ /** Set or reset the assert() callback of the environment.
+ * Disabled if liblmdb is buillt with NDEBUG.
+ * @note This hack should become obsolete as lmdb's error handling matures.
+ * @param[in] env An environment handle returned by #mdb_env_create().
+ * @param[in] func An #MDB_assert_func function, or 0.
+ * @return A non-zero error value on failure and 0 on success.
+ */
+int mdb_env_set_assert(MDB_env *env, MDB_assert_func *func);
+
+ /** @brief Create a transaction for use with the environment.
+ *
+ * The transaction handle may be discarded using #mdb_txn_abort() or #mdb_txn_commit().
+ * @note A transaction and its cursors must only be used by a single
+ * thread, and a thread may only have a single transaction at a time.
+ * If #MDB_NOTLS is in use, this does not apply to read-only transactions.
+ * @note Cursors may not span transactions.
+ * @param[in] env An environment handle returned by #mdb_env_create()
+ * @param[in] parent If this parameter is non-NULL, the new transaction
+ * will be a nested transaction, with the transaction indicated by \b parent
+ * as its parent. Transactions may be nested to any level. A parent
+ * transaction and its cursors may not issue any other operations than
+ * mdb_txn_commit and mdb_txn_abort while it has active child transactions.
+ * @param[in] flags Special options for this transaction. This parameter
+ * must be set to 0 or by bitwise OR'ing together one or more of the
+ * values described here.
+ * <ul>
+ * <li>#MDB_RDONLY
+ * This transaction will not perform any write operations.
+ * </ul>
+ * @param[out] txn Address where the new #MDB_txn handle will be stored
+ * @return A non-zero error value on failure and 0 on success. Some possible
+ * errors are:
+ * <ul>
+ * <li>#MDB_PANIC - a fatal error occurred earlier and the environment
+ * must be shut down.
+ * <li>#MDB_MAP_RESIZED - another process wrote data beyond this MDB_env's
+ * mapsize and this environment's map must be resized as well.
+ * See #mdb_env_set_mapsize().
+ * <li>#MDB_READERS_FULL - a read-only transaction was requested and
+ * the reader lock table is full. See #mdb_env_set_maxreaders().
+ * <li>ENOMEM - out of memory.
+ * </ul>
+ */
+int mdb_txn_begin(MDB_env *env, MDB_txn *parent, unsigned int flags, MDB_txn **txn);
+
+ /** @brief Returns the transaction's #MDB_env
+ *
+ * @param[in] txn A transaction handle returned by #mdb_txn_begin()
+ */
+MDB_env *mdb_txn_env(MDB_txn *txn);
+
+ /** @brief Return the transaction's ID.
+ *
+ * This returns the identifier associated with this transaction. For a
+ * read-only transaction, this corresponds to the snapshot being read;
+ * concurrent readers will frequently have the same transaction ID.
+ *
+ * @param[in] txn A transaction handle returned by #mdb_txn_begin()
+ * @return A transaction ID, valid if input is an active transaction.
+ */
+size_t mdb_txn_id(MDB_txn *txn);
+
+ /** @brief Commit all the operations of a transaction into the database.
+ *
+ * The transaction handle is freed. It and its cursors must not be used
+ * again after this call, except with #mdb_cursor_renew().
+ * @note Earlier documentation incorrectly said all cursors would be freed.
+ * Only write-transactions free cursors.
+ * @param[in] txn A transaction handle returned by #mdb_txn_begin()
+ * @return A non-zero error value on failure and 0 on success. Some possible
+ * errors are:
+ * <ul>
+ * <li>EINVAL - an invalid parameter was specified.
+ * <li>ENOSPC - no more disk space.
+ * <li>EIO - a low-level I/O error occurred while writing.
+ * <li>ENOMEM - out of memory.
+ * </ul>
+ */
+int mdb_txn_commit(MDB_txn *txn);
+
+ /** @brief Abandon all the operations of the transaction instead of saving them.
+ *
+ * The transaction handle is freed. It and its cursors must not be used
+ * again after this call, except with #mdb_cursor_renew().
+ * @note Earlier documentation incorrectly said all cursors would be freed.
+ * Only write-transactions free cursors.
+ * @param[in] txn A transaction handle returned by #mdb_txn_begin()
+ */
+void mdb_txn_abort(MDB_txn *txn);
+
+ /** @brief Reset a read-only transaction.
+ *
+ * Abort the transaction like #mdb_txn_abort(), but keep the transaction
+ * handle. #mdb_txn_renew() may reuse the handle. This saves allocation
+ * overhead if the process will start a new read-only transaction soon,
+ * and also locking overhead if #MDB_NOTLS is in use. The reader table
+ * lock is released, but the table slot stays tied to its thread or
+ * #MDB_txn. Use mdb_txn_abort() to discard a reset handle, and to free
+ * its lock table slot if MDB_NOTLS is in use.
+ * Cursors opened within the transaction must not be used
+ * again after this call, except with #mdb_cursor_renew().
+ * Reader locks generally don't interfere with writers, but they keep old
+ * versions of database pages allocated. Thus they prevent the old pages
+ * from being reused when writers commit new data, and so under heavy load
+ * the database size may grow much more rapidly than otherwise.
+ * @param[in] txn A transaction handle returned by #mdb_txn_begin()
+ */
+void mdb_txn_reset(MDB_txn *txn);
+
+ /** @brief Renew a read-only transaction.
+ *
+ * This acquires a new reader lock for a transaction handle that had been
+ * released by #mdb_txn_reset(). It must be called before a reset transaction
+ * may be used again.
+ * @param[in] txn A transaction handle returned by #mdb_txn_begin()
+ * @return A non-zero error value on failure and 0 on success. Some possible
+ * errors are:
+ * <ul>
+ * <li>#MDB_PANIC - a fatal error occurred earlier and the environment
+ * must be shut down.
+ * <li>EINVAL - an invalid parameter was specified.
+ * </ul>
+ */
+int mdb_txn_renew(MDB_txn *txn);
+
+/** Compat with version <= 0.9.4, avoid clash with libmdb from MDB Tools project */
+#define mdb_open(txn,name,flags,dbi) mdb_dbi_open(txn,name,flags,dbi)
+/** Compat with version <= 0.9.4, avoid clash with libmdb from MDB Tools project */
+#define mdb_close(env,dbi) mdb_dbi_close(env,dbi)
+
+ /** @brief Open a database in the environment.
+ *
+ * A database handle denotes the name and parameters of a database,
+ * independently of whether such a database exists.
+ * The database handle may be discarded by calling #mdb_dbi_close().
+ * The old database handle is returned if the database was already open.
+ * The handle may only be closed once.
+ *
+ * The database handle will be private to the current transaction until
+ * the transaction is successfully committed. If the transaction is
+ * aborted the handle will be closed automatically.
+ * After a successful commit the handle will reside in the shared
+ * environment, and may be used by other transactions.
+ *
+ * This function must not be called from multiple concurrent
+ * transactions in the same process. A transaction that uses
+ * this function must finish (either commit or abort) before
+ * any other transaction in the process may use this function.
+ *
+ * To use named databases (with name != NULL), #mdb_env_set_maxdbs()
+ * must be called before opening the environment. Database names are
+ * keys in the unnamed database, and may be read but not written.
+ *
+ * @param[in] txn A transaction handle returned by #mdb_txn_begin()
+ * @param[in] name The name of the database to open. If only a single
+ * database is needed in the environment, this value may be NULL.
+ * @param[in] flags Special options for this database. This parameter
+ * must be set to 0 or by bitwise OR'ing together one or more of the
+ * values described here.
+ * <ul>
+ * <li>#MDB_REVERSEKEY
+ * Keys are strings to be compared in reverse order, from the end
+ * of the strings to the beginning. By default, Keys are treated as strings and
+ * compared from beginning to end.
+ * <li>#MDB_DUPSORT
+ * Duplicate keys may be used in the database. (Or, from another perspective,
+ * keys may have multiple data items, stored in sorted order.) By default
+ * keys must be unique and may have only a single data item.
+ * <li>#MDB_INTEGERKEY
+ * Keys are binary integers in native byte order, either unsigned int
+ * or size_t, and will be sorted as such.
+ * The keys must all be of the same size.
+ * <li>#MDB_DUPFIXED
+ * This flag may only be used in combination with #MDB_DUPSORT. This option
+ * tells the library that the data items for this database are all the same
+ * size, which allows further optimizations in storage and retrieval. When
+ * all data items are the same size, the #MDB_GET_MULTIPLE, #MDB_NEXT_MULTIPLE
+ * and #MDB_PREV_MULTIPLE cursor operations may be used to retrieve multiple
+ * items at once.
+ * <li>#MDB_INTEGERDUP
+ * This option specifies that duplicate data items are binary integers,
+ * similar to #MDB_INTEGERKEY keys.
+ * <li>#MDB_REVERSEDUP
+ * This option specifies that duplicate data items should be compared as
+ * strings in reverse order.
+ * <li>#MDB_CREATE
+ * Create the named database if it doesn't exist. This option is not
+ * allowed in a read-only transaction or a read-only environment.
+ * </ul>
+ * @param[out] dbi Address where the new #MDB_dbi handle will be stored
+ * @return A non-zero error value on failure and 0 on success. Some possible
+ * errors are:
+ * <ul>
+ * <li>#MDB_NOTFOUND - the specified database doesn't exist in the environment
+ * and #MDB_CREATE was not specified.
+ * <li>#MDB_DBS_FULL - too many databases have been opened. See #mdb_env_set_maxdbs().
+ * </ul>
+ */
+int mdb_dbi_open(MDB_txn *txn, const char *name, unsigned int flags, MDB_dbi *dbi);
+
+ /** @brief Retrieve statistics for a database.
+ *
+ * @param[in] txn A transaction handle returned by #mdb_txn_begin()
+ * @param[in] dbi A database handle returned by #mdb_dbi_open()
+ * @param[out] stat The address of an #MDB_stat structure
+ * where the statistics will be copied
+ * @return A non-zero error value on failure and 0 on success. Some possible
+ * errors are:
+ * <ul>
+ * <li>EINVAL - an invalid parameter was specified.
+ * </ul>
+ */
+int mdb_stat(MDB_txn *txn, MDB_dbi dbi, MDB_stat *stat);
+
+ /** @brief Retrieve the DB flags for a database handle.
+ *
+ * @param[in] txn A transaction handle returned by #mdb_txn_begin()
+ * @param[in] dbi A database handle returned by #mdb_dbi_open()
+ * @param[out] flags Address where the flags will be returned.
+ * @return A non-zero error value on failure and 0 on success.
+ */
+int mdb_dbi_flags(MDB_txn *txn, MDB_dbi dbi, unsigned int *flags);
+
+ /** @brief Close a database handle. Normally unnecessary. Use with care:
+ *
+ * This call is not mutex protected. Handles should only be closed by
+ * a single thread, and only if no other threads are going to reference
+ * the database handle or one of its cursors any further. Do not close
+ * a handle if an existing transaction has modified its database.
+ * Doing so can cause misbehavior from database corruption to errors
+ * like MDB_BAD_VALSIZE (since the DB name is gone).
+ *
+ * Closing a database handle is not necessary, but lets #mdb_dbi_open()
+ * reuse the handle value. Usually it's better to set a bigger
+ * #mdb_env_set_maxdbs(), unless that value would be large.
+ *
+ * @param[in] env An environment handle returned by #mdb_env_create()
+ * @param[in] dbi A database handle returned by #mdb_dbi_open()
+ */
+void mdb_dbi_close(MDB_env *env, MDB_dbi dbi);
+
+ /** @brief Empty or delete+close a database.
+ *
+ * See #mdb_dbi_close() for restrictions about closing the DB handle.
+ * @param[in] txn A transaction handle returned by #mdb_txn_begin()
+ * @param[in] dbi A database handle returned by #mdb_dbi_open()
+ * @param[in] del 0 to empty the DB, 1 to delete it from the
+ * environment and close the DB handle.
+ * @return A non-zero error value on failure and 0 on success.
+ */
+int mdb_drop(MDB_txn *txn, MDB_dbi dbi, int del);
+
+ /** @brief Set a custom key comparison function for a database.
+ *
+ * The comparison function is called whenever it is necessary to compare a
+ * key specified by the application with a key currently stored in the database.
+ * If no comparison function is specified, and no special key flags were specified
+ * with #mdb_dbi_open(), the keys are compared lexically, with shorter keys collating
+ * before longer keys.
+ * @warning This function must be called before any data access functions are used,
+ * otherwise data corruption may occur. The same comparison function must be used by every
+ * program accessing the database, every time the database is used.
+ * @param[in] txn A transaction handle returned by #mdb_txn_begin()
+ * @param[in] dbi A database handle returned by #mdb_dbi_open()
+ * @param[in] cmp A #MDB_cmp_func function
+ * @return A non-zero error value on failure and 0 on success. Some possible
+ * errors are:
+ * <ul>
+ * <li>EINVAL - an invalid parameter was specified.
+ * </ul>
+ */
+int mdb_set_compare(MDB_txn *txn, MDB_dbi dbi, MDB_cmp_func *cmp);
+
+ /** @brief Set a custom data comparison function for a #MDB_DUPSORT database.
+ *
+ * This comparison function is called whenever it is necessary to compare a data
+ * item specified by the application with a data item currently stored in the database.
+ * This function only takes effect if the database was opened with the #MDB_DUPSORT
+ * flag.
+ * If no comparison function is specified, and no special key flags were specified
+ * with #mdb_dbi_open(), the data items are compared lexically, with shorter items collating
+ * before longer items.
+ * @warning This function must be called before any data access functions are used,
+ * otherwise data corruption may occur. The same comparison function must be used by every
+ * program accessing the database, every time the database is used.
+ * @param[in] txn A transaction handle returned by #mdb_txn_begin()
+ * @param[in] dbi A database handle returned by #mdb_dbi_open()
+ * @param[in] cmp A #MDB_cmp_func function
+ * @return A non-zero error value on failure and 0 on success. Some possible
+ * errors are:
+ * <ul>
+ * <li>EINVAL - an invalid parameter was specified.
+ * </ul>
+ */
+int mdb_set_dupsort(MDB_txn *txn, MDB_dbi dbi, MDB_cmp_func *cmp);
+
+ /** @brief Set a relocation function for a #MDB_FIXEDMAP database.
+ *
+ * @todo The relocation function is called whenever it is necessary to move the data
+ * of an item to a different position in the database (e.g. through tree
+ * balancing operations, shifts as a result of adds or deletes, etc.). It is
+ * intended to allow address/position-dependent data items to be stored in
+ * a database in an environment opened with the #MDB_FIXEDMAP option.
+ * Currently the relocation feature is unimplemented and setting
+ * this function has no effect.
+ * @param[in] txn A transaction handle returned by #mdb_txn_begin()
+ * @param[in] dbi A database handle returned by #mdb_dbi_open()
+ * @param[in] rel A #MDB_rel_func function
+ * @return A non-zero error value on failure and 0 on success. Some possible
+ * errors are:
+ * <ul>
+ * <li>EINVAL - an invalid parameter was specified.
+ * </ul>
+ */
+int mdb_set_relfunc(MDB_txn *txn, MDB_dbi dbi, MDB_rel_func *rel);
+
+ /** @brief Set a context pointer for a #MDB_FIXEDMAP database's relocation function.
+ *
+ * See #mdb_set_relfunc and #MDB_rel_func for more details.
+ * @param[in] txn A transaction handle returned by #mdb_txn_begin()
+ * @param[in] dbi A database handle returned by #mdb_dbi_open()
+ * @param[in] ctx An arbitrary pointer for whatever the application needs.
+ * It will be passed to the callback function set by #mdb_set_relfunc
+ * as its \b relctx parameter whenever the callback is invoked.
+ * @return A non-zero error value on failure and 0 on success. Some possible
+ * errors are:
+ * <ul>
+ * <li>EINVAL - an invalid parameter was specified.
+ * </ul>
+ */
+int mdb_set_relctx(MDB_txn *txn, MDB_dbi dbi, void *ctx);
+
+ /** @brief Get items from a database.
+ *
+ * This function retrieves key/data pairs from the database. The address
+ * and length of the data associated with the specified \b key are returned
+ * in the structure to which \b data refers.
+ * If the database supports duplicate keys (#MDB_DUPSORT) then the
+ * first data item for the key will be returned. Retrieval of other
+ * items requires the use of #mdb_cursor_get().
+ *
+ * @note The memory pointed to by the returned values is owned by the
+ * database. The caller need not dispose of the memory, and may not
+ * modify it in any way. For values returned in a read-only transaction
+ * any modification attempts will cause a SIGSEGV.
+ * @note Values returned from the database are valid only until a
+ * subsequent update operation, or the end of the transaction.
+ * @param[in] txn A transaction handle returned by #mdb_txn_begin()
+ * @param[in] dbi A database handle returned by #mdb_dbi_open()
+ * @param[in] key The key to search for in the database
+ * @param[out] data The data corresponding to the key
+ * @return A non-zero error value on failure and 0 on success. Some possible
+ * errors are:
+ * <ul>
+ * <li>#MDB_NOTFOUND - the key was not in the database.
+ * <li>EINVAL - an invalid parameter was specified.
+ * </ul>
+ */
+int mdb_get(MDB_txn *txn, MDB_dbi dbi, MDB_val *key, MDB_val *data);
+
+ /** @brief Store items into a database.
+ *
+ * This function stores key/data pairs in the database. The default behavior
+ * is to enter the new key/data pair, replacing any previously existing key
+ * if duplicates are disallowed, or adding a duplicate data item if
+ * duplicates are allowed (#MDB_DUPSORT).
+ * @param[in] txn A transaction handle returned by #mdb_txn_begin()
+ * @param[in] dbi A database handle returned by #mdb_dbi_open()
+ * @param[in] key The key to store in the database
+ * @param[in,out] data The data to store
+ * @param[in] flags Special options for this operation. This parameter
+ * must be set to 0 or by bitwise OR'ing together one or more of the
+ * values described here.
+ * <ul>
+ * <li>#MDB_NODUPDATA - enter the new key/data pair only if it does not
+ * already appear in the database. This flag may only be specified
+ * if the database was opened with #MDB_DUPSORT. The function will
+ * return #MDB_KEYEXIST if the key/data pair already appears in the
+ * database.
+ * <li>#MDB_NOOVERWRITE - enter the new key/data pair only if the key
+ * does not already appear in the database. The function will return
+ * #MDB_KEYEXIST if the key already appears in the database, even if
+ * the database supports duplicates (#MDB_DUPSORT). The \b data
+ * parameter will be set to point to the existing item.
+ * <li>#MDB_RESERVE - reserve space for data of the given size, but
+ * don't copy the given data. Instead, return a pointer to the
+ * reserved space, which the caller can fill in later - before
+ * the next update operation or the transaction ends. This saves
+ * an extra memcpy if the data is being generated later.
+ * LMDB does nothing else with this memory, the caller is expected
+ * to modify all of the space requested. This flag must not be
+ * specified if the database was opened with #MDB_DUPSORT.
+ * <li>#MDB_APPEND - append the given key/data pair to the end of the
+ * database. This option allows fast bulk loading when keys are
+ * already known to be in the correct order. Loading unsorted keys
+ * with this flag will cause a #MDB_KEYEXIST error.
+ * <li>#MDB_APPENDDUP - as above, but for sorted dup data.
+ * </ul>
+ * @return A non-zero error value on failure and 0 on success. Some possible
+ * errors are:
+ * <ul>
+ * <li>#MDB_MAP_FULL - the database is full, see #mdb_env_set_mapsize().
+ * <li>#MDB_TXN_FULL - the transaction has too many dirty pages.
+ * <li>EACCES - an attempt was made to write in a read-only transaction.
+ * <li>EINVAL - an invalid parameter was specified.
+ * </ul>
+ */
+int mdb_put(MDB_txn *txn, MDB_dbi dbi, MDB_val *key, MDB_val *data,
+ unsigned int flags);
+
+ /** @brief Delete items from a database.
+ *
+ * This function removes key/data pairs from the database.
+ * If the database does not support sorted duplicate data items
+ * (#MDB_DUPSORT) the data parameter is ignored.
+ * If the database supports sorted duplicates and the data parameter
+ * is NULL, all of the duplicate data items for the key will be
+ * deleted. Otherwise, if the data parameter is non-NULL
+ * only the matching data item will be deleted.
+ * This function will return #MDB_NOTFOUND if the specified key/data
+ * pair is not in the database.
+ * @param[in] txn A transaction handle returned by #mdb_txn_begin()
+ * @param[in] dbi A database handle returned by #mdb_dbi_open()
+ * @param[in] key The key to delete from the database
+ * @param[in] data The data to delete
+ * @return A non-zero error value on failure and 0 on success. Some possible
+ * errors are:
+ * <ul>
+ * <li>EACCES - an attempt was made to write in a read-only transaction.
+ * <li>EINVAL - an invalid parameter was specified.
+ * </ul>
+ */
+int mdb_del(MDB_txn *txn, MDB_dbi dbi, MDB_val *key, MDB_val *data);
+
+ /** @brief Create a cursor handle.
+ *
+ * A cursor is associated with a specific transaction and database.
+ * A cursor cannot be used when its database handle is closed. Nor
+ * when its transaction has ended, except with #mdb_cursor_renew().
+ * It can be discarded with #mdb_cursor_close().
+ * A cursor in a write-transaction can be closed before its transaction
+ * ends, and will otherwise be closed when its transaction ends.
+ * A cursor in a read-only transaction must be closed explicitly, before
+ * or after its transaction ends. It can be reused with
+ * #mdb_cursor_renew() before finally closing it.
+ * @note Earlier documentation said that cursors in every transaction
+ * were closed when the transaction committed or aborted.
+ * @param[in] txn A transaction handle returned by #mdb_txn_begin()
+ * @param[in] dbi A database handle returned by #mdb_dbi_open()
+ * @param[out] cursor Address where the new #MDB_cursor handle will be stored
+ * @return A non-zero error value on failure and 0 on success. Some possible
+ * errors are:
+ * <ul>
+ * <li>EINVAL - an invalid parameter was specified.
+ * </ul>
+ */
+int mdb_cursor_open(MDB_txn *txn, MDB_dbi dbi, MDB_cursor **cursor);
+
+ /** @brief Close a cursor handle.
+ *
+ * The cursor handle will be freed and must not be used again after this call.
+ * Its transaction must still be live if it is a write-transaction.
+ * @param[in] cursor A cursor handle returned by #mdb_cursor_open()
+ */
+void mdb_cursor_close(MDB_cursor *cursor);
+
+ /** @brief Renew a cursor handle.
+ *
+ * A cursor is associated with a specific transaction and database.
+ * Cursors that are only used in read-only
+ * transactions may be re-used, to avoid unnecessary malloc/free overhead.
+ * The cursor may be associated with a new read-only transaction, and
+ * referencing the same database handle as it was created with.
+ * This may be done whether the previous transaction is live or dead.
+ * @param[in] txn A transaction handle returned by #mdb_txn_begin()
+ * @param[in] cursor A cursor handle returned by #mdb_cursor_open()
+ * @return A non-zero error value on failure and 0 on success. Some possible
+ * errors are:
+ * <ul>
+ * <li>EINVAL - an invalid parameter was specified.
+ * </ul>
+ */
+int mdb_cursor_renew(MDB_txn *txn, MDB_cursor *cursor);
+
+ /** @brief Return the cursor's transaction handle.
+ *
+ * @param[in] cursor A cursor handle returned by #mdb_cursor_open()
+ */
+MDB_txn *mdb_cursor_txn(MDB_cursor *cursor);
+
+ /** @brief Return the cursor's database handle.
+ *
+ * @param[in] cursor A cursor handle returned by #mdb_cursor_open()
+ */
+MDB_dbi mdb_cursor_dbi(MDB_cursor *cursor);
+
+ /** @brief Retrieve by cursor.
+ *
+ * This function retrieves key/data pairs from the database. The address and length
+ * of the key are returned in the object to which \b key refers (except for the
+ * case of the #MDB_SET option, in which the \b key object is unchanged), and
+ * the address and length of the data are returned in the object to which \b data
+ * refers.
+ * See #mdb_get() for restrictions on using the output values.
+ * @param[in] cursor A cursor handle returned by #mdb_cursor_open()
+ * @param[in,out] key The key for a retrieved item
+ * @param[in,out] data The data of a retrieved item
+ * @param[in] op A cursor operation #MDB_cursor_op
+ * @return A non-zero error value on failure and 0 on success. Some possible
+ * errors are:
+ * <ul>
+ * <li>#MDB_NOTFOUND - no matching key found.
+ * <li>EINVAL - an invalid parameter was specified.
+ * </ul>
+ */
+int mdb_cursor_get(MDB_cursor *cursor, MDB_val *key, MDB_val *data,
+ MDB_cursor_op op);
+
+ /** @brief Store by cursor.
+ *
+ * This function stores key/data pairs into the database.
+ * The cursor is positioned at the new item, or on failure usually near it.
+ * @note Earlier documentation incorrectly said errors would leave the
+ * state of the cursor unchanged.
+ * @param[in] cursor A cursor handle returned by #mdb_cursor_open()
+ * @param[in] key The key operated on.
+ * @param[in] data The data operated on.
+ * @param[in] flags Options for this operation. This parameter
+ * must be set to 0 or one of the values described here.
+ * <ul>
+ * <li>#MDB_CURRENT - replace the item at the current cursor position.
+ * The \b key parameter must still be provided, and must match it.
+ * If using sorted duplicates (#MDB_DUPSORT) the data item must still
+ * sort into the same place. This is intended to be used when the
+ * new data is the same size as the old. Otherwise it will simply
+ * perform a delete of the old record followed by an insert.
+ * <li>#MDB_NODUPDATA - enter the new key/data pair only if it does not
+ * already appear in the database. This flag may only be specified
+ * if the database was opened with #MDB_DUPSORT. The function will
+ * return #MDB_KEYEXIST if the key/data pair already appears in the
+ * database.
+ * <li>#MDB_NOOVERWRITE - enter the new key/data pair only if the key
+ * does not already appear in the database. The function will return
+ * #MDB_KEYEXIST if the key already appears in the database, even if
+ * the database supports duplicates (#MDB_DUPSORT).
+ * <li>#MDB_RESERVE - reserve space for data of the given size, but
+ * don't copy the given data. Instead, return a pointer to the
+ * reserved space, which the caller can fill in later - before
+ * the next update operation or the transaction ends. This saves
+ * an extra memcpy if the data is being generated later. This flag
+ * must not be specified if the database was opened with #MDB_DUPSORT.
+ * <li>#MDB_APPEND - append the given key/data pair to the end of the
+ * database. No key comparisons are performed. This option allows
+ * fast bulk loading when keys are already known to be in the
+ * correct order. Loading unsorted keys with this flag will cause
+ * a #MDB_KEYEXIST error.
+ * <li>#MDB_APPENDDUP - as above, but for sorted dup data.
+ * <li>#MDB_MULTIPLE - store multiple contiguous data elements in a
+ * single request. This flag may only be specified if the database
+ * was opened with #MDB_DUPFIXED. The \b data argument must be an
+ * array of two MDB_vals. The mv_size of the first MDB_val must be
+ * the size of a single data element. The mv_data of the first MDB_val
+ * must point to the beginning of the array of contiguous data elements.
+ * The mv_size of the second MDB_val must be the count of the number
+ * of data elements to store. On return this field will be set to
+ * the count of the number of elements actually written. The mv_data
+ * of the second MDB_val is unused.
+ * </ul>
+ * @return A non-zero error value on failure and 0 on success. Some possible
+ * errors are:
+ * <ul>
+ * <li>#MDB_MAP_FULL - the database is full, see #mdb_env_set_mapsize().
+ * <li>#MDB_TXN_FULL - the transaction has too many dirty pages.
+ * <li>EACCES - an attempt was made to write in a read-only transaction.
+ * <li>EINVAL - an invalid parameter was specified.
+ * </ul>
+ */
+int mdb_cursor_put(MDB_cursor *cursor, MDB_val *key, MDB_val *data,
+ unsigned int flags);
+
+ /** @brief Delete current key/data pair
+ *
+ * This function deletes the key/data pair to which the cursor refers.
+ * @param[in] cursor A cursor handle returned by #mdb_cursor_open()
+ * @param[in] flags Options for this operation. This parameter
+ * must be set to 0 or one of the values described here.
+ * <ul>
+ * <li>#MDB_NODUPDATA - delete all of the data items for the current key.
+ * This flag may only be specified if the database was opened with #MDB_DUPSORT.
+ * </ul>
+ * @return A non-zero error value on failure and 0 on success. Some possible
+ * errors are:
+ * <ul>
+ * <li>EACCES - an attempt was made to write in a read-only transaction.
+ * <li>EINVAL - an invalid parameter was specified.
+ * </ul>
+ */
+int mdb_cursor_del(MDB_cursor *cursor, unsigned int flags);
+
+ /** @brief Return count of duplicates for current key.
+ *
+ * This call is only valid on databases that support sorted duplicate
+ * data items #MDB_DUPSORT.
+ * @param[in] cursor A cursor handle returned by #mdb_cursor_open()
+ * @param[out] countp Address where the count will be stored
+ * @return A non-zero error value on failure and 0 on success. Some possible
+ * errors are:
+ * <ul>
+ * <li>EINVAL - cursor is not initialized, or an invalid parameter was specified.
+ * </ul>
+ */
+int mdb_cursor_count(MDB_cursor *cursor, size_t *countp);
+
+ /** @brief Compare two data items according to a particular database.
+ *
+ * This returns a comparison as if the two data items were keys in the
+ * specified database.
+ * @param[in] txn A transaction handle returned by #mdb_txn_begin()
+ * @param[in] dbi A database handle returned by #mdb_dbi_open()
+ * @param[in] a The first item to compare
+ * @param[in] b The second item to compare
+ * @return < 0 if a < b, 0 if a == b, > 0 if a > b
+ */
+int mdb_cmp(MDB_txn *txn, MDB_dbi dbi, const MDB_val *a, const MDB_val *b);
+
+ /** @brief Compare two data items according to a particular database.
+ *
+ * This returns a comparison as if the two items were data items of
+ * the specified database. The database must have the #MDB_DUPSORT flag.
+ * @param[in] txn A transaction handle returned by #mdb_txn_begin()
+ * @param[in] dbi A database handle returned by #mdb_dbi_open()
+ * @param[in] a The first item to compare
+ * @param[in] b The second item to compare
+ * @return < 0 if a < b, 0 if a == b, > 0 if a > b
+ */
+int mdb_dcmp(MDB_txn *txn, MDB_dbi dbi, const MDB_val *a, const MDB_val *b);
+
+ /** @brief A callback function used to print a message from the library.
+ *
+ * @param[in] msg The string to be printed.
+ * @param[in] ctx An arbitrary context pointer for the callback.
+ * @return < 0 on failure, >= 0 on success.
+ */
+typedef int (MDB_msg_func)(const char *msg, void *ctx);
+
+ /** @brief Dump the entries in the reader lock table.
+ *
+ * @param[in] env An environment handle returned by #mdb_env_create()
+ * @param[in] func A #MDB_msg_func function
+ * @param[in] ctx Anything the message function needs
+ * @return < 0 on failure, >= 0 on success.
+ */
+int mdb_reader_list(MDB_env *env, MDB_msg_func *func, void *ctx);
+
+ /** @brief Check for stale entries in the reader lock table.
+ *
+ * @param[in] env An environment handle returned by #mdb_env_create()
+ * @param[out] dead Number of stale slots that were cleared
+ * @return 0 on success, non-zero on failure.
+ */
+int mdb_reader_check(MDB_env *env, int *dead);
+/** @} */
+
+#ifdef __cplusplus
+}
+#endif
+/** @page tools LMDB Command Line Tools
+ The following describes the command line tools that are available for LMDB.
+ \li \ref mdb_copy_1
+ \li \ref mdb_dump_1
+ \li \ref mdb_load_1
+ \li \ref mdb_stat_1
+*/
+
+#endif /* _LMDB_H_ */
diff --git a/src/contrib/lmdb/mdb.c b/src/contrib/lmdb/mdb.c
new file mode 100644
index 0000000..d9e7c5e
--- /dev/null
+++ b/src/contrib/lmdb/mdb.c
@@ -0,0 +1,10266 @@
+/** @file mdb.c
+ * @brief Lightning memory-mapped database library
+ *
+ * A Btree-based database management library modeled loosely on the
+ * BerkeleyDB API, but much simplified.
+ */
+/*
+ * Copyright 2011-2018 Howard Chu, Symas Corp.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ *
+ * This code is derived from btree.c written by Martin Hedenfalk.
+ *
+ * Copyright (c) 2009, 2010 Martin Hedenfalk <martin@bzero.se>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE 1
+#endif
+#if defined(__WIN64__)
+#define _FILE_OFFSET_BITS 64
+#endif
+#ifdef _WIN32
+#include <malloc.h>
+#include <windows.h>
+#include <wchar.h> /* get wcscpy() */
+
+/** getpid() returns int; MinGW defines pid_t but MinGW64 typedefs it
+ * as int64 which is wrong. MSVC doesn't define it at all, so just
+ * don't use it.
+ */
+#define MDB_PID_T int
+#define MDB_THR_T DWORD
+#include <sys/types.h>
+#include <sys/stat.h>
+#ifdef __GNUC__
+# include <sys/param.h>
+#else
+# define LITTLE_ENDIAN 1234
+# define BIG_ENDIAN 4321
+# define BYTE_ORDER LITTLE_ENDIAN
+# ifndef SSIZE_MAX
+# define SSIZE_MAX INT_MAX
+# endif
+#endif
+#else
+#include <sys/types.h>
+#include <sys/stat.h>
+#define MDB_PID_T pid_t
+#define MDB_THR_T pthread_t
+#include <sys/param.h>
+#include <sys/uio.h>
+#include <sys/mman.h>
+#ifdef HAVE_SYS_FILE_H
+#include <sys/file.h>
+#endif
+#include <fcntl.h>
+#endif
+
+#if defined(__mips) && defined(__linux)
+/* MIPS has cache coherency issues, requires explicit cache control */
+#include <asm/cachectl.h>
+extern int cacheflush(char *addr, int nbytes, int cache);
+#define CACHEFLUSH(addr, bytes, cache) cacheflush(addr, bytes, cache)
+#else
+#define CACHEFLUSH(addr, bytes, cache)
+#endif
+
+#if defined(__linux) && !defined(MDB_FDATASYNC_WORKS)
+/** fdatasync is broken on ext3/ext4fs on older kernels, see
+ * description in #mdb_env_open2 comments. You can safely
+ * define MDB_FDATASYNC_WORKS if this code will only be run
+ * on kernels 3.6 and newer.
+ */
+#define BROKEN_FDATASYNC
+#endif
+
+#include <errno.h>
+#include <limits.h>
+#include <stddef.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#ifdef _MSC_VER
+#include <io.h>
+typedef SSIZE_T ssize_t;
+#else
+#include <unistd.h>
+#endif
+
+#if defined(__sun) || defined(ANDROID)
+/* Most platforms have posix_memalign, older may only have memalign */
+#define HAVE_MEMALIGN 1
+#include <malloc.h>
+/* On Solaris, we need the POSIX sigwait function */
+#if defined (__sun)
+# define _POSIX_PTHREAD_SEMANTICS 1
+#endif
+#endif
+
+#if !(defined(BYTE_ORDER) || defined(__BYTE_ORDER))
+#include <netinet/in.h>
+#include <resolv.h> /* defines BYTE_ORDER on HPUX and Solaris */
+#endif
+
+#if defined(__APPLE__) || defined (BSD) || defined(__FreeBSD_kernel__)
+# define MDB_USE_POSIX_SEM 1
+# define MDB_FDATASYNC fsync
+#elif defined(ANDROID)
+# define MDB_FDATASYNC fsync
+#endif
+
+#ifndef _WIN32
+#include <pthread.h>
+#include <signal.h>
+#ifdef MDB_USE_POSIX_SEM
+# define MDB_USE_HASH 1
+#include <semaphore.h>
+#else
+#define MDB_USE_POSIX_MUTEX 1
+#endif
+#endif
+
+#if defined(_WIN32) + defined(MDB_USE_POSIX_SEM) \
+ + defined(MDB_USE_POSIX_MUTEX) != 1
+# error "Ambiguous shared-lock implementation"
+#endif
+
+#ifdef USE_VALGRIND
+#include <valgrind/memcheck.h>
+#define VGMEMP_CREATE(h,r,z) VALGRIND_CREATE_MEMPOOL(h,r,z)
+#define VGMEMP_ALLOC(h,a,s) VALGRIND_MEMPOOL_ALLOC(h,a,s)
+#define VGMEMP_FREE(h,a) VALGRIND_MEMPOOL_FREE(h,a)
+#define VGMEMP_DESTROY(h) VALGRIND_DESTROY_MEMPOOL(h)
+#define VGMEMP_DEFINED(a,s) VALGRIND_MAKE_MEM_DEFINED(a,s)
+#else
+#define VGMEMP_CREATE(h,r,z)
+#define VGMEMP_ALLOC(h,a,s)
+#define VGMEMP_FREE(h,a)
+#define VGMEMP_DESTROY(h)
+#define VGMEMP_DEFINED(a,s)
+#endif
+
+#ifndef BYTE_ORDER
+# if (defined(_LITTLE_ENDIAN) || defined(_BIG_ENDIAN)) && !(defined(_LITTLE_ENDIAN) && defined(_BIG_ENDIAN))
+/* Solaris just defines one or the other */
+# define LITTLE_ENDIAN 1234
+# define BIG_ENDIAN 4321
+# ifdef _LITTLE_ENDIAN
+# define BYTE_ORDER LITTLE_ENDIAN
+# else
+# define BYTE_ORDER BIG_ENDIAN
+# endif
+# else
+# define BYTE_ORDER __BYTE_ORDER
+# endif
+#endif
+
+#ifndef LITTLE_ENDIAN
+#define LITTLE_ENDIAN __LITTLE_ENDIAN
+#endif
+#ifndef BIG_ENDIAN
+#define BIG_ENDIAN __BIG_ENDIAN
+#endif
+
+#if defined(__i386) || defined(__x86_64) || defined(_M_IX86)
+#define MISALIGNED_OK 1
+#endif
+
+#include "lmdb.h"
+#include "midl.h"
+
+#if (BYTE_ORDER == LITTLE_ENDIAN) == (BYTE_ORDER == BIG_ENDIAN)
+# error "Unknown or unsupported endianness (BYTE_ORDER)"
+#elif (-6 & 5) || CHAR_BIT != 8 || UINT_MAX < 0xffffffff || ULONG_MAX % 0xFFFF
+# error "Two's complement, reasonably sized integer types, please"
+#endif
+
+#ifdef __GNUC__
+/** Put infrequently used env functions in separate section */
+# ifdef __APPLE__
+# define ESECT __attribute__ ((section("__TEXT,text_env")))
+# else
+# define ESECT __attribute__ ((section("text_env")))
+# endif
+#else
+#define ESECT
+#endif
+
+#ifdef _WIN32
+#define CALL_CONV WINAPI
+#else
+#define CALL_CONV
+#endif
+
+/** @defgroup internal LMDB Internals
+ * @{
+ */
+/** @defgroup compat Compatibility Macros
+ * A bunch of macros to minimize the amount of platform-specific ifdefs
+ * needed throughout the rest of the code. When the features this library
+ * needs are similar enough to POSIX to be hidden in a one-or-two line
+ * replacement, this macro approach is used.
+ * @{
+ */
+
+ /** Features under development */
+#ifndef MDB_DEVEL
+#define MDB_DEVEL 0
+#endif
+
+ /** Wrapper around __func__, which is a C99 feature */
+#if __STDC_VERSION__ >= 199901L
+# define mdb_func_ __func__
+#elif __GNUC__ >= 2 || _MSC_VER >= 1300
+# define mdb_func_ __FUNCTION__
+#else
+/* If a debug message says <mdb_unknown>(), update the #if statements above */
+# define mdb_func_ "<mdb_unknown>"
+#endif
+
+/* Internal error codes, not exposed outside liblmdb */
+#define MDB_NO_ROOT (MDB_LAST_ERRCODE + 10)
+#ifdef _WIN32
+#define MDB_OWNERDEAD ((int) WAIT_ABANDONED)
+#elif defined(MDB_USE_POSIX_MUTEX) && defined(EOWNERDEAD)
+#define MDB_OWNERDEAD EOWNERDEAD /**< #LOCK_MUTEX0() result if dead owner */
+#endif
+
+#ifdef __GLIBC__
+#define GLIBC_VER ((__GLIBC__ << 16 )| __GLIBC_MINOR__)
+#endif
+/** Some platforms define the EOWNERDEAD error code
+ * even though they don't support Robust Mutexes.
+ * Compile with -DMDB_USE_ROBUST=0, or use some other
+ * mechanism like -DMDB_USE_POSIX_SEM instead of
+ * -DMDB_USE_POSIX_MUTEX.
+ * (Posix semaphores are not robust.)
+ */
+#ifndef MDB_USE_ROBUST
+/* Android currently lacks Robust Mutex support. So does glibc < 2.4. */
+# if defined(MDB_USE_POSIX_MUTEX) && (defined(ANDROID) || \
+ (defined(__GLIBC__) && GLIBC_VER < 0x020004))
+# define MDB_USE_ROBUST 0
+# else
+# define MDB_USE_ROBUST 1
+# endif
+#endif /* !MDB_USE_ROBUST */
+
+#if defined(MDB_USE_POSIX_MUTEX) && (MDB_USE_ROBUST)
+/* glibc < 2.12 only provided _np API */
+# if (defined(__GLIBC__) && GLIBC_VER < 0x02000c) || \
+ (defined(PTHREAD_MUTEX_ROBUST_NP) && !defined(PTHREAD_MUTEX_ROBUST))
+# define PTHREAD_MUTEX_ROBUST PTHREAD_MUTEX_ROBUST_NP
+# define pthread_mutexattr_setrobust(attr, flag) pthread_mutexattr_setrobust_np(attr, flag)
+# define pthread_mutex_consistent(mutex) pthread_mutex_consistent_np(mutex)
+# endif
+#endif /* MDB_USE_POSIX_MUTEX && MDB_USE_ROBUST */
+
+#if defined(MDB_OWNERDEAD) && (MDB_USE_ROBUST)
+#define MDB_ROBUST_SUPPORTED 1
+#endif
+
+#ifdef _WIN32
+#define MDB_USE_HASH 1
+#define MDB_PIDLOCK 0
+#define THREAD_RET DWORD
+#define pthread_t HANDLE
+#define pthread_mutex_t HANDLE
+#define pthread_cond_t HANDLE
+typedef HANDLE mdb_mutex_t, mdb_mutexref_t;
+#define pthread_key_t DWORD
+#define pthread_self() GetCurrentThreadId()
+#define pthread_key_create(x,y) \
+ ((*(x) = TlsAlloc()) == TLS_OUT_OF_INDEXES ? ErrCode() : 0)
+#define pthread_key_delete(x) TlsFree(x)
+#define pthread_getspecific(x) TlsGetValue(x)
+#define pthread_setspecific(x,y) (TlsSetValue(x,y) ? 0 : ErrCode())
+#define pthread_mutex_unlock(x) ReleaseMutex(*x)
+#define pthread_mutex_lock(x) WaitForSingleObject(*x, INFINITE)
+#define pthread_cond_signal(x) SetEvent(*x)
+#define pthread_cond_wait(cond,mutex) do{SignalObjectAndWait(*mutex, *cond, INFINITE, FALSE); WaitForSingleObject(*mutex, INFINITE);}while(0)
+#define THREAD_CREATE(thr,start,arg) \
+ (((thr) = CreateThread(NULL, 0, start, arg, 0, NULL)) ? 0 : ErrCode())
+#define THREAD_FINISH(thr) \
+ (WaitForSingleObject(thr, INFINITE) ? ErrCode() : 0)
+#define LOCK_MUTEX0(mutex) WaitForSingleObject(mutex, INFINITE)
+#define UNLOCK_MUTEX(mutex) ReleaseMutex(mutex)
+#define mdb_mutex_consistent(mutex) 0
+#define getpid() GetCurrentProcessId()
+#define MDB_FDATASYNC(fd) (!FlushFileBuffers(fd))
+#define MDB_MSYNC(addr,len,flags) (!FlushViewOfFile(addr,len))
+#define ErrCode() GetLastError()
+#define GET_PAGESIZE(x) {SYSTEM_INFO si; GetSystemInfo(&si); (x) = si.dwPageSize;}
+#define close(fd) (CloseHandle(fd) ? 0 : -1)
+#define munmap(ptr,len) UnmapViewOfFile(ptr)
+#ifdef PROCESS_QUERY_LIMITED_INFORMATION
+#define MDB_PROCESS_QUERY_LIMITED_INFORMATION PROCESS_QUERY_LIMITED_INFORMATION
+#else
+#define MDB_PROCESS_QUERY_LIMITED_INFORMATION 0x1000
+#endif
+#define Z "I"
+#else
+#define THREAD_RET void *
+#define THREAD_CREATE(thr,start,arg) pthread_create(&thr,NULL,start,arg)
+#define THREAD_FINISH(thr) pthread_join(thr,NULL)
+#define Z "z" /**< printf format modifier for size_t */
+
+ /** For MDB_LOCK_FORMAT: True if readers take a pid lock in the lockfile */
+#define MDB_PIDLOCK 1
+
+#ifdef MDB_USE_POSIX_SEM
+
+typedef sem_t *mdb_mutex_t, *mdb_mutexref_t;
+#define LOCK_MUTEX0(mutex) mdb_sem_wait(mutex)
+#define UNLOCK_MUTEX(mutex) sem_post(mutex)
+
+static int
+mdb_sem_wait(sem_t *sem)
+{
+ int rc;
+ while ((rc = sem_wait(sem)) && (rc = errno) == EINTR) ;
+ return rc;
+}
+
+#else /* MDB_USE_POSIX_MUTEX: */
+ /** Shared mutex/semaphore as the original is stored.
+ *
+ * Not for copies. Instead it can be assigned to an #mdb_mutexref_t.
+ * When mdb_mutexref_t is a pointer and mdb_mutex_t is not, then it
+ * is array[size 1] so it can be assigned to the pointer.
+ */
+typedef pthread_mutex_t mdb_mutex_t[1];
+ /** Reference to an #mdb_mutex_t */
+typedef pthread_mutex_t *mdb_mutexref_t;
+ /** Lock the reader or writer mutex.
+ * Returns 0 or a code to give #mdb_mutex_failed(), as in #LOCK_MUTEX().
+ */
+#define LOCK_MUTEX0(mutex) pthread_mutex_lock(mutex)
+ /** Unlock the reader or writer mutex.
+ */
+#define UNLOCK_MUTEX(mutex) pthread_mutex_unlock(mutex)
+ /** Mark mutex-protected data as repaired, after death of previous owner.
+ */
+#define mdb_mutex_consistent(mutex) pthread_mutex_consistent(mutex)
+#endif /* MDB_USE_POSIX_SEM */
+
+ /** Get the error code for the last failed system function.
+ */
+#define ErrCode() errno
+
+ /** An abstraction for a file handle.
+ * On POSIX systems file handles are small integers. On Windows
+ * they're opaque pointers.
+ */
+#define HANDLE int
+
+ /** A value for an invalid file handle.
+ * Mainly used to initialize file variables and signify that they are
+ * unused.
+ */
+#define INVALID_HANDLE_VALUE (-1)
+
+ /** Get the size of a memory page for the system.
+ * This is the basic size that the platform's memory manager uses, and is
+ * fundamental to the use of memory-mapped files.
+ */
+#define GET_PAGESIZE(x) ((x) = sysconf(_SC_PAGE_SIZE))
+#endif
+
+#if defined(_WIN32) || defined(MDB_USE_POSIX_SEM)
+#define MNAME_LEN 32
+#else
+#define MNAME_LEN (sizeof(pthread_mutex_t))
+#endif
+
+/** @} */
+
+#ifdef MDB_ROBUST_SUPPORTED
+ /** Lock mutex, handle any error, set rc = result.
+ * Return 0 on success, nonzero (not rc) on error.
+ */
+#define LOCK_MUTEX(rc, env, mutex) \
+ (((rc) = LOCK_MUTEX0(mutex)) && \
+ ((rc) = mdb_mutex_failed(env, mutex, rc)))
+static int mdb_mutex_failed(MDB_env *env, mdb_mutexref_t mutex, int rc);
+#else
+#define LOCK_MUTEX(rc, env, mutex) ((rc) = LOCK_MUTEX0(mutex))
+#define mdb_mutex_failed(env, mutex, rc) (rc)
+#endif
+
+#ifndef _WIN32
+/** A flag for opening a file and requesting synchronous data writes.
+ * This is only used when writing a meta page. It's not strictly needed;
+ * we could just do a normal write and then immediately perform a flush.
+ * But if this flag is available it saves us an extra system call.
+ *
+ * @note If O_DSYNC is undefined but exists in /usr/include,
+ * preferably set some compiler flag to get the definition.
+ */
+#ifndef MDB_DSYNC
+# ifdef O_DSYNC
+# define MDB_DSYNC O_DSYNC
+# else
+# define MDB_DSYNC O_SYNC
+# endif
+#endif
+#endif
+
+/** Function for flushing the data of a file. Define this to fsync
+ * if fdatasync() is not supported.
+ */
+#ifndef MDB_FDATASYNC
+# define MDB_FDATASYNC fdatasync
+#endif
+
+#ifndef MDB_MSYNC
+# define MDB_MSYNC(addr,len,flags) msync(addr,len,flags)
+#endif
+
+#ifndef MS_SYNC
+#define MS_SYNC 1
+#endif
+
+#ifndef MS_ASYNC
+#define MS_ASYNC 0
+#endif
+
+ /** A page number in the database.
+ * Note that 64 bit page numbers are overkill, since pages themselves
+ * already represent 12-13 bits of addressable memory, and the OS will
+ * always limit applications to a maximum of 63 bits of address space.
+ *
+ * @note In the #MDB_node structure, we only store 48 bits of this value,
+ * which thus limits us to only 60 bits of addressable data.
+ */
+typedef MDB_ID pgno_t;
+
+ /** A transaction ID.
+ * See struct MDB_txn.mt_txnid for details.
+ */
+typedef MDB_ID txnid_t;
+
+/** @defgroup debug Debug Macros
+ * @{
+ */
+#ifndef MDB_DEBUG
+ /** Enable debug output. Needs variable argument macros (a C99 feature).
+ * Set this to 1 for copious tracing. Set to 2 to add dumps of all IDLs
+ * read from and written to the database (used for free space management).
+ */
+#define MDB_DEBUG 0
+#endif
+
+#if MDB_DEBUG
+static int mdb_debug;
+static txnid_t mdb_debug_start;
+
+ /** Print a debug message with printf formatting.
+ * Requires double parenthesis around 2 or more args.
+ */
+# define DPRINTF(args) ((void) ((mdb_debug) && DPRINTF0 args))
+# define DPRINTF0(fmt, ...) \
+ fprintf(stderr, "%s:%d " fmt "\n", mdb_func_, __LINE__, __VA_ARGS__)
+#else
+# define DPRINTF(args) ((void) 0)
+#endif
+ /** Print a debug string.
+ * The string is printed literally, with no format processing.
+ */
+#define DPUTS(arg) DPRINTF(("%s", arg))
+ /** Debuging output value of a cursor DBI: Negative in a sub-cursor. */
+#define DDBI(mc) \
+ (((mc)->mc_flags & C_SUB) ? -(int)(mc)->mc_dbi : (int)(mc)->mc_dbi)
+/** @} */
+
+ /** @brief The maximum size of a database page.
+ *
+ * It is 32k or 64k, since value-PAGEBASE must fit in
+ * #MDB_page.%mp_upper.
+ *
+ * LMDB will use database pages < OS pages if needed.
+ * That causes more I/O in write transactions: The OS must
+ * know (read) the whole page before writing a partial page.
+ *
+ * Note that we don't currently support Huge pages. On Linux,
+ * regular data files cannot use Huge pages, and in general
+ * Huge pages aren't actually pageable. We rely on the OS
+ * demand-pager to read our data and page it out when memory
+ * pressure from other processes is high. So until OSs have
+ * actual paging support for Huge pages, they're not viable.
+ */
+#define MAX_PAGESIZE (PAGEBASE ? 0x10000 : 0x8000)
+
+ /** The minimum number of keys required in a database page.
+ * Setting this to a larger value will place a smaller bound on the
+ * maximum size of a data item. Data items larger than this size will
+ * be pushed into overflow pages instead of being stored directly in
+ * the B-tree node. This value used to default to 4. With a page size
+ * of 4096 bytes that meant that any item larger than 1024 bytes would
+ * go into an overflow page. That also meant that on average 2-3KB of
+ * each overflow page was wasted space. The value cannot be lower than
+ * 2 because then there would no longer be a tree structure. With this
+ * value, items larger than 2KB will go into overflow pages, and on
+ * average only 1KB will be wasted.
+ */
+#define MDB_MINKEYS 2
+
+ /** A stamp that identifies a file as an LMDB file.
+ * There's nothing special about this value other than that it is easily
+ * recognizable, and it will reflect any byte order mismatches.
+ */
+#define MDB_MAGIC 0xBEEFC0DE
+
+ /** The version number for a database's datafile format. */
+#define MDB_DATA_VERSION ((MDB_DEVEL) ? 999 : 1)
+ /** The version number for a database's lockfile format. */
+#define MDB_LOCK_VERSION 1
+
+ /** @brief The max size of a key we can write, or 0 for computed max.
+ *
+ * This macro should normally be left alone or set to 0.
+ * Note that a database with big keys or dupsort data cannot be
+ * reliably modified by a liblmdb which uses a smaller max.
+ * The default is 511 for backwards compat, or 0 when #MDB_DEVEL.
+ *
+ * Other values are allowed, for backwards compat. However:
+ * A value bigger than the computed max can break if you do not
+ * know what you are doing, and liblmdb <= 0.9.10 can break when
+ * modifying a DB with keys/dupsort data bigger than its max.
+ *
+ * Data items in an #MDB_DUPSORT database are also limited to
+ * this size, since they're actually keys of a sub-DB. Keys and
+ * #MDB_DUPSORT data items must fit on a node in a regular page.
+ */
+#ifndef MDB_MAXKEYSIZE
+#define MDB_MAXKEYSIZE ((MDB_DEVEL) ? 0 : 511)
+#endif
+
+ /** The maximum size of a key we can write to the environment. */
+#if MDB_MAXKEYSIZE
+#define ENV_MAXKEY(env) (MDB_MAXKEYSIZE)
+#else
+#define ENV_MAXKEY(env) ((env)->me_maxkey)
+#endif
+
+ /** @brief The maximum size of a data item.
+ *
+ * We only store a 32 bit value for node sizes.
+ */
+#define MAXDATASIZE 0xffffffffUL
+
+#if MDB_DEBUG
+ /** Key size which fits in a #DKBUF.
+ * @ingroup debug
+ */
+#define DKBUF_MAXKEYSIZE ((MDB_MAXKEYSIZE) > 0 ? (MDB_MAXKEYSIZE) : 511)
+ /** A key buffer.
+ * @ingroup debug
+ * This is used for printing a hex dump of a key's contents.
+ */
+#define DKBUF char kbuf[DKBUF_MAXKEYSIZE*2+1]
+ /** Display a key in hex.
+ * @ingroup debug
+ * Invoke a function to display a key in hex.
+ */
+#define DKEY(x) mdb_dkey(x, kbuf)
+#else
+#define DKBUF
+#define DKEY(x) 0
+#endif
+
+ /** An invalid page number.
+ * Mainly used to denote an empty tree.
+ */
+#define P_INVALID (~(pgno_t)0)
+
+ /** Test if the flags \b f are set in a flag word \b w. */
+#define F_ISSET(w, f) (((w) & (f)) == (f))
+
+ /** Round \b n up to an even number. */
+#define EVEN(n) (((n) + 1U) & -2) /* sign-extending -2 to match n+1U */
+
+ /** Used for offsets within a single page.
+ * Since memory pages are typically 4 or 8KB in size, 12-13 bits,
+ * this is plenty.
+ */
+typedef uint16_t indx_t;
+
+ /** Default size of memory map.
+ * This is certainly too small for any actual applications. Apps should always set
+ * the size explicitly using #mdb_env_set_mapsize().
+ */
+#define DEFAULT_MAPSIZE 1048576
+
+/** @defgroup readers Reader Lock Table
+ * Readers don't acquire any locks for their data access. Instead, they
+ * simply record their transaction ID in the reader table. The reader
+ * mutex is needed just to find an empty slot in the reader table. The
+ * slot's address is saved in thread-specific data so that subsequent read
+ * transactions started by the same thread need no further locking to proceed.
+ *
+ * If #MDB_NOTLS is set, the slot address is not saved in thread-specific data.
+ *
+ * No reader table is used if the database is on a read-only filesystem, or
+ * if #MDB_NOLOCK is set.
+ *
+ * Since the database uses multi-version concurrency control, readers don't
+ * actually need any locking. This table is used to keep track of which
+ * readers are using data from which old transactions, so that we'll know
+ * when a particular old transaction is no longer in use. Old transactions
+ * that have discarded any data pages can then have those pages reclaimed
+ * for use by a later write transaction.
+ *
+ * The lock table is constructed such that reader slots are aligned with the
+ * processor's cache line size. Any slot is only ever used by one thread.
+ * This alignment guarantees that there will be no contention or cache
+ * thrashing as threads update their own slot info, and also eliminates
+ * any need for locking when accessing a slot.
+ *
+ * A writer thread will scan every slot in the table to determine the oldest
+ * outstanding reader transaction. Any freed pages older than this will be
+ * reclaimed by the writer. The writer doesn't use any locks when scanning
+ * this table. This means that there's no guarantee that the writer will
+ * see the most up-to-date reader info, but that's not required for correct
+ * operation - all we need is to know the upper bound on the oldest reader,
+ * we don't care at all about the newest reader. So the only consequence of
+ * reading stale information here is that old pages might hang around a
+ * while longer before being reclaimed. That's actually good anyway, because
+ * the longer we delay reclaiming old pages, the more likely it is that a
+ * string of contiguous pages can be found after coalescing old pages from
+ * many old transactions together.
+ * @{
+ */
+ /** Number of slots in the reader table.
+ * This value was chosen somewhat arbitrarily. 126 readers plus a
+ * couple mutexes fit exactly into 8KB on my development machine.
+ * Applications should set the table size using #mdb_env_set_maxreaders().
+ */
+#define DEFAULT_READERS 126
+
+ /** The size of a CPU cache line in bytes. We want our lock structures
+ * aligned to this size to avoid false cache line sharing in the
+ * lock table.
+ * This value works for most CPUs. For Itanium this should be 128.
+ */
+#ifndef CACHELINE
+#define CACHELINE 64
+#endif
+
+ /** The information we store in a single slot of the reader table.
+ * In addition to a transaction ID, we also record the process and
+ * thread ID that owns a slot, so that we can detect stale information,
+ * e.g. threads or processes that went away without cleaning up.
+ * @note We currently don't check for stale records. We simply re-init
+ * the table when we know that we're the only process opening the
+ * lock file.
+ */
+typedef struct MDB_rxbody {
+ /** Current Transaction ID when this transaction began, or (txnid_t)-1.
+ * Multiple readers that start at the same time will probably have the
+ * same ID here. Again, it's not important to exclude them from
+ * anything; all we need to know is which version of the DB they
+ * started from so we can avoid overwriting any data used in that
+ * particular version.
+ */
+ volatile txnid_t mrb_txnid;
+ /** The process ID of the process owning this reader txn. */
+ volatile MDB_PID_T mrb_pid;
+ /** The thread ID of the thread owning this txn. */
+ volatile MDB_THR_T mrb_tid;
+} MDB_rxbody;
+
+ /** The actual reader record, with cacheline padding. */
+typedef struct MDB_reader {
+ union {
+ MDB_rxbody mrx;
+ /** shorthand for mrb_txnid */
+#define mr_txnid mru.mrx.mrb_txnid
+#define mr_pid mru.mrx.mrb_pid
+#define mr_tid mru.mrx.mrb_tid
+ /** cache line alignment */
+ char pad[(sizeof(MDB_rxbody)+CACHELINE-1) & ~(CACHELINE-1)];
+ } mru;
+} MDB_reader;
+
+ /** The header for the reader table.
+ * The table resides in a memory-mapped file. (This is a different file
+ * than is used for the main database.)
+ *
+ * For POSIX the actual mutexes reside in the shared memory of this
+ * mapped file. On Windows, mutexes are named objects allocated by the
+ * kernel; we store the mutex names in this mapped file so that other
+ * processes can grab them. This same approach is also used on
+ * MacOSX/Darwin (using named semaphores) since MacOSX doesn't support
+ * process-shared POSIX mutexes. For these cases where a named object
+ * is used, the object name is derived from a 64 bit FNV hash of the
+ * environment pathname. As such, naming collisions are extremely
+ * unlikely. If a collision occurs, the results are unpredictable.
+ */
+typedef struct MDB_txbody {
+ /** Stamp identifying this as an LMDB file. It must be set
+ * to #MDB_MAGIC. */
+ uint32_t mtb_magic;
+ /** Format of this lock file. Must be set to #MDB_LOCK_FORMAT. */
+ uint32_t mtb_format;
+#if defined(_WIN32) || defined(MDB_USE_POSIX_SEM)
+ char mtb_rmname[MNAME_LEN];
+#else
+ /** Mutex protecting access to this table.
+ * This is the reader table lock used with LOCK_MUTEX().
+ */
+ mdb_mutex_t mtb_rmutex;
+#endif
+ /** The ID of the last transaction committed to the database.
+ * This is recorded here only for convenience; the value can always
+ * be determined by reading the main database meta pages.
+ */
+ volatile txnid_t mtb_txnid;
+ /** The number of slots that have been used in the reader table.
+ * This always records the maximum count, it is not decremented
+ * when readers release their slots.
+ */
+ volatile unsigned mtb_numreaders;
+} MDB_txbody;
+
+ /** The actual reader table definition. */
+typedef struct MDB_txninfo {
+ union {
+ MDB_txbody mtb;
+#define mti_magic mt1.mtb.mtb_magic
+#define mti_format mt1.mtb.mtb_format
+#define mti_rmutex mt1.mtb.mtb_rmutex
+#define mti_rmname mt1.mtb.mtb_rmname
+#define mti_txnid mt1.mtb.mtb_txnid
+#define mti_numreaders mt1.mtb.mtb_numreaders
+ char pad[(sizeof(MDB_txbody)+CACHELINE-1) & ~(CACHELINE-1)];
+ } mt1;
+ union {
+#if defined(_WIN32) || defined(MDB_USE_POSIX_SEM)
+ char mt2_wmname[MNAME_LEN];
+#define mti_wmname mt2.mt2_wmname
+#else
+ mdb_mutex_t mt2_wmutex;
+#define mti_wmutex mt2.mt2_wmutex
+#endif
+ char pad[(MNAME_LEN+CACHELINE-1) & ~(CACHELINE-1)];
+ } mt2;
+ MDB_reader mti_readers[1];
+} MDB_txninfo;
+
+ /** Lockfile format signature: version, features and field layout */
+#define MDB_LOCK_FORMAT \
+ ((uint32_t) \
+ ((MDB_LOCK_VERSION) \
+ /* Flags which describe functionality */ \
+ + (((MDB_PIDLOCK) != 0) << 16)))
+/** @} */
+
+/** Common header for all page types. The page type depends on #mp_flags.
+ *
+ * #P_BRANCH and #P_LEAF pages have unsorted '#MDB_node's at the end, with
+ * sorted #mp_ptrs[] entries referring to them. Exception: #P_LEAF2 pages
+ * omit mp_ptrs and pack sorted #MDB_DUPFIXED values after the page header.
+ *
+ * #P_OVERFLOW records occupy one or more contiguous pages where only the
+ * first has a page header. They hold the real data of #F_BIGDATA nodes.
+ *
+ * #P_SUBP sub-pages are small leaf "pages" with duplicate data.
+ * A node with flag #F_DUPDATA but not #F_SUBDATA contains a sub-page.
+ * (Duplicate data can also go in sub-databases, which use normal pages.)
+ *
+ * #P_META pages contain #MDB_meta, the start point of an LMDB snapshot.
+ *
+ * Each non-metapage up to #MDB_meta.%mm_last_pg is reachable exactly once
+ * in the snapshot: Either used by a database or listed in a freeDB record.
+ */
+typedef struct MDB_page {
+#define mp_pgno mp_p.p_pgno
+#define mp_next mp_p.p_next
+ union {
+ pgno_t p_pgno; /**< page number */
+ struct MDB_page *p_next; /**< for in-memory list of freed pages */
+ } mp_p;
+ uint16_t mp_pad; /**< key size if this is a LEAF2 page */
+/** @defgroup mdb_page Page Flags
+ * @ingroup internal
+ * Flags for the page headers.
+ * @{
+ */
+#define P_BRANCH 0x01 /**< branch page */
+#define P_LEAF 0x02 /**< leaf page */
+#define P_OVERFLOW 0x04 /**< overflow page */
+#define P_META 0x08 /**< meta page */
+#define P_DIRTY 0x10 /**< dirty page, also set for #P_SUBP pages */
+#define P_LEAF2 0x20 /**< for #MDB_DUPFIXED records */
+#define P_SUBP 0x40 /**< for #MDB_DUPSORT sub-pages */
+#define P_LOOSE 0x4000 /**< page was dirtied then freed, can be reused */
+#define P_KEEP 0x8000 /**< leave this page alone during spill */
+/** @} */
+ uint16_t mp_flags; /**< @ref mdb_page */
+#define mp_lower mp_pb.pb.pb_lower
+#define mp_upper mp_pb.pb.pb_upper
+#define mp_pages mp_pb.pb_pages
+ union {
+ struct {
+ indx_t pb_lower; /**< lower bound of free space */
+ indx_t pb_upper; /**< upper bound of free space */
+ } pb;
+ uint32_t pb_pages; /**< number of overflow pages */
+ } mp_pb;
+ indx_t mp_ptrs[1]; /**< dynamic size */
+} MDB_page;
+
+ /** Size of the page header, excluding dynamic data at the end */
+#define PAGEHDRSZ ((unsigned) offsetof(MDB_page, mp_ptrs))
+
+ /** Address of first usable data byte in a page, after the header */
+#define METADATA(p) ((void *)((char *)(p) + PAGEHDRSZ))
+
+ /** ITS#7713, change PAGEBASE to handle 65536 byte pages */
+#define PAGEBASE ((MDB_DEVEL) ? PAGEHDRSZ : 0)
+
+ /** Number of nodes on a page */
+#define NUMKEYS(p) (((p)->mp_lower - (PAGEHDRSZ-PAGEBASE)) >> 1)
+
+ /** The amount of space remaining in the page */
+#define SIZELEFT(p) (indx_t)((p)->mp_upper - (p)->mp_lower)
+
+ /** The percentage of space used in the page, in tenths of a percent. */
+#define PAGEFILL(env, p) (1000L * ((env)->me_psize - PAGEHDRSZ - SIZELEFT(p)) / \
+ ((env)->me_psize - PAGEHDRSZ))
+ /** The minimum page fill factor, in tenths of a percent.
+ * Pages emptier than this are candidates for merging.
+ */
+#define FILL_THRESHOLD 250
+
+ /** Test if a page is a leaf page */
+#define IS_LEAF(p) F_ISSET((p)->mp_flags, P_LEAF)
+ /** Test if a page is a LEAF2 page */
+#define IS_LEAF2(p) F_ISSET((p)->mp_flags, P_LEAF2)
+ /** Test if a page is a branch page */
+#define IS_BRANCH(p) F_ISSET((p)->mp_flags, P_BRANCH)
+ /** Test if a page is an overflow page */
+#define IS_OVERFLOW(p) F_ISSET((p)->mp_flags, P_OVERFLOW)
+ /** Test if a page is a sub page */
+#define IS_SUBP(p) F_ISSET((p)->mp_flags, P_SUBP)
+
+ /** The number of overflow pages needed to store the given size. */
+#define OVPAGES(size, psize) ((PAGEHDRSZ-1 + (size)) / (psize) + 1)
+
+ /** Link in #MDB_txn.%mt_loose_pgs list.
+ * Kept outside the page header, which is needed when reusing the page.
+ */
+#define NEXT_LOOSE_PAGE(p) (*(MDB_page **)((p) + 2))
+
+ /** Header for a single key/data pair within a page.
+ * Used in pages of type #P_BRANCH and #P_LEAF without #P_LEAF2.
+ * We guarantee 2-byte alignment for 'MDB_node's.
+ *
+ * #mn_lo and #mn_hi are used for data size on leaf nodes, and for child
+ * pgno on branch nodes. On 64 bit platforms, #mn_flags is also used
+ * for pgno. (Branch nodes have no flags). Lo and hi are in host byte
+ * order in case some accesses can be optimized to 32-bit word access.
+ *
+ * Leaf node flags describe node contents. #F_BIGDATA says the node's
+ * data part is the page number of an overflow page with actual data.
+ * #F_DUPDATA and #F_SUBDATA can be combined giving duplicate data in
+ * a sub-page/sub-database, and named databases (just #F_SUBDATA).
+ */
+typedef struct MDB_node {
+ /** part of data size or pgno
+ * @{ */
+#if BYTE_ORDER == LITTLE_ENDIAN
+ unsigned short mn_lo, mn_hi;
+#else
+ unsigned short mn_hi, mn_lo;
+#endif
+ /** @} */
+/** @defgroup mdb_node Node Flags
+ * @ingroup internal
+ * Flags for node headers.
+ * @{
+ */
+#define F_BIGDATA 0x01 /**< data put on overflow page */
+#define F_SUBDATA 0x02 /**< data is a sub-database */
+#define F_DUPDATA 0x04 /**< data has duplicates */
+
+/** valid flags for #mdb_node_add() */
+#define NODE_ADD_FLAGS (F_DUPDATA|F_SUBDATA|MDB_RESERVE|MDB_APPEND)
+
+/** @} */
+ unsigned short mn_flags; /**< @ref mdb_node */
+ unsigned short mn_ksize; /**< key size */
+ char mn_data[1]; /**< key and data are appended here */
+} MDB_node;
+
+ /** Size of the node header, excluding dynamic data at the end */
+#define NODESIZE offsetof(MDB_node, mn_data)
+
+ /** Bit position of top word in page number, for shifting mn_flags */
+#define PGNO_TOPWORD ((pgno_t)-1 > 0xffffffffu ? 32 : 0)
+
+ /** Size of a node in a branch page with a given key.
+ * This is just the node header plus the key, there is no data.
+ */
+#define INDXSIZE(k) (NODESIZE + ((k) == NULL ? 0 : (k)->mv_size))
+
+ /** Size of a node in a leaf page with a given key and data.
+ * This is node header plus key plus data size.
+ */
+#define LEAFSIZE(k, d) (NODESIZE + (k)->mv_size + (d)->mv_size)
+
+ /** Address of node \b i in page \b p */
+#define NODEPTR(p, i) ((MDB_node *)((char *)(p) + (p)->mp_ptrs[i] + PAGEBASE))
+
+ /** Address of the key for the node */
+#define NODEKEY(node) (void *)((node)->mn_data)
+
+ /** Address of the data for a node */
+#define NODEDATA(node) (void *)((char *)(node)->mn_data + (node)->mn_ksize)
+
+ /** Get the page number pointed to by a branch node */
+#define NODEPGNO(node) \
+ ((node)->mn_lo | ((pgno_t) (node)->mn_hi << 16) | \
+ (PGNO_TOPWORD ? ((pgno_t) (node)->mn_flags << PGNO_TOPWORD) : 0))
+ /** Set the page number in a branch node */
+#define SETPGNO(node,pgno) do { \
+ (node)->mn_lo = (pgno) & 0xffff; (node)->mn_hi = (pgno) >> 16; \
+ if (PGNO_TOPWORD) (node)->mn_flags = (pgno) >> PGNO_TOPWORD; } while(0)
+
+ /** Get the size of the data in a leaf node */
+#define NODEDSZ(node) ((node)->mn_lo | ((unsigned)(node)->mn_hi << 16))
+ /** Set the size of the data for a leaf node */
+#define SETDSZ(node,size) do { \
+ (node)->mn_lo = (size) & 0xffff; (node)->mn_hi = (size) >> 16;} while(0)
+ /** The size of a key in a node */
+#define NODEKSZ(node) ((node)->mn_ksize)
+
+ /** Copy a page number from src to dst */
+#ifdef MISALIGNED_OK
+#define COPY_PGNO(dst,src) dst = src
+#else
+#if SIZE_MAX > 4294967295UL
+#define COPY_PGNO(dst,src) do { \
+ unsigned short *s, *d; \
+ s = (unsigned short *)&(src); \
+ d = (unsigned short *)&(dst); \
+ *d++ = *s++; \
+ *d++ = *s++; \
+ *d++ = *s++; \
+ *d = *s; \
+} while (0)
+#else
+#define COPY_PGNO(dst,src) do { \
+ unsigned short *s, *d; \
+ s = (unsigned short *)&(src); \
+ d = (unsigned short *)&(dst); \
+ *d++ = *s++; \
+ *d = *s; \
+} while (0)
+#endif
+#endif
+ /** The address of a key in a LEAF2 page.
+ * LEAF2 pages are used for #MDB_DUPFIXED sorted-duplicate sub-DBs.
+ * There are no node headers, keys are stored contiguously.
+ */
+#define LEAF2KEY(p, i, ks) ((char *)(p) + PAGEHDRSZ + ((i)*(ks)))
+
+ /** Set the \b node's key into \b keyptr, if requested. */
+#define MDB_GET_KEY(node, keyptr) { if ((keyptr) != NULL) { \
+ (keyptr)->mv_size = NODEKSZ(node); (keyptr)->mv_data = NODEKEY(node); } }
+
+ /** Set the \b node's key into \b key. */
+#define MDB_GET_KEY2(node, key) { key.mv_size = NODEKSZ(node); key.mv_data = NODEKEY(node); }
+
+ /** Information about a single database in the environment. */
+typedef struct MDB_db {
+ uint32_t md_pad; /**< also ksize for LEAF2 pages */
+ uint16_t md_flags; /**< @ref mdb_dbi_open */
+ uint16_t md_depth; /**< depth of this tree */
+ pgno_t md_branch_pages; /**< number of internal pages */
+ pgno_t md_leaf_pages; /**< number of leaf pages */
+ pgno_t md_overflow_pages; /**< number of overflow pages */
+ size_t md_entries; /**< number of data items */
+ pgno_t md_root; /**< the root page of this tree */
+} MDB_db;
+
+#define MDB_VALID 0x8000 /**< DB handle is valid, for me_dbflags */
+#define PERSISTENT_FLAGS (0xffff & ~(MDB_VALID))
+ /** #mdb_dbi_open() flags */
+#define VALID_FLAGS (MDB_REVERSEKEY|MDB_DUPSORT|MDB_INTEGERKEY|MDB_DUPFIXED|\
+ MDB_INTEGERDUP|MDB_REVERSEDUP|MDB_CREATE)
+
+ /** Handle for the DB used to track free pages. */
+#define FREE_DBI 0
+ /** Handle for the default DB. */
+#define MAIN_DBI 1
+ /** Number of DBs in metapage (free and main) - also hardcoded elsewhere */
+#define CORE_DBS 2
+
+ /** Number of meta pages - also hardcoded elsewhere */
+#define NUM_METAS 2
+
+ /** Meta page content.
+ * A meta page is the start point for accessing a database snapshot.
+ * Pages 0-1 are meta pages. Transaction N writes meta page #(N % 2).
+ */
+typedef struct MDB_meta {
+ /** Stamp identifying this as an LMDB file. It must be set
+ * to #MDB_MAGIC. */
+ uint32_t mm_magic;
+ /** Version number of this file. Must be set to #MDB_DATA_VERSION. */
+ uint32_t mm_version;
+ void *mm_address; /**< address for fixed mapping */
+ size_t mm_mapsize; /**< size of mmap region */
+ MDB_db mm_dbs[CORE_DBS]; /**< first is free space, 2nd is main db */
+ /** The size of pages used in this DB */
+#define mm_psize mm_dbs[FREE_DBI].md_pad
+ /** Any persistent environment flags. @ref mdb_env */
+#define mm_flags mm_dbs[FREE_DBI].md_flags
+ /** Last used page in the datafile.
+ * Actually the file may be shorter if the freeDB lists the final pages.
+ */
+ pgno_t mm_last_pg;
+ volatile txnid_t mm_txnid; /**< txnid that committed this page */
+} MDB_meta;
+
+ /** Buffer for a stack-allocated meta page.
+ * The members define size and alignment, and silence type
+ * aliasing warnings. They are not used directly; that could
+ * mean incorrectly using several union members in parallel.
+ */
+typedef union MDB_metabuf {
+ MDB_page mb_page;
+ struct {
+ char mm_pad[PAGEHDRSZ];
+ MDB_meta mm_meta;
+ } mb_metabuf;
+} MDB_metabuf;
+
+ /** Auxiliary DB info.
+ * The information here is mostly static/read-only. There is
+ * only a single copy of this record in the environment.
+ */
+typedef struct MDB_dbx {
+ MDB_val md_name; /**< name of the database */
+ MDB_cmp_func *md_cmp; /**< function for comparing keys */
+ MDB_cmp_func *md_dcmp; /**< function for comparing data items */
+ MDB_rel_func *md_rel; /**< user relocate function */
+ void *md_relctx; /**< user-provided context for md_rel */
+} MDB_dbx;
+
+ /** A database transaction.
+ * Every operation requires a transaction handle.
+ */
+struct MDB_txn {
+ MDB_txn *mt_parent; /**< parent of a nested txn */
+ /** Nested txn under this txn, set together with flag #MDB_TXN_HAS_CHILD */
+ MDB_txn *mt_child;
+ pgno_t mt_next_pgno; /**< next unallocated page */
+ /** The ID of this transaction. IDs are integers incrementing from 1.
+ * Only committed write transactions increment the ID. If a transaction
+ * aborts, the ID may be re-used by the next writer.
+ */
+ txnid_t mt_txnid;
+ MDB_env *mt_env; /**< the DB environment */
+ /** The list of pages that became unused during this transaction.
+ */
+ MDB_IDL mt_free_pgs;
+ /** The list of loose pages that became unused and may be reused
+ * in this transaction, linked through #NEXT_LOOSE_PAGE(page).
+ */
+ MDB_page *mt_loose_pgs;
+ /** Number of loose pages (#mt_loose_pgs) */
+ int mt_loose_count;
+ /** The sorted list of dirty pages we temporarily wrote to disk
+ * because the dirty list was full. page numbers in here are
+ * shifted left by 1, deleted slots have the LSB set.
+ */
+ MDB_IDL mt_spill_pgs;
+ union {
+ /** For write txns: Modified pages. Sorted when not MDB_WRITEMAP. */
+ MDB_ID2L dirty_list;
+ /** For read txns: This thread/txn's reader table slot, or NULL. */
+ MDB_reader *reader;
+ } mt_u;
+ /** Array of records for each DB known in the environment. */
+ MDB_dbx *mt_dbxs;
+ /** Array of MDB_db records for each known DB */
+ MDB_db *mt_dbs;
+ /** Array of sequence numbers for each DB handle */
+ unsigned int *mt_dbiseqs;
+/** @defgroup mt_dbflag Transaction DB Flags
+ * @ingroup internal
+ * @{
+ */
+#define DB_DIRTY 0x01 /**< DB was written in this txn */
+#define DB_STALE 0x02 /**< Named-DB record is older than txnID */
+#define DB_NEW 0x04 /**< Named-DB handle opened in this txn */
+#define DB_VALID 0x08 /**< DB handle is valid, see also #MDB_VALID */
+#define DB_USRVALID 0x10 /**< As #DB_VALID, but not set for #FREE_DBI */
+#define DB_DUPDATA 0x20 /**< DB is #MDB_DUPSORT data */
+/** @} */
+ /** In write txns, array of cursors for each DB */
+ MDB_cursor **mt_cursors;
+ /** Array of flags for each DB */
+ unsigned char *mt_dbflags;
+ /** Number of DB records in use, or 0 when the txn is finished.
+ * This number only ever increments until the txn finishes; we
+ * don't decrement it when individual DB handles are closed.
+ */
+ MDB_dbi mt_numdbs;
+
+/** @defgroup mdb_txn Transaction Flags
+ * @ingroup internal
+ * @{
+ */
+ /** #mdb_txn_begin() flags */
+#define MDB_TXN_BEGIN_FLAGS MDB_RDONLY
+#define MDB_TXN_RDONLY MDB_RDONLY /**< read-only transaction */
+ /* internal txn flags */
+#define MDB_TXN_WRITEMAP MDB_WRITEMAP /**< copy of #MDB_env flag in writers */
+#define MDB_TXN_FINISHED 0x01 /**< txn is finished or never began */
+#define MDB_TXN_ERROR 0x02 /**< txn is unusable after an error */
+#define MDB_TXN_DIRTY 0x04 /**< must write, even if dirty list is empty */
+#define MDB_TXN_SPILLS 0x08 /**< txn or a parent has spilled pages */
+#define MDB_TXN_HAS_CHILD 0x10 /**< txn has an #MDB_txn.%mt_child */
+ /** most operations on the txn are currently illegal */
+#define MDB_TXN_BLOCKED (MDB_TXN_FINISHED|MDB_TXN_ERROR|MDB_TXN_HAS_CHILD)
+/** @} */
+ unsigned int mt_flags; /**< @ref mdb_txn */
+ /** #dirty_list room: Array size - \#dirty pages visible to this txn.
+ * Includes ancestor txns' dirty pages not hidden by other txns'
+ * dirty/spilled pages. Thus commit(nested txn) has room to merge
+ * dirty_list into mt_parent after freeing hidden mt_parent pages.
+ */
+ unsigned int mt_dirty_room;
+};
+
+/** Enough space for 2^32 nodes with minimum of 2 keys per node. I.e., plenty.
+ * At 4 keys per node, enough for 2^64 nodes, so there's probably no need to
+ * raise this on a 64 bit machine.
+ */
+#define CURSOR_STACK 32
+
+struct MDB_xcursor;
+
+ /** Cursors are used for all DB operations.
+ * A cursor holds a path of (page pointer, key index) from the DB
+ * root to a position in the DB, plus other state. #MDB_DUPSORT
+ * cursors include an xcursor to the current data item. Write txns
+ * track their cursors and keep them up to date when data moves.
+ * Exception: An xcursor's pointer to a #P_SUBP page can be stale.
+ * (A node with #F_DUPDATA but no #F_SUBDATA contains a subpage).
+ */
+struct MDB_cursor {
+ /** Next cursor on this DB in this txn */
+ MDB_cursor *mc_next;
+ /** Backup of the original cursor if this cursor is a shadow */
+ MDB_cursor *mc_backup;
+ /** Context used for databases with #MDB_DUPSORT, otherwise NULL */
+ struct MDB_xcursor *mc_xcursor;
+ /** The transaction that owns this cursor */
+ MDB_txn *mc_txn;
+ /** The database handle this cursor operates on */
+ MDB_dbi mc_dbi;
+ /** The database record for this cursor */
+ MDB_db *mc_db;
+ /** The database auxiliary record for this cursor */
+ MDB_dbx *mc_dbx;
+ /** The @ref mt_dbflag for this database */
+ unsigned char *mc_dbflag;
+ unsigned short mc_snum; /**< number of pushed pages */
+ unsigned short mc_top; /**< index of top page, normally mc_snum-1 */
+/** @defgroup mdb_cursor Cursor Flags
+ * @ingroup internal
+ * Cursor state flags.
+ * @{
+ */
+#define C_INITIALIZED 0x01 /**< cursor has been initialized and is valid */
+#define C_EOF 0x02 /**< No more data */
+#define C_SUB 0x04 /**< Cursor is a sub-cursor */
+#define C_DEL 0x08 /**< last op was a cursor_del */
+#define C_UNTRACK 0x40 /**< Un-track cursor when closing */
+/** @} */
+ unsigned int mc_flags; /**< @ref mdb_cursor */
+ MDB_page *mc_pg[CURSOR_STACK]; /**< stack of pushed pages */
+ indx_t mc_ki[CURSOR_STACK]; /**< stack of page indices */
+};
+
+ /** Context for sorted-dup records.
+ * We could have gone to a fully recursive design, with arbitrarily
+ * deep nesting of sub-databases. But for now we only handle these
+ * levels - main DB, optional sub-DB, sorted-duplicate DB.
+ */
+typedef struct MDB_xcursor {
+ /** A sub-cursor for traversing the Dup DB */
+ MDB_cursor mx_cursor;
+ /** The database record for this Dup DB */
+ MDB_db mx_db;
+ /** The auxiliary DB record for this Dup DB */
+ MDB_dbx mx_dbx;
+ /** The @ref mt_dbflag for this Dup DB */
+ unsigned char mx_dbflag;
+} MDB_xcursor;
+
+ /** Check if there is an inited xcursor */
+#define XCURSOR_INITED(mc) \
+ ((mc)->mc_xcursor && ((mc)->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED))
+
+ /** Update the xcursor's sub-page pointer, if any, in \b mc. Needed
+ * when the node which contains the sub-page may have moved. Called
+ * with leaf page \b mp = mc->mc_pg[\b top].
+ */
+#define XCURSOR_REFRESH(mc, top, mp) do { \
+ MDB_page *xr_pg = (mp); \
+ MDB_node *xr_node; \
+ if (!XCURSOR_INITED(mc) || (mc)->mc_ki[top] >= NUMKEYS(xr_pg)) break; \
+ xr_node = NODEPTR(xr_pg, (mc)->mc_ki[top]); \
+ if ((xr_node->mn_flags & (F_DUPDATA|F_SUBDATA)) == F_DUPDATA) \
+ (mc)->mc_xcursor->mx_cursor.mc_pg[0] = NODEDATA(xr_node); \
+} while (0)
+
+ /** State of FreeDB old pages, stored in the MDB_env */
+typedef struct MDB_pgstate {
+ pgno_t *mf_pghead; /**< Reclaimed freeDB pages, or NULL before use */
+ txnid_t mf_pglast; /**< ID of last used record, or 0 if !mf_pghead */
+} MDB_pgstate;
+
+ /** The database environment. */
+struct MDB_env {
+ HANDLE me_fd; /**< The main data file */
+ HANDLE me_lfd; /**< The lock file */
+ HANDLE me_mfd; /**< For writing and syncing the meta pages */
+ /** Failed to update the meta page. Probably an I/O error. */
+#define MDB_FATAL_ERROR 0x80000000U
+ /** Some fields are initialized. */
+#define MDB_ENV_ACTIVE 0x20000000U
+ /** me_txkey is set */
+#define MDB_ENV_TXKEY 0x10000000U
+ /** fdatasync is unreliable */
+#define MDB_FSYNCONLY 0x08000000U
+ uint32_t me_flags; /**< @ref mdb_env */
+ unsigned int me_psize; /**< DB page size, inited from me_os_psize */
+ unsigned int me_os_psize; /**< OS page size, from #GET_PAGESIZE */
+ unsigned int me_maxreaders; /**< size of the reader table */
+ /** Max #MDB_txninfo.%mti_numreaders of interest to #mdb_env_close() */
+ volatile int me_close_readers;
+ MDB_dbi me_numdbs; /**< number of DBs opened */
+ MDB_dbi me_maxdbs; /**< size of the DB table */
+ MDB_PID_T me_pid; /**< process ID of this env */
+ char *me_path; /**< path to the DB files */
+ char *me_map; /**< the memory map of the data file */
+ MDB_txninfo *me_txns; /**< the memory map of the lock file or NULL */
+ MDB_meta *me_metas[NUM_METAS]; /**< pointers to the two meta pages */
+ void *me_pbuf; /**< scratch area for DUPSORT put() */
+ MDB_txn *me_txn; /**< current write transaction */
+ MDB_txn *me_txn0; /**< prealloc'd write transaction */
+ size_t me_mapsize; /**< size of the data memory map */
+ off_t me_size; /**< current file size */
+ pgno_t me_maxpg; /**< me_mapsize / me_psize */
+ MDB_dbx *me_dbxs; /**< array of static DB info */
+ uint16_t *me_dbflags; /**< array of flags from MDB_db.md_flags */
+ unsigned int *me_dbiseqs; /**< array of dbi sequence numbers */
+ pthread_key_t me_txkey; /**< thread-key for readers */
+ txnid_t me_pgoldest; /**< ID of oldest reader last time we looked */
+ MDB_pgstate me_pgstate; /**< state of old pages from freeDB */
+# define me_pglast me_pgstate.mf_pglast
+# define me_pghead me_pgstate.mf_pghead
+ MDB_page *me_dpages; /**< list of malloc'd blocks for re-use */
+ /** IDL of pages that became unused in a write txn */
+ MDB_IDL me_free_pgs;
+ /** ID2L of pages written during a write txn. Length MDB_IDL_UM_SIZE. */
+ MDB_ID2L me_dirty_list;
+ /** Max number of freelist items that can fit in a single overflow page */
+ int me_maxfree_1pg;
+ /** Max size of a node on a page */
+ unsigned int me_nodemax;
+#if !(MDB_MAXKEYSIZE)
+ unsigned int me_maxkey; /**< max size of a key */
+#endif
+ int me_live_reader; /**< have liveness lock in reader table */
+#ifdef _WIN32
+ int me_pidquery; /**< Used in OpenProcess */
+#endif
+#ifdef MDB_USE_POSIX_MUTEX /* Posix mutexes reside in shared mem */
+# define me_rmutex me_txns->mti_rmutex /**< Shared reader lock */
+# define me_wmutex me_txns->mti_wmutex /**< Shared writer lock */
+#else
+ mdb_mutex_t me_rmutex;
+ mdb_mutex_t me_wmutex;
+#endif
+ void *me_userctx; /**< User-settable context */
+ MDB_assert_func *me_assert_func; /**< Callback for assertion failures */
+};
+
+ /** Nested transaction */
+typedef struct MDB_ntxn {
+ MDB_txn mnt_txn; /**< the transaction */
+ MDB_pgstate mnt_pgstate; /**< parent transaction's saved freestate */
+} MDB_ntxn;
+
+ /** max number of pages to commit in one writev() call */
+#define MDB_COMMIT_PAGES 64
+#if defined(IOV_MAX) && IOV_MAX < MDB_COMMIT_PAGES
+#undef MDB_COMMIT_PAGES
+#define MDB_COMMIT_PAGES IOV_MAX
+#endif
+
+ /** max bytes to write in one call */
+#define MAX_WRITE (0x40000000U >> (sizeof(ssize_t) == 4))
+
+ /** Check \b txn and \b dbi arguments to a function */
+#define TXN_DBI_EXIST(txn, dbi, validity) \
+ ((txn) && (dbi)<(txn)->mt_numdbs && ((txn)->mt_dbflags[dbi] & (validity)))
+
+ /** Check for misused \b dbi handles */
+#define TXN_DBI_CHANGED(txn, dbi) \
+ ((txn)->mt_dbiseqs[dbi] != (txn)->mt_env->me_dbiseqs[dbi])
+
+static int mdb_page_alloc(MDB_cursor *mc, int num, MDB_page **mp);
+static int mdb_page_new(MDB_cursor *mc, uint32_t flags, int num, MDB_page **mp);
+static int mdb_page_touch(MDB_cursor *mc);
+
+#define MDB_END_NAMES {"committed", "empty-commit", "abort", "reset", \
+ "reset-tmp", "fail-begin", "fail-beginchild"}
+enum {
+ /* mdb_txn_end operation number, for logging */
+ MDB_END_COMMITTED, MDB_END_EMPTY_COMMIT, MDB_END_ABORT, MDB_END_RESET,
+ MDB_END_RESET_TMP, MDB_END_FAIL_BEGIN, MDB_END_FAIL_BEGINCHILD
+};
+#define MDB_END_OPMASK 0x0F /**< mask for #mdb_txn_end() operation number */
+#define MDB_END_UPDATE 0x10 /**< update env state (DBIs) */
+#define MDB_END_FREE 0x20 /**< free txn unless it is #MDB_env.%me_txn0 */
+#define MDB_END_SLOT MDB_NOTLS /**< release any reader slot if #MDB_NOTLS */
+static void mdb_txn_end(MDB_txn *txn, unsigned mode);
+
+static int mdb_page_get(MDB_cursor *mc, pgno_t pgno, MDB_page **mp, int *lvl);
+static int mdb_page_search_root(MDB_cursor *mc,
+ MDB_val *key, int modify);
+#define MDB_PS_MODIFY 1
+#define MDB_PS_ROOTONLY 2
+#define MDB_PS_FIRST 4
+#define MDB_PS_LAST 8
+static int mdb_page_search(MDB_cursor *mc,
+ MDB_val *key, int flags);
+static int mdb_page_merge(MDB_cursor *csrc, MDB_cursor *cdst);
+
+#define MDB_SPLIT_REPLACE MDB_APPENDDUP /**< newkey is not new */
+static int mdb_page_split(MDB_cursor *mc, MDB_val *newkey, MDB_val *newdata,
+ pgno_t newpgno, unsigned int nflags);
+
+static int mdb_env_read_header(MDB_env *env, MDB_meta *meta);
+static MDB_meta *mdb_env_pick_meta(const MDB_env *env);
+static int mdb_env_write_meta(MDB_txn *txn);
+#ifdef MDB_USE_POSIX_MUTEX /* Drop unused excl arg */
+# define mdb_env_close0(env, excl) mdb_env_close1(env)
+#endif
+static void mdb_env_close0(MDB_env *env, int excl);
+
+static MDB_node *mdb_node_search(MDB_cursor *mc, MDB_val *key, int *exactp);
+static int mdb_node_add(MDB_cursor *mc, indx_t indx,
+ MDB_val *key, MDB_val *data, pgno_t pgno, unsigned int flags);
+static void mdb_node_del(MDB_cursor *mc, int ksize);
+static void mdb_node_shrink(MDB_page *mp, indx_t indx);
+static int mdb_node_move(MDB_cursor *csrc, MDB_cursor *cdst, int fromleft);
+static int mdb_node_read(MDB_cursor *mc, MDB_node *leaf, MDB_val *data);
+static size_t mdb_leaf_size(MDB_env *env, MDB_val *key, MDB_val *data);
+static size_t mdb_branch_size(MDB_env *env, MDB_val *key);
+
+static int mdb_rebalance(MDB_cursor *mc);
+static int mdb_update_key(MDB_cursor *mc, MDB_val *key);
+
+static void mdb_cursor_pop(MDB_cursor *mc);
+static int mdb_cursor_push(MDB_cursor *mc, MDB_page *mp);
+
+static int mdb_cursor_del0(MDB_cursor *mc);
+static int mdb_del0(MDB_txn *txn, MDB_dbi dbi, MDB_val *key, MDB_val *data, unsigned flags);
+static int mdb_cursor_sibling(MDB_cursor *mc, int move_right);
+static int mdb_cursor_next(MDB_cursor *mc, MDB_val *key, MDB_val *data, MDB_cursor_op op);
+static int mdb_cursor_prev(MDB_cursor *mc, MDB_val *key, MDB_val *data, MDB_cursor_op op);
+static int mdb_cursor_set(MDB_cursor *mc, MDB_val *key, MDB_val *data, MDB_cursor_op op,
+ int *exactp);
+static int mdb_cursor_first(MDB_cursor *mc, MDB_val *key, MDB_val *data);
+static int mdb_cursor_last(MDB_cursor *mc, MDB_val *key, MDB_val *data);
+
+static void mdb_cursor_init(MDB_cursor *mc, MDB_txn *txn, MDB_dbi dbi, MDB_xcursor *mx);
+static void mdb_xcursor_init0(MDB_cursor *mc);
+static void mdb_xcursor_init1(MDB_cursor *mc, MDB_node *node);
+static void mdb_xcursor_init2(MDB_cursor *mc, MDB_xcursor *src_mx, int force);
+
+static int mdb_drop0(MDB_cursor *mc, int subs);
+static void mdb_default_cmp(MDB_txn *txn, MDB_dbi dbi);
+static int mdb_reader_check0(MDB_env *env, int rlocked, int *dead);
+
+/** @cond */
+static MDB_cmp_func mdb_cmp_memn, mdb_cmp_memnr, mdb_cmp_int, mdb_cmp_cint, mdb_cmp_long;
+/** @endcond */
+
+/** Compare two items pointing at size_t's of unknown alignment. */
+#ifdef MISALIGNED_OK
+# define mdb_cmp_clong mdb_cmp_long
+#else
+# define mdb_cmp_clong mdb_cmp_cint
+#endif
+
+#ifdef _WIN32
+static SECURITY_DESCRIPTOR mdb_null_sd;
+static SECURITY_ATTRIBUTES mdb_all_sa;
+static int mdb_sec_inited;
+
+struct MDB_name;
+static int utf8_to_utf16(const char *src, struct MDB_name *dst, int xtra);
+#endif
+
+/** Return the library version info. */
+char * ESECT
+mdb_version(int *major, int *minor, int *patch)
+{
+ if (major) *major = MDB_VERSION_MAJOR;
+ if (minor) *minor = MDB_VERSION_MINOR;
+ if (patch) *patch = MDB_VERSION_PATCH;
+ return MDB_VERSION_STRING;
+}
+
+/** Table of descriptions for LMDB @ref errors */
+static char *const mdb_errstr[] = {
+ "MDB_KEYEXIST: Key/data pair already exists",
+ "MDB_NOTFOUND: No matching key/data pair found",
+ "MDB_PAGE_NOTFOUND: Requested page not found",
+ "MDB_CORRUPTED: Located page was wrong type",
+ "MDB_PANIC: Update of meta page failed or environment had fatal error",
+ "MDB_VERSION_MISMATCH: Database environment version mismatch",
+ "MDB_INVALID: File is not an LMDB file",
+ "MDB_MAP_FULL: Environment mapsize limit reached",
+ "MDB_DBS_FULL: Environment maxdbs limit reached",
+ "MDB_READERS_FULL: Environment maxreaders limit reached",
+ "MDB_TLS_FULL: Thread-local storage keys full - too many environments open",
+ "MDB_TXN_FULL: Transaction has too many dirty pages - transaction too big",
+ "MDB_CURSOR_FULL: Internal error - cursor stack limit reached",
+ "MDB_PAGE_FULL: Internal error - page has no more space",
+ "MDB_MAP_RESIZED: Database contents grew beyond environment mapsize",
+ "MDB_INCOMPATIBLE: Operation and DB incompatible, or DB flags changed",
+ "MDB_BAD_RSLOT: Invalid reuse of reader locktable slot",
+ "MDB_BAD_TXN: Transaction must abort, has a child, or is invalid",
+ "MDB_BAD_VALSIZE: Unsupported size of key/DB name/data, or wrong DUPFIXED size",
+ "MDB_BAD_DBI: The specified DBI handle was closed/changed unexpectedly",
+};
+
+char *
+mdb_strerror(int err)
+{
+#ifdef _WIN32
+ /** HACK: pad 4KB on stack over the buf. Return system msgs in buf.
+ * This works as long as no function between the call to mdb_strerror
+ * and the actual use of the message uses more than 4K of stack.
+ */
+#define MSGSIZE 1024
+#define PADSIZE 4096
+ char buf[MSGSIZE+PADSIZE], *ptr = buf;
+#endif
+ int i;
+ if (!err)
+ return ("Successful return: 0");
+
+ if (err >= MDB_KEYEXIST && err <= MDB_LAST_ERRCODE) {
+ i = err - MDB_KEYEXIST;
+ return mdb_errstr[i];
+ }
+
+#ifdef _WIN32
+ /* These are the C-runtime error codes we use. The comment indicates
+ * their numeric value, and the Win32 error they would correspond to
+ * if the error actually came from a Win32 API. A major mess, we should
+ * have used LMDB-specific error codes for everything.
+ */
+ switch(err) {
+ case ENOENT: /* 2, FILE_NOT_FOUND */
+ case EIO: /* 5, ACCESS_DENIED */
+ case ENOMEM: /* 12, INVALID_ACCESS */
+ case EACCES: /* 13, INVALID_DATA */
+ case EBUSY: /* 16, CURRENT_DIRECTORY */
+ case EINVAL: /* 22, BAD_COMMAND */
+ case ENOSPC: /* 28, OUT_OF_PAPER */
+ return strerror(err);
+ default:
+ ;
+ }
+ buf[0] = 0;
+ FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL, err, 0, ptr, MSGSIZE, (va_list *)buf+MSGSIZE);
+ return ptr;
+#else
+ return strerror(err);
+#endif
+}
+
+/** assert(3) variant in cursor context */
+#define mdb_cassert(mc, expr) mdb_assert0((mc)->mc_txn->mt_env, expr, #expr)
+/** assert(3) variant in transaction context */
+#define mdb_tassert(txn, expr) mdb_assert0((txn)->mt_env, expr, #expr)
+/** assert(3) variant in environment context */
+#define mdb_eassert(env, expr) mdb_assert0(env, expr, #expr)
+
+#ifndef NDEBUG
+# define mdb_assert0(env, expr, expr_txt) ((expr) ? (void)0 : \
+ mdb_assert_fail(env, expr_txt, mdb_func_, __FILE__, __LINE__))
+
+static void ESECT
+mdb_assert_fail(MDB_env *env, const char *expr_txt,
+ const char *func, const char *file, int line)
+{
+ char buf[400];
+ sprintf(buf, "%.100s:%d: Assertion '%.200s' failed in %.40s()",
+ file, line, expr_txt, func);
+ if (env->me_assert_func)
+ env->me_assert_func(env, buf);
+ fprintf(stderr, "%s\n", buf);
+ abort();
+}
+#else
+# define mdb_assert0(env, expr, expr_txt) ((void) 0)
+#endif /* NDEBUG */
+
+#if MDB_DEBUG
+/** Return the page number of \b mp which may be sub-page, for debug output */
+static pgno_t
+mdb_dbg_pgno(MDB_page *mp)
+{
+ pgno_t ret;
+ COPY_PGNO(ret, mp->mp_pgno);
+ return ret;
+}
+
+/** Display a key in hexadecimal and return the address of the result.
+ * @param[in] key the key to display
+ * @param[in] buf the buffer to write into. Should always be #DKBUF.
+ * @return The key in hexadecimal form.
+ */
+char *
+mdb_dkey(MDB_val *key, char *buf)
+{
+ char *ptr = buf;
+ unsigned char *c = key->mv_data;
+ unsigned int i;
+
+ if (!key)
+ return "";
+
+ if (key->mv_size > DKBUF_MAXKEYSIZE)
+ return "MDB_MAXKEYSIZE";
+ /* may want to make this a dynamic check: if the key is mostly
+ * printable characters, print it as-is instead of converting to hex.
+ */
+#if 1
+ buf[0] = '\0';
+ for (i=0; i<key->mv_size; i++)
+ ptr += sprintf(ptr, "%02x", *c++);
+#else
+ sprintf(buf, "%.*s", key->mv_size, key->mv_data);
+#endif
+ return buf;
+}
+
+static const char *
+mdb_leafnode_type(MDB_node *n)
+{
+ static char *const tp[2][2] = {{"", ": DB"}, {": sub-page", ": sub-DB"}};
+ return F_ISSET(n->mn_flags, F_BIGDATA) ? ": overflow page" :
+ tp[F_ISSET(n->mn_flags, F_DUPDATA)][F_ISSET(n->mn_flags, F_SUBDATA)];
+}
+
+/** Display all the keys in the page. */
+void
+mdb_page_list(MDB_page *mp)
+{
+ pgno_t pgno = mdb_dbg_pgno(mp);
+ const char *type, *state = (mp->mp_flags & P_DIRTY) ? ", dirty" : "";
+ MDB_node *node;
+ unsigned int i, nkeys, nsize, total = 0;
+ MDB_val key;
+ DKBUF;
+
+ switch (mp->mp_flags & (P_BRANCH|P_LEAF|P_LEAF2|P_META|P_OVERFLOW|P_SUBP)) {
+ case P_BRANCH: type = "Branch page"; break;
+ case P_LEAF: type = "Leaf page"; break;
+ case P_LEAF|P_SUBP: type = "Sub-page"; break;
+ case P_LEAF|P_LEAF2: type = "LEAF2 page"; break;
+ case P_LEAF|P_LEAF2|P_SUBP: type = "LEAF2 sub-page"; break;
+ case P_OVERFLOW:
+ fprintf(stderr, "Overflow page %"Z"u pages %u%s\n",
+ pgno, mp->mp_pages, state);
+ return;
+ case P_META:
+ fprintf(stderr, "Meta-page %"Z"u txnid %"Z"u\n",
+ pgno, ((MDB_meta *)METADATA(mp))->mm_txnid);
+ return;
+ default:
+ fprintf(stderr, "Bad page %"Z"u flags 0x%X\n", pgno, mp->mp_flags);
+ return;
+ }
+
+ nkeys = NUMKEYS(mp);
+ fprintf(stderr, "%s %"Z"u numkeys %d%s\n", type, pgno, nkeys, state);
+
+ for (i=0; i<nkeys; i++) {
+ if (IS_LEAF2(mp)) { /* LEAF2 pages have no mp_ptrs[] or node headers */
+ key.mv_size = nsize = mp->mp_pad;
+ key.mv_data = LEAF2KEY(mp, i, nsize);
+ total += nsize;
+ fprintf(stderr, "key %d: nsize %d, %s\n", i, nsize, DKEY(&key));
+ continue;
+ }
+ node = NODEPTR(mp, i);
+ key.mv_size = node->mn_ksize;
+ key.mv_data = node->mn_data;
+ nsize = NODESIZE + key.mv_size;
+ if (IS_BRANCH(mp)) {
+ fprintf(stderr, "key %d: page %"Z"u, %s\n", i, NODEPGNO(node),
+ DKEY(&key));
+ total += nsize;
+ } else {
+ if (F_ISSET(node->mn_flags, F_BIGDATA))
+ nsize += sizeof(pgno_t);
+ else
+ nsize += NODEDSZ(node);
+ total += nsize;
+ nsize += sizeof(indx_t);
+ fprintf(stderr, "key %d: nsize %d, %s%s\n",
+ i, nsize, DKEY(&key), mdb_leafnode_type(node));
+ }
+ total = EVEN(total);
+ }
+ fprintf(stderr, "Total: header %d + contents %d + unused %d\n",
+ IS_LEAF2(mp) ? PAGEHDRSZ : PAGEBASE + mp->mp_lower, total, SIZELEFT(mp));
+}
+
+void
+mdb_cursor_chk(MDB_cursor *mc)
+{
+ unsigned int i;
+ MDB_node *node;
+ MDB_page *mp;
+
+ if (!mc->mc_snum || !(mc->mc_flags & C_INITIALIZED)) return;
+ for (i=0; i<mc->mc_top; i++) {
+ mp = mc->mc_pg[i];
+ node = NODEPTR(mp, mc->mc_ki[i]);
+ if (NODEPGNO(node) != mc->mc_pg[i+1]->mp_pgno)
+ printf("oops!\n");
+ }
+ if (mc->mc_ki[i] >= NUMKEYS(mc->mc_pg[i]))
+ printf("ack!\n");
+ if (XCURSOR_INITED(mc)) {
+ node = NODEPTR(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top]);
+ if (((node->mn_flags & (F_DUPDATA|F_SUBDATA)) == F_DUPDATA) &&
+ mc->mc_xcursor->mx_cursor.mc_pg[0] != NODEDATA(node)) {
+ printf("blah!\n");
+ }
+ }
+}
+#endif
+
+#if (MDB_DEBUG) > 2
+/** Count all the pages in each DB and in the freelist
+ * and make sure it matches the actual number of pages
+ * being used.
+ * All named DBs must be open for a correct count.
+ */
+static void mdb_audit(MDB_txn *txn)
+{
+ MDB_cursor mc;
+ MDB_val key, data;
+ MDB_ID freecount, count;
+ MDB_dbi i;
+ int rc;
+
+ freecount = 0;
+ mdb_cursor_init(&mc, txn, FREE_DBI, NULL);
+ while ((rc = mdb_cursor_get(&mc, &key, &data, MDB_NEXT)) == 0)
+ freecount += *(MDB_ID *)data.mv_data;
+ mdb_tassert(txn, rc == MDB_NOTFOUND);
+
+ count = 0;
+ for (i = 0; i<txn->mt_numdbs; i++) {
+ MDB_xcursor mx;
+ if (!(txn->mt_dbflags[i] & DB_VALID))
+ continue;
+ mdb_cursor_init(&mc, txn, i, &mx);
+ if (txn->mt_dbs[i].md_root == P_INVALID)
+ continue;
+ count += txn->mt_dbs[i].md_branch_pages +
+ txn->mt_dbs[i].md_leaf_pages +
+ txn->mt_dbs[i].md_overflow_pages;
+ if (txn->mt_dbs[i].md_flags & MDB_DUPSORT) {
+ rc = mdb_page_search(&mc, NULL, MDB_PS_FIRST);
+ for (; rc == MDB_SUCCESS; rc = mdb_cursor_sibling(&mc, 1)) {
+ unsigned j;
+ MDB_page *mp;
+ mp = mc.mc_pg[mc.mc_top];
+ for (j=0; j<NUMKEYS(mp); j++) {
+ MDB_node *leaf = NODEPTR(mp, j);
+ if (leaf->mn_flags & F_SUBDATA) {
+ MDB_db db;
+ memcpy(&db, NODEDATA(leaf), sizeof(db));
+ count += db.md_branch_pages + db.md_leaf_pages +
+ db.md_overflow_pages;
+ }
+ }
+ }
+ mdb_tassert(txn, rc == MDB_NOTFOUND);
+ }
+ }
+ if (freecount + count + NUM_METAS != txn->mt_next_pgno) {
+ fprintf(stderr, "audit: %"Z"u freecount: %"Z"u count: %"Z"u total: %"Z"u next_pgno: %"Z"u\n",
+ txn->mt_txnid, freecount, count+NUM_METAS,
+ freecount+count+NUM_METAS, txn->mt_next_pgno);
+ }
+}
+#endif
+
+int
+mdb_cmp(MDB_txn *txn, MDB_dbi dbi, const MDB_val *a, const MDB_val *b)
+{
+ return txn->mt_dbxs[dbi].md_cmp(a, b);
+}
+
+int
+mdb_dcmp(MDB_txn *txn, MDB_dbi dbi, const MDB_val *a, const MDB_val *b)
+{
+ MDB_cmp_func *dcmp = txn->mt_dbxs[dbi].md_dcmp;
+#if UINT_MAX < SIZE_MAX
+ if (dcmp == mdb_cmp_int && a->mv_size == sizeof(size_t))
+ dcmp = mdb_cmp_clong;
+#endif
+ return dcmp(a, b);
+}
+
+/** Allocate memory for a page.
+ * Re-use old malloc'd pages first for singletons, otherwise just malloc.
+ * Set #MDB_TXN_ERROR on failure.
+ */
+static MDB_page *
+mdb_page_malloc(MDB_txn *txn, unsigned num)
+{
+ MDB_env *env = txn->mt_env;
+ MDB_page *ret = env->me_dpages;
+ size_t psize = env->me_psize, sz = psize, off;
+ /* For ! #MDB_NOMEMINIT, psize counts how much to init.
+ * For a single page alloc, we init everything after the page header.
+ * For multi-page, we init the final page; if the caller needed that
+ * many pages they will be filling in at least up to the last page.
+ */
+ if (num == 1) {
+ if (ret) {
+ VGMEMP_ALLOC(env, ret, sz);
+ VGMEMP_DEFINED(ret, sizeof(ret->mp_next));
+ env->me_dpages = ret->mp_next;
+ return ret;
+ }
+ psize -= off = PAGEHDRSZ;
+ } else {
+ sz *= num;
+ off = sz - psize;
+ }
+ if ((ret = malloc(sz)) != NULL) {
+ VGMEMP_ALLOC(env, ret, sz);
+ if (!(env->me_flags & MDB_NOMEMINIT)) {
+ memset((char *)ret + off, 0, psize);
+ ret->mp_pad = 0;
+ }
+ } else {
+ txn->mt_flags |= MDB_TXN_ERROR;
+ }
+ return ret;
+}
+/** Free a single page.
+ * Saves single pages to a list, for future reuse.
+ * (This is not used for multi-page overflow pages.)
+ */
+static void
+mdb_page_free(MDB_env *env, MDB_page *mp)
+{
+ mp->mp_next = env->me_dpages;
+ VGMEMP_FREE(env, mp);
+ env->me_dpages = mp;
+}
+
+/** Free a dirty page */
+static void
+mdb_dpage_free(MDB_env *env, MDB_page *dp)
+{
+ if (!IS_OVERFLOW(dp) || dp->mp_pages == 1) {
+ mdb_page_free(env, dp);
+ } else {
+ /* large pages just get freed directly */
+ VGMEMP_FREE(env, dp);
+ free(dp);
+ }
+}
+
+/** Return all dirty pages to dpage list */
+static void
+mdb_dlist_free(MDB_txn *txn)
+{
+ MDB_env *env = txn->mt_env;
+ MDB_ID2L dl = txn->mt_u.dirty_list;
+ unsigned i, n = dl[0].mid;
+
+ for (i = 1; i <= n; i++) {
+ mdb_dpage_free(env, dl[i].mptr);
+ }
+ dl[0].mid = 0;
+}
+
+/** Loosen or free a single page.
+ * Saves single pages to a list for future reuse
+ * in this same txn. It has been pulled from the freeDB
+ * and already resides on the dirty list, but has been
+ * deleted. Use these pages first before pulling again
+ * from the freeDB.
+ *
+ * If the page wasn't dirtied in this txn, just add it
+ * to this txn's free list.
+ */
+static int
+mdb_page_loose(MDB_cursor *mc, MDB_page *mp)
+{
+ int loose = 0;
+ pgno_t pgno = mp->mp_pgno;
+ MDB_txn *txn = mc->mc_txn;
+
+ if ((mp->mp_flags & P_DIRTY) && mc->mc_dbi != FREE_DBI) {
+ if (txn->mt_parent) {
+ MDB_ID2 *dl = txn->mt_u.dirty_list;
+ /* If txn has a parent, make sure the page is in our
+ * dirty list.
+ */
+ if (dl[0].mid) {
+ unsigned x = mdb_mid2l_search(dl, pgno);
+ if (x <= dl[0].mid && dl[x].mid == pgno) {
+ if (mp != dl[x].mptr) { /* bad cursor? */
+ mc->mc_flags &= ~(C_INITIALIZED|C_EOF);
+ txn->mt_flags |= MDB_TXN_ERROR;
+ return MDB_CORRUPTED;
+ }
+ /* ok, it's ours */
+ loose = 1;
+ }
+ }
+ } else {
+ /* no parent txn, so it's just ours */
+ loose = 1;
+ }
+ }
+ if (loose) {
+ DPRINTF(("loosen db %d page %"Z"u", DDBI(mc),
+ mp->mp_pgno));
+ NEXT_LOOSE_PAGE(mp) = txn->mt_loose_pgs;
+ txn->mt_loose_pgs = mp;
+ txn->mt_loose_count++;
+ mp->mp_flags |= P_LOOSE;
+ } else {
+ int rc = mdb_midl_append(&txn->mt_free_pgs, pgno);
+ if (rc)
+ return rc;
+ }
+
+ return MDB_SUCCESS;
+}
+
+/** Set or clear P_KEEP in dirty, non-overflow, non-sub pages watched by txn.
+ * @param[in] mc A cursor handle for the current operation.
+ * @param[in] pflags Flags of the pages to update:
+ * P_DIRTY to set P_KEEP, P_DIRTY|P_KEEP to clear it.
+ * @param[in] all No shortcuts. Needed except after a full #mdb_page_flush().
+ * @return 0 on success, non-zero on failure.
+ */
+static int
+mdb_pages_xkeep(MDB_cursor *mc, unsigned pflags, int all)
+{
+ enum { Mask = P_SUBP|P_DIRTY|P_LOOSE|P_KEEP };
+ MDB_txn *txn = mc->mc_txn;
+ MDB_cursor *m3, *m0 = mc;
+ MDB_xcursor *mx;
+ MDB_page *dp, *mp;
+ MDB_node *leaf;
+ unsigned i, j;
+ int rc = MDB_SUCCESS, level;
+
+ /* Mark pages seen by cursors */
+ if (mc->mc_flags & C_UNTRACK)
+ mc = NULL; /* will find mc in mt_cursors */
+ for (i = txn->mt_numdbs;; mc = txn->mt_cursors[--i]) {
+ for (; mc; mc=mc->mc_next) {
+ if (!(mc->mc_flags & C_INITIALIZED))
+ continue;
+ for (m3 = mc;; m3 = &mx->mx_cursor) {
+ mp = NULL;
+ for (j=0; j<m3->mc_snum; j++) {
+ mp = m3->mc_pg[j];
+ if ((mp->mp_flags & Mask) == pflags)
+ mp->mp_flags ^= P_KEEP;
+ }
+ mx = m3->mc_xcursor;
+ /* Proceed to mx if it is at a sub-database */
+ if (! (mx && (mx->mx_cursor.mc_flags & C_INITIALIZED)))
+ break;
+ if (! (mp && (mp->mp_flags & P_LEAF)))
+ break;
+ leaf = NODEPTR(mp, m3->mc_ki[j-1]);
+ if (!(leaf->mn_flags & F_SUBDATA))
+ break;
+ }
+ }
+ if (i == 0)
+ break;
+ }
+
+ if (all) {
+ /* Mark dirty root pages */
+ for (i=0; i<txn->mt_numdbs; i++) {
+ if (txn->mt_dbflags[i] & DB_DIRTY) {
+ pgno_t pgno = txn->mt_dbs[i].md_root;
+ if (pgno == P_INVALID)
+ continue;
+ if ((rc = mdb_page_get(m0, pgno, &dp, &level)) != MDB_SUCCESS)
+ break;
+ if ((dp->mp_flags & Mask) == pflags && level <= 1)
+ dp->mp_flags ^= P_KEEP;
+ }
+ }
+ }
+
+ return rc;
+}
+
+static int mdb_page_flush(MDB_txn *txn, int keep);
+
+/** Spill pages from the dirty list back to disk.
+ * This is intended to prevent running into #MDB_TXN_FULL situations,
+ * but note that they may still occur in a few cases:
+ * 1) our estimate of the txn size could be too small. Currently this
+ * seems unlikely, except with a large number of #MDB_MULTIPLE items.
+ * 2) child txns may run out of space if their parents dirtied a
+ * lot of pages and never spilled them. TODO: we probably should do
+ * a preemptive spill during #mdb_txn_begin() of a child txn, if
+ * the parent's dirty_room is below a given threshold.
+ *
+ * Otherwise, if not using nested txns, it is expected that apps will
+ * not run into #MDB_TXN_FULL any more. The pages are flushed to disk
+ * the same way as for a txn commit, e.g. their P_DIRTY flag is cleared.
+ * If the txn never references them again, they can be left alone.
+ * If the txn only reads them, they can be used without any fuss.
+ * If the txn writes them again, they can be dirtied immediately without
+ * going thru all of the work of #mdb_page_touch(). Such references are
+ * handled by #mdb_page_unspill().
+ *
+ * Also note, we never spill DB root pages, nor pages of active cursors,
+ * because we'll need these back again soon anyway. And in nested txns,
+ * we can't spill a page in a child txn if it was already spilled in a
+ * parent txn. That would alter the parent txns' data even though
+ * the child hasn't committed yet, and we'd have no way to undo it if
+ * the child aborted.
+ *
+ * @param[in] m0 cursor A cursor handle identifying the transaction and
+ * database for which we are checking space.
+ * @param[in] key For a put operation, the key being stored.
+ * @param[in] data For a put operation, the data being stored.
+ * @return 0 on success, non-zero on failure.
+ */
+static int
+mdb_page_spill(MDB_cursor *m0, MDB_val *key, MDB_val *data)
+{
+ MDB_txn *txn = m0->mc_txn;
+ MDB_page *dp;
+ MDB_ID2L dl = txn->mt_u.dirty_list;
+ unsigned int i, j, need;
+ int rc;
+
+ if (m0->mc_flags & C_SUB)
+ return MDB_SUCCESS;
+
+ /* Estimate how much space this op will take */
+ i = m0->mc_db->md_depth;
+ /* Named DBs also dirty the main DB */
+ if (m0->mc_dbi >= CORE_DBS)
+ i += txn->mt_dbs[MAIN_DBI].md_depth;
+ /* For puts, roughly factor in the key+data size */
+ if (key)
+ i += (LEAFSIZE(key, data) + txn->mt_env->me_psize) / txn->mt_env->me_psize;
+ i += i; /* double it for good measure */
+ need = i;
+
+ if (txn->mt_dirty_room > i)
+ return MDB_SUCCESS;
+
+ if (!txn->mt_spill_pgs) {
+ txn->mt_spill_pgs = mdb_midl_alloc(MDB_IDL_UM_MAX);
+ if (!txn->mt_spill_pgs)
+ return ENOMEM;
+ } else {
+ /* purge deleted slots */
+ MDB_IDL sl = txn->mt_spill_pgs;
+ unsigned int num = sl[0];
+ j=0;
+ for (i=1; i<=num; i++) {
+ if (!(sl[i] & 1))
+ sl[++j] = sl[i];
+ }
+ sl[0] = j;
+ }
+
+ /* Preserve pages which may soon be dirtied again */
+ if ((rc = mdb_pages_xkeep(m0, P_DIRTY, 1)) != MDB_SUCCESS)
+ goto done;
+
+ /* Less aggressive spill - we originally spilled the entire dirty list,
+ * with a few exceptions for cursor pages and DB root pages. But this
+ * turns out to be a lot of wasted effort because in a large txn many
+ * of those pages will need to be used again. So now we spill only 1/8th
+ * of the dirty pages. Testing revealed this to be a good tradeoff,
+ * better than 1/2, 1/4, or 1/10.
+ */
+ if (need < MDB_IDL_UM_MAX / 8)
+ need = MDB_IDL_UM_MAX / 8;
+
+ /* Save the page IDs of all the pages we're flushing */
+ /* flush from the tail forward, this saves a lot of shifting later on. */
+ for (i=dl[0].mid; i && need; i--) {
+ MDB_ID pn = dl[i].mid << 1;
+ dp = dl[i].mptr;
+ if (dp->mp_flags & (P_LOOSE|P_KEEP))
+ continue;
+ /* Can't spill twice, make sure it's not already in a parent's
+ * spill list.
+ */
+ if (txn->mt_parent) {
+ MDB_txn *tx2;
+ for (tx2 = txn->mt_parent; tx2; tx2 = tx2->mt_parent) {
+ if (tx2->mt_spill_pgs) {
+ j = mdb_midl_search(tx2->mt_spill_pgs, pn);
+ if (j <= tx2->mt_spill_pgs[0] && tx2->mt_spill_pgs[j] == pn) {
+ dp->mp_flags |= P_KEEP;
+ break;
+ }
+ }
+ }
+ if (tx2)
+ continue;
+ }
+ if ((rc = mdb_midl_append(&txn->mt_spill_pgs, pn)))
+ goto done;
+ need--;
+ }
+ mdb_midl_sort(txn->mt_spill_pgs);
+
+ /* Flush the spilled part of dirty list */
+ if ((rc = mdb_page_flush(txn, i)) != MDB_SUCCESS)
+ goto done;
+
+ /* Reset any dirty pages we kept that page_flush didn't see */
+ rc = mdb_pages_xkeep(m0, P_DIRTY|P_KEEP, i);
+
+done:
+ txn->mt_flags |= rc ? MDB_TXN_ERROR : MDB_TXN_SPILLS;
+ return rc;
+}
+
+/** Find oldest txnid still referenced. Expects txn->mt_txnid > 0. */
+static txnid_t
+mdb_find_oldest(MDB_txn *txn)
+{
+ int i;
+ txnid_t mr, oldest = txn->mt_txnid - 1;
+ if (txn->mt_env->me_txns) {
+ MDB_reader *r = txn->mt_env->me_txns->mti_readers;
+ for (i = txn->mt_env->me_txns->mti_numreaders; --i >= 0; ) {
+ if (r[i].mr_pid) {
+ mr = r[i].mr_txnid;
+ if (oldest > mr)
+ oldest = mr;
+ }
+ }
+ }
+ return oldest;
+}
+
+/** Add a page to the txn's dirty list */
+static void
+mdb_page_dirty(MDB_txn *txn, MDB_page *mp)
+{
+ MDB_ID2 mid;
+ int rc, (*insert)(MDB_ID2L, MDB_ID2 *);
+
+ if (txn->mt_flags & MDB_TXN_WRITEMAP) {
+ insert = mdb_mid2l_append;
+ } else {
+ insert = mdb_mid2l_insert;
+ }
+ mid.mid = mp->mp_pgno;
+ mid.mptr = mp;
+ rc = insert(txn->mt_u.dirty_list, &mid);
+ mdb_tassert(txn, rc == 0);
+ txn->mt_dirty_room--;
+}
+
+/** Allocate page numbers and memory for writing. Maintain me_pglast,
+ * me_pghead and mt_next_pgno. Set #MDB_TXN_ERROR on failure.
+ *
+ * If there are free pages available from older transactions, they
+ * are re-used first. Otherwise allocate a new page at mt_next_pgno.
+ * Do not modify the freedB, just merge freeDB records into me_pghead[]
+ * and move me_pglast to say which records were consumed. Only this
+ * function can create me_pghead and move me_pglast/mt_next_pgno.
+ * @param[in] mc cursor A cursor handle identifying the transaction and
+ * database for which we are allocating.
+ * @param[in] num the number of pages to allocate.
+ * @param[out] mp Address of the allocated page(s). Requests for multiple pages
+ * will always be satisfied by a single contiguous chunk of memory.
+ * @return 0 on success, non-zero on failure.
+ */
+static int
+mdb_page_alloc(MDB_cursor *mc, int num, MDB_page **mp)
+{
+#ifdef MDB_PARANOID /* Seems like we can ignore this now */
+ /* Get at most <Max_retries> more freeDB records once me_pghead
+ * has enough pages. If not enough, use new pages from the map.
+ * If <Paranoid> and mc is updating the freeDB, only get new
+ * records if me_pghead is empty. Then the freelist cannot play
+ * catch-up with itself by growing while trying to save it.
+ */
+ enum { Paranoid = 1, Max_retries = 500 };
+#else
+ enum { Paranoid = 0, Max_retries = INT_MAX /*infinite*/ };
+#endif
+ int rc, retry = num * 60;
+ MDB_txn *txn = mc->mc_txn;
+ MDB_env *env = txn->mt_env;
+ pgno_t pgno, *mop = env->me_pghead;
+ unsigned i, j, mop_len = mop ? mop[0] : 0, n2 = num-1;
+ MDB_page *np;
+ txnid_t oldest = 0, last;
+ MDB_cursor_op op;
+ MDB_cursor m2;
+ int found_old = 0;
+
+ /* If there are any loose pages, just use them */
+ if (num == 1 && txn->mt_loose_pgs) {
+ np = txn->mt_loose_pgs;
+ txn->mt_loose_pgs = NEXT_LOOSE_PAGE(np);
+ txn->mt_loose_count--;
+ DPRINTF(("db %d use loose page %"Z"u", DDBI(mc),
+ np->mp_pgno));
+ *mp = np;
+ return MDB_SUCCESS;
+ }
+
+ *mp = NULL;
+
+ /* If our dirty list is already full, we can't do anything */
+ if (txn->mt_dirty_room == 0) {
+ rc = MDB_TXN_FULL;
+ goto fail;
+ }
+
+ for (op = MDB_FIRST;; op = MDB_NEXT) {
+ MDB_val key, data;
+ MDB_node *leaf;
+ pgno_t *idl;
+
+ /* Seek a big enough contiguous page range. Prefer
+ * pages at the tail, just truncating the list.
+ */
+ if (mop_len > n2) {
+ i = mop_len;
+ do {
+ pgno = mop[i];
+ if (mop[i-n2] == pgno+n2)
+ goto search_done;
+ } while (--i > n2);
+ if (--retry < 0)
+ break;
+ }
+
+ if (op == MDB_FIRST) { /* 1st iteration */
+ /* Prepare to fetch more and coalesce */
+ last = env->me_pglast;
+ oldest = env->me_pgoldest;
+ mdb_cursor_init(&m2, txn, FREE_DBI, NULL);
+ if (last) {
+ op = MDB_SET_RANGE;
+ key.mv_data = &last; /* will look up last+1 */
+ key.mv_size = sizeof(last);
+ }
+ if (Paranoid && mc->mc_dbi == FREE_DBI)
+ retry = -1;
+ }
+ if (Paranoid && retry < 0 && mop_len)
+ break;
+
+ last++;
+ /* Do not fetch more if the record will be too recent */
+ if (oldest <= last) {
+ if (!found_old) {
+ oldest = mdb_find_oldest(txn);
+ env->me_pgoldest = oldest;
+ found_old = 1;
+ }
+ if (oldest <= last)
+ break;
+ }
+ rc = mdb_cursor_get(&m2, &key, NULL, op);
+ if (rc) {
+ if (rc == MDB_NOTFOUND)
+ break;
+ goto fail;
+ }
+ last = *(txnid_t*)key.mv_data;
+ if (oldest <= last) {
+ if (!found_old) {
+ oldest = mdb_find_oldest(txn);
+ env->me_pgoldest = oldest;
+ found_old = 1;
+ }
+ if (oldest <= last)
+ break;
+ }
+ np = m2.mc_pg[m2.mc_top];
+ leaf = NODEPTR(np, m2.mc_ki[m2.mc_top]);
+ if ((rc = mdb_node_read(&m2, leaf, &data)) != MDB_SUCCESS)
+ goto fail;
+
+ idl = (MDB_ID *) data.mv_data;
+ i = idl[0];
+ if (!mop) {
+ if (!(env->me_pghead = mop = mdb_midl_alloc(i))) {
+ rc = ENOMEM;
+ goto fail;
+ }
+ } else {
+ if ((rc = mdb_midl_need(&env->me_pghead, i)) != 0)
+ goto fail;
+ mop = env->me_pghead;
+ }
+ env->me_pglast = last;
+#if (MDB_DEBUG) > 1
+ DPRINTF(("IDL read txn %"Z"u root %"Z"u num %u",
+ last, txn->mt_dbs[FREE_DBI].md_root, i));
+ for (j = i; j; j--)
+ DPRINTF(("IDL %"Z"u", idl[j]));
+#endif
+ /* Merge in descending sorted order */
+ mdb_midl_xmerge(mop, idl);
+ mop_len = mop[0];
+ }
+
+ /* Use new pages from the map when nothing suitable in the freeDB */
+ i = 0;
+ pgno = txn->mt_next_pgno;
+ if (pgno + num >= env->me_maxpg) {
+ DPUTS("DB size maxed out");
+ rc = MDB_MAP_FULL;
+ goto fail;
+ }
+
+search_done:
+ if (env->me_flags & MDB_WRITEMAP) {
+ np = (MDB_page *)(env->me_map + env->me_psize * pgno);
+ } else {
+ if (!(np = mdb_page_malloc(txn, num))) {
+ rc = ENOMEM;
+ goto fail;
+ }
+ }
+ if (i) {
+ mop[0] = mop_len -= num;
+ /* Move any stragglers down */
+ for (j = i-num; j < mop_len; )
+ mop[++j] = mop[++i];
+ } else {
+ txn->mt_next_pgno = pgno + num;
+ }
+ np->mp_pgno = pgno;
+ mdb_page_dirty(txn, np);
+ *mp = np;
+
+ return MDB_SUCCESS;
+
+fail:
+ txn->mt_flags |= MDB_TXN_ERROR;
+ return rc;
+}
+
+/** Copy the used portions of a non-overflow page.
+ * @param[in] dst page to copy into
+ * @param[in] src page to copy from
+ * @param[in] psize size of a page
+ */
+static void
+mdb_page_copy(MDB_page *dst, MDB_page *src, unsigned int psize)
+{
+ enum { Align = sizeof(pgno_t) };
+ indx_t upper = src->mp_upper, lower = src->mp_lower, unused = upper-lower;
+
+ /* If page isn't full, just copy the used portion. Adjust
+ * alignment so memcpy may copy words instead of bytes.
+ */
+ if ((unused &= -Align) && !IS_LEAF2(src)) {
+ upper = (upper + PAGEBASE) & -Align;
+ memcpy(dst, src, (lower + PAGEBASE + (Align-1)) & -Align);
+ memcpy((pgno_t *)((char *)dst+upper), (pgno_t *)((char *)src+upper),
+ psize - upper);
+ } else {
+ memcpy(dst, src, psize - unused);
+ }
+}
+
+/** Pull a page off the txn's spill list, if present.
+ * If a page being referenced was spilled to disk in this txn, bring
+ * it back and make it dirty/writable again.
+ * @param[in] txn the transaction handle.
+ * @param[in] mp the page being referenced. It must not be dirty.
+ * @param[out] ret the writable page, if any. ret is unchanged if
+ * mp wasn't spilled.
+ */
+static int
+mdb_page_unspill(MDB_txn *txn, MDB_page *mp, MDB_page **ret)
+{
+ MDB_env *env = txn->mt_env;
+ const MDB_txn *tx2;
+ unsigned x;
+ pgno_t pgno = mp->mp_pgno, pn = pgno << 1;
+
+ for (tx2 = txn; tx2; tx2=tx2->mt_parent) {
+ if (!tx2->mt_spill_pgs)
+ continue;
+ x = mdb_midl_search(tx2->mt_spill_pgs, pn);
+ if (x <= tx2->mt_spill_pgs[0] && tx2->mt_spill_pgs[x] == pn) {
+ MDB_page *np;
+ int num;
+ if (txn->mt_dirty_room == 0)
+ return MDB_TXN_FULL;
+ if (IS_OVERFLOW(mp))
+ num = mp->mp_pages;
+ else
+ num = 1;
+ if (env->me_flags & MDB_WRITEMAP) {
+ np = mp;
+ } else {
+ np = mdb_page_malloc(txn, num);
+ if (!np)
+ return ENOMEM;
+ if (num > 1)
+ memcpy(np, mp, num * env->me_psize);
+ else
+ mdb_page_copy(np, mp, env->me_psize);
+ }
+ if (tx2 == txn) {
+ /* If in current txn, this page is no longer spilled.
+ * If it happens to be the last page, truncate the spill list.
+ * Otherwise mark it as deleted by setting the LSB.
+ */
+ if (x == txn->mt_spill_pgs[0])
+ txn->mt_spill_pgs[0]--;
+ else
+ txn->mt_spill_pgs[x] |= 1;
+ } /* otherwise, if belonging to a parent txn, the
+ * page remains spilled until child commits
+ */
+
+ mdb_page_dirty(txn, np);
+ np->mp_flags |= P_DIRTY;
+ *ret = np;
+ break;
+ }
+ }
+ return MDB_SUCCESS;
+}
+
+/** Touch a page: make it dirty and re-insert into tree with updated pgno.
+ * Set #MDB_TXN_ERROR on failure.
+ * @param[in] mc cursor pointing to the page to be touched
+ * @return 0 on success, non-zero on failure.
+ */
+static int
+mdb_page_touch(MDB_cursor *mc)
+{
+ MDB_page *mp = mc->mc_pg[mc->mc_top], *np;
+ MDB_txn *txn = mc->mc_txn;
+ MDB_cursor *m2, *m3;
+ pgno_t pgno;
+ int rc;
+
+ if (!F_ISSET(mp->mp_flags, P_DIRTY)) {
+ if (txn->mt_flags & MDB_TXN_SPILLS) {
+ np = NULL;
+ rc = mdb_page_unspill(txn, mp, &np);
+ if (rc)
+ goto fail;
+ if (np)
+ goto done;
+ }
+ if ((rc = mdb_midl_need(&txn->mt_free_pgs, 1)) ||
+ (rc = mdb_page_alloc(mc, 1, &np)))
+ goto fail;
+ pgno = np->mp_pgno;
+ DPRINTF(("touched db %d page %"Z"u -> %"Z"u", DDBI(mc),
+ mp->mp_pgno, pgno));
+ mdb_cassert(mc, mp->mp_pgno != pgno);
+ mdb_midl_xappend(txn->mt_free_pgs, mp->mp_pgno);
+ /* Update the parent page, if any, to point to the new page */
+ if (mc->mc_top) {
+ MDB_page *parent = mc->mc_pg[mc->mc_top-1];
+ MDB_node *node = NODEPTR(parent, mc->mc_ki[mc->mc_top-1]);
+ SETPGNO(node, pgno);
+ } else {
+ mc->mc_db->md_root = pgno;
+ }
+ } else if (txn->mt_parent && !IS_SUBP(mp)) {
+ MDB_ID2 mid, *dl = txn->mt_u.dirty_list;
+ pgno = mp->mp_pgno;
+ /* If txn has a parent, make sure the page is in our
+ * dirty list.
+ */
+ if (dl[0].mid) {
+ unsigned x = mdb_mid2l_search(dl, pgno);
+ if (x <= dl[0].mid && dl[x].mid == pgno) {
+ if (mp != dl[x].mptr) { /* bad cursor? */
+ mc->mc_flags &= ~(C_INITIALIZED|C_EOF);
+ txn->mt_flags |= MDB_TXN_ERROR;
+ return MDB_CORRUPTED;
+ }
+ return 0;
+ }
+ }
+ mdb_cassert(mc, dl[0].mid < MDB_IDL_UM_MAX);
+ /* No - copy it */
+ np = mdb_page_malloc(txn, 1);
+ if (!np)
+ return ENOMEM;
+ mid.mid = pgno;
+ mid.mptr = np;
+ rc = mdb_mid2l_insert(dl, &mid);
+ mdb_cassert(mc, rc == 0);
+ } else {
+ return 0;
+ }
+
+ mdb_page_copy(np, mp, txn->mt_env->me_psize);
+ np->mp_pgno = pgno;
+ np->mp_flags |= P_DIRTY;
+
+done:
+ /* Adjust cursors pointing to mp */
+ mc->mc_pg[mc->mc_top] = np;
+ m2 = txn->mt_cursors[mc->mc_dbi];
+ if (mc->mc_flags & C_SUB) {
+ for (; m2; m2=m2->mc_next) {
+ m3 = &m2->mc_xcursor->mx_cursor;
+ if (m3->mc_snum < mc->mc_snum) continue;
+ if (m3->mc_pg[mc->mc_top] == mp)
+ m3->mc_pg[mc->mc_top] = np;
+ }
+ } else {
+ for (; m2; m2=m2->mc_next) {
+ if (m2->mc_snum < mc->mc_snum) continue;
+ if (m2 == mc) continue;
+ if (m2->mc_pg[mc->mc_top] == mp) {
+ m2->mc_pg[mc->mc_top] = np;
+ if (IS_LEAF(np))
+ XCURSOR_REFRESH(m2, mc->mc_top, np);
+ }
+ }
+ }
+ return 0;
+
+fail:
+ txn->mt_flags |= MDB_TXN_ERROR;
+ return rc;
+}
+
+int
+mdb_env_sync(MDB_env *env, int force)
+{
+ int rc = 0;
+ if (env->me_flags & MDB_RDONLY)
+ return EACCES;
+ if (force || !F_ISSET(env->me_flags, MDB_NOSYNC)) {
+ if (env->me_flags & MDB_WRITEMAP) {
+ int flags = ((env->me_flags & MDB_MAPASYNC) && !force)
+ ? MS_ASYNC : MS_SYNC;
+ if (MDB_MSYNC(env->me_map, env->me_mapsize, flags))
+ rc = ErrCode();
+#ifdef _WIN32
+ else if (flags == MS_SYNC && MDB_FDATASYNC(env->me_fd))
+ rc = ErrCode();
+#endif
+ } else {
+#ifdef BROKEN_FDATASYNC
+ if (env->me_flags & MDB_FSYNCONLY) {
+ if (fsync(env->me_fd))
+ rc = ErrCode();
+ } else
+#endif
+ if (MDB_FDATASYNC(env->me_fd))
+ rc = ErrCode();
+ }
+ }
+ return rc;
+}
+
+/** Back up parent txn's cursors, then grab the originals for tracking */
+static int
+mdb_cursor_shadow(MDB_txn *src, MDB_txn *dst)
+{
+ MDB_cursor *mc, *bk;
+ MDB_xcursor *mx;
+ size_t size;
+ int i;
+
+ for (i = src->mt_numdbs; --i >= 0; ) {
+ if ((mc = src->mt_cursors[i]) != NULL) {
+ size = sizeof(MDB_cursor);
+ if (mc->mc_xcursor)
+ size += sizeof(MDB_xcursor);
+ for (; mc; mc = bk->mc_next) {
+ bk = malloc(size);
+ if (!bk)
+ return ENOMEM;
+ *bk = *mc;
+ mc->mc_backup = bk;
+ mc->mc_db = &dst->mt_dbs[i];
+ /* Kill pointers into src to reduce abuse: The
+ * user may not use mc until dst ends. But we need a valid
+ * txn pointer here for cursor fixups to keep working.
+ */
+ mc->mc_txn = dst;
+ mc->mc_dbflag = &dst->mt_dbflags[i];
+ if ((mx = mc->mc_xcursor) != NULL) {
+ *(MDB_xcursor *)(bk+1) = *mx;
+ mx->mx_cursor.mc_txn = dst;
+ }
+ mc->mc_next = dst->mt_cursors[i];
+ dst->mt_cursors[i] = mc;
+ }
+ }
+ }
+ return MDB_SUCCESS;
+}
+
+/** Close this write txn's cursors, give parent txn's cursors back to parent.
+ * @param[in] txn the transaction handle.
+ * @param[in] merge true to keep changes to parent cursors, false to revert.
+ * @return 0 on success, non-zero on failure.
+ */
+static void
+mdb_cursors_close(MDB_txn *txn, unsigned merge)
+{
+ MDB_cursor **cursors = txn->mt_cursors, *mc, *next, *bk;
+ MDB_xcursor *mx;
+ int i;
+
+ for (i = txn->mt_numdbs; --i >= 0; ) {
+ for (mc = cursors[i]; mc; mc = next) {
+ next = mc->mc_next;
+ if ((bk = mc->mc_backup) != NULL) {
+ if (merge) {
+ /* Commit changes to parent txn */
+ mc->mc_next = bk->mc_next;
+ mc->mc_backup = bk->mc_backup;
+ mc->mc_txn = bk->mc_txn;
+ mc->mc_db = bk->mc_db;
+ mc->mc_dbflag = bk->mc_dbflag;
+ if ((mx = mc->mc_xcursor) != NULL)
+ mx->mx_cursor.mc_txn = bk->mc_txn;
+ } else {
+ /* Abort nested txn */
+ *mc = *bk;
+ if ((mx = mc->mc_xcursor) != NULL)
+ *mx = *(MDB_xcursor *)(bk+1);
+ }
+ mc = bk;
+ }
+ /* Only malloced cursors are permanently tracked. */
+ free(mc);
+ }
+ cursors[i] = NULL;
+ }
+}
+
+#if !(MDB_PIDLOCK) /* Currently the same as defined(_WIN32) */
+enum Pidlock_op {
+ Pidset, Pidcheck
+};
+#else
+enum Pidlock_op {
+ Pidset = F_SETLK, Pidcheck = F_GETLK
+};
+#endif
+
+/** Set or check a pid lock. Set returns 0 on success.
+ * Check returns 0 if the process is certainly dead, nonzero if it may
+ * be alive (the lock exists or an error happened so we do not know).
+ *
+ * On Windows Pidset is a no-op, we merely check for the existence
+ * of the process with the given pid. On POSIX we use a single byte
+ * lock on the lockfile, set at an offset equal to the pid.
+ */
+static int
+mdb_reader_pid(MDB_env *env, enum Pidlock_op op, MDB_PID_T pid)
+{
+#if !(MDB_PIDLOCK) /* Currently the same as defined(_WIN32) */
+ int ret = 0;
+ HANDLE h;
+ if (op == Pidcheck) {
+ h = OpenProcess(env->me_pidquery, FALSE, pid);
+ /* No documented "no such process" code, but other program use this: */
+ if (!h)
+ return ErrCode() != ERROR_INVALID_PARAMETER;
+ /* A process exists until all handles to it close. Has it exited? */
+ ret = WaitForSingleObject(h, 0) != 0;
+ CloseHandle(h);
+ }
+ return ret;
+#else
+ for (;;) {
+ int rc;
+ struct flock lock_info;
+ memset(&lock_info, 0, sizeof(lock_info));
+ lock_info.l_type = F_WRLCK;
+ lock_info.l_whence = SEEK_SET;
+ lock_info.l_start = pid;
+ lock_info.l_len = 1;
+ if ((rc = fcntl(env->me_lfd, op, &lock_info)) == 0) {
+ if (op == F_GETLK && lock_info.l_type != F_UNLCK)
+ rc = -1;
+ } else if ((rc = ErrCode()) == EINTR) {
+ continue;
+ }
+ return rc;
+ }
+#endif
+}
+
+/** Common code for #mdb_txn_begin() and #mdb_txn_renew().
+ * @param[in] txn the transaction handle to initialize
+ * @return 0 on success, non-zero on failure.
+ */
+static int
+mdb_txn_renew0(MDB_txn *txn)
+{
+ MDB_env *env = txn->mt_env;
+ MDB_txninfo *ti = env->me_txns;
+ MDB_meta *meta;
+ unsigned int i, nr, flags = txn->mt_flags;
+ uint16_t x;
+ int rc, new_notls = 0;
+
+ if ((flags &= MDB_TXN_RDONLY) != 0) {
+ if (!ti) {
+ meta = mdb_env_pick_meta(env);
+ txn->mt_txnid = meta->mm_txnid;
+ txn->mt_u.reader = NULL;
+ } else {
+ MDB_reader *r = (env->me_flags & MDB_NOTLS) ? txn->mt_u.reader :
+ pthread_getspecific(env->me_txkey);
+ if (r) {
+ if (r->mr_pid != env->me_pid || r->mr_txnid != (txnid_t)-1)
+ return MDB_BAD_RSLOT;
+ } else {
+ MDB_PID_T pid = env->me_pid;
+ MDB_THR_T tid = pthread_self();
+ mdb_mutexref_t rmutex = env->me_rmutex;
+
+ if (!env->me_live_reader) {
+ rc = mdb_reader_pid(env, Pidset, pid);
+ if (rc)
+ return rc;
+ env->me_live_reader = 1;
+ }
+
+ if (LOCK_MUTEX(rc, env, rmutex))
+ return rc;
+ nr = ti->mti_numreaders;
+ for (i=0; i<nr; i++)
+ if (ti->mti_readers[i].mr_pid == 0)
+ break;
+ if (i == env->me_maxreaders) {
+ UNLOCK_MUTEX(rmutex);
+ return MDB_READERS_FULL;
+ }
+ r = &ti->mti_readers[i];
+ /* Claim the reader slot, carefully since other code
+ * uses the reader table un-mutexed: First reset the
+ * slot, next publish it in mti_numreaders. After
+ * that, it is safe for mdb_env_close() to touch it.
+ * When it will be closed, we can finally claim it.
+ */
+ r->mr_pid = 0;
+ r->mr_txnid = (txnid_t)-1;
+ r->mr_tid = tid;
+ if (i == nr)
+ ti->mti_numreaders = ++nr;
+ env->me_close_readers = nr;
+ r->mr_pid = pid;
+ UNLOCK_MUTEX(rmutex);
+
+ new_notls = (env->me_flags & MDB_NOTLS);
+ if (!new_notls && (rc=pthread_setspecific(env->me_txkey, r))) {
+ r->mr_pid = 0;
+ return rc;
+ }
+ }
+ do /* LY: Retry on a race, ITS#7970. */
+ r->mr_txnid = ti->mti_txnid;
+ while(r->mr_txnid != ti->mti_txnid);
+ txn->mt_txnid = r->mr_txnid;
+ txn->mt_u.reader = r;
+ meta = env->me_metas[txn->mt_txnid & 1];
+ }
+
+ } else {
+ /* Not yet touching txn == env->me_txn0, it may be active */
+ if (ti) {
+ if (LOCK_MUTEX(rc, env, env->me_wmutex))
+ return rc;
+ txn->mt_txnid = ti->mti_txnid;
+ meta = env->me_metas[txn->mt_txnid & 1];
+ } else {
+ meta = mdb_env_pick_meta(env);
+ txn->mt_txnid = meta->mm_txnid;
+ }
+ txn->mt_txnid++;
+#if MDB_DEBUG
+ if (txn->mt_txnid == mdb_debug_start)
+ mdb_debug = 1;
+#endif
+ txn->mt_child = NULL;
+ txn->mt_loose_pgs = NULL;
+ txn->mt_loose_count = 0;
+ txn->mt_dirty_room = MDB_IDL_UM_MAX;
+ txn->mt_u.dirty_list = env->me_dirty_list;
+ txn->mt_u.dirty_list[0].mid = 0;
+ txn->mt_free_pgs = env->me_free_pgs;
+ txn->mt_free_pgs[0] = 0;
+ txn->mt_spill_pgs = NULL;
+ env->me_txn = txn;
+ memcpy(txn->mt_dbiseqs, env->me_dbiseqs, env->me_maxdbs * sizeof(unsigned int));
+ }
+
+ /* Copy the DB info and flags */
+ memcpy(txn->mt_dbs, meta->mm_dbs, CORE_DBS * sizeof(MDB_db));
+
+ /* Moved to here to avoid a data race in read TXNs */
+ txn->mt_next_pgno = meta->mm_last_pg+1;
+
+ txn->mt_flags = flags;
+
+ /* Setup db info */
+ txn->mt_numdbs = env->me_numdbs;
+ for (i=CORE_DBS; i<txn->mt_numdbs; i++) {
+ x = env->me_dbflags[i];
+ txn->mt_dbs[i].md_flags = x & PERSISTENT_FLAGS;
+ txn->mt_dbflags[i] = (x & MDB_VALID) ? DB_VALID|DB_USRVALID|DB_STALE : 0;
+ }
+ txn->mt_dbflags[MAIN_DBI] = DB_VALID|DB_USRVALID;
+ txn->mt_dbflags[FREE_DBI] = DB_VALID;
+
+ if (env->me_flags & MDB_FATAL_ERROR) {
+ DPUTS("environment had fatal error, must shutdown!");
+ rc = MDB_PANIC;
+ } else if (env->me_maxpg < txn->mt_next_pgno) {
+ rc = MDB_MAP_RESIZED;
+ } else {
+ return MDB_SUCCESS;
+ }
+ mdb_txn_end(txn, new_notls /*0 or MDB_END_SLOT*/ | MDB_END_FAIL_BEGIN);
+ return rc;
+}
+
+int
+mdb_txn_renew(MDB_txn *txn)
+{
+ int rc;
+
+ if (!txn || !F_ISSET(txn->mt_flags, MDB_TXN_RDONLY|MDB_TXN_FINISHED))
+ return EINVAL;
+
+ rc = mdb_txn_renew0(txn);
+ if (rc == MDB_SUCCESS) {
+ DPRINTF(("renew txn %"Z"u%c %p on mdbenv %p, root page %"Z"u",
+ txn->mt_txnid, (txn->mt_flags & MDB_TXN_RDONLY) ? 'r' : 'w',
+ (void *)txn, (void *)txn->mt_env, txn->mt_dbs[MAIN_DBI].md_root));
+ }
+ return rc;
+}
+
+int
+mdb_txn_begin(MDB_env *env, MDB_txn *parent, unsigned int flags, MDB_txn **ret)
+{
+ MDB_txn *txn;
+ MDB_ntxn *ntxn;
+ int rc, size, tsize;
+
+ flags &= MDB_TXN_BEGIN_FLAGS;
+ flags |= env->me_flags & MDB_WRITEMAP;
+
+ if (env->me_flags & MDB_RDONLY & ~flags) /* write txn in RDONLY env */
+ return EACCES;
+
+ if (parent) {
+ /* Nested transactions: Max 1 child, write txns only, no writemap */
+ flags |= parent->mt_flags;
+ if (flags & (MDB_RDONLY|MDB_WRITEMAP|MDB_TXN_BLOCKED)) {
+ return (parent->mt_flags & MDB_TXN_RDONLY) ? EINVAL : MDB_BAD_TXN;
+ }
+ /* Child txns save MDB_pgstate and use own copy of cursors */
+ size = env->me_maxdbs * (sizeof(MDB_db)+sizeof(MDB_cursor *)+1);
+ size += tsize = sizeof(MDB_ntxn);
+ } else if (flags & MDB_RDONLY) {
+ size = env->me_maxdbs * (sizeof(MDB_db)+1);
+ size += tsize = sizeof(MDB_txn);
+ } else {
+ /* Reuse preallocated write txn. However, do not touch it until
+ * mdb_txn_renew0() succeeds, since it currently may be active.
+ */
+ txn = env->me_txn0;
+ goto renew;
+ }
+ if ((txn = calloc(1, size)) == NULL) {
+ DPRINTF(("calloc: %s", strerror(errno)));
+ return ENOMEM;
+ }
+ txn->mt_dbxs = env->me_dbxs; /* static */
+ txn->mt_dbs = (MDB_db *) ((char *)txn + tsize);
+ txn->mt_dbflags = (unsigned char *)txn + size - env->me_maxdbs;
+ txn->mt_flags = flags;
+ txn->mt_env = env;
+
+ if (parent) {
+ unsigned int i;
+ txn->mt_cursors = (MDB_cursor **)(txn->mt_dbs + env->me_maxdbs);
+ txn->mt_dbiseqs = parent->mt_dbiseqs;
+ txn->mt_u.dirty_list = malloc(sizeof(MDB_ID2)*MDB_IDL_UM_SIZE);
+ if (!txn->mt_u.dirty_list ||
+ !(txn->mt_free_pgs = mdb_midl_alloc(MDB_IDL_UM_MAX)))
+ {
+ free(txn->mt_u.dirty_list);
+ free(txn);
+ return ENOMEM;
+ }
+ txn->mt_txnid = parent->mt_txnid;
+ txn->mt_dirty_room = parent->mt_dirty_room;
+ txn->mt_u.dirty_list[0].mid = 0;
+ txn->mt_spill_pgs = NULL;
+ txn->mt_next_pgno = parent->mt_next_pgno;
+ parent->mt_flags |= MDB_TXN_HAS_CHILD;
+ parent->mt_child = txn;
+ txn->mt_parent = parent;
+ txn->mt_numdbs = parent->mt_numdbs;
+ memcpy(txn->mt_dbs, parent->mt_dbs, txn->mt_numdbs * sizeof(MDB_db));
+ /* Copy parent's mt_dbflags, but clear DB_NEW */
+ for (i=0; i<txn->mt_numdbs; i++)
+ txn->mt_dbflags[i] = parent->mt_dbflags[i] & ~DB_NEW;
+ rc = 0;
+ ntxn = (MDB_ntxn *)txn;
+ ntxn->mnt_pgstate = env->me_pgstate; /* save parent me_pghead & co */
+ if (env->me_pghead) {
+ size = MDB_IDL_SIZEOF(env->me_pghead);
+ env->me_pghead = mdb_midl_alloc(env->me_pghead[0]);
+ if (env->me_pghead)
+ memcpy(env->me_pghead, ntxn->mnt_pgstate.mf_pghead, size);
+ else
+ rc = ENOMEM;
+ }
+ if (!rc)
+ rc = mdb_cursor_shadow(parent, txn);
+ if (rc)
+ mdb_txn_end(txn, MDB_END_FAIL_BEGINCHILD);
+ } else { /* MDB_RDONLY */
+ txn->mt_dbiseqs = env->me_dbiseqs;
+renew:
+ rc = mdb_txn_renew0(txn);
+ }
+ if (rc) {
+ if (txn != env->me_txn0)
+ free(txn);
+ } else {
+ txn->mt_flags |= flags; /* could not change txn=me_txn0 earlier */
+ *ret = txn;
+ DPRINTF(("begin txn %"Z"u%c %p on mdbenv %p, root page %"Z"u",
+ txn->mt_txnid, (flags & MDB_RDONLY) ? 'r' : 'w',
+ (void *) txn, (void *) env, txn->mt_dbs[MAIN_DBI].md_root));
+ }
+
+ return rc;
+}
+
+MDB_env *
+mdb_txn_env(MDB_txn *txn)
+{
+ if(!txn) return NULL;
+ return txn->mt_env;
+}
+
+size_t
+mdb_txn_id(MDB_txn *txn)
+{
+ if(!txn) return 0;
+ return txn->mt_txnid;
+}
+
+/** Export or close DBI handles opened in this txn. */
+static void
+mdb_dbis_update(MDB_txn *txn, int keep)
+{
+ int i;
+ MDB_dbi n = txn->mt_numdbs;
+ MDB_env *env = txn->mt_env;
+ unsigned char *tdbflags = txn->mt_dbflags;
+
+ for (i = n; --i >= CORE_DBS;) {
+ if (tdbflags[i] & DB_NEW) {
+ if (keep) {
+ env->me_dbflags[i] = txn->mt_dbs[i].md_flags | MDB_VALID;
+ } else {
+ char *ptr = env->me_dbxs[i].md_name.mv_data;
+ if (ptr) {
+ env->me_dbxs[i].md_name.mv_data = NULL;
+ env->me_dbxs[i].md_name.mv_size = 0;
+ env->me_dbflags[i] = 0;
+ env->me_dbiseqs[i]++;
+ free(ptr);
+ }
+ }
+ }
+ }
+ if (keep && env->me_numdbs < n)
+ env->me_numdbs = n;
+}
+
+/** End a transaction, except successful commit of a nested transaction.
+ * May be called twice for readonly txns: First reset it, then abort.
+ * @param[in] txn the transaction handle to end
+ * @param[in] mode why and how to end the transaction
+ */
+static void
+mdb_txn_end(MDB_txn *txn, unsigned mode)
+{
+ MDB_env *env = txn->mt_env;
+#if MDB_DEBUG
+ static const char *const names[] = MDB_END_NAMES;
+#endif
+
+ /* Export or close DBI handles opened in this txn */
+ mdb_dbis_update(txn, mode & MDB_END_UPDATE);
+
+ DPRINTF(("%s txn %"Z"u%c %p on mdbenv %p, root page %"Z"u",
+ names[mode & MDB_END_OPMASK],
+ txn->mt_txnid, (txn->mt_flags & MDB_TXN_RDONLY) ? 'r' : 'w',
+ (void *) txn, (void *)env, txn->mt_dbs[MAIN_DBI].md_root));
+
+ if (F_ISSET(txn->mt_flags, MDB_TXN_RDONLY)) {
+ if (txn->mt_u.reader) {
+ txn->mt_u.reader->mr_txnid = (txnid_t)-1;
+ if (!(env->me_flags & MDB_NOTLS)) {
+ txn->mt_u.reader = NULL; /* txn does not own reader */
+ } else if (mode & MDB_END_SLOT) {
+ txn->mt_u.reader->mr_pid = 0;
+ txn->mt_u.reader = NULL;
+ } /* else txn owns the slot until it does MDB_END_SLOT */
+ }
+ txn->mt_numdbs = 0; /* prevent further DBI activity */
+ txn->mt_flags |= MDB_TXN_FINISHED;
+
+ } else if (!F_ISSET(txn->mt_flags, MDB_TXN_FINISHED)) {
+ pgno_t *pghead = env->me_pghead;
+
+ if (!(mode & MDB_END_UPDATE)) /* !(already closed cursors) */
+ mdb_cursors_close(txn, 0);
+ if (!(env->me_flags & MDB_WRITEMAP)) {
+ mdb_dlist_free(txn);
+ }
+
+ txn->mt_numdbs = 0;
+ txn->mt_flags = MDB_TXN_FINISHED;
+
+ if (!txn->mt_parent) {
+ mdb_midl_shrink(&txn->mt_free_pgs);
+ env->me_free_pgs = txn->mt_free_pgs;
+ /* me_pgstate: */
+ env->me_pghead = NULL;
+ env->me_pglast = 0;
+
+ env->me_txn = NULL;
+ mode = 0; /* txn == env->me_txn0, do not free() it */
+
+ /* The writer mutex was locked in mdb_txn_begin. */
+ if (env->me_txns)
+ UNLOCK_MUTEX(env->me_wmutex);
+ } else {
+ txn->mt_parent->mt_child = NULL;
+ txn->mt_parent->mt_flags &= ~MDB_TXN_HAS_CHILD;
+ env->me_pgstate = ((MDB_ntxn *)txn)->mnt_pgstate;
+ mdb_midl_free(txn->mt_free_pgs);
+ mdb_midl_free(txn->mt_spill_pgs);
+ free(txn->mt_u.dirty_list);
+ }
+
+ mdb_midl_free(pghead);
+ }
+
+ if (mode & MDB_END_FREE)
+ free(txn);
+}
+
+void
+mdb_txn_reset(MDB_txn *txn)
+{
+ if (txn == NULL)
+ return;
+
+ /* This call is only valid for read-only txns */
+ if (!(txn->mt_flags & MDB_TXN_RDONLY))
+ return;
+
+ mdb_txn_end(txn, MDB_END_RESET);
+}
+
+void
+mdb_txn_abort(MDB_txn *txn)
+{
+ if (txn == NULL)
+ return;
+
+ if (txn->mt_child)
+ mdb_txn_abort(txn->mt_child);
+
+ mdb_txn_end(txn, MDB_END_ABORT|MDB_END_SLOT|MDB_END_FREE);
+}
+
+/** Save the freelist as of this transaction to the freeDB.
+ * This changes the freelist. Keep trying until it stabilizes.
+ */
+static int
+mdb_freelist_save(MDB_txn *txn)
+{
+ /* env->me_pghead[] can grow and shrink during this call.
+ * env->me_pglast and txn->mt_free_pgs[] can only grow.
+ * Page numbers cannot disappear from txn->mt_free_pgs[].
+ */
+ MDB_cursor mc;
+ MDB_env *env = txn->mt_env;
+ int rc, maxfree_1pg = env->me_maxfree_1pg, more = 1;
+ txnid_t pglast = 0, head_id = 0;
+ pgno_t freecnt = 0, *free_pgs, *mop;
+ ssize_t head_room = 0, total_room = 0, mop_len, clean_limit;
+
+ mdb_cursor_init(&mc, txn, FREE_DBI, NULL);
+
+ if (env->me_pghead) {
+ /* Make sure first page of freeDB is touched and on freelist */
+ rc = mdb_page_search(&mc, NULL, MDB_PS_FIRST|MDB_PS_MODIFY);
+ if (rc && rc != MDB_NOTFOUND)
+ return rc;
+ }
+
+ if (!env->me_pghead && txn->mt_loose_pgs) {
+ /* Put loose page numbers in mt_free_pgs, since
+ * we may be unable to return them to me_pghead.
+ */
+ MDB_page *mp = txn->mt_loose_pgs;
+ if ((rc = mdb_midl_need(&txn->mt_free_pgs, txn->mt_loose_count)) != 0)
+ return rc;
+ for (; mp; mp = NEXT_LOOSE_PAGE(mp))
+ mdb_midl_xappend(txn->mt_free_pgs, mp->mp_pgno);
+ txn->mt_loose_pgs = NULL;
+ txn->mt_loose_count = 0;
+ }
+
+ /* MDB_RESERVE cancels meminit in ovpage malloc (when no WRITEMAP) */
+ clean_limit = (env->me_flags & (MDB_NOMEMINIT|MDB_WRITEMAP))
+ ? SSIZE_MAX : maxfree_1pg;
+
+ for (;;) {
+ /* Come back here after each Put() in case freelist changed */
+ MDB_val key, data;
+ pgno_t *pgs;
+ ssize_t j;
+
+ /* If using records from freeDB which we have not yet
+ * deleted, delete them and any we reserved for me_pghead.
+ */
+ while (pglast < env->me_pglast) {
+ rc = mdb_cursor_first(&mc, &key, NULL);
+ if (rc)
+ return rc;
+ pglast = head_id = *(txnid_t *)key.mv_data;
+ total_room = head_room = 0;
+ mdb_tassert(txn, pglast <= env->me_pglast);
+ rc = mdb_cursor_del(&mc, 0);
+ if (rc)
+ return rc;
+ }
+
+ /* Save the IDL of pages freed by this txn, to a single record */
+ if (freecnt < txn->mt_free_pgs[0]) {
+ if (!freecnt) {
+ /* Make sure last page of freeDB is touched and on freelist */
+ rc = mdb_page_search(&mc, NULL, MDB_PS_LAST|MDB_PS_MODIFY);
+ if (rc && rc != MDB_NOTFOUND)
+ return rc;
+ }
+ free_pgs = txn->mt_free_pgs;
+ /* Write to last page of freeDB */
+ key.mv_size = sizeof(txn->mt_txnid);
+ key.mv_data = &txn->mt_txnid;
+ do {
+ freecnt = free_pgs[0];
+ data.mv_size = MDB_IDL_SIZEOF(free_pgs);
+ rc = mdb_cursor_put(&mc, &key, &data, MDB_RESERVE);
+ if (rc)
+ return rc;
+ /* Retry if mt_free_pgs[] grew during the Put() */
+ free_pgs = txn->mt_free_pgs;
+ } while (freecnt < free_pgs[0]);
+ mdb_midl_sort(free_pgs);
+ memcpy(data.mv_data, free_pgs, data.mv_size);
+#if (MDB_DEBUG) > 1
+ {
+ unsigned int i = free_pgs[0];
+ DPRINTF(("IDL write txn %"Z"u root %"Z"u num %u",
+ txn->mt_txnid, txn->mt_dbs[FREE_DBI].md_root, i));
+ for (; i; i--)
+ DPRINTF(("IDL %"Z"u", free_pgs[i]));
+ }
+#endif
+ continue;
+ }
+
+ mop = env->me_pghead;
+ mop_len = (mop ? mop[0] : 0) + txn->mt_loose_count;
+
+ /* Reserve records for me_pghead[]. Split it if multi-page,
+ * to avoid searching freeDB for a page range. Use keys in
+ * range [1,me_pglast]: Smaller than txnid of oldest reader.
+ */
+ if (total_room >= mop_len) {
+ if (total_room == mop_len || --more < 0)
+ break;
+ } else if (head_room >= maxfree_1pg && head_id > 1) {
+ /* Keep current record (overflow page), add a new one */
+ head_id--;
+ head_room = 0;
+ }
+ /* (Re)write {key = head_id, IDL length = head_room} */
+ total_room -= head_room;
+ head_room = mop_len - total_room;
+ if (head_room > maxfree_1pg && head_id > 1) {
+ /* Overflow multi-page for part of me_pghead */
+ head_room /= head_id; /* amortize page sizes */
+ head_room += maxfree_1pg - head_room % (maxfree_1pg + 1);
+ } else if (head_room < 0) {
+ /* Rare case, not bothering to delete this record */
+ head_room = 0;
+ }
+ key.mv_size = sizeof(head_id);
+ key.mv_data = &head_id;
+ data.mv_size = (head_room + 1) * sizeof(pgno_t);
+ rc = mdb_cursor_put(&mc, &key, &data, MDB_RESERVE);
+ if (rc)
+ return rc;
+ /* IDL is initially empty, zero out at least the length */
+ pgs = (pgno_t *)data.mv_data;
+ j = head_room > clean_limit ? head_room : 0;
+ do {
+ pgs[j] = 0;
+ } while (--j >= 0);
+ total_room += head_room;
+ }
+
+ /* Return loose page numbers to me_pghead, though usually none are
+ * left at this point. The pages themselves remain in dirty_list.
+ */
+ if (txn->mt_loose_pgs) {
+ MDB_page *mp = txn->mt_loose_pgs;
+ unsigned count = txn->mt_loose_count;
+ MDB_IDL loose;
+ /* Room for loose pages + temp IDL with same */
+ if ((rc = mdb_midl_need(&env->me_pghead, 2*count+1)) != 0)
+ return rc;
+ mop = env->me_pghead;
+ loose = mop + MDB_IDL_ALLOCLEN(mop) - count;
+ for (count = 0; mp; mp = NEXT_LOOSE_PAGE(mp))
+ loose[ ++count ] = mp->mp_pgno;
+ loose[0] = count;
+ mdb_midl_sort(loose);
+ mdb_midl_xmerge(mop, loose);
+ txn->mt_loose_pgs = NULL;
+ txn->mt_loose_count = 0;
+ mop_len = mop[0];
+ }
+
+ /* Fill in the reserved me_pghead records */
+ rc = MDB_SUCCESS;
+ if (mop_len) {
+ MDB_val key, data;
+
+ mop += mop_len;
+ rc = mdb_cursor_first(&mc, &key, &data);
+ for (; !rc; rc = mdb_cursor_next(&mc, &key, &data, MDB_NEXT)) {
+ txnid_t id = *(txnid_t *)key.mv_data;
+ ssize_t len = (ssize_t)(data.mv_size / sizeof(MDB_ID)) - 1;
+ MDB_ID save;
+
+ mdb_tassert(txn, len >= 0 && id <= env->me_pglast);
+ key.mv_data = &id;
+ if (len > mop_len) {
+ len = mop_len;
+ data.mv_size = (len + 1) * sizeof(MDB_ID);
+ }
+ data.mv_data = mop -= len;
+ save = mop[0];
+ mop[0] = len;
+ rc = mdb_cursor_put(&mc, &key, &data, MDB_CURRENT);
+ mop[0] = save;
+ if (rc || !(mop_len -= len))
+ break;
+ }
+ }
+ return rc;
+}
+
+/** Flush (some) dirty pages to the map, after clearing their dirty flag.
+ * @param[in] txn the transaction that's being committed
+ * @param[in] keep number of initial pages in dirty_list to keep dirty.
+ * @return 0 on success, non-zero on failure.
+ */
+static int
+mdb_page_flush(MDB_txn *txn, int keep)
+{
+ MDB_env *env = txn->mt_env;
+ MDB_ID2L dl = txn->mt_u.dirty_list;
+ unsigned psize = env->me_psize, j;
+ int i, pagecount = dl[0].mid, rc;
+ size_t size = 0, pos = 0;
+ pgno_t pgno = 0;
+ MDB_page *dp = NULL;
+#ifdef _WIN32
+ OVERLAPPED ov;
+#else
+ struct iovec iov[MDB_COMMIT_PAGES];
+ ssize_t wpos = 0, wsize = 0, wres;
+ size_t next_pos = 1; /* impossible pos, so pos != next_pos */
+ int n = 0;
+#endif
+
+ j = i = keep;
+
+ if (env->me_flags & MDB_WRITEMAP) {
+ /* Clear dirty flags */
+ while (++i <= pagecount) {
+ dp = dl[i].mptr;
+ /* Don't flush this page yet */
+ if (dp->mp_flags & (P_LOOSE|P_KEEP)) {
+ dp->mp_flags &= ~P_KEEP;
+ dl[++j] = dl[i];
+ continue;
+ }
+ dp->mp_flags &= ~P_DIRTY;
+ }
+ goto done;
+ }
+
+ /* Write the pages */
+ for (;;) {
+ if (++i <= pagecount) {
+ dp = dl[i].mptr;
+ /* Don't flush this page yet */
+ if (dp->mp_flags & (P_LOOSE|P_KEEP)) {
+ dp->mp_flags &= ~P_KEEP;
+ dl[i].mid = 0;
+ continue;
+ }
+ pgno = dl[i].mid;
+ /* clear dirty flag */
+ dp->mp_flags &= ~P_DIRTY;
+ pos = pgno * psize;
+ size = psize;
+ if (IS_OVERFLOW(dp)) size *= dp->mp_pages;
+ }
+#ifdef _WIN32
+ else break;
+
+ /* Windows actually supports scatter/gather I/O, but only on
+ * unbuffered file handles. Since we're relying on the OS page
+ * cache for all our data, that's self-defeating. So we just
+ * write pages one at a time. We use the ov structure to set
+ * the write offset, to at least save the overhead of a Seek
+ * system call.
+ */
+ DPRINTF(("committing page %"Z"u", pgno));
+ memset(&ov, 0, sizeof(ov));
+ ov.Offset = pos & 0xffffffff;
+ ov.OffsetHigh = pos >> 16 >> 16;
+ if (!WriteFile(env->me_fd, dp, size, NULL, &ov)) {
+ rc = ErrCode();
+ DPRINTF(("WriteFile: %d", rc));
+ return rc;
+ }
+#else
+ /* Write up to MDB_COMMIT_PAGES dirty pages at a time. */
+ if (pos!=next_pos || n==MDB_COMMIT_PAGES || wsize+size>MAX_WRITE) {
+ if (n) {
+retry_write:
+ /* Write previous page(s) */
+#ifdef MDB_USE_PWRITEV
+ wres = pwritev(env->me_fd, iov, n, wpos);
+#else
+ if (n == 1) {
+ wres = pwrite(env->me_fd, iov[0].iov_base, wsize, wpos);
+ } else {
+retry_seek:
+ if (lseek(env->me_fd, wpos, SEEK_SET) == -1) {
+ rc = ErrCode();
+ if (rc == EINTR)
+ goto retry_seek;
+ DPRINTF(("lseek: %s", strerror(rc)));
+ return rc;
+ }
+ wres = writev(env->me_fd, iov, n);
+ }
+#endif
+ if (wres != wsize) {
+ if (wres < 0) {
+ rc = ErrCode();
+ if (rc == EINTR)
+ goto retry_write;
+ DPRINTF(("Write error: %s", strerror(rc)));
+ } else {
+ rc = EIO; /* TODO: Use which error code? */
+ DPUTS("short write, filesystem full?");
+ }
+ return rc;
+ }
+ n = 0;
+ }
+ if (i > pagecount)
+ break;
+ wpos = pos;
+ wsize = 0;
+ }
+ DPRINTF(("committing page %"Z"u", pgno));
+ next_pos = pos + size;
+ iov[n].iov_len = size;
+ iov[n].iov_base = (char *)dp;
+ wsize += size;
+ n++;
+#endif /* _WIN32 */
+ }
+
+ /* MIPS has cache coherency issues, this is a no-op everywhere else
+ * Note: for any size >= on-chip cache size, entire on-chip cache is
+ * flushed.
+ */
+ CACHEFLUSH(env->me_map, txn->mt_next_pgno * env->me_psize, DCACHE);
+
+ for (i = keep; ++i <= pagecount; ) {
+ dp = dl[i].mptr;
+ /* This is a page we skipped above */
+ if (!dl[i].mid) {
+ dl[++j] = dl[i];
+ dl[j].mid = dp->mp_pgno;
+ continue;
+ }
+ mdb_dpage_free(env, dp);
+ }
+
+done:
+ i--;
+ txn->mt_dirty_room += i - j;
+ dl[0].mid = j;
+ return MDB_SUCCESS;
+}
+
+int
+mdb_txn_commit(MDB_txn *txn)
+{
+ int rc;
+ unsigned int i, end_mode;
+ MDB_env *env;
+
+ if (txn == NULL)
+ return EINVAL;
+
+ /* mdb_txn_end() mode for a commit which writes nothing */
+ end_mode = MDB_END_EMPTY_COMMIT|MDB_END_UPDATE|MDB_END_SLOT|MDB_END_FREE;
+
+ if (txn->mt_child) {
+ rc = mdb_txn_commit(txn->mt_child);
+ if (rc)
+ goto fail;
+ }
+
+ env = txn->mt_env;
+
+ if (F_ISSET(txn->mt_flags, MDB_TXN_RDONLY)) {
+ goto done;
+ }
+
+ if (txn->mt_flags & (MDB_TXN_FINISHED|MDB_TXN_ERROR)) {
+ DPUTS("txn has failed/finished, can't commit");
+ if (txn->mt_parent)
+ txn->mt_parent->mt_flags |= MDB_TXN_ERROR;
+ rc = MDB_BAD_TXN;
+ goto fail;
+ }
+
+ if (txn->mt_parent) {
+ MDB_txn *parent = txn->mt_parent;
+ MDB_page **lp;
+ MDB_ID2L dst, src;
+ MDB_IDL pspill;
+ unsigned x, y, len, ps_len;
+
+ /* Append our free list to parent's */
+ rc = mdb_midl_append_list(&parent->mt_free_pgs, txn->mt_free_pgs);
+ if (rc)
+ goto fail;
+ mdb_midl_free(txn->mt_free_pgs);
+ /* Failures after this must either undo the changes
+ * to the parent or set MDB_TXN_ERROR in the parent.
+ */
+
+ parent->mt_next_pgno = txn->mt_next_pgno;
+ parent->mt_flags = txn->mt_flags;
+
+ /* Merge our cursors into parent's and close them */
+ mdb_cursors_close(txn, 1);
+
+ /* Update parent's DB table. */
+ memcpy(parent->mt_dbs, txn->mt_dbs, txn->mt_numdbs * sizeof(MDB_db));
+ parent->mt_numdbs = txn->mt_numdbs;
+ parent->mt_dbflags[FREE_DBI] = txn->mt_dbflags[FREE_DBI];
+ parent->mt_dbflags[MAIN_DBI] = txn->mt_dbflags[MAIN_DBI];
+ for (i=CORE_DBS; i<txn->mt_numdbs; i++) {
+ /* preserve parent's DB_NEW status */
+ x = parent->mt_dbflags[i] & DB_NEW;
+ parent->mt_dbflags[i] = txn->mt_dbflags[i] | x;
+ }
+
+ dst = parent->mt_u.dirty_list;
+ src = txn->mt_u.dirty_list;
+ /* Remove anything in our dirty list from parent's spill list */
+ if ((pspill = parent->mt_spill_pgs) && (ps_len = pspill[0])) {
+ x = y = ps_len;
+ pspill[0] = (pgno_t)-1;
+ /* Mark our dirty pages as deleted in parent spill list */
+ for (i=0, len=src[0].mid; ++i <= len; ) {
+ MDB_ID pn = src[i].mid << 1;
+ while (pn > pspill[x])
+ x--;
+ if (pn == pspill[x]) {
+ pspill[x] = 1;
+ y = --x;
+ }
+ }
+ /* Squash deleted pagenums if we deleted any */
+ for (x=y; ++x <= ps_len; )
+ if (!(pspill[x] & 1))
+ pspill[++y] = pspill[x];
+ pspill[0] = y;
+ }
+
+ /* Remove anything in our spill list from parent's dirty list */
+ if (txn->mt_spill_pgs && txn->mt_spill_pgs[0]) {
+ for (i=1; i<=txn->mt_spill_pgs[0]; i++) {
+ MDB_ID pn = txn->mt_spill_pgs[i];
+ if (pn & 1)
+ continue; /* deleted spillpg */
+ pn >>= 1;
+ y = mdb_mid2l_search(dst, pn);
+ if (y <= dst[0].mid && dst[y].mid == pn) {
+ free(dst[y].mptr);
+ while (y < dst[0].mid) {
+ dst[y] = dst[y+1];
+ y++;
+ }
+ dst[0].mid--;
+ }
+ }
+ }
+
+ /* Find len = length of merging our dirty list with parent's */
+ x = dst[0].mid;
+ dst[0].mid = 0; /* simplify loops */
+ if (parent->mt_parent) {
+ len = x + src[0].mid;
+ y = mdb_mid2l_search(src, dst[x].mid + 1) - 1;
+ for (i = x; y && i; y--) {
+ pgno_t yp = src[y].mid;
+ while (yp < dst[i].mid)
+ i--;
+ if (yp == dst[i].mid) {
+ i--;
+ len--;
+ }
+ }
+ } else { /* Simplify the above for single-ancestor case */
+ len = MDB_IDL_UM_MAX - txn->mt_dirty_room;
+ }
+ /* Merge our dirty list with parent's */
+ y = src[0].mid;
+ for (i = len; y; dst[i--] = src[y--]) {
+ pgno_t yp = src[y].mid;
+ while (yp < dst[x].mid)
+ dst[i--] = dst[x--];
+ if (yp == dst[x].mid)
+ free(dst[x--].mptr);
+ }
+ mdb_tassert(txn, i == x);
+ dst[0].mid = len;
+ free(txn->mt_u.dirty_list);
+ parent->mt_dirty_room = txn->mt_dirty_room;
+ if (txn->mt_spill_pgs) {
+ if (parent->mt_spill_pgs) {
+ /* TODO: Prevent failure here, so parent does not fail */
+ rc = mdb_midl_append_list(&parent->mt_spill_pgs, txn->mt_spill_pgs);
+ if (rc)
+ parent->mt_flags |= MDB_TXN_ERROR;
+ mdb_midl_free(txn->mt_spill_pgs);
+ mdb_midl_sort(parent->mt_spill_pgs);
+ } else {
+ parent->mt_spill_pgs = txn->mt_spill_pgs;
+ }
+ }
+
+ /* Append our loose page list to parent's */
+ for (lp = &parent->mt_loose_pgs; *lp; lp = &NEXT_LOOSE_PAGE(*lp))
+ ;
+ *lp = txn->mt_loose_pgs;
+ parent->mt_loose_count += txn->mt_loose_count;
+
+ parent->mt_child = NULL;
+ mdb_midl_free(((MDB_ntxn *)txn)->mnt_pgstate.mf_pghead);
+ free(txn);
+ return rc;
+ }
+
+ if (txn != env->me_txn) {
+ DPUTS("attempt to commit unknown transaction");
+ rc = EINVAL;
+ goto fail;
+ }
+
+ mdb_cursors_close(txn, 0);
+
+ if (!txn->mt_u.dirty_list[0].mid &&
+ !(txn->mt_flags & (MDB_TXN_DIRTY|MDB_TXN_SPILLS)))
+ goto done;
+
+ DPRINTF(("committing txn %"Z"u %p on mdbenv %p, root page %"Z"u",
+ txn->mt_txnid, (void*)txn, (void*)env, txn->mt_dbs[MAIN_DBI].md_root));
+
+ /* Update DB root pointers */
+ if (txn->mt_numdbs > CORE_DBS) {
+ MDB_cursor mc;
+ MDB_dbi i;
+ MDB_val data;
+ data.mv_size = sizeof(MDB_db);
+
+ mdb_cursor_init(&mc, txn, MAIN_DBI, NULL);
+ for (i = CORE_DBS; i < txn->mt_numdbs; i++) {
+ if (txn->mt_dbflags[i] & DB_DIRTY) {
+ if (TXN_DBI_CHANGED(txn, i)) {
+ rc = MDB_BAD_DBI;
+ goto fail;
+ }
+ data.mv_data = &txn->mt_dbs[i];
+ rc = mdb_cursor_put(&mc, &txn->mt_dbxs[i].md_name, &data,
+ F_SUBDATA);
+ if (rc)
+ goto fail;
+ }
+ }
+ }
+
+ rc = mdb_freelist_save(txn);
+ if (rc)
+ goto fail;
+
+ mdb_midl_free(env->me_pghead);
+ env->me_pghead = NULL;
+ mdb_midl_shrink(&txn->mt_free_pgs);
+
+#if (MDB_DEBUG) > 2
+ mdb_audit(txn);
+#endif
+
+ if ((rc = mdb_page_flush(txn, 0)) ||
+ (rc = mdb_env_sync(env, 0)) ||
+ (rc = mdb_env_write_meta(txn)))
+ goto fail;
+ end_mode = MDB_END_COMMITTED|MDB_END_UPDATE;
+
+done:
+ mdb_txn_end(txn, end_mode);
+ return MDB_SUCCESS;
+
+fail:
+ mdb_txn_abort(txn);
+ return rc;
+}
+
+/** Read the environment parameters of a DB environment before
+ * mapping it into memory.
+ * @param[in] env the environment handle
+ * @param[out] meta address of where to store the meta information
+ * @return 0 on success, non-zero on failure.
+ */
+static int ESECT
+mdb_env_read_header(MDB_env *env, MDB_meta *meta)
+{
+ MDB_metabuf pbuf;
+ MDB_page *p;
+ MDB_meta *m;
+ int i, rc, off;
+ enum { Size = sizeof(pbuf) };
+
+ /* We don't know the page size yet, so use a minimum value.
+ * Read both meta pages so we can use the latest one.
+ */
+
+ for (i=off=0; i<NUM_METAS; i++, off += meta->mm_psize) {
+#ifdef _WIN32
+ DWORD len;
+ OVERLAPPED ov;
+ memset(&ov, 0, sizeof(ov));
+ ov.Offset = off;
+ rc = ReadFile(env->me_fd, &pbuf, Size, &len, &ov) ? (int)len : -1;
+ if (rc == -1 && ErrCode() == ERROR_HANDLE_EOF)
+ rc = 0;
+#else
+ rc = pread(env->me_fd, &pbuf, Size, off);
+#endif
+ if (rc != Size) {
+ if (rc == 0 && off == 0)
+ return ENOENT;
+ rc = rc < 0 ? (int) ErrCode() : MDB_INVALID;
+ DPRINTF(("read: %s", mdb_strerror(rc)));
+ return rc;
+ }
+
+ p = (MDB_page *)&pbuf;
+
+ if (!F_ISSET(p->mp_flags, P_META)) {
+ DPRINTF(("page %"Z"u not a meta page", p->mp_pgno));
+ return MDB_INVALID;
+ }
+
+ m = METADATA(p);
+ if (m->mm_magic != MDB_MAGIC) {
+ DPUTS("meta has invalid magic");
+ return MDB_INVALID;
+ }
+
+ if (m->mm_version != MDB_DATA_VERSION) {
+ DPRINTF(("database is version %u, expected version %u",
+ m->mm_version, MDB_DATA_VERSION));
+ return MDB_VERSION_MISMATCH;
+ }
+
+ if (off == 0 || m->mm_txnid > meta->mm_txnid)
+ *meta = *m;
+ }
+ return 0;
+}
+
+/** Fill in most of the zeroed #MDB_meta for an empty database environment */
+static void ESECT
+mdb_env_init_meta0(MDB_env *env, MDB_meta *meta)
+{
+ meta->mm_magic = MDB_MAGIC;
+ meta->mm_version = MDB_DATA_VERSION;
+ meta->mm_mapsize = env->me_mapsize;
+ meta->mm_psize = env->me_psize;
+ meta->mm_last_pg = NUM_METAS-1;
+ meta->mm_flags = env->me_flags & 0xffff;
+ meta->mm_flags |= MDB_INTEGERKEY; /* this is mm_dbs[FREE_DBI].md_flags */
+ meta->mm_dbs[FREE_DBI].md_root = P_INVALID;
+ meta->mm_dbs[MAIN_DBI].md_root = P_INVALID;
+}
+
+/** Write the environment parameters of a freshly created DB environment.
+ * @param[in] env the environment handle
+ * @param[in] meta the #MDB_meta to write
+ * @return 0 on success, non-zero on failure.
+ */
+static int ESECT
+mdb_env_init_meta(MDB_env *env, MDB_meta *meta)
+{
+ MDB_page *p, *q;
+ int rc;
+ unsigned int psize;
+#ifdef _WIN32
+ DWORD len;
+ OVERLAPPED ov;
+ memset(&ov, 0, sizeof(ov));
+#define DO_PWRITE(rc, fd, ptr, size, len, pos) do { \
+ ov.Offset = pos; \
+ rc = WriteFile(fd, ptr, size, &len, &ov); } while(0)
+#else
+ int len;
+#define DO_PWRITE(rc, fd, ptr, size, len, pos) do { \
+ len = pwrite(fd, ptr, size, pos); \
+ if (len == -1 && ErrCode() == EINTR) continue; \
+ rc = (len >= 0); break; } while(1)
+#endif
+
+ DPUTS("writing new meta page");
+
+ psize = env->me_psize;
+
+ p = calloc(NUM_METAS, psize);
+ if (!p)
+ return ENOMEM;
+
+ p->mp_pgno = 0;
+ p->mp_flags = P_META;
+ *(MDB_meta *)METADATA(p) = *meta;
+
+ q = (MDB_page *)((char *)p + psize);
+ q->mp_pgno = 1;
+ q->mp_flags = P_META;
+ *(MDB_meta *)METADATA(q) = *meta;
+
+ DO_PWRITE(rc, env->me_fd, p, psize * NUM_METAS, len, 0);
+ if (!rc)
+ rc = ErrCode();
+ else if ((unsigned) len == psize * NUM_METAS)
+ rc = MDB_SUCCESS;
+ else
+ rc = ENOSPC;
+ free(p);
+ return rc;
+}
+
+/** Update the environment info to commit a transaction.
+ * @param[in] txn the transaction that's being committed
+ * @return 0 on success, non-zero on failure.
+ */
+static int
+mdb_env_write_meta(MDB_txn *txn)
+{
+ MDB_env *env;
+ MDB_meta meta, metab, *mp;
+ unsigned flags;
+ size_t mapsize;
+ off_t off;
+ int rc, len, toggle;
+ char *ptr;
+ HANDLE mfd;
+#ifdef _WIN32
+ OVERLAPPED ov;
+#else
+ int r2;
+#endif
+
+ toggle = txn->mt_txnid & 1;
+ DPRINTF(("writing meta page %d for root page %"Z"u",
+ toggle, txn->mt_dbs[MAIN_DBI].md_root));
+
+ env = txn->mt_env;
+ flags = env->me_flags;
+ mp = env->me_metas[toggle];
+ mapsize = env->me_metas[toggle ^ 1]->mm_mapsize;
+ /* Persist any increases of mapsize config */
+ if (mapsize < env->me_mapsize)
+ mapsize = env->me_mapsize;
+
+ if (flags & MDB_WRITEMAP) {
+ mp->mm_mapsize = mapsize;
+ mp->mm_dbs[FREE_DBI] = txn->mt_dbs[FREE_DBI];
+ mp->mm_dbs[MAIN_DBI] = txn->mt_dbs[MAIN_DBI];
+ mp->mm_last_pg = txn->mt_next_pgno - 1;
+#if (__GNUC__ * 100 + __GNUC_MINOR__ >= 404) && /* TODO: portability */ \
+ !(defined(__i386__) || defined(__x86_64__))
+ /* LY: issue a memory barrier, if not x86. ITS#7969 */
+ __sync_synchronize();
+#endif
+ mp->mm_txnid = txn->mt_txnid;
+ if (!(flags & (MDB_NOMETASYNC|MDB_NOSYNC))) {
+ unsigned meta_size = env->me_psize;
+ rc = (env->me_flags & MDB_MAPASYNC) ? MS_ASYNC : MS_SYNC;
+ ptr = (char *)mp - PAGEHDRSZ;
+#ifndef _WIN32 /* POSIX msync() requires ptr = start of OS page */
+ r2 = (ptr - env->me_map) & (env->me_os_psize - 1);
+ ptr -= r2;
+ meta_size += r2;
+#endif
+ if (MDB_MSYNC(ptr, meta_size, rc)) {
+ rc = ErrCode();
+ goto fail;
+ }
+ }
+ goto done;
+ }
+ metab.mm_txnid = mp->mm_txnid;
+ metab.mm_last_pg = mp->mm_last_pg;
+
+ meta.mm_mapsize = mapsize;
+ meta.mm_dbs[FREE_DBI] = txn->mt_dbs[FREE_DBI];
+ meta.mm_dbs[MAIN_DBI] = txn->mt_dbs[MAIN_DBI];
+ meta.mm_last_pg = txn->mt_next_pgno - 1;
+ meta.mm_txnid = txn->mt_txnid;
+
+ off = offsetof(MDB_meta, mm_mapsize);
+ ptr = (char *)&meta + off;
+ len = sizeof(MDB_meta) - off;
+ off += (char *)mp - env->me_map;
+
+ /* Write to the SYNC fd unless MDB_NOSYNC/MDB_NOMETASYNC.
+ * (me_mfd goes to the same file as me_fd, but writing to it
+ * also syncs to disk. Avoids a separate fdatasync() call.)
+ */
+ mfd = (flags & (MDB_NOSYNC|MDB_NOMETASYNC)) ? env->me_fd : env->me_mfd;
+#ifdef _WIN32
+ {
+ memset(&ov, 0, sizeof(ov));
+ ov.Offset = off;
+ if (!WriteFile(mfd, ptr, len, (DWORD *)&rc, &ov))
+ rc = -1;
+ }
+#else
+retry_write:
+ rc = pwrite(mfd, ptr, len, off);
+#endif
+ if (rc != len) {
+ rc = rc < 0 ? ErrCode() : EIO;
+#ifndef _WIN32
+ if (rc == EINTR)
+ goto retry_write;
+#endif
+ DPUTS("write failed, disk error?");
+ /* On a failure, the pagecache still contains the new data.
+ * Write some old data back, to prevent it from being used.
+ * Use the non-SYNC fd; we know it will fail anyway.
+ */
+ meta.mm_last_pg = metab.mm_last_pg;
+ meta.mm_txnid = metab.mm_txnid;
+#ifdef _WIN32
+ memset(&ov, 0, sizeof(ov));
+ ov.Offset = off;
+ WriteFile(env->me_fd, ptr, len, NULL, &ov);
+#else
+ r2 = pwrite(env->me_fd, ptr, len, off);
+ (void)r2; /* Silence warnings. We don't care about pwrite's return value */
+#endif
+fail:
+ env->me_flags |= MDB_FATAL_ERROR;
+ return rc;
+ }
+ /* MIPS has cache coherency issues, this is a no-op everywhere else */
+ CACHEFLUSH(env->me_map + off, len, DCACHE);
+done:
+ /* Memory ordering issues are irrelevant; since the entire writer
+ * is wrapped by wmutex, all of these changes will become visible
+ * after the wmutex is unlocked. Since the DB is multi-version,
+ * readers will get consistent data regardless of how fresh or
+ * how stale their view of these values is.
+ */
+ if (env->me_txns)
+ env->me_txns->mti_txnid = txn->mt_txnid;
+
+ return MDB_SUCCESS;
+}
+
+/** Check both meta pages to see which one is newer.
+ * @param[in] env the environment handle
+ * @return newest #MDB_meta.
+ */
+static MDB_meta *
+mdb_env_pick_meta(const MDB_env *env)
+{
+ MDB_meta *const *metas = env->me_metas;
+ return metas[ metas[0]->mm_txnid < metas[1]->mm_txnid ];
+}
+
+int ESECT
+mdb_env_create(MDB_env **env)
+{
+ MDB_env *e;
+
+ e = calloc(1, sizeof(MDB_env));
+ if (!e)
+ return ENOMEM;
+
+ e->me_maxreaders = DEFAULT_READERS;
+ e->me_maxdbs = e->me_numdbs = CORE_DBS;
+ e->me_fd = INVALID_HANDLE_VALUE;
+ e->me_lfd = INVALID_HANDLE_VALUE;
+ e->me_mfd = INVALID_HANDLE_VALUE;
+#ifdef MDB_USE_POSIX_SEM
+ e->me_rmutex = SEM_FAILED;
+ e->me_wmutex = SEM_FAILED;
+#endif
+ e->me_pid = getpid();
+ GET_PAGESIZE(e->me_os_psize);
+ VGMEMP_CREATE(e,0,0);
+ *env = e;
+ return MDB_SUCCESS;
+}
+
+static int ESECT
+mdb_env_map(MDB_env *env, void *addr)
+{
+ MDB_page *p;
+ unsigned int flags = env->me_flags;
+#ifdef _WIN32
+ int rc;
+ HANDLE mh;
+ LONG sizelo, sizehi;
+ size_t msize;
+
+ if (flags & MDB_RDONLY) {
+ /* Don't set explicit map size, use whatever exists */
+ msize = 0;
+ sizelo = 0;
+ sizehi = 0;
+ } else {
+ msize = env->me_mapsize;
+ sizelo = msize & 0xffffffff;
+ sizehi = msize >> 16 >> 16; /* only needed on Win64 */
+
+ /* Windows won't create mappings for zero length files.
+ * and won't map more than the file size.
+ * Just set the maxsize right now.
+ */
+ if (SetFilePointer(env->me_fd, sizelo, &sizehi, 0) != (DWORD)sizelo
+ || !SetEndOfFile(env->me_fd)
+ || SetFilePointer(env->me_fd, 0, NULL, 0) != 0)
+ return ErrCode();
+ }
+
+ mh = CreateFileMapping(env->me_fd, NULL, flags & MDB_WRITEMAP ?
+ PAGE_READWRITE : PAGE_READONLY,
+ sizehi, sizelo, NULL);
+ if (!mh)
+ return ErrCode();
+ env->me_map = MapViewOfFileEx(mh, flags & MDB_WRITEMAP ?
+ FILE_MAP_WRITE : FILE_MAP_READ,
+ 0, 0, msize, addr);
+ rc = env->me_map ? 0 : ErrCode();
+ CloseHandle(mh);
+ if (rc)
+ return rc;
+#else
+ int prot = PROT_READ;
+ if (flags & MDB_WRITEMAP) {
+ prot |= PROT_WRITE;
+ if (ftruncate(env->me_fd, env->me_mapsize) < 0)
+ return ErrCode();
+ }
+ env->me_map = mmap(addr, env->me_mapsize, prot, MAP_SHARED,
+ env->me_fd, 0);
+ if (env->me_map == MAP_FAILED) {
+ env->me_map = NULL;
+ return ErrCode();
+ }
+
+ if (flags & MDB_NORDAHEAD) {
+ /* Turn off readahead. It's harmful when the DB is larger than RAM. */
+#ifdef MADV_RANDOM
+ madvise(env->me_map, env->me_mapsize, MADV_RANDOM);
+#else
+#ifdef POSIX_MADV_RANDOM
+ posix_madvise(env->me_map, env->me_mapsize, POSIX_MADV_RANDOM);
+#endif /* POSIX_MADV_RANDOM */
+#endif /* MADV_RANDOM */
+ }
+#endif /* _WIN32 */
+
+ /* Can happen because the address argument to mmap() is just a
+ * hint. mmap() can pick another, e.g. if the range is in use.
+ * The MAP_FIXED flag would prevent that, but then mmap could
+ * instead unmap existing pages to make room for the new map.
+ */
+ if (addr && env->me_map != addr)
+ return EBUSY; /* TODO: Make a new MDB_* error code? */
+
+ p = (MDB_page *)env->me_map;
+ env->me_metas[0] = METADATA(p);
+ env->me_metas[1] = (MDB_meta *)((char *)env->me_metas[0] + env->me_psize);
+
+ return MDB_SUCCESS;
+}
+
+int ESECT
+mdb_env_set_mapsize(MDB_env *env, size_t size)
+{
+ /* If env is already open, caller is responsible for making
+ * sure there are no active txns.
+ */
+ if (env->me_map) {
+ int rc;
+ MDB_meta *meta;
+ void *old;
+ if (env->me_txn)
+ return EINVAL;
+ meta = mdb_env_pick_meta(env);
+ if (!size)
+ size = meta->mm_mapsize;
+ {
+ /* Silently round up to minimum if the size is too small */
+ size_t minsize = (meta->mm_last_pg + 1) * env->me_psize;
+ if (size < minsize)
+ size = minsize;
+ }
+ munmap(env->me_map, env->me_mapsize);
+ env->me_mapsize = size;
+ old = (env->me_flags & MDB_FIXEDMAP) ? env->me_map : NULL;
+ rc = mdb_env_map(env, old);
+ if (rc)
+ return rc;
+ }
+ env->me_mapsize = size;
+ if (env->me_psize)
+ env->me_maxpg = env->me_mapsize / env->me_psize;
+ return MDB_SUCCESS;
+}
+
+int ESECT
+mdb_env_set_maxdbs(MDB_env *env, MDB_dbi dbs)
+{
+ if (env->me_map)
+ return EINVAL;
+ env->me_maxdbs = dbs + CORE_DBS;
+ return MDB_SUCCESS;
+}
+
+int ESECT
+mdb_env_set_maxreaders(MDB_env *env, unsigned int readers)
+{
+ if (env->me_map || readers < 1)
+ return EINVAL;
+ env->me_maxreaders = readers;
+ return MDB_SUCCESS;
+}
+
+int ESECT
+mdb_env_get_maxreaders(MDB_env *env, unsigned int *readers)
+{
+ if (!env || !readers)
+ return EINVAL;
+ *readers = env->me_maxreaders;
+ return MDB_SUCCESS;
+}
+
+static int ESECT
+mdb_fsize(HANDLE fd, size_t *size)
+{
+#ifdef _WIN32
+ LARGE_INTEGER fsize;
+
+ if (!GetFileSizeEx(fd, &fsize))
+ return ErrCode();
+
+ *size = fsize.QuadPart;
+#else
+ struct stat st;
+
+ if (fstat(fd, &st))
+ return ErrCode();
+
+ *size = st.st_size;
+#endif
+ return MDB_SUCCESS;
+}
+
+
+#ifdef _WIN32
+typedef wchar_t mdb_nchar_t;
+# define MDB_NAME(str) L##str
+# define mdb_name_cpy wcscpy
+#else
+/** Character type for file names: char on Unix, wchar_t on Windows */
+typedef char mdb_nchar_t;
+# define MDB_NAME(str) str /**< #mdb_nchar_t[] string literal */
+# define mdb_name_cpy strcpy /**< Copy name (#mdb_nchar_t string) */
+#endif
+
+/** Filename - string of #mdb_nchar_t[] */
+typedef struct MDB_name {
+ int mn_len; /**< Length */
+ int mn_alloced; /**< True if #mn_val was malloced */
+ mdb_nchar_t *mn_val; /**< Contents */
+} MDB_name;
+
+/** Filename suffixes [datafile,lockfile][without,with MDB_NOSUBDIR] */
+static const mdb_nchar_t *const mdb_suffixes[2][2] = {
+ { MDB_NAME("/data.mdb"), MDB_NAME("") },
+ { MDB_NAME("/lock.mdb"), MDB_NAME("-lock") }
+};
+
+#define MDB_SUFFLEN 9 /**< Max string length in #mdb_suffixes[] */
+
+/** Set up filename + scratch area for filename suffix, for opening files.
+ * It should be freed with #mdb_fname_destroy().
+ * On Windows, paths are converted from char *UTF-8 to wchar_t *UTF-16.
+ *
+ * @param[in] path Pathname for #mdb_env_open().
+ * @param[in] envflags Whether a subdir and/or lockfile will be used.
+ * @param[out] fname Resulting filename, with room for a suffix if necessary.
+ */
+static int ESECT
+mdb_fname_init(const char *path, unsigned envflags, MDB_name *fname)
+{
+ int no_suffix = F_ISSET(envflags, MDB_NOSUBDIR|MDB_NOLOCK);
+ fname->mn_alloced = 0;
+#ifdef _WIN32
+ return utf8_to_utf16(path, fname, no_suffix ? 0 : MDB_SUFFLEN);
+#else
+ fname->mn_len = strlen(path);
+ if (no_suffix)
+ fname->mn_val = (char *) path;
+ else if ((fname->mn_val = malloc(fname->mn_len + MDB_SUFFLEN+1)) != NULL) {
+ fname->mn_alloced = 1;
+ strcpy(fname->mn_val, path);
+ }
+ else
+ return ENOMEM;
+ return MDB_SUCCESS;
+#endif
+}
+
+/** Destroy \b fname from #mdb_fname_init() */
+#define mdb_fname_destroy(fname) \
+ do { if ((fname).mn_alloced) free((fname).mn_val); } while (0)
+
+#ifdef O_CLOEXEC /* POSIX.1-2008: Set FD_CLOEXEC atomically at open() */
+# define MDB_CLOEXEC O_CLOEXEC
+#else
+# define MDB_CLOEXEC 0
+#endif
+
+/** File type, access mode etc. for #mdb_fopen() */
+enum mdb_fopen_type {
+#ifdef _WIN32
+ MDB_O_RDONLY, MDB_O_RDWR, MDB_O_META, MDB_O_COPY, MDB_O_LOCKS
+#else
+ /* A comment in mdb_fopen() explains some O_* flag choices. */
+ MDB_O_RDONLY= O_RDONLY, /**< for RDONLY me_fd */
+ MDB_O_RDWR = O_RDWR |O_CREAT, /**< for me_fd */
+ MDB_O_META = O_WRONLY|MDB_DSYNC |MDB_CLOEXEC, /**< for me_mfd */
+ MDB_O_COPY = O_WRONLY|O_CREAT|O_EXCL|MDB_CLOEXEC, /**< for #mdb_env_copy() */
+ /** Bitmask for open() flags in enum #mdb_fopen_type. The other bits
+ * distinguish otherwise-equal MDB_O_* constants from each other.
+ */
+ MDB_O_MASK = MDB_O_RDWR|MDB_CLOEXEC | MDB_O_RDONLY|MDB_O_META|MDB_O_COPY,
+ MDB_O_LOCKS = MDB_O_RDWR|MDB_CLOEXEC | ((MDB_O_MASK+1) & ~MDB_O_MASK) /**< for me_lfd */
+#endif
+};
+
+/** Open an LMDB file.
+ * @param[in] env The LMDB environment.
+ * @param[in,out] fname Path from from #mdb_fname_init(). A suffix is
+ * appended if necessary to create the filename, without changing mn_len.
+ * @param[in] which Determines file type, access mode, etc.
+ * @param[in] mode The Unix permissions for the file, if we create it.
+ * @param[out] res Resulting file handle.
+ * @return 0 on success, non-zero on failure.
+ */
+static int ESECT
+mdb_fopen(const MDB_env *env, MDB_name *fname,
+ enum mdb_fopen_type which, mdb_mode_t mode,
+ HANDLE *res)
+{
+ int rc = MDB_SUCCESS;
+ HANDLE fd;
+#ifdef _WIN32
+ DWORD acc, share, disp, attrs;
+#else
+ int flags;
+#endif
+
+ if (fname->mn_alloced) /* modifiable copy */
+ mdb_name_cpy(fname->mn_val + fname->mn_len,
+ mdb_suffixes[which==MDB_O_LOCKS][F_ISSET(env->me_flags, MDB_NOSUBDIR)]);
+
+ /* The directory must already exist. Usually the file need not.
+ * MDB_O_META requires the file because we already created it using
+ * MDB_O_RDWR. MDB_O_COPY must not overwrite an existing file.
+ *
+ * With MDB_O_COPY we do not want the OS to cache the writes, since
+ * the source data is already in the OS cache.
+ *
+ * The lockfile needs FD_CLOEXEC (close file descriptor on exec*())
+ * to avoid the flock() issues noted under Caveats in lmdb.h.
+ * Also set it for other filehandles which the user cannot get at
+ * and close himself, which he may need after fork(). I.e. all but
+ * me_fd, which programs do use via mdb_env_get_fd().
+ */
+
+#ifdef _WIN32
+ acc = GENERIC_READ|GENERIC_WRITE;
+ share = FILE_SHARE_READ|FILE_SHARE_WRITE;
+ disp = OPEN_ALWAYS;
+ attrs = FILE_ATTRIBUTE_NORMAL;
+ switch (which) {
+ case MDB_O_RDONLY: /* read-only datafile */
+ acc = GENERIC_READ;
+ disp = OPEN_EXISTING;
+ break;
+ case MDB_O_META: /* for writing metapages */
+ acc = GENERIC_WRITE;
+ disp = OPEN_EXISTING;
+ attrs = FILE_ATTRIBUTE_NORMAL|FILE_FLAG_WRITE_THROUGH;
+ break;
+ case MDB_O_COPY: /* mdb_env_copy() & co */
+ acc = GENERIC_WRITE;
+ share = 0;
+ disp = CREATE_NEW;
+ attrs = FILE_FLAG_NO_BUFFERING|FILE_FLAG_WRITE_THROUGH;
+ break;
+ default: break; /* silence gcc -Wswitch (not all enum values handled) */
+ }
+ fd = CreateFileW(fname->mn_val, acc, share, NULL, disp, attrs, NULL);
+#else
+ fd = open(fname->mn_val, which & MDB_O_MASK, mode);
+#endif
+
+ if (fd == INVALID_HANDLE_VALUE)
+ rc = ErrCode();
+#ifndef _WIN32
+ else {
+ if (which != MDB_O_RDONLY && which != MDB_O_RDWR) {
+ /* Set CLOEXEC if we could not pass it to open() */
+ if (!MDB_CLOEXEC && (flags = fcntl(fd, F_GETFD)) != -1)
+ (void) fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
+ }
+ if (which == MDB_O_COPY && env->me_psize >= env->me_os_psize) {
+ /* This may require buffer alignment. There is no portable
+ * way to ask how much, so we require OS pagesize alignment.
+ */
+# ifdef F_NOCACHE /* __APPLE__ */
+ (void) fcntl(fd, F_NOCACHE, 1);
+# elif defined O_DIRECT
+ /* open(...O_DIRECT...) would break on filesystems without
+ * O_DIRECT support (ITS#7682). Try to set it here instead.
+ */
+ if ((flags = fcntl(fd, F_GETFL)) != -1)
+ (void) fcntl(fd, F_SETFL, flags | O_DIRECT);
+# endif
+ }
+ }
+#endif /* !_WIN32 */
+
+ *res = fd;
+ return rc;
+}
+
+
+#ifdef BROKEN_FDATASYNC
+#include <sys/utsname.h>
+#include <sys/vfs.h>
+#endif
+
+/** Further setup required for opening an LMDB environment
+ */
+static int ESECT
+mdb_env_open2(MDB_env *env)
+{
+ unsigned int flags = env->me_flags;
+ int i, newenv = 0, rc;
+ MDB_meta meta;
+
+#ifdef _WIN32
+ /* See if we should use QueryLimited */
+ rc = GetVersion();
+ if ((rc & 0xff) > 5)
+ env->me_pidquery = MDB_PROCESS_QUERY_LIMITED_INFORMATION;
+ else
+ env->me_pidquery = PROCESS_QUERY_INFORMATION;
+#endif /* _WIN32 */
+
+#ifdef BROKEN_FDATASYNC
+ /* ext3/ext4 fdatasync is broken on some older Linux kernels.
+ * https://lkml.org/lkml/2012/9/3/83
+ * Kernels after 3.6-rc6 are known good.
+ * https://lkml.org/lkml/2012/9/10/556
+ * See if the DB is on ext3/ext4, then check for new enough kernel
+ * Kernels 2.6.32.60, 2.6.34.15, 3.2.30, and 3.5.4 are also known
+ * to be patched.
+ */
+ {
+ struct statfs st;
+ fstatfs(env->me_fd, &st);
+ while (st.f_type == 0xEF53) {
+ struct utsname uts;
+ int i;
+ uname(&uts);
+ if (uts.release[0] < '3') {
+ if (!strncmp(uts.release, "2.6.32.", 7)) {
+ i = atoi(uts.release+7);
+ if (i >= 60)
+ break; /* 2.6.32.60 and newer is OK */
+ } else if (!strncmp(uts.release, "2.6.34.", 7)) {
+ i = atoi(uts.release+7);
+ if (i >= 15)
+ break; /* 2.6.34.15 and newer is OK */
+ }
+ } else if (uts.release[0] == '3') {
+ i = atoi(uts.release+2);
+ if (i > 5)
+ break; /* 3.6 and newer is OK */
+ if (i == 5) {
+ i = atoi(uts.release+4);
+ if (i >= 4)
+ break; /* 3.5.4 and newer is OK */
+ } else if (i == 2) {
+ i = atoi(uts.release+4);
+ if (i >= 30)
+ break; /* 3.2.30 and newer is OK */
+ }
+ } else { /* 4.x and newer is OK */
+ break;
+ }
+ env->me_flags |= MDB_FSYNCONLY;
+ break;
+ }
+ }
+#endif
+
+ if ((i = mdb_env_read_header(env, &meta)) != 0) {
+ if (i != ENOENT)
+ return i;
+ DPUTS("new mdbenv");
+ newenv = 1;
+ env->me_psize = env->me_os_psize;
+ if (env->me_psize > MAX_PAGESIZE)
+ env->me_psize = MAX_PAGESIZE;
+ memset(&meta, 0, sizeof(meta));
+ mdb_env_init_meta0(env, &meta);
+ meta.mm_mapsize = DEFAULT_MAPSIZE;
+ } else {
+ env->me_psize = meta.mm_psize;
+ }
+
+ /* Was a mapsize configured? */
+ if (!env->me_mapsize) {
+ env->me_mapsize = meta.mm_mapsize;
+ }
+ {
+ /* Make sure mapsize >= committed data size. Even when using
+ * mm_mapsize, which could be broken in old files (ITS#7789).
+ */
+ size_t minsize = (meta.mm_last_pg + 1) * meta.mm_psize;
+ if (env->me_mapsize < minsize)
+ env->me_mapsize = minsize;
+ }
+ meta.mm_mapsize = env->me_mapsize;
+
+ if (newenv && !(flags & MDB_FIXEDMAP)) {
+ /* mdb_env_map() may grow the datafile. Write the metapages
+ * first, so the file will be valid if initialization fails.
+ * Except with FIXEDMAP, since we do not yet know mm_address.
+ * We could fill in mm_address later, but then a different
+ * program might end up doing that - one with a memory layout
+ * and map address which does not suit the main program.
+ */
+ rc = mdb_env_init_meta(env, &meta);
+ if (rc)
+ return rc;
+ newenv = 0;
+ }
+
+ rc = mdb_env_map(env, (flags & MDB_FIXEDMAP) ? meta.mm_address : NULL);
+ if (rc)
+ return rc;
+
+ if (newenv) {
+ if (flags & MDB_FIXEDMAP)
+ meta.mm_address = env->me_map;
+ i = mdb_env_init_meta(env, &meta);
+ if (i != MDB_SUCCESS) {
+ return i;
+ }
+ }
+
+ env->me_maxfree_1pg = (env->me_psize - PAGEHDRSZ) / sizeof(pgno_t) - 1;
+ env->me_nodemax = (((env->me_psize - PAGEHDRSZ) / MDB_MINKEYS) & -2)
+ - sizeof(indx_t);
+#if !(MDB_MAXKEYSIZE)
+ env->me_maxkey = env->me_nodemax - (NODESIZE + sizeof(MDB_db));
+#endif
+ env->me_maxpg = env->me_mapsize / env->me_psize;
+
+#if MDB_DEBUG
+ {
+ MDB_meta *meta = mdb_env_pick_meta(env);
+ MDB_db *db = &meta->mm_dbs[MAIN_DBI];
+
+ DPRINTF(("opened database version %u, pagesize %u",
+ meta->mm_version, env->me_psize));
+ DPRINTF(("using meta page %d", (int) (meta->mm_txnid & 1)));
+ DPRINTF(("depth: %u", db->md_depth));
+ DPRINTF(("entries: %"Z"u", db->md_entries));
+ DPRINTF(("branch pages: %"Z"u", db->md_branch_pages));
+ DPRINTF(("leaf pages: %"Z"u", db->md_leaf_pages));
+ DPRINTF(("overflow pages: %"Z"u", db->md_overflow_pages));
+ DPRINTF(("root: %"Z"u", db->md_root));
+ }
+#endif
+
+ return MDB_SUCCESS;
+}
+
+
+/** Release a reader thread's slot in the reader lock table.
+ * This function is called automatically when a thread exits.
+ * @param[in] ptr This points to the slot in the reader lock table.
+ */
+static void
+mdb_env_reader_dest(void *ptr)
+{
+ MDB_reader *reader = ptr;
+
+#ifndef _WIN32
+ if (reader->mr_pid == getpid()) /* catch pthread_exit() in child process */
+#endif
+ /* We omit the mutex, so do this atomically (i.e. skip mr_txnid) */
+ reader->mr_pid = 0;
+}
+
+#ifdef _WIN32
+/** Junk for arranging thread-specific callbacks on Windows. This is
+ * necessarily platform and compiler-specific. Windows supports up
+ * to 1088 keys. Let's assume nobody opens more than 64 environments
+ * in a single process, for now. They can override this if needed.
+ */
+#ifndef MAX_TLS_KEYS
+#define MAX_TLS_KEYS 64
+#endif
+static pthread_key_t mdb_tls_keys[MAX_TLS_KEYS];
+static int mdb_tls_nkeys;
+
+static void NTAPI mdb_tls_callback(PVOID module, DWORD reason, PVOID ptr)
+{
+ int i;
+ switch(reason) {
+ case DLL_PROCESS_ATTACH: break;
+ case DLL_THREAD_ATTACH: break;
+ case DLL_THREAD_DETACH:
+ for (i=0; i<mdb_tls_nkeys; i++) {
+ MDB_reader *r = pthread_getspecific(mdb_tls_keys[i]);
+ if (r) {
+ mdb_env_reader_dest(r);
+ }
+ }
+ break;
+ case DLL_PROCESS_DETACH: break;
+ }
+}
+#ifdef __GNUC__
+#ifdef _WIN64
+const PIMAGE_TLS_CALLBACK mdb_tls_cbp __attribute__((section (".CRT$XLB"))) = mdb_tls_callback;
+#else
+PIMAGE_TLS_CALLBACK mdb_tls_cbp __attribute__((section (".CRT$XLB"))) = mdb_tls_callback;
+#endif
+#else
+#ifdef _WIN64
+/* Force some symbol references.
+ * _tls_used forces the linker to create the TLS directory if not already done
+ * mdb_tls_cbp prevents whole-program-optimizer from dropping the symbol.
+ */
+#pragma comment(linker, "/INCLUDE:_tls_used")
+#pragma comment(linker, "/INCLUDE:mdb_tls_cbp")
+#pragma const_seg(".CRT$XLB")
+extern const PIMAGE_TLS_CALLBACK mdb_tls_cbp;
+const PIMAGE_TLS_CALLBACK mdb_tls_cbp = mdb_tls_callback;
+#pragma const_seg()
+#else /* _WIN32 */
+#pragma comment(linker, "/INCLUDE:__tls_used")
+#pragma comment(linker, "/INCLUDE:_mdb_tls_cbp")
+#pragma data_seg(".CRT$XLB")
+PIMAGE_TLS_CALLBACK mdb_tls_cbp = mdb_tls_callback;
+#pragma data_seg()
+#endif /* WIN 32/64 */
+#endif /* !__GNUC__ */
+#endif
+
+/** Downgrade the exclusive lock on the region back to shared */
+static int ESECT
+mdb_env_share_locks(MDB_env *env, int *excl)
+{
+ int rc = 0;
+ MDB_meta *meta = mdb_env_pick_meta(env);
+
+ env->me_txns->mti_txnid = meta->mm_txnid;
+
+#ifdef _WIN32
+ {
+ OVERLAPPED ov;
+ /* First acquire a shared lock. The Unlock will
+ * then release the existing exclusive lock.
+ */
+ memset(&ov, 0, sizeof(ov));
+ if (!LockFileEx(env->me_lfd, 0, 0, 1, 0, &ov)) {
+ rc = ErrCode();
+ } else {
+ UnlockFile(env->me_lfd, 0, 0, 1, 0);
+ *excl = 0;
+ }
+ }
+#else
+ {
+ struct flock lock_info;
+ /* The shared lock replaces the existing lock */
+ memset((void *)&lock_info, 0, sizeof(lock_info));
+ lock_info.l_type = F_RDLCK;
+ lock_info.l_whence = SEEK_SET;
+ lock_info.l_start = 0;
+ lock_info.l_len = 1;
+ while ((rc = fcntl(env->me_lfd, F_SETLK, &lock_info)) &&
+ (rc = ErrCode()) == EINTR) ;
+ *excl = rc ? -1 : 0; /* error may mean we lost the lock */
+ }
+#endif
+
+ return rc;
+}
+
+/** Try to get exclusive lock, otherwise shared.
+ * Maintain *excl = -1: no/unknown lock, 0: shared, 1: exclusive.
+ */
+static int ESECT
+mdb_env_excl_lock(MDB_env *env, int *excl)
+{
+ int rc = 0;
+#ifdef _WIN32
+ if (LockFile(env->me_lfd, 0, 0, 1, 0)) {
+ *excl = 1;
+ } else {
+ OVERLAPPED ov;
+ memset(&ov, 0, sizeof(ov));
+ if (LockFileEx(env->me_lfd, 0, 0, 1, 0, &ov)) {
+ *excl = 0;
+ } else {
+ rc = ErrCode();
+ }
+ }
+#else
+ struct flock lock_info;
+ memset((void *)&lock_info, 0, sizeof(lock_info));
+ lock_info.l_type = F_WRLCK;
+ lock_info.l_whence = SEEK_SET;
+ lock_info.l_start = 0;
+ lock_info.l_len = 1;
+ while ((rc = fcntl(env->me_lfd, F_SETLK, &lock_info)) &&
+ (rc = ErrCode()) == EINTR) ;
+ if (!rc) {
+ *excl = 1;
+ } else
+# ifndef MDB_USE_POSIX_MUTEX
+ if (*excl < 0) /* always true when MDB_USE_POSIX_MUTEX */
+# endif
+ {
+ lock_info.l_type = F_RDLCK;
+ while ((rc = fcntl(env->me_lfd, F_SETLKW, &lock_info)) &&
+ (rc = ErrCode()) == EINTR) ;
+ if (rc == 0)
+ *excl = 0;
+ }
+#endif
+ return rc;
+}
+
+#ifdef MDB_USE_HASH
+/*
+ * hash_64 - 64 bit Fowler/Noll/Vo-0 FNV-1a hash code
+ *
+ * @(#) $Revision: 5.1 $
+ * @(#) $Id: hash_64a.c,v 5.1 2009/06/30 09:01:38 chongo Exp $
+ * @(#) $Source: /usr/local/src/cmd/fnv/RCS/hash_64a.c,v $
+ *
+ * http://www.isthe.com/chongo/tech/comp/fnv/index.html
+ *
+ ***
+ *
+ * Please do not copyright this code. This code is in the public domain.
+ *
+ * LANDON CURT NOLL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO
+ * EVENT SHALL LANDON CURT NOLL BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
+ * USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+ * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ *
+ * By:
+ * chongo <Landon Curt Noll> /\oo/\
+ * http://www.isthe.com/chongo/
+ *
+ * Share and Enjoy! :-)
+ */
+
+typedef unsigned long long mdb_hash_t;
+#define MDB_HASH_INIT ((mdb_hash_t)0xcbf29ce484222325ULL)
+
+/** perform a 64 bit Fowler/Noll/Vo FNV-1a hash on a buffer
+ * @param[in] val value to hash
+ * @param[in] hval initial value for hash
+ * @return 64 bit hash
+ *
+ * NOTE: To use the recommended 64 bit FNV-1a hash, use MDB_HASH_INIT as the
+ * hval arg on the first call.
+ */
+static mdb_hash_t
+mdb_hash_val(MDB_val *val, mdb_hash_t hval)
+{
+ unsigned char *s = (unsigned char *)val->mv_data; /* unsigned string */
+ unsigned char *end = s + val->mv_size;
+ /*
+ * FNV-1a hash each octet of the string
+ */
+ while (s < end) {
+ /* xor the bottom with the current octet */
+ hval ^= (mdb_hash_t)*s++;
+
+ /* multiply by the 64 bit FNV magic prime mod 2^64 */
+ hval += (hval << 1) + (hval << 4) + (hval << 5) +
+ (hval << 7) + (hval << 8) + (hval << 40);
+ }
+ /* return our new hash value */
+ return hval;
+}
+
+/** Hash the string and output the encoded hash.
+ * This uses modified RFC1924 Ascii85 encoding to accommodate systems with
+ * very short name limits. We don't care about the encoding being reversible,
+ * we just want to preserve as many bits of the input as possible in a
+ * small printable string.
+ * @param[in] str string to hash
+ * @param[out] encbuf an array of 11 chars to hold the hash
+ */
+static const char mdb_a85[]= "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~";
+
+static void ESECT
+mdb_pack85(unsigned long l, char *out)
+{
+ int i;
+
+ for (i=0; i<5; i++) {
+ *out++ = mdb_a85[l % 85];
+ l /= 85;
+ }
+}
+
+static void ESECT
+mdb_hash_enc(MDB_val *val, char *encbuf)
+{
+ mdb_hash_t h = mdb_hash_val(val, MDB_HASH_INIT);
+
+ mdb_pack85(h, encbuf);
+ mdb_pack85(h>>32, encbuf+5);
+ encbuf[10] = '\0';
+}
+#endif
+
+/** Open and/or initialize the lock region for the environment.
+ * @param[in] env The LMDB environment.
+ * @param[in] fname Filename + scratch area, from #mdb_fname_init().
+ * @param[in] mode The Unix permissions for the file, if we create it.
+ * @param[in,out] excl In -1, out lock type: -1 none, 0 shared, 1 exclusive
+ * @return 0 on success, non-zero on failure.
+ */
+static int ESECT
+mdb_env_setup_locks(MDB_env *env, MDB_name *fname, int mode, int *excl)
+{
+#ifdef _WIN32
+# define MDB_ERRCODE_ROFS ERROR_WRITE_PROTECT
+#else
+# define MDB_ERRCODE_ROFS EROFS
+#endif
+ int rc;
+ off_t size, rsize;
+
+ rc = mdb_fopen(env, fname, MDB_O_LOCKS, mode, &env->me_lfd);
+ if (rc) {
+ /* Omit lockfile if read-only env on read-only filesystem */
+ if (rc == MDB_ERRCODE_ROFS && (env->me_flags & MDB_RDONLY)) {
+ return MDB_SUCCESS;
+ }
+ goto fail;
+ }
+
+ if (!(env->me_flags & MDB_NOTLS)) {
+ rc = pthread_key_create(&env->me_txkey, mdb_env_reader_dest);
+ if (rc)
+ goto fail;
+ env->me_flags |= MDB_ENV_TXKEY;
+#ifdef _WIN32
+ /* Windows TLS callbacks need help finding their TLS info. */
+ if (mdb_tls_nkeys >= MAX_TLS_KEYS) {
+ rc = MDB_TLS_FULL;
+ goto fail;
+ }
+ mdb_tls_keys[mdb_tls_nkeys++] = env->me_txkey;
+#endif
+ }
+
+ /* Try to get exclusive lock. If we succeed, then
+ * nobody is using the lock region and we should initialize it.
+ */
+ if ((rc = mdb_env_excl_lock(env, excl))) goto fail;
+
+#ifdef _WIN32
+ size = GetFileSize(env->me_lfd, NULL);
+#else
+ size = lseek(env->me_lfd, 0, SEEK_END);
+ if (size == -1) goto fail_errno;
+#endif
+ rsize = (env->me_maxreaders-1) * sizeof(MDB_reader) + sizeof(MDB_txninfo);
+ if (size < rsize && *excl > 0) {
+#ifdef _WIN32
+ if (SetFilePointer(env->me_lfd, rsize, NULL, FILE_BEGIN) != (DWORD)rsize
+ || !SetEndOfFile(env->me_lfd))
+ goto fail_errno;
+#else
+ if (ftruncate(env->me_lfd, rsize) != 0) goto fail_errno;
+#endif
+ } else {
+ rsize = size;
+ size = rsize - sizeof(MDB_txninfo);
+ env->me_maxreaders = size/sizeof(MDB_reader) + 1;
+ }
+ {
+#ifdef _WIN32
+ HANDLE mh;
+ mh = CreateFileMapping(env->me_lfd, NULL, PAGE_READWRITE,
+ 0, 0, NULL);
+ if (!mh) goto fail_errno;
+ env->me_txns = MapViewOfFileEx(mh, FILE_MAP_WRITE, 0, 0, rsize, NULL);
+ CloseHandle(mh);
+ if (!env->me_txns) goto fail_errno;
+#else
+ void *m = mmap(NULL, rsize, PROT_READ|PROT_WRITE, MAP_SHARED,
+ env->me_lfd, 0);
+ if (m == MAP_FAILED) goto fail_errno;
+ env->me_txns = m;
+#endif
+ }
+ if (*excl > 0) {
+#ifdef _WIN32
+ BY_HANDLE_FILE_INFORMATION stbuf;
+ struct {
+ DWORD volume;
+ DWORD nhigh;
+ DWORD nlow;
+ } idbuf;
+ MDB_val val;
+ char encbuf[11];
+
+ if (!mdb_sec_inited) {
+ InitializeSecurityDescriptor(&mdb_null_sd,
+ SECURITY_DESCRIPTOR_REVISION);
+ SetSecurityDescriptorDacl(&mdb_null_sd, TRUE, 0, FALSE);
+ mdb_all_sa.nLength = sizeof(SECURITY_ATTRIBUTES);
+ mdb_all_sa.bInheritHandle = FALSE;
+ mdb_all_sa.lpSecurityDescriptor = &mdb_null_sd;
+ mdb_sec_inited = 1;
+ }
+ if (!GetFileInformationByHandle(env->me_lfd, &stbuf)) goto fail_errno;
+ idbuf.volume = stbuf.dwVolumeSerialNumber;
+ idbuf.nhigh = stbuf.nFileIndexHigh;
+ idbuf.nlow = stbuf.nFileIndexLow;
+ val.mv_data = &idbuf;
+ val.mv_size = sizeof(idbuf);
+ mdb_hash_enc(&val, encbuf);
+ sprintf(env->me_txns->mti_rmname, "Global\\MDBr%s", encbuf);
+ sprintf(env->me_txns->mti_wmname, "Global\\MDBw%s", encbuf);
+ env->me_rmutex = CreateMutexA(&mdb_all_sa, FALSE, env->me_txns->mti_rmname);
+ if (!env->me_rmutex) goto fail_errno;
+ env->me_wmutex = CreateMutexA(&mdb_all_sa, FALSE, env->me_txns->mti_wmname);
+ if (!env->me_wmutex) goto fail_errno;
+#elif defined(MDB_USE_POSIX_SEM)
+ struct stat stbuf;
+ struct {
+ dev_t dev;
+ ino_t ino;
+ } idbuf;
+ MDB_val val;
+ char encbuf[11];
+
+#if defined(__NetBSD__)
+#define MDB_SHORT_SEMNAMES 1 /* limited to 14 chars */
+#endif
+ if (fstat(env->me_lfd, &stbuf)) goto fail_errno;
+ idbuf.dev = stbuf.st_dev;
+ idbuf.ino = stbuf.st_ino;
+ val.mv_data = &idbuf;
+ val.mv_size = sizeof(idbuf);
+ mdb_hash_enc(&val, encbuf);
+#ifdef MDB_SHORT_SEMNAMES
+ encbuf[9] = '\0'; /* drop name from 15 chars to 14 chars */
+#endif
+ sprintf(env->me_txns->mti_rmname, "/MDBr%s", encbuf);
+ sprintf(env->me_txns->mti_wmname, "/MDBw%s", encbuf);
+ /* Clean up after a previous run, if needed: Try to
+ * remove both semaphores before doing anything else.
+ */
+ sem_unlink(env->me_txns->mti_rmname);
+ sem_unlink(env->me_txns->mti_wmname);
+ env->me_rmutex = sem_open(env->me_txns->mti_rmname,
+ O_CREAT|O_EXCL, mode, 1);
+ if (env->me_rmutex == SEM_FAILED) goto fail_errno;
+ env->me_wmutex = sem_open(env->me_txns->mti_wmname,
+ O_CREAT|O_EXCL, mode, 1);
+ if (env->me_wmutex == SEM_FAILED) goto fail_errno;
+#else /* MDB_USE_POSIX_MUTEX: */
+ pthread_mutexattr_t mattr;
+
+ /* Solaris needs this before initing a robust mutex. Otherwise
+ * it may skip the init and return EBUSY "seems someone already
+ * inited" or EINVAL "it was inited differently".
+ */
+ memset(env->me_txns->mti_rmutex, 0, sizeof(*env->me_txns->mti_rmutex));
+ memset(env->me_txns->mti_wmutex, 0, sizeof(*env->me_txns->mti_wmutex));
+
+ if ((rc = pthread_mutexattr_init(&mattr)))
+ goto fail;
+
+ rc = pthread_mutexattr_setpshared(&mattr, PTHREAD_PROCESS_SHARED);
+#ifdef MDB_ROBUST_SUPPORTED
+ if (!rc) rc = pthread_mutexattr_setrobust(&mattr, PTHREAD_MUTEX_ROBUST);
+#endif
+ if (!rc) rc = pthread_mutex_init(env->me_txns->mti_rmutex, &mattr);
+ if (!rc) rc = pthread_mutex_init(env->me_txns->mti_wmutex, &mattr);
+ pthread_mutexattr_destroy(&mattr);
+ if (rc)
+ goto fail;
+#endif /* _WIN32 || MDB_USE_POSIX_SEM */
+
+ env->me_txns->mti_magic = MDB_MAGIC;
+ env->me_txns->mti_format = MDB_LOCK_FORMAT;
+ env->me_txns->mti_txnid = 0;
+ env->me_txns->mti_numreaders = 0;
+
+ } else {
+ if (env->me_txns->mti_magic != MDB_MAGIC) {
+ DPUTS("lock region has invalid magic");
+ rc = MDB_INVALID;
+ goto fail;
+ }
+ if (env->me_txns->mti_format != MDB_LOCK_FORMAT) {
+ DPRINTF(("lock region has format+version 0x%x, expected 0x%x",
+ env->me_txns->mti_format, MDB_LOCK_FORMAT));
+ rc = MDB_VERSION_MISMATCH;
+ goto fail;
+ }
+ rc = ErrCode();
+ if (rc && rc != EACCES && rc != EAGAIN) {
+ goto fail;
+ }
+#ifdef _WIN32
+ env->me_rmutex = OpenMutexA(SYNCHRONIZE, FALSE, env->me_txns->mti_rmname);
+ if (!env->me_rmutex) goto fail_errno;
+ env->me_wmutex = OpenMutexA(SYNCHRONIZE, FALSE, env->me_txns->mti_wmname);
+ if (!env->me_wmutex) goto fail_errno;
+#elif defined(MDB_USE_POSIX_SEM)
+ env->me_rmutex = sem_open(env->me_txns->mti_rmname, 0);
+ if (env->me_rmutex == SEM_FAILED) goto fail_errno;
+ env->me_wmutex = sem_open(env->me_txns->mti_wmname, 0);
+ if (env->me_wmutex == SEM_FAILED) goto fail_errno;
+#endif
+ }
+ return MDB_SUCCESS;
+
+fail_errno:
+ rc = ErrCode();
+fail:
+ return rc;
+}
+
+ /** Only a subset of the @ref mdb_env flags can be changed
+ * at runtime. Changing other flags requires closing the
+ * environment and re-opening it with the new flags.
+ */
+#define CHANGEABLE (MDB_NOSYNC|MDB_NOMETASYNC|MDB_MAPASYNC|MDB_NOMEMINIT)
+#define CHANGELESS (MDB_FIXEDMAP|MDB_NOSUBDIR|MDB_RDONLY| \
+ MDB_WRITEMAP|MDB_NOTLS|MDB_NOLOCK|MDB_NORDAHEAD)
+
+#if VALID_FLAGS & PERSISTENT_FLAGS & (CHANGEABLE|CHANGELESS)
+# error "Persistent DB flags & env flags overlap, but both go in mm_flags"
+#endif
+
+int ESECT
+mdb_env_open(MDB_env *env, const char *path, unsigned int flags, mdb_mode_t mode)
+{
+ int rc, excl = -1;
+ MDB_name fname;
+
+ if (env->me_fd!=INVALID_HANDLE_VALUE || (flags & ~(CHANGEABLE|CHANGELESS)))
+ return EINVAL;
+
+ flags |= env->me_flags;
+
+ rc = mdb_fname_init(path, flags, &fname);
+ if (rc)
+ return rc;
+
+ if (flags & MDB_RDONLY) {
+ /* silently ignore WRITEMAP when we're only getting read access */
+ flags &= ~MDB_WRITEMAP;
+ } else {
+ if (!((env->me_free_pgs = mdb_midl_alloc(MDB_IDL_UM_MAX)) &&
+ (env->me_dirty_list = calloc(MDB_IDL_UM_SIZE, sizeof(MDB_ID2)))))
+ rc = ENOMEM;
+ }
+ env->me_flags = flags |= MDB_ENV_ACTIVE;
+ if (rc)
+ goto leave;
+
+ env->me_path = strdup(path);
+ env->me_dbxs = calloc(env->me_maxdbs, sizeof(MDB_dbx));
+ env->me_dbflags = calloc(env->me_maxdbs, sizeof(uint16_t));
+ env->me_dbiseqs = calloc(env->me_maxdbs, sizeof(unsigned int));
+ if (!(env->me_dbxs && env->me_path && env->me_dbflags && env->me_dbiseqs)) {
+ rc = ENOMEM;
+ goto leave;
+ }
+ env->me_dbxs[FREE_DBI].md_cmp = mdb_cmp_long; /* aligned MDB_INTEGERKEY */
+
+ /* For RDONLY, get lockfile after we know datafile exists */
+ if (!(flags & (MDB_RDONLY|MDB_NOLOCK))) {
+ rc = mdb_env_setup_locks(env, &fname, mode, &excl);
+ if (rc)
+ goto leave;
+ }
+
+ rc = mdb_fopen(env, &fname,
+ (flags & MDB_RDONLY) ? MDB_O_RDONLY : MDB_O_RDWR,
+ mode, &env->me_fd);
+ if (rc)
+ goto leave;
+
+ if ((flags & (MDB_RDONLY|MDB_NOLOCK)) == MDB_RDONLY) {
+ rc = mdb_env_setup_locks(env, &fname, mode, &excl);
+ if (rc)
+ goto leave;
+ }
+
+ if ((rc = mdb_env_open2(env)) == MDB_SUCCESS) {
+ if (!(flags & (MDB_RDONLY|MDB_WRITEMAP))) {
+ /* Synchronous fd for meta writes. Needed even with
+ * MDB_NOSYNC/MDB_NOMETASYNC, in case these get reset.
+ */
+ rc = mdb_fopen(env, &fname, MDB_O_META, mode, &env->me_mfd);
+ if (rc)
+ goto leave;
+ }
+ DPRINTF(("opened dbenv %p", (void *) env));
+ if (excl > 0) {
+ rc = mdb_env_share_locks(env, &excl);
+ if (rc)
+ goto leave;
+ }
+ if (!(flags & MDB_RDONLY)) {
+ MDB_txn *txn;
+ int tsize = sizeof(MDB_txn), size = tsize + env->me_maxdbs *
+ (sizeof(MDB_db)+sizeof(MDB_cursor *)+sizeof(unsigned int)+1);
+ if ((env->me_pbuf = calloc(1, env->me_psize)) &&
+ (txn = calloc(1, size)))
+ {
+ txn->mt_dbs = (MDB_db *)((char *)txn + tsize);
+ txn->mt_cursors = (MDB_cursor **)(txn->mt_dbs + env->me_maxdbs);
+ txn->mt_dbiseqs = (unsigned int *)(txn->mt_cursors + env->me_maxdbs);
+ txn->mt_dbflags = (unsigned char *)(txn->mt_dbiseqs + env->me_maxdbs);
+ txn->mt_env = env;
+ txn->mt_dbxs = env->me_dbxs;
+ txn->mt_flags = MDB_TXN_FINISHED;
+ env->me_txn0 = txn;
+ } else {
+ rc = ENOMEM;
+ }
+ }
+ }
+
+leave:
+ if (rc) {
+ mdb_env_close0(env, excl);
+ }
+ mdb_fname_destroy(fname);
+ return rc;
+}
+
+/** Destroy resources from mdb_env_open(), clear our readers & DBIs */
+static void ESECT
+mdb_env_close0(MDB_env *env, int excl)
+{
+ int i;
+
+ if (!(env->me_flags & MDB_ENV_ACTIVE))
+ return;
+
+ /* Doing this here since me_dbxs may not exist during mdb_env_close */
+ if (env->me_dbxs) {
+ for (i = env->me_maxdbs; --i >= CORE_DBS; )
+ free(env->me_dbxs[i].md_name.mv_data);
+ free(env->me_dbxs);
+ }
+
+ free(env->me_pbuf);
+ free(env->me_dbiseqs);
+ free(env->me_dbflags);
+ free(env->me_path);
+ free(env->me_dirty_list);
+ free(env->me_txn0);
+ mdb_midl_free(env->me_free_pgs);
+
+ if (env->me_flags & MDB_ENV_TXKEY) {
+ pthread_key_delete(env->me_txkey);
+#ifdef _WIN32
+ /* Delete our key from the global list */
+ for (i=0; i<mdb_tls_nkeys; i++)
+ if (mdb_tls_keys[i] == env->me_txkey) {
+ mdb_tls_keys[i] = mdb_tls_keys[mdb_tls_nkeys-1];
+ mdb_tls_nkeys--;
+ break;
+ }
+#endif
+ }
+
+ if (env->me_map) {
+ munmap(env->me_map, env->me_mapsize);
+ }
+ if (env->me_mfd != INVALID_HANDLE_VALUE)
+ (void) close(env->me_mfd);
+ if (env->me_fd != INVALID_HANDLE_VALUE)
+ (void) close(env->me_fd);
+ if (env->me_txns) {
+ MDB_PID_T pid = env->me_pid;
+ /* Clearing readers is done in this function because
+ * me_txkey with its destructor must be disabled first.
+ *
+ * We skip the the reader mutex, so we touch only
+ * data owned by this process (me_close_readers and
+ * our readers), and clear each reader atomically.
+ */
+ for (i = env->me_close_readers; --i >= 0; )
+ if (env->me_txns->mti_readers[i].mr_pid == pid)
+ env->me_txns->mti_readers[i].mr_pid = 0;
+#ifdef _WIN32
+ if (env->me_rmutex) {
+ CloseHandle(env->me_rmutex);
+ if (env->me_wmutex) CloseHandle(env->me_wmutex);
+ }
+ /* Windows automatically destroys the mutexes when
+ * the last handle closes.
+ */
+#elif defined(MDB_USE_POSIX_SEM)
+ if (env->me_rmutex != SEM_FAILED) {
+ sem_close(env->me_rmutex);
+ if (env->me_wmutex != SEM_FAILED)
+ sem_close(env->me_wmutex);
+ /* If we have the filelock: If we are the
+ * only remaining user, clean up semaphores.
+ */
+ if (excl == 0)
+ mdb_env_excl_lock(env, &excl);
+ if (excl > 0) {
+ sem_unlink(env->me_txns->mti_rmname);
+ sem_unlink(env->me_txns->mti_wmname);
+ }
+ }
+#endif
+ munmap((void *)env->me_txns, (env->me_maxreaders-1)*sizeof(MDB_reader)+sizeof(MDB_txninfo));
+ }
+ if (env->me_lfd != INVALID_HANDLE_VALUE) {
+#ifdef _WIN32
+ if (excl >= 0) {
+ /* Unlock the lockfile. Windows would have unlocked it
+ * after closing anyway, but not necessarily at once.
+ */
+ UnlockFile(env->me_lfd, 0, 0, 1, 0);
+ }
+#endif
+ (void) close(env->me_lfd);
+ }
+
+ env->me_flags &= ~(MDB_ENV_ACTIVE|MDB_ENV_TXKEY);
+}
+
+void ESECT
+mdb_env_close(MDB_env *env)
+{
+ MDB_page *dp;
+
+ if (env == NULL)
+ return;
+
+ VGMEMP_DESTROY(env);
+ while ((dp = env->me_dpages) != NULL) {
+ VGMEMP_DEFINED(&dp->mp_next, sizeof(dp->mp_next));
+ env->me_dpages = dp->mp_next;
+ free(dp);
+ }
+
+ mdb_env_close0(env, 0);
+ free(env);
+}
+
+/** Compare two items pointing at aligned size_t's */
+static int
+mdb_cmp_long(const MDB_val *a, const MDB_val *b)
+{
+ return (*(size_t *)a->mv_data < *(size_t *)b->mv_data) ? -1 :
+ *(size_t *)a->mv_data > *(size_t *)b->mv_data;
+}
+
+/** Compare two items pointing at aligned unsigned int's.
+ *
+ * This is also set as #MDB_INTEGERDUP|#MDB_DUPFIXED's #MDB_dbx.%md_dcmp,
+ * but #mdb_cmp_clong() is called instead if the data type is size_t.
+ */
+static int
+mdb_cmp_int(const MDB_val *a, const MDB_val *b)
+{
+ return (*(unsigned int *)a->mv_data < *(unsigned int *)b->mv_data) ? -1 :
+ *(unsigned int *)a->mv_data > *(unsigned int *)b->mv_data;
+}
+
+/** Compare two items pointing at unsigned ints of unknown alignment.
+ * Nodes and keys are guaranteed to be 2-byte aligned.
+ */
+static int
+mdb_cmp_cint(const MDB_val *a, const MDB_val *b)
+{
+#if BYTE_ORDER == LITTLE_ENDIAN
+ unsigned short *u, *c;
+ int x;
+
+ u = (unsigned short *) ((char *) a->mv_data + a->mv_size);
+ c = (unsigned short *) ((char *) b->mv_data + a->mv_size);
+ do {
+ x = *--u - *--c;
+ } while(!x && u > (unsigned short *)a->mv_data);
+ return x;
+#else
+ unsigned short *u, *c, *end;
+ int x;
+
+ end = (unsigned short *) ((char *) a->mv_data + a->mv_size);
+ u = (unsigned short *)a->mv_data;
+ c = (unsigned short *)b->mv_data;
+ do {
+ x = *u++ - *c++;
+ } while(!x && u < end);
+ return x;
+#endif
+}
+
+/** Compare two items lexically */
+static int
+mdb_cmp_memn(const MDB_val *a, const MDB_val *b)
+{
+ int diff;
+ ssize_t len_diff;
+ unsigned int len;
+
+ len = a->mv_size;
+ len_diff = (ssize_t) a->mv_size - (ssize_t) b->mv_size;
+ if (len_diff > 0) {
+ len = b->mv_size;
+ len_diff = 1;
+ }
+
+ diff = memcmp(a->mv_data, b->mv_data, len);
+ return diff ? diff : len_diff<0 ? -1 : len_diff;
+}
+
+/** Compare two items in reverse byte order */
+static int
+mdb_cmp_memnr(const MDB_val *a, const MDB_val *b)
+{
+ const unsigned char *p1, *p2, *p1_lim;
+ ssize_t len_diff;
+ int diff;
+
+ p1_lim = (const unsigned char *)a->mv_data;
+ p1 = (const unsigned char *)a->mv_data + a->mv_size;
+ p2 = (const unsigned char *)b->mv_data + b->mv_size;
+
+ len_diff = (ssize_t) a->mv_size - (ssize_t) b->mv_size;
+ if (len_diff > 0) {
+ p1_lim += len_diff;
+ len_diff = 1;
+ }
+
+ while (p1 > p1_lim) {
+ diff = *--p1 - *--p2;
+ if (diff)
+ return diff;
+ }
+ return len_diff<0 ? -1 : len_diff;
+}
+
+/** Search for key within a page, using binary search.
+ * Returns the smallest entry larger or equal to the key.
+ * If exactp is non-null, stores whether the found entry was an exact match
+ * in *exactp (1 or 0).
+ * Updates the cursor index with the index of the found entry.
+ * If no entry larger or equal to the key is found, returns NULL.
+ */
+static MDB_node *
+mdb_node_search(MDB_cursor *mc, MDB_val *key, int *exactp)
+{
+ unsigned int i = 0, nkeys;
+ int low, high;
+ int rc = 0;
+ MDB_page *mp = mc->mc_pg[mc->mc_top];
+ MDB_node *node = NULL;
+ MDB_val nodekey;
+ MDB_cmp_func *cmp;
+ DKBUF;
+
+ nkeys = NUMKEYS(mp);
+
+ DPRINTF(("searching %u keys in %s %spage %"Z"u",
+ nkeys, IS_LEAF(mp) ? "leaf" : "branch", IS_SUBP(mp) ? "sub-" : "",
+ mdb_dbg_pgno(mp)));
+
+ low = IS_LEAF(mp) ? 0 : 1;
+ high = nkeys - 1;
+ cmp = mc->mc_dbx->md_cmp;
+
+ /* Branch pages have no data, so if using integer keys,
+ * alignment is guaranteed. Use faster mdb_cmp_int.
+ */
+ if (cmp == mdb_cmp_cint && IS_BRANCH(mp)) {
+ if (NODEPTR(mp, 1)->mn_ksize == sizeof(size_t))
+ cmp = mdb_cmp_long;
+ else
+ cmp = mdb_cmp_int;
+ }
+
+ if (IS_LEAF2(mp)) {
+ nodekey.mv_size = mc->mc_db->md_pad;
+ node = NODEPTR(mp, 0); /* fake */
+ while (low <= high) {
+ i = (low + high) >> 1;
+ nodekey.mv_data = LEAF2KEY(mp, i, nodekey.mv_size);
+ rc = cmp(key, &nodekey);
+ DPRINTF(("found leaf index %u [%s], rc = %i",
+ i, DKEY(&nodekey), rc));
+ if (rc == 0)
+ break;
+ if (rc > 0)
+ low = i + 1;
+ else
+ high = i - 1;
+ }
+ } else {
+ while (low <= high) {
+ i = (low + high) >> 1;
+
+ node = NODEPTR(mp, i);
+ nodekey.mv_size = NODEKSZ(node);
+ nodekey.mv_data = NODEKEY(node);
+
+ rc = cmp(key, &nodekey);
+#if MDB_DEBUG
+ if (IS_LEAF(mp))
+ DPRINTF(("found leaf index %u [%s], rc = %i",
+ i, DKEY(&nodekey), rc));
+ else
+ DPRINTF(("found branch index %u [%s -> %"Z"u], rc = %i",
+ i, DKEY(&nodekey), NODEPGNO(node), rc));
+#endif
+ if (rc == 0)
+ break;
+ if (rc > 0)
+ low = i + 1;
+ else
+ high = i - 1;
+ }
+ }
+
+ if (rc > 0) { /* Found entry is less than the key. */
+ i++; /* Skip to get the smallest entry larger than key. */
+ if (!IS_LEAF2(mp))
+ node = NODEPTR(mp, i);
+ }
+ if (exactp)
+ *exactp = (rc == 0 && nkeys > 0);
+ /* store the key index */
+ mc->mc_ki[mc->mc_top] = i;
+ if (i >= nkeys)
+ /* There is no entry larger or equal to the key. */
+ return NULL;
+
+ /* nodeptr is fake for LEAF2 */
+ return node;
+}
+
+#if 0
+static void
+mdb_cursor_adjust(MDB_cursor *mc, func)
+{
+ MDB_cursor *m2;
+
+ for (m2 = mc->mc_txn->mt_cursors[mc->mc_dbi]; m2; m2=m2->mc_next) {
+ if (m2->mc_pg[m2->mc_top] == mc->mc_pg[mc->mc_top]) {
+ func(mc, m2);
+ }
+ }
+}
+#endif
+
+/** Pop a page off the top of the cursor's stack. */
+static void
+mdb_cursor_pop(MDB_cursor *mc)
+{
+ if (mc->mc_snum) {
+ DPRINTF(("popping page %"Z"u off db %d cursor %p",
+ mc->mc_pg[mc->mc_top]->mp_pgno, DDBI(mc), (void *) mc));
+
+ mc->mc_snum--;
+ if (mc->mc_snum) {
+ mc->mc_top--;
+ } else {
+ mc->mc_flags &= ~C_INITIALIZED;
+ }
+ }
+}
+
+/** Push a page onto the top of the cursor's stack.
+ * Set #MDB_TXN_ERROR on failure.
+ */
+static int
+mdb_cursor_push(MDB_cursor *mc, MDB_page *mp)
+{
+ DPRINTF(("pushing page %"Z"u on db %d cursor %p", mp->mp_pgno,
+ DDBI(mc), (void *) mc));
+
+ if (mc->mc_snum >= CURSOR_STACK) {
+ mc->mc_txn->mt_flags |= MDB_TXN_ERROR;
+ return MDB_CURSOR_FULL;
+ }
+
+ mc->mc_top = mc->mc_snum++;
+ mc->mc_pg[mc->mc_top] = mp;
+ mc->mc_ki[mc->mc_top] = 0;
+
+ return MDB_SUCCESS;
+}
+
+/** Find the address of the page corresponding to a given page number.
+ * Set #MDB_TXN_ERROR on failure.
+ * @param[in] mc the cursor accessing the page.
+ * @param[in] pgno the page number for the page to retrieve.
+ * @param[out] ret address of a pointer where the page's address will be stored.
+ * @param[out] lvl dirty_list inheritance level of found page. 1=current txn, 0=mapped page.
+ * @return 0 on success, non-zero on failure.
+ */
+static int
+mdb_page_get(MDB_cursor *mc, pgno_t pgno, MDB_page **ret, int *lvl)
+{
+ MDB_txn *txn = mc->mc_txn;
+ MDB_env *env = txn->mt_env;
+ MDB_page *p = NULL;
+ int level;
+
+ if (! (txn->mt_flags & (MDB_TXN_RDONLY|MDB_TXN_WRITEMAP))) {
+ MDB_txn *tx2 = txn;
+ level = 1;
+ do {
+ MDB_ID2L dl = tx2->mt_u.dirty_list;
+ unsigned x;
+ /* Spilled pages were dirtied in this txn and flushed
+ * because the dirty list got full. Bring this page
+ * back in from the map (but don't unspill it here,
+ * leave that unless page_touch happens again).
+ */
+ if (tx2->mt_spill_pgs) {
+ MDB_ID pn = pgno << 1;
+ x = mdb_midl_search(tx2->mt_spill_pgs, pn);
+ if (x <= tx2->mt_spill_pgs[0] && tx2->mt_spill_pgs[x] == pn) {
+ p = (MDB_page *)(env->me_map + env->me_psize * pgno);
+ goto done;
+ }
+ }
+ if (dl[0].mid) {
+ unsigned x = mdb_mid2l_search(dl, pgno);
+ if (x <= dl[0].mid && dl[x].mid == pgno) {
+ p = dl[x].mptr;
+ goto done;
+ }
+ }
+ level++;
+ } while ((tx2 = tx2->mt_parent) != NULL);
+ }
+
+ if (pgno < txn->mt_next_pgno) {
+ level = 0;
+ p = (MDB_page *)(env->me_map + env->me_psize * pgno);
+ } else {
+ DPRINTF(("page %"Z"u not found", pgno));
+ txn->mt_flags |= MDB_TXN_ERROR;
+ return MDB_PAGE_NOTFOUND;
+ }
+
+done:
+ *ret = p;
+ if (lvl)
+ *lvl = level;
+ return MDB_SUCCESS;
+}
+
+/** Finish #mdb_page_search() / #mdb_page_search_lowest().
+ * The cursor is at the root page, set up the rest of it.
+ */
+static int
+mdb_page_search_root(MDB_cursor *mc, MDB_val *key, int flags)
+{
+ MDB_page *mp = mc->mc_pg[mc->mc_top];
+ int rc;
+ DKBUF;
+
+ while (IS_BRANCH(mp)) {
+ MDB_node *node;
+ indx_t i;
+
+ DPRINTF(("branch page %"Z"u has %u keys", mp->mp_pgno, NUMKEYS(mp)));
+ /* Don't assert on branch pages in the FreeDB. We can get here
+ * while in the process of rebalancing a FreeDB branch page; we must
+ * let that proceed. ITS#8336
+ */
+ mdb_cassert(mc, !mc->mc_dbi || NUMKEYS(mp) > 1);
+ DPRINTF(("found index 0 to page %"Z"u", NODEPGNO(NODEPTR(mp, 0))));
+
+ if (flags & (MDB_PS_FIRST|MDB_PS_LAST)) {
+ i = 0;
+ if (flags & MDB_PS_LAST) {
+ i = NUMKEYS(mp) - 1;
+ /* if already init'd, see if we're already in right place */
+ if (mc->mc_flags & C_INITIALIZED) {
+ if (mc->mc_ki[mc->mc_top] == i) {
+ mc->mc_top = mc->mc_snum++;
+ mp = mc->mc_pg[mc->mc_top];
+ goto ready;
+ }
+ }
+ }
+ } else {
+ int exact;
+ node = mdb_node_search(mc, key, &exact);
+ if (node == NULL)
+ i = NUMKEYS(mp) - 1;
+ else {
+ i = mc->mc_ki[mc->mc_top];
+ if (!exact) {
+ mdb_cassert(mc, i > 0);
+ i--;
+ }
+ }
+ DPRINTF(("following index %u for key [%s]", i, DKEY(key)));
+ }
+
+ mdb_cassert(mc, i < NUMKEYS(mp));
+ node = NODEPTR(mp, i);
+
+ if ((rc = mdb_page_get(mc, NODEPGNO(node), &mp, NULL)) != 0)
+ return rc;
+
+ mc->mc_ki[mc->mc_top] = i;
+ if ((rc = mdb_cursor_push(mc, mp)))
+ return rc;
+
+ready:
+ if (flags & MDB_PS_MODIFY) {
+ if ((rc = mdb_page_touch(mc)) != 0)
+ return rc;
+ mp = mc->mc_pg[mc->mc_top];
+ }
+ }
+
+ if (!IS_LEAF(mp)) {
+ DPRINTF(("internal error, index points to a %02X page!?",
+ mp->mp_flags));
+ mc->mc_txn->mt_flags |= MDB_TXN_ERROR;
+ return MDB_CORRUPTED;
+ }
+
+ DPRINTF(("found leaf page %"Z"u for key [%s]", mp->mp_pgno,
+ key ? DKEY(key) : "null"));
+ mc->mc_flags |= C_INITIALIZED;
+ mc->mc_flags &= ~C_EOF;
+
+ return MDB_SUCCESS;
+}
+
+/** Search for the lowest key under the current branch page.
+ * This just bypasses a NUMKEYS check in the current page
+ * before calling mdb_page_search_root(), because the callers
+ * are all in situations where the current page is known to
+ * be underfilled.
+ */
+static int
+mdb_page_search_lowest(MDB_cursor *mc)
+{
+ MDB_page *mp = mc->mc_pg[mc->mc_top];
+ MDB_node *node = NODEPTR(mp, 0);
+ int rc;
+
+ if ((rc = mdb_page_get(mc, NODEPGNO(node), &mp, NULL)) != 0)
+ return rc;
+
+ mc->mc_ki[mc->mc_top] = 0;
+ if ((rc = mdb_cursor_push(mc, mp)))
+ return rc;
+ return mdb_page_search_root(mc, NULL, MDB_PS_FIRST);
+}
+
+/** Search for the page a given key should be in.
+ * Push it and its parent pages on the cursor stack.
+ * @param[in,out] mc the cursor for this operation.
+ * @param[in] key the key to search for, or NULL for first/last page.
+ * @param[in] flags If MDB_PS_MODIFY is set, visited pages in the DB
+ * are touched (updated with new page numbers).
+ * If MDB_PS_FIRST or MDB_PS_LAST is set, find first or last leaf.
+ * This is used by #mdb_cursor_first() and #mdb_cursor_last().
+ * If MDB_PS_ROOTONLY set, just fetch root node, no further lookups.
+ * @return 0 on success, non-zero on failure.
+ */
+static int
+mdb_page_search(MDB_cursor *mc, MDB_val *key, int flags)
+{
+ int rc;
+ pgno_t root;
+
+ /* Make sure the txn is still viable, then find the root from
+ * the txn's db table and set it as the root of the cursor's stack.
+ */
+ if (mc->mc_txn->mt_flags & MDB_TXN_BLOCKED) {
+ DPUTS("transaction may not be used now");
+ return MDB_BAD_TXN;
+ } else {
+ /* Make sure we're using an up-to-date root */
+ if (*mc->mc_dbflag & DB_STALE) {
+ MDB_cursor mc2;
+ if (TXN_DBI_CHANGED(mc->mc_txn, mc->mc_dbi))
+ return MDB_BAD_DBI;
+ mdb_cursor_init(&mc2, mc->mc_txn, MAIN_DBI, NULL);
+ rc = mdb_page_search(&mc2, &mc->mc_dbx->md_name, 0);
+ if (rc)
+ return rc;
+ {
+ MDB_val data;
+ int exact = 0;
+ uint16_t flags;
+ MDB_node *leaf = mdb_node_search(&mc2,
+ &mc->mc_dbx->md_name, &exact);
+ if (!exact)
+ return MDB_NOTFOUND;
+ if ((leaf->mn_flags & (F_DUPDATA|F_SUBDATA)) != F_SUBDATA)
+ return MDB_INCOMPATIBLE; /* not a named DB */
+ rc = mdb_node_read(&mc2, leaf, &data);
+ if (rc)
+ return rc;
+ memcpy(&flags, ((char *) data.mv_data + offsetof(MDB_db, md_flags)),
+ sizeof(uint16_t));
+ /* The txn may not know this DBI, or another process may
+ * have dropped and recreated the DB with other flags.
+ */
+ if ((mc->mc_db->md_flags & PERSISTENT_FLAGS) != flags)
+ return MDB_INCOMPATIBLE;
+ memcpy(mc->mc_db, data.mv_data, sizeof(MDB_db));
+ }
+ *mc->mc_dbflag &= ~DB_STALE;
+ }
+ root = mc->mc_db->md_root;
+
+ if (root == P_INVALID) { /* Tree is empty. */
+ DPUTS("tree is empty");
+ return MDB_NOTFOUND;
+ }
+ }
+
+ mdb_cassert(mc, root > 1);
+ if (!mc->mc_pg[0] || mc->mc_pg[0]->mp_pgno != root)
+ if ((rc = mdb_page_get(mc, root, &mc->mc_pg[0], NULL)) != 0)
+ return rc;
+
+ mc->mc_snum = 1;
+ mc->mc_top = 0;
+
+ DPRINTF(("db %d root page %"Z"u has flags 0x%X",
+ DDBI(mc), root, mc->mc_pg[0]->mp_flags));
+
+ if (flags & MDB_PS_MODIFY) {
+ if ((rc = mdb_page_touch(mc)))
+ return rc;
+ }
+
+ if (flags & MDB_PS_ROOTONLY)
+ return MDB_SUCCESS;
+
+ return mdb_page_search_root(mc, key, flags);
+}
+
+static int
+mdb_ovpage_free(MDB_cursor *mc, MDB_page *mp)
+{
+ MDB_txn *txn = mc->mc_txn;
+ pgno_t pg = mp->mp_pgno;
+ unsigned x = 0, ovpages = mp->mp_pages;
+ MDB_env *env = txn->mt_env;
+ MDB_IDL sl = txn->mt_spill_pgs;
+ MDB_ID pn = pg << 1;
+ int rc;
+
+ DPRINTF(("free ov page %"Z"u (%d)", pg, ovpages));
+ /* If the page is dirty or on the spill list we just acquired it,
+ * so we should give it back to our current free list, if any.
+ * Otherwise put it onto the list of pages we freed in this txn.
+ *
+ * Won't create me_pghead: me_pglast must be inited along with it.
+ * Unsupported in nested txns: They would need to hide the page
+ * range in ancestor txns' dirty and spilled lists.
+ */
+ if (env->me_pghead &&
+ !txn->mt_parent &&
+ ((mp->mp_flags & P_DIRTY) ||
+ (sl && (x = mdb_midl_search(sl, pn)) <= sl[0] && sl[x] == pn)))
+ {
+ unsigned i, j;
+ pgno_t *mop;
+ MDB_ID2 *dl, ix, iy;
+ rc = mdb_midl_need(&env->me_pghead, ovpages);
+ if (rc)
+ return rc;
+ if (!(mp->mp_flags & P_DIRTY)) {
+ /* This page is no longer spilled */
+ if (x == sl[0])
+ sl[0]--;
+ else
+ sl[x] |= 1;
+ goto release;
+ }
+ /* Remove from dirty list */
+ dl = txn->mt_u.dirty_list;
+ x = dl[0].mid--;
+ for (ix = dl[x]; ix.mptr != mp; ix = iy) {
+ if (x > 1) {
+ x--;
+ iy = dl[x];
+ dl[x] = ix;
+ } else {
+ mdb_cassert(mc, x > 1);
+ j = ++(dl[0].mid);
+ dl[j] = ix; /* Unsorted. OK when MDB_TXN_ERROR. */
+ txn->mt_flags |= MDB_TXN_ERROR;
+ return MDB_CORRUPTED;
+ }
+ }
+ txn->mt_dirty_room++;
+ if (!(env->me_flags & MDB_WRITEMAP))
+ mdb_dpage_free(env, mp);
+release:
+ /* Insert in me_pghead */
+ mop = env->me_pghead;
+ j = mop[0] + ovpages;
+ for (i = mop[0]; i && mop[i] < pg; i--)
+ mop[j--] = mop[i];
+ while (j>i)
+ mop[j--] = pg++;
+ mop[0] += ovpages;
+ } else {
+ rc = mdb_midl_append_range(&txn->mt_free_pgs, pg, ovpages);
+ if (rc)
+ return rc;
+ }
+ mc->mc_db->md_overflow_pages -= ovpages;
+ return 0;
+}
+
+/** Return the data associated with a given node.
+ * @param[in] mc The cursor for this operation.
+ * @param[in] leaf The node being read.
+ * @param[out] data Updated to point to the node's data.
+ * @return 0 on success, non-zero on failure.
+ */
+static int
+mdb_node_read(MDB_cursor *mc, MDB_node *leaf, MDB_val *data)
+{
+ MDB_page *omp; /* overflow page */
+ pgno_t pgno;
+ int rc;
+
+ if (!F_ISSET(leaf->mn_flags, F_BIGDATA)) {
+ data->mv_size = NODEDSZ(leaf);
+ data->mv_data = NODEDATA(leaf);
+ return MDB_SUCCESS;
+ }
+
+ /* Read overflow data.
+ */
+ data->mv_size = NODEDSZ(leaf);
+ memcpy(&pgno, NODEDATA(leaf), sizeof(pgno));
+ if ((rc = mdb_page_get(mc, pgno, &omp, NULL)) != 0) {
+ DPRINTF(("read overflow page %"Z"u failed", pgno));
+ return rc;
+ }
+ data->mv_data = METADATA(omp);
+
+ return MDB_SUCCESS;
+}
+
+int
+mdb_get(MDB_txn *txn, MDB_dbi dbi,
+ MDB_val *key, MDB_val *data)
+{
+ MDB_cursor mc;
+ MDB_xcursor mx;
+ int exact = 0;
+ DKBUF;
+
+ DPRINTF(("===> get db %u key [%s]", dbi, DKEY(key)));
+
+ if (!key || !data || !TXN_DBI_EXIST(txn, dbi, DB_USRVALID))
+ return EINVAL;
+
+ if (txn->mt_flags & MDB_TXN_BLOCKED)
+ return MDB_BAD_TXN;
+
+ mdb_cursor_init(&mc, txn, dbi, &mx);
+ return mdb_cursor_set(&mc, key, data, MDB_SET, &exact);
+}
+
+/** Find a sibling for a page.
+ * Replaces the page at the top of the cursor's stack with the
+ * specified sibling, if one exists.
+ * @param[in] mc The cursor for this operation.
+ * @param[in] move_right Non-zero if the right sibling is requested,
+ * otherwise the left sibling.
+ * @return 0 on success, non-zero on failure.
+ */
+static int
+mdb_cursor_sibling(MDB_cursor *mc, int move_right)
+{
+ int rc;
+ MDB_node *indx;
+ MDB_page *mp;
+
+ if (mc->mc_snum < 2) {
+ return MDB_NOTFOUND; /* root has no siblings */
+ }
+
+ mdb_cursor_pop(mc);
+ DPRINTF(("parent page is page %"Z"u, index %u",
+ mc->mc_pg[mc->mc_top]->mp_pgno, mc->mc_ki[mc->mc_top]));
+
+ if (move_right ? (mc->mc_ki[mc->mc_top] + 1u >= NUMKEYS(mc->mc_pg[mc->mc_top]))
+ : (mc->mc_ki[mc->mc_top] == 0)) {
+ DPRINTF(("no more keys left, moving to %s sibling",
+ move_right ? "right" : "left"));
+ if ((rc = mdb_cursor_sibling(mc, move_right)) != MDB_SUCCESS) {
+ /* undo cursor_pop before returning */
+ mc->mc_top++;
+ mc->mc_snum++;
+ return rc;
+ }
+ } else {
+ if (move_right)
+ mc->mc_ki[mc->mc_top]++;
+ else
+ mc->mc_ki[mc->mc_top]--;
+ DPRINTF(("just moving to %s index key %u",
+ move_right ? "right" : "left", mc->mc_ki[mc->mc_top]));
+ }
+ mdb_cassert(mc, IS_BRANCH(mc->mc_pg[mc->mc_top]));
+
+ indx = NODEPTR(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top]);
+ if ((rc = mdb_page_get(mc, NODEPGNO(indx), &mp, NULL)) != 0) {
+ /* mc will be inconsistent if caller does mc_snum++ as above */
+ mc->mc_flags &= ~(C_INITIALIZED|C_EOF);
+ return rc;
+ }
+
+ mdb_cursor_push(mc, mp);
+ if (!move_right)
+ mc->mc_ki[mc->mc_top] = NUMKEYS(mp)-1;
+
+ return MDB_SUCCESS;
+}
+
+/** Move the cursor to the next data item. */
+static int
+mdb_cursor_next(MDB_cursor *mc, MDB_val *key, MDB_val *data, MDB_cursor_op op)
+{
+ MDB_page *mp;
+ MDB_node *leaf;
+ int rc;
+
+ if ((mc->mc_flags & C_DEL && op == MDB_NEXT_DUP))
+ return MDB_NOTFOUND;
+
+ if (!(mc->mc_flags & C_INITIALIZED))
+ return mdb_cursor_first(mc, key, data);
+
+ mp = mc->mc_pg[mc->mc_top];
+
+ if (mc->mc_flags & C_EOF) {
+ if (mc->mc_ki[mc->mc_top] >= NUMKEYS(mp)-1)
+ return MDB_NOTFOUND;
+ mc->mc_flags ^= C_EOF;
+ }
+
+ if (mc->mc_db->md_flags & MDB_DUPSORT) {
+ leaf = NODEPTR(mp, mc->mc_ki[mc->mc_top]);
+ if (F_ISSET(leaf->mn_flags, F_DUPDATA)) {
+ if (op == MDB_NEXT || op == MDB_NEXT_DUP) {
+ rc = mdb_cursor_next(&mc->mc_xcursor->mx_cursor, data, NULL, MDB_NEXT);
+ if (op != MDB_NEXT || rc != MDB_NOTFOUND) {
+ if (rc == MDB_SUCCESS)
+ MDB_GET_KEY(leaf, key);
+ return rc;
+ }
+ }
+ } else {
+ mc->mc_xcursor->mx_cursor.mc_flags &= ~(C_INITIALIZED|C_EOF);
+ if (op == MDB_NEXT_DUP)
+ return MDB_NOTFOUND;
+ }
+ }
+
+ DPRINTF(("cursor_next: top page is %"Z"u in cursor %p",
+ mdb_dbg_pgno(mp), (void *) mc));
+ if (mc->mc_flags & C_DEL) {
+ mc->mc_flags ^= C_DEL;
+ goto skip;
+ }
+
+ if (mc->mc_ki[mc->mc_top] + 1u >= NUMKEYS(mp)) {
+ DPUTS("=====> move to next sibling page");
+ if ((rc = mdb_cursor_sibling(mc, 1)) != MDB_SUCCESS) {
+ mc->mc_flags |= C_EOF;
+ return rc;
+ }
+ mp = mc->mc_pg[mc->mc_top];
+ DPRINTF(("next page is %"Z"u, key index %u", mp->mp_pgno, mc->mc_ki[mc->mc_top]));
+ } else
+ mc->mc_ki[mc->mc_top]++;
+
+skip:
+ DPRINTF(("==> cursor points to page %"Z"u with %u keys, key index %u",
+ mdb_dbg_pgno(mp), NUMKEYS(mp), mc->mc_ki[mc->mc_top]));
+
+ if (IS_LEAF2(mp)) {
+ key->mv_size = mc->mc_db->md_pad;
+ key->mv_data = LEAF2KEY(mp, mc->mc_ki[mc->mc_top], key->mv_size);
+ return MDB_SUCCESS;
+ }
+
+ mdb_cassert(mc, IS_LEAF(mp));
+ leaf = NODEPTR(mp, mc->mc_ki[mc->mc_top]);
+
+ if (F_ISSET(leaf->mn_flags, F_DUPDATA)) {
+ mdb_xcursor_init1(mc, leaf);
+ }
+ if (data) {
+ if ((rc = mdb_node_read(mc, leaf, data)) != MDB_SUCCESS)
+ return rc;
+
+ if (F_ISSET(leaf->mn_flags, F_DUPDATA)) {
+ rc = mdb_cursor_first(&mc->mc_xcursor->mx_cursor, data, NULL);
+ if (rc != MDB_SUCCESS)
+ return rc;
+ }
+ }
+
+ MDB_GET_KEY(leaf, key);
+ return MDB_SUCCESS;
+}
+
+/** Move the cursor to the previous data item. */
+static int
+mdb_cursor_prev(MDB_cursor *mc, MDB_val *key, MDB_val *data, MDB_cursor_op op)
+{
+ MDB_page *mp;
+ MDB_node *leaf;
+ int rc;
+
+ if (!(mc->mc_flags & C_INITIALIZED)) {
+ rc = mdb_cursor_last(mc, key, data);
+ if (rc)
+ return rc;
+ mc->mc_ki[mc->mc_top]++;
+ }
+
+ mp = mc->mc_pg[mc->mc_top];
+
+ if (mc->mc_db->md_flags & MDB_DUPSORT) {
+ leaf = NODEPTR(mp, mc->mc_ki[mc->mc_top]);
+ if (F_ISSET(leaf->mn_flags, F_DUPDATA)) {
+ if (op == MDB_PREV || op == MDB_PREV_DUP) {
+ rc = mdb_cursor_prev(&mc->mc_xcursor->mx_cursor, data, NULL, MDB_PREV);
+ if (op != MDB_PREV || rc != MDB_NOTFOUND) {
+ if (rc == MDB_SUCCESS) {
+ MDB_GET_KEY(leaf, key);
+ mc->mc_flags &= ~C_EOF;
+ }
+ return rc;
+ }
+ }
+ } else {
+ mc->mc_xcursor->mx_cursor.mc_flags &= ~(C_INITIALIZED|C_EOF);
+ if (op == MDB_PREV_DUP)
+ return MDB_NOTFOUND;
+ }
+ }
+
+ DPRINTF(("cursor_prev: top page is %"Z"u in cursor %p",
+ mdb_dbg_pgno(mp), (void *) mc));
+
+ mc->mc_flags &= ~(C_EOF|C_DEL);
+
+ if (mc->mc_ki[mc->mc_top] == 0) {
+ DPUTS("=====> move to prev sibling page");
+ if ((rc = mdb_cursor_sibling(mc, 0)) != MDB_SUCCESS) {
+ return rc;
+ }
+ mp = mc->mc_pg[mc->mc_top];
+ mc->mc_ki[mc->mc_top] = NUMKEYS(mp) - 1;
+ DPRINTF(("prev page is %"Z"u, key index %u", mp->mp_pgno, mc->mc_ki[mc->mc_top]));
+ } else
+ mc->mc_ki[mc->mc_top]--;
+
+ DPRINTF(("==> cursor points to page %"Z"u with %u keys, key index %u",
+ mdb_dbg_pgno(mp), NUMKEYS(mp), mc->mc_ki[mc->mc_top]));
+
+ if (IS_LEAF2(mp)) {
+ key->mv_size = mc->mc_db->md_pad;
+ key->mv_data = LEAF2KEY(mp, mc->mc_ki[mc->mc_top], key->mv_size);
+ return MDB_SUCCESS;
+ }
+
+ mdb_cassert(mc, IS_LEAF(mp));
+ leaf = NODEPTR(mp, mc->mc_ki[mc->mc_top]);
+
+ if (F_ISSET(leaf->mn_flags, F_DUPDATA)) {
+ mdb_xcursor_init1(mc, leaf);
+ }
+ if (data) {
+ if ((rc = mdb_node_read(mc, leaf, data)) != MDB_SUCCESS)
+ return rc;
+
+ if (F_ISSET(leaf->mn_flags, F_DUPDATA)) {
+ rc = mdb_cursor_last(&mc->mc_xcursor->mx_cursor, data, NULL);
+ if (rc != MDB_SUCCESS)
+ return rc;
+ }
+ }
+
+ MDB_GET_KEY(leaf, key);
+ return MDB_SUCCESS;
+}
+
+/** Set the cursor on a specific data item. */
+static int
+mdb_cursor_set(MDB_cursor *mc, MDB_val *key, MDB_val *data,
+ MDB_cursor_op op, int *exactp)
+{
+ int rc;
+ MDB_page *mp;
+ MDB_node *leaf = NULL;
+ DKBUF;
+
+ if (key->mv_size == 0)
+ return MDB_BAD_VALSIZE;
+
+ if (mc->mc_xcursor)
+ mc->mc_xcursor->mx_cursor.mc_flags &= ~(C_INITIALIZED|C_EOF);
+
+ /* See if we're already on the right page */
+ if (mc->mc_flags & C_INITIALIZED) {
+ MDB_val nodekey;
+
+ mp = mc->mc_pg[mc->mc_top];
+ if (!NUMKEYS(mp)) {
+ mc->mc_ki[mc->mc_top] = 0;
+ return MDB_NOTFOUND;
+ }
+ if (mp->mp_flags & P_LEAF2) {
+ nodekey.mv_size = mc->mc_db->md_pad;
+ nodekey.mv_data = LEAF2KEY(mp, 0, nodekey.mv_size);
+ } else {
+ leaf = NODEPTR(mp, 0);
+ MDB_GET_KEY2(leaf, nodekey);
+ }
+ rc = mc->mc_dbx->md_cmp(key, &nodekey);
+ if (rc == 0) {
+ /* Probably happens rarely, but first node on the page
+ * was the one we wanted.
+ */
+ mc->mc_ki[mc->mc_top] = 0;
+ if (exactp)
+ *exactp = 1;
+ goto set1;
+ }
+ if (rc > 0) {
+ unsigned int i;
+ unsigned int nkeys = NUMKEYS(mp);
+ if (nkeys > 1) {
+ if (mp->mp_flags & P_LEAF2) {
+ nodekey.mv_data = LEAF2KEY(mp,
+ nkeys-1, nodekey.mv_size);
+ } else {
+ leaf = NODEPTR(mp, nkeys-1);
+ MDB_GET_KEY2(leaf, nodekey);
+ }
+ rc = mc->mc_dbx->md_cmp(key, &nodekey);
+ if (rc == 0) {
+ /* last node was the one we wanted */
+ mc->mc_ki[mc->mc_top] = nkeys-1;
+ if (exactp)
+ *exactp = 1;
+ goto set1;
+ }
+ if (rc < 0) {
+ if (mc->mc_ki[mc->mc_top] < NUMKEYS(mp)) {
+ /* This is definitely the right page, skip search_page */
+ if (mp->mp_flags & P_LEAF2) {
+ nodekey.mv_data = LEAF2KEY(mp,
+ mc->mc_ki[mc->mc_top], nodekey.mv_size);
+ } else {
+ leaf = NODEPTR(mp, mc->mc_ki[mc->mc_top]);
+ MDB_GET_KEY2(leaf, nodekey);
+ }
+ rc = mc->mc_dbx->md_cmp(key, &nodekey);
+ if (rc == 0) {
+ /* current node was the one we wanted */
+ if (exactp)
+ *exactp = 1;
+ goto set1;
+ }
+ }
+ rc = 0;
+ mc->mc_flags &= ~C_EOF;
+ goto set2;
+ }
+ }
+ /* If any parents have right-sibs, search.
+ * Otherwise, there's nothing further.
+ */
+ for (i=0; i<mc->mc_top; i++)
+ if (mc->mc_ki[i] <
+ NUMKEYS(mc->mc_pg[i])-1)
+ break;
+ if (i == mc->mc_top) {
+ /* There are no other pages */
+ mc->mc_ki[mc->mc_top] = nkeys;
+ return MDB_NOTFOUND;
+ }
+ }
+ if (!mc->mc_top) {
+ /* There are no other pages */
+ mc->mc_ki[mc->mc_top] = 0;
+ if (op == MDB_SET_RANGE && !exactp) {
+ rc = 0;
+ goto set1;
+ } else
+ return MDB_NOTFOUND;
+ }
+ } else {
+ mc->mc_pg[0] = 0;
+ }
+
+ rc = mdb_page_search(mc, key, 0);
+ if (rc != MDB_SUCCESS)
+ return rc;
+
+ mp = mc->mc_pg[mc->mc_top];
+ mdb_cassert(mc, IS_LEAF(mp));
+
+set2:
+ leaf = mdb_node_search(mc, key, exactp);
+ if (exactp != NULL && !*exactp) {
+ /* MDB_SET specified and not an exact match. */
+ return MDB_NOTFOUND;
+ }
+
+ if (leaf == NULL) {
+ DPUTS("===> inexact leaf not found, goto sibling");
+ if ((rc = mdb_cursor_sibling(mc, 1)) != MDB_SUCCESS) {
+ mc->mc_flags |= C_EOF;
+ return rc; /* no entries matched */
+ }
+ mp = mc->mc_pg[mc->mc_top];
+ mdb_cassert(mc, IS_LEAF(mp));
+ leaf = NODEPTR(mp, 0);
+ }
+
+set1:
+ mc->mc_flags |= C_INITIALIZED;
+ mc->mc_flags &= ~C_EOF;
+
+ if (IS_LEAF2(mp)) {
+ if (op == MDB_SET_RANGE || op == MDB_SET_KEY) {
+ key->mv_size = mc->mc_db->md_pad;
+ key->mv_data = LEAF2KEY(mp, mc->mc_ki[mc->mc_top], key->mv_size);
+ }
+ return MDB_SUCCESS;
+ }
+
+ if (F_ISSET(leaf->mn_flags, F_DUPDATA)) {
+ mdb_xcursor_init1(mc, leaf);
+ }
+ if (data) {
+ if (F_ISSET(leaf->mn_flags, F_DUPDATA)) {
+ if (op == MDB_SET || op == MDB_SET_KEY || op == MDB_SET_RANGE) {
+ rc = mdb_cursor_first(&mc->mc_xcursor->mx_cursor, data, NULL);
+ } else {
+ int ex2, *ex2p;
+ if (op == MDB_GET_BOTH) {
+ ex2p = &ex2;
+ ex2 = 0;
+ } else {
+ ex2p = NULL;
+ }
+ rc = mdb_cursor_set(&mc->mc_xcursor->mx_cursor, data, NULL, MDB_SET_RANGE, ex2p);
+ if (rc != MDB_SUCCESS)
+ return rc;
+ }
+ } else if (op == MDB_GET_BOTH || op == MDB_GET_BOTH_RANGE) {
+ MDB_val olddata;
+ MDB_cmp_func *dcmp;
+ if ((rc = mdb_node_read(mc, leaf, &olddata)) != MDB_SUCCESS)
+ return rc;
+ dcmp = mc->mc_dbx->md_dcmp;
+#if UINT_MAX < SIZE_MAX
+ if (dcmp == mdb_cmp_int && olddata.mv_size == sizeof(size_t))
+ dcmp = mdb_cmp_clong;
+#endif
+ rc = dcmp(data, &olddata);
+ if (rc) {
+ if (op == MDB_GET_BOTH || rc > 0)
+ return MDB_NOTFOUND;
+ rc = 0;
+ }
+ *data = olddata;
+
+ } else {
+ if (mc->mc_xcursor)
+ mc->mc_xcursor->mx_cursor.mc_flags &= ~(C_INITIALIZED|C_EOF);
+ if ((rc = mdb_node_read(mc, leaf, data)) != MDB_SUCCESS)
+ return rc;
+ }
+ }
+
+ /* The key already matches in all other cases */
+ if (op == MDB_SET_RANGE || op == MDB_SET_KEY)
+ MDB_GET_KEY(leaf, key);
+ DPRINTF(("==> cursor placed on key [%s]", DKEY(key)));
+
+ return rc;
+}
+
+/** Move the cursor to the first item in the database. */
+static int
+mdb_cursor_first(MDB_cursor *mc, MDB_val *key, MDB_val *data)
+{
+ int rc;
+ MDB_node *leaf;
+
+ if (mc->mc_xcursor)
+ mc->mc_xcursor->mx_cursor.mc_flags &= ~(C_INITIALIZED|C_EOF);
+
+ if (!(mc->mc_flags & C_INITIALIZED) || mc->mc_top) {
+ rc = mdb_page_search(mc, NULL, MDB_PS_FIRST);
+ if (rc != MDB_SUCCESS)
+ return rc;
+ }
+ mdb_cassert(mc, IS_LEAF(mc->mc_pg[mc->mc_top]));
+
+ leaf = NODEPTR(mc->mc_pg[mc->mc_top], 0);
+ mc->mc_flags |= C_INITIALIZED;
+ mc->mc_flags &= ~C_EOF;
+
+ mc->mc_ki[mc->mc_top] = 0;
+
+ if (IS_LEAF2(mc->mc_pg[mc->mc_top])) {
+ key->mv_size = mc->mc_db->md_pad;
+ key->mv_data = LEAF2KEY(mc->mc_pg[mc->mc_top], 0, key->mv_size);
+ return MDB_SUCCESS;
+ }
+
+ if (data) {
+ if (F_ISSET(leaf->mn_flags, F_DUPDATA)) {
+ mdb_xcursor_init1(mc, leaf);
+ rc = mdb_cursor_first(&mc->mc_xcursor->mx_cursor, data, NULL);
+ if (rc)
+ return rc;
+ } else {
+ if ((rc = mdb_node_read(mc, leaf, data)) != MDB_SUCCESS)
+ return rc;
+ }
+ }
+ MDB_GET_KEY(leaf, key);
+ return MDB_SUCCESS;
+}
+
+/** Move the cursor to the last item in the database. */
+static int
+mdb_cursor_last(MDB_cursor *mc, MDB_val *key, MDB_val *data)
+{
+ int rc;
+ MDB_node *leaf;
+
+ if (mc->mc_xcursor)
+ mc->mc_xcursor->mx_cursor.mc_flags &= ~(C_INITIALIZED|C_EOF);
+
+ if (!(mc->mc_flags & C_INITIALIZED) || mc->mc_top) {
+ rc = mdb_page_search(mc, NULL, MDB_PS_LAST);
+ if (rc != MDB_SUCCESS)
+ return rc;
+ }
+ mdb_cassert(mc, IS_LEAF(mc->mc_pg[mc->mc_top]));
+
+ mc->mc_ki[mc->mc_top] = NUMKEYS(mc->mc_pg[mc->mc_top]) - 1;
+ mc->mc_flags |= C_INITIALIZED|C_EOF;
+ leaf = NODEPTR(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top]);
+
+ if (IS_LEAF2(mc->mc_pg[mc->mc_top])) {
+ key->mv_size = mc->mc_db->md_pad;
+ key->mv_data = LEAF2KEY(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top], key->mv_size);
+ return MDB_SUCCESS;
+ }
+
+ if (data) {
+ if (F_ISSET(leaf->mn_flags, F_DUPDATA)) {
+ mdb_xcursor_init1(mc, leaf);
+ rc = mdb_cursor_last(&mc->mc_xcursor->mx_cursor, data, NULL);
+ if (rc)
+ return rc;
+ } else {
+ if ((rc = mdb_node_read(mc, leaf, data)) != MDB_SUCCESS)
+ return rc;
+ }
+ }
+
+ MDB_GET_KEY(leaf, key);
+ return MDB_SUCCESS;
+}
+
+int
+mdb_cursor_get(MDB_cursor *mc, MDB_val *key, MDB_val *data,
+ MDB_cursor_op op)
+{
+ int rc;
+ int exact = 0;
+ int (*mfunc)(MDB_cursor *mc, MDB_val *key, MDB_val *data);
+
+ if (mc == NULL)
+ return EINVAL;
+
+ if (mc->mc_txn->mt_flags & MDB_TXN_BLOCKED)
+ return MDB_BAD_TXN;
+
+ switch (op) {
+ case MDB_GET_CURRENT:
+ if (!(mc->mc_flags & C_INITIALIZED)) {
+ rc = EINVAL;
+ } else {
+ MDB_page *mp = mc->mc_pg[mc->mc_top];
+ int nkeys = NUMKEYS(mp);
+ if (!nkeys || mc->mc_ki[mc->mc_top] >= nkeys) {
+ mc->mc_ki[mc->mc_top] = nkeys;
+ rc = MDB_NOTFOUND;
+ break;
+ }
+ rc = MDB_SUCCESS;
+ if (IS_LEAF2(mp)) {
+ key->mv_size = mc->mc_db->md_pad;
+ key->mv_data = LEAF2KEY(mp, mc->mc_ki[mc->mc_top], key->mv_size);
+ } else {
+ MDB_node *leaf = NODEPTR(mp, mc->mc_ki[mc->mc_top]);
+ MDB_GET_KEY(leaf, key);
+ if (data) {
+ if (F_ISSET(leaf->mn_flags, F_DUPDATA)) {
+ rc = mdb_cursor_get(&mc->mc_xcursor->mx_cursor, data, NULL, MDB_GET_CURRENT);
+ } else {
+ rc = mdb_node_read(mc, leaf, data);
+ }
+ }
+ }
+ }
+ break;
+ case MDB_GET_BOTH:
+ case MDB_GET_BOTH_RANGE:
+ if (data == NULL) {
+ rc = EINVAL;
+ break;
+ }
+ if (mc->mc_xcursor == NULL) {
+ rc = MDB_INCOMPATIBLE;
+ break;
+ }
+ /* FALLTHRU */
+ case MDB_SET:
+ case MDB_SET_KEY:
+ case MDB_SET_RANGE:
+ if (key == NULL) {
+ rc = EINVAL;
+ } else {
+ rc = mdb_cursor_set(mc, key, data, op,
+ op == MDB_SET_RANGE ? NULL : &exact);
+ }
+ break;
+ case MDB_GET_MULTIPLE:
+ if (data == NULL || !(mc->mc_flags & C_INITIALIZED)) {
+ rc = EINVAL;
+ break;
+ }
+ if (!(mc->mc_db->md_flags & MDB_DUPFIXED)) {
+ rc = MDB_INCOMPATIBLE;
+ break;
+ }
+ rc = MDB_SUCCESS;
+ if (!(mc->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED) ||
+ (mc->mc_xcursor->mx_cursor.mc_flags & C_EOF))
+ break;
+ goto fetchm;
+ case MDB_NEXT_MULTIPLE:
+ if (data == NULL) {
+ rc = EINVAL;
+ break;
+ }
+ if (!(mc->mc_db->md_flags & MDB_DUPFIXED)) {
+ rc = MDB_INCOMPATIBLE;
+ break;
+ }
+ rc = mdb_cursor_next(mc, key, data, MDB_NEXT_DUP);
+ if (rc == MDB_SUCCESS) {
+ if (mc->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED) {
+ MDB_cursor *mx;
+fetchm:
+ mx = &mc->mc_xcursor->mx_cursor;
+ data->mv_size = NUMKEYS(mx->mc_pg[mx->mc_top]) *
+ mx->mc_db->md_pad;
+ data->mv_data = METADATA(mx->mc_pg[mx->mc_top]);
+ mx->mc_ki[mx->mc_top] = NUMKEYS(mx->mc_pg[mx->mc_top])-1;
+ } else {
+ rc = MDB_NOTFOUND;
+ }
+ }
+ break;
+ case MDB_PREV_MULTIPLE:
+ if (data == NULL) {
+ rc = EINVAL;
+ break;
+ }
+ if (!(mc->mc_db->md_flags & MDB_DUPFIXED)) {
+ rc = MDB_INCOMPATIBLE;
+ break;
+ }
+ if (!(mc->mc_flags & C_INITIALIZED))
+ rc = mdb_cursor_last(mc, key, data);
+ else
+ rc = MDB_SUCCESS;
+ if (rc == MDB_SUCCESS) {
+ MDB_cursor *mx = &mc->mc_xcursor->mx_cursor;
+ if (mx->mc_flags & C_INITIALIZED) {
+ rc = mdb_cursor_sibling(mx, 0);
+ if (rc == MDB_SUCCESS)
+ goto fetchm;
+ } else {
+ rc = MDB_NOTFOUND;
+ }
+ }
+ break;
+ case MDB_NEXT:
+ case MDB_NEXT_DUP:
+ case MDB_NEXT_NODUP:
+ rc = mdb_cursor_next(mc, key, data, op);
+ break;
+ case MDB_PREV:
+ case MDB_PREV_DUP:
+ case MDB_PREV_NODUP:
+ rc = mdb_cursor_prev(mc, key, data, op);
+ break;
+ case MDB_FIRST:
+ rc = mdb_cursor_first(mc, key, data);
+ break;
+ case MDB_FIRST_DUP:
+ mfunc = mdb_cursor_first;
+ mmove:
+ if (data == NULL || !(mc->mc_flags & C_INITIALIZED)) {
+ rc = EINVAL;
+ break;
+ }
+ if (mc->mc_xcursor == NULL) {
+ rc = MDB_INCOMPATIBLE;
+ break;
+ }
+ if (mc->mc_ki[mc->mc_top] >= NUMKEYS(mc->mc_pg[mc->mc_top])) {
+ mc->mc_ki[mc->mc_top] = NUMKEYS(mc->mc_pg[mc->mc_top]);
+ rc = MDB_NOTFOUND;
+ break;
+ }
+ {
+ MDB_node *leaf = NODEPTR(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top]);
+ if (!F_ISSET(leaf->mn_flags, F_DUPDATA)) {
+ MDB_GET_KEY(leaf, key);
+ rc = mdb_node_read(mc, leaf, data);
+ break;
+ }
+ }
+ if (!(mc->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED)) {
+ rc = EINVAL;
+ break;
+ }
+ rc = mfunc(&mc->mc_xcursor->mx_cursor, data, NULL);
+ break;
+ case MDB_LAST:
+ rc = mdb_cursor_last(mc, key, data);
+ break;
+ case MDB_LAST_DUP:
+ mfunc = mdb_cursor_last;
+ goto mmove;
+ default:
+ DPRINTF(("unhandled/unimplemented cursor operation %u", op));
+ rc = EINVAL;
+ break;
+ }
+
+ if (mc->mc_flags & C_DEL)
+ mc->mc_flags ^= C_DEL;
+
+ return rc;
+}
+
+/** Touch all the pages in the cursor stack. Set mc_top.
+ * Makes sure all the pages are writable, before attempting a write operation.
+ * @param[in] mc The cursor to operate on.
+ */
+static int
+mdb_cursor_touch(MDB_cursor *mc)
+{
+ int rc = MDB_SUCCESS;
+
+ if (mc->mc_dbi >= CORE_DBS && !(*mc->mc_dbflag & (DB_DIRTY|DB_DUPDATA))) {
+ /* Touch DB record of named DB */
+ MDB_cursor mc2;
+ MDB_xcursor mcx;
+ if (TXN_DBI_CHANGED(mc->mc_txn, mc->mc_dbi))
+ return MDB_BAD_DBI;
+ mdb_cursor_init(&mc2, mc->mc_txn, MAIN_DBI, &mcx);
+ rc = mdb_page_search(&mc2, &mc->mc_dbx->md_name, MDB_PS_MODIFY);
+ if (rc)
+ return rc;
+ *mc->mc_dbflag |= DB_DIRTY;
+ }
+ mc->mc_top = 0;
+ if (mc->mc_snum) {
+ do {
+ rc = mdb_page_touch(mc);
+ } while (!rc && ++(mc->mc_top) < mc->mc_snum);
+ mc->mc_top = mc->mc_snum-1;
+ }
+ return rc;
+}
+
+/** Do not spill pages to disk if txn is getting full, may fail instead */
+#define MDB_NOSPILL 0x8000
+
+int
+mdb_cursor_put(MDB_cursor *mc, MDB_val *key, MDB_val *data,
+ unsigned int flags)
+{
+ MDB_env *env;
+ MDB_node *leaf = NULL;
+ MDB_page *fp, *mp, *sub_root = NULL;
+ uint16_t fp_flags;
+ MDB_val xdata, *rdata, dkey, olddata;
+ MDB_db dummy;
+ int do_sub = 0, insert_key, insert_data;
+ unsigned int mcount = 0, dcount = 0, nospill;
+ size_t nsize;
+ int rc, rc2;
+ unsigned int nflags;
+ DKBUF;
+
+ if (mc == NULL || key == NULL)
+ return EINVAL;
+
+ env = mc->mc_txn->mt_env;
+
+ /* Check this first so counter will always be zero on any
+ * early failures.
+ */
+ if (flags & MDB_MULTIPLE) {
+ dcount = data[1].mv_size;
+ data[1].mv_size = 0;
+ if (!F_ISSET(mc->mc_db->md_flags, MDB_DUPFIXED))
+ return MDB_INCOMPATIBLE;
+ }
+
+ nospill = flags & MDB_NOSPILL;
+ flags &= ~MDB_NOSPILL;
+
+ if (mc->mc_txn->mt_flags & (MDB_TXN_RDONLY|MDB_TXN_BLOCKED))
+ return (mc->mc_txn->mt_flags & MDB_TXN_RDONLY) ? EACCES : MDB_BAD_TXN;
+
+ if (key->mv_size-1 >= ENV_MAXKEY(env))
+ return MDB_BAD_VALSIZE;
+
+#if SIZE_MAX > MAXDATASIZE
+ if (data->mv_size > ((mc->mc_db->md_flags & MDB_DUPSORT) ? ENV_MAXKEY(env) : MAXDATASIZE))
+ return MDB_BAD_VALSIZE;
+#else
+ if ((mc->mc_db->md_flags & MDB_DUPSORT) && data->mv_size > ENV_MAXKEY(env))
+ return MDB_BAD_VALSIZE;
+#endif
+
+ DPRINTF(("==> put db %d key [%s], size %"Z"u, data size %"Z"u",
+ DDBI(mc), DKEY(key), key ? key->mv_size : 0, data->mv_size));
+
+ dkey.mv_size = 0;
+
+ if (flags == MDB_CURRENT) {
+ if (!(mc->mc_flags & C_INITIALIZED))
+ return EINVAL;
+ rc = MDB_SUCCESS;
+ } else if (mc->mc_db->md_root == P_INVALID) {
+ /* new database, cursor has nothing to point to */
+ mc->mc_snum = 0;
+ mc->mc_top = 0;
+ mc->mc_flags &= ~C_INITIALIZED;
+ rc = MDB_NO_ROOT;
+ } else {
+ int exact = 0;
+ MDB_val d2;
+ if (flags & MDB_APPEND) {
+ MDB_val k2;
+ rc = mdb_cursor_last(mc, &k2, &d2);
+ if (rc == 0) {
+ rc = mc->mc_dbx->md_cmp(key, &k2);
+ if (rc > 0) {
+ rc = MDB_NOTFOUND;
+ mc->mc_ki[mc->mc_top]++;
+ } else {
+ /* new key is <= last key */
+ rc = MDB_KEYEXIST;
+ }
+ }
+ } else {
+ rc = mdb_cursor_set(mc, key, &d2, MDB_SET, &exact);
+ }
+ if ((flags & MDB_NOOVERWRITE) && rc == 0) {
+ DPRINTF(("duplicate key [%s]", DKEY(key)));
+ *data = d2;
+ return MDB_KEYEXIST;
+ }
+ if (rc && rc != MDB_NOTFOUND)
+ return rc;
+ }
+
+ if (mc->mc_flags & C_DEL)
+ mc->mc_flags ^= C_DEL;
+
+ /* Cursor is positioned, check for room in the dirty list */
+ if (!nospill) {
+ if (flags & MDB_MULTIPLE) {
+ rdata = &xdata;
+ xdata.mv_size = data->mv_size * dcount;
+ } else {
+ rdata = data;
+ }
+ if ((rc2 = mdb_page_spill(mc, key, rdata)))
+ return rc2;
+ }
+
+ if (rc == MDB_NO_ROOT) {
+ MDB_page *np;
+ /* new database, write a root leaf page */
+ DPUTS("allocating new root leaf page");
+ if ((rc2 = mdb_page_new(mc, P_LEAF, 1, &np))) {
+ return rc2;
+ }
+ mdb_cursor_push(mc, np);
+ mc->mc_db->md_root = np->mp_pgno;
+ mc->mc_db->md_depth++;
+ *mc->mc_dbflag |= DB_DIRTY;
+ if ((mc->mc_db->md_flags & (MDB_DUPSORT|MDB_DUPFIXED))
+ == MDB_DUPFIXED)
+ np->mp_flags |= P_LEAF2;
+ mc->mc_flags |= C_INITIALIZED;
+ } else {
+ /* make sure all cursor pages are writable */
+ rc2 = mdb_cursor_touch(mc);
+ if (rc2)
+ return rc2;
+ }
+
+ insert_key = insert_data = rc;
+ if (insert_key) {
+ /* The key does not exist */
+ DPRINTF(("inserting key at index %i", mc->mc_ki[mc->mc_top]));
+ if ((mc->mc_db->md_flags & MDB_DUPSORT) &&
+ LEAFSIZE(key, data) > env->me_nodemax)
+ {
+ /* Too big for a node, insert in sub-DB. Set up an empty
+ * "old sub-page" for prep_subDB to expand to a full page.
+ */
+ fp_flags = P_LEAF|P_DIRTY;
+ fp = env->me_pbuf;
+ fp->mp_pad = data->mv_size; /* used if MDB_DUPFIXED */
+ fp->mp_lower = fp->mp_upper = (PAGEHDRSZ-PAGEBASE);
+ olddata.mv_size = PAGEHDRSZ;
+ goto prep_subDB;
+ }
+ } else {
+ /* there's only a key anyway, so this is a no-op */
+ if (IS_LEAF2(mc->mc_pg[mc->mc_top])) {
+ char *ptr;
+ unsigned int ksize = mc->mc_db->md_pad;
+ if (key->mv_size != ksize)
+ return MDB_BAD_VALSIZE;
+ ptr = LEAF2KEY(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top], ksize);
+ memcpy(ptr, key->mv_data, ksize);
+fix_parent:
+ /* if overwriting slot 0 of leaf, need to
+ * update branch key if there is a parent page
+ */
+ if (mc->mc_top && !mc->mc_ki[mc->mc_top]) {
+ unsigned short dtop = 1;
+ mc->mc_top--;
+ /* slot 0 is always an empty key, find real slot */
+ while (mc->mc_top && !mc->mc_ki[mc->mc_top]) {
+ mc->mc_top--;
+ dtop++;
+ }
+ if (mc->mc_ki[mc->mc_top])
+ rc2 = mdb_update_key(mc, key);
+ else
+ rc2 = MDB_SUCCESS;
+ mc->mc_top += dtop;
+ if (rc2)
+ return rc2;
+ }
+ return MDB_SUCCESS;
+ }
+
+more:
+ leaf = NODEPTR(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top]);
+ olddata.mv_size = NODEDSZ(leaf);
+ olddata.mv_data = NODEDATA(leaf);
+
+ /* DB has dups? */
+ if (F_ISSET(mc->mc_db->md_flags, MDB_DUPSORT)) {
+ /* Prepare (sub-)page/sub-DB to accept the new item,
+ * if needed. fp: old sub-page or a header faking
+ * it. mp: new (sub-)page. offset: growth in page
+ * size. xdata: node data with new page or DB.
+ */
+ unsigned i, offset = 0;
+ mp = fp = xdata.mv_data = env->me_pbuf;
+ mp->mp_pgno = mc->mc_pg[mc->mc_top]->mp_pgno;
+
+ /* Was a single item before, must convert now */
+ if (!F_ISSET(leaf->mn_flags, F_DUPDATA)) {
+ MDB_cmp_func *dcmp;
+ /* Just overwrite the current item */
+ if (flags == MDB_CURRENT)
+ goto current;
+ dcmp = mc->mc_dbx->md_dcmp;
+#if UINT_MAX < SIZE_MAX
+ if (dcmp == mdb_cmp_int && olddata.mv_size == sizeof(size_t))
+ dcmp = mdb_cmp_clong;
+#endif
+ /* does data match? */
+ if (!dcmp(data, &olddata)) {
+ if (flags & (MDB_NODUPDATA|MDB_APPENDDUP))
+ return MDB_KEYEXIST;
+ /* overwrite it */
+ goto current;
+ }
+
+ /* Back up original data item */
+ dkey.mv_size = olddata.mv_size;
+ dkey.mv_data = memcpy(fp+1, olddata.mv_data, olddata.mv_size);
+
+ /* Make sub-page header for the dup items, with dummy body */
+ fp->mp_flags = P_LEAF|P_DIRTY|P_SUBP;
+ fp->mp_lower = (PAGEHDRSZ-PAGEBASE);
+ xdata.mv_size = PAGEHDRSZ + dkey.mv_size + data->mv_size;
+ if (mc->mc_db->md_flags & MDB_DUPFIXED) {
+ fp->mp_flags |= P_LEAF2;
+ fp->mp_pad = data->mv_size;
+ xdata.mv_size += 2 * data->mv_size; /* leave space for 2 more */
+ } else {
+ xdata.mv_size += 2 * (sizeof(indx_t) + NODESIZE) +
+ (dkey.mv_size & 1) + (data->mv_size & 1);
+ }
+ fp->mp_upper = xdata.mv_size - PAGEBASE;
+ olddata.mv_size = xdata.mv_size; /* pretend olddata is fp */
+ } else if (leaf->mn_flags & F_SUBDATA) {
+ /* Data is on sub-DB, just store it */
+ flags |= F_DUPDATA|F_SUBDATA;
+ goto put_sub;
+ } else {
+ /* Data is on sub-page */
+ fp = olddata.mv_data;
+ switch (flags) {
+ default:
+ if (!(mc->mc_db->md_flags & MDB_DUPFIXED)) {
+ offset = EVEN(NODESIZE + sizeof(indx_t) +
+ data->mv_size);
+ break;
+ }
+ offset = fp->mp_pad;
+ if (SIZELEFT(fp) < offset) {
+ offset *= 4; /* space for 4 more */
+ break;
+ }
+ /* FALLTHRU: Big enough MDB_DUPFIXED sub-page */
+ case MDB_CURRENT:
+ fp->mp_flags |= P_DIRTY;
+ COPY_PGNO(fp->mp_pgno, mp->mp_pgno);
+ mc->mc_xcursor->mx_cursor.mc_pg[0] = fp;
+ flags |= F_DUPDATA;
+ goto put_sub;
+ }
+ xdata.mv_size = olddata.mv_size + offset;
+ }
+
+ fp_flags = fp->mp_flags;
+ if (NODESIZE + NODEKSZ(leaf) + xdata.mv_size > env->me_nodemax) {
+ /* Too big for a sub-page, convert to sub-DB */
+ fp_flags &= ~P_SUBP;
+prep_subDB:
+ if (mc->mc_db->md_flags & MDB_DUPFIXED) {
+ fp_flags |= P_LEAF2;
+ dummy.md_pad = fp->mp_pad;
+ dummy.md_flags = MDB_DUPFIXED;
+ if (mc->mc_db->md_flags & MDB_INTEGERDUP)
+ dummy.md_flags |= MDB_INTEGERKEY;
+ } else {
+ dummy.md_pad = 0;
+ dummy.md_flags = 0;
+ }
+ dummy.md_depth = 1;
+ dummy.md_branch_pages = 0;
+ dummy.md_leaf_pages = 1;
+ dummy.md_overflow_pages = 0;
+ dummy.md_entries = NUMKEYS(fp);
+ xdata.mv_size = sizeof(MDB_db);
+ xdata.mv_data = &dummy;
+ if ((rc = mdb_page_alloc(mc, 1, &mp)))
+ return rc;
+ offset = env->me_psize - olddata.mv_size;
+ flags |= F_DUPDATA|F_SUBDATA;
+ dummy.md_root = mp->mp_pgno;
+ sub_root = mp;
+ }
+ if (mp != fp) {
+ mp->mp_flags = fp_flags | P_DIRTY;
+ mp->mp_pad = fp->mp_pad;
+ mp->mp_lower = fp->mp_lower;
+ mp->mp_upper = fp->mp_upper + offset;
+ if (fp_flags & P_LEAF2) {
+ memcpy(METADATA(mp), METADATA(fp), NUMKEYS(fp) * fp->mp_pad);
+ } else {
+ memcpy((char *)mp + mp->mp_upper + PAGEBASE, (char *)fp + fp->mp_upper + PAGEBASE,
+ olddata.mv_size - fp->mp_upper - PAGEBASE);
+ memcpy((char *)(&mp->mp_ptrs), (char *)(&fp->mp_ptrs), NUMKEYS(fp) * sizeof(mp->mp_ptrs[0]));
+ for (i=0; i<NUMKEYS(fp); i++)
+ mp->mp_ptrs[i] += offset;
+ }
+ }
+
+ rdata = &xdata;
+ flags |= F_DUPDATA;
+ do_sub = 1;
+ if (!insert_key)
+ mdb_node_del(mc, 0);
+ goto new_sub;
+ }
+current:
+ /* LMDB passes F_SUBDATA in 'flags' to write a DB record */
+ if ((leaf->mn_flags ^ flags) & F_SUBDATA)
+ return MDB_INCOMPATIBLE;
+ /* overflow page overwrites need special handling */
+ if (F_ISSET(leaf->mn_flags, F_BIGDATA)) {
+ MDB_page *omp;
+ pgno_t pg;
+ int level, ovpages, dpages = OVPAGES(data->mv_size, env->me_psize);
+
+ memcpy(&pg, olddata.mv_data, sizeof(pg));
+ if ((rc2 = mdb_page_get(mc, pg, &omp, &level)) != 0)
+ return rc2;
+ ovpages = omp->mp_pages;
+
+ /* Is the ov page large enough? */
+ if (ovpages >= dpages) {
+ if (!(omp->mp_flags & P_DIRTY) &&
+ (level || (env->me_flags & MDB_WRITEMAP)))
+ {
+ rc = mdb_page_unspill(mc->mc_txn, omp, &omp);
+ if (rc)
+ return rc;
+ level = 0; /* dirty in this txn or clean */
+ }
+ /* Is it dirty? */
+ if (omp->mp_flags & P_DIRTY) {
+ /* yes, overwrite it. Note in this case we don't
+ * bother to try shrinking the page if the new data
+ * is smaller than the overflow threshold.
+ */
+ if (level > 1) {
+ /* It is writable only in a parent txn */
+ size_t sz = (size_t) env->me_psize * ovpages, off;
+ MDB_page *np = mdb_page_malloc(mc->mc_txn, ovpages);
+ MDB_ID2 id2;
+ if (!np)
+ return ENOMEM;
+ id2.mid = pg;
+ id2.mptr = np;
+ /* Note - this page is already counted in parent's dirty_room */
+ rc2 = mdb_mid2l_insert(mc->mc_txn->mt_u.dirty_list, &id2);
+ mdb_cassert(mc, rc2 == 0);
+ /* Currently we make the page look as with put() in the
+ * parent txn, in case the user peeks at MDB_RESERVEd
+ * or unused parts. Some users treat ovpages specially.
+ */
+ if (!(flags & MDB_RESERVE)) {
+ /* Skip the part where LMDB will put *data.
+ * Copy end of page, adjusting alignment so
+ * compiler may copy words instead of bytes.
+ */
+ off = (PAGEHDRSZ + data->mv_size) & -sizeof(size_t);
+ memcpy((size_t *)((char *)np + off),
+ (size_t *)((char *)omp + off), sz - off);
+ sz = PAGEHDRSZ;
+ }
+ memcpy(np, omp, sz); /* Copy beginning of page */
+ omp = np;
+ }
+ SETDSZ(leaf, data->mv_size);
+ if (F_ISSET(flags, MDB_RESERVE))
+ data->mv_data = METADATA(omp);
+ else
+ memcpy(METADATA(omp), data->mv_data, data->mv_size);
+ return MDB_SUCCESS;
+ }
+ }
+ if ((rc2 = mdb_ovpage_free(mc, omp)) != MDB_SUCCESS)
+ return rc2;
+ } else if (data->mv_size == olddata.mv_size) {
+ /* same size, just replace it. Note that we could
+ * also reuse this node if the new data is smaller,
+ * but instead we opt to shrink the node in that case.
+ */
+ if (F_ISSET(flags, MDB_RESERVE))
+ data->mv_data = olddata.mv_data;
+ else if (!(mc->mc_flags & C_SUB))
+ memcpy(olddata.mv_data, data->mv_data, data->mv_size);
+ else {
+ memcpy(NODEKEY(leaf), key->mv_data, key->mv_size);
+ goto fix_parent;
+ }
+ return MDB_SUCCESS;
+ }
+ mdb_node_del(mc, 0);
+ }
+
+ rdata = data;
+
+new_sub:
+ nflags = flags & NODE_ADD_FLAGS;
+ nsize = IS_LEAF2(mc->mc_pg[mc->mc_top]) ? key->mv_size : mdb_leaf_size(env, key, rdata);
+ if (SIZELEFT(mc->mc_pg[mc->mc_top]) < nsize) {
+ if (( flags & (F_DUPDATA|F_SUBDATA)) == F_DUPDATA )
+ nflags &= ~MDB_APPEND; /* sub-page may need room to grow */
+ if (!insert_key)
+ nflags |= MDB_SPLIT_REPLACE;
+ rc = mdb_page_split(mc, key, rdata, P_INVALID, nflags);
+ } else {
+ /* There is room already in this leaf page. */
+ rc = mdb_node_add(mc, mc->mc_ki[mc->mc_top], key, rdata, 0, nflags);
+ if (rc == 0) {
+ /* Adjust other cursors pointing to mp */
+ MDB_cursor *m2, *m3;
+ MDB_dbi dbi = mc->mc_dbi;
+ unsigned i = mc->mc_top;
+ MDB_page *mp = mc->mc_pg[i];
+
+ for (m2 = mc->mc_txn->mt_cursors[dbi]; m2; m2=m2->mc_next) {
+ if (mc->mc_flags & C_SUB)
+ m3 = &m2->mc_xcursor->mx_cursor;
+ else
+ m3 = m2;
+ if (m3 == mc || m3->mc_snum < mc->mc_snum || m3->mc_pg[i] != mp) continue;
+ if (m3->mc_ki[i] >= mc->mc_ki[i] && insert_key) {
+ m3->mc_ki[i]++;
+ }
+ XCURSOR_REFRESH(m3, i, mp);
+ }
+ }
+ }
+
+ if (rc == MDB_SUCCESS) {
+ /* Now store the actual data in the child DB. Note that we're
+ * storing the user data in the keys field, so there are strict
+ * size limits on dupdata. The actual data fields of the child
+ * DB are all zero size.
+ */
+ if (do_sub) {
+ int xflags, new_dupdata;
+ size_t ecount;
+put_sub:
+ xdata.mv_size = 0;
+ xdata.mv_data = "";
+ leaf = NODEPTR(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top]);
+ if (flags & MDB_CURRENT) {
+ xflags = MDB_CURRENT|MDB_NOSPILL;
+ } else {
+ mdb_xcursor_init1(mc, leaf);
+ xflags = (flags & MDB_NODUPDATA) ?
+ MDB_NOOVERWRITE|MDB_NOSPILL : MDB_NOSPILL;
+ }
+ if (sub_root)
+ mc->mc_xcursor->mx_cursor.mc_pg[0] = sub_root;
+ new_dupdata = (int)dkey.mv_size;
+ /* converted, write the original data first */
+ if (dkey.mv_size) {
+ rc = mdb_cursor_put(&mc->mc_xcursor->mx_cursor, &dkey, &xdata, xflags);
+ if (rc)
+ goto bad_sub;
+ /* we've done our job */
+ dkey.mv_size = 0;
+ }
+ if (!(leaf->mn_flags & F_SUBDATA) || sub_root) {
+ /* Adjust other cursors pointing to mp */
+ MDB_cursor *m2;
+ MDB_xcursor *mx = mc->mc_xcursor;
+ unsigned i = mc->mc_top;
+ MDB_page *mp = mc->mc_pg[i];
+
+ for (m2 = mc->mc_txn->mt_cursors[mc->mc_dbi]; m2; m2=m2->mc_next) {
+ if (m2 == mc || m2->mc_snum < mc->mc_snum) continue;
+ if (!(m2->mc_flags & C_INITIALIZED)) continue;
+ if (m2->mc_pg[i] == mp) {
+ if (m2->mc_ki[i] == mc->mc_ki[i]) {
+ mdb_xcursor_init2(m2, mx, new_dupdata);
+ } else if (!insert_key) {
+ XCURSOR_REFRESH(m2, i, mp);
+ }
+ }
+ }
+ }
+ ecount = mc->mc_xcursor->mx_db.md_entries;
+ if (flags & MDB_APPENDDUP)
+ xflags |= MDB_APPEND;
+ rc = mdb_cursor_put(&mc->mc_xcursor->mx_cursor, data, &xdata, xflags);
+ if (flags & F_SUBDATA) {
+ void *db = NODEDATA(leaf);
+ memcpy(db, &mc->mc_xcursor->mx_db, sizeof(MDB_db));
+ }
+ insert_data = mc->mc_xcursor->mx_db.md_entries - ecount;
+ }
+ /* Increment count unless we just replaced an existing item. */
+ if (insert_data)
+ mc->mc_db->md_entries++;
+ if (insert_key) {
+ /* Invalidate txn if we created an empty sub-DB */
+ if (rc)
+ goto bad_sub;
+ /* If we succeeded and the key didn't exist before,
+ * make sure the cursor is marked valid.
+ */
+ mc->mc_flags |= C_INITIALIZED;
+ }
+ if (flags & MDB_MULTIPLE) {
+ if (!rc) {
+ mcount++;
+ /* let caller know how many succeeded, if any */
+ data[1].mv_size = mcount;
+ if (mcount < dcount) {
+ data[0].mv_data = (char *)data[0].mv_data + data[0].mv_size;
+ insert_key = insert_data = 0;
+ goto more;
+ }
+ }
+ }
+ return rc;
+bad_sub:
+ if (rc == MDB_KEYEXIST) /* should not happen, we deleted that item */
+ rc = MDB_CORRUPTED;
+ }
+ mc->mc_txn->mt_flags |= MDB_TXN_ERROR;
+ return rc;
+}
+
+int
+mdb_cursor_del(MDB_cursor *mc, unsigned int flags)
+{
+ MDB_node *leaf;
+ MDB_page *mp;
+ int rc;
+
+ if (mc->mc_txn->mt_flags & (MDB_TXN_RDONLY|MDB_TXN_BLOCKED))
+ return (mc->mc_txn->mt_flags & MDB_TXN_RDONLY) ? EACCES : MDB_BAD_TXN;
+
+ if (!(mc->mc_flags & C_INITIALIZED))
+ return EINVAL;
+
+ if (mc->mc_ki[mc->mc_top] >= NUMKEYS(mc->mc_pg[mc->mc_top]))
+ return MDB_NOTFOUND;
+
+ if (!(flags & MDB_NOSPILL) && (rc = mdb_page_spill(mc, NULL, NULL)))
+ return rc;
+
+ rc = mdb_cursor_touch(mc);
+ if (rc)
+ return rc;
+
+ mp = mc->mc_pg[mc->mc_top];
+ if (IS_LEAF2(mp))
+ goto del_key;
+ leaf = NODEPTR(mp, mc->mc_ki[mc->mc_top]);
+
+ if (F_ISSET(leaf->mn_flags, F_DUPDATA)) {
+ if (flags & MDB_NODUPDATA) {
+ /* mdb_cursor_del0() will subtract the final entry */
+ mc->mc_db->md_entries -= mc->mc_xcursor->mx_db.md_entries - 1;
+ mc->mc_xcursor->mx_cursor.mc_flags &= ~C_INITIALIZED;
+ } else {
+ if (!F_ISSET(leaf->mn_flags, F_SUBDATA)) {
+ mc->mc_xcursor->mx_cursor.mc_pg[0] = NODEDATA(leaf);
+ }
+ rc = mdb_cursor_del(&mc->mc_xcursor->mx_cursor, MDB_NOSPILL);
+ if (rc)
+ return rc;
+ /* If sub-DB still has entries, we're done */
+ if (mc->mc_xcursor->mx_db.md_entries) {
+ if (leaf->mn_flags & F_SUBDATA) {
+ /* update subDB info */
+ void *db = NODEDATA(leaf);
+ memcpy(db, &mc->mc_xcursor->mx_db, sizeof(MDB_db));
+ } else {
+ MDB_cursor *m2;
+ /* shrink fake page */
+ mdb_node_shrink(mp, mc->mc_ki[mc->mc_top]);
+ leaf = NODEPTR(mp, mc->mc_ki[mc->mc_top]);
+ mc->mc_xcursor->mx_cursor.mc_pg[0] = NODEDATA(leaf);
+ /* fix other sub-DB cursors pointed at fake pages on this page */
+ for (m2 = mc->mc_txn->mt_cursors[mc->mc_dbi]; m2; m2=m2->mc_next) {
+ if (m2 == mc || m2->mc_snum < mc->mc_snum) continue;
+ if (!(m2->mc_flags & C_INITIALIZED)) continue;
+ if (m2->mc_pg[mc->mc_top] == mp) {
+ XCURSOR_REFRESH(m2, mc->mc_top, mp);
+ }
+ }
+ }
+ mc->mc_db->md_entries--;
+ return rc;
+ } else {
+ mc->mc_xcursor->mx_cursor.mc_flags &= ~C_INITIALIZED;
+ }
+ /* otherwise fall thru and delete the sub-DB */
+ }
+
+ if (leaf->mn_flags & F_SUBDATA) {
+ /* add all the child DB's pages to the free list */
+ rc = mdb_drop0(&mc->mc_xcursor->mx_cursor, 0);
+ if (rc)
+ goto fail;
+ }
+ }
+ /* LMDB passes F_SUBDATA in 'flags' to delete a DB record */
+ else if ((leaf->mn_flags ^ flags) & F_SUBDATA) {
+ rc = MDB_INCOMPATIBLE;
+ goto fail;
+ }
+
+ /* add overflow pages to free list */
+ if (F_ISSET(leaf->mn_flags, F_BIGDATA)) {
+ MDB_page *omp;
+ pgno_t pg;
+
+ memcpy(&pg, NODEDATA(leaf), sizeof(pg));
+ if ((rc = mdb_page_get(mc, pg, &omp, NULL)) ||
+ (rc = mdb_ovpage_free(mc, omp)))
+ goto fail;
+ }
+
+del_key:
+ return mdb_cursor_del0(mc);
+
+fail:
+ mc->mc_txn->mt_flags |= MDB_TXN_ERROR;
+ return rc;
+}
+
+/** Allocate and initialize new pages for a database.
+ * Set #MDB_TXN_ERROR on failure.
+ * @param[in] mc a cursor on the database being added to.
+ * @param[in] flags flags defining what type of page is being allocated.
+ * @param[in] num the number of pages to allocate. This is usually 1,
+ * unless allocating overflow pages for a large record.
+ * @param[out] mp Address of a page, or NULL on failure.
+ * @return 0 on success, non-zero on failure.
+ */
+static int
+mdb_page_new(MDB_cursor *mc, uint32_t flags, int num, MDB_page **mp)
+{
+ MDB_page *np;
+ int rc;
+
+ if ((rc = mdb_page_alloc(mc, num, &np)))
+ return rc;
+ DPRINTF(("allocated new mpage %"Z"u, page size %u",
+ np->mp_pgno, mc->mc_txn->mt_env->me_psize));
+ np->mp_flags = flags | P_DIRTY;
+ np->mp_lower = (PAGEHDRSZ-PAGEBASE);
+ np->mp_upper = mc->mc_txn->mt_env->me_psize - PAGEBASE;
+
+ if (IS_BRANCH(np))
+ mc->mc_db->md_branch_pages++;
+ else if (IS_LEAF(np))
+ mc->mc_db->md_leaf_pages++;
+ else if (IS_OVERFLOW(np)) {
+ mc->mc_db->md_overflow_pages += num;
+ np->mp_pages = num;
+ }
+ *mp = np;
+
+ return 0;
+}
+
+/** Calculate the size of a leaf node.
+ * The size depends on the environment's page size; if a data item
+ * is too large it will be put onto an overflow page and the node
+ * size will only include the key and not the data. Sizes are always
+ * rounded up to an even number of bytes, to guarantee 2-byte alignment
+ * of the #MDB_node headers.
+ * @param[in] env The environment handle.
+ * @param[in] key The key for the node.
+ * @param[in] data The data for the node.
+ * @return The number of bytes needed to store the node.
+ */
+static size_t
+mdb_leaf_size(MDB_env *env, MDB_val *key, MDB_val *data)
+{
+ size_t sz;
+
+ sz = LEAFSIZE(key, data);
+ if (sz > env->me_nodemax) {
+ /* put on overflow page */
+ sz -= data->mv_size - sizeof(pgno_t);
+ }
+
+ return EVEN(sz + sizeof(indx_t));
+}
+
+/** Calculate the size of a branch node.
+ * The size should depend on the environment's page size but since
+ * we currently don't support spilling large keys onto overflow
+ * pages, it's simply the size of the #MDB_node header plus the
+ * size of the key. Sizes are always rounded up to an even number
+ * of bytes, to guarantee 2-byte alignment of the #MDB_node headers.
+ * @param[in] env The environment handle.
+ * @param[in] key The key for the node.
+ * @return The number of bytes needed to store the node.
+ */
+static size_t
+mdb_branch_size(MDB_env *env, MDB_val *key)
+{
+ size_t sz;
+
+ sz = INDXSIZE(key);
+ if (sz > env->me_nodemax) {
+ /* put on overflow page */
+ /* not implemented */
+ /* sz -= key->size - sizeof(pgno_t); */
+ }
+
+ return sz + sizeof(indx_t);
+}
+
+/** Add a node to the page pointed to by the cursor.
+ * Set #MDB_TXN_ERROR on failure.
+ * @param[in] mc The cursor for this operation.
+ * @param[in] indx The index on the page where the new node should be added.
+ * @param[in] key The key for the new node.
+ * @param[in] data The data for the new node, if any.
+ * @param[in] pgno The page number, if adding a branch node.
+ * @param[in] flags Flags for the node.
+ * @return 0 on success, non-zero on failure. Possible errors are:
+ * <ul>
+ * <li>ENOMEM - failed to allocate overflow pages for the node.
+ * <li>MDB_PAGE_FULL - there is insufficient room in the page. This error
+ * should never happen since all callers already calculate the
+ * page's free space before calling this function.
+ * </ul>
+ */
+static int
+mdb_node_add(MDB_cursor *mc, indx_t indx,
+ MDB_val *key, MDB_val *data, pgno_t pgno, unsigned int flags)
+{
+ unsigned int i;
+ size_t node_size = NODESIZE;
+ ssize_t room;
+ indx_t ofs;
+ MDB_node *node;
+ MDB_page *mp = mc->mc_pg[mc->mc_top];
+ MDB_page *ofp = NULL; /* overflow page */
+ void *ndata;
+ DKBUF;
+
+ mdb_cassert(mc, mp->mp_upper >= mp->mp_lower);
+
+ DPRINTF(("add to %s %spage %"Z"u index %i, data size %"Z"u key size %"Z"u [%s]",
+ IS_LEAF(mp) ? "leaf" : "branch",
+ IS_SUBP(mp) ? "sub-" : "",
+ mdb_dbg_pgno(mp), indx, data ? data->mv_size : 0,
+ key ? key->mv_size : 0, key ? DKEY(key) : "null"));
+
+ if (IS_LEAF2(mp)) {
+ /* Move higher keys up one slot. */
+ int ksize = mc->mc_db->md_pad, dif;
+ char *ptr = LEAF2KEY(mp, indx, ksize);
+ dif = NUMKEYS(mp) - indx;
+ if (dif > 0)
+ memmove(ptr+ksize, ptr, dif*ksize);
+ /* insert new key */
+ memcpy(ptr, key->mv_data, ksize);
+
+ /* Just using these for counting */
+ mp->mp_lower += sizeof(indx_t);
+ mp->mp_upper -= ksize - sizeof(indx_t);
+ return MDB_SUCCESS;
+ }
+
+ room = (ssize_t)SIZELEFT(mp) - (ssize_t)sizeof(indx_t);
+ if (key != NULL)
+ node_size += key->mv_size;
+ if (IS_LEAF(mp)) {
+ mdb_cassert(mc, key && data);
+ if (F_ISSET(flags, F_BIGDATA)) {
+ /* Data already on overflow page. */
+ node_size += sizeof(pgno_t);
+ } else if (node_size + data->mv_size > mc->mc_txn->mt_env->me_nodemax) {
+ int ovpages = OVPAGES(data->mv_size, mc->mc_txn->mt_env->me_psize);
+ int rc;
+ /* Put data on overflow page. */
+ DPRINTF(("data size is %"Z"u, node would be %"Z"u, put data on overflow page",
+ data->mv_size, node_size+data->mv_size));
+ node_size = EVEN(node_size + sizeof(pgno_t));
+ if ((ssize_t)node_size > room)
+ goto full;
+ if ((rc = mdb_page_new(mc, P_OVERFLOW, ovpages, &ofp)))
+ return rc;
+ DPRINTF(("allocated overflow page %"Z"u", ofp->mp_pgno));
+ flags |= F_BIGDATA;
+ goto update;
+ } else {
+ node_size += data->mv_size;
+ }
+ }
+ node_size = EVEN(node_size);
+ if ((ssize_t)node_size > room)
+ goto full;
+
+update:
+ /* Move higher pointers up one slot. */
+ for (i = NUMKEYS(mp); i > indx; i--)
+ mp->mp_ptrs[i] = mp->mp_ptrs[i - 1];
+
+ /* Adjust free space offsets. */
+ ofs = mp->mp_upper - node_size;
+ mdb_cassert(mc, ofs >= mp->mp_lower + sizeof(indx_t));
+ mp->mp_ptrs[indx] = ofs;
+ mp->mp_upper = ofs;
+ mp->mp_lower += sizeof(indx_t);
+
+ /* Write the node data. */
+ node = NODEPTR(mp, indx);
+ node->mn_ksize = (key == NULL) ? 0 : key->mv_size;
+ node->mn_flags = flags;
+ if (IS_LEAF(mp))
+ SETDSZ(node,data->mv_size);
+ else
+ SETPGNO(node,pgno);
+
+ if (key)
+ memcpy(NODEKEY(node), key->mv_data, key->mv_size);
+
+ if (IS_LEAF(mp)) {
+ ndata = NODEDATA(node);
+ if (ofp == NULL) {
+ if (F_ISSET(flags, F_BIGDATA))
+ memcpy(ndata, data->mv_data, sizeof(pgno_t));
+ else if (F_ISSET(flags, MDB_RESERVE))
+ data->mv_data = ndata;
+ else
+ memcpy(ndata, data->mv_data, data->mv_size);
+ } else {
+ memcpy(ndata, &ofp->mp_pgno, sizeof(pgno_t));
+ ndata = METADATA(ofp);
+ if (F_ISSET(flags, MDB_RESERVE))
+ data->mv_data = ndata;
+ else
+ memcpy(ndata, data->mv_data, data->mv_size);
+ }
+ }
+
+ return MDB_SUCCESS;
+
+full:
+ DPRINTF(("not enough room in page %"Z"u, got %u ptrs",
+ mdb_dbg_pgno(mp), NUMKEYS(mp)));
+ DPRINTF(("upper-lower = %u - %u = %"Z"d", mp->mp_upper,mp->mp_lower,room));
+ DPRINTF(("node size = %"Z"u", node_size));
+ mc->mc_txn->mt_flags |= MDB_TXN_ERROR;
+ return MDB_PAGE_FULL;
+}
+
+/** Delete the specified node from a page.
+ * @param[in] mc Cursor pointing to the node to delete.
+ * @param[in] ksize The size of a node. Only used if the page is
+ * part of a #MDB_DUPFIXED database.
+ */
+static void
+mdb_node_del(MDB_cursor *mc, int ksize)
+{
+ MDB_page *mp = mc->mc_pg[mc->mc_top];
+ indx_t indx = mc->mc_ki[mc->mc_top];
+ unsigned int sz;
+ indx_t i, j, numkeys, ptr;
+ MDB_node *node;
+ char *base;
+
+ DPRINTF(("delete node %u on %s page %"Z"u", indx,
+ IS_LEAF(mp) ? "leaf" : "branch", mdb_dbg_pgno(mp)));
+ numkeys = NUMKEYS(mp);
+ mdb_cassert(mc, indx < numkeys);
+
+ if (IS_LEAF2(mp)) {
+ int x = numkeys - 1 - indx;
+ base = LEAF2KEY(mp, indx, ksize);
+ if (x)
+ memmove(base, base + ksize, x * ksize);
+ mp->mp_lower -= sizeof(indx_t);
+ mp->mp_upper += ksize - sizeof(indx_t);
+ return;
+ }
+
+ node = NODEPTR(mp, indx);
+ sz = NODESIZE + node->mn_ksize;
+ if (IS_LEAF(mp)) {
+ if (F_ISSET(node->mn_flags, F_BIGDATA))
+ sz += sizeof(pgno_t);
+ else
+ sz += NODEDSZ(node);
+ }
+ sz = EVEN(sz);
+
+ ptr = mp->mp_ptrs[indx];
+ for (i = j = 0; i < numkeys; i++) {
+ if (i != indx) {
+ mp->mp_ptrs[j] = mp->mp_ptrs[i];
+ if (mp->mp_ptrs[i] < ptr)
+ mp->mp_ptrs[j] += sz;
+ j++;
+ }
+ }
+
+ base = (char *)mp + mp->mp_upper + PAGEBASE;
+ memmove(base + sz, base, ptr - mp->mp_upper);
+
+ mp->mp_lower -= sizeof(indx_t);
+ mp->mp_upper += sz;
+}
+
+/** Compact the main page after deleting a node on a subpage.
+ * @param[in] mp The main page to operate on.
+ * @param[in] indx The index of the subpage on the main page.
+ */
+static void
+mdb_node_shrink(MDB_page *mp, indx_t indx)
+{
+ MDB_node *node;
+ MDB_page *sp, *xp;
+ char *base;
+ indx_t delta, nsize, len, ptr;
+ int i;
+
+ node = NODEPTR(mp, indx);
+ sp = (MDB_page *)NODEDATA(node);
+ delta = SIZELEFT(sp);
+ nsize = NODEDSZ(node) - delta;
+
+ /* Prepare to shift upward, set len = length(subpage part to shift) */
+ if (IS_LEAF2(sp)) {
+ len = nsize;
+ if (nsize & 1)
+ return; /* do not make the node uneven-sized */
+ } else {
+ xp = (MDB_page *)((char *)sp + delta); /* destination subpage */
+ for (i = NUMKEYS(sp); --i >= 0; )
+ xp->mp_ptrs[i] = sp->mp_ptrs[i] - delta;
+ len = PAGEHDRSZ;
+ }
+ sp->mp_upper = sp->mp_lower;
+ COPY_PGNO(sp->mp_pgno, mp->mp_pgno);
+ SETDSZ(node, nsize);
+
+ /* Shift <lower nodes...initial part of subpage> upward */
+ base = (char *)mp + mp->mp_upper + PAGEBASE;
+ memmove(base + delta, base, (char *)sp + len - base);
+
+ ptr = mp->mp_ptrs[indx];
+ for (i = NUMKEYS(mp); --i >= 0; ) {
+ if (mp->mp_ptrs[i] <= ptr)
+ mp->mp_ptrs[i] += delta;
+ }
+ mp->mp_upper += delta;
+}
+
+/** Initial setup of a sorted-dups cursor.
+ * Sorted duplicates are implemented as a sub-database for the given key.
+ * The duplicate data items are actually keys of the sub-database.
+ * Operations on the duplicate data items are performed using a sub-cursor
+ * initialized when the sub-database is first accessed. This function does
+ * the preliminary setup of the sub-cursor, filling in the fields that
+ * depend only on the parent DB.
+ * @param[in] mc The main cursor whose sorted-dups cursor is to be initialized.
+ */
+static void
+mdb_xcursor_init0(MDB_cursor *mc)
+{
+ MDB_xcursor *mx = mc->mc_xcursor;
+
+ mx->mx_cursor.mc_xcursor = NULL;
+ mx->mx_cursor.mc_txn = mc->mc_txn;
+ mx->mx_cursor.mc_db = &mx->mx_db;
+ mx->mx_cursor.mc_dbx = &mx->mx_dbx;
+ mx->mx_cursor.mc_dbi = mc->mc_dbi;
+ mx->mx_cursor.mc_dbflag = &mx->mx_dbflag;
+ mx->mx_cursor.mc_snum = 0;
+ mx->mx_cursor.mc_top = 0;
+ mx->mx_cursor.mc_flags = C_SUB;
+ mx->mx_dbx.md_name.mv_size = 0;
+ mx->mx_dbx.md_name.mv_data = NULL;
+ mx->mx_dbx.md_cmp = mc->mc_dbx->md_dcmp;
+ mx->mx_dbx.md_dcmp = NULL;
+ mx->mx_dbx.md_rel = mc->mc_dbx->md_rel;
+}
+
+/** Final setup of a sorted-dups cursor.
+ * Sets up the fields that depend on the data from the main cursor.
+ * @param[in] mc The main cursor whose sorted-dups cursor is to be initialized.
+ * @param[in] node The data containing the #MDB_db record for the
+ * sorted-dup database.
+ */
+static void
+mdb_xcursor_init1(MDB_cursor *mc, MDB_node *node)
+{
+ MDB_xcursor *mx = mc->mc_xcursor;
+
+ if (node->mn_flags & F_SUBDATA) {
+ memcpy(&mx->mx_db, NODEDATA(node), sizeof(MDB_db));
+ mx->mx_cursor.mc_pg[0] = 0;
+ mx->mx_cursor.mc_snum = 0;
+ mx->mx_cursor.mc_top = 0;
+ mx->mx_cursor.mc_flags = C_SUB;
+ } else {
+ MDB_page *fp = NODEDATA(node);
+ mx->mx_db.md_pad = 0;
+ mx->mx_db.md_flags = 0;
+ mx->mx_db.md_depth = 1;
+ mx->mx_db.md_branch_pages = 0;
+ mx->mx_db.md_leaf_pages = 1;
+ mx->mx_db.md_overflow_pages = 0;
+ mx->mx_db.md_entries = NUMKEYS(fp);
+ COPY_PGNO(mx->mx_db.md_root, fp->mp_pgno);
+ mx->mx_cursor.mc_snum = 1;
+ mx->mx_cursor.mc_top = 0;
+ mx->mx_cursor.mc_flags = C_INITIALIZED|C_SUB;
+ mx->mx_cursor.mc_pg[0] = fp;
+ mx->mx_cursor.mc_ki[0] = 0;
+ if (mc->mc_db->md_flags & MDB_DUPFIXED) {
+ mx->mx_db.md_flags = MDB_DUPFIXED;
+ mx->mx_db.md_pad = fp->mp_pad;
+ if (mc->mc_db->md_flags & MDB_INTEGERDUP)
+ mx->mx_db.md_flags |= MDB_INTEGERKEY;
+ }
+ }
+ DPRINTF(("Sub-db -%u root page %"Z"u", mx->mx_cursor.mc_dbi,
+ mx->mx_db.md_root));
+ mx->mx_dbflag = DB_VALID|DB_USRVALID|DB_DUPDATA;
+#if UINT_MAX < SIZE_MAX
+ if (mx->mx_dbx.md_cmp == mdb_cmp_int && mx->mx_db.md_pad == sizeof(size_t))
+ mx->mx_dbx.md_cmp = mdb_cmp_clong;
+#endif
+}
+
+
+/** Fixup a sorted-dups cursor due to underlying update.
+ * Sets up some fields that depend on the data from the main cursor.
+ * Almost the same as init1, but skips initialization steps if the
+ * xcursor had already been used.
+ * @param[in] mc The main cursor whose sorted-dups cursor is to be fixed up.
+ * @param[in] src_mx The xcursor of an up-to-date cursor.
+ * @param[in] new_dupdata True if converting from a non-#F_DUPDATA item.
+ */
+static void
+mdb_xcursor_init2(MDB_cursor *mc, MDB_xcursor *src_mx, int new_dupdata)
+{
+ MDB_xcursor *mx = mc->mc_xcursor;
+
+ if (new_dupdata) {
+ mx->mx_cursor.mc_snum = 1;
+ mx->mx_cursor.mc_top = 0;
+ mx->mx_cursor.mc_flags |= C_INITIALIZED;
+ mx->mx_cursor.mc_ki[0] = 0;
+ mx->mx_dbflag = DB_VALID|DB_USRVALID|DB_DUPDATA;
+#if UINT_MAX < SIZE_MAX
+ mx->mx_dbx.md_cmp = src_mx->mx_dbx.md_cmp;
+#endif
+ } else if (!(mx->mx_cursor.mc_flags & C_INITIALIZED)) {
+ return;
+ }
+ mx->mx_db = src_mx->mx_db;
+ mx->mx_cursor.mc_pg[0] = src_mx->mx_cursor.mc_pg[0];
+ DPRINTF(("Sub-db -%u root page %"Z"u", mx->mx_cursor.mc_dbi,
+ mx->mx_db.md_root));
+}
+
+/** Initialize a cursor for a given transaction and database. */
+static void
+mdb_cursor_init(MDB_cursor *mc, MDB_txn *txn, MDB_dbi dbi, MDB_xcursor *mx)
+{
+ mc->mc_next = NULL;
+ mc->mc_backup = NULL;
+ mc->mc_dbi = dbi;
+ mc->mc_txn = txn;
+ mc->mc_db = &txn->mt_dbs[dbi];
+ mc->mc_dbx = &txn->mt_dbxs[dbi];
+ mc->mc_dbflag = &txn->mt_dbflags[dbi];
+ mc->mc_snum = 0;
+ mc->mc_top = 0;
+ mc->mc_pg[0] = 0;
+ mc->mc_ki[0] = 0;
+ mc->mc_flags = 0;
+ if (txn->mt_dbs[dbi].md_flags & MDB_DUPSORT) {
+ mdb_tassert(txn, mx != NULL);
+ mc->mc_xcursor = mx;
+ mdb_xcursor_init0(mc);
+ } else {
+ mc->mc_xcursor = NULL;
+ }
+ if (*mc->mc_dbflag & DB_STALE) {
+ mdb_page_search(mc, NULL, MDB_PS_ROOTONLY);
+ }
+}
+
+int
+mdb_cursor_open(MDB_txn *txn, MDB_dbi dbi, MDB_cursor **ret)
+{
+ MDB_cursor *mc;
+ size_t size = sizeof(MDB_cursor);
+
+ if (!ret || !TXN_DBI_EXIST(txn, dbi, DB_VALID))
+ return EINVAL;
+
+ if (txn->mt_flags & MDB_TXN_BLOCKED)
+ return MDB_BAD_TXN;
+
+ if (dbi == FREE_DBI && !F_ISSET(txn->mt_flags, MDB_TXN_RDONLY))
+ return EINVAL;
+
+ if (txn->mt_dbs[dbi].md_flags & MDB_DUPSORT)
+ size += sizeof(MDB_xcursor);
+
+ if ((mc = malloc(size)) != NULL) {
+ mdb_cursor_init(mc, txn, dbi, (MDB_xcursor *)(mc + 1));
+ if (txn->mt_cursors) {
+ mc->mc_next = txn->mt_cursors[dbi];
+ txn->mt_cursors[dbi] = mc;
+ mc->mc_flags |= C_UNTRACK;
+ }
+ } else {
+ return ENOMEM;
+ }
+
+ *ret = mc;
+
+ return MDB_SUCCESS;
+}
+
+int
+mdb_cursor_renew(MDB_txn *txn, MDB_cursor *mc)
+{
+ if (!mc || !TXN_DBI_EXIST(txn, mc->mc_dbi, DB_VALID))
+ return EINVAL;
+
+ if ((mc->mc_flags & C_UNTRACK) || txn->mt_cursors)
+ return EINVAL;
+
+ if (txn->mt_flags & MDB_TXN_BLOCKED)
+ return MDB_BAD_TXN;
+
+ mdb_cursor_init(mc, txn, mc->mc_dbi, mc->mc_xcursor);
+ return MDB_SUCCESS;
+}
+
+/* Return the count of duplicate data items for the current key */
+int
+mdb_cursor_count(MDB_cursor *mc, size_t *countp)
+{
+ MDB_node *leaf;
+
+ if (mc == NULL || countp == NULL)
+ return EINVAL;
+
+ if (mc->mc_xcursor == NULL)
+ return MDB_INCOMPATIBLE;
+
+ if (mc->mc_txn->mt_flags & MDB_TXN_BLOCKED)
+ return MDB_BAD_TXN;
+
+ if (!(mc->mc_flags & C_INITIALIZED))
+ return EINVAL;
+
+ if (!mc->mc_snum)
+ return MDB_NOTFOUND;
+
+ if (mc->mc_flags & C_EOF) {
+ if (mc->mc_ki[mc->mc_top] >= NUMKEYS(mc->mc_pg[mc->mc_top]))
+ return MDB_NOTFOUND;
+ mc->mc_flags ^= C_EOF;
+ }
+
+ leaf = NODEPTR(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top]);
+ if (!F_ISSET(leaf->mn_flags, F_DUPDATA)) {
+ *countp = 1;
+ } else {
+ if (!(mc->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED))
+ return EINVAL;
+
+ *countp = mc->mc_xcursor->mx_db.md_entries;
+ }
+ return MDB_SUCCESS;
+}
+
+void
+mdb_cursor_close(MDB_cursor *mc)
+{
+ if (mc && !mc->mc_backup) {
+ /* remove from txn, if tracked */
+ if ((mc->mc_flags & C_UNTRACK) && mc->mc_txn->mt_cursors) {
+ MDB_cursor **prev = &mc->mc_txn->mt_cursors[mc->mc_dbi];
+ while (*prev && *prev != mc) prev = &(*prev)->mc_next;
+ if (*prev == mc)
+ *prev = mc->mc_next;
+ }
+ free(mc);
+ }
+}
+
+MDB_txn *
+mdb_cursor_txn(MDB_cursor *mc)
+{
+ if (!mc) return NULL;
+ return mc->mc_txn;
+}
+
+MDB_dbi
+mdb_cursor_dbi(MDB_cursor *mc)
+{
+ return mc->mc_dbi;
+}
+
+/** Replace the key for a branch node with a new key.
+ * Set #MDB_TXN_ERROR on failure.
+ * @param[in] mc Cursor pointing to the node to operate on.
+ * @param[in] key The new key to use.
+ * @return 0 on success, non-zero on failure.
+ */
+static int
+mdb_update_key(MDB_cursor *mc, MDB_val *key)
+{
+ MDB_page *mp;
+ MDB_node *node;
+ char *base;
+ size_t len;
+ int delta, ksize, oksize;
+ indx_t ptr, i, numkeys, indx;
+ DKBUF;
+
+ indx = mc->mc_ki[mc->mc_top];
+ mp = mc->mc_pg[mc->mc_top];
+ node = NODEPTR(mp, indx);
+ ptr = mp->mp_ptrs[indx];
+#if MDB_DEBUG
+ {
+ MDB_val k2;
+ char kbuf2[DKBUF_MAXKEYSIZE*2+1];
+ k2.mv_data = NODEKEY(node);
+ k2.mv_size = node->mn_ksize;
+ DPRINTF(("update key %u (ofs %u) [%s] to [%s] on page %"Z"u",
+ indx, ptr,
+ mdb_dkey(&k2, kbuf2),
+ DKEY(key),
+ mp->mp_pgno));
+ }
+#endif
+
+ /* Sizes must be 2-byte aligned. */
+ ksize = EVEN(key->mv_size);
+ oksize = EVEN(node->mn_ksize);
+ delta = ksize - oksize;
+
+ /* Shift node contents if EVEN(key length) changed. */
+ if (delta) {
+ if (delta > 0 && SIZELEFT(mp) < delta) {
+ pgno_t pgno;
+ /* not enough space left, do a delete and split */
+ DPRINTF(("Not enough room, delta = %d, splitting...", delta));
+ pgno = NODEPGNO(node);
+ mdb_node_del(mc, 0);
+ return mdb_page_split(mc, key, NULL, pgno, MDB_SPLIT_REPLACE);
+ }
+
+ numkeys = NUMKEYS(mp);
+ for (i = 0; i < numkeys; i++) {
+ if (mp->mp_ptrs[i] <= ptr)
+ mp->mp_ptrs[i] -= delta;
+ }
+
+ base = (char *)mp + mp->mp_upper + PAGEBASE;
+ len = ptr - mp->mp_upper + NODESIZE;
+ memmove(base - delta, base, len);
+ mp->mp_upper -= delta;
+
+ node = NODEPTR(mp, indx);
+ }
+
+ /* But even if no shift was needed, update ksize */
+ if (node->mn_ksize != key->mv_size)
+ node->mn_ksize = key->mv_size;
+
+ if (key->mv_size)
+ memcpy(NODEKEY(node), key->mv_data, key->mv_size);
+
+ return MDB_SUCCESS;
+}
+
+static void
+mdb_cursor_copy(const MDB_cursor *csrc, MDB_cursor *cdst);
+
+/** Perform \b act while tracking temporary cursor \b mn */
+#define WITH_CURSOR_TRACKING(mn, act) do { \
+ MDB_cursor dummy, *tracked, **tp = &(mn).mc_txn->mt_cursors[mn.mc_dbi]; \
+ if ((mn).mc_flags & C_SUB) { \
+ dummy.mc_flags = C_INITIALIZED; \
+ dummy.mc_xcursor = (MDB_xcursor *)&(mn); \
+ tracked = &dummy; \
+ } else { \
+ tracked = &(mn); \
+ } \
+ tracked->mc_next = *tp; \
+ *tp = tracked; \
+ { act; } \
+ *tp = tracked->mc_next; \
+} while (0)
+
+/** Move a node from csrc to cdst.
+ */
+static int
+mdb_node_move(MDB_cursor *csrc, MDB_cursor *cdst, int fromleft)
+{
+ MDB_node *srcnode;
+ MDB_val key, data;
+ pgno_t srcpg;
+ MDB_cursor mn;
+ int rc;
+ unsigned short flags;
+
+ DKBUF;
+
+ /* Mark src and dst as dirty. */
+ if ((rc = mdb_page_touch(csrc)) ||
+ (rc = mdb_page_touch(cdst)))
+ return rc;
+
+ if (IS_LEAF2(csrc->mc_pg[csrc->mc_top])) {
+ key.mv_size = csrc->mc_db->md_pad;
+ key.mv_data = LEAF2KEY(csrc->mc_pg[csrc->mc_top], csrc->mc_ki[csrc->mc_top], key.mv_size);
+ data.mv_size = 0;
+ data.mv_data = NULL;
+ srcpg = 0;
+ flags = 0;
+ } else {
+ srcnode = NODEPTR(csrc->mc_pg[csrc->mc_top], csrc->mc_ki[csrc->mc_top]);
+ mdb_cassert(csrc, !((size_t)srcnode & 1));
+ srcpg = NODEPGNO(srcnode);
+ flags = srcnode->mn_flags;
+ if (csrc->mc_ki[csrc->mc_top] == 0 && IS_BRANCH(csrc->mc_pg[csrc->mc_top])) {
+ unsigned int snum = csrc->mc_snum;
+ MDB_node *s2;
+ /* must find the lowest key below src */
+ rc = mdb_page_search_lowest(csrc);
+ if (rc)
+ return rc;
+ if (IS_LEAF2(csrc->mc_pg[csrc->mc_top])) {
+ key.mv_size = csrc->mc_db->md_pad;
+ key.mv_data = LEAF2KEY(csrc->mc_pg[csrc->mc_top], 0, key.mv_size);
+ } else {
+ s2 = NODEPTR(csrc->mc_pg[csrc->mc_top], 0);
+ key.mv_size = NODEKSZ(s2);
+ key.mv_data = NODEKEY(s2);
+ }
+ csrc->mc_snum = snum--;
+ csrc->mc_top = snum;
+ } else {
+ key.mv_size = NODEKSZ(srcnode);
+ key.mv_data = NODEKEY(srcnode);
+ }
+ data.mv_size = NODEDSZ(srcnode);
+ data.mv_data = NODEDATA(srcnode);
+ }
+ mn.mc_xcursor = NULL;
+ if (IS_BRANCH(cdst->mc_pg[cdst->mc_top]) && cdst->mc_ki[cdst->mc_top] == 0) {
+ unsigned int snum = cdst->mc_snum;
+ MDB_node *s2;
+ MDB_val bkey;
+ /* must find the lowest key below dst */
+ mdb_cursor_copy(cdst, &mn);
+ rc = mdb_page_search_lowest(&mn);
+ if (rc)
+ return rc;
+ if (IS_LEAF2(mn.mc_pg[mn.mc_top])) {
+ bkey.mv_size = mn.mc_db->md_pad;
+ bkey.mv_data = LEAF2KEY(mn.mc_pg[mn.mc_top], 0, bkey.mv_size);
+ } else {
+ s2 = NODEPTR(mn.mc_pg[mn.mc_top], 0);
+ bkey.mv_size = NODEKSZ(s2);
+ bkey.mv_data = NODEKEY(s2);
+ }
+ mn.mc_snum = snum--;
+ mn.mc_top = snum;
+ mn.mc_ki[snum] = 0;
+ rc = mdb_update_key(&mn, &bkey);
+ if (rc)
+ return rc;
+ }
+
+ DPRINTF(("moving %s node %u [%s] on page %"Z"u to node %u on page %"Z"u",
+ IS_LEAF(csrc->mc_pg[csrc->mc_top]) ? "leaf" : "branch",
+ csrc->mc_ki[csrc->mc_top],
+ DKEY(&key),
+ csrc->mc_pg[csrc->mc_top]->mp_pgno,
+ cdst->mc_ki[cdst->mc_top], cdst->mc_pg[cdst->mc_top]->mp_pgno));
+
+ /* Add the node to the destination page.
+ */
+ rc = mdb_node_add(cdst, cdst->mc_ki[cdst->mc_top], &key, &data, srcpg, flags);
+ if (rc != MDB_SUCCESS)
+ return rc;
+
+ /* Delete the node from the source page.
+ */
+ mdb_node_del(csrc, key.mv_size);
+
+ {
+ /* Adjust other cursors pointing to mp */
+ MDB_cursor *m2, *m3;
+ MDB_dbi dbi = csrc->mc_dbi;
+ MDB_page *mpd, *mps;
+
+ mps = csrc->mc_pg[csrc->mc_top];
+ /* If we're adding on the left, bump others up */
+ if (fromleft) {
+ mpd = cdst->mc_pg[csrc->mc_top];
+ for (m2 = csrc->mc_txn->mt_cursors[dbi]; m2; m2=m2->mc_next) {
+ if (csrc->mc_flags & C_SUB)
+ m3 = &m2->mc_xcursor->mx_cursor;
+ else
+ m3 = m2;
+ if (!(m3->mc_flags & C_INITIALIZED) || m3->mc_top < csrc->mc_top)
+ continue;
+ if (m3 != cdst &&
+ m3->mc_pg[csrc->mc_top] == mpd &&
+ m3->mc_ki[csrc->mc_top] >= cdst->mc_ki[csrc->mc_top]) {
+ m3->mc_ki[csrc->mc_top]++;
+ }
+ if (m3 !=csrc &&
+ m3->mc_pg[csrc->mc_top] == mps &&
+ m3->mc_ki[csrc->mc_top] == csrc->mc_ki[csrc->mc_top]) {
+ m3->mc_pg[csrc->mc_top] = cdst->mc_pg[cdst->mc_top];
+ m3->mc_ki[csrc->mc_top] = cdst->mc_ki[cdst->mc_top];
+ m3->mc_ki[csrc->mc_top-1]++;
+ }
+ if (IS_LEAF(mps))
+ XCURSOR_REFRESH(m3, csrc->mc_top, m3->mc_pg[csrc->mc_top]);
+ }
+ } else
+ /* Adding on the right, bump others down */
+ {
+ for (m2 = csrc->mc_txn->mt_cursors[dbi]; m2; m2=m2->mc_next) {
+ if (csrc->mc_flags & C_SUB)
+ m3 = &m2->mc_xcursor->mx_cursor;
+ else
+ m3 = m2;
+ if (m3 == csrc) continue;
+ if (!(m3->mc_flags & C_INITIALIZED) || m3->mc_top < csrc->mc_top)
+ continue;
+ if (m3->mc_pg[csrc->mc_top] == mps) {
+ if (!m3->mc_ki[csrc->mc_top]) {
+ m3->mc_pg[csrc->mc_top] = cdst->mc_pg[cdst->mc_top];
+ m3->mc_ki[csrc->mc_top] = cdst->mc_ki[cdst->mc_top];
+ m3->mc_ki[csrc->mc_top-1]--;
+ } else {
+ m3->mc_ki[csrc->mc_top]--;
+ }
+ if (IS_LEAF(mps))
+ XCURSOR_REFRESH(m3, csrc->mc_top, m3->mc_pg[csrc->mc_top]);
+ }
+ }
+ }
+ }
+
+ /* Update the parent separators.
+ */
+ if (csrc->mc_ki[csrc->mc_top] == 0) {
+ if (csrc->mc_ki[csrc->mc_top-1] != 0) {
+ if (IS_LEAF2(csrc->mc_pg[csrc->mc_top])) {
+ key.mv_data = LEAF2KEY(csrc->mc_pg[csrc->mc_top], 0, key.mv_size);
+ } else {
+ srcnode = NODEPTR(csrc->mc_pg[csrc->mc_top], 0);
+ key.mv_size = NODEKSZ(srcnode);
+ key.mv_data = NODEKEY(srcnode);
+ }
+ DPRINTF(("update separator for source page %"Z"u to [%s]",
+ csrc->mc_pg[csrc->mc_top]->mp_pgno, DKEY(&key)));
+ mdb_cursor_copy(csrc, &mn);
+ mn.mc_snum--;
+ mn.mc_top--;
+ /* We want mdb_rebalance to find mn when doing fixups */
+ WITH_CURSOR_TRACKING(mn,
+ rc = mdb_update_key(&mn, &key));
+ if (rc)
+ return rc;
+ }
+ if (IS_BRANCH(csrc->mc_pg[csrc->mc_top])) {
+ MDB_val nullkey;
+ indx_t ix = csrc->mc_ki[csrc->mc_top];
+ nullkey.mv_size = 0;
+ csrc->mc_ki[csrc->mc_top] = 0;
+ rc = mdb_update_key(csrc, &nullkey);
+ csrc->mc_ki[csrc->mc_top] = ix;
+ mdb_cassert(csrc, rc == MDB_SUCCESS);
+ }
+ }
+
+ if (cdst->mc_ki[cdst->mc_top] == 0) {
+ if (cdst->mc_ki[cdst->mc_top-1] != 0) {
+ if (IS_LEAF2(csrc->mc_pg[csrc->mc_top])) {
+ key.mv_data = LEAF2KEY(cdst->mc_pg[cdst->mc_top], 0, key.mv_size);
+ } else {
+ srcnode = NODEPTR(cdst->mc_pg[cdst->mc_top], 0);
+ key.mv_size = NODEKSZ(srcnode);
+ key.mv_data = NODEKEY(srcnode);
+ }
+ DPRINTF(("update separator for destination page %"Z"u to [%s]",
+ cdst->mc_pg[cdst->mc_top]->mp_pgno, DKEY(&key)));
+ mdb_cursor_copy(cdst, &mn);
+ mn.mc_snum--;
+ mn.mc_top--;
+ /* We want mdb_rebalance to find mn when doing fixups */
+ WITH_CURSOR_TRACKING(mn,
+ rc = mdb_update_key(&mn, &key));
+ if (rc)
+ return rc;
+ }
+ if (IS_BRANCH(cdst->mc_pg[cdst->mc_top])) {
+ MDB_val nullkey;
+ indx_t ix = cdst->mc_ki[cdst->mc_top];
+ nullkey.mv_size = 0;
+ cdst->mc_ki[cdst->mc_top] = 0;
+ rc = mdb_update_key(cdst, &nullkey);
+ cdst->mc_ki[cdst->mc_top] = ix;
+ mdb_cassert(cdst, rc == MDB_SUCCESS);
+ }
+ }
+
+ return MDB_SUCCESS;
+}
+
+/** Merge one page into another.
+ * The nodes from the page pointed to by \b csrc will
+ * be copied to the page pointed to by \b cdst and then
+ * the \b csrc page will be freed.
+ * @param[in] csrc Cursor pointing to the source page.
+ * @param[in] cdst Cursor pointing to the destination page.
+ * @return 0 on success, non-zero on failure.
+ */
+static int
+mdb_page_merge(MDB_cursor *csrc, MDB_cursor *cdst)
+{
+ MDB_page *psrc, *pdst;
+ MDB_node *srcnode;
+ MDB_val key, data;
+ unsigned nkeys;
+ int rc;
+ indx_t i, j;
+
+ psrc = csrc->mc_pg[csrc->mc_top];
+ pdst = cdst->mc_pg[cdst->mc_top];
+
+ DPRINTF(("merging page %"Z"u into %"Z"u", psrc->mp_pgno, pdst->mp_pgno));
+
+ mdb_cassert(csrc, csrc->mc_snum > 1); /* can't merge root page */
+ mdb_cassert(csrc, cdst->mc_snum > 1);
+
+ /* Mark dst as dirty. */
+ if ((rc = mdb_page_touch(cdst)))
+ return rc;
+
+ /* get dst page again now that we've touched it. */
+ pdst = cdst->mc_pg[cdst->mc_top];
+
+ /* Move all nodes from src to dst.
+ */
+ j = nkeys = NUMKEYS(pdst);
+ if (IS_LEAF2(psrc)) {
+ key.mv_size = csrc->mc_db->md_pad;
+ key.mv_data = METADATA(psrc);
+ for (i = 0; i < NUMKEYS(psrc); i++, j++) {
+ rc = mdb_node_add(cdst, j, &key, NULL, 0, 0);
+ if (rc != MDB_SUCCESS)
+ return rc;
+ key.mv_data = (char *)key.mv_data + key.mv_size;
+ }
+ } else {
+ for (i = 0; i < NUMKEYS(psrc); i++, j++) {
+ srcnode = NODEPTR(psrc, i);
+ if (i == 0 && IS_BRANCH(psrc)) {
+ MDB_cursor mn;
+ MDB_node *s2;
+ mdb_cursor_copy(csrc, &mn);
+ mn.mc_xcursor = NULL;
+ /* must find the lowest key below src */
+ rc = mdb_page_search_lowest(&mn);
+ if (rc)
+ return rc;
+ if (IS_LEAF2(mn.mc_pg[mn.mc_top])) {
+ key.mv_size = mn.mc_db->md_pad;
+ key.mv_data = LEAF2KEY(mn.mc_pg[mn.mc_top], 0, key.mv_size);
+ } else {
+ s2 = NODEPTR(mn.mc_pg[mn.mc_top], 0);
+ key.mv_size = NODEKSZ(s2);
+ key.mv_data = NODEKEY(s2);
+ }
+ } else {
+ key.mv_size = srcnode->mn_ksize;
+ key.mv_data = NODEKEY(srcnode);
+ }
+
+ data.mv_size = NODEDSZ(srcnode);
+ data.mv_data = NODEDATA(srcnode);
+ rc = mdb_node_add(cdst, j, &key, &data, NODEPGNO(srcnode), srcnode->mn_flags);
+ if (rc != MDB_SUCCESS)
+ return rc;
+ }
+ }
+
+ DPRINTF(("dst page %"Z"u now has %u keys (%.1f%% filled)",
+ pdst->mp_pgno, NUMKEYS(pdst),
+ (float)PAGEFILL(cdst->mc_txn->mt_env, pdst) / 10));
+
+ /* Unlink the src page from parent and add to free list.
+ */
+ csrc->mc_top--;
+ mdb_node_del(csrc, 0);
+ if (csrc->mc_ki[csrc->mc_top] == 0) {
+ key.mv_size = 0;
+ rc = mdb_update_key(csrc, &key);
+ if (rc) {
+ csrc->mc_top++;
+ return rc;
+ }
+ }
+ csrc->mc_top++;
+
+ psrc = csrc->mc_pg[csrc->mc_top];
+ /* If not operating on FreeDB, allow this page to be reused
+ * in this txn. Otherwise just add to free list.
+ */
+ rc = mdb_page_loose(csrc, psrc);
+ if (rc)
+ return rc;
+ if (IS_LEAF(psrc))
+ csrc->mc_db->md_leaf_pages--;
+ else
+ csrc->mc_db->md_branch_pages--;
+ {
+ /* Adjust other cursors pointing to mp */
+ MDB_cursor *m2, *m3;
+ MDB_dbi dbi = csrc->mc_dbi;
+ unsigned int top = csrc->mc_top;
+
+ for (m2 = csrc->mc_txn->mt_cursors[dbi]; m2; m2=m2->mc_next) {
+ if (csrc->mc_flags & C_SUB)
+ m3 = &m2->mc_xcursor->mx_cursor;
+ else
+ m3 = m2;
+ if (m3 == csrc) continue;
+ if (m3->mc_snum < csrc->mc_snum) continue;
+ if (m3->mc_pg[top] == psrc) {
+ m3->mc_pg[top] = pdst;
+ m3->mc_ki[top] += nkeys;
+ m3->mc_ki[top-1] = cdst->mc_ki[top-1];
+ } else if (m3->mc_pg[top-1] == csrc->mc_pg[top-1] &&
+ m3->mc_ki[top-1] > csrc->mc_ki[top-1]) {
+ m3->mc_ki[top-1]--;
+ }
+ if (IS_LEAF(psrc))
+ XCURSOR_REFRESH(m3, top, m3->mc_pg[top]);
+ }
+ }
+ {
+ unsigned int snum = cdst->mc_snum;
+ uint16_t depth = cdst->mc_db->md_depth;
+ mdb_cursor_pop(cdst);
+ rc = mdb_rebalance(cdst);
+ /* Did the tree height change? */
+ if (depth != cdst->mc_db->md_depth)
+ snum += cdst->mc_db->md_depth - depth;
+ cdst->mc_snum = snum;
+ cdst->mc_top = snum-1;
+ }
+ return rc;
+}
+
+/** Copy the contents of a cursor.
+ * @param[in] csrc The cursor to copy from.
+ * @param[out] cdst The cursor to copy to.
+ */
+static void
+mdb_cursor_copy(const MDB_cursor *csrc, MDB_cursor *cdst)
+{
+ unsigned int i;
+
+ cdst->mc_txn = csrc->mc_txn;
+ cdst->mc_dbi = csrc->mc_dbi;
+ cdst->mc_db = csrc->mc_db;
+ cdst->mc_dbx = csrc->mc_dbx;
+ cdst->mc_snum = csrc->mc_snum;
+ cdst->mc_top = csrc->mc_top;
+ cdst->mc_flags = csrc->mc_flags;
+
+ for (i=0; i<csrc->mc_snum; i++) {
+ cdst->mc_pg[i] = csrc->mc_pg[i];
+ cdst->mc_ki[i] = csrc->mc_ki[i];
+ }
+}
+
+/** Rebalance the tree after a delete operation.
+ * @param[in] mc Cursor pointing to the page where rebalancing
+ * should begin.
+ * @return 0 on success, non-zero on failure.
+ */
+static int
+mdb_rebalance(MDB_cursor *mc)
+{
+ MDB_node *node;
+ int rc, fromleft;
+ unsigned int ptop, minkeys, thresh;
+ MDB_cursor mn;
+ indx_t oldki;
+
+ if (IS_BRANCH(mc->mc_pg[mc->mc_top])) {
+ minkeys = 2;
+ thresh = 1;
+ } else {
+ minkeys = 1;
+ thresh = FILL_THRESHOLD;
+ }
+ DPRINTF(("rebalancing %s page %"Z"u (has %u keys, %.1f%% full)",
+ IS_LEAF(mc->mc_pg[mc->mc_top]) ? "leaf" : "branch",
+ mdb_dbg_pgno(mc->mc_pg[mc->mc_top]), NUMKEYS(mc->mc_pg[mc->mc_top]),
+ (float)PAGEFILL(mc->mc_txn->mt_env, mc->mc_pg[mc->mc_top]) / 10));
+
+ if (PAGEFILL(mc->mc_txn->mt_env, mc->mc_pg[mc->mc_top]) >= thresh &&
+ NUMKEYS(mc->mc_pg[mc->mc_top]) >= minkeys) {
+ DPRINTF(("no need to rebalance page %"Z"u, above fill threshold",
+ mdb_dbg_pgno(mc->mc_pg[mc->mc_top])));
+ return MDB_SUCCESS;
+ }
+
+ if (mc->mc_snum < 2) {
+ MDB_page *mp = mc->mc_pg[0];
+ if (IS_SUBP(mp)) {
+ DPUTS("Can't rebalance a subpage, ignoring");
+ return MDB_SUCCESS;
+ }
+ if (NUMKEYS(mp) == 0) {
+ DPUTS("tree is completely empty");
+ mc->mc_db->md_root = P_INVALID;
+ mc->mc_db->md_depth = 0;
+ mc->mc_db->md_leaf_pages = 0;
+ rc = mdb_midl_append(&mc->mc_txn->mt_free_pgs, mp->mp_pgno);
+ if (rc)
+ return rc;
+ /* Adjust cursors pointing to mp */
+ mc->mc_snum = 0;
+ mc->mc_top = 0;
+ mc->mc_flags &= ~C_INITIALIZED;
+ {
+ MDB_cursor *m2, *m3;
+ MDB_dbi dbi = mc->mc_dbi;
+
+ for (m2 = mc->mc_txn->mt_cursors[dbi]; m2; m2=m2->mc_next) {
+ if (mc->mc_flags & C_SUB)
+ m3 = &m2->mc_xcursor->mx_cursor;
+ else
+ m3 = m2;
+ if (!(m3->mc_flags & C_INITIALIZED) || (m3->mc_snum < mc->mc_snum))
+ continue;
+ if (m3->mc_pg[0] == mp) {
+ m3->mc_snum = 0;
+ m3->mc_top = 0;
+ m3->mc_flags &= ~C_INITIALIZED;
+ }
+ }
+ }
+ } else if (IS_BRANCH(mp) && NUMKEYS(mp) == 1) {
+ int i;
+ DPUTS("collapsing root page!");
+ rc = mdb_midl_append(&mc->mc_txn->mt_free_pgs, mp->mp_pgno);
+ if (rc)
+ return rc;
+ mc->mc_db->md_root = NODEPGNO(NODEPTR(mp, 0));
+ rc = mdb_page_get(mc, mc->mc_db->md_root, &mc->mc_pg[0], NULL);
+ if (rc)
+ return rc;
+ mc->mc_db->md_depth--;
+ mc->mc_db->md_branch_pages--;
+ mc->mc_ki[0] = mc->mc_ki[1];
+ for (i = 1; i<mc->mc_db->md_depth; i++) {
+ mc->mc_pg[i] = mc->mc_pg[i+1];
+ mc->mc_ki[i] = mc->mc_ki[i+1];
+ }
+ {
+ /* Adjust other cursors pointing to mp */
+ MDB_cursor *m2, *m3;
+ MDB_dbi dbi = mc->mc_dbi;
+
+ for (m2 = mc->mc_txn->mt_cursors[dbi]; m2; m2=m2->mc_next) {
+ if (mc->mc_flags & C_SUB)
+ m3 = &m2->mc_xcursor->mx_cursor;
+ else
+ m3 = m2;
+ if (m3 == mc) continue;
+ if (!(m3->mc_flags & C_INITIALIZED))
+ continue;
+ if (m3->mc_pg[0] == mp) {
+ for (i=0; i<mc->mc_db->md_depth; i++) {
+ m3->mc_pg[i] = m3->mc_pg[i+1];
+ m3->mc_ki[i] = m3->mc_ki[i+1];
+ }
+ m3->mc_snum--;
+ m3->mc_top--;
+ }
+ }
+ }
+ } else
+ DPUTS("root page doesn't need rebalancing");
+ return MDB_SUCCESS;
+ }
+
+ /* The parent (branch page) must have at least 2 pointers,
+ * otherwise the tree is invalid.
+ */
+ ptop = mc->mc_top-1;
+ mdb_cassert(mc, NUMKEYS(mc->mc_pg[ptop]) > 1);
+
+ /* Leaf page fill factor is below the threshold.
+ * Try to move keys from left or right neighbor, or
+ * merge with a neighbor page.
+ */
+
+ /* Find neighbors.
+ */
+ mdb_cursor_copy(mc, &mn);
+ mn.mc_xcursor = NULL;
+
+ oldki = mc->mc_ki[mc->mc_top];
+ if (mc->mc_ki[ptop] == 0) {
+ /* We're the leftmost leaf in our parent.
+ */
+ DPUTS("reading right neighbor");
+ mn.mc_ki[ptop]++;
+ node = NODEPTR(mc->mc_pg[ptop], mn.mc_ki[ptop]);
+ rc = mdb_page_get(mc, NODEPGNO(node), &mn.mc_pg[mn.mc_top], NULL);
+ if (rc)
+ return rc;
+ mn.mc_ki[mn.mc_top] = 0;
+ mc->mc_ki[mc->mc_top] = NUMKEYS(mc->mc_pg[mc->mc_top]);
+ fromleft = 0;
+ } else {
+ /* There is at least one neighbor to the left.
+ */
+ DPUTS("reading left neighbor");
+ mn.mc_ki[ptop]--;
+ node = NODEPTR(mc->mc_pg[ptop], mn.mc_ki[ptop]);
+ rc = mdb_page_get(mc, NODEPGNO(node), &mn.mc_pg[mn.mc_top], NULL);
+ if (rc)
+ return rc;
+ mn.mc_ki[mn.mc_top] = NUMKEYS(mn.mc_pg[mn.mc_top]) - 1;
+ mc->mc_ki[mc->mc_top] = 0;
+ fromleft = 1;
+ }
+
+ DPRINTF(("found neighbor page %"Z"u (%u keys, %.1f%% full)",
+ mn.mc_pg[mn.mc_top]->mp_pgno, NUMKEYS(mn.mc_pg[mn.mc_top]),
+ (float)PAGEFILL(mc->mc_txn->mt_env, mn.mc_pg[mn.mc_top]) / 10));
+
+ /* If the neighbor page is above threshold and has enough keys,
+ * move one key from it. Otherwise we should try to merge them.
+ * (A branch page must never have less than 2 keys.)
+ */
+ if (PAGEFILL(mc->mc_txn->mt_env, mn.mc_pg[mn.mc_top]) >= thresh && NUMKEYS(mn.mc_pg[mn.mc_top]) > minkeys) {
+ rc = mdb_node_move(&mn, mc, fromleft);
+ if (fromleft) {
+ /* if we inserted on left, bump position up */
+ oldki++;
+ }
+ } else {
+ if (!fromleft) {
+ rc = mdb_page_merge(&mn, mc);
+ } else {
+ oldki += NUMKEYS(mn.mc_pg[mn.mc_top]);
+ mn.mc_ki[mn.mc_top] += mc->mc_ki[mn.mc_top] + 1;
+ /* We want mdb_rebalance to find mn when doing fixups */
+ WITH_CURSOR_TRACKING(mn,
+ rc = mdb_page_merge(mc, &mn));
+ mdb_cursor_copy(&mn, mc);
+ }
+ mc->mc_flags &= ~C_EOF;
+ }
+ mc->mc_ki[mc->mc_top] = oldki;
+ return rc;
+}
+
+/** Complete a delete operation started by #mdb_cursor_del(). */
+static int
+mdb_cursor_del0(MDB_cursor *mc)
+{
+ int rc;
+ MDB_page *mp;
+ indx_t ki;
+ unsigned int nkeys;
+ MDB_cursor *m2, *m3;
+ MDB_dbi dbi = mc->mc_dbi;
+
+ ki = mc->mc_ki[mc->mc_top];
+ mp = mc->mc_pg[mc->mc_top];
+ mdb_node_del(mc, mc->mc_db->md_pad);
+ mc->mc_db->md_entries--;
+ {
+ /* Adjust other cursors pointing to mp */
+ for (m2 = mc->mc_txn->mt_cursors[dbi]; m2; m2=m2->mc_next) {
+ m3 = (mc->mc_flags & C_SUB) ? &m2->mc_xcursor->mx_cursor : m2;
+ if (! (m2->mc_flags & m3->mc_flags & C_INITIALIZED))
+ continue;
+ if (m3 == mc || m3->mc_snum < mc->mc_snum)
+ continue;
+ if (m3->mc_pg[mc->mc_top] == mp) {
+ if (m3->mc_ki[mc->mc_top] == ki) {
+ m3->mc_flags |= C_DEL;
+ if (mc->mc_db->md_flags & MDB_DUPSORT) {
+ /* Sub-cursor referred into dataset which is gone */
+ m3->mc_xcursor->mx_cursor.mc_flags &= ~(C_INITIALIZED|C_EOF);
+ }
+ continue;
+ } else if (m3->mc_ki[mc->mc_top] > ki) {
+ m3->mc_ki[mc->mc_top]--;
+ }
+ XCURSOR_REFRESH(m3, mc->mc_top, mp);
+ }
+ }
+ }
+ rc = mdb_rebalance(mc);
+
+ if (rc == MDB_SUCCESS) {
+ /* DB is totally empty now, just bail out.
+ * Other cursors adjustments were already done
+ * by mdb_rebalance and aren't needed here.
+ */
+ if (!mc->mc_snum)
+ return rc;
+
+ mp = mc->mc_pg[mc->mc_top];
+ nkeys = NUMKEYS(mp);
+
+ /* Adjust other cursors pointing to mp */
+ for (m2 = mc->mc_txn->mt_cursors[dbi]; !rc && m2; m2=m2->mc_next) {
+ m3 = (mc->mc_flags & C_SUB) ? &m2->mc_xcursor->mx_cursor : m2;
+ if (! (m2->mc_flags & m3->mc_flags & C_INITIALIZED))
+ continue;
+ if (m3->mc_snum < mc->mc_snum)
+ continue;
+ if (m3->mc_pg[mc->mc_top] == mp) {
+ /* if m3 points past last node in page, find next sibling */
+ if (m3->mc_ki[mc->mc_top] >= mc->mc_ki[mc->mc_top]) {
+ if (m3->mc_ki[mc->mc_top] >= nkeys) {
+ rc = mdb_cursor_sibling(m3, 1);
+ if (rc == MDB_NOTFOUND) {
+ m3->mc_flags |= C_EOF;
+ rc = MDB_SUCCESS;
+ continue;
+ }
+ }
+ if (mc->mc_db->md_flags & MDB_DUPSORT) {
+ MDB_node *node = NODEPTR(m3->mc_pg[m3->mc_top], m3->mc_ki[m3->mc_top]);
+ /* If this node has dupdata, it may need to be reinited
+ * because its data has moved.
+ * If the xcursor was not initd it must be reinited.
+ * Else if node points to a subDB, nothing is needed.
+ * Else (xcursor was initd, not a subDB) needs mc_pg[0] reset.
+ */
+ if (node->mn_flags & F_DUPDATA) {
+ if (m3->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED) {
+ if (!(node->mn_flags & F_SUBDATA))
+ m3->mc_xcursor->mx_cursor.mc_pg[0] = NODEDATA(node);
+ } else {
+ mdb_xcursor_init1(m3, node);
+ m3->mc_xcursor->mx_cursor.mc_flags |= C_DEL;
+ }
+ }
+ }
+ }
+ }
+ }
+ mc->mc_flags |= C_DEL;
+ }
+
+ if (rc)
+ mc->mc_txn->mt_flags |= MDB_TXN_ERROR;
+ return rc;
+}
+
+int
+mdb_del(MDB_txn *txn, MDB_dbi dbi,
+ MDB_val *key, MDB_val *data)
+{
+ if (!key || !TXN_DBI_EXIST(txn, dbi, DB_USRVALID))
+ return EINVAL;
+
+ if (txn->mt_flags & (MDB_TXN_RDONLY|MDB_TXN_BLOCKED))
+ return (txn->mt_flags & MDB_TXN_RDONLY) ? EACCES : MDB_BAD_TXN;
+
+ if (!F_ISSET(txn->mt_dbs[dbi].md_flags, MDB_DUPSORT)) {
+ /* must ignore any data */
+ data = NULL;
+ }
+
+ return mdb_del0(txn, dbi, key, data, 0);
+}
+
+static int
+mdb_del0(MDB_txn *txn, MDB_dbi dbi,
+ MDB_val *key, MDB_val *data, unsigned flags)
+{
+ MDB_cursor mc;
+ MDB_xcursor mx;
+ MDB_cursor_op op;
+ MDB_val rdata, *xdata;
+ int rc, exact = 0;
+ DKBUF;
+
+ DPRINTF(("====> delete db %u key [%s]", dbi, DKEY(key)));
+
+ mdb_cursor_init(&mc, txn, dbi, &mx);
+
+ if (data) {
+ op = MDB_GET_BOTH;
+ rdata = *data;
+ xdata = &rdata;
+ } else {
+ op = MDB_SET;
+ xdata = NULL;
+ flags |= MDB_NODUPDATA;
+ }
+ rc = mdb_cursor_set(&mc, key, xdata, op, &exact);
+ if (rc == 0) {
+ /* let mdb_page_split know about this cursor if needed:
+ * delete will trigger a rebalance; if it needs to move
+ * a node from one page to another, it will have to
+ * update the parent's separator key(s). If the new sepkey
+ * is larger than the current one, the parent page may
+ * run out of space, triggering a split. We need this
+ * cursor to be consistent until the end of the rebalance.
+ */
+ mc.mc_flags |= C_UNTRACK;
+ mc.mc_next = txn->mt_cursors[dbi];
+ txn->mt_cursors[dbi] = &mc;
+ rc = mdb_cursor_del(&mc, flags);
+ txn->mt_cursors[dbi] = mc.mc_next;
+ }
+ return rc;
+}
+
+/** Split a page and insert a new node.
+ * Set #MDB_TXN_ERROR on failure.
+ * @param[in,out] mc Cursor pointing to the page and desired insertion index.
+ * The cursor will be updated to point to the actual page and index where
+ * the node got inserted after the split.
+ * @param[in] newkey The key for the newly inserted node.
+ * @param[in] newdata The data for the newly inserted node.
+ * @param[in] newpgno The page number, if the new node is a branch node.
+ * @param[in] nflags The #NODE_ADD_FLAGS for the new node.
+ * @return 0 on success, non-zero on failure.
+ */
+static int
+mdb_page_split(MDB_cursor *mc, MDB_val *newkey, MDB_val *newdata, pgno_t newpgno,
+ unsigned int nflags)
+{
+ unsigned int flags;
+ int rc = MDB_SUCCESS, new_root = 0, did_split = 0;
+ indx_t newindx;
+ pgno_t pgno = 0;
+ int i, j, split_indx, nkeys, pmax;
+ MDB_env *env = mc->mc_txn->mt_env;
+ MDB_node *node;
+ MDB_val sepkey, rkey, xdata, *rdata = &xdata;
+ MDB_page *copy = NULL;
+ MDB_page *mp, *rp, *pp;
+ int ptop;
+ MDB_cursor mn;
+ DKBUF;
+
+ mp = mc->mc_pg[mc->mc_top];
+ newindx = mc->mc_ki[mc->mc_top];
+ nkeys = NUMKEYS(mp);
+
+ DPRINTF(("-----> splitting %s page %"Z"u and adding [%s] at index %i/%i",
+ IS_LEAF(mp) ? "leaf" : "branch", mp->mp_pgno,
+ DKEY(newkey), mc->mc_ki[mc->mc_top], nkeys));
+
+ /* Create a right sibling. */
+ if ((rc = mdb_page_new(mc, mp->mp_flags, 1, &rp)))
+ return rc;
+ rp->mp_pad = mp->mp_pad;
+ DPRINTF(("new right sibling: page %"Z"u", rp->mp_pgno));
+
+ /* Usually when splitting the root page, the cursor
+ * height is 1. But when called from mdb_update_key,
+ * the cursor height may be greater because it walks
+ * up the stack while finding the branch slot to update.
+ */
+ if (mc->mc_top < 1) {
+ if ((rc = mdb_page_new(mc, P_BRANCH, 1, &pp)))
+ goto done;
+ /* shift current top to make room for new parent */
+ for (i=mc->mc_snum; i>0; i--) {
+ mc->mc_pg[i] = mc->mc_pg[i-1];
+ mc->mc_ki[i] = mc->mc_ki[i-1];
+ }
+ mc->mc_pg[0] = pp;
+ mc->mc_ki[0] = 0;
+ mc->mc_db->md_root = pp->mp_pgno;
+ DPRINTF(("root split! new root = %"Z"u", pp->mp_pgno));
+ new_root = mc->mc_db->md_depth++;
+
+ /* Add left (implicit) pointer. */
+ if ((rc = mdb_node_add(mc, 0, NULL, NULL, mp->mp_pgno, 0)) != MDB_SUCCESS) {
+ /* undo the pre-push */
+ mc->mc_pg[0] = mc->mc_pg[1];
+ mc->mc_ki[0] = mc->mc_ki[1];
+ mc->mc_db->md_root = mp->mp_pgno;
+ mc->mc_db->md_depth--;
+ goto done;
+ }
+ mc->mc_snum++;
+ mc->mc_top++;
+ ptop = 0;
+ } else {
+ ptop = mc->mc_top-1;
+ DPRINTF(("parent branch page is %"Z"u", mc->mc_pg[ptop]->mp_pgno));
+ }
+
+ mdb_cursor_copy(mc, &mn);
+ mn.mc_xcursor = NULL;
+ mn.mc_pg[mn.mc_top] = rp;
+ mn.mc_ki[ptop] = mc->mc_ki[ptop]+1;
+
+ if (nflags & MDB_APPEND) {
+ mn.mc_ki[mn.mc_top] = 0;
+ sepkey = *newkey;
+ split_indx = newindx;
+ nkeys = 0;
+ } else {
+
+ split_indx = (nkeys+1) / 2;
+
+ if (IS_LEAF2(rp)) {
+ char *split, *ins;
+ int x;
+ unsigned int lsize, rsize, ksize;
+ /* Move half of the keys to the right sibling */
+ x = mc->mc_ki[mc->mc_top] - split_indx;
+ ksize = mc->mc_db->md_pad;
+ split = LEAF2KEY(mp, split_indx, ksize);
+ rsize = (nkeys - split_indx) * ksize;
+ lsize = (nkeys - split_indx) * sizeof(indx_t);
+ mp->mp_lower -= lsize;
+ rp->mp_lower += lsize;
+ mp->mp_upper += rsize - lsize;
+ rp->mp_upper -= rsize - lsize;
+ sepkey.mv_size = ksize;
+ if (newindx == split_indx) {
+ sepkey.mv_data = newkey->mv_data;
+ } else {
+ sepkey.mv_data = split;
+ }
+ if (x<0) {
+ ins = LEAF2KEY(mp, mc->mc_ki[mc->mc_top], ksize);
+ memcpy(rp->mp_ptrs, split, rsize);
+ sepkey.mv_data = rp->mp_ptrs;
+ memmove(ins+ksize, ins, (split_indx - mc->mc_ki[mc->mc_top]) * ksize);
+ memcpy(ins, newkey->mv_data, ksize);
+ mp->mp_lower += sizeof(indx_t);
+ mp->mp_upper -= ksize - sizeof(indx_t);
+ } else {
+ if (x)
+ memcpy(rp->mp_ptrs, split, x * ksize);
+ ins = LEAF2KEY(rp, x, ksize);
+ memcpy(ins, newkey->mv_data, ksize);
+ memcpy(ins+ksize, split + x * ksize, rsize - x * ksize);
+ rp->mp_lower += sizeof(indx_t);
+ rp->mp_upper -= ksize - sizeof(indx_t);
+ mc->mc_ki[mc->mc_top] = x;
+ }
+ } else {
+ int psize, nsize, k;
+ /* Maximum free space in an empty page */
+ pmax = env->me_psize - PAGEHDRSZ;
+ if (IS_LEAF(mp))
+ nsize = mdb_leaf_size(env, newkey, newdata);
+ else
+ nsize = mdb_branch_size(env, newkey);
+ nsize = EVEN(nsize);
+
+ /* grab a page to hold a temporary copy */
+ copy = mdb_page_malloc(mc->mc_txn, 1);
+ if (copy == NULL) {
+ rc = ENOMEM;
+ goto done;
+ }
+ copy->mp_pgno = mp->mp_pgno;
+ copy->mp_flags = mp->mp_flags;
+ copy->mp_lower = (PAGEHDRSZ-PAGEBASE);
+ copy->mp_upper = env->me_psize - PAGEBASE;
+
+ /* prepare to insert */
+ for (i=0, j=0; i<nkeys; i++) {
+ if (i == newindx) {
+ copy->mp_ptrs[j++] = 0;
+ }
+ copy->mp_ptrs[j++] = mp->mp_ptrs[i];
+ }
+
+ /* When items are relatively large the split point needs
+ * to be checked, because being off-by-one will make the
+ * difference between success or failure in mdb_node_add.
+ *
+ * It's also relevant if a page happens to be laid out
+ * such that one half of its nodes are all "small" and
+ * the other half of its nodes are "large." If the new
+ * item is also "large" and falls on the half with
+ * "large" nodes, it also may not fit.
+ *
+ * As a final tweak, if the new item goes on the last
+ * spot on the page (and thus, onto the new page), bias
+ * the split so the new page is emptier than the old page.
+ * This yields better packing during sequential inserts.
+ */
+ if (nkeys < 20 || nsize > pmax/16 || newindx >= nkeys) {
+ /* Find split point */
+ psize = 0;
+ if (newindx <= split_indx || newindx >= nkeys) {
+ i = 0; j = 1;
+ k = newindx >= nkeys ? nkeys : split_indx+1+IS_LEAF(mp);
+ } else {
+ i = nkeys; j = -1;
+ k = split_indx-1;
+ }
+ for (; i!=k; i+=j) {
+ if (i == newindx) {
+ psize += nsize;
+ node = NULL;
+ } else {
+ node = (MDB_node *)((char *)mp + copy->mp_ptrs[i] + PAGEBASE);
+ psize += NODESIZE + NODEKSZ(node) + sizeof(indx_t);
+ if (IS_LEAF(mp)) {
+ if (F_ISSET(node->mn_flags, F_BIGDATA))
+ psize += sizeof(pgno_t);
+ else
+ psize += NODEDSZ(node);
+ }
+ psize = EVEN(psize);
+ }
+ if (psize > pmax || i == k-j) {
+ split_indx = i + (j<0);
+ break;
+ }
+ }
+ }
+ if (split_indx == newindx) {
+ sepkey.mv_size = newkey->mv_size;
+ sepkey.mv_data = newkey->mv_data;
+ } else {
+ node = (MDB_node *)((char *)mp + copy->mp_ptrs[split_indx] + PAGEBASE);
+ sepkey.mv_size = node->mn_ksize;
+ sepkey.mv_data = NODEKEY(node);
+ }
+ }
+ }
+
+ DPRINTF(("separator is %d [%s]", split_indx, DKEY(&sepkey)));
+
+ /* Copy separator key to the parent.
+ */
+ if (SIZELEFT(mn.mc_pg[ptop]) < mdb_branch_size(env, &sepkey)) {
+ int snum = mc->mc_snum;
+ mn.mc_snum--;
+ mn.mc_top--;
+ did_split = 1;
+ /* We want other splits to find mn when doing fixups */
+ WITH_CURSOR_TRACKING(mn,
+ rc = mdb_page_split(&mn, &sepkey, NULL, rp->mp_pgno, 0));
+ if (rc)
+ goto done;
+
+ /* root split? */
+ if (mc->mc_snum > snum) {
+ ptop++;
+ }
+ /* Right page might now have changed parent.
+ * Check if left page also changed parent.
+ */
+ if (mn.mc_pg[ptop] != mc->mc_pg[ptop] &&
+ mc->mc_ki[ptop] >= NUMKEYS(mc->mc_pg[ptop])) {
+ for (i=0; i<ptop; i++) {
+ mc->mc_pg[i] = mn.mc_pg[i];
+ mc->mc_ki[i] = mn.mc_ki[i];
+ }
+ mc->mc_pg[ptop] = mn.mc_pg[ptop];
+ if (mn.mc_ki[ptop]) {
+ mc->mc_ki[ptop] = mn.mc_ki[ptop] - 1;
+ } else {
+ /* find right page's left sibling */
+ mc->mc_ki[ptop] = mn.mc_ki[ptop];
+ mdb_cursor_sibling(mc, 0);
+ }
+ }
+ } else {
+ mn.mc_top--;
+ rc = mdb_node_add(&mn, mn.mc_ki[ptop], &sepkey, NULL, rp->mp_pgno, 0);
+ mn.mc_top++;
+ }
+ if (rc != MDB_SUCCESS) {
+ goto done;
+ }
+ if (nflags & MDB_APPEND) {
+ mc->mc_pg[mc->mc_top] = rp;
+ mc->mc_ki[mc->mc_top] = 0;
+ rc = mdb_node_add(mc, 0, newkey, newdata, newpgno, nflags);
+ if (rc)
+ goto done;
+ for (i=0; i<mc->mc_top; i++)
+ mc->mc_ki[i] = mn.mc_ki[i];
+ } else if (!IS_LEAF2(mp)) {
+ /* Move nodes */
+ mc->mc_pg[mc->mc_top] = rp;
+ i = split_indx;
+ j = 0;
+ do {
+ if (i == newindx) {
+ rkey.mv_data = newkey->mv_data;
+ rkey.mv_size = newkey->mv_size;
+ if (IS_LEAF(mp)) {
+ rdata = newdata;
+ } else
+ pgno = newpgno;
+ flags = nflags;
+ /* Update index for the new key. */
+ mc->mc_ki[mc->mc_top] = j;
+ } else {
+ node = (MDB_node *)((char *)mp + copy->mp_ptrs[i] + PAGEBASE);
+ rkey.mv_data = NODEKEY(node);
+ rkey.mv_size = node->mn_ksize;
+ if (IS_LEAF(mp)) {
+ xdata.mv_data = NODEDATA(node);
+ xdata.mv_size = NODEDSZ(node);
+ rdata = &xdata;
+ } else
+ pgno = NODEPGNO(node);
+ flags = node->mn_flags;
+ }
+
+ if (!IS_LEAF(mp) && j == 0) {
+ /* First branch index doesn't need key data. */
+ rkey.mv_size = 0;
+ }
+
+ rc = mdb_node_add(mc, j, &rkey, rdata, pgno, flags);
+ if (rc)
+ goto done;
+ if (i == nkeys) {
+ i = 0;
+ j = 0;
+ mc->mc_pg[mc->mc_top] = copy;
+ } else {
+ i++;
+ j++;
+ }
+ } while (i != split_indx);
+
+ nkeys = NUMKEYS(copy);
+ for (i=0; i<nkeys; i++)
+ mp->mp_ptrs[i] = copy->mp_ptrs[i];
+ mp->mp_lower = copy->mp_lower;
+ mp->mp_upper = copy->mp_upper;
+ memcpy(NODEPTR(mp, nkeys-1), NODEPTR(copy, nkeys-1),
+ env->me_psize - copy->mp_upper - PAGEBASE);
+
+ /* reset back to original page */
+ if (newindx < split_indx) {
+ mc->mc_pg[mc->mc_top] = mp;
+ } else {
+ mc->mc_pg[mc->mc_top] = rp;
+ mc->mc_ki[ptop]++;
+ /* Make sure mc_ki is still valid.
+ */
+ if (mn.mc_pg[ptop] != mc->mc_pg[ptop] &&
+ mc->mc_ki[ptop] >= NUMKEYS(mc->mc_pg[ptop])) {
+ for (i=0; i<=ptop; i++) {
+ mc->mc_pg[i] = mn.mc_pg[i];
+ mc->mc_ki[i] = mn.mc_ki[i];
+ }
+ }
+ }
+ if (nflags & MDB_RESERVE) {
+ node = NODEPTR(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top]);
+ if (!(node->mn_flags & F_BIGDATA))
+ newdata->mv_data = NODEDATA(node);
+ }
+ } else {
+ if (newindx >= split_indx) {
+ mc->mc_pg[mc->mc_top] = rp;
+ mc->mc_ki[ptop]++;
+ /* Make sure mc_ki is still valid.
+ */
+ if (mn.mc_pg[ptop] != mc->mc_pg[ptop] &&
+ mc->mc_ki[ptop] >= NUMKEYS(mc->mc_pg[ptop])) {
+ for (i=0; i<=ptop; i++) {
+ mc->mc_pg[i] = mn.mc_pg[i];
+ mc->mc_ki[i] = mn.mc_ki[i];
+ }
+ }
+ }
+ }
+
+ {
+ /* Adjust other cursors pointing to mp */
+ MDB_cursor *m2, *m3;
+ MDB_dbi dbi = mc->mc_dbi;
+ nkeys = NUMKEYS(mp);
+
+ for (m2 = mc->mc_txn->mt_cursors[dbi]; m2; m2=m2->mc_next) {
+ if (mc->mc_flags & C_SUB)
+ m3 = &m2->mc_xcursor->mx_cursor;
+ else
+ m3 = m2;
+ if (m3 == mc)
+ continue;
+ if (!(m2->mc_flags & m3->mc_flags & C_INITIALIZED))
+ continue;
+ if (new_root) {
+ int k;
+ /* sub cursors may be on different DB */
+ if (m3->mc_pg[0] != mp)
+ continue;
+ /* root split */
+ for (k=new_root; k>=0; k--) {
+ m3->mc_ki[k+1] = m3->mc_ki[k];
+ m3->mc_pg[k+1] = m3->mc_pg[k];
+ }
+ if (m3->mc_ki[0] >= nkeys) {
+ m3->mc_ki[0] = 1;
+ } else {
+ m3->mc_ki[0] = 0;
+ }
+ m3->mc_pg[0] = mc->mc_pg[0];
+ m3->mc_snum++;
+ m3->mc_top++;
+ }
+ if (m3->mc_top >= mc->mc_top && m3->mc_pg[mc->mc_top] == mp) {
+ if (m3->mc_ki[mc->mc_top] >= newindx && !(nflags & MDB_SPLIT_REPLACE))
+ m3->mc_ki[mc->mc_top]++;
+ if (m3->mc_ki[mc->mc_top] >= nkeys) {
+ m3->mc_pg[mc->mc_top] = rp;
+ m3->mc_ki[mc->mc_top] -= nkeys;
+ for (i=0; i<mc->mc_top; i++) {
+ m3->mc_ki[i] = mn.mc_ki[i];
+ m3->mc_pg[i] = mn.mc_pg[i];
+ }
+ }
+ } else if (!did_split && m3->mc_top >= ptop && m3->mc_pg[ptop] == mc->mc_pg[ptop] &&
+ m3->mc_ki[ptop] >= mc->mc_ki[ptop]) {
+ m3->mc_ki[ptop]++;
+ }
+ if (IS_LEAF(mp))
+ XCURSOR_REFRESH(m3, mc->mc_top, m3->mc_pg[mc->mc_top]);
+ }
+ }
+ DPRINTF(("mp left: %d, rp left: %d", SIZELEFT(mp), SIZELEFT(rp)));
+
+done:
+ if (copy) /* tmp page */
+ mdb_page_free(env, copy);
+ if (rc)
+ mc->mc_txn->mt_flags |= MDB_TXN_ERROR;
+ return rc;
+}
+
+int
+mdb_put(MDB_txn *txn, MDB_dbi dbi,
+ MDB_val *key, MDB_val *data, unsigned int flags)
+{
+ MDB_cursor mc;
+ MDB_xcursor mx;
+ int rc;
+
+ if (!key || !data || !TXN_DBI_EXIST(txn, dbi, DB_USRVALID))
+ return EINVAL;
+
+ if (flags & ~(MDB_NOOVERWRITE|MDB_NODUPDATA|MDB_RESERVE|MDB_APPEND|MDB_APPENDDUP))
+ return EINVAL;
+
+ if (txn->mt_flags & (MDB_TXN_RDONLY|MDB_TXN_BLOCKED))
+ return (txn->mt_flags & MDB_TXN_RDONLY) ? EACCES : MDB_BAD_TXN;
+
+ mdb_cursor_init(&mc, txn, dbi, &mx);
+ mc.mc_next = txn->mt_cursors[dbi];
+ txn->mt_cursors[dbi] = &mc;
+ rc = mdb_cursor_put(&mc, key, data, flags);
+ txn->mt_cursors[dbi] = mc.mc_next;
+ return rc;
+}
+
+#ifndef MDB_WBUF
+#define MDB_WBUF (1024*1024)
+#endif
+#define MDB_EOF 0x10 /**< #mdb_env_copyfd1() is done reading */
+
+ /** State needed for a double-buffering compacting copy. */
+typedef struct mdb_copy {
+ MDB_env *mc_env;
+ MDB_txn *mc_txn;
+ pthread_mutex_t mc_mutex;
+ pthread_cond_t mc_cond; /**< Condition variable for #mc_new */
+ char *mc_wbuf[2];
+ char *mc_over[2];
+ int mc_wlen[2];
+ int mc_olen[2];
+ pgno_t mc_next_pgno;
+ HANDLE mc_fd;
+ int mc_toggle; /**< Buffer number in provider */
+ int mc_new; /**< (0-2 buffers to write) | (#MDB_EOF at end) */
+ /** Error code. Never cleared if set. Both threads can set nonzero
+ * to fail the copy. Not mutex-protected, LMDB expects atomic int.
+ */
+ volatile int mc_error;
+} mdb_copy;
+
+ /** Dedicated writer thread for compacting copy. */
+static THREAD_RET ESECT CALL_CONV
+mdb_env_copythr(void *arg)
+{
+ mdb_copy *my = arg;
+ char *ptr;
+ int toggle = 0, wsize, rc;
+#ifdef _WIN32
+ DWORD len;
+#define DO_WRITE(rc, fd, ptr, w2, len) rc = WriteFile(fd, ptr, w2, &len, NULL)
+#else
+ int len;
+#define DO_WRITE(rc, fd, ptr, w2, len) len = write(fd, ptr, w2); rc = (len >= 0)
+#ifdef SIGPIPE
+ sigset_t set;
+ sigemptyset(&set);
+ sigaddset(&set, SIGPIPE);
+ if ((rc = pthread_sigmask(SIG_BLOCK, &set, NULL)) != 0)
+ my->mc_error = rc;
+#endif
+#endif
+
+ pthread_mutex_lock(&my->mc_mutex);
+ for(;;) {
+ while (!my->mc_new)
+ pthread_cond_wait(&my->mc_cond, &my->mc_mutex);
+ if (my->mc_new == 0 + MDB_EOF) /* 0 buffers, just EOF */
+ break;
+ wsize = my->mc_wlen[toggle];
+ ptr = my->mc_wbuf[toggle];
+again:
+ rc = MDB_SUCCESS;
+ while (wsize > 0 && !my->mc_error) {
+ DO_WRITE(rc, my->mc_fd, ptr, wsize, len);
+ if (!rc) {
+ rc = ErrCode();
+#if defined(SIGPIPE) && !defined(_WIN32)
+ if (rc == EPIPE) {
+ /* Collect the pending SIGPIPE, otherwise at least OS X
+ * gives it to the process on thread-exit (ITS#8504).
+ */
+ int tmp;
+ sigwait(&set, &tmp);
+ }
+#endif
+ break;
+ } else if (len > 0) {
+ rc = MDB_SUCCESS;
+ ptr += len;
+ wsize -= len;
+ continue;
+ } else {
+ rc = EIO;
+ break;
+ }
+ }
+ if (rc) {
+ my->mc_error = rc;
+ }
+ /* If there's an overflow page tail, write it too */
+ if (my->mc_olen[toggle]) {
+ wsize = my->mc_olen[toggle];
+ ptr = my->mc_over[toggle];
+ my->mc_olen[toggle] = 0;
+ goto again;
+ }
+ my->mc_wlen[toggle] = 0;
+ toggle ^= 1;
+ /* Return the empty buffer to provider */
+ my->mc_new--;
+ pthread_cond_signal(&my->mc_cond);
+ }
+ pthread_mutex_unlock(&my->mc_mutex);
+ return (THREAD_RET)0;
+#undef DO_WRITE
+}
+
+ /** Give buffer and/or #MDB_EOF to writer thread, await unused buffer.
+ *
+ * @param[in] my control structure.
+ * @param[in] adjust (1 to hand off 1 buffer) | (MDB_EOF when ending).
+ */
+static int ESECT
+mdb_env_cthr_toggle(mdb_copy *my, int adjust)
+{
+ pthread_mutex_lock(&my->mc_mutex);
+ my->mc_new += adjust;
+ pthread_cond_signal(&my->mc_cond);
+ while (my->mc_new & 2) /* both buffers in use */
+ pthread_cond_wait(&my->mc_cond, &my->mc_mutex);
+ pthread_mutex_unlock(&my->mc_mutex);
+
+ my->mc_toggle ^= (adjust & 1);
+ /* Both threads reset mc_wlen, to be safe from threading errors */
+ my->mc_wlen[my->mc_toggle] = 0;
+ return my->mc_error;
+}
+
+ /** Depth-first tree traversal for compacting copy.
+ * @param[in] my control structure.
+ * @param[in,out] pg database root.
+ * @param[in] flags includes #F_DUPDATA if it is a sorted-duplicate sub-DB.
+ */
+static int ESECT
+mdb_env_cwalk(mdb_copy *my, pgno_t *pg, int flags)
+{
+ MDB_cursor mc = {0};
+ MDB_node *ni;
+ MDB_page *mo, *mp, *leaf;
+ char *buf, *ptr;
+ int rc, toggle;
+ unsigned int i;
+
+ /* Empty DB, nothing to do */
+ if (*pg == P_INVALID)
+ return MDB_SUCCESS;
+
+ mc.mc_snum = 1;
+ mc.mc_txn = my->mc_txn;
+
+ rc = mdb_page_get(&mc, *pg, &mc.mc_pg[0], NULL);
+ if (rc)
+ return rc;
+ rc = mdb_page_search_root(&mc, NULL, MDB_PS_FIRST);
+ if (rc)
+ return rc;
+
+ /* Make cursor pages writable */
+ buf = ptr = malloc(my->mc_env->me_psize * mc.mc_snum);
+ if (buf == NULL)
+ return ENOMEM;
+
+ for (i=0; i<mc.mc_top; i++) {
+ mdb_page_copy((MDB_page *)ptr, mc.mc_pg[i], my->mc_env->me_psize);
+ mc.mc_pg[i] = (MDB_page *)ptr;
+ ptr += my->mc_env->me_psize;
+ }
+
+ /* This is writable space for a leaf page. Usually not needed. */
+ leaf = (MDB_page *)ptr;
+
+ toggle = my->mc_toggle;
+ while (mc.mc_snum > 0) {
+ unsigned n;
+ mp = mc.mc_pg[mc.mc_top];
+ n = NUMKEYS(mp);
+
+ if (IS_LEAF(mp)) {
+ if (!IS_LEAF2(mp) && !(flags & F_DUPDATA)) {
+ for (i=0; i<n; i++) {
+ ni = NODEPTR(mp, i);
+ if (ni->mn_flags & F_BIGDATA) {
+ MDB_page *omp;
+ pgno_t pg;
+
+ /* Need writable leaf */
+ if (mp != leaf) {
+ mc.mc_pg[mc.mc_top] = leaf;
+ mdb_page_copy(leaf, mp, my->mc_env->me_psize);
+ mp = leaf;
+ ni = NODEPTR(mp, i);
+ }
+
+ memcpy(&pg, NODEDATA(ni), sizeof(pg));
+ memcpy(NODEDATA(ni), &my->mc_next_pgno, sizeof(pgno_t));
+ rc = mdb_page_get(&mc, pg, &omp, NULL);
+ if (rc)
+ goto done;
+ if (my->mc_wlen[toggle] >= MDB_WBUF) {
+ rc = mdb_env_cthr_toggle(my, 1);
+ if (rc)
+ goto done;
+ toggle = my->mc_toggle;
+ }
+ mo = (MDB_page *)(my->mc_wbuf[toggle] + my->mc_wlen[toggle]);
+ memcpy(mo, omp, my->mc_env->me_psize);
+ mo->mp_pgno = my->mc_next_pgno;
+ my->mc_next_pgno += omp->mp_pages;
+ my->mc_wlen[toggle] += my->mc_env->me_psize;
+ if (omp->mp_pages > 1) {
+ my->mc_olen[toggle] = my->mc_env->me_psize * (omp->mp_pages - 1);
+ my->mc_over[toggle] = (char *)omp + my->mc_env->me_psize;
+ rc = mdb_env_cthr_toggle(my, 1);
+ if (rc)
+ goto done;
+ toggle = my->mc_toggle;
+ }
+ } else if (ni->mn_flags & F_SUBDATA) {
+ MDB_db db;
+
+ /* Need writable leaf */
+ if (mp != leaf) {
+ mc.mc_pg[mc.mc_top] = leaf;
+ mdb_page_copy(leaf, mp, my->mc_env->me_psize);
+ mp = leaf;
+ ni = NODEPTR(mp, i);
+ }
+
+ memcpy(&db, NODEDATA(ni), sizeof(db));
+ my->mc_toggle = toggle;
+ rc = mdb_env_cwalk(my, &db.md_root, ni->mn_flags & F_DUPDATA);
+ if (rc)
+ goto done;
+ toggle = my->mc_toggle;
+ memcpy(NODEDATA(ni), &db, sizeof(db));
+ }
+ }
+ }
+ } else {
+ mc.mc_ki[mc.mc_top]++;
+ if (mc.mc_ki[mc.mc_top] < n) {
+ pgno_t pg;
+again:
+ ni = NODEPTR(mp, mc.mc_ki[mc.mc_top]);
+ pg = NODEPGNO(ni);
+ rc = mdb_page_get(&mc, pg, &mp, NULL);
+ if (rc)
+ goto done;
+ mc.mc_top++;
+ mc.mc_snum++;
+ mc.mc_ki[mc.mc_top] = 0;
+ if (IS_BRANCH(mp)) {
+ /* Whenever we advance to a sibling branch page,
+ * we must proceed all the way down to its first leaf.
+ */
+ mdb_page_copy(mc.mc_pg[mc.mc_top], mp, my->mc_env->me_psize);
+ goto again;
+ } else
+ mc.mc_pg[mc.mc_top] = mp;
+ continue;
+ }
+ }
+ if (my->mc_wlen[toggle] >= MDB_WBUF) {
+ rc = mdb_env_cthr_toggle(my, 1);
+ if (rc)
+ goto done;
+ toggle = my->mc_toggle;
+ }
+ mo = (MDB_page *)(my->mc_wbuf[toggle] + my->mc_wlen[toggle]);
+ mdb_page_copy(mo, mp, my->mc_env->me_psize);
+ mo->mp_pgno = my->mc_next_pgno++;
+ my->mc_wlen[toggle] += my->mc_env->me_psize;
+ if (mc.mc_top) {
+ /* Update parent if there is one */
+ ni = NODEPTR(mc.mc_pg[mc.mc_top-1], mc.mc_ki[mc.mc_top-1]);
+ SETPGNO(ni, mo->mp_pgno);
+ mdb_cursor_pop(&mc);
+ } else {
+ /* Otherwise we're done */
+ *pg = mo->mp_pgno;
+ break;
+ }
+ }
+done:
+ free(buf);
+ return rc;
+}
+
+ /** Copy environment with compaction. */
+static int ESECT
+mdb_env_copyfd1(MDB_env *env, HANDLE fd)
+{
+ MDB_meta *mm;
+ MDB_page *mp;
+ mdb_copy my = {0};
+ MDB_txn *txn = NULL;
+ pthread_t thr;
+ pgno_t root, new_root;
+ int rc = MDB_SUCCESS;
+
+#ifdef _WIN32
+ if (!(my.mc_mutex = CreateMutex(NULL, FALSE, NULL)) ||
+ !(my.mc_cond = CreateEvent(NULL, FALSE, FALSE, NULL))) {
+ rc = ErrCode();
+ goto done;
+ }
+ my.mc_wbuf[0] = _aligned_malloc(MDB_WBUF*2, env->me_os_psize);
+ if (my.mc_wbuf[0] == NULL) {
+ /* _aligned_malloc() sets errno, but we use Windows error codes */
+ rc = ERROR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+#else
+ if ((rc = pthread_mutex_init(&my.mc_mutex, NULL)) != 0)
+ return rc;
+ if ((rc = pthread_cond_init(&my.mc_cond, NULL)) != 0)
+ goto done2;
+#ifdef HAVE_MEMALIGN
+ my.mc_wbuf[0] = memalign(env->me_os_psize, MDB_WBUF*2);
+ if (my.mc_wbuf[0] == NULL) {
+ rc = errno;
+ goto done;
+ }
+#else
+ {
+ void *p;
+ if ((rc = posix_memalign(&p, env->me_os_psize, MDB_WBUF*2)) != 0)
+ goto done;
+ my.mc_wbuf[0] = p;
+ }
+#endif
+#endif
+ memset(my.mc_wbuf[0], 0, MDB_WBUF*2);
+ my.mc_wbuf[1] = my.mc_wbuf[0] + MDB_WBUF;
+ my.mc_next_pgno = NUM_METAS;
+ my.mc_env = env;
+ my.mc_fd = fd;
+ rc = THREAD_CREATE(thr, mdb_env_copythr, &my);
+ if (rc)
+ goto done;
+
+ rc = mdb_txn_begin(env, NULL, MDB_RDONLY, &txn);
+ if (rc)
+ goto finish;
+
+ mp = (MDB_page *)my.mc_wbuf[0];
+ memset(mp, 0, NUM_METAS * env->me_psize);
+ mp->mp_pgno = 0;
+ mp->mp_flags = P_META;
+ mm = (MDB_meta *)METADATA(mp);
+ mdb_env_init_meta0(env, mm);
+ mm->mm_address = env->me_metas[0]->mm_address;
+
+ mp = (MDB_page *)(my.mc_wbuf[0] + env->me_psize);
+ mp->mp_pgno = 1;
+ mp->mp_flags = P_META;
+ *(MDB_meta *)METADATA(mp) = *mm;
+ mm = (MDB_meta *)METADATA(mp);
+
+ /* Set metapage 1 with current main DB */
+ root = new_root = txn->mt_dbs[MAIN_DBI].md_root;
+ if (root != P_INVALID) {
+ /* Count free pages + freeDB pages. Subtract from last_pg
+ * to find the new last_pg, which also becomes the new root.
+ */
+ MDB_ID freecount = 0;
+ MDB_cursor mc;
+ MDB_val key, data;
+ mdb_cursor_init(&mc, txn, FREE_DBI, NULL);
+ while ((rc = mdb_cursor_get(&mc, &key, &data, MDB_NEXT)) == 0)
+ freecount += *(MDB_ID *)data.mv_data;
+ if (rc != MDB_NOTFOUND)
+ goto finish;
+ freecount += txn->mt_dbs[FREE_DBI].md_branch_pages +
+ txn->mt_dbs[FREE_DBI].md_leaf_pages +
+ txn->mt_dbs[FREE_DBI].md_overflow_pages;
+
+ new_root = txn->mt_next_pgno - 1 - freecount;
+ mm->mm_last_pg = new_root;
+ mm->mm_dbs[MAIN_DBI] = txn->mt_dbs[MAIN_DBI];
+ mm->mm_dbs[MAIN_DBI].md_root = new_root;
+ } else {
+ /* When the DB is empty, handle it specially to
+ * fix any breakage like page leaks from ITS#8174.
+ */
+ mm->mm_dbs[MAIN_DBI].md_flags = txn->mt_dbs[MAIN_DBI].md_flags;
+ }
+ if (root != P_INVALID || mm->mm_dbs[MAIN_DBI].md_flags) {
+ mm->mm_txnid = 1; /* use metapage 1 */
+ }
+
+ my.mc_wlen[0] = env->me_psize * NUM_METAS;
+ my.mc_txn = txn;
+ rc = mdb_env_cwalk(&my, &root, 0);
+ if (rc == MDB_SUCCESS && root != new_root) {
+ rc = MDB_INCOMPATIBLE; /* page leak or corrupt DB */
+ }
+
+finish:
+ if (rc)
+ my.mc_error = rc;
+ mdb_env_cthr_toggle(&my, 1 | MDB_EOF);
+ rc = THREAD_FINISH(thr);
+ mdb_txn_abort(txn);
+
+done:
+#ifdef _WIN32
+ if (my.mc_wbuf[0]) _aligned_free(my.mc_wbuf[0]);
+ if (my.mc_cond) CloseHandle(my.mc_cond);
+ if (my.mc_mutex) CloseHandle(my.mc_mutex);
+#else
+ free(my.mc_wbuf[0]);
+ pthread_cond_destroy(&my.mc_cond);
+done2:
+ pthread_mutex_destroy(&my.mc_mutex);
+#endif
+ return rc ? rc : my.mc_error;
+}
+
+ /** Copy environment as-is. */
+static int ESECT
+mdb_env_copyfd0(MDB_env *env, HANDLE fd)
+{
+ MDB_txn *txn = NULL;
+ mdb_mutexref_t wmutex = NULL;
+ int rc;
+ size_t wsize, w3;
+ char *ptr;
+#ifdef _WIN32
+ DWORD len, w2;
+#define DO_WRITE(rc, fd, ptr, w2, len) rc = WriteFile(fd, ptr, w2, &len, NULL)
+#else
+ ssize_t len;
+ size_t w2;
+#define DO_WRITE(rc, fd, ptr, w2, len) len = write(fd, ptr, w2); rc = (len >= 0)
+#endif
+
+ /* Do the lock/unlock of the reader mutex before starting the
+ * write txn. Otherwise other read txns could block writers.
+ */
+ rc = mdb_txn_begin(env, NULL, MDB_RDONLY, &txn);
+ if (rc)
+ return rc;
+
+ if (env->me_txns) {
+ /* We must start the actual read txn after blocking writers */
+ mdb_txn_end(txn, MDB_END_RESET_TMP);
+
+ /* Temporarily block writers until we snapshot the meta pages */
+ wmutex = env->me_wmutex;
+ if (LOCK_MUTEX(rc, env, wmutex))
+ goto leave;
+
+ rc = mdb_txn_renew0(txn);
+ if (rc) {
+ UNLOCK_MUTEX(wmutex);
+ goto leave;
+ }
+ }
+
+ wsize = env->me_psize * NUM_METAS;
+ ptr = env->me_map;
+ w2 = wsize;
+ while (w2 > 0) {
+ DO_WRITE(rc, fd, ptr, w2, len);
+ if (!rc) {
+ rc = ErrCode();
+ break;
+ } else if (len > 0) {
+ rc = MDB_SUCCESS;
+ ptr += len;
+ w2 -= len;
+ continue;
+ } else {
+ /* Non-blocking or async handles are not supported */
+ rc = EIO;
+ break;
+ }
+ }
+ if (wmutex)
+ UNLOCK_MUTEX(wmutex);
+
+ if (rc)
+ goto leave;
+
+ w3 = txn->mt_next_pgno * env->me_psize;
+ {
+ size_t fsize = 0;
+ if ((rc = mdb_fsize(env->me_fd, &fsize)))
+ goto leave;
+ if (w3 > fsize)
+ w3 = fsize;
+ }
+ wsize = w3 - wsize;
+ while (wsize > 0) {
+ if (wsize > MAX_WRITE)
+ w2 = MAX_WRITE;
+ else
+ w2 = wsize;
+ DO_WRITE(rc, fd, ptr, w2, len);
+ if (!rc) {
+ rc = ErrCode();
+ break;
+ } else if (len > 0) {
+ rc = MDB_SUCCESS;
+ ptr += len;
+ wsize -= len;
+ continue;
+ } else {
+ rc = EIO;
+ break;
+ }
+ }
+
+leave:
+ mdb_txn_abort(txn);
+ return rc;
+}
+
+int ESECT
+mdb_env_copyfd2(MDB_env *env, HANDLE fd, unsigned int flags)
+{
+ if (flags & MDB_CP_COMPACT)
+ return mdb_env_copyfd1(env, fd);
+ else
+ return mdb_env_copyfd0(env, fd);
+}
+
+int ESECT
+mdb_env_copyfd(MDB_env *env, HANDLE fd)
+{
+ return mdb_env_copyfd2(env, fd, 0);
+}
+
+int ESECT
+mdb_env_copy2(MDB_env *env, const char *path, unsigned int flags)
+{
+ int rc;
+ MDB_name fname;
+ HANDLE newfd = INVALID_HANDLE_VALUE;
+
+ rc = mdb_fname_init(path, env->me_flags | MDB_NOLOCK, &fname);
+ if (rc == MDB_SUCCESS) {
+ rc = mdb_fopen(env, &fname, MDB_O_COPY, 0666, &newfd);
+ mdb_fname_destroy(fname);
+ }
+ if (rc == MDB_SUCCESS) {
+ rc = mdb_env_copyfd2(env, newfd, flags);
+ if (close(newfd) < 0 && rc == MDB_SUCCESS)
+ rc = ErrCode();
+ }
+ return rc;
+}
+
+int ESECT
+mdb_env_copy(MDB_env *env, const char *path)
+{
+ return mdb_env_copy2(env, path, 0);
+}
+
+int ESECT
+mdb_env_set_flags(MDB_env *env, unsigned int flag, int onoff)
+{
+ if (flag & ~CHANGEABLE)
+ return EINVAL;
+ if (onoff)
+ env->me_flags |= flag;
+ else
+ env->me_flags &= ~flag;
+ return MDB_SUCCESS;
+}
+
+int ESECT
+mdb_env_get_flags(MDB_env *env, unsigned int *arg)
+{
+ if (!env || !arg)
+ return EINVAL;
+
+ *arg = env->me_flags & (CHANGEABLE|CHANGELESS);
+ return MDB_SUCCESS;
+}
+
+int ESECT
+mdb_env_set_userctx(MDB_env *env, void *ctx)
+{
+ if (!env)
+ return EINVAL;
+ env->me_userctx = ctx;
+ return MDB_SUCCESS;
+}
+
+void * ESECT
+mdb_env_get_userctx(MDB_env *env)
+{
+ return env ? env->me_userctx : NULL;
+}
+
+int ESECT
+mdb_env_set_assert(MDB_env *env, MDB_assert_func *func)
+{
+ if (!env)
+ return EINVAL;
+#ifndef NDEBUG
+ env->me_assert_func = func;
+#endif
+ return MDB_SUCCESS;
+}
+
+int ESECT
+mdb_env_get_path(MDB_env *env, const char **arg)
+{
+ if (!env || !arg)
+ return EINVAL;
+
+ *arg = env->me_path;
+ return MDB_SUCCESS;
+}
+
+int ESECT
+mdb_env_get_fd(MDB_env *env, mdb_filehandle_t *arg)
+{
+ if (!env || !arg)
+ return EINVAL;
+
+ *arg = env->me_fd;
+ return MDB_SUCCESS;
+}
+
+/** Common code for #mdb_stat() and #mdb_env_stat().
+ * @param[in] env the environment to operate in.
+ * @param[in] db the #MDB_db record containing the stats to return.
+ * @param[out] arg the address of an #MDB_stat structure to receive the stats.
+ * @return 0, this function always succeeds.
+ */
+static int ESECT
+mdb_stat0(MDB_env *env, MDB_db *db, MDB_stat *arg)
+{
+ arg->ms_psize = env->me_psize;
+ arg->ms_depth = db->md_depth;
+ arg->ms_branch_pages = db->md_branch_pages;
+ arg->ms_leaf_pages = db->md_leaf_pages;
+ arg->ms_overflow_pages = db->md_overflow_pages;
+ arg->ms_entries = db->md_entries;
+
+ return MDB_SUCCESS;
+}
+
+int ESECT
+mdb_env_stat(MDB_env *env, MDB_stat *arg)
+{
+ MDB_meta *meta;
+
+ if (env == NULL || arg == NULL)
+ return EINVAL;
+
+ meta = mdb_env_pick_meta(env);
+
+ return mdb_stat0(env, &meta->mm_dbs[MAIN_DBI], arg);
+}
+
+int ESECT
+mdb_env_info(MDB_env *env, MDB_envinfo *arg)
+{
+ MDB_meta *meta;
+
+ if (env == NULL || arg == NULL)
+ return EINVAL;
+
+ meta = mdb_env_pick_meta(env);
+ arg->me_mapaddr = meta->mm_address;
+ arg->me_last_pgno = meta->mm_last_pg;
+ arg->me_last_txnid = meta->mm_txnid;
+
+ arg->me_mapsize = env->me_mapsize;
+ arg->me_maxreaders = env->me_maxreaders;
+ arg->me_numreaders = env->me_txns ? env->me_txns->mti_numreaders : 0;
+ return MDB_SUCCESS;
+}
+
+/** Set the default comparison functions for a database.
+ * Called immediately after a database is opened to set the defaults.
+ * The user can then override them with #mdb_set_compare() or
+ * #mdb_set_dupsort().
+ * @param[in] txn A transaction handle returned by #mdb_txn_begin()
+ * @param[in] dbi A database handle returned by #mdb_dbi_open()
+ */
+static void
+mdb_default_cmp(MDB_txn *txn, MDB_dbi dbi)
+{
+ uint16_t f = txn->mt_dbs[dbi].md_flags;
+
+ txn->mt_dbxs[dbi].md_cmp =
+ (f & MDB_REVERSEKEY) ? mdb_cmp_memnr :
+ (f & MDB_INTEGERKEY) ? mdb_cmp_cint : mdb_cmp_memn;
+
+ txn->mt_dbxs[dbi].md_dcmp =
+ !(f & MDB_DUPSORT) ? 0 :
+ ((f & MDB_INTEGERDUP)
+ ? ((f & MDB_DUPFIXED) ? mdb_cmp_int : mdb_cmp_cint)
+ : ((f & MDB_REVERSEDUP) ? mdb_cmp_memnr : mdb_cmp_memn));
+}
+
+int mdb_dbi_open(MDB_txn *txn, const char *name, unsigned int flags, MDB_dbi *dbi)
+{
+ MDB_val key, data;
+ MDB_dbi i;
+ MDB_cursor mc;
+ MDB_db dummy;
+ int rc, dbflag, exact;
+ unsigned int unused = 0, seq;
+ char *namedup;
+ size_t len;
+
+ if (flags & ~VALID_FLAGS)
+ return EINVAL;
+ if (txn->mt_flags & MDB_TXN_BLOCKED)
+ return MDB_BAD_TXN;
+
+ /* main DB? */
+ if (!name) {
+ *dbi = MAIN_DBI;
+ if (flags & PERSISTENT_FLAGS) {
+ uint16_t f2 = flags & PERSISTENT_FLAGS;
+ /* make sure flag changes get committed */
+ if ((txn->mt_dbs[MAIN_DBI].md_flags | f2) != txn->mt_dbs[MAIN_DBI].md_flags) {
+ txn->mt_dbs[MAIN_DBI].md_flags |= f2;
+ txn->mt_flags |= MDB_TXN_DIRTY;
+ }
+ }
+ mdb_default_cmp(txn, MAIN_DBI);
+ return MDB_SUCCESS;
+ }
+
+ if (txn->mt_dbxs[MAIN_DBI].md_cmp == NULL) {
+ mdb_default_cmp(txn, MAIN_DBI);
+ }
+
+ /* Is the DB already open? */
+ len = strlen(name);
+ for (i=CORE_DBS; i<txn->mt_numdbs; i++) {
+ if (!txn->mt_dbxs[i].md_name.mv_size) {
+ /* Remember this free slot */
+ if (!unused) unused = i;
+ continue;
+ }
+ if (len == txn->mt_dbxs[i].md_name.mv_size &&
+ !strncmp(name, txn->mt_dbxs[i].md_name.mv_data, len)) {
+ *dbi = i;
+ return MDB_SUCCESS;
+ }
+ }
+
+ /* If no free slot and max hit, fail */
+ if (!unused && txn->mt_numdbs >= txn->mt_env->me_maxdbs)
+ return MDB_DBS_FULL;
+
+ /* Cannot mix named databases with some mainDB flags */
+ if (txn->mt_dbs[MAIN_DBI].md_flags & (MDB_DUPSORT|MDB_INTEGERKEY))
+ return (flags & MDB_CREATE) ? MDB_INCOMPATIBLE : MDB_NOTFOUND;
+
+ /* Find the DB info */
+ dbflag = DB_NEW|DB_VALID|DB_USRVALID;
+ exact = 0;
+ key.mv_size = len;
+ key.mv_data = (void *)name;
+ mdb_cursor_init(&mc, txn, MAIN_DBI, NULL);
+ rc = mdb_cursor_set(&mc, &key, &data, MDB_SET, &exact);
+ if (rc == MDB_SUCCESS) {
+ /* make sure this is actually a DB */
+ MDB_node *node = NODEPTR(mc.mc_pg[mc.mc_top], mc.mc_ki[mc.mc_top]);
+ if ((node->mn_flags & (F_DUPDATA|F_SUBDATA)) != F_SUBDATA)
+ return MDB_INCOMPATIBLE;
+ } else {
+ if (rc != MDB_NOTFOUND || !(flags & MDB_CREATE))
+ return rc;
+ if (F_ISSET(txn->mt_flags, MDB_TXN_RDONLY))
+ return EACCES;
+ }
+
+ /* Done here so we cannot fail after creating a new DB */
+ if ((namedup = strdup(name)) == NULL)
+ return ENOMEM;
+
+ if (rc) {
+ /* MDB_NOTFOUND and MDB_CREATE: Create new DB */
+ data.mv_size = sizeof(MDB_db);
+ data.mv_data = &dummy;
+ memset(&dummy, 0, sizeof(dummy));
+ dummy.md_root = P_INVALID;
+ dummy.md_flags = flags & PERSISTENT_FLAGS;
+ WITH_CURSOR_TRACKING(mc,
+ rc = mdb_cursor_put(&mc, &key, &data, F_SUBDATA));
+ dbflag |= DB_DIRTY;
+ }
+
+ if (rc) {
+ free(namedup);
+ } else {
+ /* Got info, register DBI in this txn */
+ unsigned int slot = unused ? unused : txn->mt_numdbs;
+ txn->mt_dbxs[slot].md_name.mv_data = namedup;
+ txn->mt_dbxs[slot].md_name.mv_size = len;
+ txn->mt_dbxs[slot].md_rel = NULL;
+ txn->mt_dbflags[slot] = dbflag;
+ /* txn-> and env-> are the same in read txns, use
+ * tmp variable to avoid undefined assignment
+ */
+ seq = ++txn->mt_env->me_dbiseqs[slot];
+ txn->mt_dbiseqs[slot] = seq;
+
+ memcpy(&txn->mt_dbs[slot], data.mv_data, sizeof(MDB_db));
+ *dbi = slot;
+ mdb_default_cmp(txn, slot);
+ if (!unused) {
+ txn->mt_numdbs++;
+ }
+ }
+
+ return rc;
+}
+
+int ESECT
+mdb_stat(MDB_txn *txn, MDB_dbi dbi, MDB_stat *arg)
+{
+ if (!arg || !TXN_DBI_EXIST(txn, dbi, DB_VALID))
+ return EINVAL;
+
+ if (txn->mt_flags & MDB_TXN_BLOCKED)
+ return MDB_BAD_TXN;
+
+ if (txn->mt_dbflags[dbi] & DB_STALE) {
+ MDB_cursor mc;
+ MDB_xcursor mx;
+ /* Stale, must read the DB's root. cursor_init does it for us. */
+ mdb_cursor_init(&mc, txn, dbi, &mx);
+ }
+ return mdb_stat0(txn->mt_env, &txn->mt_dbs[dbi], arg);
+}
+
+void mdb_dbi_close(MDB_env *env, MDB_dbi dbi)
+{
+ char *ptr;
+ if (dbi < CORE_DBS || dbi >= env->me_maxdbs)
+ return;
+ ptr = env->me_dbxs[dbi].md_name.mv_data;
+ /* If there was no name, this was already closed */
+ if (ptr) {
+ env->me_dbxs[dbi].md_name.mv_data = NULL;
+ env->me_dbxs[dbi].md_name.mv_size = 0;
+ env->me_dbflags[dbi] = 0;
+ env->me_dbiseqs[dbi]++;
+ free(ptr);
+ }
+}
+
+int mdb_dbi_flags(MDB_txn *txn, MDB_dbi dbi, unsigned int *flags)
+{
+ /* We could return the flags for the FREE_DBI too but what's the point? */
+ if (!TXN_DBI_EXIST(txn, dbi, DB_USRVALID))
+ return EINVAL;
+ *flags = txn->mt_dbs[dbi].md_flags & PERSISTENT_FLAGS;
+ return MDB_SUCCESS;
+}
+
+/** Add all the DB's pages to the free list.
+ * @param[in] mc Cursor on the DB to free.
+ * @param[in] subs non-Zero to check for sub-DBs in this DB.
+ * @return 0 on success, non-zero on failure.
+ */
+static int
+mdb_drop0(MDB_cursor *mc, int subs)
+{
+ int rc;
+
+ rc = mdb_page_search(mc, NULL, MDB_PS_FIRST);
+ if (rc == MDB_SUCCESS) {
+ MDB_txn *txn = mc->mc_txn;
+ MDB_node *ni;
+ MDB_cursor mx;
+ unsigned int i;
+
+ /* DUPSORT sub-DBs have no ovpages/DBs. Omit scanning leaves.
+ * This also avoids any P_LEAF2 pages, which have no nodes.
+ * Also if the DB doesn't have sub-DBs and has no overflow
+ * pages, omit scanning leaves.
+ */
+ if ((mc->mc_flags & C_SUB) ||
+ (!subs && !mc->mc_db->md_overflow_pages))
+ mdb_cursor_pop(mc);
+
+ mdb_cursor_copy(mc, &mx);
+ while (mc->mc_snum > 0) {
+ MDB_page *mp = mc->mc_pg[mc->mc_top];
+ unsigned n = NUMKEYS(mp);
+ if (IS_LEAF(mp)) {
+ for (i=0; i<n; i++) {
+ ni = NODEPTR(mp, i);
+ if (ni->mn_flags & F_BIGDATA) {
+ MDB_page *omp;
+ pgno_t pg;
+ memcpy(&pg, NODEDATA(ni), sizeof(pg));
+ rc = mdb_page_get(mc, pg, &omp, NULL);
+ if (rc != 0)
+ goto done;
+ mdb_cassert(mc, IS_OVERFLOW(omp));
+ rc = mdb_midl_append_range(&txn->mt_free_pgs,
+ pg, omp->mp_pages);
+ if (rc)
+ goto done;
+ mc->mc_db->md_overflow_pages -= omp->mp_pages;
+ if (!mc->mc_db->md_overflow_pages && !subs)
+ break;
+ } else if (subs && (ni->mn_flags & F_SUBDATA)) {
+ mdb_xcursor_init1(mc, ni);
+ rc = mdb_drop0(&mc->mc_xcursor->mx_cursor, 0);
+ if (rc)
+ goto done;
+ }
+ }
+ if (!subs && !mc->mc_db->md_overflow_pages)
+ goto pop;
+ } else {
+ if ((rc = mdb_midl_need(&txn->mt_free_pgs, n)) != 0)
+ goto done;
+ for (i=0; i<n; i++) {
+ pgno_t pg;
+ ni = NODEPTR(mp, i);
+ pg = NODEPGNO(ni);
+ /* free it */
+ mdb_midl_xappend(txn->mt_free_pgs, pg);
+ }
+ }
+ if (!mc->mc_top)
+ break;
+ mc->mc_ki[mc->mc_top] = i;
+ rc = mdb_cursor_sibling(mc, 1);
+ if (rc) {
+ if (rc != MDB_NOTFOUND)
+ goto done;
+ /* no more siblings, go back to beginning
+ * of previous level.
+ */
+pop:
+ mdb_cursor_pop(mc);
+ mc->mc_ki[0] = 0;
+ for (i=1; i<mc->mc_snum; i++) {
+ mc->mc_ki[i] = 0;
+ mc->mc_pg[i] = mx.mc_pg[i];
+ }
+ }
+ }
+ /* free it */
+ rc = mdb_midl_append(&txn->mt_free_pgs, mc->mc_db->md_root);
+done:
+ if (rc)
+ txn->mt_flags |= MDB_TXN_ERROR;
+ } else if (rc == MDB_NOTFOUND) {
+ rc = MDB_SUCCESS;
+ }
+ mc->mc_flags &= ~C_INITIALIZED;
+ return rc;
+}
+
+int mdb_drop(MDB_txn *txn, MDB_dbi dbi, int del)
+{
+ MDB_cursor *mc, *m2;
+ int rc;
+
+ if ((unsigned)del > 1 || !TXN_DBI_EXIST(txn, dbi, DB_USRVALID))
+ return EINVAL;
+
+ if (F_ISSET(txn->mt_flags, MDB_TXN_RDONLY))
+ return EACCES;
+
+ if (TXN_DBI_CHANGED(txn, dbi))
+ return MDB_BAD_DBI;
+
+ rc = mdb_cursor_open(txn, dbi, &mc);
+ if (rc)
+ return rc;
+
+ rc = mdb_drop0(mc, mc->mc_db->md_flags & MDB_DUPSORT);
+ /* Invalidate the dropped DB's cursors */
+ for (m2 = txn->mt_cursors[dbi]; m2; m2 = m2->mc_next)
+ m2->mc_flags &= ~(C_INITIALIZED|C_EOF);
+ if (rc)
+ goto leave;
+
+ /* Can't delete the main DB */
+ if (del && dbi >= CORE_DBS) {
+ rc = mdb_del0(txn, MAIN_DBI, &mc->mc_dbx->md_name, NULL, F_SUBDATA);
+ if (!rc) {
+ txn->mt_dbflags[dbi] = DB_STALE;
+ mdb_dbi_close(txn->mt_env, dbi);
+ } else {
+ txn->mt_flags |= MDB_TXN_ERROR;
+ }
+ } else {
+ /* reset the DB record, mark it dirty */
+ txn->mt_dbflags[dbi] |= DB_DIRTY;
+ txn->mt_dbs[dbi].md_depth = 0;
+ txn->mt_dbs[dbi].md_branch_pages = 0;
+ txn->mt_dbs[dbi].md_leaf_pages = 0;
+ txn->mt_dbs[dbi].md_overflow_pages = 0;
+ txn->mt_dbs[dbi].md_entries = 0;
+ txn->mt_dbs[dbi].md_root = P_INVALID;
+
+ txn->mt_flags |= MDB_TXN_DIRTY;
+ }
+leave:
+ mdb_cursor_close(mc);
+ return rc;
+}
+
+int mdb_set_compare(MDB_txn *txn, MDB_dbi dbi, MDB_cmp_func *cmp)
+{
+ if (!TXN_DBI_EXIST(txn, dbi, DB_USRVALID))
+ return EINVAL;
+
+ txn->mt_dbxs[dbi].md_cmp = cmp;
+ return MDB_SUCCESS;
+}
+
+int mdb_set_dupsort(MDB_txn *txn, MDB_dbi dbi, MDB_cmp_func *cmp)
+{
+ if (!TXN_DBI_EXIST(txn, dbi, DB_USRVALID))
+ return EINVAL;
+
+ txn->mt_dbxs[dbi].md_dcmp = cmp;
+ return MDB_SUCCESS;
+}
+
+int mdb_set_relfunc(MDB_txn *txn, MDB_dbi dbi, MDB_rel_func *rel)
+{
+ if (!TXN_DBI_EXIST(txn, dbi, DB_USRVALID))
+ return EINVAL;
+
+ txn->mt_dbxs[dbi].md_rel = rel;
+ return MDB_SUCCESS;
+}
+
+int mdb_set_relctx(MDB_txn *txn, MDB_dbi dbi, void *ctx)
+{
+ if (!TXN_DBI_EXIST(txn, dbi, DB_USRVALID))
+ return EINVAL;
+
+ txn->mt_dbxs[dbi].md_relctx = ctx;
+ return MDB_SUCCESS;
+}
+
+int ESECT
+mdb_env_get_maxkeysize(MDB_env *env)
+{
+ return ENV_MAXKEY(env);
+}
+
+int ESECT
+mdb_reader_list(MDB_env *env, MDB_msg_func *func, void *ctx)
+{
+ unsigned int i, rdrs;
+ MDB_reader *mr;
+ char buf[64];
+ int rc = 0, first = 1;
+
+ if (!env || !func)
+ return -1;
+ if (!env->me_txns) {
+ return func("(no reader locks)\n", ctx);
+ }
+ rdrs = env->me_txns->mti_numreaders;
+ mr = env->me_txns->mti_readers;
+ for (i=0; i<rdrs; i++) {
+ if (mr[i].mr_pid) {
+ txnid_t txnid = mr[i].mr_txnid;
+ sprintf(buf, txnid == (txnid_t)-1 ?
+ "%10d %"Z"x -\n" : "%10d %"Z"x %"Z"u\n",
+ (int)mr[i].mr_pid, (size_t)mr[i].mr_tid, txnid);
+ if (first) {
+ first = 0;
+ rc = func(" pid thread txnid\n", ctx);
+ if (rc < 0)
+ break;
+ }
+ rc = func(buf, ctx);
+ if (rc < 0)
+ break;
+ }
+ }
+ if (first) {
+ rc = func("(no active readers)\n", ctx);
+ }
+ return rc;
+}
+
+/** Insert pid into list if not already present.
+ * return -1 if already present.
+ */
+static int ESECT
+mdb_pid_insert(MDB_PID_T *ids, MDB_PID_T pid)
+{
+ /* binary search of pid in list */
+ unsigned base = 0;
+ unsigned cursor = 1;
+ int val = 0;
+ unsigned n = ids[0];
+
+ while( 0 < n ) {
+ unsigned pivot = n >> 1;
+ cursor = base + pivot + 1;
+ val = pid - ids[cursor];
+
+ if( val < 0 ) {
+ n = pivot;
+
+ } else if ( val > 0 ) {
+ base = cursor;
+ n -= pivot + 1;
+
+ } else {
+ /* found, so it's a duplicate */
+ return -1;
+ }
+ }
+
+ if( val > 0 ) {
+ ++cursor;
+ }
+ ids[0]++;
+ for (n = ids[0]; n > cursor; n--)
+ ids[n] = ids[n-1];
+ ids[n] = pid;
+ return 0;
+}
+
+int ESECT
+mdb_reader_check(MDB_env *env, int *dead)
+{
+ if (!env)
+ return EINVAL;
+ if (dead)
+ *dead = 0;
+ return env->me_txns ? mdb_reader_check0(env, 0, dead) : MDB_SUCCESS;
+}
+
+/** As #mdb_reader_check(). \b rlocked is set if caller locked #me_rmutex. */
+static int ESECT
+mdb_reader_check0(MDB_env *env, int rlocked, int *dead)
+{
+ mdb_mutexref_t rmutex = rlocked ? NULL : env->me_rmutex;
+ unsigned int i, j, rdrs;
+ MDB_reader *mr;
+ MDB_PID_T *pids, pid;
+ int rc = MDB_SUCCESS, count = 0;
+
+ rdrs = env->me_txns->mti_numreaders;
+ pids = malloc((rdrs+1) * sizeof(MDB_PID_T));
+ if (!pids)
+ return ENOMEM;
+ pids[0] = 0;
+ mr = env->me_txns->mti_readers;
+ for (i=0; i<rdrs; i++) {
+ pid = mr[i].mr_pid;
+ if (pid && pid != env->me_pid) {
+ if (mdb_pid_insert(pids, pid) == 0) {
+ if (!mdb_reader_pid(env, Pidcheck, pid)) {
+ /* Stale reader found */
+ j = i;
+ if (rmutex) {
+ if ((rc = LOCK_MUTEX0(rmutex)) != 0) {
+ if ((rc = mdb_mutex_failed(env, rmutex, rc)))
+ break;
+ rdrs = 0; /* the above checked all readers */
+ } else {
+ /* Recheck, a new process may have reused pid */
+ if (mdb_reader_pid(env, Pidcheck, pid))
+ j = rdrs;
+ }
+ }
+ for (; j<rdrs; j++)
+ if (mr[j].mr_pid == pid) {
+ DPRINTF(("clear stale reader pid %u txn %"Z"d",
+ (unsigned) pid, mr[j].mr_txnid));
+ mr[j].mr_pid = 0;
+ count++;
+ }
+ if (rmutex)
+ UNLOCK_MUTEX(rmutex);
+ }
+ }
+ }
+ }
+ free(pids);
+ if (dead)
+ *dead = count;
+ return rc;
+}
+
+#ifdef MDB_ROBUST_SUPPORTED
+/** Handle #LOCK_MUTEX0() failure.
+ * Try to repair the lock file if the mutex owner died.
+ * @param[in] env the environment handle
+ * @param[in] mutex LOCK_MUTEX0() mutex
+ * @param[in] rc LOCK_MUTEX0() error (nonzero)
+ * @return 0 on success with the mutex locked, or an error code on failure.
+ */
+static int ESECT
+mdb_mutex_failed(MDB_env *env, mdb_mutexref_t mutex, int rc)
+{
+ int rlocked, rc2;
+ MDB_meta *meta;
+
+ if (rc == MDB_OWNERDEAD) {
+ /* We own the mutex. Clean up after dead previous owner. */
+ rc = MDB_SUCCESS;
+ rlocked = (mutex == env->me_rmutex);
+ if (!rlocked) {
+ /* Keep mti_txnid updated, otherwise next writer can
+ * overwrite data which latest meta page refers to.
+ */
+ meta = mdb_env_pick_meta(env);
+ env->me_txns->mti_txnid = meta->mm_txnid;
+ /* env is hosed if the dead thread was ours */
+ if (env->me_txn) {
+ env->me_flags |= MDB_FATAL_ERROR;
+ env->me_txn = NULL;
+ rc = MDB_PANIC;
+ }
+ }
+ DPRINTF(("%cmutex owner died, %s", (rlocked ? 'r' : 'w'),
+ (rc ? "this process' env is hosed" : "recovering")));
+ rc2 = mdb_reader_check0(env, rlocked, NULL);
+ if (rc2 == 0)
+ rc2 = mdb_mutex_consistent(mutex);
+ if (rc || (rc = rc2)) {
+ DPRINTF(("LOCK_MUTEX recovery failed, %s", mdb_strerror(rc)));
+ UNLOCK_MUTEX(mutex);
+ }
+ } else {
+#ifdef _WIN32
+ rc = ErrCode();
+#endif
+ DPRINTF(("LOCK_MUTEX failed, %s", mdb_strerror(rc)));
+ }
+
+ return rc;
+}
+#endif /* MDB_ROBUST_SUPPORTED */
+
+#if defined(_WIN32)
+/** Convert \b src to new wchar_t[] string with room for \b xtra extra chars */
+static int ESECT
+utf8_to_utf16(const char *src, MDB_name *dst, int xtra)
+{
+ int rc, need = 0;
+ wchar_t *result = NULL;
+ for (;;) { /* malloc result, then fill it in */
+ need = MultiByteToWideChar(CP_UTF8, 0, src, -1, result, need);
+ if (!need) {
+ rc = ErrCode();
+ free(result);
+ return rc;
+ }
+ if (!result) {
+ result = malloc(sizeof(wchar_t) * (need + xtra));
+ if (!result)
+ return ENOMEM;
+ continue;
+ }
+ dst->mn_alloced = 1;
+ dst->mn_len = need - 1;
+ dst->mn_val = result;
+ return MDB_SUCCESS;
+ }
+}
+#endif /* defined(_WIN32) */
+/** @} */
diff --git a/src/contrib/lmdb/midl.c b/src/contrib/lmdb/midl.c
new file mode 100644
index 0000000..7b2b77e
--- /dev/null
+++ b/src/contrib/lmdb/midl.c
@@ -0,0 +1,359 @@
+/** @file midl.c
+ * @brief ldap bdb back-end ID List functions */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2018 The OpenLDAP Foundation.
+ * Portions Copyright 2001-2018 Howard Chu, Symas Corp.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include <limits.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <sys/types.h>
+#include "midl.h"
+
+/** @defgroup internal LMDB Internals
+ * @{
+ */
+/** @defgroup idls ID List Management
+ * @{
+ */
+#define CMP(x,y) ( (x) < (y) ? -1 : (x) > (y) )
+
+unsigned mdb_midl_search( MDB_IDL ids, MDB_ID id )
+{
+ /*
+ * binary search of id in ids
+ * if found, returns position of id
+ * if not found, returns first position greater than id
+ */
+ unsigned base = 0;
+ unsigned cursor = 1;
+ int val = 0;
+ unsigned n = ids[0];
+
+ while( 0 < n ) {
+ unsigned pivot = n >> 1;
+ cursor = base + pivot + 1;
+ val = CMP( ids[cursor], id );
+
+ if( val < 0 ) {
+ n = pivot;
+
+ } else if ( val > 0 ) {
+ base = cursor;
+ n -= pivot + 1;
+
+ } else {
+ return cursor;
+ }
+ }
+
+ if( val > 0 ) {
+ ++cursor;
+ }
+ return cursor;
+}
+
+#if 0 /* superseded by append/sort */
+int mdb_midl_insert( MDB_IDL ids, MDB_ID id )
+{
+ unsigned x, i;
+
+ x = mdb_midl_search( ids, id );
+ assert( x > 0 );
+
+ if( x < 1 ) {
+ /* internal error */
+ return -2;
+ }
+
+ if ( x <= ids[0] && ids[x] == id ) {
+ /* duplicate */
+ assert(0);
+ return -1;
+ }
+
+ if ( ++ids[0] >= MDB_IDL_DB_MAX ) {
+ /* no room */
+ --ids[0];
+ return -2;
+
+ } else {
+ /* insert id */
+ for (i=ids[0]; i>x; i--)
+ ids[i] = ids[i-1];
+ ids[x] = id;
+ }
+
+ return 0;
+}
+#endif
+
+MDB_IDL mdb_midl_alloc(int num)
+{
+ MDB_IDL ids = malloc((num+2) * sizeof(MDB_ID));
+ if (ids) {
+ *ids++ = num;
+ *ids = 0;
+ }
+ return ids;
+}
+
+void mdb_midl_free(MDB_IDL ids)
+{
+ if (ids)
+ free(ids-1);
+}
+
+void mdb_midl_shrink( MDB_IDL *idp )
+{
+ MDB_IDL ids = *idp;
+ if (*(--ids) > MDB_IDL_UM_MAX &&
+ (ids = realloc(ids, (MDB_IDL_UM_MAX+2) * sizeof(MDB_ID))))
+ {
+ *ids++ = MDB_IDL_UM_MAX;
+ *idp = ids;
+ }
+}
+
+static int mdb_midl_grow( MDB_IDL *idp, int num )
+{
+ MDB_IDL idn = *idp-1;
+ /* grow it */
+ idn = realloc(idn, (*idn + num + 2) * sizeof(MDB_ID));
+ if (!idn)
+ return ENOMEM;
+ *idn++ += num;
+ *idp = idn;
+ return 0;
+}
+
+int mdb_midl_need( MDB_IDL *idp, unsigned num )
+{
+ MDB_IDL ids = *idp;
+ num += ids[0];
+ if (num > ids[-1]) {
+ num = (num + num/4 + (256 + 2)) & -256;
+ if (!(ids = realloc(ids-1, num * sizeof(MDB_ID))))
+ return ENOMEM;
+ *ids++ = num - 2;
+ *idp = ids;
+ }
+ return 0;
+}
+
+int mdb_midl_append( MDB_IDL *idp, MDB_ID id )
+{
+ MDB_IDL ids = *idp;
+ /* Too big? */
+ if (ids[0] >= ids[-1]) {
+ if (mdb_midl_grow(idp, MDB_IDL_UM_MAX))
+ return ENOMEM;
+ ids = *idp;
+ }
+ ids[0]++;
+ ids[ids[0]] = id;
+ return 0;
+}
+
+int mdb_midl_append_list( MDB_IDL *idp, MDB_IDL app )
+{
+ MDB_IDL ids = *idp;
+ /* Too big? */
+ if (ids[0] + app[0] >= ids[-1]) {
+ if (mdb_midl_grow(idp, app[0]))
+ return ENOMEM;
+ ids = *idp;
+ }
+ memcpy(&ids[ids[0]+1], &app[1], app[0] * sizeof(MDB_ID));
+ ids[0] += app[0];
+ return 0;
+}
+
+int mdb_midl_append_range( MDB_IDL *idp, MDB_ID id, unsigned n )
+{
+ MDB_ID *ids = *idp, len = ids[0];
+ /* Too big? */
+ if (len + n > ids[-1]) {
+ if (mdb_midl_grow(idp, n | MDB_IDL_UM_MAX))
+ return ENOMEM;
+ ids = *idp;
+ }
+ ids[0] = len + n;
+ ids += len;
+ while (n)
+ ids[n--] = id++;
+ return 0;
+}
+
+void mdb_midl_xmerge( MDB_IDL idl, MDB_IDL merge )
+{
+ MDB_ID old_id, merge_id, i = merge[0], j = idl[0], k = i+j, total = k;
+ idl[0] = (MDB_ID)-1; /* delimiter for idl scan below */
+ old_id = idl[j];
+ while (i) {
+ merge_id = merge[i--];
+ for (; old_id < merge_id; old_id = idl[--j])
+ idl[k--] = old_id;
+ idl[k--] = merge_id;
+ }
+ idl[0] = total;
+}
+
+/* Quicksort + Insertion sort for small arrays */
+
+#define SMALL 8
+#define MIDL_SWAP(a,b) { itmp=(a); (a)=(b); (b)=itmp; }
+
+void
+mdb_midl_sort( MDB_IDL ids )
+{
+ /* Max possible depth of int-indexed tree * 2 items/level */
+ int istack[sizeof(int)*CHAR_BIT * 2];
+ int i,j,k,l,ir,jstack;
+ MDB_ID a, itmp;
+
+ ir = (int)ids[0];
+ l = 1;
+ jstack = 0;
+ for(;;) {
+ if (ir - l < SMALL) { /* Insertion sort */
+ for (j=l+1;j<=ir;j++) {
+ a = ids[j];
+ for (i=j-1;i>=1;i--) {
+ if (ids[i] >= a) break;
+ ids[i+1] = ids[i];
+ }
+ ids[i+1] = a;
+ }
+ if (jstack == 0) break;
+ ir = istack[jstack--];
+ l = istack[jstack--];
+ } else {
+ k = (l + ir) >> 1; /* Choose median of left, center, right */
+ MIDL_SWAP(ids[k], ids[l+1]);
+ if (ids[l] < ids[ir]) {
+ MIDL_SWAP(ids[l], ids[ir]);
+ }
+ if (ids[l+1] < ids[ir]) {
+ MIDL_SWAP(ids[l+1], ids[ir]);
+ }
+ if (ids[l] < ids[l+1]) {
+ MIDL_SWAP(ids[l], ids[l+1]);
+ }
+ i = l+1;
+ j = ir;
+ a = ids[l+1];
+ for(;;) {
+ do i++; while(ids[i] > a);
+ do j--; while(ids[j] < a);
+ if (j < i) break;
+ MIDL_SWAP(ids[i],ids[j]);
+ }
+ ids[l+1] = ids[j];
+ ids[j] = a;
+ jstack += 2;
+ if (ir-i+1 >= j-l) {
+ istack[jstack] = ir;
+ istack[jstack-1] = i;
+ ir = j-1;
+ } else {
+ istack[jstack] = j-1;
+ istack[jstack-1] = l;
+ l = i;
+ }
+ }
+ }
+}
+
+unsigned mdb_mid2l_search( MDB_ID2L ids, MDB_ID id )
+{
+ /*
+ * binary search of id in ids
+ * if found, returns position of id
+ * if not found, returns first position greater than id
+ */
+ unsigned base = 0;
+ unsigned cursor = 1;
+ int val = 0;
+ unsigned n = (unsigned)ids[0].mid;
+
+ while( 0 < n ) {
+ unsigned pivot = n >> 1;
+ cursor = base + pivot + 1;
+ val = CMP( id, ids[cursor].mid );
+
+ if( val < 0 ) {
+ n = pivot;
+
+ } else if ( val > 0 ) {
+ base = cursor;
+ n -= pivot + 1;
+
+ } else {
+ return cursor;
+ }
+ }
+
+ if( val > 0 ) {
+ ++cursor;
+ }
+ return cursor;
+}
+
+int mdb_mid2l_insert( MDB_ID2L ids, MDB_ID2 *id )
+{
+ unsigned x, i;
+
+ x = mdb_mid2l_search( ids, id->mid );
+
+ if( x < 1 ) {
+ /* internal error */
+ return -2;
+ }
+
+ if ( x <= ids[0].mid && ids[x].mid == id->mid ) {
+ /* duplicate */
+ return -1;
+ }
+
+ if ( ids[0].mid >= MDB_IDL_UM_MAX ) {
+ /* too big */
+ return -2;
+
+ } else {
+ /* insert id */
+ ids[0].mid++;
+ for (i=(unsigned)ids[0].mid; i>x; i--)
+ ids[i] = ids[i-1];
+ ids[x] = *id;
+ }
+
+ return 0;
+}
+
+int mdb_mid2l_append( MDB_ID2L ids, MDB_ID2 *id )
+{
+ /* Too big? */
+ if (ids[0].mid >= MDB_IDL_UM_MAX) {
+ return -2;
+ }
+ ids[0].mid++;
+ ids[ids[0].mid] = *id;
+ return 0;
+}
+
+/** @} */
+/** @} */
diff --git a/src/contrib/lmdb/midl.h b/src/contrib/lmdb/midl.h
new file mode 100644
index 0000000..1aa374c
--- /dev/null
+++ b/src/contrib/lmdb/midl.h
@@ -0,0 +1,186 @@
+/** @file midl.h
+ * @brief LMDB ID List header file.
+ *
+ * This file was originally part of back-bdb but has been
+ * modified for use in libmdb. Most of the macros defined
+ * in this file are unused, just left over from the original.
+ *
+ * This file is only used internally in libmdb and its definitions
+ * are not exposed publicly.
+ */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2000-2018 The OpenLDAP Foundation.
+ * Portions Copyright 2001-2018 Howard Chu, Symas Corp.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#ifndef _MDB_MIDL_H_
+#define _MDB_MIDL_H_
+
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** @defgroup internal LMDB Internals
+ * @{
+ */
+
+/** @defgroup idls ID List Management
+ * @{
+ */
+ /** A generic unsigned ID number. These were entryIDs in back-bdb.
+ * Preferably it should have the same size as a pointer.
+ */
+typedef size_t MDB_ID;
+
+ /** An IDL is an ID List, a sorted array of IDs. The first
+ * element of the array is a counter for how many actual
+ * IDs are in the list. In the original back-bdb code, IDLs are
+ * sorted in ascending order. For libmdb IDLs are sorted in
+ * descending order.
+ */
+typedef MDB_ID *MDB_IDL;
+
+/* IDL sizes - likely should be even bigger
+ * limiting factors: sizeof(ID), thread stack size
+ */
+#define MDB_IDL_LOGN 16 /* DB_SIZE is 2^16, UM_SIZE is 2^17 */
+#define MDB_IDL_DB_SIZE (1<<MDB_IDL_LOGN)
+#define MDB_IDL_UM_SIZE (1<<(MDB_IDL_LOGN+1))
+
+#define MDB_IDL_DB_MAX (MDB_IDL_DB_SIZE-1)
+#define MDB_IDL_UM_MAX (MDB_IDL_UM_SIZE-1)
+
+#define MDB_IDL_SIZEOF(ids) (((ids)[0]+1) * sizeof(MDB_ID))
+#define MDB_IDL_IS_ZERO(ids) ( (ids)[0] == 0 )
+#define MDB_IDL_CPY( dst, src ) (memcpy( dst, src, MDB_IDL_SIZEOF( src ) ))
+#define MDB_IDL_FIRST( ids ) ( (ids)[1] )
+#define MDB_IDL_LAST( ids ) ( (ids)[(ids)[0]] )
+
+ /** Current max length of an #mdb_midl_alloc()ed IDL */
+#define MDB_IDL_ALLOCLEN( ids ) ( (ids)[-1] )
+
+ /** Append ID to IDL. The IDL must be big enough. */
+#define mdb_midl_xappend(idl, id) do { \
+ MDB_ID *xidl = (idl), xlen = ++(xidl[0]); \
+ xidl[xlen] = (id); \
+ } while (0)
+
+ /** Search for an ID in an IDL.
+ * @param[in] ids The IDL to search.
+ * @param[in] id The ID to search for.
+ * @return The index of the first ID greater than or equal to \b id.
+ */
+unsigned mdb_midl_search( MDB_IDL ids, MDB_ID id );
+
+ /** Allocate an IDL.
+ * Allocates memory for an IDL of the given size.
+ * @return IDL on success, NULL on failure.
+ */
+MDB_IDL mdb_midl_alloc(int num);
+
+ /** Free an IDL.
+ * @param[in] ids The IDL to free.
+ */
+void mdb_midl_free(MDB_IDL ids);
+
+ /** Shrink an IDL.
+ * Return the IDL to the default size if it has grown larger.
+ * @param[in,out] idp Address of the IDL to shrink.
+ */
+void mdb_midl_shrink(MDB_IDL *idp);
+
+ /** Make room for num additional elements in an IDL.
+ * @param[in,out] idp Address of the IDL.
+ * @param[in] num Number of elements to make room for.
+ * @return 0 on success, ENOMEM on failure.
+ */
+int mdb_midl_need(MDB_IDL *idp, unsigned num);
+
+ /** Append an ID onto an IDL.
+ * @param[in,out] idp Address of the IDL to append to.
+ * @param[in] id The ID to append.
+ * @return 0 on success, ENOMEM if the IDL is too large.
+ */
+int mdb_midl_append( MDB_IDL *idp, MDB_ID id );
+
+ /** Append an IDL onto an IDL.
+ * @param[in,out] idp Address of the IDL to append to.
+ * @param[in] app The IDL to append.
+ * @return 0 on success, ENOMEM if the IDL is too large.
+ */
+int mdb_midl_append_list( MDB_IDL *idp, MDB_IDL app );
+
+ /** Append an ID range onto an IDL.
+ * @param[in,out] idp Address of the IDL to append to.
+ * @param[in] id The lowest ID to append.
+ * @param[in] n Number of IDs to append.
+ * @return 0 on success, ENOMEM if the IDL is too large.
+ */
+int mdb_midl_append_range( MDB_IDL *idp, MDB_ID id, unsigned n );
+
+ /** Merge an IDL onto an IDL. The destination IDL must be big enough.
+ * @param[in] idl The IDL to merge into.
+ * @param[in] merge The IDL to merge.
+ */
+void mdb_midl_xmerge( MDB_IDL idl, MDB_IDL merge );
+
+ /** Sort an IDL.
+ * @param[in,out] ids The IDL to sort.
+ */
+void mdb_midl_sort( MDB_IDL ids );
+
+ /** An ID2 is an ID/pointer pair.
+ */
+typedef struct MDB_ID2 {
+ MDB_ID mid; /**< The ID */
+ void *mptr; /**< The pointer */
+} MDB_ID2;
+
+ /** An ID2L is an ID2 List, a sorted array of ID2s.
+ * The first element's \b mid member is a count of how many actual
+ * elements are in the array. The \b mptr member of the first element is unused.
+ * The array is sorted in ascending order by \b mid.
+ */
+typedef MDB_ID2 *MDB_ID2L;
+
+ /** Search for an ID in an ID2L.
+ * @param[in] ids The ID2L to search.
+ * @param[in] id The ID to search for.
+ * @return The index of the first ID2 whose \b mid member is greater than or equal to \b id.
+ */
+unsigned mdb_mid2l_search( MDB_ID2L ids, MDB_ID id );
+
+
+ /** Insert an ID2 into a ID2L.
+ * @param[in,out] ids The ID2L to insert into.
+ * @param[in] id The ID2 to insert.
+ * @return 0 on success, -1 if the ID was already present in the ID2L.
+ */
+int mdb_mid2l_insert( MDB_ID2L ids, MDB_ID2 *id );
+
+ /** Append an ID2 into a ID2L.
+ * @param[in,out] ids The ID2L to append into.
+ * @param[in] id The ID2 to append.
+ * @return 0 on success, -2 if the ID2L is too big.
+ */
+int mdb_mid2l_append( MDB_ID2L ids, MDB_ID2 *id );
+
+/** @} */
+/** @} */
+#ifdef __cplusplus
+}
+#endif
+#endif /* _MDB_MIDL_H_ */
diff --git a/src/contrib/macros.h b/src/contrib/macros.h
new file mode 100644
index 0000000..1db2241
--- /dev/null
+++ b/src/contrib/macros.h
@@ -0,0 +1,43 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*!
+ * \brief Common macros.
+ */
+
+#pragma once
+
+/*! \brief Eliminate compiler warning with unused parameters. */
+#define UNUSED(param) (void)(param)
+
+#ifndef MIN
+/*! \brief Type-safe minimum macro. */
+#define MIN(a, b) \
+ ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a < _b ? _a : _b; })
+
+/*! \brief Type-safe maximum macro. */
+#define MAX(a, b) \
+ ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a > _b ? _a : _b; })
+#endif
+
+#ifndef likely
+/*! \brief Optimize for x to be true value. */
+#define likely(x) __builtin_expect((x), 1)
+#endif
+
+#ifndef unlikely
+/*! \brief Optimize for x to be false value. */
+#define unlikely(x) __builtin_expect((x), 0)
+#endif
diff --git a/src/contrib/mempattern.c b/src/contrib/mempattern.c
new file mode 100644
index 0000000..74add91
--- /dev/null
+++ b/src/contrib/mempattern.c
@@ -0,0 +1,122 @@
+/* Copyright (C) 2017 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+
+#include "contrib/mempattern.h"
+#include "contrib/string.h"
+#include "contrib/ucw/mempool.h"
+
+static void mm_nofree(void *p)
+{
+ /* nop */
+}
+
+static void *mm_malloc(void *ctx, size_t n)
+{
+ (void)ctx;
+ return malloc(n);
+}
+
+void *mm_alloc(knot_mm_t *mm, size_t size)
+{
+ if (mm) {
+ return mm->alloc(mm->ctx, size);
+ } else {
+ return malloc(size);
+ }
+}
+
+void *mm_calloc(knot_mm_t *mm, size_t nmemb, size_t size)
+{
+ if (nmemb == 0 || size == 0) {
+ return NULL;
+ }
+ if (mm) {
+ size_t total_size = nmemb * size;
+ if (total_size / nmemb != size) { // Overflow check
+ return NULL;
+ }
+ void *mem = mm_alloc(mm, total_size);
+ if (mem == NULL) {
+ return NULL;
+ }
+ return memzero(mem, total_size);
+ } else {
+ return calloc(nmemb, size);
+ }
+}
+
+void *mm_realloc(knot_mm_t *mm, void *what, size_t size, size_t prev_size)
+{
+ if (mm) {
+ void *p = mm->alloc(mm->ctx, size);
+ if (p == NULL) {
+ return NULL;
+ } else {
+ if (what) {
+ memcpy(p, what,
+ prev_size < size ? prev_size : size);
+ }
+ mm_free(mm, what);
+ return p;
+ }
+ } else {
+ return realloc(what, size);
+ }
+}
+
+char *mm_strdup(knot_mm_t *mm, const char *s)
+{
+ if (s == NULL) {
+ return NULL;
+ }
+ if (mm) {
+ size_t len = strlen(s) + 1;
+ void *mem = mm_alloc(mm, len);
+ if (mem == NULL) {
+ return NULL;
+ }
+ return memcpy(mem, s, len);
+ } else {
+ return strdup(s);
+ }
+}
+
+void mm_free(knot_mm_t *mm, void *what)
+{
+ if (mm) {
+ if (mm->free) {
+ mm->free(what);
+ }
+ } else {
+ free(what);
+ }
+}
+
+void mm_ctx_init(knot_mm_t *mm)
+{
+ mm->ctx = NULL;
+ mm->alloc = mm_malloc;
+ mm->free = free;
+}
+
+void mm_ctx_mempool(knot_mm_t *mm, size_t chunk_size)
+{
+ mm->ctx = mp_new(chunk_size);
+ mm->alloc = (knot_mm_alloc_t)mp_alloc;
+ mm->free = mm_nofree;
+}
diff --git a/src/contrib/mempattern.h b/src/contrib/mempattern.h
new file mode 100644
index 0000000..1c5d9a2
--- /dev/null
+++ b/src/contrib/mempattern.h
@@ -0,0 +1,46 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*!
+ * \brief Memory allocation related functions.
+ */
+
+#pragma once
+
+#include "libknot/mm_ctx.h"
+
+/*! \brief Default memory block size. */
+#define MM_DEFAULT_BLKSIZE 4096
+
+/*! \brief Allocs using 'mm' if any, uses system malloc() otherwise. */
+void *mm_alloc(knot_mm_t *mm, size_t size);
+
+/*! \brief Callocs using 'mm' if any, uses system calloc() otherwise. */
+void *mm_calloc(knot_mm_t *mm, size_t nmemb, size_t size);
+
+/*! \brief Reallocs using 'mm' if any, uses system realloc() otherwise. */
+void *mm_realloc(knot_mm_t *mm, void *what, size_t size, size_t prev_size);
+
+/*! \brief Strdups using 'mm' if any, uses system strdup() otherwise. */
+char *mm_strdup(knot_mm_t *mm, const char *s);
+
+/*! \brief Free using 'mm' if any, uses system free() otherwise. */
+void mm_free(knot_mm_t *mm, void *what);
+
+/*! \brief Initialize default memory allocation context. */
+void mm_ctx_init(knot_mm_t *mm);
+
+/*! \brief Memory pool context. */
+void mm_ctx_mempool(knot_mm_t *mm, size_t chunk_size);
diff --git a/src/contrib/net.c b/src/contrib/net.c
new file mode 100644
index 0000000..81fc8ef
--- /dev/null
+++ b/src/contrib/net.c
@@ -0,0 +1,598 @@
+/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <netinet/in.h>
+#include <poll.h>
+#include <stdbool.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <unistd.h>
+
+#include "libknot/errcode.h"
+#include "contrib/net.h"
+#include "contrib/sockaddr.h"
+
+/*
+ * OS X doesn't support MSG_NOSIGNAL. Use SO_NOSIGPIPE socket option instead.
+ */
+#if defined(__APPLE__) && !defined(MSG_NOSIGNAL)
+# define MSG_NOSIGNAL 0
+# define osx_block_sigpipe(sock) sockopt_enable(sock, SOL_SOCKET, SO_NOSIGPIPE)
+#else
+# define osx_block_sigpipe(sock) KNOT_EOK
+#endif
+
+/*!
+ * \brief Enable socket option.
+ */
+static int sockopt_enable(int sock, int level, int optname)
+{
+ const int enable = 1;
+ if (setsockopt(sock, level, optname, &enable, sizeof(enable)) != 0) {
+ return knot_map_errno();
+ }
+
+ return KNOT_EOK;
+}
+
+/*!
+ * \brief Create a non-blocking socket.
+ *
+ * Prefer SOCK_NONBLOCK if available to save one fcntl() syscall.
+ *
+ */
+static int socket_create(int family, int type, int proto)
+{
+#ifdef SOCK_NONBLOCK
+ type |= SOCK_NONBLOCK;
+#endif
+ int sock = socket(family, type, proto);
+ if (sock < 0) {
+ return knot_map_errno();
+ }
+
+#ifndef SOCK_NONBLOCK
+ if (fcntl(sock, F_SETFL, O_NONBLOCK) != 0) {
+ int ret = knot_map_errno();
+ close(sock);
+ return ret;
+ }
+#endif
+
+ int ret = osx_block_sigpipe(sock);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ return sock;
+}
+
+int net_unbound_socket(int type, const struct sockaddr *sa)
+{
+ if (sa == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ /* Create socket. */
+ return socket_create(sa->sa_family, type, 0);
+}
+
+struct option {
+ int level;
+ int name;
+};
+
+/*!
+ * \brief Get setsock option for binding non-local address.
+ */
+static const struct option *nonlocal_option(int family)
+{
+ static const struct option ipv4 = {
+ #if defined(IP_FREEBIND)
+ IPPROTO_IP, IP_FREEBIND
+ #elif defined(IP_BINDANY)
+ IPPROTO_IP, IP_BINDANY
+ #else
+ 0, 0
+ #endif
+ };
+
+ static const struct option ipv6 = {
+ #if defined(IP_FREEBIND)
+ IPPROTO_IP, IP_FREEBIND
+ #elif defined(IPV6_BINDANY)
+ IPPROTO_IPV6, IPV6_BINDANY
+ #else
+ 0, 0
+ #endif
+
+ };
+
+ switch (family) {
+ case AF_INET: return &ipv4;
+ case AF_INET6: return &ipv6;
+ default:
+ return NULL;
+ }
+}
+
+static int enable_nonlocal(int sock, int family)
+{
+ const struct option *opt = nonlocal_option(family);
+ if (opt == NULL || opt->name == 0) {
+ return KNOT_ENOTSUP;
+ }
+
+ return sockopt_enable(sock, opt->level, opt->name);
+}
+
+static int enable_reuseport(int sock)
+{
+#ifdef ENABLE_REUSEPORT
+ return sockopt_enable(sock, SOL_SOCKET, SO_REUSEPORT);
+#else
+ return KNOT_ENOTSUP;
+#endif
+}
+
+static void unlink_unix_socket(const struct sockaddr *addr)
+{
+ char path[SOCKADDR_STRLEN] = { 0 };
+ sockaddr_tostr(path, sizeof(path), addr);
+ unlink(path);
+}
+
+int net_bound_socket(int type, const struct sockaddr *sa, enum net_flags flags)
+{
+ /* Create socket. */
+ int sock = net_unbound_socket(type, sa);
+ if (sock < 0) {
+ return sock;
+ }
+
+ /* Unlink UNIX sock if exists. */
+ if (sa->sa_family == AF_UNIX) {
+ unlink_unix_socket(sa);
+ }
+
+ /* Reuse old address if taken. */
+ int ret = sockopt_enable(sock, SOL_SOCKET, SO_REUSEADDR);
+ if (ret != KNOT_EOK) {
+ close(sock);
+ return ret;
+ }
+
+ /* Don't bind IPv4 for IPv6 any address. */
+ if (sa->sa_family == AF_INET6) {
+ ret = sockopt_enable(sock, IPPROTO_IPV6, IPV6_V6ONLY);
+ if (ret != KNOT_EOK) {
+ close(sock);
+ return ret;
+ }
+ }
+
+ /* Allow bind to non-local address. */
+ if (flags & NET_BIND_NONLOCAL) {
+ ret = enable_nonlocal(sock, sa->sa_family);
+ if (ret != KNOT_EOK) {
+ close(sock);
+ return ret;
+ }
+ }
+
+ /* Allow to bind the same address by multiple threads. */
+ if (flags & NET_BIND_MULTIPLE) {
+ ret = enable_reuseport(sock);
+ if (ret != KNOT_EOK) {
+ close(sock);
+ return ret;
+ }
+ }
+
+ /* Bind to specified address. */
+ ret = bind(sock, sa, sockaddr_len(sa));
+ if (ret < 0) {
+ ret = knot_map_errno();
+ close(sock);
+ return ret;
+ }
+
+ return sock;
+}
+
+int net_connected_socket(int type, const struct sockaddr *dst_addr,
+ const struct sockaddr *src_addr)
+{
+ if (dst_addr == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ /* Check port. */
+ if (sockaddr_port(dst_addr) == 0) {
+ return KNOT_NET_EADDR;
+ }
+
+ /* Bind to specific source address - if set. */
+ int sock = -1;
+ if (src_addr && src_addr->sa_family != AF_UNSPEC) {
+ sock = net_bound_socket(type, src_addr, 0);
+ } else {
+ sock = net_unbound_socket(type, dst_addr);
+ }
+ if (sock < 0) {
+ return sock;
+ }
+
+ /* Connect to destination. */
+ const struct sockaddr *sa = (const struct sockaddr *)dst_addr;
+ int ret = connect(sock, sa, sockaddr_len(sa));
+ if (ret != 0 && errno != EINPROGRESS) {
+ ret = knot_map_errno();
+ close(sock);
+ return ret;
+ }
+
+ return sock;
+}
+
+bool net_is_connected(int sock)
+{
+ struct sockaddr_storage ss;
+ socklen_t len = sizeof(ss);
+ return (getpeername(sock, (struct sockaddr *)&ss, &len) == 0);
+}
+
+int net_socktype(int sock)
+{
+ int type;
+ socklen_t size = sizeof(type);
+
+ if (getsockopt(sock, SOL_SOCKET, SO_TYPE, &type, &size) == 0) {
+ return type;
+ } else {
+ return AF_UNSPEC;
+ }
+}
+
+bool net_is_stream(int sock)
+{
+ return net_socktype(sock) == SOCK_STREAM;
+}
+
+int net_accept(int sock, struct sockaddr_storage *addr)
+{
+ socklen_t sa_len = sizeof(*addr);
+
+ int remote = -1;
+
+#if defined(HAVE_ACCEPT4) && defined(SOCK_NONBLOCK)
+ remote = accept4(sock, (struct sockaddr *)addr, &sa_len, SOCK_NONBLOCK);
+ if (remote < 0) {
+ return knot_map_errno();
+ }
+#else
+ remote = accept(sock, (struct sockaddr *)addr, &sa_len);
+ if (fcntl(remote, F_SETFL, O_NONBLOCK) != 0) {
+ int error = knot_map_errno();
+ close(remote);
+ return error;
+ }
+#endif
+
+ return remote;
+}
+
+/* -- I/O interface handling partial -------------------------------------- */
+
+/*!
+ * \brief Perform \a poll() on one socket.
+ */
+static int poll_one(int fd, int events, int timeout_ms)
+{
+ struct pollfd pfd = {
+ .fd = fd,
+ .events = events
+ };
+
+ return poll(&pfd, 1, timeout_ms);
+}
+
+/*!
+ * \brief Check if we should wait for I/O readiness.
+ *
+ * \param error \a errno set by the failed I/O operation.
+ */
+static bool io_should_wait(int error)
+{
+ /* socket data not ready */
+ if (error == EAGAIN || error == EWOULDBLOCK) {
+ return true;
+ }
+
+#ifndef __linux__
+ /* FreeBSD: connection in progress */
+ if (error == ENOTCONN) {
+ return true;
+ }
+#endif
+
+ return false;
+}
+
+/*!
+ * \brief I/O operation callbacks.
+ */
+struct io {
+ ssize_t (*process)(int sockfd, struct msghdr *msg);
+ int (*wait)(int sockfd, int timeout_ms);
+};
+
+/*!
+ * \brief Get total size of I/O vector in a message.
+ */
+static size_t msg_iov_len(const struct msghdr *msg)
+{
+ size_t total = 0;
+
+ for (int i = 0; i < msg->msg_iovlen; i++) {
+ total += msg->msg_iov[i].iov_len;
+ }
+
+ return total;
+}
+
+/*!
+ * \brief Shift processed data out of message IO vectors.
+ */
+static void msg_iov_shift(struct msghdr *msg, size_t done)
+{
+ struct iovec *iov = msg->msg_iov;
+ int iovlen = msg->msg_iovlen;
+
+ for (int i = 0; i < iovlen && done > 0; i++) {
+ if (iov[i].iov_len > done) {
+ iov[i].iov_base += done;
+ iov[i].iov_len -= done;
+ done = 0;
+ } else {
+ done -= iov[i].iov_len;
+ msg->msg_iov += 1;
+ msg->msg_iovlen -= 1;
+ }
+ }
+
+ assert(done == 0);
+}
+
+/*!
+ * \brief Perform an I/O operation with a socket with waiting.
+ *
+ * \param oneshot If set, doesn't wait until the buffer is fully processed.
+ *
+ */
+static ssize_t io_exec(const struct io *io, int fd, struct msghdr *msg,
+ bool oneshot, int timeout_ms)
+{
+ size_t done = 0;
+ size_t total = msg_iov_len(msg);
+
+ for (;;) {
+ /* Perform I/O. */
+ ssize_t ret = io->process(fd, msg);
+ if (ret == -1 && errno == EINTR) {
+ continue;
+ }
+ if (ret > 0) {
+ done += ret;
+ if (oneshot || done == total) {
+ break;
+ }
+ msg_iov_shift(msg, ret);
+ }
+
+ /* Wait for data readiness. */
+ if (ret > 0 || (ret == -1 && io_should_wait(errno))) {
+ do {
+ ret = io->wait(fd, timeout_ms);
+ } while (ret == -1 && errno == EINTR);
+ if (ret == 1) {
+ continue;
+ } else if (ret == 0) {
+ return KNOT_ETIMEOUT;
+ }
+ }
+
+ /* Disconnected or error. */
+ return KNOT_ECONN;
+ }
+
+ return done;
+}
+
+static ssize_t recv_process(int fd, struct msghdr *msg)
+{
+ return recvmsg(fd, msg, MSG_DONTWAIT | MSG_NOSIGNAL);
+}
+
+static int recv_wait(int fd, int timeout_ms)
+{
+ return poll_one(fd, POLLIN, timeout_ms);
+}
+
+static ssize_t recv_data(int sock, struct msghdr *msg, bool oneshot, int timeout_ms)
+{
+ static const struct io RECV_IO = {
+ .process = recv_process,
+ .wait = recv_wait
+ };
+
+ return io_exec(&RECV_IO, sock, msg, oneshot, timeout_ms);
+}
+
+static ssize_t send_process(int fd, struct msghdr *msg)
+{
+ return sendmsg(fd, msg, MSG_NOSIGNAL);
+}
+
+static int send_wait(int fd, int timeout_ms)
+{
+ return poll_one(fd, POLLOUT, timeout_ms);
+}
+
+static ssize_t send_data(int sock, struct msghdr *msg, int timeout_ms)
+{
+ static const struct io SEND_IO = {
+ .process = send_process,
+ .wait = send_wait
+ };
+
+ return io_exec(&SEND_IO, sock, msg, false, timeout_ms);
+}
+
+/* -- generic stream and datagram I/O -------------------------------------- */
+
+ssize_t net_send(int sock, const uint8_t *buffer, size_t size,
+ const struct sockaddr *addr, int timeout_ms)
+{
+ if (sock < 0 || buffer == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ struct iovec iov = { 0 };
+ iov.iov_base = (void *)buffer;
+ iov.iov_len = size;
+
+ struct msghdr msg = { 0 };
+ msg.msg_name = (void *)addr;
+ msg.msg_namelen = sockaddr_len(addr);
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+
+ int ret = send_data(sock, &msg, timeout_ms);
+ if (ret < 0) {
+ return ret;
+ } else if (ret != size) {
+ return KNOT_ECONN;
+ }
+
+ return ret;
+}
+
+ssize_t net_recv(int sock, uint8_t *buffer, size_t size,
+ struct sockaddr_storage *addr, int timeout_ms)
+{
+ if (sock < 0 || buffer == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ struct iovec iov = { 0 };
+ iov.iov_base = buffer;
+ iov.iov_len = size;
+
+ struct msghdr msg = { 0 };
+ msg.msg_name = (void *)addr;
+ msg.msg_namelen = addr ? sizeof(*addr) : 0;
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+
+ return recv_data(sock, &msg, true, timeout_ms);
+}
+
+ssize_t net_dgram_send(int sock, const uint8_t *buffer, size_t size,
+ const struct sockaddr *addr)
+{
+ return net_send(sock, buffer, size, addr, 0);
+}
+
+ssize_t net_dgram_recv(int sock, uint8_t *buffer, size_t size, int timeout_ms)
+{
+ return net_recv(sock, buffer, size, NULL, timeout_ms);
+}
+
+ssize_t net_stream_send(int sock, const uint8_t *buffer, size_t size, int timeout_ms)
+{
+ return net_send(sock, buffer, size, NULL, timeout_ms);
+}
+
+ssize_t net_stream_recv(int sock, uint8_t *buffer, size_t size, int timeout_ms)
+{
+ return net_recv(sock, buffer, size, NULL, timeout_ms);
+}
+
+/* -- DNS specific I/O ----------------------------------------------------- */
+
+ssize_t net_dns_tcp_send(int sock, const uint8_t *buffer, size_t size, int timeout_ms)
+{
+ if (sock < 0 || buffer == NULL || size > UINT16_MAX) {
+ return KNOT_EINVAL;
+ }
+
+ struct iovec iov[2];
+ uint16_t pktsize = htons(size);
+ iov[0].iov_base = &pktsize;
+ iov[0].iov_len = sizeof(uint16_t);
+ iov[1].iov_base = (void *)buffer;
+ iov[1].iov_len = size;
+
+ struct msghdr msg = { 0 };
+ msg.msg_iov = iov;
+ msg.msg_iovlen = 2;
+
+ ssize_t ret = send_data(sock, &msg, timeout_ms);
+ if (ret < 0) {
+ return ret;
+ }
+
+ return size; /* Do not count the size prefix. */
+}
+
+ssize_t net_dns_tcp_recv(int sock, uint8_t *buffer, size_t size, int timeout_ms)
+{
+ if (sock < 0 || buffer == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ struct iovec iov = { 0 };
+ struct msghdr msg = { 0 };
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+
+ /* Receive size. */
+ uint16_t pktsize = 0;
+ iov.iov_base = &pktsize;
+ iov.iov_len = sizeof(pktsize);
+ int ret = recv_data(sock, &msg, false, timeout_ms);
+ if (ret != sizeof(pktsize)) {
+ return ret;
+ }
+
+ pktsize = ntohs(pktsize);
+
+ /* Check packet size */
+ if (size < pktsize) {
+ return KNOT_ESPACE;
+ }
+
+ /* Receive payload. */
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ iov.iov_base = buffer;
+ iov.iov_len = pktsize;
+ return recv_data(sock, &msg, false, timeout_ms);
+}
diff --git a/src/contrib/net.h b/src/contrib/net.h
new file mode 100644
index 0000000..1b6b527
--- /dev/null
+++ b/src/contrib/net.h
@@ -0,0 +1,185 @@
+/* Copyright (C) 2015 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#pragma once
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <sys/socket.h>
+
+/*!
+ * \brief Network interface flags.
+ */
+enum net_flags {
+ NET_BIND_NONLOCAL = (1 << 0), //!< Allow to bind unavailable address.
+ NET_BIND_MULTIPLE = (1 << 1), //!< Allow to bind address multiple times.
+};
+
+/*!
+ * \brief Create unbound socket of given family and type.
+ *
+ * \note The socket is set to non-blocking mode.
+ *
+ * \param type Socket transport type (SOCK_STREAM, SOCK_DGRAM).
+ * \param sa Socket address.
+ *
+ * \return socket or error code
+ */
+int net_unbound_socket(int type, const struct sockaddr *sa);
+
+/*!
+ * \brief Create socket bound to given address.
+ *
+ * The socket is set to non-blocking mode.
+ *
+ * \param type Socket transport type (SOCK_STREAM, SOCK_DGRAM).
+ * \param sa Socket address.
+ * \param flags Socket binding options.
+ *
+ * \return socket or error code
+ */
+int net_bound_socket(int type, const struct sockaddr *sa, enum net_flags flags);
+
+/*!
+ * \brief Create socket connected (asynchronously) to destination address.
+ *
+ * \note The socket is set to non-blocking mode.
+ *
+ * \param type Socket transport type (SOCK_STREAM, SOCK_DGRAM).
+ * \param dst_addr Destination address.
+ * \param src_addr Source address (can be NULL).
+ *
+ * \return socket or error code
+ */
+int net_connected_socket(int type, const struct sockaddr *dst_addr,
+ const struct sockaddr *src_addr);
+
+/*!
+ * \brief Return true if the socket is fully connected.
+ *
+ * \param sock Socket.
+ *
+ * \return true if connected
+ */
+bool net_is_connected(int sock);
+
+/*!
+ * \brief Get socket type (e.g. \a SOCK_STREAM).
+ *
+ * \param sock Socket.
+ */
+int net_socktype(int sock);
+
+/*!
+ * \brief Check if socket is a SOCK_STREAM socket.
+ */
+bool net_is_stream(int sock);
+
+/*!
+ * \brief Accept a connection on a listening socket.
+ *
+ * \brief The socket is set to non-blocking mode.
+ *
+ * \param sock Socket
+ * \param addr Remote address (can be NULL).
+ *
+ * \return socket or error code
+ */
+int net_accept(int sock, struct sockaddr_storage *addr);
+
+/*!
+ * \brief Send a message on a socket.
+ *
+ * The socket can be SOCK_STREAM or SOCK_DGRAM.
+ *
+ * The implementation handles partial-writes automatically.
+ *
+ * \param[in] sock Socket.
+ * \param[in] buffer Message buffer.
+ * \param[in] size Size of the message.
+ * \param[in] addr Remote address (ignored for SOCK_STREAM).
+ * \param[in] timeout_ms Write timeout in miliseconds (-1 for infinity,
+ * not valid for SOCK_DGRAM).
+ *
+ * \return Number of bytes sent or negative error code.
+ */
+ssize_t net_send(int sock, const uint8_t *buffer, size_t size,
+ const struct sockaddr *addr, int timeout_ms);
+
+/*!
+ * \brief Receive a message from a socket.
+ *
+ * \param[in] sock Socket.
+ * \param[out] buffer Receiving buffer.
+ * \param[in] size Capacity of the receiving buffer.
+ * \param[out] addr Remote address (can be NULL).
+ * \param[in] timeout_ms Read timeout in miliseconds (-1 for infinity).
+ *
+ * \return Number of bytes read or negative error code.
+ */
+ssize_t net_recv(int sock, uint8_t *buffer, size_t size,
+ struct sockaddr_storage *addr, int timeout_ms);
+
+/*!
+ * \brief Send a message on a SOCK_DGRAM socket.
+ *
+ * \see net_send
+ */
+ssize_t net_dgram_send(int sock, const uint8_t *buffer, size_t size,
+ const struct sockaddr *addr);
+
+/*!
+ * \brief Receive a message from a SOCK_DGRAM socket.
+ *
+ * \see net_recv
+ */
+ssize_t net_dgram_recv(int sock, uint8_t *buffer, size_t size, int timeout_ms);
+
+/*!
+ * \brief Send a message on a SOCK_STREAM socket.
+ *
+ * \see net_send
+ */
+ssize_t net_stream_send(int sock, const uint8_t *buffer, size_t size, int timeout_ms);
+
+/*!
+ * \brief Receive a message from a SOCK_STREAM socket.
+ *
+ * \see net_recv
+ */
+ssize_t net_stream_recv(int sock, uint8_t *buffer, size_t size, int timeout_ms);
+
+/*!
+ * \brief Send a DNS message on a TCP socket.
+ *
+ * The outgoing message is prefixed with a two-byte value carrying the DNS
+ * message size according to the specification. These two bytes are not
+ * reflected in the return value.
+ *
+ * \see net_send
+ */
+ssize_t net_dns_tcp_send(int sock, const uint8_t *buffer, size_t size, int timeout_ms);
+
+/*!
+ * \brief Receive a DNS message from a TCP socket.
+ *
+ * The first two bytes of the incoming message are interpreted as a DNS message
+ * size according to the specification. These two bytes are not included in
+ * the returned size. Only a complete DNS message is retrieved.
+ *
+ * \see net_recv
+ */
+ssize_t net_dns_tcp_recv(int sock, uint8_t *buffer, size_t size, int timeout_ms);
diff --git a/src/contrib/openbsd/LICENSE b/src/contrib/openbsd/LICENSE
new file mode 100644
index 0000000..e9a1aaa
--- /dev/null
+++ b/src/contrib/openbsd/LICENSE
@@ -0,0 +1,2 @@
+../licenses/0BSD
+../licenses/BSD-3-Clause \ No newline at end of file
diff --git a/src/contrib/openbsd/siphash.c b/src/contrib/openbsd/siphash.c
new file mode 100644
index 0000000..26b8cfc
--- /dev/null
+++ b/src/contrib/openbsd/siphash.c
@@ -0,0 +1,176 @@
+/* $OpenBSD: siphash.c,v 1.6 2017/04/12 17:41:49 deraadt Exp $ */
+
+/*-
+ * Copyright (c) 2013 Andre Oppermann <andre@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * SipHash is a family of PRFs SipHash-c-d where the integer parameters c and d
+ * are the number of compression rounds and the number of finalization rounds.
+ * A compression round is identical to a finalization round and this round
+ * function is called SipRound. Given a 128-bit key k and a (possibly empty)
+ * byte string m, SipHash-c-d returns a 64-bit value SipHash-c-d(k; m).
+ *
+ * Implemented from the paper "SipHash: a fast short-input PRF", 2012.09.18,
+ * by Jean-Philippe Aumasson and Daniel J. Bernstein,
+ * Permanent Document ID b9a943a805fbfc6fde808af9fc0ecdfa
+ * https://131002.net/siphash/siphash.pdf
+ * https://131002.net/siphash/
+ */
+
+#include <string.h>
+
+#include "libknot/endian.h"
+#include "contrib/string.h"
+#include "contrib/openbsd/siphash.h"
+
+static void SipHash_CRounds(SIPHASH_CTX *, int);
+static void SipHash_Rounds(SIPHASH_CTX *, int);
+
+void
+SipHash_Init(SIPHASH_CTX *ctx, const SIPHASH_KEY *key)
+{
+ uint64_t k0, k1;
+
+ k0 = le64toh(key->k0);
+ k1 = le64toh(key->k1);
+
+ ctx->v[0] = 0x736f6d6570736575ULL ^ k0;
+ ctx->v[1] = 0x646f72616e646f6dULL ^ k1;
+ ctx->v[2] = 0x6c7967656e657261ULL ^ k0;
+ ctx->v[3] = 0x7465646279746573ULL ^ k1;
+
+ memset(ctx->buf, 0, sizeof(ctx->buf));
+ ctx->bytes = 0;
+}
+
+void
+SipHash_Update(SIPHASH_CTX *ctx, int rc, int rf, const void *src, size_t len)
+{
+ const uint8_t *ptr = src;
+ size_t left, used;
+
+ if (len == 0)
+ return;
+
+ used = ctx->bytes % sizeof(ctx->buf);
+ ctx->bytes += len;
+
+ if (used > 0) {
+ left = sizeof(ctx->buf) - used;
+
+ if (len >= left) {
+ memcpy(&ctx->buf[used], ptr, left);
+ SipHash_CRounds(ctx, rc);
+ len -= left;
+ ptr += left;
+ } else {
+ memcpy(&ctx->buf[used], ptr, len);
+ return;
+ }
+ }
+
+ while (len >= sizeof(ctx->buf)) {
+ memcpy(ctx->buf, ptr, sizeof(ctx->buf));
+ SipHash_CRounds(ctx, rc);
+ len -= sizeof(ctx->buf);
+ ptr += sizeof(ctx->buf);
+ }
+
+ if (len > 0)
+ memcpy(&ctx->buf, ptr, len);
+}
+
+uint64_t
+SipHash_End(SIPHASH_CTX *ctx, int rc, int rf)
+{
+ uint64_t r;
+ size_t left, used;
+
+ used = ctx->bytes % sizeof(ctx->buf);
+ left = sizeof(ctx->buf) - used;
+ memset(&ctx->buf[used], 0, left - 1);
+ ctx->buf[7] = ctx->bytes;
+
+ SipHash_CRounds(ctx, rc);
+ ctx->v[2] ^= 0xff;
+ SipHash_Rounds(ctx, rf);
+
+ r = (ctx->v[0] ^ ctx->v[1]) ^ (ctx->v[2] ^ ctx->v[3]);
+ memzero(ctx, sizeof(*ctx));
+ return htole64(r);
+}
+
+uint64_t
+SipHash(const SIPHASH_KEY *key, int rc, int rf, const void *src, size_t len)
+{
+ SIPHASH_CTX ctx;
+
+ SipHash_Init(&ctx, key);
+ SipHash_Update(&ctx, rc, rf, src, len);
+ return (SipHash_End(&ctx, rc, rf));
+}
+
+#define SIP_ROTL(x, b) ((x) << (b)) | ( (x) >> (64 - (b)))
+
+static void
+SipHash_Rounds(SIPHASH_CTX *ctx, int rounds)
+{
+ while (rounds--) {
+ ctx->v[0] += ctx->v[1];
+ ctx->v[2] += ctx->v[3];
+ ctx->v[1] = SIP_ROTL(ctx->v[1], 13);
+ ctx->v[3] = SIP_ROTL(ctx->v[3], 16);
+
+ ctx->v[1] ^= ctx->v[0];
+ ctx->v[3] ^= ctx->v[2];
+ ctx->v[0] = SIP_ROTL(ctx->v[0], 32);
+
+ ctx->v[2] += ctx->v[1];
+ ctx->v[0] += ctx->v[3];
+ ctx->v[1] = SIP_ROTL(ctx->v[1], 17);
+ ctx->v[3] = SIP_ROTL(ctx->v[3], 21);
+
+ ctx->v[1] ^= ctx->v[2];
+ ctx->v[3] ^= ctx->v[0];
+ ctx->v[2] = SIP_ROTL(ctx->v[2], 32);
+ }
+}
+
+static void
+SipHash_CRounds(SIPHASH_CTX *ctx, int rounds)
+{
+ uint64_t tmp;
+
+ memcpy(&tmp, ctx->buf, sizeof(tmp));
+ uint64_t m = le64toh(tmp);
+
+ ctx->v[3] ^= m;
+ SipHash_Rounds(ctx, rounds);
+ ctx->v[0] ^= m;
+}
diff --git a/src/contrib/openbsd/siphash.h b/src/contrib/openbsd/siphash.h
new file mode 100644
index 0000000..d551fe8
--- /dev/null
+++ b/src/contrib/openbsd/siphash.h
@@ -0,0 +1,83 @@
+/*-
+ * Copyright (c) 2013 Andre Oppermann <andre@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $OpenBSD: siphash.h,v 1.3 2015/02/20 11:51:03 tedu Exp $
+ */
+
+/*
+ * SipHash is a family of pseudorandom functions (a.k.a. keyed hash functions)
+ * optimized for speed on short messages returning a 64bit hash/digest value.
+ *
+ * The number of rounds is defined during the initialization:
+ * SipHash24_Init() for the fast and resonable strong version
+ * SipHash48_Init() for the strong version (half as fast)
+ *
+ * struct SIPHASH_CTX ctx;
+ * SipHash24_Init(&ctx);
+ * SipHash_SetKey(&ctx, "16bytes long key");
+ * SipHash_Update(&ctx, pointer_to_string, length_of_string);
+ * SipHash_End(&ctx);
+ */
+
+#ifndef _SIPHASH_H_
+#define _SIPHASH_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#define SIPHASH_BLOCK_LENGTH 8
+#define SIPHASH_KEY_LENGTH 16
+#define SIPHASH_DIGEST_LENGTH 8
+
+typedef struct _SIPHASH_CTX {
+ uint64_t v[4];
+ uint8_t buf[SIPHASH_BLOCK_LENGTH];
+ uint32_t bytes;
+} SIPHASH_CTX;
+
+typedef struct {
+ uint64_t k0;
+ uint64_t k1;
+} SIPHASH_KEY;
+
+void SipHash_Init(SIPHASH_CTX *, const SIPHASH_KEY *);
+void SipHash_Update(SIPHASH_CTX *, int, int, const void *, size_t);
+uint64_t SipHash_End(SIPHASH_CTX *, int, int);
+uint64_t SipHash(const SIPHASH_KEY *, int, int, const void *, size_t);
+
+#define SipHash24_Init(_c, _k) SipHash_Init((_c), (_k))
+#define SipHash24_Update(_c, _p, _l) SipHash_Update((_c), 2, 4, (_p), (_l))
+#define SipHash24_End(_d) SipHash_End((_d), 2, 4)
+#define SipHash24(_k, _p, _l) SipHash((_k), 2, 4, (_p), (_l))
+
+#define SipHash48_Init(_c, _k) SipHash_Init((_c), (_k))
+#define SipHash48_Update(_c, _p, _l) SipHash_Update((_c), 4, 8, (_p), (_l))
+#define SipHash48_End(_d) SipHash_End((_d), 4, 8)
+#define SipHash48(_k, _p, _l) SipHash((_k), 4, 8, (_p), (_l))
+
+#endif /* _SIPHASH_H_ */
diff --git a/src/contrib/openbsd/strlcat.c b/src/contrib/openbsd/strlcat.c
new file mode 100644
index 0000000..1409062
--- /dev/null
+++ b/src/contrib/openbsd/strlcat.c
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <string.h>
+
+#include "contrib/openbsd/strlcat.h"
+
+size_t
+knot_strlcat(char *dst, const char *src, size_t siz)
+{
+ char *d = dst;
+ const char *s = src;
+ size_t n = siz;
+ size_t dlen;
+
+ /* Find the end of dst and adjust bytes left but don't go past end */
+ while (n-- != 0 && *d != '\0')
+ d++;
+ dlen = d - dst;
+ n = siz - dlen;
+
+ if (n == 0)
+ return(dlen + strlen(s));
+ while (*s != '\0') {
+ if (n != 1) {
+ *d++ = *s;
+ n--;
+ }
+ s++;
+ }
+ *d = '\0';
+
+ return(dlen + (s - src)); /* count does not include NUL */
+}
diff --git a/src/contrib/openbsd/strlcat.h b/src/contrib/openbsd/strlcat.h
new file mode 100644
index 0000000..7016069
--- /dev/null
+++ b/src/contrib/openbsd/strlcat.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#pragma once
+
+#ifndef HAVE_STRLCAT
+#define strlcat(dst, src, size) knot_strlcat(dst, src, size)
+#endif
+
+/*
+ * Appends src to string dst of size siz (unlike strncat, siz is the
+ * full size of dst, not space left). At most siz-1 characters
+ * will be copied. Always NUL terminates (unless siz <= strlen(dst)).
+ * Returns strlen(src) + MIN(siz, strlen(initial dst)).
+ * If retval >= siz, truncation occurred.
+ */
+size_t
+knot_strlcat(char *dst, const char *src, size_t siz);
diff --git a/src/contrib/openbsd/strlcpy.c b/src/contrib/openbsd/strlcpy.c
new file mode 100644
index 0000000..eafc0e4
--- /dev/null
+++ b/src/contrib/openbsd/strlcpy.c
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <string.h>
+
+#include "contrib/openbsd/strlcpy.h"
+
+size_t
+knot_strlcpy(char *dst, const char *src, size_t siz)
+{
+ char *d = dst;
+ const char *s = src;
+ size_t n = siz;
+
+ /* Copy as many bytes as will fit */
+ if (n != 0) {
+ while (--n != 0) {
+ if ((*d++ = *s++) == '\0')
+ break;
+ }
+ }
+
+ /* Not enough room in dst, add NUL and traverse rest of src */
+ if (n == 0) {
+ if (siz != 0)
+ *d = '\0'; /* NUL-terminate dst */
+ while (*s++)
+ ;
+ }
+
+ return(s - src - 1); /* count does not include NUL */
+}
diff --git a/src/contrib/openbsd/strlcpy.h b/src/contrib/openbsd/strlcpy.h
new file mode 100644
index 0000000..6421068
--- /dev/null
+++ b/src/contrib/openbsd/strlcpy.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#pragma once
+
+#ifndef HAVE_STRLCPY
+#define strlcpy(dst, src, size) knot_strlcpy(dst, src, size)
+#endif
+
+/*
+ * Copy src to string dst of size siz. At most siz-1 characters
+ * will be copied. Always NUL terminates (unless siz == 0).
+ * Returns strlen(src); if retval >= siz, truncation occurred.
+ */
+size_t
+knot_strlcpy(char *dst, const char *src, size_t siz);
diff --git a/src/contrib/qp-trie/trie.c b/src/contrib/qp-trie/trie.c
new file mode 100644
index 0000000..10f9919
--- /dev/null
+++ b/src/contrib/qp-trie/trie.c
@@ -0,0 +1,835 @@
+/* Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ The code originated from https://github.com/fanf2/qp/blob/master/qp.c
+ at revision 5f6d93753.
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "contrib/qp-trie/trie.h"
+#include "contrib/macros.h"
+#include "contrib/mempattern.h"
+#include "libknot/errcode.h"
+
+#if defined(__i386) || defined(__x86_64) || defined(_M_IX86) \
+ || (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN) \
+ && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
+
+ /*!
+ * \brief Use a pointer alignment hack to save memory.
+ *
+ * When on, isbranch() relies on the fact that in leaf_t the first pointer
+ * is aligned on multiple of 4 bytes and that the flags bitfield is
+ * overlaid over the lowest two bits of that pointer.
+ * Neither is really guaranteed by the C standards; the second part should
+ * be OK with x86_64 ABI and most likely any other little-endian platform.
+ * It would be possible to manipulate the right bits portably, but it would
+ * complicate the code nontrivially. C++ doesn't even guarantee type-punning.
+ * In debug mode we check this works OK when creating a new trie instance.
+ */
+ #define FLAGS_HACK 1
+#else
+ #define FLAGS_HACK 0
+#endif
+
+typedef unsigned char byte;
+typedef unsigned int uint;
+typedef uint bitmap_t; /*! Bit-maps, using the range of 1<<0 to 1<<16 (inclusive). */
+
+typedef struct {
+ uint32_t len; // 32 bits are enough for key lengths; probably even 16 bits would be.
+ char chars[];
+} tkey_t;
+
+/*! \brief Leaf of trie. */
+typedef struct {
+ #if !FLAGS_HACK
+ byte flags;
+ #endif
+ tkey_t *key; /*!< The pointer must be aligned to 4-byte multiples! */
+ trie_val_t val;
+} leaf_t;
+
+/*! \brief A trie node is either leaf_t or branch_t. */
+typedef union node node_t;
+
+/*!
+ * \brief Branch node of trie.
+ *
+ * - The flags distinguish whether the node is a leaf_t (0), or a branch
+ * testing the more-important nibble (1) or the less-important one (2).
+ * - It stores the index of the byte that the node tests. The combined
+ * value (index*4 + flags) increases in branch nodes as you go deeper
+ * into the trie. All the keys below a branch are identical up to the
+ * nibble identified by the branch. Indices have to be stored because
+ * we skip any branch nodes that would have a single child.
+ * (Consequently, the skipped parts of key have to be validated in a leaf.)
+ * - The bitmap indicates which subtries are present. The present child nodes
+ * are stored in the twigs array (with no holes between them).
+ * - To simplify storing keys that are prefixes of each other, the end-of-string
+ * position is treated as another nibble value, ordered before all others.
+ * That affects the bitmap and twigs fields.
+ *
+ * \note The branch nodes are never allocated individually, but they are
+ * always part of either the root node or the twigs array of the parent.
+ */
+typedef struct {
+ #if FLAGS_HACK
+ uint32_t flags : 2,
+ bitmap : 17; /*!< The first bitmap bit is for end-of-string child. */
+ #else
+ byte flags;
+ uint32_t bitmap;
+ #endif
+ uint32_t index;
+ node_t *twigs;
+} branch_t;
+
+union node {
+ leaf_t leaf;
+ branch_t branch;
+};
+
+struct trie {
+ node_t root; // undefined when weight == 0, see empty_root()
+ size_t weight;
+ knot_mm_t mm;
+};
+
+/*! \brief Make the root node empty (debug-only). */
+static inline void empty_root(node_t *root) {
+#ifndef NDEBUG
+ *root = (node_t){ .branch = {
+ .flags = 3, // invalid value that fits
+ .bitmap = 0,
+ .index = -1,
+ .twigs = NULL
+ } };
+#endif
+}
+
+/*! \brief Check that unportable code works OK (debug-only). */
+static void assert_portability(void) {
+#if FLAGS_HACK
+ assert(((union node){ .leaf = {
+ .key = ((void *)NULL) + 1,
+ .val = NULL
+ } }).branch.flags == 1);
+#endif
+}
+
+/*! \brief Propagate error codes. */
+#define ERR_RETURN(x) \
+ do { \
+ int err_code_ = x; \
+ if (unlikely(err_code_ != KNOT_EOK)) \
+ return err_code_; \
+ } while (false)
+
+/*!
+ * \brief Count the number of set bits.
+ *
+ * \TODO This implementation may be relatively slow on some HW.
+ */
+static uint bitmap_weight(bitmap_t w)
+{
+ assert((w & ~((1 << 17) - 1)) == 0); // using the least-important 17 bits
+ return __builtin_popcount(w);
+}
+
+/*! \brief Test flags to determine type of this node. */
+static bool isbranch(const node_t *t)
+{
+ uint f = t->branch.flags;
+ assert(f <= 2);
+ return f != 0;
+}
+
+/*! \brief Make a bitmask for testing a branch bitmap. */
+static bitmap_t nibbit(byte k, uint flags)
+{
+ uint shift = (2 - flags) << 2;
+ uint nibble = (k >> shift) & 0xf;
+ return 1 << (nibble + 1/*because of prefix keys*/);
+}
+
+/*! \brief Extract a nibble from a key and turn it into a bitmask. */
+static bitmap_t twigbit(node_t *t, const char *key, uint32_t len)
+{
+ assert(isbranch(t));
+ uint i = t->branch.index;
+
+ if (i >= len)
+ return 1 << 0; // leaf position
+
+ return nibbit((byte)key[i], t->branch.flags);
+}
+
+/*! \brief Test if a branch node has a child indicated by a bitmask. */
+static bool hastwig(node_t *t, bitmap_t bit)
+{
+ assert(isbranch(t));
+ return t->branch.bitmap & bit;
+}
+
+/*! \brief Compute offset of an existing child in a branch node. */
+static uint twigoff(node_t *t, bitmap_t b)
+{
+ assert(isbranch(t));
+ return bitmap_weight(t->branch.bitmap & (b - 1));
+}
+
+/*! \brief Get pointer to a particular child of a branch node. */
+static node_t* twig(node_t *t, uint i)
+{
+ assert(isbranch(t));
+ return &t->branch.twigs[i];
+}
+
+/*!
+ * \brief For a branch nod, compute offset of a child and child count.
+ *
+ * Having this separate might be meaningful for performance optimization.
+ */
+#define TWIGOFFMAX(off, max, t, b) do { \
+ off = twigoff(t, b); \
+ max = bitmap_weight(t->branch.bitmap); \
+ } while(0)
+
+/*! \brief Simple string comparator. */
+static int key_cmp(const char *k1, uint32_t k1_len, const char *k2, uint32_t k2_len)
+{
+ int ret = memcmp(k1, k2, MIN(k1_len, k2_len));
+ if (ret != 0) {
+ return ret;
+ }
+
+ /* Key string is equal, compare lengths. */
+ if (k1_len == k2_len) {
+ return 0;
+ } else if (k1_len < k2_len) {
+ return -1;
+ } else {
+ return 1;
+ }
+}
+
+trie_t* trie_create(knot_mm_t *mm)
+{
+ assert_portability();
+ trie_t *trie = mm_alloc(mm, sizeof(trie_t));
+ if (trie != NULL) {
+ empty_root(&trie->root);
+ trie->weight = 0;
+ if (mm != NULL)
+ trie->mm = *mm;
+ else
+ mm_ctx_init(&trie->mm);
+ }
+ return trie;
+}
+
+/*! \brief Free anything under the trie node, except for the passed pointer itself. */
+static void clear_trie(node_t *trie, knot_mm_t *mm)
+{
+ if (!isbranch(trie)) {
+ mm_free(mm, trie->leaf.key);
+ } else {
+ branch_t *b = &trie->branch;
+ int len = bitmap_weight(b->bitmap);
+ for (int i = 0; i < len; ++i)
+ clear_trie(b->twigs + i, mm);
+ mm_free(mm, b->twigs);
+ }
+}
+
+void trie_free(trie_t *tbl)
+{
+ if (tbl == NULL)
+ return;
+ if (tbl->weight)
+ clear_trie(&tbl->root, &tbl->mm);
+ mm_free(&tbl->mm, tbl);
+}
+
+void trie_clear(trie_t *tbl)
+{
+ assert(tbl);
+ if (!tbl->weight)
+ return;
+ clear_trie(&tbl->root, &tbl->mm);
+ empty_root(&tbl->root);
+ tbl->weight = 0;
+}
+
+size_t trie_weight(const trie_t *tbl)
+{
+ assert(tbl);
+ return tbl->weight;
+}
+
+trie_val_t* trie_get_try(trie_t *tbl, const char *key, uint32_t len)
+{
+ assert(tbl);
+ if (!tbl->weight)
+ return NULL;
+ node_t *t = &tbl->root;
+ while (isbranch(t)) {
+ __builtin_prefetch(t->branch.twigs);
+ bitmap_t b = twigbit(t, key, len);
+ if (!hastwig(t, b))
+ return NULL;
+ t = twig(t, twigoff(t, b));
+ }
+ if (key_cmp(key, len, t->leaf.key->chars, t->leaf.key->len) != 0)
+ return NULL;
+ return &t->leaf.val;
+}
+
+int trie_del(trie_t *tbl, const char *key, uint32_t len, trie_val_t *val)
+{
+ assert(tbl);
+ if (!tbl->weight)
+ return KNOT_ENOENT;
+ node_t *t = &tbl->root; // current and parent node
+ branch_t *p = NULL;
+ bitmap_t b = 0;
+ while (isbranch(t)) {
+ __builtin_prefetch(t->branch.twigs);
+ b = twigbit(t, key, len);
+ if (!hastwig(t, b))
+ return KNOT_ENOENT;
+ p = &t->branch;
+ t = twig(t, twigoff(t, b));
+ }
+ if (key_cmp(key, len, t->leaf.key->chars, t->leaf.key->len) != 0)
+ return KNOT_ENOENT;
+ mm_free(&tbl->mm, t->leaf.key);
+ if (val != NULL)
+ *val = t->leaf.val; // we return trie_val_t directly when deleting
+ --tbl->weight;
+ if (unlikely(!p)) { // whole trie was a single leaf
+ assert(tbl->weight == 0);
+ empty_root(&tbl->root);
+ return KNOT_EOK;
+ }
+ // remove leaf t as child of p
+ int ci = t - p->twigs, // child index via pointer arithmetic
+ cc = bitmap_weight(p->bitmap); // child count
+ assert(ci >= 0 && ci < cc);
+
+ if (cc == 2) { // collapse binary node p: move the other child to this node
+ node_t *twigs = p->twigs;
+ (*(node_t *)p) = twigs[1 - ci]; // it might be a leaf or branch
+ mm_free(&tbl->mm, twigs);
+ return KNOT_EOK;
+ }
+ memmove(p->twigs + ci, p->twigs + ci + 1, sizeof(node_t) * (cc - ci - 1));
+ p->bitmap &= ~b;
+ node_t *twigs = mm_realloc(&tbl->mm, p->twigs, sizeof(node_t) * (cc - 1),
+ sizeof(node_t) * cc);
+ if (likely(twigs != NULL))
+ p->twigs = twigs;
+ /* We can ignore mm_realloc failure, only beware that next time
+ * the prev_size passed to it wouldn't be correct; TODO? */
+ return KNOT_EOK;
+}
+
+/*!
+ * \brief Stack of nodes, storing a path down a trie.
+ *
+ * The structure also serves directly as the public trie_it_t type,
+ * in which case it always points to the current leaf, unless we've finished
+ * (i.e. it->len == 0).
+ */
+typedef struct trie_it {
+ node_t* *stack; /*!< The stack; malloc is used directly instead of mm. */
+ uint32_t len; /*!< Current length of the stack. */
+ uint32_t alen; /*!< Allocated/available length of the stack. */
+ /*! \brief Initial storage for \a stack; it should fit in most use cases. */
+ node_t* stack_init[2000 / sizeof(node_t *)];
+} nstack_t;
+
+/*! \brief Create a node stack containing just the root (or empty). */
+static void ns_init(nstack_t *ns, trie_t *tbl)
+{
+ assert(tbl);
+ ns->stack = ns->stack_init;
+ ns->alen = sizeof(ns->stack_init) / sizeof(ns->stack_init[0]);
+ if (tbl->weight) {
+ ns->len = 1;
+ ns->stack[0] = &tbl->root;
+ } else {
+ ns->len = 0;
+ }
+}
+
+/*! \brief Free inside of the stack, i.e. not the passed pointer itself. */
+static void ns_cleanup(nstack_t *ns)
+{
+ assert(ns && ns->stack);
+ if (likely(ns->stack == ns->stack_init))
+ return;
+ free(ns->stack);
+ #ifndef NDEBUG
+ ns->stack = NULL;
+ ns->alen = 0;
+ #endif
+}
+
+/*! \brief Allocate more space for the stack. */
+static int ns_longer_alloc(nstack_t *ns)
+{
+ ns->alen *= 2;
+ size_t new_size = sizeof(nstack_t) + ns->alen * sizeof(node_t *);
+ node_t **st;
+ if (ns->stack == ns->stack_init) {
+ st = malloc(new_size);
+ if (st != NULL)
+ memcpy(st, ns->stack, ns->len * sizeof(node_t *));
+ } else {
+ st = realloc(ns->stack, new_size);
+ }
+ if (st == NULL)
+ return KNOT_ENOMEM;
+ ns->stack = st;
+ return KNOT_EOK;
+}
+
+/*! \brief Ensure the node stack can be extended by one. */
+static inline int ns_longer(nstack_t *ns)
+{
+ // get a longer stack if needed
+ if (likely(ns->len < ns->alen))
+ return KNOT_EOK;
+ return ns_longer_alloc(ns); // hand-split the part suitable for inlining
+}
+
+/*!
+ * \brief Find the "branching point" as if searching for a key.
+ *
+ * The whole path to the point is kept on the passed stack;
+ * always at least the root will remain on the top of it.
+ * Beware: the precise semantics of this function is rather tricky.
+ * The top of the stack will contain: the corresponding leaf if exact match is found;
+ * or the immediate node below a branching-point-on-edge or the branching-point itself.
+ *
+ * \param info Set position of the point of first mismatch (in index and flags).
+ * \param first Set the value of the first non-matching character (from trie),
+ * optionally; end-of-string character has value -256 (that's why it's int).
+ * Note: the character is converted to *unsigned* char (i.e. 0..255),
+ * as that's the ordering used in the trie.
+ *
+ * \return KNOT_EOK or KNOT_ENOMEM.
+ */
+static int ns_find_branch(nstack_t *ns, const char *key, uint32_t len,
+ branch_t *info, int *first)
+{
+ assert(ns && ns->len && info);
+ // First find some leaf with longest matching prefix.
+ while (isbranch(ns->stack[ns->len - 1])) {
+ ERR_RETURN(ns_longer(ns));
+ node_t *t = ns->stack[ns->len - 1];
+ __builtin_prefetch(t->branch.twigs);
+ bitmap_t b = twigbit(t, key, len);
+ // Even if our key is missing from this branch we need to
+ // keep iterating down to a leaf. It doesn't matter which
+ // twig we choose since the keys are all the same up to this
+ // index. Note that blindly using twigoff(t, b) can cause
+ // an out-of-bounds index if it equals twigmax(t).
+ uint i = hastwig(t, b) ? twigoff(t, b) : 0;
+ ns->stack[ns->len++] = twig(t, i);
+ }
+ tkey_t *lkey = ns->stack[ns->len-1]->leaf.key;
+ // Find index of the first char that differs.
+ uint32_t index = 0;
+ while (index < MIN(len,lkey->len)) {
+ if (key[index] != lkey->chars[index])
+ break;
+ else
+ ++index;
+ }
+ info->index = index;
+ if (first)
+ *first = lkey->len > index ? (unsigned char)lkey->chars[index] : -256;
+ // Find flags: which half-byte has matched.
+ uint flags;
+ if (index == len && len == lkey->len) { // found equivalent key
+ info->flags = flags = 0;
+ goto success;
+ }
+ if (likely(index < MIN(len,lkey->len))) {
+ byte k2 = (byte)lkey->chars[index];
+ byte k1 = (byte)key[index];
+ flags = ((k1 ^ k2) & 0xf0) ? 1 : 2;
+ } else { // one is prefix of another
+ flags = 1;
+ }
+ info->flags = flags;
+ // now go up the trie from the current leaf
+ branch_t *t;
+ do {
+ if (unlikely(ns->len == 1))
+ goto success; // only the root stays on the stack
+ t = (branch_t*)ns->stack[ns->len - 2];
+ if (t->index < index || (t->index == index && t->flags < flags))
+ goto success;
+ --ns->len;
+ } while (true);
+success:
+ #ifndef NDEBUG // invariants on successful return
+ assert(ns->len);
+ if (isbranch(ns->stack[ns->len - 1])) {
+ t = &ns->stack[ns->len - 1]->branch;
+ assert(t->index > index || (t->index == index && t->flags >= flags));
+ }
+ if (ns->len > 1) {
+ t = &ns->stack[ns->len - 2]->branch;
+ assert(t->index < index || (t->index == index
+ && (t->flags < flags || (t->flags == 1 && flags == 0))));
+ }
+ #endif
+ return KNOT_EOK;
+}
+
+/*!
+ * \brief Advance the node stack to the last leaf in the subtree.
+ *
+ * \return KNOT_EOK or KNOT_ENOMEM.
+ */
+static int ns_last_leaf(nstack_t *ns)
+{
+ assert(ns);
+ do {
+ ERR_RETURN(ns_longer(ns));
+ node_t *t = ns->stack[ns->len - 1];
+ if (!isbranch(t))
+ return KNOT_EOK;
+ int lasti = bitmap_weight(t->branch.bitmap) - 1;
+ assert(lasti >= 0);
+ ns->stack[ns->len++] = twig(t, lasti);
+ } while (true);
+}
+
+/*!
+ * \brief Advance the node stack to the first leaf in the subtree.
+ *
+ * \return KNOT_EOK or KNOT_ENOMEM.
+ */
+static int ns_first_leaf(nstack_t *ns)
+{
+ assert(ns && ns->len);
+ do {
+ ERR_RETURN(ns_longer(ns));
+ node_t *t = ns->stack[ns->len - 1];
+ if (!isbranch(t))
+ return KNOT_EOK;
+ ns->stack[ns->len++] = twig(t, 0);
+ } while (true);
+}
+
+/*!
+ * \brief Advance the node stack to the leaf that is previous to the current node.
+ *
+ * \note Prefix leaf under the current node DOES count (if present; perhaps questionable).
+ * \return KNOT_EOK on success, KNOT_ENOENT on not-found, or possibly KNOT_ENOMEM.
+ */
+static int ns_prev_leaf(nstack_t *ns)
+{
+ assert(ns && ns->len > 0);
+
+ node_t *t = ns->stack[ns->len - 1];
+ if (hastwig(t, 1 << 0)) { // the prefix leaf
+ t = twig(t, 0);
+ ERR_RETURN(ns_longer(ns));
+ ns->stack[ns->len++] = t;
+ return KNOT_EOK;
+ }
+
+ do {
+ if (ns->len < 2)
+ return KNOT_ENOENT; // root without empty key has no previous leaf
+ t = ns->stack[ns->len - 1];
+ node_t *p = ns->stack[ns->len - 2];
+ int pindex = t - p->branch.twigs; // index in parent via pointer arithmetic
+ assert(pindex >= 0 && pindex <= 16);
+ if (pindex > 0) { // t isn't the first child -> go down the previous one
+ ns->stack[ns->len - 1] = twig(p, pindex - 1);
+ return ns_last_leaf(ns);
+ }
+ // we've got to go up again
+ --ns->len;
+ } while (true);
+}
+
+/*!
+ * \brief Advance the node stack to the leaf that is successor to the current node.
+ *
+ * \note Prefix leaf or anything else under the current node DOES count.
+ * \return KNOT_EOK on success, KNOT_ENOENT on not-found, or possibly KNOT_ENOMEM.
+ */
+static int ns_next_leaf(nstack_t *ns)
+{
+ assert(ns && ns->len > 0);
+
+ node_t *t = ns->stack[ns->len - 1];
+ if (isbranch(t))
+ return ns_first_leaf(ns);
+ do {
+ if (ns->len < 2)
+ return KNOT_ENOENT; // not found, as no more parent is available
+ t = ns->stack[ns->len - 1];
+ node_t *p = ns->stack[ns->len - 2];
+ int pindex = t - p->branch.twigs; // index in parent via pointer arithmetic
+ assert(pindex >= 0 && pindex <= 16);
+ int pcount = bitmap_weight(p->branch.bitmap);
+ if (pindex + 1 < pcount) { // t isn't the last child -> go down the next one
+ ns->stack[ns->len - 1] = twig(p, pindex + 1);
+ return ns_first_leaf(ns);
+ }
+ // we've got to go up again
+ --ns->len;
+ } while (true);
+}
+
+int trie_get_leq(trie_t *tbl, const char *key, uint32_t len, trie_val_t **val)
+{
+ assert(tbl && val);
+ *val = NULL; // so on failure we can just return;
+ if (tbl->weight == 0)
+ return KNOT_ENOENT;
+ { // Intentionally un-indented; until end of function, to bound cleanup attr.
+ // First find a key with longest-matching prefix
+ __attribute__((cleanup(ns_cleanup)))
+ nstack_t ns_local;
+ ns_init(&ns_local, tbl);
+ nstack_t *ns = &ns_local;
+ branch_t bp;
+ int un_leaf; // first unmatched character in the leaf
+ ERR_RETURN(ns_find_branch(ns, key, len, &bp, &un_leaf));
+ int un_key = bp.index < len ? (unsigned char)key[bp.index] : -256;
+ node_t *t = ns->stack[ns->len - 1];
+ if (bp.flags == 0) { // found exact match
+ *val = &t->leaf.val;
+ return KNOT_EOK;
+ }
+ // Get t: the last node on matching path
+ if (isbranch(t) && t->branch.index == bp.index && t->branch.flags == bp.flags) {
+ // t is OK
+ } else {
+ // the top of the stack was the first unmatched node -> step up
+ if (ns->len == 1) {
+ // root was unmatched already
+ if (un_key < un_leaf)
+ return KNOT_ENOENT;
+ ERR_RETURN(ns_last_leaf(ns));
+ goto success;
+ }
+ --ns->len;
+ t = ns->stack[ns->len - 1];
+ }
+ // Now we re-do the first "non-matching" step in the trie
+ // but try the previous child if key was less (it may not exist)
+ bitmap_t b = twigbit(t, key, len);
+ int i = hastwig(t, b)
+ ? twigoff(t, b) - (un_key < un_leaf)
+ : twigoff(t, b) - 1 /*twigoff returns successor when !hastwig*/;
+ if (i >= 0) {
+ ERR_RETURN(ns_longer(ns));
+ ns->stack[ns->len++] = twig(t, i);
+ ERR_RETURN(ns_last_leaf(ns));
+ } else {
+ ERR_RETURN(ns_prev_leaf(ns));
+ }
+success:
+ assert(!isbranch(ns->stack[ns->len - 1]));
+ *val = &ns->stack[ns->len - 1]->leaf.val;
+ return 1;
+ }
+}
+
+/*! \brief Initialize a new leaf, copying the key, and returning failure code. */
+static int mk_leaf(node_t *leaf, const char *key, uint32_t len, knot_mm_t *mm)
+{
+ tkey_t *k = mm_alloc(mm, sizeof(tkey_t) + len);
+ #if FLAGS_HACK
+ assert(((uintptr_t)k) % 4 == 0); // we need an aligned pointer
+ #endif
+ if (unlikely(!k))
+ return KNOT_ENOMEM;
+ k->len = len;
+ memcpy(k->chars, key, len);
+ leaf->leaf = (leaf_t){
+ #if !FLAGS_HACK
+ .flags = 0,
+ #endif
+ .val = NULL,
+ .key = k
+ };
+ return KNOT_EOK;
+}
+
+trie_val_t* trie_get_ins(trie_t *tbl, const char *key, uint32_t len)
+{
+ assert(tbl);
+ // First leaf in an empty tbl?
+ if (unlikely(!tbl->weight)) {
+ if (unlikely(mk_leaf(&tbl->root, key, len, &tbl->mm)))
+ return NULL;
+ ++tbl->weight;
+ return &tbl->root.leaf.val;
+ }
+ { // Intentionally un-indented; until end of function, to bound cleanup attr.
+ // Find the branching-point
+ __attribute__((cleanup(ns_cleanup)))
+ nstack_t ns_local;
+ ns_init(&ns_local, tbl);
+ nstack_t *ns = &ns_local;
+ branch_t bp; // branch-point: index and flags signifying the longest common prefix
+ int k2; // the first unmatched character in the leaf
+ if (unlikely(ns_find_branch(ns, key, len, &bp, &k2)))
+ return NULL;
+ node_t *t = ns->stack[ns->len - 1];
+ if (bp.flags == 0) // the same key was already present
+ return &t->leaf.val;
+ node_t leaf;
+ if (unlikely(mk_leaf(&leaf, key, len, &tbl->mm)))
+ return NULL;
+
+ if (isbranch(t) && bp.index == t->branch.index && bp.flags == t->branch.flags) {
+ // The node t needs a new leaf child.
+ bitmap_t b1 = twigbit(t, key, len);
+ assert(!hastwig(t, b1));
+ uint s, m; TWIGOFFMAX(s, m, t, b1); // new child position and original child count
+ node_t *twigs = mm_realloc(&tbl->mm, t->branch.twigs,
+ sizeof(node_t) * (m + 1), sizeof(node_t) * m);
+ if (unlikely(!twigs))
+ goto err_leaf;
+ memmove(twigs + s + 1, twigs + s, sizeof(node_t) * (m - s));
+ twigs[s] = leaf;
+ t->branch.twigs = twigs;
+ t->branch.bitmap |= b1;
+ ++tbl->weight;
+ return &twigs[s].leaf.val;
+ } else {
+ // We need to insert a new binary branch with leaf at *t.
+ // Note: it works the same for the case where we insert above root t.
+ #ifndef NDEBUG
+ if (ns->len > 1) {
+ node_t *pt = ns->stack[ns->len - 2];
+ assert(hastwig(pt, twigbit(pt, key, len)));
+ }
+ #endif
+ node_t *twigs = mm_alloc(&tbl->mm, sizeof(node_t) * 2);
+ if (unlikely(!twigs))
+ goto err_leaf;
+ node_t t2 = *t; // Save before overwriting t.
+ t->branch.flags = bp.flags;
+ t->branch.index = bp.index;
+ t->branch.twigs = twigs;
+ bitmap_t b1 = twigbit(t, key, len);
+ bitmap_t b2 = unlikely(k2 == -256) ? (1 << 0) : nibbit(k2, bp.flags);
+ t->branch.bitmap = b1 | b2;
+ *twig(t, twigoff(t, b1)) = leaf;
+ *twig(t, twigoff(t, b2)) = t2;
+ ++tbl->weight;
+ return &twig(t, twigoff(t, b1))->leaf.val;
+ };
+err_leaf:
+ mm_free(&tbl->mm, leaf.leaf.key);
+ return NULL;
+ }
+}
+
+/*! \brief Apply a function to every trie_val_t*, in order; a recursive solution. */
+static int apply_trie(node_t *t, int (*f)(trie_val_t *, void *), void *d)
+{
+ assert(t);
+ if (!isbranch(t))
+ return f(&t->leaf.val, d);
+ int child_count = bitmap_weight(t->branch.bitmap);
+ for (int i = 0; i < child_count; ++i)
+ ERR_RETURN(apply_trie(twig(t, i), f, d));
+ return KNOT_EOK;
+}
+
+int trie_apply(trie_t *tbl, int (*f)(trie_val_t *, void *), void *d)
+{
+ assert(tbl && f);
+ if (!tbl->weight)
+ return KNOT_EOK;
+ return apply_trie(&tbl->root, f, d);
+}
+
+/* These are all thin wrappers around static Tns* functions. */
+trie_it_t* trie_it_begin(trie_t *tbl)
+{
+ assert(tbl);
+ trie_it_t *it = malloc(sizeof(nstack_t));
+ if (!it)
+ return NULL;
+ ns_init(it, tbl);
+ if (it->len == 0) // empty tbl
+ return it;
+ if (ns_first_leaf(it)) {
+ ns_cleanup(it);
+ free(it);
+ return NULL;
+ }
+ return it;
+}
+
+void trie_it_next(trie_it_t *it)
+{
+ assert(it && it->len);
+ if (ns_next_leaf(it) != KNOT_EOK)
+ it->len = 0;
+}
+
+bool trie_it_finished(trie_it_t *it)
+{
+ assert(it);
+ return it->len == 0;
+}
+
+void trie_it_free(trie_it_t *it)
+{
+ if (!it)
+ return;
+ ns_cleanup(it);
+ free(it);
+}
+
+const char* trie_it_key(trie_it_t *it, size_t *len)
+{
+ assert(it && it->len);
+ node_t *t = it->stack[it->len - 1];
+ assert(!isbranch(t));
+ tkey_t *key = t->leaf.key;
+ if (len)
+ *len = key->len;
+ return key->chars;
+}
+
+trie_val_t* trie_it_val(trie_it_t *it)
+{
+ assert(it && it->len);
+ node_t *t = it->stack[it->len - 1];
+ assert(!isbranch(t));
+ return &t->leaf.val;
+}
diff --git a/src/contrib/qp-trie/trie.h b/src/contrib/qp-trie/trie.h
new file mode 100644
index 0000000..28ea571
--- /dev/null
+++ b/src/contrib/qp-trie/trie.h
@@ -0,0 +1,112 @@
+/* Copyright (C) 2017 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "libknot/mm_ctx.h"
+
+/*!
+ * \brief Native API of QP-tries:
+ *
+ * - keys are char strings, not necessarily zero-terminated,
+ * the structure copies the contents of the passed keys
+ * - values are void* pointers, typically you get an ephemeral pointer to it
+ * - key lengths are limited by 2^32-1 ATM
+ */
+
+/*! \brief Element value. */
+typedef void* trie_val_t;
+
+/*! \brief Opaque structure holding a QP-trie. */
+typedef struct trie trie_t;
+
+/*! \brief Opaque type for holding a QP-trie iterator. */
+typedef struct trie_it trie_it_t;
+
+/*! \brief Create a trie instance. */
+trie_t* trie_create(knot_mm_t *mm);
+
+/*! \brief Free a trie instance. */
+void trie_free(trie_t *tbl);
+
+/*! \brief Clear a trie instance (make it empty). */
+void trie_clear(trie_t *tbl);
+
+/*! \brief Return the number of keys in the trie. */
+size_t trie_weight(const trie_t *tbl);
+
+/*! \brief Search the trie, returning NULL on failure. */
+trie_val_t* trie_get_try(trie_t *tbl, const char *key, uint32_t len);
+
+/*! \brief Search the trie, inserting NULL trie_val_t on failure. */
+trie_val_t* trie_get_ins(trie_t *tbl, const char *key, uint32_t len);
+
+/*!
+ * \brief Search for less-or-equal element.
+ *
+ * \param tbl Trie.
+ * \param key Searched key.
+ * \param len Key length.
+ * \param val Must be valid; it will be set to NULL if not found or errored.
+ * \return KNOT_EOK for exact match, 1 for previous, KNOT_ENOENT for not-found,
+ * or KNOT_E*.
+ */
+int trie_get_leq(trie_t *tbl, const char *key, uint32_t len, trie_val_t **val);
+
+/*!
+ * \brief Apply a function to every trie_val_t, in order.
+ *
+ * \return KNOT_EOK if success or KNOT_E* if error.
+ */
+int trie_apply(trie_t *tbl, int (*f)(trie_val_t *, void *), void *d);
+
+/*!
+ * \brief Remove an item, returning KNOT_EOK if succeeded or KNOT_ENOENT if not found.
+ *
+ * If val!=NULL and deletion succeeded, the deleted value is set.
+ */
+int trie_del(trie_t *tbl, const char *key, uint32_t len, trie_val_t *val);
+
+/*! \brief Create a new iterator pointing to the first element (if any). */
+trie_it_t* trie_it_begin(trie_t *tbl);
+
+/*!
+ * \brief Advance the iterator to the next element.
+ *
+ * Iteration is in ascending lexicographical order.
+ * In particular, the empty string would be considered as the very first.
+ */
+void trie_it_next(trie_it_t *it);
+
+/*! \brief Test if the iterator has gone past the last element. */
+bool trie_it_finished(trie_it_t *it);
+
+/*! \brief Free any resources of the iterator. It's OK to call it on NULL. */
+void trie_it_free(trie_it_t *it);
+
+/*!
+ * \brief Return pointer to the key of the current element.
+ *
+ * \note The len is uint32_t internally but size_t is better for our usage
+ * as it is without an additional type conversion.
+ */
+const char* trie_it_key(trie_it_t *it, size_t *len);
+
+/*! \brief Return pointer to the value of the current element (writable). */
+trie_val_t* trie_it_val(trie_it_t *it);
diff --git a/src/contrib/sockaddr.c b/src/contrib/sockaddr.c
new file mode 100644
index 0000000..d569292
--- /dev/null
+++ b/src/contrib/sockaddr.c
@@ -0,0 +1,351 @@
+/* Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <netdb.h>
+
+#include "libknot/consts.h"
+#include "libknot/errcode.h"
+#include "contrib/sockaddr.h"
+#include "contrib/openbsd/strlcpy.h"
+#include "contrib/macros.h"
+
+int sockaddr_len(const struct sockaddr *sa)
+{
+ if (sa == NULL) {
+ return 0;
+ }
+
+ switch(sa->sa_family) {
+ case AF_INET:
+ return sizeof(struct sockaddr_in);
+ case AF_INET6:
+ return sizeof(struct sockaddr_in6);
+ case AF_UNIX:
+ return sizeof(struct sockaddr_un);
+ default:
+ return 0;
+ }
+}
+
+static int cmp_ipv4(const struct sockaddr_in *a, const struct sockaddr_in *b)
+{
+ if (a->sin_addr.s_addr < b->sin_addr.s_addr) {
+ return -1;
+ } else if (a->sin_addr.s_addr > b->sin_addr.s_addr) {
+ return 1;
+ } else {
+ return a->sin_port - b->sin_port;
+ }
+}
+
+static int cmp_ipv6(const struct sockaddr_in6 *a, const struct sockaddr_in6 *b)
+{
+ int ret = memcmp(&a->sin6_addr, &b->sin6_addr, sizeof(struct in6_addr));
+ if (ret == 0) {
+ ret = a->sin6_port - b->sin6_port;
+ }
+
+ return ret;
+}
+
+static int cmp_unix(const struct sockaddr_un *a, const struct sockaddr_un *b)
+{
+ int len_a = strnlen(a->sun_path, sizeof(a->sun_path));
+ int len_b = strnlen(b->sun_path, sizeof(b->sun_path));
+ int len_min = len_a <= len_b ? len_a : len_b;
+
+ int ret = strncmp(a->sun_path, b->sun_path, len_min);
+ if (ret == 0) {
+ ret = len_a - len_b;
+ }
+
+ return ret;
+}
+
+int sockaddr_cmp(const struct sockaddr *a, const struct sockaddr *b)
+{
+ if (a->sa_family != b->sa_family) {
+ return (int)a->sa_family - (int)b->sa_family;
+ }
+
+ switch (a->sa_family) {
+ case AF_UNSPEC:
+ return 0;
+ case AF_INET:
+ return cmp_ipv4((struct sockaddr_in *)a, (struct sockaddr_in *)b);
+ case AF_INET6:
+ return cmp_ipv6((struct sockaddr_in6 *)a, (struct sockaddr_in6 *)b);
+ case AF_UNIX:
+ return cmp_unix((struct sockaddr_un *)a, (struct sockaddr_un *)b);
+ default:
+ return 1;
+ }
+}
+
+int sockaddr_set(struct sockaddr_storage *ss, int family, const char *straddr, int port)
+{
+ if (ss == NULL || straddr == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ /* Set family and port. */
+ memset(ss, 0, sizeof(*ss));
+ ss->ss_family = family;
+ sockaddr_port_set((struct sockaddr *)ss, port);
+
+ /* Initialize address depending on address family. */
+ if (family == AF_INET6) {
+ struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)ss;
+ if (inet_pton(family, straddr, &ipv6->sin6_addr) < 1) {
+ return KNOT_ERROR;
+ }
+ return KNOT_EOK;
+ } else if (family == AF_INET) {
+ struct sockaddr_in *ipv4 = (struct sockaddr_in *)ss;
+ if (inet_pton(family, straddr, &ipv4->sin_addr) < 1) {
+ return KNOT_ERROR;
+ }
+ return KNOT_EOK;
+ } else if (family == AF_UNIX) {
+ struct sockaddr_un *un = (struct sockaddr_un *)ss;
+ size_t ret = strlcpy(un->sun_path, straddr, sizeof(un->sun_path));
+ if (ret >= sizeof(un->sun_path)) {
+ return KNOT_ESPACE;
+ }
+ return KNOT_EOK;
+ }
+
+ return KNOT_EINVAL;
+}
+
+void *sockaddr_raw(const struct sockaddr *sa, size_t *addr_size)
+{
+ if (sa == NULL || addr_size == NULL) {
+ return NULL;
+ }
+
+ if (sa->sa_family == AF_INET) {
+ struct sockaddr_in *ipv4 = (struct sockaddr_in *)sa;
+ *addr_size = sizeof(ipv4->sin_addr);
+ return &ipv4->sin_addr;
+ } else if (sa->sa_family == AF_INET6) {
+ struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)sa;
+ *addr_size = sizeof(ipv6->sin6_addr);
+ return &ipv6->sin6_addr;
+ } else {
+ return NULL;
+ }
+}
+
+int sockaddr_set_raw(struct sockaddr_storage *ss, int family,
+ const uint8_t *raw_addr, size_t raw_addr_size)
+{
+ if (ss == NULL || raw_addr == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ memset(ss, 0, sizeof(*ss));
+ ss->ss_family = family;
+
+ size_t sa_size = 0;
+ void *sa_data = sockaddr_raw((struct sockaddr *)ss, &sa_size);
+ if (sa_data == NULL || sa_size != raw_addr_size) {
+ return KNOT_EINVAL;
+ }
+
+ memcpy(sa_data, raw_addr, sa_size);
+
+ return KNOT_EOK;
+}
+
+int sockaddr_tostr(char *buf, size_t maxlen, const struct sockaddr *sa)
+{
+ if (sa == NULL || buf == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ const char *out = NULL;
+
+ /* Convert network address string. */
+ if (sa->sa_family == AF_INET6) {
+ const struct sockaddr_in6 *s = (const struct sockaddr_in6 *)sa;
+ out = inet_ntop(sa->sa_family, &s->sin6_addr, buf, maxlen);
+ } else if (sa->sa_family == AF_INET) {
+ const struct sockaddr_in *s = (const struct sockaddr_in *)sa;
+ out = inet_ntop(sa->sa_family, &s->sin_addr, buf, maxlen);
+ } else if (sa->sa_family == AF_UNIX) {
+ const struct sockaddr_un *s = (const struct sockaddr_un *)sa;
+ size_t ret = strlcpy(buf, s->sun_path, maxlen);
+ out = (ret < maxlen) ? buf : NULL;
+ } else {
+ return KNOT_EINVAL;
+ }
+
+ if (out == NULL) {
+ *buf = '\0';
+ return KNOT_ESPACE;
+ }
+
+ /* Write separator and port. */
+ int written = strlen(buf);
+ int port = sockaddr_port(sa);
+ if (port > 0) {
+ int ret = snprintf(&buf[written], maxlen - written, "@%d", port);
+ if (ret <= 0 || (size_t)ret >= maxlen - written) {
+ *buf = '\0';
+ return KNOT_ESPACE;
+ }
+
+ written += ret;
+ }
+
+ return written;
+}
+
+int sockaddr_port(const struct sockaddr *sa)
+{
+ if (sa == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ if (sa->sa_family == AF_INET6) {
+ return ntohs(((struct sockaddr_in6 *)sa)->sin6_port);
+ } else if (sa->sa_family == AF_INET) {
+ return ntohs(((struct sockaddr_in *)sa)->sin_port);
+ } else {
+ return KNOT_EINVAL;
+ }
+}
+
+void sockaddr_port_set(struct sockaddr *sa, uint16_t port)
+{
+ if (sa == NULL) {
+ return;
+ }
+
+ if (sa->sa_family == AF_INET6) {
+ ((struct sockaddr_in6 *)sa)->sin6_port = htons(port);
+ } else if (sa->sa_family == AF_INET) {
+ ((struct sockaddr_in *)sa)->sin_port = htons(port);
+ }
+}
+
+char *sockaddr_hostname(void)
+{
+ /* Fetch hostname. */
+ char host[KNOT_DNAME_MAXLEN + 1] = { '\0' };
+ if (gethostname(host, sizeof(host)) != 0) {
+ return NULL;
+ }
+ /* Just to be sure. */
+ host[sizeof(host) - 1] = '\0';
+
+ /* Fetch canonical name for this address/DNS. */
+ struct addrinfo hints, *info = NULL;
+ memset(&hints, 0, sizeof hints);
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_flags = AI_CANONNAME;
+ if (getaddrinfo(host, "domain", &hints, &info) != 0) {
+ return NULL;
+ }
+
+ /* Fetch first valid hostname. */
+ char *hname = NULL;
+ struct addrinfo *p = NULL;
+ for (p = info; p != NULL; p = p->ai_next) {
+ if (p->ai_canonname) {
+ hname = strdup(p->ai_canonname);
+ break;
+ }
+ }
+
+ /* No valid hostname found, resort to gethostname() result */
+ if (hname == NULL) {
+ hname = strdup(host);
+ }
+
+ freeaddrinfo(info);
+ return hname;
+}
+
+bool sockaddr_is_any(const struct sockaddr *sa)
+{
+ if (sa == NULL) {
+ return false;
+ }
+
+ if (sa->sa_family == AF_INET) {
+ const struct sockaddr_in *ipv4 = (struct sockaddr_in *)sa;
+ return ipv4->sin_addr.s_addr == INADDR_ANY;
+ }
+
+ if (sa->sa_family == AF_INET6) {
+ const struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)sa;
+ return memcmp(&ipv6->sin6_addr, &in6addr_any, sizeof(ipv6->sin6_addr)) == 0;
+ }
+
+ return false;
+}
+
+bool sockaddr_net_match(const struct sockaddr *ss1,
+ const struct sockaddr *ss2,
+ unsigned prefix)
+{
+ if (ss1 == NULL || ss2 == NULL) {
+ return false;
+ }
+
+ if (ss1->sa_family != ss2->sa_family) {
+ return false;
+ }
+
+ size_t raw_len = 0;
+ const uint8_t *raw_1 = sockaddr_raw(ss1, &raw_len);
+ const uint8_t *raw_2 = sockaddr_raw(ss2, &raw_len);
+
+ prefix = MIN(prefix, raw_len * 8);
+ unsigned bytes = prefix / 8;
+ unsigned bits = prefix % 8;
+
+ /* Compare full bytes. */
+ if (memcmp(raw_1, raw_2, bytes) != 0) {
+ return false;
+ }
+
+ /* Compare last partial byte. */
+ return bits == 0 ||
+ (raw_1[bytes] >> (8 - bits) == raw_2[bytes] >> (8 - bits));
+}
+
+bool sockaddr_range_match(const struct sockaddr *sa,
+ const struct sockaddr *ss_min,
+ const struct sockaddr *ss_max)
+{
+ if (sa == NULL || ss_min == NULL || ss_max == NULL) {
+ return false;
+ }
+
+ if (ss_min->sa_family != ss_max->sa_family ||
+ ss_min->sa_family != sa->sa_family) {
+ return false;
+ }
+
+ return sockaddr_cmp(sa, ss_min) >= 0 && sockaddr_cmp(sa, ss_max) <= 0;
+}
diff --git a/src/contrib/sockaddr.h b/src/contrib/sockaddr.h
new file mode 100644
index 0000000..0fbf1ab
--- /dev/null
+++ b/src/contrib/sockaddr.h
@@ -0,0 +1,160 @@
+/* Copyright (C) 2017 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+/* BSD IPv6 */
+#ifndef __POSIX_VISIBLE
+#define __POSIX_VISIBLE 200112
+#endif
+
+#include <stdbool.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <stdint.h>
+#include <unistd.h>
+
+/* Subnet maximum prefix length. */
+#define IPV4_PREFIXLEN 32
+#define IPV6_PREFIXLEN 128
+
+/* Address string "address[@port]" maximum length. */
+#define SOCKADDR_STRLEN_EXT (1 + 6) /* '@', 5 digits number, \0 */
+#define SOCKADDR_STRLEN (sizeof(struct sockaddr_un) + SOCKADDR_STRLEN_EXT)
+
+/*!
+ * \brief Calculate current structure length based on address family.
+ *
+ * \param sa Socket address.
+ *
+ * \return Number of bytes or error code.
+ */
+int sockaddr_len(const struct sockaddr *sa);
+
+/*!
+ * \brief Compare addresses.
+ *
+ * \return like memcmp(3)
+ */
+int sockaddr_cmp(const struct sockaddr *k1, const struct sockaddr *k2);
+
+/*!
+ * \brief Set address and port.
+ *
+ * \param ss Socket address.
+ * \param family Address family.
+ * \param straddr IP address in string format.
+ * \param port Port.
+ *
+ * \return KNOT_EOK on success or an error code.
+ */
+int sockaddr_set(struct sockaddr_storage *ss, int family, const char *straddr, int port);
+
+/*!
+ * \brief Return raw network address in network byte order.
+ *
+ * \param[in] sa Socket address.
+ * \param[out] addr_size Address length.
+ *
+ * \return Pointer to binary buffer of size addr_size.
+ */
+void *sockaddr_raw(const struct sockaddr *sa, size_t *addr_size);
+
+/*!
+ * \brief Set raw address.
+ *
+ * \param ss Socket address.
+ * \param family Address family.
+ * \param raw_addr IP address in binary format.
+ * \param raw_addr_size Size of the binary address.
+ *
+ * \return KNOT_EOK on success or an error code.
+ */
+int sockaddr_set_raw(struct sockaddr_storage *ss, int family,
+ const uint8_t *raw_addr, size_t raw_addr_size);
+
+/*!
+ * \brief Return string representation of socket address.
+ *
+ * \note String format: \<address>[@<port>], f.e. '127.0.0.1@53'
+ *
+ * \param buf Destination for string representation.
+ * \param maxlen Maximum number of written bytes.
+ * \param sa Socket address.
+ *
+ * \return Number of bytes written on success, error code on failure.
+ */
+int sockaddr_tostr(char *buf, size_t maxlen, const struct sockaddr *sa);
+
+/*!
+ * \brief Return port number from address.
+ *
+ * \param sa Socket address.
+ *
+ * \return Port number or error code.
+ */
+int sockaddr_port(const struct sockaddr *sa);
+
+/*!
+ * \brief Set port number.
+ *
+ * \param sa Socket address.
+ * \param port Port to set.
+ */
+void sockaddr_port_set(struct sockaddr *sa, uint16_t port);
+
+/*!
+ * \brief Get host FQDN address.
+ *
+ * \return Hostname string or NULL.
+ */
+char *sockaddr_hostname(void);
+
+/*!
+ * \brief Check if address is ANY address.
+ *
+ * \param sa Socket address.
+ */
+bool sockaddr_is_any(const struct sockaddr *sa);
+
+/*!
+ * \brief Check if two addresses match the given network prefix.
+ *
+ * \param sa1 First address.
+ * \param sa2 Second address.
+ * \param prefix Prefix length.
+ *
+ * \return True on match.
+ */
+bool sockaddr_net_match(const struct sockaddr *sa1,
+ const struct sockaddr *sa2,
+ unsigned prefix);
+
+/*!
+ * \brief Check if the address is within the given address range (inclusive).
+ *
+ * \param sa Address to check.
+ * \param sa_min Minimum address.
+ * \param sa_max Maximum address.
+ *
+ * \return True on match.
+ */
+bool sockaddr_range_match(const struct sockaddr *sa,
+ const struct sockaddr *sa_min,
+ const struct sockaddr *sa_max);
diff --git a/src/contrib/string.c b/src/contrib/string.c
new file mode 100644
index 0000000..6e0eea9
--- /dev/null
+++ b/src/contrib/string.c
@@ -0,0 +1,112 @@
+/* Copyright (C) 2017 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "contrib/string.h"
+#include "contrib/ctype.h"
+
+uint8_t *memdup(const uint8_t *data, size_t data_size)
+{
+ uint8_t *result = (uint8_t *)malloc(data_size);
+ if (!result) {
+ return NULL;
+ }
+
+ return memcpy(result, data, data_size);
+}
+
+char *sprintf_alloc(const char *fmt, ...)
+{
+ char *strp = NULL;
+ va_list ap;
+
+ va_start(ap, fmt);
+ int ret = vasprintf(&strp, fmt, ap);
+ va_end(ap);
+
+ if (ret < 0) {
+ return NULL;
+ }
+ return strp;
+}
+
+char *strcdup(const char *s1, const char *s2)
+{
+ if (!s1 || !s2) {
+ return NULL;
+ }
+
+ size_t s1len = strlen(s1);
+ size_t s2len = strlen(s2);
+ size_t nlen = s1len + s2len + 1;
+
+ char* dst = malloc(nlen);
+ if (dst == NULL) {
+ return NULL;
+ }
+
+ memcpy(dst, s1, s1len);
+ memcpy(dst + s1len, s2, s2len + 1);
+ return dst;
+}
+
+char *strstrip(const char *str)
+{
+ // leading white-spaces
+ const char *scan = str;
+ while (is_space(scan[0])) {
+ scan += 1;
+ }
+
+ // trailing white-spaces
+ size_t len = strlen(scan);
+ while (len > 0 && is_space(scan[len - 1])) {
+ len -= 1;
+ }
+
+ char *trimmed = malloc(len + 1);
+ if (!trimmed) {
+ return NULL;
+ }
+
+ memcpy(trimmed, scan, len);
+ trimmed[len] = '\0';
+
+ return trimmed;
+}
+
+int const_time_memcmp(const void *s1, const void *s2, size_t n)
+{
+ volatile uint8_t equal = 0;
+
+ for (size_t i = 0; i < n; i++) {
+ equal |= ((uint8_t *)s1)[i] ^ ((uint8_t *)s2)[i];
+ }
+
+ return equal;
+}
+
+typedef void *(*memset_t)(void *, int, size_t);
+static volatile memset_t volatile_memset = memset;
+
+void *memzero(void *s, size_t n)
+{
+ return volatile_memset(s, 0, n);
+}
diff --git a/src/contrib/string.h b/src/contrib/string.h
new file mode 100644
index 0000000..19c74c7
--- /dev/null
+++ b/src/contrib/string.h
@@ -0,0 +1,82 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*!
+ * \brief String manipulations.
+ */
+
+#pragma once
+
+#include <stddef.h>
+#include <stdint.h>
+
+/*!
+ * \brief Create a copy of a binary buffer.
+ *
+ * Like \c strdup, but for binary data.
+ */
+uint8_t *memdup(const uint8_t *data, size_t data_size);
+
+/*!
+ * \brief Format string and take care of allocating memory.
+ *
+ * \note sprintf(3) manual page reference implementation.
+ *
+ * \param fmt Message format.
+ * \return formatted message or NULL.
+ */
+char *sprintf_alloc(const char *fmt, ...);
+
+/*!
+ * \brief Create new string from a concatenation of s1 and s2.
+ *
+ * \param s1 First string.
+ * \param s2 Second string.
+ *
+ * \retval Newly allocated string on success.
+ * \retval NULL on error.
+ */
+char *strcdup(const char *s1, const char *s2);
+
+/*!
+ * \brief Create a copy of a string skipping leading and trailing white spaces.
+ *
+ * \return Newly allocated string, NULL in case of error.
+ */
+char *strstrip(const char *str);
+
+/*!
+ * \brief Compare data in time based on string length.
+ * This function just checks for (in)equality not for relation
+ *
+ * \param s1 The first address to compare.
+ * \param s2 The second address to compare.
+ * \param n The size of memory to compare.
+ *
+ * \return Non zero on difference and zero if the buffers are identical.
+ */
+int const_time_memcmp(const void *s1, const void *s2, size_t n);
+
+/*!
+ * \brief Fill memory with zeroes.
+ *
+ * Inspired by OPENSSL_cleanse. Such a memset shouldn't be optimized out.
+ *
+ * \param s The address to fill.
+ * \param n The size of memory to fill.
+ *
+ * \return Pointer to the memory.
+ */
+void *memzero(void *s, size_t n);
diff --git a/src/contrib/strtonum.h b/src/contrib/strtonum.h
new file mode 100644
index 0000000..6cfe8bb
--- /dev/null
+++ b/src/contrib/strtonum.h
@@ -0,0 +1,120 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#pragma once
+
+#include <assert.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include "libknot/errcode.h"
+#include "contrib/ctype.h"
+
+inline static int intmax_from_str(const char *src, intmax_t *dst,
+ intmax_t min, intmax_t max)
+{
+ if (!is_digit(*src) && *src != '-' && *src != '+') {
+ return KNOT_EINVAL;
+ }
+
+ errno = 0;
+ char *end = NULL;
+ intmax_t result = strtoimax(src, &end, 10);
+
+ if (errno == ERANGE) {
+ return KNOT_ERANGE;
+ }
+
+ if (src == end || *end != '\0') {
+ return KNOT_EINVAL;
+ }
+
+ if (result < min || result > max) {
+ return KNOT_ERANGE;
+ }
+
+ *dst = result;
+ return KNOT_EOK;
+}
+
+inline static int uintmax_from_str(const char *src, uintmax_t *dst,
+ uintmax_t min, uintmax_t max)
+{
+ if (!is_digit(*src) && *src != '+') {
+ return KNOT_EINVAL;
+ }
+
+ errno = 0;
+ char *end = NULL;
+ uintmax_t result = strtoumax(src, &end, 10);
+
+ if (errno == ERANGE) {
+ return KNOT_ERANGE;
+ }
+
+ if (src == end || *end != '\0') {
+ return KNOT_EINVAL;
+ }
+
+ if (result < min || result > max) {
+ return KNOT_ERANGE;
+ }
+
+ *dst = result;
+ return KNOT_EOK;
+}
+
+#define CONVERT(prefix, type, min, max, src, dst) \
+{ \
+ assert(src && dst); \
+ prefix##max_t value; \
+ int result = prefix##max_from_str(src, &value, min, max); \
+ if (result != KNOT_EOK) { \
+ return result; \
+ } \
+ *dst = (type)value; \
+ return KNOT_EOK; \
+}
+
+inline static int str_to_int(const char *src, int *dst, int min, int max)
+{
+ CONVERT(int, int, min, max, src, dst);
+}
+
+inline static int str_to_u8(const char *src, uint8_t *dst)
+{
+ CONVERT(uint, uint8_t, 0, UINT8_MAX, src, dst);
+}
+
+inline static int str_to_u16(const char *src, uint16_t *dst)
+{
+ CONVERT(uint, uint16_t, 0, UINT16_MAX, src, dst);
+}
+
+inline static int str_to_u32(const char *src, uint32_t *dst)
+{
+ CONVERT(uint, uint32_t, 0, UINT32_MAX, src, dst);
+}
+
+inline static int str_to_size(const char *src, size_t *dst, size_t min, size_t max)
+{
+ CONVERT(uint, size_t, min, max, src, dst);
+}
+
+#undef CONVERT
diff --git a/src/contrib/time.c b/src/contrib/time.c
new file mode 100644
index 0000000..8c32c2d
--- /dev/null
+++ b/src/contrib/time.c
@@ -0,0 +1,405 @@
+/* Copyright (C) 2017 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "contrib/time.h"
+#include "contrib/ctype.h"
+#ifndef HAVE_CLOCK_GETTIME
+ #include <sys/time.h>
+#endif
+
+struct timespec time_now(void)
+{
+ struct timespec result = { 0 };
+
+#ifdef HAVE_CLOCK_GETTIME
+ clock_gettime(CLOCK_MONOTONIC, &result);
+#else // OS X < Sierra fallback.
+ struct timeval tmp = { 0 };
+ gettimeofday(&tmp, NULL);
+ result.tv_sec = tmp.tv_sec;
+ result.tv_nsec = 1000 * tmp.tv_usec;
+#endif
+
+ return result;
+}
+
+struct timespec time_diff(const struct timespec *begin, const struct timespec *end)
+{
+ struct timespec result = { 0 };
+
+ if (end->tv_nsec >= begin->tv_nsec) {
+ result.tv_sec = end->tv_sec - begin->tv_sec;
+ result.tv_nsec = end->tv_nsec - begin->tv_nsec;
+ } else {
+ result.tv_sec = end->tv_sec - begin->tv_sec - 1;
+ result.tv_nsec = 1000000000 - begin->tv_nsec + end->tv_nsec;
+ }
+
+ return result;
+}
+
+double time_diff_ms(const struct timespec *begin, const struct timespec *end)
+{
+ struct timespec result = time_diff(begin, end);
+
+ return (result.tv_sec * 1e3) + (result.tv_nsec / 1e6);
+}
+
+typedef struct {
+ const char *format;
+ const char *timespec;
+ const char *parsed;
+ knot_timediff_t offset;
+ char offset_sign;
+ char offset_unit;
+ struct tm calendar;
+ int error;
+} time_ctx_t;
+
+// After casting (struct tm) to (int []), we can use indexes...
+static int calendar_index(char ind)
+{
+ switch (ind) {
+ case 'Y': return 5;
+ case 'M': return 4;
+ case 'D': return 3;
+ case 'h': return 2;
+ case 'm': return 1;
+ case 's': return 0;
+ default: assert(0); return 6;
+ }
+}
+
+static size_t calendar_digits(int index)
+{
+ return index == 5 ? 4 : 2;
+}
+
+static size_t unit_value(char unit)
+{
+ size_t val = 1;
+ switch (unit) {
+ case 'M':
+ return 3600 * 24 * 30;
+ case 'Y':
+ val *= 365;
+ // FALLTHROUGH
+ case 'D':
+ val *= 24;
+ // FALLTHROUGH
+ case 'h':
+ val *= 60;
+ // FALLTHROUGH
+ case 'm':
+ val *= 60;
+ // FALLTHROUGH
+ case 's':
+ default:
+ return val;
+ }
+}
+
+static knot_time_t time_ctx_finalize(time_ctx_t *ctx)
+{
+ if (ctx->offset_sign) {
+ ctx->offset *= unit_value(ctx->offset_unit);
+ return knot_time_add(knot_time(), (ctx->offset_sign == '-' ? -1 : 1) * ctx->offset);
+ } else if (ctx->offset) {
+ return (knot_time_t)ctx->offset;
+ } else if (ctx->calendar.tm_year != 0) {
+ ctx->calendar.tm_isdst = -1;
+ ctx->calendar.tm_year -= 1900;
+ ctx->calendar.tm_mon -= 1;
+ // Set UTC timezone before using mktime
+ putenv("TZ=UTC");
+ tzset();
+ return (knot_time_t)mktime(&ctx->calendar);
+ } else {
+ return (knot_time_t)0;
+ }
+}
+
+static void time_ctx_reset(time_ctx_t *ctx)
+{
+ ctx->parsed = ctx->timespec;
+ ctx->offset = 0;
+ ctx->offset_sign = 0;
+ memset(&ctx->calendar, 0, sizeof(ctx->calendar));
+ ctx->error = 0;
+}
+
+static void parse_quote(time_ctx_t *ctx)
+{
+ while (*ctx->format != '|' && *ctx->format != '\0') {
+ if (*ctx->format == '\'') {
+ ctx->format++;
+ return;
+ }
+ if (*ctx->format++ != *ctx->parsed++) {
+ ctx->error = -1;
+ return;
+ }
+ }
+ ctx->error = -2;
+ return;
+}
+
+static void parse_offset(time_ctx_t *ctx)
+{
+ ctx->offset = 0;
+ ctx->error = -1;
+ while (is_digit(*ctx->parsed)) {
+ ctx->offset *= 10;
+ ctx->offset += *ctx->parsed++ - '0';
+ ctx->error = 0;
+ }
+}
+
+static void parse_calendar(time_ctx_t *ctx, int index)
+{
+ int *cal_arr = (int *)&ctx->calendar;
+ cal_arr[index] = 0;
+ for (size_t i = 0; i < calendar_digits(index); i++) {
+ if (!is_digit(*ctx->parsed)) {
+ ctx->error = -1;
+ return;
+ }
+ cal_arr[index] *= 10;
+ cal_arr[index] += *ctx->parsed++ - '0';
+ }
+}
+
+static void parse_sign(time_ctx_t *ctx)
+{
+ char sign1 = *(ctx->format - 1), sign2 = *ctx->format;
+
+ bool use_sign2 = (sign2 == '+' || sign2 == '-');
+
+ bool allow_plus = (sign1 == '+' || (sign1 == '-' && sign2 == '+'));
+ bool allow_minus = (sign1 == '-' || (sign1 == '+' && sign2 == '-'));
+ assert(sign1 == '+' || sign1 == '-');
+
+ if ((*ctx->parsed == '+' && allow_plus) || (*ctx->parsed == '-' && allow_minus)) {
+ ctx->offset_sign = *ctx->parsed++;
+ ctx->format += (use_sign2 ? 1 : 0);
+ } else {
+ ctx->error = -11;
+ }
+}
+
+static void parse_unit1(time_ctx_t *ctx)
+{
+ char u = *ctx->parsed++;
+ switch (u) {
+ case 'Y':
+ case 'M':
+ case 'D':
+ case 'h':
+ case 'm':
+ case 's':
+ ctx->offset_unit = u;
+ break;
+ default:
+ ctx->error = -1;
+ }
+}
+
+static void parse_unit2(time_ctx_t *ctx)
+{
+ char u = *ctx->parsed++;
+ switch (u) {
+ case 'y':
+ case 'd':
+ ctx->offset_unit = toupper((unsigned char)u);
+ break;
+ case 'h':
+ case 's':
+ ctx->offset_unit = u;
+ break;
+ case 'm':
+ switch (*ctx->parsed++) {
+ case 'o':
+ ctx->offset_unit = 'M';
+ break;
+ case 'i':
+ ctx->offset_unit = 'm';
+ break;
+ default:
+ ctx->error = -1;
+ }
+ break;
+ default:
+ ctx->error = -1;
+ }
+}
+
+int knot_time_parse(const char *format, const char *timespec, knot_time_t *time)
+{
+ if (format == NULL || timespec == NULL || time == NULL) {
+ return -1;
+ }
+
+ time_ctx_t ctx = {
+ .format = format,
+ .timespec = timespec,
+ .parsed = timespec,
+ .offset = 0,
+ .offset_sign = 0,
+ // we hope that .calendar is zeroed by default
+ .error = 0,
+ };
+
+ while (ctx.error == 0 && *ctx.format != '\0') {
+ switch (*ctx.format++) {
+ case '|':
+ if (*ctx.parsed == '\0') {
+ *time = time_ctx_finalize(&ctx);
+ return 0;
+ } else {
+ time_ctx_reset(&ctx);
+ }
+ break;
+ case '\'':
+ parse_quote(&ctx);
+ break;
+ case '#':
+ parse_offset(&ctx);
+ break;
+ case 'Y':
+ case 'M':
+ case 'D':
+ case 'h':
+ case 'm':
+ case 's':
+ parse_calendar(&ctx, calendar_index(*(ctx.format - 1)));
+ break;
+ case '+':
+ case '-':
+ parse_sign(&ctx);
+ break;
+ case 'U':
+ parse_unit1(&ctx);
+ break;
+ case 'u':
+ parse_unit2(&ctx);
+ break;
+ default:
+ return -1;
+ }
+
+ if (ctx.error < 0) {
+ while (*ctx.format != '|' && *ctx.format != '\0') {
+ ctx.format++;
+ }
+ time_ctx_reset(&ctx);
+ ctx.error = (*ctx.format == '\0' ? -1 : 0);
+ }
+ }
+
+ if (ctx.error == 0 && *ctx.parsed == '\0') {
+ *time = time_ctx_finalize(&ctx);
+ return 0;
+ }
+ return -1;
+}
+
+static char *unit_names_mixed[] = { "Y", "M", "D", "h", "m", "s" };
+static char *unit_names_lower[] = { "y", "mo", "d", "h", "mi", "s" };
+static size_t unit_sizes[] = { 3600*24*365, 3600*24*30, 3600*24, 3600, 60, 1 };
+static const size_t unit_count = 6;
+
+static int print_unit(char *dst, size_t dst_len, char *unit_names[unit_count],
+ size_t max_units, knot_time_t time)
+{
+ int ret;
+ if (time == 0) {
+ ret = snprintf(dst, dst_len, "0");
+ return (ret < 0 || ret >= dst_len ? -1 : 0);
+ }
+ knot_timediff_t diff = knot_time_diff(time, knot_time());
+ if (dst_len-- < 1) {
+ return -1;
+ }
+ *dst++ = (diff < 0 ? '-' : '+');
+ if (diff < 0) {
+ diff = -diff;
+ } else if (diff == 0) {
+ ret = snprintf(dst, dst_len, "0%s", unit_names[unit_count - 1]);
+ return (ret < 0 || ret >= dst_len ? -1 : 0);
+ }
+ size_t curr_unit = 0, used_units = 0;
+ while (curr_unit < unit_count && used_units < max_units) {
+ if (diff >= unit_sizes[curr_unit]) {
+ ret = snprintf(dst, dst_len, "%"KNOT_TIMEDIFF_PRINTF"%s",
+ diff / unit_sizes[curr_unit],
+ unit_names[curr_unit]);
+ if (ret < 0 || ret >= dst_len) {
+ return -1;
+ }
+ dst += ret;
+ dst_len -= ret;
+ used_units++;
+ diff %= unit_sizes[curr_unit];
+ }
+ curr_unit++;
+ }
+ return 0;
+}
+
+int knot_time_print(knot_time_print_t format, knot_time_t time, char *dst, size_t dst_len)
+{
+ if (dst == NULL) {
+ return -1;
+ }
+
+ int ret;
+ switch (format) {
+ case TIME_PRINT_UNIX:
+ ret = snprintf(dst, dst_len, "%"KNOT_TIME_PRINTF, time);
+ return ((ret >= 0 && ret < dst_len) ? 0 : -1);
+ case TIME_PRINT_ISO8601:
+ if (time > LONG_MAX) {
+ return -1;
+ }
+
+ // Set timezone to UTC before using timezone dependent functions
+ putenv("TZ=UTC");
+ tzset();
+
+ struct tm lt;
+ time_t tt = (time_t)time;
+ ret = (localtime_r(&tt, &lt) == NULL ? -1 :
+ strftime(dst, dst_len, "%Y-%m-%dT%H:%M:%SZ", &lt));
+ return (ret > 0 ? 0 : -1);
+ case TIME_PRINT_RELSEC:
+ ret = snprintf(dst, dst_len, "%+"KNOT_TIMEDIFF_PRINTF,
+ knot_time_diff(time, knot_time()));
+ return ((ret >= 0 && ret < dst_len) ? 0 : -1);
+ case TIME_PRINT_HUMAN_MIXED:
+ return print_unit(dst, dst_len, unit_names_mixed, unit_count, time);
+ case TIME_PRINT_HUMAN_LOWER:
+ return print_unit(dst, dst_len, unit_names_lower, unit_count, time);
+ default:
+ return -1;
+ }
+}
diff --git a/src/contrib/time.h b/src/contrib/time.h
new file mode 100644
index 0000000..55b8727
--- /dev/null
+++ b/src/contrib/time.h
@@ -0,0 +1,171 @@
+/* Copyright (C) 2017 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <time.h>
+#include <inttypes.h>
+
+/*!
+ * \brief Specify output format for knot_time_print().
+ */
+typedef enum {
+ TIME_PRINT_UNIX, // numeric UNIX time
+ TIME_PRINT_ISO8601, // 2016-12-31T23:59:00
+ TIME_PRINT_RELSEC, // relative +6523
+ TIME_PRINT_HUMAN_MIXED, // relative with mixed-case units
+ TIME_PRINT_HUMAN_LOWER, // relative with lower-case units
+} knot_time_print_t;
+
+/*!
+ * \brief Get current time.
+ */
+struct timespec time_now(void);
+
+/*!
+ * \brief Get time elapsed between two events.
+ */
+struct timespec time_diff(const struct timespec *begin, const struct timespec *end);
+
+/*!
+ * \brief Get time elapsed between two events in miliseconds.
+ */
+double time_diff_ms(const struct timespec *begin, const struct timespec *end);
+
+/*!
+ * \brief Data type for keeping UNIX timestamps.
+ *
+ * This is because time_t can be 32-bit on some systems, which is bad.
+ * Zero value represents infinity.
+ */
+typedef uint64_t knot_time_t;
+
+/*!
+ * \brief Data type for keeping time differences.
+ */
+typedef int64_t knot_timediff_t;
+
+#define KNOT_TIMEDIFF_MIN INT64_MIN
+#define KNOT_TIMEDIFF_MAX INT64_MAX
+
+#define KNOT_TIME_PRINTF PRIu64
+#define KNOT_TIMEDIFF_PRINTF PRId64
+
+/*!
+ * \brief Returns current time sice epoch.
+ */
+inline static knot_time_t knot_time(void)
+{
+ return (knot_time_t)time(NULL);
+}
+
+/*!
+ * \brief Compare two timestamps.
+ *
+ * \return 0 if equal, -1 if the former is smaller (=earlier), 1 else.
+ */
+inline static int knot_time_cmp(knot_time_t a, knot_time_t b)
+{
+ return (a == b ? 0 : 1) * ((a && b) == 0 ? -1 : 1) * (a < b ? -1 : 1);
+}
+
+/*!
+ * \brief Return the smaller (=earlier) from given two timestamps.
+ */
+inline static knot_time_t knot_time_min(knot_time_t a, knot_time_t b)
+{
+ if ((a && b) == 0) {
+ return a + b;
+ } else {
+ return (a < b ? a : b);
+ }
+}
+
+/*!
+ * \brief Return the difference between two timestamps (to "minus" from).
+ *
+ * \note If both are zero (=infinity), KNOT_TIMEDIFF_MAX is returned.
+ */
+inline static knot_timediff_t knot_time_diff(knot_time_t to, knot_time_t from)
+{
+ if ((to && from) == 0) {
+ return (to > from ? KNOT_TIMEDIFF_MIN : KNOT_TIMEDIFF_MAX);
+ } else {
+ return (knot_timediff_t)to - (knot_timediff_t)from;
+ }
+}
+
+/*!
+ * \brief Add a time difference to timestamp.
+ */
+inline static knot_time_t knot_time_add(knot_time_t since, knot_timediff_t howlong)
+{
+ return (since != 0 ? since + howlong : since);
+}
+
+/*!
+ * \brief Convert uint32_t-encoded timestamp to knot_time_t.
+ *
+ * In RRSIG rdata, there are inception and expiration timestamps in uint32_t format.
+ * One shall use 'serial arithmetics' to decode them.
+ *
+ * \todo However it needs time(now) context which is slow to obtain, so we don't do it
+ * for now. Please fix this in next 100 years.
+ */
+inline static knot_time_t knot_time_from_u32(uint32_t u32time)
+{
+ return (knot_time_t)u32time;
+}
+
+/*!
+ * \brief Parse a text-formatted timestamp to knot_time_t using format specification.
+ *
+ * \param format The timestamp text format specification.
+ * \param timespec Text-formatted timestamp.
+ * \param time The parsed timestamp.
+ *
+ * The format specification basics:
+ * <format 1>|<format 2> - The pipe sign separates two time format specifications. Leftmost
+ * specification matching the timespec is used.
+ * '<a string>' - Matches exactly <a string> (not containing apostrophes) in timespec.
+ * # - Hashtag matches for a number in timespec, stands for either a UNIX timestamp,
+ * or, within a context of an unit, as a number of such units.
+ * Y, M, D, h, m, s - Matches a number, stands for a number of years, months, days, hours,
+ * minutes and seconds, respectively.
+ * +, - - The + and - signs declaring that following timespec is relative to "now".
+ * A single sign can be used to limit the timestamp being in future or in past,
+ * or both +- allow the timestamp to select any (just one) of them.
+ * U - Matches one of Y, M, D, h, m, s in the timespec standing for a time unit.
+ * u - Like U, but the unit in the timestamp is from: y, mo, d, h, mi, s.
+ *
+ * \retval -1 An error occurred, out_time has no sense.
+ * \return 0 OK, timestamp parsed successfully.
+ */
+int knot_time_parse(const char *format, const char *timespec, knot_time_t *time);
+
+/*!
+ * \brief Print the timestamp in specified format into a string buffer.
+ *
+ * \param format The timestamp text format specification.
+ * \param time The timestamp to be printed.
+ * \param dst The destination buffer pointer with text-formatted timestamp.
+ * \param dst_len The destination buffer length.
+ *
+ * \retval -1 An error occurred, the buffer may be filled with nonsense.
+ * \return 0 OK, timestamp printed successfully.
+ */
+int knot_time_print(knot_time_print_t format, knot_time_t time, char *dst, size_t dst_len);
diff --git a/src/contrib/tolower.h b/src/contrib/tolower.h
new file mode 100644
index 0000000..8f55182
--- /dev/null
+++ b/src/contrib/tolower.h
@@ -0,0 +1,51 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*!
+ * \brief Table for converting ASCII characters to lowercase.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+/*!
+ * \brief Converts binary character to lowercase.
+ *
+ * \param c Character code.
+ *
+ * \return \a c converted to lowercase (or \a c if not applicable).
+ */
+static inline uint8_t knot_tolower(uint8_t c) {
+ const uint8_t *tolower_table = (uint8_t *)
+ "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F"
+ "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"
+ "\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2A\x2B\x2C\x2D\x2E\x2F"
+ "\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3A\x3B\x3C\x3D\x3E\x3F"
+ "\x40\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6A\x6B\x6C\x6D\x6E\x6F"
+ "\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7A\x5B\x5C\x5D\x5E\x5F"
+ "\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6A\x6B\x6C\x6D\x6E\x6F"
+ "\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7A\x7B\x7C\x7D\x7E\x7F"
+ "\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8A\x8B\x8C\x8D\x8E\x8F"
+ "\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9A\x9B\x9C\x9D\x9E\x9F"
+ "\xA0\xA1\xA2\xA3\xA4\xA5\xA6\xA7\xA8\xA9\xAA\xAB\xAC\xAD\xAE\xAF"
+ "\xB0\xB1\xB2\xB3\xB4\xB5\xB6\xB7\xB8\xB9\xBA\xBB\xBC\xBD\xBE\xBF"
+ "\xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF"
+ "\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD7\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF"
+ "\xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF"
+ "\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF7\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xFF";
+
+ return tolower_table[c];
+}
diff --git a/src/contrib/trim.h b/src/contrib/trim.h
new file mode 100644
index 0000000..c1b83fa
--- /dev/null
+++ b/src/contrib/trim.h
@@ -0,0 +1,35 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+/*!
+ * \brief Heap memory trimmer.
+ */
+
+#pragma once
+
+#ifdef HAVE_MALLOC_TRIM
+#include <malloc.h>
+#endif
+
+/*!
+ * \brief Trim excess heap memory.
+ */
+static inline void mem_trim(void)
+{
+#ifdef HAVE_MALLOC_TRIM
+ malloc_trim(0);
+#endif
+ return;
+}
diff --git a/src/contrib/ucw/LICENSE b/src/contrib/ucw/LICENSE
new file mode 100644
index 0000000..b463d57
--- /dev/null
+++ b/src/contrib/ucw/LICENSE
@@ -0,0 +1 @@
+../licenses/LGPL-2.0 \ No newline at end of file
diff --git a/src/contrib/ucw/array-sort.h b/src/contrib/ucw/array-sort.h
new file mode 100644
index 0000000..1ff1377
--- /dev/null
+++ b/src/contrib/ucw/array-sort.h
@@ -0,0 +1,195 @@
+/*
+ * UCW Library -- Universal Simple Array Sorter
+ *
+ * (c) 2003--2008 Martin Mares <mj@ucw.cz>
+ *
+ * This software may be freely distributed and used according to the terms
+ * of the GNU Lesser General Public License.
+ */
+
+#pragma once
+
+#include "contrib/macros.h"
+
+/*
+ * This is not a normal header file, it's a generator of sorting
+ * routines. Each time you include it with parameters set in the
+ * corresponding preprocessor macros, it generates an array sorter
+ * with the parameters given.
+ *
+ * You might wonder why the heck do we implement our own array sorter
+ * instead of using qsort(). The primary reason is that qsort handles
+ * only continuous arrays, but we need to sort array-like data structures
+ * where the only way to access elements is by using an indexing macro.
+ * Besides that, we are more than 2 times faster.
+ *
+ * So much for advocacy, there are the parameters (those marked with [*]
+ * are mandatory):
+ *
+ * ASORT_PREFIX(x) [*] add a name prefix (used on all global names
+ * defined by the sorter)
+ * ASORT_KEY_TYPE [*] data type of a single array entry key
+ * ASORT_ELT(i) returns the key of i-th element; if this macro is not
+ * defined, the function gets a pointer to an array to be sorted
+ * ASORT_LT(x,y) x < y for ASORT_KEY_TYPE (default: "x<y")
+ * ASORT_SWAP(i,j) swap i-th and j-th element (default: assume _ELT
+ * is an l-value and swap just the keys)
+ * ASORT_THRESHOLD threshold for switching between quicksort and insertsort
+ * ASORT_EXTRA_ARGS extra arguments for the sort function (they are always
+ * visible in all the macros supplied above), starts with comma
+ *
+ * After including this file, a function ASORT_PREFIX(sort)(unsigned array_size)
+ * or ASORT_PREFIX(sort)(ASORT_KEY_TYPE *array, unsigned array_size) [if ASORT_ELT
+ * is not defined] is declared and all parameter macros are automatically
+ * undef'd.
+ */
+
+#ifndef ASORT_LT
+#define ASORT_LT(x,y) ((x) < (y))
+#endif
+
+#ifndef ASORT_SWAP
+#define ASORT_SWAP(i,j) do { ASORT_KEY_TYPE tmp = ASORT_ELT(i); ASORT_ELT(i)=ASORT_ELT(j); ASORT_ELT(j)=tmp; } while (0)
+#endif
+
+#ifndef ASORT_THRESHOLD
+#define ASORT_THRESHOLD 8 /* Guesswork and experimentation */
+#endif
+
+#ifndef ASORT_EXTRA_ARGS
+#define ASORT_EXTRA_ARGS
+#endif
+
+#ifndef ASORT_ELT
+#define ASORT_ARRAY_ARG ASORT_KEY_TYPE *array,
+#define ASORT_ELT(i) array[i]
+#else
+#define ASORT_ARRAY_ARG
+#endif
+
+/**
+ * The generated sorting function. If `ASORT_ELT` macro is not provided, the
+ * @ASORT_ARRAY_ARG is equal to `ASORT_KEY_TYPE *array` and is the array to be
+ * sorted. If the macro is provided, this parameter is omitted. In that case,
+ * you can sort global variables or pass your structure by @ASORT_EXTRA_ARGS.
+ **/
+static void ASORT_PREFIX(sort)(ASORT_ARRAY_ARG unsigned array_size ASORT_EXTRA_ARGS)
+{
+ struct stk { int l, r; } stack[8*sizeof(unsigned)];
+ int l, r, left, right, m;
+ unsigned sp = 0;
+ ASORT_KEY_TYPE pivot;
+
+ if (array_size <= 1)
+ return;
+
+ /* QuickSort with optimizations a'la Sedgewick, but stop at ASORT_THRESHOLD */
+
+ left = 0;
+ right = array_size - 1;
+ for(;;)
+ {
+ l = left;
+ r = right;
+ m = (l+r)/2;
+ if (ASORT_LT(ASORT_ELT(m), ASORT_ELT(l)))
+ ASORT_SWAP(l,m);
+ if (ASORT_LT(ASORT_ELT(r), ASORT_ELT(m)))
+ {
+ ASORT_SWAP(m,r);
+ if (ASORT_LT(ASORT_ELT(m), ASORT_ELT(l)))
+ ASORT_SWAP(l,m);
+ }
+ pivot = ASORT_ELT(m);
+ do
+ {
+ while (ASORT_LT(ASORT_ELT(l), pivot))
+ l++;
+ while (ASORT_LT(pivot, ASORT_ELT(r)))
+ r--;
+ if (l < r)
+ {
+ ASORT_SWAP(l,r);
+ l++;
+ r--;
+ }
+ else if (l == r)
+ {
+ l++;
+ r--;
+ }
+ }
+ while (l <= r);
+ if ((r - left) >= ASORT_THRESHOLD && (right - l) >= ASORT_THRESHOLD)
+ {
+ /* Both partitions ok => push the larger one */
+ if ((r - left) > (right - l))
+ {
+ stack[sp].l = left;
+ stack[sp].r = r;
+ left = l;
+ }
+ else
+ {
+ stack[sp].l = l;
+ stack[sp].r = right;
+ right = r;
+ }
+ sp++;
+ }
+ else if ((r - left) >= ASORT_THRESHOLD)
+ {
+ /* Left partition OK, right undersize */
+ right = r;
+ }
+ else if ((right - l) >= ASORT_THRESHOLD)
+ {
+ /* Right partition OK, left undersize */
+ left = l;
+ }
+ else
+ {
+ /* Both partitions undersize => pop */
+ if (!sp)
+ break;
+ sp--;
+ left = stack[sp].l;
+ right = stack[sp].r;
+ }
+ }
+
+ /*
+ * We have a partially sorted array, finish by insertsort. Inspired
+ * by qsort() in GNU libc.
+ */
+
+ /* Find minimal element which will serve as a barrier */
+ r = MIN(array_size, ASORT_THRESHOLD);
+ m = 0;
+ for (l=1; l<r; l++)
+ if (ASORT_LT(ASORT_ELT(l),ASORT_ELT(m)))
+ m = l;
+ ASORT_SWAP(0,m);
+
+ /* Insertion sort */
+ for (m=1; m<(int)array_size; m++)
+ {
+ l=m;
+ while (ASORT_LT(ASORT_ELT(m),ASORT_ELT(l-1)))
+ l--;
+ while (l < m)
+ {
+ ASORT_SWAP(l,m);
+ l++;
+ }
+ }
+}
+
+#undef ASORT_PREFIX
+#undef ASORT_KEY_TYPE
+#undef ASORT_ELT
+#undef ASORT_LT
+#undef ASORT_SWAP
+#undef ASORT_THRESHOLD
+#undef ASORT_EXTRA_ARGS
+#undef ASORT_ARRAY_ARG
diff --git a/src/contrib/ucw/binsearch.h b/src/contrib/ucw/binsearch.h
new file mode 100644
index 0000000..b791d39
--- /dev/null
+++ b/src/contrib/ucw/binsearch.h
@@ -0,0 +1,50 @@
+/*
+ * UCW Library -- Generic Binary Search
+ *
+ * (c) 2005 Martin Mares <mj@ucw.cz>
+ *
+ * This software may be freely distributed and used according to the terms
+ * of the GNU Lesser General Public License.
+ */
+
+#pragma once
+
+/***
+ * [[defs]]
+ * Definitions
+ * -----------
+ ***/
+
+/**
+ * Find the first element not lower than \p x in the sorted array \p ary of \p N elements (non-decreasing order).
+ * Returns the index of the found element or \p N if no exists. Uses `ary_lt_x(ary,i,x)` to compare the i'th element with \p x.
+ * The time complexity is `O(log(N))`.
+ **/
+#define BIN_SEARCH_FIRST_GE_CMP(ary, N, ary_lt_x, x, ...) ({ \
+ unsigned l = 0, r = (N); \
+ while (l < r) \
+ { \
+ unsigned m = (l+r)/2; \
+ if (ary_lt_x(ary, m, x, __VA_ARGS__)) \
+ l = m+1; \
+ else \
+ r = m; \
+ } \
+ l; \
+})
+
+/**
+ * The default comparison macro for \ref BIN_SEARCH_FIRST_GE_CMP().
+ **/
+#define ARY_LT_NUM(ary,i,x) (ary)[i] < (x)
+
+/**
+ * Same as \ref BIN_SEARCH_FIRST_GE_CMP(), but uses the default `<` operator for comparisons.
+ **/
+#define BIN_SEARCH_FIRST_GE(ary,N,x) BIN_SEARCH_FIRST_GE_CMP(ary,N,ARY_LT_NUM,x)
+
+/**
+ * Search the sorted array \p ary of \p N elements (non-decreasing) for the first occurrence of \p x.
+ * Returns the index or -1 if no such element exists. Uses the `<` operator for comparisons.
+ **/
+#define BIN_SEARCH_EQ(ary,N,x) ({ int i = BIN_SEARCH_FIRST_GE(ary,N,x); if (i >= (N) || (ary)[i] != (x)) i=-1; i; })
diff --git a/src/contrib/ucw/heap.c b/src/contrib/ucw/heap.c
new file mode 100644
index 0000000..d7ed18e
--- /dev/null
+++ b/src/contrib/ucw/heap.c
@@ -0,0 +1,166 @@
+/*
+ * Binary heap
+ *
+ * (c) 2012 Ondrej Filip <feela@network.cz>
+ *
+ * This software may be freely distributed and used according to the terms
+ * of the GNU Lesser General Public License.
+ */
+
+/***
+ * Introduction
+ * ------------
+ *
+ * Binary heap is a simple data structure, which for example supports efficient insertions, deletions
+ * and access to the minimal inserted item. We define several macros for such operations.
+ * Note that because of simplicity of heaps, we have decided to define direct macros instead
+ * of a <<generic:,macro generator>> as for several other data structures in the Libucw.
+ *
+ * A heap is represented by a number of elements and by an array of values. Beware that we
+ * index this array from one, not from zero as do the standard C arrays.
+ *
+ * Most macros use these parameters:
+ *
+ * - @num - a variable (signed or unsigned integer) with the number of elements
+ * - @heap - a C array of type @type; the heap is stored in `heap[1] .. heap[num]`; `heap[0]` is unused
+ *
+ * A valid heap must follow these rules:
+ *
+ * - `num >= 0`
+ * - `heap[i] >= heap[i / 2]` for each `i` in `[2, num]`
+ *
+ * The first element `heap[1]` is always lower or equal to all other elements.
+ ***/
+
+#include <string.h>
+#include <stdlib.h>
+#include "contrib/ucw/heap.h"
+
+static inline void heap_swap(heap_val_t **e1, heap_val_t **e2)
+{
+ if (e1 == e2) return; /* Stack tmp should be faster than tmpelem. */
+ heap_val_t *tmp = *e1; /* Even faster than 2-XOR nowadays. */
+ *e1 = *e2;
+ *e2 = tmp;
+ int pos = (*e1)->pos;
+ (*e1)->pos = (*e2)->pos;
+ (*e2)->pos = pos;
+}
+
+int heap_init(struct heap *h, int (*cmp)(void *, void *), int init_size)
+{
+ int isize = init_size ? init_size : INITIAL_HEAP_SIZE;
+
+ h->num = 0;
+ h->max_size = isize;
+ h->cmp = cmp;
+ h->data = malloc((isize + 1) * sizeof(heap_val_t*)); /* Temp element unused. */
+
+ return h->data ? 1 : 0;
+}
+
+void heap_deinit(struct heap *h)
+{
+ free(h->data);
+ memset(h, 0, sizeof(*h));
+}
+
+static inline void _heap_bubble_down(struct heap *h, int e)
+{
+ int e1;
+ for (;;)
+ {
+ e1 = 2*e;
+ if(e1 > h->num) break;
+ if((h->cmp(*HELEMENT(h, e),*HELEMENT(h,e1)) < 0) && (e1 == h->num || (h->cmp(*HELEMENT(h, e),*HELEMENT(h,e1+1)) < 0))) break;
+ if((e1 != h->num) && (h->cmp(*HELEMENT(h, e1+1), *HELEMENT(h,e1)) < 0)) e1++;
+ heap_swap(HELEMENT(h,e),HELEMENT(h,e1));
+ e = e1;
+ }
+}
+
+static inline void _heap_bubble_up(struct heap *h, int e)
+{
+ int e1;
+ while (e > 1)
+ {
+ e1 = e/2;
+ if(h->cmp(*HELEMENT(h, e1),*HELEMENT(h,e)) < 0) break;
+ heap_swap(HELEMENT(h,e),HELEMENT(h,e1));
+ e = e1;
+ }
+
+}
+
+static void heap_increase(struct heap *h, int pos, heap_val_t *e)
+{
+ *HELEMENT(h, pos) = e;
+ e->pos = pos;
+ _heap_bubble_down(h, pos);
+}
+
+static void heap_decrease(struct heap *h, int pos, heap_val_t *e)
+{
+ *HELEMENT(h, pos) = e;
+ e->pos = pos;
+ _heap_bubble_up(h, pos);
+}
+
+void heap_replace(struct heap *h, int pos, heap_val_t *e)
+{
+ if (h->cmp(*HELEMENT(h, pos),e) < 0) {
+ heap_increase(h, pos, e);
+ } else {
+ heap_decrease(h, pos, e);
+ }
+}
+
+void heap_delmin(struct heap *h)
+{
+ if(h->num == 0) return;
+ if(h->num > 1)
+ {
+ heap_swap(HHEAD(h),HELEMENT(h,h->num));
+ }
+ (*HELEMENT(h, h->num))->pos = 0;
+ --h->num;
+ _heap_bubble_down(h, 1);
+}
+
+int heap_insert(struct heap *h, heap_val_t *e)
+{
+ if(h->num == h->max_size)
+ {
+ h->max_size = h->max_size * HEAP_INCREASE_STEP;
+ h->data = realloc(h->data, (h->max_size + 1) * sizeof(heap_val_t*));
+ if (!h->data) {
+ return 0;
+ }
+ }
+
+ h->num++;
+ *HELEMENT(h,h->num) = e;
+ e->pos = h->num;
+ _heap_bubble_up(h,h->num);
+ return 1;
+}
+
+int heap_find(struct heap *h, heap_val_t *elm)
+{
+ return ((struct heap_val *) elm)->pos;
+}
+
+void heap_delete(struct heap *h, int e)
+{
+ heap_swap(HELEMENT(h, e), HELEMENT(h, h->num));
+ (*HELEMENT(h, h->num))->pos = 0;
+ h->num--;
+ if(h->cmp(*HELEMENT(h, e), *HELEMENT(h, h->num + 1)) < 0) _heap_bubble_up(h, e);
+ else _heap_bubble_down(h, e);
+
+ if ((h->num > INITIAL_HEAP_SIZE) && (h->num < h->max_size / HEAP_DECREASE_THRESHOLD))
+ {
+ h->max_size = h->max_size / HEAP_INCREASE_STEP;
+ h->data = realloc(h->data, (h->max_size + 1) * sizeof(heap_val_t*));
+ }
+}
diff --git a/src/contrib/ucw/heap.h b/src/contrib/ucw/heap.h
new file mode 100644
index 0000000..7419b34
--- /dev/null
+++ b/src/contrib/ucw/heap.h
@@ -0,0 +1,46 @@
+/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+struct heap_val {
+ int pos;
+};
+
+typedef struct heap_val heap_val_t;
+
+struct heap {
+ int num; /* Number of elements */
+ int max_size; /* Size of allocated memory */
+ int (*cmp)(void *, void *);
+ heap_val_t **data;
+}; /* Array follows */
+
+#define INITIAL_HEAP_SIZE 512 /* initial heap size */
+#define HEAP_INCREASE_STEP 2 /* multiplier for each inflation, keep conservative */
+#define HEAP_DECREASE_THRESHOLD 2 /* threshold for deflation, keep conservative */
+#define HELEMENT(h,num) ((h)->data + (num))
+#define HHEAD(h) HELEMENT((h), 1)
+#define EMPTY_HEAP(h) ((h)->num == 0) /* h->num == 0 */
+
+int heap_init(struct heap *, int (*cmp)(void *, void *), int);
+void heap_deinit(struct heap *);
+
+void heap_delmin(struct heap *);
+int heap_insert(struct heap *, heap_val_t *);
+int heap_find(struct heap *, heap_val_t *);
+void heap_delete(struct heap *, int);
+void heap_replace(struct heap *h, int pos, heap_val_t *);
diff --git a/src/contrib/ucw/lists.c b/src/contrib/ucw/lists.c
new file mode 100644
index 0000000..8a9fa96
--- /dev/null
+++ b/src/contrib/ucw/lists.c
@@ -0,0 +1,235 @@
+/*
+ * BIRD Library -- Linked Lists
+ *
+ * (c) 1998 Martin Mares <mj@ucw.cz>
+ * (c) 2015, 2017 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+/**
+ * DOC: Linked lists
+ *
+ * The BIRD library provides a set of functions for operating on linked
+ * lists. The lists are internally represented as standard doubly linked
+ * lists with synthetic head and tail which makes all the basic operations
+ * run in constant time and contain no extra end-of-list checks. Each list
+ * is described by a &list structure, nodes can have any format as long
+ * as they start with a &node structure. If you want your nodes to belong
+ * to multiple lists at once, you can embed multiple &node structures in them
+ * and use the SKIP_BACK() macro to calculate a pointer to the start of the
+ * structure from a &node pointer, but beware of obscurity.
+ *
+ * There also exist safe linked lists (&slist, &snode and all functions
+ * being prefixed with |s_|) which support asynchronous walking very
+ * similar to that used in the &fib structure.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include "contrib/ucw/lists.h"
+#include "contrib/mempattern.h"
+
+/**
+ * add_tail - append a node to a list
+ * \p l: linked list
+ * \p n: list node
+ *
+ * add_tail() takes a node \p n and appends it at the end of the list \p l.
+ */
+void
+add_tail(list_t *l, node_t *n)
+{
+ node_t *z = l->tail;
+
+ n->next = (node_t *) &l->null;
+ n->prev = z;
+ z->next = n;
+ l->tail = n;
+}
+
+/**
+ * add_head - prepend a node to a list
+ * \p l: linked list
+ * \p n: list node
+ *
+ * add_head() takes a node \p n and prepends it at the start of the list \p l.
+ */
+void
+add_head(list_t *l, node_t *n)
+{
+ node_t *z = l->head;
+
+ n->next = z;
+ n->prev = (node_t *) &l->head;
+ z->prev = n;
+ l->head = n;
+}
+
+/**
+ * insert_node - insert a node to a list
+ * \p n: a new list node
+ * \p after: a node of a list
+ *
+ * Inserts a node \p n to a linked list after an already inserted
+ * node \p after.
+ */
+void
+insert_node(node_t *n, node_t *after)
+{
+ node_t *z = after->next;
+
+ n->next = z;
+ n->prev = after;
+ after->next = n;
+ z->prev = n;
+}
+
+/**
+ * rem_node - remove a node from a list
+ * \p n: node to be removed
+ *
+ * Removes a node \p n from the list it's linked in.
+ */
+void
+rem_node(node_t *n)
+{
+ node_t *z = n->prev;
+ node_t *x = n->next;
+
+ z->next = x;
+ x->prev = z;
+ n->prev = 0;
+ n->next = 0;
+}
+
+/**
+ * init_list - create an empty list
+ * \p l: list
+ *
+ * init_list() takes a &list structure and initializes its
+ * fields, so that it represents an empty list.
+ */
+void
+init_list(list_t *l)
+{
+ l->head = (node_t *) &l->null;
+ l->null = NULL;
+ l->tail = (node_t *) &l->head;
+}
+
+/**
+ * add_tail_list - concatenate two lists
+ * \p to: destination list
+ * \p l: source list
+ *
+ * This function appends all elements of the list \p l to
+ * the list \p to in constant time.
+ */
+void
+add_tail_list(list_t *to, list_t *l)
+{
+ node_t *p = to->tail;
+ node_t *q = l->head;
+
+ p->next = q;
+ q->prev = p;
+ q = l->tail;
+ q->next = (node_t *) &to->null;
+ to->tail = q;
+}
+
+/**
+ * list_dup - duplicate list
+ * \p to: destination list
+ * \p l: source list
+ *
+ * This function duplicates all elements of the list \p l to
+ * the list \p to in linear time.
+ *
+ * This function only works with a homogenous item size.
+ */
+void list_dup(list_t *dst, list_t *src, size_t itemsz)
+{
+ node_t *n = 0;
+ WALK_LIST(n, *src) {
+ node_t *i = malloc(itemsz);
+ memcpy(i, n, itemsz);
+ add_tail(dst, i);
+ }
+}
+
+/**
+ * list_size - gets number of nodes
+ * \p l: list
+ *
+ * This function counts nodes in list \p l and returns this number.
+ */
+size_t list_size(const list_t *l)
+{
+ size_t count = 0;
+
+ node_t *n = 0;
+ WALK_LIST(n, *l) {
+ count++;
+ }
+
+ return count;
+}
+
+/**
+ * ptrlist_add - add pointer to pointer list
+ * \p to: destination list
+ * \p val: added pointer
+ * \p mm: memory context
+ */
+ptrnode_t *ptrlist_add(list_t *to, void *val, knot_mm_t *mm)
+{
+ ptrnode_t *node = mm_alloc(mm , sizeof(ptrnode_t));
+ if (node == NULL) {
+ return NULL;
+ } else {
+ node->d = val;
+ }
+ add_tail(to, &node->n);
+ return node;
+}
+
+/**
+ * ptrlist_free - free all nodes in pointer list
+ * \p list: list nodes
+ * \p mm: memory context
+ */
+void ptrlist_free(list_t *list, knot_mm_t *mm)
+{
+ node_t *n = NULL, *nxt = NULL;
+ WALK_LIST_DELSAFE(n, nxt, *list) {
+ mm_free(mm, n);
+ }
+ init_list(list);
+}
+
+/**
+ * ptrlist_rem - remove pointer node
+ * \p val: pointer to remove
+ * \p mm: memory context
+ */
+void ptrlist_rem(ptrnode_t *node, knot_mm_t *mm)
+{
+ rem_node(&node->n);
+ mm_free(mm, node);
+}
+
+/**
+ * ptrlist_deep_free - free all nodes incl referenced data
+ * \p list: list nodes
+ * \p mm: memory context
+ */
+void ptrlist_deep_free(list_t *l, knot_mm_t *mm)
+{
+ ptrnode_t *n;
+ WALK_LIST(n, *l) {
+ mm_free(mm, n->d);
+ }
+ ptrlist_free(l, mm);
+}
diff --git a/src/contrib/ucw/lists.h b/src/contrib/ucw/lists.h
new file mode 100644
index 0000000..922e152
--- /dev/null
+++ b/src/contrib/ucw/lists.h
@@ -0,0 +1,84 @@
+/*
+ * BIRD Library -- Linked Lists
+ *
+ * (c) 1998 Martin Mares <mj@ucw.cz>
+ * (c) 2015, 2017 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#pragma once
+
+/*
+ * I admit the list structure is very tricky and also somewhat awkward,
+ * but it's both efficient and easy to manipulate once one understands the
+ * basic trick: The list head always contains two synthetic nodes which are
+ * always present in the list: the head and the tail. But as the `next'
+ * entry of the tail and the `prev' entry of the head are both NULL, the
+ * nodes can overlap each other:
+ *
+ * head head_node.next
+ * null head_node.prev tail_node.next
+ * tail tail_node.prev
+ */
+
+#include <string.h>
+#include "libknot/mm_ctx.h"
+
+typedef struct node {
+ struct node *next, *prev;
+} node_t;
+
+typedef struct list { /* In fact two overlayed nodes */
+ struct node *head, *null, *tail;
+} list_t;
+
+#define NODE (node_t *)
+#define HEAD(list) ((void *)((list).head))
+#define TAIL(list) ((void *)((list).tail))
+#define WALK_LIST(n,list) for(n=HEAD(list);(NODE (n))->next; \
+ n=(void *)((NODE (n))->next))
+#define WALK_LIST_DELSAFE(n,nxt,list) \
+ for(n=HEAD(list); (nxt=(void *)((NODE (n))->next)); n=(void *) nxt)
+/* WALK_LIST_FIRST supposes that called code removes each processed node */
+#define WALK_LIST_FIRST(n,list) \
+ while(n=HEAD(list), (NODE (n))->next)
+#define WALK_LIST_BACKWARDS(n,list) for(n=TAIL(list);(NODE (n))->prev; \
+ n=(void *)((NODE (n))->prev))
+#define WALK_LIST_BACKWARDS_DELSAFE(n,prv,list) \
+ for(n=TAIL(list); prv=(void *)((NODE (n))->prev); n=(void *) prv)
+
+#define EMPTY_LIST(list) (!(list).head->next)
+
+/*! \brief Free every node in the list. */
+#define WALK_LIST_FREE(list) \
+ do { \
+ node_t *n=0,*nxt=0; \
+ WALK_LIST_DELSAFE(n,nxt,list) { \
+ free(n); \
+ } \
+ init_list(&list); \
+ } while(0)
+
+void add_tail(list_t *, node_t *);
+void add_head(list_t *, node_t *);
+void rem_node(node_t *);
+void add_tail_list(list_t *, list_t *);
+void init_list(list_t *);
+void insert_node(node_t *, node_t *);
+void list_dup(list_t *dst, list_t *src, size_t itemsz);
+size_t list_size(const list_t *);
+
+/*!
+ * \brief Generic pointer list implementation.
+ */
+typedef struct ptrnode {
+ node_t n;
+ void *d;
+} ptrnode_t;
+
+ptrnode_t *ptrlist_add(list_t *, void *, knot_mm_t *);
+void ptrlist_free(list_t *, knot_mm_t *);
+void ptrlist_rem(ptrnode_t *node, knot_mm_t *mm);
+void ptrlist_deep_free(list_t *, knot_mm_t *);
+
diff --git a/src/contrib/ucw/mempool.c b/src/contrib/ucw/mempool.c
new file mode 100644
index 0000000..bc41345
--- /dev/null
+++ b/src/contrib/ucw/mempool.c
@@ -0,0 +1,322 @@
+/*
+ * UCW Library -- Memory Pools (One-Time Allocation)
+ *
+ * (c) 1997--2001 Martin Mares <mj@ucw.cz>
+ * (c) 2007 Pavel Charvat <pchar@ucw.cz>
+ * (c) 2015, 2017 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+ *
+ * This software may be freely distributed and used according to the terms
+ * of the GNU Lesser General Public License.
+ */
+
+#undef LOCAL_DEBUG
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <assert.h>
+#include "contrib/asan.h"
+#include "contrib/macros.h"
+#include "contrib/ucw/mempool.h"
+
+/** \todo This shouldn't be precalculated, but computed on load. */
+#define CPU_PAGE_SIZE 4096
+
+/** Align an integer \p s to the nearest higher multiple of \p a (which should be a power of two) **/
+#define ALIGN_TO(s, a) (((s)+a-1)&~(a-1))
+#define MP_CHUNK_TAIL ALIGN_TO(sizeof(struct mempool_chunk), CPU_STRUCT_ALIGN)
+#define MP_SIZE_MAX (~0U - MP_CHUNK_TAIL - CPU_PAGE_SIZE)
+#define DBG(s, ...)
+
+/** \note Imported MMAP backend from bigalloc.c */
+#define CONFIG_UCW_POOL_IS_MMAP
+#ifdef CONFIG_UCW_POOL_IS_MMAP
+#include <sys/mman.h>
+static void *
+page_alloc(uint64_t len)
+{
+ if (!len) {
+ return NULL;
+ }
+ if (len > SIZE_MAX) {
+ return NULL;
+ }
+ assert(!(len & (CPU_PAGE_SIZE-1)));
+ uint8_t *p = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
+ if (p == (uint8_t*) MAP_FAILED) {
+ return NULL;
+ }
+ return p;
+}
+
+static void
+page_free(void *start, uint64_t len)
+{
+ assert(!(len & (CPU_PAGE_SIZE-1)));
+ assert(!((uintptr_t) start & (CPU_PAGE_SIZE-1)));
+ munmap(start, len);
+}
+#endif
+
+struct mempool_chunk {
+ struct mempool_chunk *next;
+ unsigned size;
+};
+
+static unsigned
+mp_align_size(unsigned size)
+{
+#ifdef CONFIG_UCW_POOL_IS_MMAP
+ return ALIGN_TO(size + MP_CHUNK_TAIL, CPU_PAGE_SIZE) - MP_CHUNK_TAIL;
+#else
+ return ALIGN_TO(size, CPU_STRUCT_ALIGN);
+#endif
+}
+
+void
+mp_init(struct mempool *pool, unsigned chunk_size)
+{
+ chunk_size = mp_align_size(MAX(sizeof(struct mempool), chunk_size));
+ *pool = (struct mempool) {
+ .chunk_size = chunk_size,
+ .threshold = chunk_size >> 1,
+ .last_big = &pool->last_big
+ };
+}
+
+static void *
+mp_new_big_chunk(unsigned size)
+{
+ uint8_t *data = malloc(size + MP_CHUNK_TAIL);
+ if (!data) {
+ return NULL;
+ }
+ ASAN_POISON_MEMORY_REGION(data, size);
+ struct mempool_chunk *chunk = (struct mempool_chunk *)(data + size);
+ chunk->size = size;
+ return chunk;
+}
+
+static void
+mp_free_big_chunk(struct mempool_chunk *chunk)
+{
+ void *ptr = (uint8_t *)chunk - chunk->size;
+ ASAN_UNPOISON_MEMORY_REGION(ptr, chunk->size);
+ free(ptr);
+}
+
+static void *
+mp_new_chunk(unsigned size)
+{
+#ifdef CONFIG_UCW_POOL_IS_MMAP
+ uint8_t *data = page_alloc(size + MP_CHUNK_TAIL);
+ if (!data) {
+ return NULL;
+ }
+ ASAN_POISON_MEMORY_REGION(data, size);
+ struct mempool_chunk *chunk = (struct mempool_chunk *)(data + size);
+ chunk->size = size;
+ return chunk;
+#else
+ return mp_new_big_chunk(size);
+#endif
+}
+
+static void
+mp_free_chunk(struct mempool_chunk *chunk)
+{
+#ifdef CONFIG_UCW_POOL_IS_MMAP
+ uint8_t *data = (uint8_t *)chunk - chunk->size;
+ ASAN_UNPOISON_MEMORY_REGION(data, chunk->size);
+ page_free(data, chunk->size + MP_CHUNK_TAIL);
+#else
+ mp_free_big_chunk(chunk);
+#endif
+}
+
+struct mempool *
+mp_new(unsigned chunk_size)
+{
+ chunk_size = mp_align_size(MAX(sizeof(struct mempool), chunk_size));
+ struct mempool_chunk *chunk = mp_new_chunk(chunk_size);
+ struct mempool *pool = (void *)chunk - chunk_size;
+ ASAN_UNPOISON_MEMORY_REGION(pool, sizeof(*pool));
+ DBG("Creating mempool %p with %u bytes long chunks", pool, chunk_size);
+ chunk->next = NULL;
+ ASAN_POISON_MEMORY_REGION(chunk, sizeof(struct mempool_chunk));
+ *pool = (struct mempool) {
+ .state = { .free = { chunk_size - sizeof(*pool) }, .last = { chunk } },
+ .chunk_size = chunk_size,
+ .threshold = chunk_size >> 1,
+ .last_big = &pool->last_big
+ };
+ return pool;
+}
+
+static void
+mp_free_chain(struct mempool_chunk *chunk)
+{
+ while (chunk) {
+ ASAN_UNPOISON_MEMORY_REGION(chunk, sizeof(struct mempool_chunk));
+ struct mempool_chunk *next = chunk->next;
+ mp_free_chunk(chunk);
+ chunk = next;
+ }
+}
+
+static void
+mp_free_big_chain(struct mempool_chunk *chunk)
+{
+ while (chunk) {
+ ASAN_UNPOISON_MEMORY_REGION(chunk, sizeof(struct mempool_chunk));
+ struct mempool_chunk *next = chunk->next;
+ mp_free_big_chunk(chunk);
+ chunk = next;
+ }
+}
+
+void
+mp_delete(struct mempool *pool)
+{
+ if (pool == NULL) {
+ return;
+ }
+ DBG("Deleting mempool %p", pool);
+ mp_free_big_chain(pool->state.last[1]);
+ mp_free_chain(pool->unused);
+ mp_free_chain(pool->state.last[0]); // can contain the mempool structure
+}
+
+void
+mp_flush(struct mempool *pool)
+{
+ mp_free_big_chain(pool->state.last[1]);
+ struct mempool_chunk *chunk = pool->state.last[0], *next;
+ while (chunk) {
+ ASAN_UNPOISON_MEMORY_REGION(chunk, sizeof(struct mempool_chunk));
+ if ((uint8_t *)chunk - chunk->size == (uint8_t *)pool) {
+ break;
+ }
+ next = chunk->next;
+ chunk->next = pool->unused;
+ ASAN_POISON_MEMORY_REGION(chunk, sizeof(struct mempool_chunk));
+ pool->unused = chunk;
+ chunk = next;
+ }
+ pool->state.last[0] = chunk;
+ if (chunk) {
+ pool->state.free[0] = chunk->size - sizeof(*pool);
+ ASAN_POISON_MEMORY_REGION(chunk, sizeof(struct mempool_chunk));
+ } else {
+ pool->state.free[0] = 0;
+ }
+ pool->state.last[1] = NULL;
+ pool->state.free[1] = 0;
+ pool->last_big = &pool->last_big;
+}
+
+static void
+mp_stats_chain(struct mempool_chunk *chunk, struct mempool_stats *stats, unsigned idx)
+{
+ struct mempool_chunk *next;
+ while (chunk) {
+ ASAN_UNPOISON_MEMORY_REGION(chunk, sizeof(struct mempool_chunk));
+ stats->chain_size[idx] += chunk->size + sizeof(*chunk);
+ stats->chain_count[idx]++;
+ next = chunk->next;
+ ASAN_POISON_MEMORY_REGION(chunk, sizeof(struct mempool_chunk));
+ chunk = next;
+ }
+ stats->total_size += stats->chain_size[idx];
+}
+
+void
+mp_stats(struct mempool *pool, struct mempool_stats *stats)
+{
+ bzero(stats, sizeof(*stats));
+ mp_stats_chain(pool->state.last[0], stats, 0);
+ mp_stats_chain(pool->state.last[1], stats, 1);
+ mp_stats_chain(pool->unused, stats, 2);
+}
+
+uint64_t
+mp_total_size(struct mempool *pool)
+{
+ struct mempool_stats stats;
+ mp_stats(pool, &stats);
+ return stats.total_size;
+}
+
+static void *
+mp_alloc_internal(struct mempool *pool, unsigned size)
+{
+ struct mempool_chunk *chunk;
+ if (size <= pool->threshold) {
+ pool->idx = 0;
+ if (pool->unused) {
+ chunk = pool->unused;
+ ASAN_UNPOISON_MEMORY_REGION(chunk, sizeof(struct mempool_chunk));
+ pool->unused = chunk->next;
+ } else {
+ chunk = mp_new_chunk(pool->chunk_size);
+ }
+ chunk->next = pool->state.last[0];
+ ASAN_POISON_MEMORY_REGION(chunk, sizeof(struct mempool_chunk));
+ pool->state.last[0] = chunk;
+ pool->state.free[0] = pool->chunk_size - size;
+ return (uint8_t *)chunk - pool->chunk_size;
+ } else if (size <= MP_SIZE_MAX) {
+ pool->idx = 1;
+ unsigned aligned = ALIGN_TO(size, CPU_STRUCT_ALIGN);
+ chunk = mp_new_big_chunk(aligned);
+ if (!chunk) {
+ return NULL;
+ }
+ chunk->next = pool->state.last[1];
+ ASAN_POISON_MEMORY_REGION(chunk, sizeof(struct mempool_chunk));
+ pool->state.last[1] = chunk;
+ pool->state.free[1] = aligned - size;
+ return pool->last_big = (uint8_t *)chunk - aligned;
+ } else {
+ fprintf(stderr, "Cannot allocate %u bytes from a mempool", size);
+ assert(0);
+ return NULL;
+ }
+}
+
+void *
+mp_alloc(struct mempool *pool, unsigned size)
+{
+ unsigned avail = pool->state.free[0] & ~(CPU_STRUCT_ALIGN - 1);
+ void *ptr = NULL;
+ if (size <= avail) {
+ pool->state.free[0] = avail - size;
+ ptr = (uint8_t*)pool->state.last[0] - avail;
+ } else {
+ ptr = mp_alloc_internal(pool, size);
+ }
+ ASAN_UNPOISON_MEMORY_REGION(ptr, size);
+ return ptr;
+}
+
+void *
+mp_alloc_noalign(struct mempool *pool, unsigned size)
+{
+ void *ptr = NULL;
+ if (size <= pool->state.free[0]) {
+ ptr = (uint8_t*)pool->state.last[0] - pool->state.free[0];
+ pool->state.free[0] -= size;
+ } else {
+ ptr = mp_alloc_internal(pool, size);
+ }
+ ASAN_UNPOISON_MEMORY_REGION(ptr, size);
+ return ptr;
+}
+
+void *
+mp_alloc_zero(struct mempool *pool, unsigned size)
+{
+ void *ptr = mp_alloc(pool, size);
+ bzero(ptr, size);
+ return ptr;
+}
diff --git a/src/contrib/ucw/mempool.h b/src/contrib/ucw/mempool.h
new file mode 100644
index 0000000..c5a4fa8
--- /dev/null
+++ b/src/contrib/ucw/mempool.h
@@ -0,0 +1,124 @@
+/*
+ * UCW Library -- Memory Pools
+ *
+ * (c) 1997--2005 Martin Mares <mj@ucw.cz>
+ * (c) 2007 Pavel Charvat <pchar@ucw.cz>
+ * (c) 2015, 2017 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+ *
+ * This software may be freely distributed and used according to the terms
+ * of the GNU Lesser General Public License.
+ */
+
+#pragma once
+
+#include <string.h>
+#include <stdint.h>
+
+#define CPU_STRUCT_ALIGN (sizeof(void*))
+
+/***
+ * [[defs]]
+ * Definitions
+ * -----------
+ ***/
+
+/**
+ * Memory pool state (see mp_push(), ...).
+ * You should use this one as an opaque handle only, the insides are internal.
+ **/
+struct mempool_state {
+ unsigned free[2];
+ void *last[2];
+};
+
+/**
+ * Memory pool.
+ * You should use this one as an opaque handle only, the insides are internal.
+ **/
+struct mempool {
+ struct mempool_state state;
+ void *unused, *last_big;
+ unsigned chunk_size, threshold, idx;
+};
+
+struct mempool_stats { /** Mempool statistics. See mp_stats(). **/
+ uint64_t total_size; /** Real allocated size in bytes. */
+ unsigned chain_count[3]; /** Number of allocated chunks in small/big/unused chains. */
+ unsigned chain_size[3]; /** Size of allocated chunks in small/big/unused chains. */
+};
+
+/***
+ * [[basic]]
+ * Basic manipulation
+ * ------------------
+ ***/
+
+/**
+ * Initialize a given mempool structure.
+ * \p chunk_size must be in the interval `[1, UINT_MAX / 2]`.
+ * It will allocate memory by this large chunks and take
+ * memory to satisfy requests from them.
+ *
+ * Memory pools can be treated as <<trans:respools,resources>>, see <<trans:res_mempool()>>.
+ **/
+void mp_init(struct mempool *pool, unsigned chunk_size);
+
+/**
+ * Allocate and initialize a new memory pool.
+ * See \ref mp_init() for \p chunk_size limitations.
+ *
+ * The new mempool structure is allocated on the new mempool.
+ *
+ * Memory pools can be treated as <<trans:respools,resources>>, see <<trans:res_mempool()>>.
+ **/
+struct mempool *mp_new(unsigned chunk_size);
+
+/**
+ * Cleanup mempool initialized by mp_init or mp_new.
+ * Frees all the memory allocated by this mempool and,
+ * if created by \ref mp_new(), the \p pool itself.
+ **/
+void mp_delete(struct mempool *pool);
+
+/**
+ * Frees all data on a memory pool, but leaves it working.
+ * It can keep some of the chunks allocated to serve
+ * further allocation requests. Leaves the \p pool alive,
+ * even if it was created with \ref mp_new().
+ **/
+void mp_flush(struct mempool *pool);
+
+/**
+ * Compute some statistics for debug purposes.
+ * See the definition of the <<struct_mempool_stats,mempool_stats structure>>.
+ **/
+void mp_stats(struct mempool *pool, struct mempool_stats *stats);
+uint64_t mp_total_size(struct mempool *pool); /** How many bytes were allocated by the pool. **/
+
+/***
+ * [[alloc]]
+ * Allocation routines
+ * -------------------
+ ***/
+
+/**
+ * The function allocates new \p size bytes on a given memory pool.
+ * If the \p size is zero, the resulting pointer is undefined,
+ * but it may be safely reallocated or used as the parameter
+ * to other functions below.
+ *
+ * The resulting pointer is always aligned to a multiple of
+ * `CPU_STRUCT_ALIGN` bytes and this condition remains true also
+ * after future reallocations.
+ **/
+void *mp_alloc(struct mempool *pool, unsigned size);
+
+/**
+ * The same as \ref mp_alloc(), but the result may be unaligned.
+ **/
+void *mp_alloc_noalign(struct mempool *pool, unsigned size);
+
+/**
+ * The same as \ref mp_alloc(), but fills the newly allocated memory with zeroes.
+ **/
+void *mp_alloc_zero(struct mempool *pool, unsigned size);
diff --git a/src/contrib/wire_ctx.h b/src/contrib/wire_ctx.h
new file mode 100644
index 0000000..25ff143
--- /dev/null
+++ b/src/contrib/wire_ctx.h
@@ -0,0 +1,361 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include "libknot/endian.h"
+#include "libknot/errcode.h"
+
+/*!
+ * \brief Struct to keep the wire context.
+ */
+typedef struct wire_ctx {
+ size_t size;
+ uint8_t *wire;
+ uint8_t *position;
+ int error;
+ bool readonly;
+} wire_ctx_t;
+
+/*!
+ * \brief Initialize wire context.
+ */
+static inline wire_ctx_t wire_ctx_init(uint8_t *data, size_t size)
+{
+ assert(data);
+
+ wire_ctx_t result = {
+ .size = size,
+ .wire = data,
+ .position = data,
+ .error = KNOT_EOK,
+ .readonly = false
+ };
+
+ return result;
+}
+
+/*!
+ * \brief Initialize read only wire context.
+ *
+ * \note No write is performed, and error is set to KNOT_EACCES.
+ *
+ */
+static inline wire_ctx_t wire_ctx_init_const(const uint8_t *data, size_t size)
+{
+ assert(data);
+
+ wire_ctx_t result = wire_ctx_init((uint8_t *)data, size);
+ result.readonly = true;
+
+ return result;
+}
+
+/*!
+ * \brief Gets actual position.
+ *
+ * \return position from the begin.
+ */
+static inline size_t wire_ctx_offset(wire_ctx_t *ctx)
+{
+ assert(ctx);
+
+ return ctx->position - ctx->wire;
+}
+
+/*!
+ * \brief Set position offset from the begin.
+ *
+ * \param offset Wire offset (starts from 0).
+ *
+ * \note Noop if previous error.
+ */
+static inline void wire_ctx_set_offset(wire_ctx_t *ctx, size_t offset)
+{
+ assert(ctx);
+
+ if (ctx->error != KNOT_EOK) {
+ return;
+ }
+
+ if (offset > ctx->size) {
+ ctx->error = KNOT_ERANGE;
+ return;
+ }
+
+ ctx->position = ctx->wire + offset;
+}
+
+/*!
+ * \brief Gets available bytes.
+ *
+ * \return Number of bytes to end.
+ */
+static inline size_t wire_ctx_available(wire_ctx_t *ctx)
+{
+ assert(ctx);
+
+ return ctx->size - wire_ctx_offset(ctx);
+}
+
+/*!
+ * \brief Add offset to the current position.
+ *
+ * \note Noop if previous error.
+ */
+static inline void wire_ctx_skip(wire_ctx_t *ctx, ssize_t offset)
+{
+ assert(ctx);
+
+ if (ctx->error != KNOT_EOK) {
+ return;
+ }
+
+ // Check for out of scope skip.
+ if (offset >= 0) {
+ if (offset > wire_ctx_available(ctx)) {
+ ctx->error = KNOT_ERANGE;
+ return;
+ }
+ } else {
+ if (-offset > wire_ctx_offset(ctx)) {
+ ctx->error = KNOT_ERANGE;
+ return;
+ }
+ }
+
+ ctx->position += offset;
+}
+
+/*!
+ * \brief Check the context if reading is possible.
+ */
+static inline int wire_ctx_can_read(wire_ctx_t *ctx, size_t size)
+{
+ assert(ctx);
+
+ if (ctx->error != KNOT_EOK) {
+ return ctx->error;
+ }
+
+ if (wire_ctx_available(ctx) < size) {
+ return KNOT_EFEWDATA;
+ }
+
+ return KNOT_EOK;
+}
+
+/*!
+ * \brief Check the context if writing is possible.
+ */
+static inline int wire_ctx_can_write(wire_ctx_t *ctx, size_t size)
+{
+ assert(ctx);
+
+ if (ctx->error != KNOT_EOK) {
+ return ctx->error;
+ }
+
+ if (ctx->readonly) {
+ return KNOT_EACCES;
+ }
+
+ if (wire_ctx_available(ctx) < size) {
+ return KNOT_ESPACE;
+ }
+
+ return KNOT_EOK;
+}
+
+
+static inline void wire_ctx_read(wire_ctx_t *ctx, void *data, size_t size)
+{
+ assert(ctx);
+ assert(data);
+
+ if (ctx->error != KNOT_EOK) {
+ /* Avoid leaving data uninitialized. */
+ memset(data, 0, size);
+ return;
+ }
+
+ int ret = wire_ctx_can_read(ctx, size);
+ if (ret != KNOT_EOK) {
+ ctx->error = ret;
+ memset(data, 0, size);
+ return;
+ }
+
+ memcpy(data, ctx->position, size);
+ ctx->position += size;
+}
+
+static inline uint8_t wire_ctx_read_u8(wire_ctx_t *ctx)
+{
+ uint8_t result;
+ wire_ctx_read(ctx, &result, sizeof(result));
+
+ return result;
+}
+
+static inline uint16_t wire_ctx_read_u16(wire_ctx_t *ctx)
+{
+ uint16_t result;
+ wire_ctx_read(ctx, &result, sizeof(result));
+
+ return be16toh(result);
+}
+
+static inline uint32_t wire_ctx_read_u32(wire_ctx_t *ctx)
+{
+ uint32_t result;
+ wire_ctx_read(ctx, &result, sizeof(result));
+
+ return be32toh(result);
+}
+
+static inline uint64_t wire_ctx_read_u48(wire_ctx_t *ctx)
+{
+ /* This case is slightly tricky. */
+ uint64_t result = 0;
+ wire_ctx_read(ctx, (uint8_t *)&result + 1, 6);
+
+ return be64toh(result) >> 8;
+}
+
+static inline uint64_t wire_ctx_read_u64(wire_ctx_t *ctx)
+{
+ uint64_t result;
+ wire_ctx_read(ctx, &result, sizeof(result));
+
+ return be64toh(result);
+}
+
+
+static inline void wire_ctx_write(wire_ctx_t *ctx, const void *data, size_t size)
+{
+ assert(ctx);
+
+ if (ctx->error != KNOT_EOK) {
+ return;
+ }
+
+ if (size == 0) {
+ return;
+ }
+
+ assert(data);
+
+ int ret = wire_ctx_can_write(ctx, size);
+ if (ret != KNOT_EOK) {
+ ctx->error = ret;
+ return;
+ }
+
+ memcpy(ctx->position, data, size);
+ ctx->position += size;
+}
+
+static inline void wire_ctx_write_u8(wire_ctx_t *ctx, uint8_t value)
+{
+ wire_ctx_write(ctx, &value, sizeof(value));
+}
+
+static inline void wire_ctx_write_u16(wire_ctx_t *ctx, uint16_t value)
+{
+ uint16_t beval = htobe16(value);
+ wire_ctx_write(ctx, &beval, sizeof(beval));
+}
+
+static inline void wire_ctx_write_u32(wire_ctx_t *ctx, uint32_t value)
+{
+ uint32_t beval = htobe32(value);
+ wire_ctx_write(ctx, &beval, sizeof(beval));
+}
+
+static inline void wire_ctx_write_u48(wire_ctx_t *ctx, uint64_t value)
+{
+ /* This case is slightly tricky. */
+ uint64_t swapped = htobe64(value << 8);
+ wire_ctx_write(ctx, (uint8_t *)&swapped + 1, 6);
+}
+
+static inline void wire_ctx_write_u64(wire_ctx_t *ctx, uint64_t value)
+{
+ uint64_t beval = htobe64(value);
+ wire_ctx_write(ctx, &beval, sizeof(beval));
+}
+
+
+static inline void wire_ctx_memset(wire_ctx_t *dst, int value, size_t size)
+{
+ assert(dst);
+
+ if (dst->error != KNOT_EOK) {
+ return;
+ }
+
+ if (size == 0) {
+ return;
+ }
+
+ int ret = wire_ctx_can_write(dst, size);
+ if (ret != KNOT_EOK) {
+ dst->error = ret;
+ return;
+ }
+
+ memset(dst->position, value, size);
+ dst->position += size;
+}
+
+static inline void wire_ctx_clear(wire_ctx_t *ctx, size_t size)
+{
+ wire_ctx_memset(ctx, 0, size);
+}
+
+static inline void wire_ctx_copy(wire_ctx_t *dst, wire_ctx_t *src, size_t size)
+{
+ assert(dst);
+ assert(src);
+
+ if (size == 0 || dst->error != KNOT_EOK) {
+ return;
+ }
+
+ if (wire_ctx_can_read(src, size) != KNOT_EOK) {
+ dst->error = KNOT_EFEWDATA;
+ return;
+ }
+
+ int ret = wire_ctx_can_write(dst, size);
+ if (ret != KNOT_EOK) {
+ dst->error = ret;
+ return;
+ }
+
+ memcpy(dst->position, src->position, size);
+ dst->position += size;
+ src->position += size;
+}
+