]> git.sur5r.net Git - openldap/blobdiff - libraries/liblmdb/mdb.c
mdb_txn_renew0(): Fix un-mutexed me_flags update.
[openldap] / libraries / liblmdb / mdb.c
index a312ba4566cac91d0c6630c4836e58b8f5652a27..a3ac603f882f58cd01dad1807c35f7381c344b45 100644 (file)
@@ -65,7 +65,6 @@
 #include <fcntl.h>
 #endif
 
-#include <assert.h>
 #include <errno.h>
 #include <limits.h>
 #include <stddef.h>
 /** @defgroup internal MDB Internals
  *     @{
  */
-/** @defgroup compat   Windows Compatibility Macros
+/** @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.
  *     @{
  */
+
+       /** 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
+
 #ifdef _WIN32
 #define MDB_USE_HASH   1
 #define MDB_PIDLOCK    0
@@ -327,7 +337,7 @@ static txnid_t mdb_debug_start;
         */
 # define DPRINTF(args) ((void) ((mdb_debug) && DPRINTF0 args))
 # define DPRINTF0(fmt, ...) \
-       fprintf(stderr, "%s:%d " fmt "\n", __func__, __LINE__, __VA_ARGS__)
+       fprintf(stderr, "%s:%d " fmt "\n", mdb_func_, __LINE__, __VA_ARGS__)
 #else
 # define DPRINTF(args) ((void) 0)
 #endif
@@ -342,7 +352,7 @@ static txnid_t mdb_debug_start;
 
        /**     @brief The maximum size of a database page.
         *
-        *      This is 32k, since it must fit in #MDB_page.#mp_upper.
+        *      This is 32k, since it 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
@@ -931,7 +941,7 @@ struct MDB_txn {
 #define MDB_TXN_SPILLS         0x08            /**< txn or a parent has spilled pages */
 /** @} */
        unsigned int    mt_flags;               /**< @ref mdb_txn */
-       /** dirty_list room: Array size - #dirty pages visible to this 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.
@@ -1024,8 +1034,6 @@ struct MDB_env {
 #define        MDB_ENV_ACTIVE  0x20000000U
        /** me_txkey is set */
 #define        MDB_ENV_TXKEY   0x10000000U
-       /** Have liveness lock in reader table */
-#define        MDB_LIVE_READER 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 */
@@ -1061,6 +1069,7 @@ struct MDB_env {
 #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 */
        HANDLE          me_rmutex;              /* Windows mutexes don't reside in shared mem */
@@ -1069,6 +1078,8 @@ struct MDB_env {
        sem_t           *me_rmutex;             /* Shared mutexes are not supported */
        sem_t           *me_wmutex;
 #endif
+       void            *me_userctx;     /**< User-settable context */
+       MDB_assert_func *me_assert_func; /**< Callback for assertion failures */
 };
 
        /** Nested transaction */
@@ -1084,7 +1095,7 @@ typedef struct MDB_ntxn {
 #define MDB_COMMIT_PAGES       IOV_MAX
 #endif
 
-       /* max bytes to write in one call */
+       /** max bytes to write in one call */
 #define MAX_WRITE              (0x80000000U >> (sizeof(ssize_t) == 4))
 
 static int  mdb_page_alloc(MDB_cursor *mc, int num, MDB_page **mp);
@@ -1117,7 +1128,7 @@ 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_page *mp, indx_t indx, int ksize);
+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);
 static int  mdb_node_read(MDB_txn *txn, MDB_node *leaf, MDB_val *data);
@@ -1204,6 +1215,33 @@ mdb_strerror(int err)
        return strerror(err);
 }
 
+/** 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(mc, 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
+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
@@ -1389,11 +1427,13 @@ mdb_page_malloc(MDB_txn *txn, unsigned 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;
                }
-               VGMEMP_ALLOC(env, ret, sz);
+       } else {
+               txn->mt_flags |= MDB_TXN_ERROR;
        }
        return ret;
 }
@@ -1659,7 +1699,7 @@ static void
 mdb_page_dirty(MDB_txn *txn, MDB_page *mp)
 {
        MDB_ID2 mid;
-       int (*insert)(MDB_ID2L, MDB_ID2 *);
+       int rc, (*insert)(MDB_ID2L, MDB_ID2 *);
 
        if (txn->mt_env->me_flags & MDB_WRITEMAP) {
                insert = mdb_mid2l_append;
@@ -1668,7 +1708,8 @@ mdb_page_dirty(MDB_txn *txn, MDB_page *mp)
        }
        mid.mid = mp->mp_pgno;
        mid.mptr = mp;
-       insert(txn->mt_u.dirty_list, &mid);
+       rc = insert(txn->mt_u.dirty_list, &mid);
+       mdb_tassert(txn, rc == 0);
        txn->mt_dirty_room--;
 }
 
@@ -1714,8 +1755,10 @@ mdb_page_alloc(MDB_cursor *mc, int num, MDB_page **mp)
        *mp = NULL;
 
        /* If our dirty list is already full, we can't do anything */
-       if (txn->mt_dirty_room == 0)
-               return MDB_TXN_FULL;
+       if (txn->mt_dirty_room == 0) {
+               rc = MDB_TXN_FULL;
+               goto fail;
+       }
 
        for (op = MDB_FIRST;; op = MDB_NEXT) {
                MDB_val key, data;
@@ -1760,7 +1803,7 @@ mdb_page_alloc(MDB_cursor *mc, int num, MDB_page **mp)
                if (rc) {
                        if (rc == MDB_NOTFOUND)
                                break;
-                       return rc;
+                       goto fail;
                }
                last = *(txnid_t*)key.mv_data;
                if (oldest <= last)
@@ -1773,11 +1816,13 @@ mdb_page_alloc(MDB_cursor *mc, int num, MDB_page **mp)
                idl = (MDB_ID *) data.mv_data;
                i = idl[0];
                if (!mop) {
-                       if (!(env->me_pghead = mop = mdb_midl_alloc(i)))
-                               return ENOMEM;
+                       if (!(env->me_pghead = mop = mdb_midl_alloc(i))) {
+                               rc = ENOMEM;
+                               goto fail;
+                       }
                } else {
                        if ((rc = mdb_midl_need(&env->me_pghead, i)) != 0)
-                               return rc;
+                               goto fail;
                        mop = env->me_pghead;
                }
                env->me_pglast = last;
@@ -1806,15 +1851,18 @@ mdb_page_alloc(MDB_cursor *mc, int num, MDB_page **mp)
        pgno = txn->mt_next_pgno;
        if (pgno + num >= env->me_maxpg) {
                        DPUTS("DB size maxed out");
-                       return MDB_MAP_FULL;
+                       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)))
-                       return ENOMEM;
+               if (!(np = mdb_page_malloc(txn, num))) {
+                       rc = ENOMEM;
+                       goto fail;
+               }
        }
        if (i) {
                mop[0] = mop_len -= num;
@@ -1829,6 +1877,10 @@ search_done:
        *mp = np;
 
        return MDB_SUCCESS;
+
+fail:
+       txn->mt_flags |= MDB_TXN_ERROR;
+       return rc;
 }
 
 /** Copy the used portions of a non-overflow page.
@@ -1935,17 +1987,17 @@ mdb_page_touch(MDB_cursor *mc)
                        np = NULL;
                        rc = mdb_page_unspill(txn, mp, &np);
                        if (rc)
-                               return rc;
+                               goto fail;
                        if (np)
                                goto done;
                }
                if ((rc = mdb_midl_need(&txn->mt_free_pgs, 1)) ||
                        (rc = mdb_page_alloc(mc, 1, &np)))
-                       return rc;
+                       goto fail;
                pgno = np->mp_pgno;
                DPRINTF(("touched db %d page %"Z"u -> %"Z"u", DDBI(mc),
                        mp->mp_pgno, pgno));
-               assert(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) {
@@ -1966,19 +2018,21 @@ mdb_page_touch(MDB_cursor *mc)
                        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;
                        }
                }
-               assert(dl[0].mid < MDB_IDL_UM_MAX);
+               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;
-               mdb_mid2l_insert(dl, &mid);
+               rc = mdb_mid2l_insert(dl, &mid);
+               mdb_cassert(mc, rc == 0);
        } else {
                return 0;
        }
@@ -2014,6 +2068,10 @@ done:
                }
        }
        return 0;
+
+fail:
+       txn->mt_flags |= MDB_TXN_ERROR;
+       return rc;
 }
 
 int
@@ -2209,11 +2267,11 @@ mdb_txn_renew0(MDB_txn *txn)
                                MDB_PID_T pid = env->me_pid;
                                pthread_t tid = pthread_self();
 
-                               if (!(env->me_flags & MDB_LIVE_READER)) {
+                               if (!env->me_live_reader) {
                                        rc = mdb_reader_pid(env, Pidset, pid);
                                        if (rc)
                                                return rc;
-                                       env->me_flags |= MDB_LIVE_READER;
+                                       env->me_live_reader = 1;
                                }
 
                                LOCK_MUTEX_R(env);
@@ -2573,7 +2631,7 @@ mdb_freelist_save(MDB_txn *txn)
                                return rc;
                        pglast = head_id = *(txnid_t *)key.mv_data;
                        total_room = head_room = 0;
-                       assert(pglast <= env->me_pglast);
+                       mdb_tassert(txn, pglast <= env->me_pglast);
                        rc = mdb_cursor_del(&mc, 0);
                        if (rc)
                                return rc;
@@ -2668,11 +2726,12 @@ mdb_freelist_save(MDB_txn *txn)
                        ssize_t len = (ssize_t)(data.mv_size / sizeof(MDB_ID)) - 1;
                        MDB_ID save;
 
-                       assert(len >= 0 && id <= env->me_pglast);
-                       key.mv_data = &id;
+                       mdb_tassert(txn, len >= 0 && id <= env->me_pglast);
                        if (len > mop_len) {
                                len = mop_len;
                                data.mv_size = (len + 1) * sizeof(MDB_ID);
+                               /* Drop MDB_CURRENT when changing the data size */
+                               key.mv_data = &id;
                                flags = 0;
                        }
                        data.mv_data = mop -= len;
@@ -2834,8 +2893,8 @@ mdb_txn_commit(MDB_txn *txn)
        unsigned int i;
        MDB_env *env;
 
-       assert(txn != NULL);
-       assert(txn->mt_env != NULL);
+       if (txn == NULL || txn->mt_env == NULL)
+               return EINVAL;
 
        if (txn->mt_child) {
                rc = mdb_txn_commit(txn->mt_child);
@@ -2943,7 +3002,7 @@ mdb_txn_commit(MDB_txn *txn)
                        if (yp == dst[x].mid)
                                free(dst[x--].mptr);
                }
-               assert(i == x);
+               mdb_tassert(txn, i == x);
                dst[0].mid = len;
                free(txn->mt_u.dirty_list);
                parent->mt_dirty_room = txn->mt_dirty_room;
@@ -3176,9 +3235,6 @@ mdb_env_write_meta(MDB_txn *txn)
        int r2;
 #endif
 
-       assert(txn != NULL);
-       assert(txn->mt_env != NULL);
-
        toggle = txn->mt_txnid & 1;
        DPRINTF(("writing meta page %d for root page %"Z"u",
                toggle, txn->mt_dbs[MAIN_DBI].md_root));
@@ -3500,7 +3556,7 @@ mdb_env_open2(MDB_env *env)
                        env->me_mapsize = minsize;
        }
 
-       rc = mdb_env_map(env, meta.mm_address, newenv);
+       rc = mdb_env_map(env, meta.mm_address, newenv || env->me_mapsize != meta.mm_mapsize);
        if (rc)
                return rc;
 
@@ -4624,7 +4680,7 @@ mdb_cursor_push(MDB_cursor *mc, MDB_page *mp)
                DDBI(mc), (void *) mc));
 
        if (mc->mc_snum >= CURSOR_STACK) {
-               assert(mc->mc_snum < CURSOR_STACK);
+               mc->mc_txn->mt_flags |= MDB_TXN_ERROR;
                return MDB_CURSOR_FULL;
        }
 
@@ -4684,7 +4740,7 @@ mdb_page_get(MDB_txn *txn, pgno_t pgno, MDB_page **ret, int *lvl)
                p = (MDB_page *)(env->me_map + env->me_psize * pgno);
        } else {
                DPRINTF(("page %"Z"u not found", pgno));
-               assert(p != NULL);
+               txn->mt_flags |= MDB_TXN_ERROR;
                return MDB_PAGE_NOTFOUND;
        }
 
@@ -4710,7 +4766,7 @@ mdb_page_search_root(MDB_cursor *mc, MDB_val *key, int flags)
                indx_t          i;
 
                DPRINTF(("branch page %"Z"u has %u keys", mp->mp_pgno, NUMKEYS(mp)));
-               assert(NUMKEYS(mp) > 1);
+               mdb_cassert(mc, NUMKEYS(mp) > 1);
                DPRINTF(("found index 0 to page %"Z"u", NODEPGNO(NODEPTR(mp, 0))));
 
                if (flags & (MDB_PS_FIRST|MDB_PS_LAST)) {
@@ -4725,14 +4781,14 @@ mdb_page_search_root(MDB_cursor *mc, MDB_val *key, int flags)
                        else {
                                i = mc->mc_ki[mc->mc_top];
                                if (!exact) {
-                                       assert(i > 0);
+                                       mdb_cassert(mc, i > 0);
                                        i--;
                                }
                        }
                        DPRINTF(("following index %u for key [%s]", i, DKEY(key)));
                }
 
-               assert(i < NUMKEYS(mp));
+               mdb_cassert(mc, i < NUMKEYS(mp));
                node = NODEPTR(mp, i);
 
                if ((rc = mdb_page_get(mc->mc_txn, NODEPGNO(node), &mp, NULL)) != 0)
@@ -4752,6 +4808,7 @@ mdb_page_search_root(MDB_cursor *mc, MDB_val *key, int flags)
        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;
        }
 
@@ -4846,7 +4903,7 @@ mdb_page_search(MDB_cursor *mc, MDB_val *key, int flags)
                }
        }
 
-       assert(root > 1);
+       mdb_cassert(mc, root > 1);
        if (!mc->mc_pg[0] || mc->mc_pg[0]->mp_pgno != root)
                if ((rc = mdb_page_get(mc->mc_txn, root, &mc->mc_pg[0], NULL)) != 0)
                        return rc;
@@ -4916,7 +4973,7 @@ mdb_ovpage_free(MDB_cursor *mc, MDB_page *mp)
                                iy = dl[x];
                                dl[x] = ix;
                        } else {
-                               assert(x > 1);
+                               mdb_cassert(mc, x > 1);
                                j = ++(dl[0].mid);
                                dl[j] = ix;             /* Unsorted. OK when MDB_TXN_ERROR. */
                                txn->mt_flags |= MDB_TXN_ERROR;
@@ -4984,8 +5041,9 @@ mdb_get(MDB_txn *txn, MDB_dbi dbi,
        int exact = 0;
        DKBUF;
 
-       assert(key);
-       assert(data);
+       if (key == NULL || data == NULL)
+               return EINVAL;
+
        DPRINTF(("===> get db %u key [%s]", dbi, DKEY(key)));
 
        if (txn == NULL || !dbi || dbi >= txn->mt_numdbs || !(txn->mt_dbflags[dbi] & DB_VALID))
@@ -5039,7 +5097,7 @@ mdb_cursor_sibling(MDB_cursor *mc, int move_right)
                DPRINTF(("just moving to %s index key %u",
                    move_right ? "right" : "left", mc->mc_ki[mc->mc_top]));
        }
-       assert(IS_BRANCH(mc->mc_pg[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->mc_txn, NODEPGNO(indx), &mp, NULL)) != 0) {
@@ -5067,7 +5125,7 @@ mdb_cursor_next(MDB_cursor *mc, MDB_val *key, MDB_val *data, MDB_cursor_op op)
                return MDB_NOTFOUND;
        }
 
-       assert(mc->mc_flags & C_INITIALIZED);
+       mdb_cassert(mc, mc->mc_flags & C_INITIALIZED);
 
        mp = mc->mc_pg[mc->mc_top];
 
@@ -5115,7 +5173,7 @@ skip:
                return MDB_SUCCESS;
        }
 
-       assert(IS_LEAF(mp));
+       mdb_cassert(mc, IS_LEAF(mp));
        leaf = NODEPTR(mp, mc->mc_ki[mc->mc_top]);
 
        if (F_ISSET(leaf->mn_flags, F_DUPDATA)) {
@@ -5144,7 +5202,7 @@ mdb_cursor_prev(MDB_cursor *mc, MDB_val *key, MDB_val *data, MDB_cursor_op op)
        MDB_node        *leaf;
        int rc;
 
-       assert(mc->mc_flags & C_INITIALIZED);
+       mdb_cassert(mc, mc->mc_flags & C_INITIALIZED);
 
        mp = mc->mc_pg[mc->mc_top];
 
@@ -5191,7 +5249,7 @@ mdb_cursor_prev(MDB_cursor *mc, MDB_val *key, MDB_val *data, MDB_cursor_op op)
                return MDB_SUCCESS;
        }
 
-       assert(IS_LEAF(mp));
+       mdb_cassert(mc, IS_LEAF(mp));
        leaf = NODEPTR(mp, mc->mc_ki[mc->mc_top]);
 
        if (F_ISSET(leaf->mn_flags, F_DUPDATA)) {
@@ -5222,8 +5280,6 @@ mdb_cursor_set(MDB_cursor *mc, MDB_val *key, MDB_val *data,
        MDB_node        *leaf = NULL;
        DKBUF;
 
-       assert(mc);
-       assert(key);
        if (key->mv_size == 0)
                return MDB_BAD_VALSIZE;
 
@@ -5326,7 +5382,7 @@ mdb_cursor_set(MDB_cursor *mc, MDB_val *key, MDB_val *data,
                return rc;
 
        mp = mc->mc_pg[mc->mc_top];
-       assert(IS_LEAF(mp));
+       mdb_cassert(mc, IS_LEAF(mp));
 
 set2:
        leaf = mdb_node_search(mc, key, exactp);
@@ -5340,7 +5396,7 @@ set2:
                if ((rc = mdb_cursor_sibling(mc, 1)) != MDB_SUCCESS)
                        return rc;              /* no entries matched */
                mp = mc->mc_pg[mc->mc_top];
-               assert(IS_LEAF(mp));
+               mdb_cassert(mc, IS_LEAF(mp));
                leaf = NODEPTR(mp, 0);
        }
 
@@ -5416,7 +5472,7 @@ mdb_cursor_first(MDB_cursor *mc, MDB_val *key, MDB_val *data)
                if (rc != MDB_SUCCESS)
                        return rc;
        }
-       assert(IS_LEAF(mc->mc_pg[mc->mc_top]));
+       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;
@@ -5462,7 +5518,7 @@ mdb_cursor_last(MDB_cursor *mc, MDB_val *key, MDB_val *data)
                        if (rc != MDB_SUCCESS)
                                return rc;
                }
-               assert(IS_LEAF(mc->mc_pg[mc->mc_top]));
+               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;
@@ -5499,7 +5555,8 @@ mdb_cursor_get(MDB_cursor *mc, MDB_val *key, MDB_val *data,
        int              exact = 0;
        int              (*mfunc)(MDB_cursor *mc, MDB_val *key, MDB_val *data);
 
-       assert(mc);
+       if (mc == NULL)
+               return EINVAL;
 
        if (mc->mc_txn->mt_flags & MDB_TXN_ERROR)
                return MDB_BAD_TXN;
@@ -5691,7 +5748,7 @@ mdb_cursor_put(MDB_cursor *mc, MDB_val *key, MDB_val *data,
     unsigned int flags)
 {
        enum { MDB_NO_ROOT = MDB_LAST_ERRCODE+10 }; /* internal code */
-       MDB_env         *env = mc->mc_txn->mt_env;
+       MDB_env         *env;
        MDB_node        *leaf = NULL;
        MDB_page        *fp, *mp;
        uint16_t        fp_flags;
@@ -5704,6 +5761,11 @@ mdb_cursor_put(MDB_cursor *mc, MDB_val *key, MDB_val *data,
        unsigned int nflags;
        DKBUF;
 
+       if (mc == NULL)
+               return EINVAL;
+
+       env = mc->mc_txn->mt_env;
+
        /* Check this first so counter will always be zero on any
         * early failures.
         */
@@ -5720,8 +5782,16 @@ mdb_cursor_put(MDB_cursor *mc, MDB_val *key, MDB_val *data,
        if (mc->mc_txn->mt_flags & (MDB_TXN_RDONLY|MDB_TXN_ERROR))
                return (mc->mc_txn->mt_flags & MDB_TXN_RDONLY) ? EACCES : MDB_BAD_TXN;
 
-       if (flags != MDB_CURRENT && key->mv_size-1 >= ENV_MAXKEY(env))
-               return MDB_BAD_VALSIZE;
+       if (flags != MDB_CURRENT) {
+               if (key == NULL)
+                       return EINVAL;
+               if (key->mv_size-1 >= ENV_MAXKEY(env))
+                       return MDB_BAD_VALSIZE;
+       } else {
+               /* Ignore key except in sub-cursor, where key holds the data */
+               if (!(mc->mc_flags & C_SUB))
+                       key = NULL;
+       }
 
 #if SIZE_MAX > MAXDATASIZE
        if (data->mv_size > ((mc->mc_db->md_flags & MDB_DUPSORT) ? ENV_MAXKEY(env) : MAXDATASIZE))
@@ -5850,7 +5920,7 @@ more:
                         * it.  mp: new (sub-)page.  offset: growth in page
                         * size.  xdata: node data with new page or DB.
                         */
-                       ssize_t         i, offset = 0;
+                       unsigned        i, offset = 0;
                        mp = fp = xdata.mv_data = env->me_pbuf;
                        mp->mp_pgno = mc->mc_pg[mc->mc_top]->mp_pgno;
 
@@ -5906,17 +5976,17 @@ more:
                                fp = olddata.mv_data;
                                switch (flags) {
                                default:
-                                       i = -(ssize_t)SIZELEFT(fp);
                                        if (!(mc->mc_db->md_flags & MDB_DUPFIXED)) {
-                                               offset = i += (ssize_t) EVEN(
-                                                       sizeof(indx_t) + NODESIZE + data->mv_size);
-                                       } else {
-                                               i += offset = fp->mp_pad;
-                                               offset *= 4; /* space for 4 more */
+                                               offset = EVEN(NODESIZE + sizeof(indx_t) +
+                                                       data->mv_size);
+                                               break;
                                        }
-                                       if (i > 0)
+                                       offset = fp->mp_pad;
+                                       if (SIZELEFT(fp) < offset) {
+                                               offset *= 4; /* space for 4 more */
                                                break;
-                                       /* FALLTHRU: Sub-page is big enough */
+                                       }
+                                       /* FALLTHRU: Big enough MDB_DUPFIXED sub-page */
                                case MDB_CURRENT:
                                        fp->mp_flags |= P_DIRTY;
                                        COPY_PGNO(fp->mp_pgno, mp->mp_pgno);
@@ -5965,7 +6035,7 @@ prep_subDB:
                                } else {
                                        memcpy((char *)mp + mp->mp_upper, (char *)fp + fp->mp_upper,
                                                olddata.mv_size - fp->mp_upper);
-                                       for (i = NUMKEYS(fp); --i >= 0; )
+                                       for (i=0; i<NUMKEYS(fp); i++)
                                                mp->mp_ptrs[i] = fp->mp_ptrs[i] + offset;
                                }
                        }
@@ -5974,7 +6044,7 @@ prep_subDB:
                        flags |= F_DUPDATA;
                        do_sub = 1;
                        if (!insert)
-                               mdb_node_del(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top], 0);
+                               mdb_node_del(mc, 0);
                        goto new_sub;
                }
 current:
@@ -6014,7 +6084,8 @@ current:
                                                return ENOMEM;
                                        id2.mid = pg;
                                        id2.mptr = np;
-                                       mdb_mid2l_insert(mc->mc_txn->mt_u.dirty_list, &id2);
+                                       rc = mdb_mid2l_insert(mc->mc_txn->mt_u.dirty_list, &id2);
+                                       mdb_cassert(mc, rc == 0);
                                        if (!(flags & MDB_RESERVE)) {
                                                /* Copy end of page, adjusting alignment so
                                                 * compiler may copy words instead of bytes.
@@ -6044,13 +6115,13 @@ current:
                         */
                        if (F_ISSET(flags, MDB_RESERVE))
                                data->mv_data = olddata.mv_data;
-                       else if (data->mv_size)
+                       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 done;
                }
-               mdb_node_del(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top], 0);
+               mdb_node_del(mc, 0);
                mc->mc_db->md_entries--;
        }
 
@@ -6351,7 +6422,7 @@ mdb_node_add(MDB_cursor *mc, indx_t indx,
        MDB_page        *ofp = NULL;            /* overflow page */
        DKBUF;
 
-       assert(mp->mp_upper >= mp->mp_lower);
+       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",
@@ -6379,7 +6450,7 @@ mdb_node_add(MDB_cursor *mc, indx_t indx,
        if (key != NULL)
                node_size += key->mv_size;
        if (IS_LEAF(mp)) {
-               assert(data);
+               mdb_cassert(mc, data);
                if (F_ISSET(flags, F_BIGDATA)) {
                        /* Data already on overflow page. */
                        node_size += sizeof(pgno_t);
@@ -6412,7 +6483,7 @@ update:
 
        /* Adjust free space offsets. */
        ofs = mp->mp_upper - node_size;
-       assert(ofs >= mp->mp_lower + sizeof(indx_t));
+       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);
@@ -6430,7 +6501,7 @@ update:
                memcpy(NODEKEY(node), key->mv_data, key->mv_size);
 
        if (IS_LEAF(mp)) {
-               assert(key);
+               mdb_cassert(mc, key);
                if (ofp == NULL) {
                        if (F_ISSET(flags, F_BIGDATA))
                                memcpy(node->mn_data + key->mv_size, data->mv_data,
@@ -6457,18 +6528,20 @@ full:
                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] mp The page to operate on.
- * @param[in] indx The index of the node to delete.
+ * @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_page *mp, indx_t indx, int ksize)
+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;
@@ -6476,10 +6549,11 @@ mdb_node_del(MDB_page *mp, indx_t indx, int ksize)
 
        DPRINTF(("delete node %u on %s page %"Z"u", indx,
            IS_LEAF(mp) ? "leaf" : "branch", mdb_dbg_pgno(mp)));
-       assert(indx < NUMKEYS(mp));
+       numkeys = NUMKEYS(mp);
+       mdb_cassert(mc, indx < numkeys);
 
        if (IS_LEAF2(mp)) {
-               int x = NUMKEYS(mp) - 1 - indx;
+               int x = numkeys - 1 - indx;
                base = LEAF2KEY(mp, indx, ksize);
                if (x)
                        memmove(base, base + ksize, x * ksize);
@@ -6499,7 +6573,6 @@ mdb_node_del(MDB_page *mp, indx_t indx, int ksize)
        sz = EVEN(sz);
 
        ptr = mp->mp_ptrs[indx];
-       numkeys = NUMKEYS(mp);
        for (i = j = 0; i < numkeys; i++) {
                if (i != indx) {
                        mp->mp_ptrs[j] = mp->mp_ptrs[i];
@@ -6666,7 +6739,7 @@ mdb_cursor_init(MDB_cursor *mc, MDB_txn *txn, MDB_dbi dbi, MDB_xcursor *mx)
        mc->mc_pg[0] = 0;
        mc->mc_flags = 0;
        if (txn->mt_dbs[dbi].md_flags & MDB_DUPSORT) {
-               assert(mx != NULL);
+               mdb_tassert(txn, mx != NULL);
                mc->mc_xcursor = mx;
                mdb_xcursor_init0(mc);
        } else {
@@ -6774,7 +6847,6 @@ mdb_cursor_txn(MDB_cursor *mc)
 MDB_dbi
 mdb_cursor_dbi(MDB_cursor *mc)
 {
-       assert(mc != NULL);
        return mc->mc_dbi;
 }
 
@@ -6824,7 +6896,7 @@ mdb_update_key(MDB_cursor *mc, MDB_val *key)
                        /* not enough space left, do a delete and split */
                        DPRINTF(("Not enough room, delta = %d, splitting...", delta));
                        pgno = NODEPGNO(node);
-                       mdb_node_del(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top], 0);
+                       mdb_node_del(mc, 0);
                        return mdb_page_split(mc, key, NULL, pgno, MDB_SPLIT_REPLACE);
                }
 
@@ -6883,7 +6955,7 @@ mdb_node_move(MDB_cursor *csrc, MDB_cursor *cdst)
                flags = 0;
        } else {
                srcnode = NODEPTR(csrc->mc_pg[csrc->mc_top], csrc->mc_ki[csrc->mc_top]);
-               assert(!((size_t)srcnode&1));
+               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])) {
@@ -6946,7 +7018,7 @@ mdb_node_move(MDB_cursor *csrc, MDB_cursor *cdst)
 
        /* Delete the node from the source page.
         */
-       mdb_node_del(csrc->mc_pg[csrc->mc_top], csrc->mc_ki[csrc->mc_top], key.mv_size);
+       mdb_node_del(csrc, key.mv_size);
 
        {
                /* Adjust other cursors pointing to mp */
@@ -6994,7 +7066,7 @@ mdb_node_move(MDB_cursor *csrc, MDB_cursor *cdst)
                        csrc->mc_ki[csrc->mc_top] = 0;
                        rc = mdb_update_key(csrc, &nullkey);
                        csrc->mc_ki[csrc->mc_top] = ix;
-                       assert(rc == MDB_SUCCESS);
+                       mdb_cassert(csrc, rc == MDB_SUCCESS);
                }
        }
 
@@ -7022,7 +7094,7 @@ mdb_node_move(MDB_cursor *csrc, MDB_cursor *cdst)
                        cdst->mc_ki[cdst->mc_top] = 0;
                        rc = mdb_update_key(cdst, &nullkey);
                        cdst->mc_ki[cdst->mc_top] = ix;
-                       assert(rc == MDB_SUCCESS);
+                       mdb_cassert(csrc, rc == MDB_SUCCESS);
                }
        }
 
@@ -7035,6 +7107,7 @@ mdb_node_move(MDB_cursor *csrc, MDB_cursor *cdst)
  *     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)
@@ -7048,8 +7121,8 @@ mdb_page_merge(MDB_cursor *csrc, MDB_cursor *cdst)
        DPRINTF(("merging page %"Z"u into %"Z"u", csrc->mc_pg[csrc->mc_top]->mp_pgno,
                cdst->mc_pg[cdst->mc_top]->mp_pgno));
 
-       assert(csrc->mc_snum > 1);      /* can't merge root page */
-       assert(cdst->mc_snum > 1);
+       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)))
@@ -7104,15 +7177,17 @@ mdb_page_merge(MDB_cursor *csrc, MDB_cursor *cdst)
 
        /* Unlink the src page from parent and add to free list.
         */
-       mdb_node_del(csrc->mc_pg[csrc->mc_top-1], csrc->mc_ki[csrc->mc_top-1], 0);
-       if (csrc->mc_ki[csrc->mc_top-1] == 0) {
+       csrc->mc_top--;
+       mdb_node_del(csrc, 0);
+       if (csrc->mc_ki[csrc->mc_top] == 0) {
                key.mv_size = 0;
-               csrc->mc_top--;
                rc = mdb_update_key(csrc, &key);
-               csrc->mc_top++;
-               if (rc)
+               if (rc) {
+                       csrc->mc_top++;
                        return rc;
+               }
        }
+       csrc->mc_top++;
 
        rc = mdb_midl_append(&csrc->mc_txn->mt_free_pgs,
                csrc->mc_pg[csrc->mc_top]->mp_pgno);
@@ -7273,7 +7348,7 @@ mdb_rebalance(MDB_cursor *mc)
         * otherwise the tree is invalid.
         */
        ptop = mc->mc_top-1;
-       assert(NUMKEYS(mc->mc_pg[ptop]) > 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
@@ -7355,13 +7430,13 @@ mdb_cursor_del0(MDB_cursor *mc, MDB_node *leaf)
                        (rc = mdb_ovpage_free(mc, omp)))
                        return rc;
        }
-       mdb_node_del(mp, ki, mc->mc_db->md_pad);
+       mdb_node_del(mc, mc->mc_db->md_pad);
        mc->mc_db->md_entries--;
        rc = mdb_rebalance(mc);
        if (rc != MDB_SUCCESS)
                mc->mc_txn->mt_flags |= MDB_TXN_ERROR;
        else {
-               MDB_cursor *m2;
+               MDB_cursor *m2, *m3;
                MDB_dbi dbi = mc->mc_dbi;
 
                mp = mc->mc_pg[mc->mc_top];
@@ -7373,18 +7448,19 @@ mdb_cursor_del0(MDB_cursor *mc, MDB_node *leaf)
 
                /* Adjust other cursors pointing to mp */
                for (m2 = mc->mc_txn->mt_cursors[dbi]; m2; m2=m2->mc_next) {
-                       if (m2 == mc || m2->mc_snum < mc->mc_snum)
+                       m3 = (mc->mc_flags & C_SUB) ? &m2->mc_xcursor->mx_cursor : m2;
+                       if (! (m2->mc_flags & m3->mc_flags & C_INITIALIZED))
                                continue;
-                       if (!(m2->mc_flags & C_INITIALIZED))
+                       if (m3 == mc || m3->mc_snum < mc->mc_snum)
                                continue;
-                       if (m2->mc_pg[mc->mc_top] == mp) {
-                               if (m2->mc_ki[mc->mc_top] >= ki) {
-                                       m2->mc_flags |= C_DEL;
-                                       if (m2->mc_ki[mc->mc_top] > ki)
-                                               m2->mc_ki[mc->mc_top]--;
+                       if (m3->mc_pg[mc->mc_top] == mp) {
+                               if (m3->mc_ki[mc->mc_top] >= ki) {
+                                       m3->mc_flags |= C_DEL;
+                                       if (m3->mc_ki[mc->mc_top] > ki)
+                                               m3->mc_ki[mc->mc_top]--;
                                }
-                               if (m2->mc_ki[mc->mc_top] >= nkeys)
-                                       mdb_cursor_sibling(m2, 1);
+                               if (m3->mc_ki[mc->mc_top] >= nkeys)
+                                       mdb_cursor_sibling(m3, 1);
                        }
                }
                mc->mc_flags |= C_DEL;
@@ -7404,7 +7480,8 @@ mdb_del(MDB_txn *txn, MDB_dbi dbi,
        int              rc, exact;
        DKBUF;
 
-       assert(key != NULL);
+       if (key == NULL)
+               return EINVAL;
 
        DPRINTF(("====> delete db %u key [%s]", dbi, DKEY(key)));
 
@@ -7687,7 +7764,13 @@ mdb_page_split(MDB_cursor *mc, MDB_val *newkey, MDB_val *newdata, pgno_t newpgno
                                mc->mc_ki[i] = mn.mc_ki[i];
                        }
                        mc->mc_pg[ptop] = mn.mc_pg[ptop];
-                       mc->mc_ki[ptop] = mn.mc_ki[ptop] - 1;
+                       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--;
@@ -7779,12 +7862,10 @@ mdb_page_split(MDB_cursor *mc, MDB_val *newkey, MDB_val *newdata, pgno_t newpgno
                         */
                        if (mn.mc_pg[ptop] != mc->mc_pg[ptop] &&
                                mc->mc_ki[ptop] >= NUMKEYS(mc->mc_pg[ptop])) {
-                               for (i=0; i<ptop; i++) {
+                               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];
-                               mc->mc_ki[ptop] = mn.mc_ki[ptop] - 1;
                        }
                }
                /* return tmp page to freelist */
@@ -7849,8 +7930,8 @@ mdb_put(MDB_txn *txn, MDB_dbi dbi,
        MDB_cursor mc;
        MDB_xcursor mx;
 
-       assert(key != NULL);
-       assert(data != NULL);
+       if (key == NULL || data == NULL)
+               return EINVAL;
 
        if (txn == NULL || !dbi || dbi >= txn->mt_numdbs || !(txn->mt_dbflags[dbi] & DB_VALID))
                return EINVAL;
@@ -7884,6 +7965,32 @@ mdb_env_get_flags(MDB_env *env, unsigned int *arg)
        return MDB_SUCCESS;
 }
 
+int
+mdb_env_set_userctx(MDB_env *env, void *ctx)
+{
+       if (!env)
+               return EINVAL;
+       env->me_userctx = ctx;
+       return MDB_SUCCESS;
+}
+
+void *
+mdb_env_get_userctx(MDB_env *env)
+{
+       return env ? env->me_userctx : NULL;
+}
+
+int
+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
 mdb_env_get_path(MDB_env *env, const char **arg)
 {
@@ -8153,7 +8260,7 @@ mdb_drop0(MDB_cursor *mc, int subs)
                                                rc = mdb_page_get(txn, pg, &omp, NULL);
                                                if (rc != 0)
                                                        return rc;
-                                               assert(IS_OVERFLOW(omp));
+                                               mdb_cassert(mc, IS_OVERFLOW(omp));
                                                rc = mdb_midl_append_range(&txn->mt_free_pgs,
                                                        pg, omp->mp_pages);
                                                if (rc)