]> git.sur5r.net Git - openldap/blobdiff - libraries/libmdb/mdb.c
Sorted dups fix
[openldap] / libraries / libmdb / mdb.c
index b485e156fbcc6427cc5224d304925a14cdb97b4b..69da48fbe350a8a849558e9a9442e4b1f334ff69 100644 (file)
@@ -29,7 +29,6 @@
  */
 #include <sys/types.h>
 #include <sys/stat.h>
-#include <sys/queue.h>
 #include <sys/param.h>
 #include <sys/uio.h>
 #include <sys/mman.h>
@@ -54,7 +53,7 @@
 #define ULONG          unsigned long
 typedef ULONG          pgno_t;
 
-#include "idl.h"
+#include "midl.h"
 
 #ifndef DEBUG
 #define DEBUG 1
@@ -83,44 +82,53 @@ typedef uint16_t     indx_t;
 #define DEFAULT_MAPSIZE        1048576
 
 /* Lock descriptor stuff */
-#define RXBODY \
-       ULONG           mr_txnid; \
-       pid_t           mr_pid; \
-       pthread_t       mr_tid
-typedef struct MDB_rxbody {
-       RXBODY;
-} MDB_rxbody;
-
 #ifndef CACHELINE
-# ifdef __APPLE__
-#  define CACHELINE    128     /* 64 is too small to contain a mutex */
-# else
-#  define CACHELINE    64      /* most CPUs. Itanium uses 128 */
-# endif
+#define CACHELINE      64      /* most CPUs. Itanium uses 128 */
 #endif
 
+typedef struct MDB_rxbody {
+       ULONG           mrb_txnid;
+       pid_t           mrb_pid;
+       pthread_t       mrb_tid;
+} MDB_rxbody;
+
 typedef struct MDB_reader {
-       RXBODY;
-       /* cache line alignment */
-       char pad[CACHELINE-sizeof(MDB_rxbody)];
+       union {
+               MDB_rxbody mrx;
+#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;
 
-#define        TXBODY \
-       uint32_t        mt_magic;       \
-       uint32_t        mt_version;     \
-       pthread_mutex_t mt_mutex;       \
-       ULONG           mt_txnid;       \
-       uint32_t        mt_numreaders
 typedef struct MDB_txbody {
-       TXBODY;
+       uint32_t        mtb_magic;
+       uint32_t        mtb_version;
+       pthread_mutex_t mtb_mutex;
+       ULONG           mtb_txnid;
+       uint32_t        mtb_numreaders;
+       uint32_t        mtb_me_toggle;
 } MDB_txbody;
 
 typedef struct MDB_txninfo {
-       TXBODY;
-       char pad[CACHELINE-sizeof(MDB_txbody)];
-       pthread_mutex_t mt_wmutex;
-       char pad2[CACHELINE-sizeof(pthread_mutex_t)];
-       MDB_reader      mt_readers[1];
+       union {
+               MDB_txbody mtb;
+#define mti_magic      mt1.mtb.mtb_magic
+#define mti_version    mt1.mtb.mtb_version
+#define mti_mutex      mt1.mtb.mtb_mutex
+#define mti_txnid      mt1.mtb.mtb_txnid
+#define mti_numreaders mt1.mtb.mtb_numreaders
+#define mti_me_toggle  mt1.mtb.mtb_me_toggle
+               char pad[(sizeof(MDB_txbody)+CACHELINE-1) & ~(CACHELINE-1)];
+       } mt1;
+       union {
+               pthread_mutex_t mt2_wmutex;
+#define mti_wmutex     mt2.mt2_wmutex
+               char pad[(sizeof(pthread_mutex_t)+CACHELINE-1) & ~(CACHELINE-1)];
+       } mt2;
+       MDB_reader      mti_readers[1];
 } MDB_txninfo;
 
 /* Common header for all page types. Overflow pages
@@ -131,13 +139,14 @@ typedef struct MDB_page {         /* represents a page of storage */
 #define        mp_pgno         mp_p.p_pgno
        union padded {
                pgno_t          p_pgno;         /* page number */
-               void *          p_pad;
+               void *          p_align;        /* for IL32P64 */
        } mp_p;
 #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 */
+#define        P_LEAF2          0x20           /* DB with small, fixed size keys and no data */
        uint32_t        mp_flags;
 #define mp_lower       mp_pb.pb.pb_lower
 #define mp_upper       mp_pb.pb.pb_upper
@@ -148,6 +157,10 @@ typedef struct MDB_page {          /* represents a page of storage */
                        indx_t          pb_upper;               /* upper bound of free space */
                } pb;
                uint32_t        pb_pages;       /* number of overflow pages */
+               struct {
+                       indx_t  pb_ksize;       /* on a LEAF2 page */
+                       indx_t  pb_numkeys;
+               } pb2;
        } mp_pb;
        indx_t          mp_ptrs[1];             /* dynamic size */
 } MDB_page;
@@ -191,7 +204,6 @@ typedef struct MDB_meta {                   /* meta (footer) page content */
 } MDB_meta;
 
 typedef struct MDB_dhead {                                     /* a dirty page */
-       STAILQ_ENTRY(MDB_dpage)  md_next;       /* queue of dirty pages */
        MDB_page        *md_parent;
        unsigned        md_pi;                          /* parent index */
        int                     md_num;
@@ -202,8 +214,6 @@ typedef struct MDB_dpage {
        MDB_page        p;
 } MDB_dpage;
 
-STAILQ_HEAD(dirty_queue, MDB_dpage);   /* FIXME: use a sorted data structure */
-
 typedef struct MDB_oldpages {
        struct MDB_oldpages *mo_next;
        ULONG           mo_txnid;
@@ -220,29 +230,25 @@ static MDB_dpage *mdb_alloc_page(MDB_txn *txn, MDB_page *parent, unsigned int pa
 static int             mdb_touch(MDB_txn *txn, MDB_pageparent *mp);
 
 typedef struct MDB_ppage {                                     /* ordered list of pages */
-       SLIST_ENTRY(MDB_ppage)   mp_entry;
        MDB_page                *mp_page;
        unsigned int    mp_ki;          /* cursor index on page */
 } MDB_ppage;
-SLIST_HEAD(page_stack, MDB_ppage);
 
-#define CURSOR_EMPTY(c)                 SLIST_EMPTY(&(c)->mc_stack)
-#define CURSOR_TOP(c)           SLIST_FIRST(&(c)->mc_stack)
-#define CURSOR_POP(c)           SLIST_REMOVE_HEAD(&(c)->mc_stack, mp_entry)
-#define CURSOR_PUSH(c,p)        SLIST_INSERT_HEAD(&(c)->mc_stack, p, mp_entry)
+#define CURSOR_TOP(c)           (&(c)->mc_stack[(c)->mc_snum-1])
+#define CURSOR_PARENT(c)        (&(c)->mc_stack[(c)->mc_snum-2])
 
 struct MDB_xcursor;
 
 struct MDB_cursor {
        MDB_txn         *mc_txn;
-       struct page_stack        mc_stack;              /* stack of parent pages */
+       MDB_ppage       mc_stack[32];           /* stack of parent pages */
+       unsigned int    mc_snum;                /* number of pushed pages */
        MDB_dbi         mc_dbi;
        short           mc_initialized; /* 1 if initialized */
        short           mc_eof;         /* 1 if end is reached */
        struct MDB_xcursor      *mc_xcursor;
 };
 
-#define METAHASHLEN     offsetof(MDB_meta, mm_hash)
 #define METADATA(p)     ((void *)((char *)p + PAGEHDRSZ))
 
 typedef struct MDB_node {
@@ -256,6 +262,7 @@ typedef struct MDB_node {
        unsigned int    mn_ksize:12;                    /* key size */
 #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 */
        char            mn_data[1];
 } MDB_node;
 
@@ -275,15 +282,15 @@ struct MDB_txn {
        MDB_env         *mt_env;        
        pgno_t          *mt_free_pgs;   /* this is an IDL */
        union {
-               struct dirty_queue      *dirty_queue;   /* modified pages */
+               MIDL2   *dirty_list;    /* modified pages */
                MDB_reader      *reader;
        } mt_u;
        MDB_dbx         *mt_dbxs;               /* array */
        MDB_db          *mt_dbs;
        unsigned int    mt_numdbs;
 
-#define MDB_TXN_RDONLY          0x01           /* read-only transaction */
-#define MDB_TXN_ERROR           0x02           /* an error has occurred */
+#define MDB_TXN_RDONLY         0x01            /* read-only transaction */
+#define MDB_TXN_ERROR          0x02            /* an error has occurred */
 #define MDB_TXN_METOGGLE       0x04            /* used meta page 1 */
        unsigned int    mt_flags;
 };
@@ -299,7 +306,10 @@ typedef struct MDB_xcursor {
 struct MDB_env {
        int                     me_fd;
        int                     me_lfd;
-       uint32_t        me_flags;
+       int                     me_mfd;                 /* just for writing the meta pages */
+#define        MDB_FATAL_ERROR 0x80000000U
+       uint32_t        me_flags;
+       uint32_t        me_extrapad;    /* unused for now */
        unsigned int    me_maxreaders;
        unsigned int    me_numdbs;
        unsigned int    me_maxdbs;
@@ -311,13 +321,15 @@ struct MDB_env {
        MDB_txn         *me_txn;                /* current write transaction */
        size_t          me_mapsize;
        off_t           me_size;                /* current file size */
+       pgno_t          me_maxpg;               /* me_mapsize / me_psize */
        unsigned int    me_psize;
-       int                     me_db_toggle;
+       unsigned int    me_db_toggle;
        MDB_dbx         *me_dbxs;               /* array */
        MDB_db          *me_dbs[2];
        MDB_oldpages *me_pghead;
        pthread_key_t   me_txkey;       /* thread-key for readers */
        pgno_t          me_free_pgs[MDB_IDL_UM_SIZE];
+       MIDL2           me_dirty_list[MDB_IDL_DB_SIZE];
 };
 
 #define NODESIZE        offsetof(MDB_node, mn_data)
@@ -331,7 +343,6 @@ struct MDB_env {
 #define NODEDSZ(node)   ((node)->mn_dsize)
 
 #define MDB_COMMIT_PAGES        64     /* max number of pages to write in one commit */
-#define MDB_MAXCACHE_DEF        1024   /* max number of pages to keep in cache  */
 
 static int  mdb_search_page_root(MDB_txn *txn,
                            MDB_dbi dbi, MDB_val *key,
@@ -342,9 +353,9 @@ static int  mdb_search_page(MDB_txn *txn,
                            MDB_cursor *cursor, int modify,
                            MDB_pageparent *mpp);
 
-static int  mdbenv_read_header(MDB_env *env, MDB_meta *meta);
-static int  mdbenv_read_meta(MDB_env *env, int *which);
-static int  mdbenv_write_meta(MDB_txn *txn);
+static int  mdb_env_read_header(MDB_env *env, MDB_meta *meta);
+static int  mdb_env_read_meta(MDB_env *env, int *which);
+static int  mdb_env_write_meta(MDB_txn *txn);
 static MDB_page *mdb_get_page(MDB_txn *txn, pgno_t pgno);
 
 static MDB_node *mdb_search_node(MDB_txn *txn, MDB_dbi dbi, MDB_page *mp,
@@ -434,6 +445,36 @@ memnrcmp(const void *s1, size_t n1, const void *s2, size_t n2)
        return *p1 - *p2;
 }
 
+char *
+mdb_version(int *maj, int *min, int *pat)
+{
+       *maj = MDB_VERSION_MAJOR;
+       *min = MDB_VERSION_MINOR;
+       *pat = MDB_VERSION_PATCH;
+       return MDB_VERSION_STRING;
+}
+
+static const char *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",
+       "MDB_VERSION_MISMATCH: Database environment version mismatch"
+};
+
+char *
+mdb_strerror(int err)
+{
+       if (!err)
+               return ("Successful return: 0");
+
+       if (err >= MDB_KEYEXIST && err <= MDB_VERSION_MISMATCH)
+               return (char *)errstr[err - MDB_KEYEXIST];
+
+       return strerror(err);
+}
+
 int
 mdb_cmp(MDB_txn *txn, MDB_dbi dbi, const MDB_val *a, const MDB_val *b)
 {
@@ -460,6 +501,7 @@ mdb_alloc_page(MDB_txn *txn, MDB_page *parent, unsigned int parent_idx, int num)
        MDB_dpage *dp;
        pgno_t pgno = P_INVALID;
        ULONG oldest;
+       MIDL2 mid;
 
        if (txn->mt_txnid > 2) {
 
@@ -512,11 +554,11 @@ mdb_alloc_page(MDB_txn *txn, MDB_page *parent, unsigned int parent_idx, int num)
        }
        if (txn->mt_env->me_pghead) {
                unsigned int i;
-               for (i=0; i<txn->mt_env->me_txns->mt_numreaders; i++) {
-                       ULONG mr = txn->mt_env->me_txns->mt_readers[i].mr_txnid;
+               for (i=0; i<txn->mt_env->me_txns->mti_numreaders; i++) {
+                       ULONG mr = txn->mt_env->me_txns->mti_readers[i].mr_txnid;
                        if (!mr) continue;
                        if (mr < oldest)
-                               oldest = txn->mt_env->me_txns->mt_readers[i].mr_txnid;
+                               oldest = txn->mt_env->me_txns->mti_readers[i].mr_txnid;
                }
                if (oldest > txn->mt_env->me_pghead->mo_txnid) {
                        MDB_oldpages *mop = txn->mt_env->me_pghead;
@@ -546,18 +588,25 @@ mdb_alloc_page(MDB_txn *txn, MDB_page *parent, unsigned int parent_idx, int num)
        }
        }
 
+       if (pgno == P_INVALID) {
+               /* DB size is maxed out */
+               if (txn->mt_next_pgno + num >= txn->mt_env->me_maxpg)
+                       return NULL;
+       }
        if ((dp = malloc(txn->mt_env->me_psize * num + sizeof(MDB_dhead))) == NULL)
                return NULL;
        dp->h.md_num = num;
        dp->h.md_parent = parent;
        dp->h.md_pi = parent_idx;
-       STAILQ_INSERT_TAIL(txn->mt_u.dirty_queue, dp, h.md_next);
        if (pgno == P_INVALID) {
                dp->p.mp_pgno = txn->mt_next_pgno;
                txn->mt_next_pgno += num;
        } else {
                dp->p.mp_pgno = pgno;
        }
+       mid.mid = dp->p.mp_pgno;
+       mid.mptr = dp;
+       mdb_midl2_insert(txn->mt_u.dirty_list, &mid);
 
        return dp;
 }
@@ -577,7 +626,7 @@ mdb_touch(MDB_txn *txn, MDB_pageparent *pp)
                if ((dp = mdb_alloc_page(txn, pp->mp_parent, pp->mp_pi, 1)) == NULL)
                        return ENOMEM;
                DPRINTF("touched page %lu -> %lu", mp->mp_pgno, dp->p.mp_pgno);
-               mdb_idl_insert(txn->mt_free_pgs, mp->mp_pgno);
+               mdb_midl_insert(txn->mt_free_pgs, mp->mp_pgno);
                pgno = dp->p.mp_pgno;
                memcpy(&dp->p, mp, txn->mt_env->me_psize);
                mp = &dp->p;
@@ -593,11 +642,11 @@ mdb_touch(MDB_txn *txn, MDB_pageparent *pp)
 }
 
 int
-mdbenv_sync(MDB_env *env)
+mdb_env_sync(MDB_env *env, int force)
 {
        int rc = 0;
-       if (!F_ISSET(env->me_flags, MDB_NOSYNC)) {
-               if (fsync(env->me_fd))
+       if (force || !F_ISSET(env->me_flags, MDB_NOSYNC)) {
+               if (fdatasync(env->me_fd))
                        rc = errno;
        }
        return rc;
@@ -609,6 +658,10 @@ mdb_txn_begin(MDB_env *env, int rdonly, MDB_txn **ret)
        MDB_txn *txn;
        int rc, toggle;
 
+       if (env->me_flags & MDB_FATAL_ERROR) {
+               DPRINTF("mdb_txn_begin: environment had fatal error, must shutdown!");
+               return MDB_PANIC;
+       }
        if ((txn = calloc(1, sizeof(MDB_txn))) == NULL) {
                DPRINTF("calloc: %s", strerror(errno));
                return ENOMEM;
@@ -617,39 +670,35 @@ mdb_txn_begin(MDB_env *env, int rdonly, MDB_txn **ret)
        if (rdonly) {
                txn->mt_flags |= MDB_TXN_RDONLY;
        } else {
-               txn->mt_u.dirty_queue = calloc(1, sizeof(*txn->mt_u.dirty_queue));
-               if (txn->mt_u.dirty_queue == NULL) {
-                       free(txn);
-                       return ENOMEM;
-               }
-               STAILQ_INIT(txn->mt_u.dirty_queue);
-
-               pthread_mutex_lock(&env->me_txns->mt_wmutex);
-               env->me_txns->mt_txnid++;
+               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;
+
+               pthread_mutex_lock(&env->me_txns->mti_wmutex);
+               env->me_txns->mti_txnid++;
        }
 
-       txn->mt_txnid = env->me_txns->mt_txnid;
+       txn->mt_txnid = env->me_txns->mti_txnid;
        if (rdonly) {
                MDB_reader *r = pthread_getspecific(env->me_txkey);
                if (!r) {
                        unsigned int i;
-                       pthread_mutex_lock(&env->me_txns->mt_mutex);
-                       for (i=0; i<env->me_txns->mt_numreaders; i++)
-                               if (env->me_txns->mt_readers[i].mr_pid == 0)
+                       pthread_mutex_lock(&env->me_txns->mti_mutex);
+                       for (i=0; i<env->me_txns->mti_numreaders; i++)
+                               if (env->me_txns->mti_readers[i].mr_pid == 0)
                                        break;
                        if (i == env->me_maxreaders) {
                                pthread_mutex_unlock(&env->me_txns->mti_mutex);
                                return ENOSPC;
                        }
-                       env->me_txns->mt_readers[i].mr_pid = getpid();
-                       env->me_txns->mt_readers[i].mr_tid = pthread_self();
-                       r = &env->me_txns->mt_readers[i];
+                       env->me_txns->mti_readers[i].mr_pid = getpid();
+                       env->me_txns->mti_readers[i].mr_tid = pthread_self();
+                       r = &env->me_txns->mti_readers[i];
                        pthread_setspecific(env->me_txkey, r);
-                       if (i >= env->me_txns->mt_numreaders)
-                               env->me_txns->mt_numreaders = i+1;
-                       pthread_mutex_unlock(&env->me_txns->mt_mutex);
+                       if (i >= env->me_txns->mti_numreaders)
+                               env->me_txns->mti_numreaders = i+1;
+                       pthread_mutex_unlock(&env->me_txns->mti_mutex);
                }
                r->mr_txnid = txn->mt_txnid;
                txn->mt_u.reader = r;
@@ -659,7 +708,8 @@ mdb_txn_begin(MDB_env *env, int rdonly, MDB_txn **ret)
 
        txn->mt_env = env;
 
-       if ((rc = mdbenv_read_meta(env, &toggle)) != MDB_SUCCESS) {
+       toggle = env->me_txns->mti_me_toggle;
+       if ((rc = mdb_env_read_meta(env, &toggle)) != MDB_SUCCESS) {
                mdb_txn_abort(txn);
                return rc;
        }
@@ -689,7 +739,6 @@ mdb_txn_begin(MDB_env *env, int rdonly, MDB_txn **ret)
 void
 mdb_txn_abort(MDB_txn *txn)
 {
-       MDB_dpage *dp;
        MDB_env *env;
 
        if (txn == NULL)
@@ -708,12 +757,8 @@ mdb_txn_abort(MDB_txn *txn)
                unsigned int i;
 
                /* Discard all dirty pages. */
-               while (!STAILQ_EMPTY(txn->mt_u.dirty_queue)) {
-                       dp = STAILQ_FIRST(txn->mt_u.dirty_queue);
-                       STAILQ_REMOVE_HEAD(txn->mt_u.dirty_queue, h.md_next);
-                       free(dp);
-               }
-               free(txn->mt_u.dirty_queue);
+               for (i=1; i<=txn->mt_u.dirty_list[0].mid; i++)
+                       free(txn->mt_u.dirty_list[i].mptr);
 
                while ((mop = txn->mt_env->me_pghead)) {
                        txn->mt_env->me_pghead = mop->mo_next;
@@ -721,10 +766,10 @@ mdb_txn_abort(MDB_txn *txn)
                }
 
                env->me_txn = NULL;
-               env->me_txns->mt_txnid--;
+               env->me_txns->mti_txnid--;
                for (i=2; i<env->me_numdbs; i++)
                        env->me_dbxs[i].md_dirty = 0;
-               pthread_mutex_unlock(&env->me_txns->mt_wmutex);
+               pthread_mutex_unlock(&env->me_txns->mti_wmutex);
        }
 
        free(txn);
@@ -748,9 +793,8 @@ mdb_txn_commit(MDB_txn *txn)
        env = txn->mt_env;
 
        if (F_ISSET(txn->mt_flags, MDB_TXN_RDONLY)) {
-               DPRINTF("attempt to commit read-only transaction");
                mdb_txn_abort(txn);
-               return EPERM;
+               return MDB_SUCCESS;
        }
 
        if (txn != env->me_txn) {
@@ -765,7 +809,7 @@ mdb_txn_commit(MDB_txn *txn)
                return EINVAL;
        }
 
-       if (STAILQ_EMPTY(txn->mt_u.dirty_queue))
+       if (!txn->mt_u.dirty_list[0].mid)
                goto done;
 
        DPRINTF("committing transaction %lu on mdbenv %p, root page %lu",
@@ -834,11 +878,13 @@ mdb_txn_commit(MDB_txn *txn)
        /* Commit up to MDB_COMMIT_PAGES dirty pages to disk until done.
         */
        next = 0;
+       i = 1;
        do {
                n = 0;
                done = 1;
                size = 0;
-               STAILQ_FOREACH(dp, txn->mt_u.dirty_queue, h.md_next) {
+               for (; i<=txn->mt_u.dirty_list[0].mid; i++) {
+                       dp = txn->mt_u.dirty_list[i].mptr;
                        if (dp->p.mp_pgno != next) {
                                if (n) {
                                        DPRINTF("committing %u dirty pages", n);
@@ -890,20 +936,17 @@ mdb_txn_commit(MDB_txn *txn)
 
        /* Drop the dirty pages.
         */
-       while (!STAILQ_EMPTY(txn->mt_u.dirty_queue)) {
-               dp = STAILQ_FIRST(txn->mt_u.dirty_queue);
-               STAILQ_REMOVE_HEAD(txn->mt_u.dirty_queue, h.md_next);
-               free(dp);
-       }
+       for (i=1; i<=txn->mt_u.dirty_list[0].mid; i++)
+               free(txn->mt_u.dirty_list[i].mptr);
 
-       if ((n = mdbenv_sync(env)) != 0 ||
-           (n = mdbenv_write_meta(txn)) != MDB_SUCCESS ||
-           (n = mdbenv_sync(env)) != 0) {
+       if ((n = mdb_env_sync(env, 0)) != 0 ||
+           (n = mdb_env_write_meta(txn)) != MDB_SUCCESS) {
                mdb_txn_abort(txn);
                return n;
        }
-       env->me_txn = NULL;
 
+done:
+       env->me_txn = NULL;
        /* update the DB tables */
        {
                int toggle = !env->me_db_toggle;
@@ -925,19 +968,14 @@ mdb_txn_commit(MDB_txn *txn)
                free(txn->mt_dbs);
        }
 
-       pthread_mutex_unlock(&env->me_txns->mt_wmutex);
-       free(txn->mt_u.dirty_queue);
+       pthread_mutex_unlock(&env->me_txns->mti_wmutex);
        free(txn);
-       txn = NULL;
-
-done:
-       mdb_txn_abort(txn);
 
        return MDB_SUCCESS;
 }
 
 static int
-mdbenv_read_header(MDB_env *env, MDB_meta *meta)
+mdb_env_read_header(MDB_env *env, MDB_meta *meta)
 {
        char             page[PAGESIZE];
        MDB_page        *p;
@@ -982,7 +1020,7 @@ mdbenv_read_header(MDB_env *env, MDB_meta *meta)
 }
 
 static int
-mdbenv_init_meta(MDB_env *env, MDB_meta *meta)
+mdb_env_init_meta(MDB_env *env, MDB_meta *meta)
 {
        MDB_page *p, *q;
        MDB_meta *m;
@@ -1022,22 +1060,26 @@ mdbenv_init_meta(MDB_env *env, MDB_meta *meta)
 }
 
 static int
-mdbenv_write_meta(MDB_txn *txn)
+mdb_env_write_meta(MDB_txn *txn)
 {
        MDB_env *env;
-       MDB_meta        meta;
+       MDB_meta        meta, metab;
        off_t off;
-       int rc, len;
+       int rc, len, toggle;
        char *ptr;
 
        assert(txn != NULL);
        assert(txn->mt_env != NULL);
 
+       toggle = !F_ISSET(txn->mt_flags, MDB_TXN_METOGGLE);
        DPRINTF("writing meta page %d for root page %lu",
-               !F_ISSET(txn->mt_flags, MDB_TXN_METOGGLE), txn->mt_dbs[MAIN_DBI].md_root);
+               toggle, txn->mt_dbs[MAIN_DBI].md_root);
 
        env = txn->mt_env;
 
+       metab.mm_txnid = env->me_metas[toggle]->mm_txnid;
+       metab.mm_last_pg = env->me_metas[toggle]->mm_last_pg;
+
        ptr = (char *)&meta;
        off = offsetof(MDB_meta, mm_dbs[0].md_depth);
        len = sizeof(MDB_meta) - off;
@@ -1048,34 +1090,43 @@ mdbenv_write_meta(MDB_txn *txn)
        meta.mm_last_pg = txn->mt_next_pgno - 1;
        meta.mm_txnid = txn->mt_txnid;
 
-       if (!F_ISSET(txn->mt_flags, MDB_TXN_METOGGLE))
+       if (toggle)
                off += env->me_psize;
        off += PAGEHDRSZ;
 
-       lseek(env->me_fd, off, SEEK_SET);
-       rc = write(env->me_fd, ptr, len);
+       /* Write to the SYNC fd */
+       rc = pwrite(env->me_mfd, ptr, len, off);
        if (rc != len) {
                DPRINTF("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;
+               rc = pwrite(env->me_fd, ptr, len, off);
+               env->me_flags |= MDB_FATAL_ERROR;
                return errno;
        }
+       txn->mt_env->me_txns->mti_me_toggle = toggle;
 
        return MDB_SUCCESS;
 }
 
 static int
-mdbenv_read_meta(MDB_env *env, int *which)
+mdb_env_read_meta(MDB_env *env, int *which)
 {
        int toggle = 0;
 
        assert(env != NULL);
 
-       if (env->me_metas[0]->mm_txnid < env->me_metas[1]->mm_txnid)
+       if (which)
+               toggle = *which;
+       else if (env->me_metas[0]->mm_txnid < env->me_metas[1]->mm_txnid)
                toggle = 1;
 
        if (env->me_meta != env->me_metas[toggle])
                env->me_meta = env->me_metas[toggle];
-       if (which)
-               *which = toggle;
 
        DPRINTF("Using meta page %d", toggle);
 
@@ -1083,7 +1134,7 @@ mdbenv_read_meta(MDB_env *env, int *which)
 }
 
 int
-mdbenv_create(MDB_env **env)
+mdb_env_create(MDB_env **env)
 {
        MDB_env *e;
 
@@ -1094,12 +1145,13 @@ mdbenv_create(MDB_env **env)
        e->me_maxdbs = 2;
        e->me_fd = -1;
        e->me_lfd = -1;
+       e->me_mfd = -1;
        *env = e;
        return MDB_SUCCESS;
 }
 
 int
-mdbenv_set_mapsize(MDB_env *env, size_t size)
+mdb_env_set_mapsize(MDB_env *env, size_t size)
 {
        if (env->me_map)
                return EINVAL;
@@ -1108,21 +1160,21 @@ mdbenv_set_mapsize(MDB_env *env, size_t size)
 }
 
 int
-mdbenv_set_maxdbs(MDB_env *env, int dbs)
+mdb_env_set_maxdbs(MDB_env *env, int dbs)
 {
        env->me_maxdbs = dbs;
        return MDB_SUCCESS;
 }
 
 int
-mdbenv_set_maxreaders(MDB_env *env, int readers)
+mdb_env_set_maxreaders(MDB_env *env, int readers)
 {
        env->me_maxreaders = readers;
        return MDB_SUCCESS;
 }
 
 int
-mdbenv_get_maxreaders(MDB_env *env, int *readers)
+mdb_env_get_maxreaders(MDB_env *env, int *readers)
 {
        if (!env || !readers)
                return EINVAL;
@@ -1131,7 +1183,7 @@ mdbenv_get_maxreaders(MDB_env *env, int *readers)
 }
 
 static int
-mdbenv_open2(MDB_env *env, unsigned int flags)
+mdb_env_open2(MDB_env *env, unsigned int flags)
 {
        int i, newenv = 0;
        MDB_meta meta;
@@ -1141,7 +1193,7 @@ mdbenv_open2(MDB_env *env, unsigned int flags)
 
        memset(&meta, 0, sizeof(meta));
 
-       if ((i = mdbenv_read_header(env, &meta)) != 0) {
+       if ((i = mdb_env_read_header(env, &meta)) != 0) {
                if (i != ENOENT)
                        return i;
                DPRINTF("new mdbenv");
@@ -1164,7 +1216,7 @@ mdbenv_open2(MDB_env *env, unsigned int flags)
                meta.mm_mapsize = env->me_mapsize;
                if (flags & MDB_FIXEDMAP)
                        meta.mm_address = env->me_map;
-               i = mdbenv_init_meta(env, &meta);
+               i = mdb_env_init_meta(env, &meta);
                if (i != MDB_SUCCESS) {
                        munmap(env->me_map, env->me_mapsize);
                        return i;
@@ -1172,11 +1224,13 @@ mdbenv_open2(MDB_env *env, unsigned int flags)
        }
        env->me_psize = meta.mm_psize;
 
-       p = (MDB_page *)(MDB_page *)(MDB_page *)(MDB_page *)(MDB_page *)(MDB_page *)(MDB_page *)(MDB_page *)(MDB_page *)env->me_map;
+       env->me_maxpg = env->me_mapsize / env->me_psize;
+
+       p = (MDB_page *)env->me_map;
        env->me_metas[0] = METADATA(p);
        env->me_metas[1] = (MDB_meta *)((char *)env->me_metas[0] + meta.mm_psize);
 
-       if ((i = mdbenv_read_meta(env, NULL)) != 0)
+       if ((i = mdb_env_read_meta(env, NULL)) != 0)
                return i;
 
        DPRINTF("opened database version %u, pagesize %u",
@@ -1192,7 +1246,7 @@ mdbenv_open2(MDB_env *env, unsigned int flags)
 }
 
 static void
-mdbenv_reader_dest(void *ptr)
+mdb_env_reader_dest(void *ptr)
 {
        MDB_reader *reader = ptr;
 
@@ -1203,11 +1257,13 @@ mdbenv_reader_dest(void *ptr)
 
 /* downgrade the exclusive lock on the region back to shared */
 static void
-mdbenv_share_locks(MDB_env *env)
+mdb_env_share_locks(MDB_env *env)
 {
        struct flock lock_info;
 
-       env->me_txns->mt_txnid = env->me_meta->mm_txnid;
+       env->me_txns->mti_txnid = env->me_meta->mm_txnid;
+       if (env->me_metas[0]->mm_txnid < env->me_metas[1]->mm_txnid)
+               env->me_txns->mti_me_toggle = 1;
 
        memset((void *)&lock_info, 0, sizeof(lock_info));
        lock_info.l_type = F_RDLCK;
@@ -1218,7 +1274,7 @@ mdbenv_share_locks(MDB_env *env)
 }
 
 static int
-mdbenv_setup_locks(MDB_env *env, char *lpath, int mode, int *excl)
+mdb_env_setup_locks(MDB_env *env, char *lpath, int mode, int *excl)
 {
        int rc;
        off_t size, rsize;
@@ -1271,23 +1327,27 @@ mdbenv_setup_locks(MDB_env *env, char *lpath, int mode, int *excl)
                pthread_mutexattr_t mattr;
 
                pthread_mutexattr_init(&mattr);
-               pthread_mutexattr_setpshared(&mattr, PTHREAD_PROCESS_SHARED);
-               pthread_mutex_init(&env->me_txns->mt_mutex, &mattr);
-               pthread_mutex_init(&env->me_txns->mt_wmutex, &mattr);
-               env->me_txns->mt_version = MDB_VERSION;
-               env->me_txns->mt_magic = MDB_MAGIC;
-               env->me_txns->mt_txnid = 0;
-               env->me_txns->mt_numreaders = 0;
+               rc = pthread_mutexattr_setpshared(&mattr, PTHREAD_PROCESS_SHARED);
+               if (rc) {
+                       goto fail;
+               }
+               pthread_mutex_init(&env->me_txns->mti_mutex, &mattr);
+               pthread_mutex_init(&env->me_txns->mti_wmutex, &mattr);
+               env->me_txns->mti_version = MDB_VERSION;
+               env->me_txns->mti_magic = MDB_MAGIC;
+               env->me_txns->mti_txnid = 0;
+               env->me_txns->mti_numreaders = 0;
+               env->me_txns->mti_me_toggle = 0;
 
        } else {
-               if (env->me_txns->mt_magic != MDB_MAGIC) {
+               if (env->me_txns->mti_magic != MDB_MAGIC) {
                        DPRINTF("lock region has invalid magic");
                        rc = EINVAL;
                        goto fail;
                }
-               if (env->me_txns->mt_version != MDB_VERSION) {
+               if (env->me_txns->mti_version != MDB_VERSION) {
                        DPRINTF("lock region is version %u, expected version %u",
-                               env->me_txns->mt_version, MDB_VERSION);
+                               env->me_txns->mti_version, MDB_VERSION);
                        rc = MDB_VERSION_MISMATCH;
                        goto fail;
                }
@@ -1300,6 +1360,7 @@ mdbenv_setup_locks(MDB_env *env, char *lpath, int mode, int *excl)
 
 fail:
        close(env->me_lfd);
+       env->me_lfd = -1;
        return rc;
 
 }
@@ -1307,7 +1368,7 @@ fail:
 #define LOCKNAME       "/lock.mdb"
 #define DATANAME       "/data.mdb"
 int
-mdbenv_open(MDB_env *env, const char *path, unsigned int flags, mode_t mode)
+mdb_env_open(MDB_env *env, const char *path, unsigned int flags, mode_t mode)
 {
        int             oflags, rc, len, excl;
        char *lpath, *dpath;
@@ -1320,7 +1381,7 @@ mdbenv_open(MDB_env *env, const char *path, unsigned int flags, mode_t mode)
        sprintf(lpath, "%s" LOCKNAME, path);
        sprintf(dpath, "%s" DATANAME, path);
 
-       rc = mdbenv_setup_locks(env, lpath, mode, &excl);
+       rc = mdb_env_setup_locks(env, lpath, mode, &excl);
        if (rc)
                goto leave;
 
@@ -1329,18 +1390,25 @@ mdbenv_open(MDB_env *env, const char *path, unsigned int flags, mode_t mode)
        else
                oflags = O_RDWR | O_CREAT;
 
-       if ((env->me_fd = open(dpath, oflags, mode)) == -1)
-               return errno;
+       if ((env->me_fd = open(dpath, oflags, mode)) == -1) {
+               rc = errno;
+               goto leave;
+       }
+
+       if ((rc = mdb_env_open2(env, flags)) == MDB_SUCCESS) {
+               /* synchronous fd for meta writes */
+               if (!(flags & (MDB_RDONLY|MDB_NOSYNC)))
+                       oflags |= O_DSYNC;
+               if ((env->me_mfd = open(dpath, oflags, mode)) == -1) {
+                       rc = errno;
+                       goto leave;
+               }
 
-       if ((rc = mdbenv_open2(env, flags)) != MDB_SUCCESS) {
-               close(env->me_fd);
-               env->me_fd = -1;
-       } else {
                env->me_path = strdup(path);
                DPRINTF("opened dbenv %p", (void *) env);
-               pthread_key_create(&env->me_txkey, mdbenv_reader_dest);
+               pthread_key_create(&env->me_txkey, mdb_env_reader_dest);
                if (excl)
-                       mdbenv_share_locks(env);
+                       mdb_env_share_locks(env);
                env->me_dbxs = calloc(env->me_maxdbs, sizeof(MDB_dbx));
                env->me_dbs[0] = calloc(env->me_maxdbs, sizeof(MDB_db));
                env->me_dbs[1] = calloc(env->me_maxdbs, sizeof(MDB_db));
@@ -1348,12 +1416,22 @@ mdbenv_open(MDB_env *env, const char *path, unsigned int flags, mode_t mode)
        }
 
 leave:
+       if (rc) {
+               if (env->me_fd >= 0) {
+                       close(env->me_fd);
+                       env->me_fd = -1;
+               }
+               if (env->me_lfd >= 0) {
+                       close(env->me_lfd);
+                       env->me_lfd = -1;
+               }
+       }
        free(lpath);
        return rc;
 }
 
 void
-mdbenv_close(MDB_env *env)
+mdb_env_close(MDB_env *env)
 {
        if (env == NULL)
                return;
@@ -1363,12 +1441,20 @@ mdbenv_close(MDB_env *env)
        free(env->me_dbxs);
        free(env->me_path);
 
+       pthread_key_delete(env->me_txkey);
+
        if (env->me_map) {
                munmap(env->me_map, env->me_mapsize);
        }
+       close(env->me_mfd);
        close(env->me_fd);
        if (env->me_txns) {
+               pid_t pid = getpid();
                size_t size = (env->me_maxreaders-1) * sizeof(MDB_reader) + sizeof(MDB_txninfo);
+               int i;
+               for (i=0; i<env->me_txns->mti_numreaders; i++)
+                       if (env->me_txns->mti_readers[i].mr_pid == pid)
+                               env->me_txns->mti_readers[i].mr_pid = 0;
                munmap(env->me_txns, size);
        }
        close(env->me_lfd);
@@ -1450,12 +1536,12 @@ cursor_pop_page(MDB_cursor *cursor)
 {
        MDB_ppage       *top;
 
-       top = CURSOR_TOP(cursor);
-       CURSOR_POP(cursor);
-
-       DPRINTF("popped page %lu off cursor %p", top->mp_page->mp_pgno, (void *) cursor);
+       if (cursor->mc_snum) {
+               top = CURSOR_TOP(cursor);
+               cursor->mc_snum--;
 
-       free(top);
+               DPRINTF("popped page %lu off cursor %p", top->mp_page->mp_pgno, (void *) cursor);
+       }
 }
 
 static MDB_ppage *
@@ -1465,10 +1551,9 @@ cursor_push_page(MDB_cursor *cursor, MDB_page *mp)
 
        DPRINTF("pushing page %lu on cursor %p", mp->mp_pgno, (void *) cursor);
 
-       if ((ppage = calloc(1, sizeof(MDB_ppage))) == NULL)
-               return NULL;
+       ppage = &cursor->mc_stack[cursor->mc_snum++];
        ppage->mp_page = mp;
-       CURSOR_PUSH(cursor, ppage);
+       ppage->mp_ki = 0;
        return ppage;
 }
 
@@ -1478,14 +1563,16 @@ mdb_get_page(MDB_txn *txn, pgno_t pgno)
        MDB_page *p = NULL;
        int found = 0;
 
-       if (!F_ISSET(txn->mt_flags, MDB_TXN_RDONLY) && !STAILQ_EMPTY(txn->mt_u.dirty_queue)) {
+       if (!F_ISSET(txn->mt_flags, MDB_TXN_RDONLY) && txn->mt_u.dirty_list[0].mid) {
                MDB_dpage *dp;
-               STAILQ_FOREACH(dp, txn->mt_u.dirty_queue, h.md_next) {
-                       if (dp->p.mp_pgno == pgno) {
-                               p = &dp->p;
-                               found = 1;
-                               break;
-                       }
+               MIDL2 id;
+               unsigned x;
+               id.mid = pgno;
+               x = mdb_midl2_search(txn->mt_u.dirty_list, &id);
+               if (x <= txn->mt_u.dirty_list[0].mid && txn->mt_u.dirty_list[x].mid == pgno) {
+                       dp = txn->mt_u.dirty_list[x].mptr;
+                       p = &dp->p;
+                       found = 1;
                }
        }
        if (!found) {
@@ -1504,7 +1591,7 @@ mdb_search_page_root(MDB_txn *txn, MDB_dbi dbi, MDB_val *key,
        int rc;
 
        if (cursor && cursor_push_page(cursor, mp) == NULL)
-               return MDB_FAIL;
+               return ENOMEM;
 
        while (IS_BRANCH(mp)) {
                unsigned int     i = 0;
@@ -1541,12 +1628,12 @@ mdb_search_page_root(MDB_txn *txn, MDB_dbi dbi, MDB_val *key,
 
                mpp->mp_parent = mp;
                if ((mp = mdb_get_page(txn, NODEPGNO(node))) == NULL)
-                       return MDB_FAIL;
+                       return MDB_PAGE_NOTFOUND;
                mpp->mp_pi = i;
                mpp->mp_page = mp;
 
                if (cursor && cursor_push_page(cursor, mp) == NULL)
-                       return MDB_FAIL;
+                       return ENOMEM;
 
                if (modify) {
                        MDB_dhead *dh = ((MDB_dhead *)mp)-1;
@@ -1563,7 +1650,7 @@ mdb_search_page_root(MDB_txn *txn, MDB_dbi dbi, MDB_val *key,
        if (!IS_LEAF(mp)) {
                DPRINTF("internal error, index points to a %02X page!?",
                    mp->mp_flags);
-               return MDB_FAIL;
+               return MDB_CORRUPTED;
        }
 
        DPRINTF("found leaf page %lu for key %.*s", mp->mp_pgno,
@@ -1601,7 +1688,7 @@ mdb_search_page(MDB_txn *txn, MDB_dbi dbi, MDB_val *key,
        }
 
        if ((mpp->mp_page = mdb_get_page(txn, root)) == NULL)
-               return MDB_FAIL;
+               return MDB_PAGE_NOTFOUND;
 
        DPRINTF("root page has flags 0x%X", mpp->mp_page->mp_flags);
 
@@ -1645,9 +1732,9 @@ mdb_read_data(MDB_txn *txn, MDB_node *leaf, MDB_val *data)
        memcpy(&pgno, NODEDATA(leaf), sizeof(pgno));
        if ((omp = mdb_get_page(txn, pgno)) == NULL) {
                DPRINTF("read overflow page %lu failed", pgno);
-               return MDB_FAIL;
+               return MDB_PAGE_NOTFOUND;
        }
-       data->mv_data = omp;
+       data->mv_data = METADATA(omp);
 
        return MDB_SUCCESS;
 }
@@ -1677,7 +1764,7 @@ mdb_get(MDB_txn *txn, MDB_dbi dbi,
        leaf = mdb_search_node(txn, dbi, mpp.mp_page, key, &exact, NULL);
        if (leaf && exact) {
                /* Return first duplicate data item */
-               if (F_ISSET(txn->mt_dbs[dbi].md_flags, MDB_DUPSORT)) {
+               if (F_ISSET(leaf->mn_flags, F_DUPDATA)) {
                        MDB_xcursor mx;
 
                        mdb_xcursor_init0(txn, dbi, &mx);
@@ -1700,13 +1787,13 @@ mdb_sibling(MDB_cursor *cursor, int move_right)
 {
        int              rc;
        MDB_node        *indx;
-       MDB_ppage       *parent, *top;
+       MDB_ppage       *parent;
        MDB_page        *mp;
 
-       top = CURSOR_TOP(cursor);
-       if ((parent = SLIST_NEXT(top, mp_entry)) == NULL) {
+       if (cursor->mc_snum < 2) {
                return MDB_NOTFOUND;            /* root has no siblings */
        }
+       parent = CURSOR_PARENT(cursor);
 
        DPRINTF("parent page is page %lu, index %u",
            parent->mp_page->mp_pgno, parent->mp_ki);
@@ -1731,7 +1818,7 @@ mdb_sibling(MDB_cursor *cursor, int move_right)
 
        indx = NODEPTR(parent->mp_page, parent->mp_ki);
        if ((mp = mdb_get_page(cursor->mc_txn, indx->mn_pgno)) == NULL)
-               return MDB_FAIL;
+               return MDB_PAGE_NOTFOUND;
 #if 0
        mp->parent = parent->mp_page;
        mp->parent_index = parent->mp_ki;
@@ -1768,17 +1855,18 @@ mdb_cursor_next(MDB_cursor *cursor, MDB_val *key, MDB_val *data, MDB_cursor_op o
 
        assert(cursor->mc_initialized);
 
+       top = CURSOR_TOP(cursor);
+       mp = top->mp_page;
+
        if (cursor->mc_txn->mt_dbs[cursor->mc_dbi].md_flags & MDB_DUPSORT) {
-               if (op == MDB_NEXT || op == MDB_NEXT_DUP) {
+               leaf = NODEPTR(mp, top->mp_ki);
+               if ((op == MDB_NEXT || op == MDB_NEXT_DUP) && F_ISSET(leaf->mn_flags, F_DUPDATA)) {
                        rc = mdb_cursor_next(&cursor->mc_xcursor->mx_cursor, data, NULL, MDB_NEXT);
                        if (op != MDB_NEXT || rc == MDB_SUCCESS)
                                return rc;
                }
        }
 
-       top = CURSOR_TOP(cursor);
-       mp = top->mp_page;
-
        DPRINTF("cursor_next: top page is %lu in cursor %p", mp->mp_pgno, (void *) cursor);
 
        if (top->mp_ki + 1 >= NUMKEYS(mp)) {
@@ -1799,12 +1887,14 @@ mdb_cursor_next(MDB_cursor *cursor, MDB_val *key, MDB_val *data, MDB_cursor_op o
        assert(IS_LEAF(mp));
        leaf = NODEPTR(mp, top->mp_ki);
 
+       if (F_ISSET(leaf->mn_flags, F_DUPDATA)) {
+               mdb_xcursor_init1(cursor->mc_txn, cursor->mc_dbi, cursor->mc_xcursor, leaf);
+       }
        if (data) {
                if ((rc = mdb_read_data(cursor->mc_txn, leaf, data) != MDB_SUCCESS))
                        return rc;
 
-               if (cursor->mc_txn->mt_dbs[cursor->mc_dbi].md_flags & MDB_DUPSORT) {
-                       mdb_xcursor_init1(cursor->mc_txn, cursor->mc_dbi, cursor->mc_xcursor, leaf);
+               if (F_ISSET(leaf->mn_flags, F_DUPDATA)) {
                        rc = mdb_cursor_first(&cursor->mc_xcursor->mx_cursor, data, NULL);
                        if (rc != MDB_SUCCESS)
                                return rc;
@@ -1824,17 +1914,18 @@ mdb_cursor_prev(MDB_cursor *cursor, MDB_val *key, MDB_val *data, MDB_cursor_op o
 
        assert(cursor->mc_initialized);
 
+       top = CURSOR_TOP(cursor);
+       mp = top->mp_page;
+
        if (cursor->mc_txn->mt_dbs[cursor->mc_dbi].md_flags & MDB_DUPSORT) {
-               if (op == MDB_PREV || op == MDB_PREV_DUP) {
+               leaf = NODEPTR(mp, top->mp_ki);
+               if ((op == MDB_PREV || op == MDB_PREV_DUP) && F_ISSET(leaf->mn_flags, F_DUPDATA)) {
                        rc = mdb_cursor_prev(&cursor->mc_xcursor->mx_cursor, data, NULL, MDB_PREV);
                        if (op != MDB_PREV || rc == MDB_SUCCESS)
                                return rc;
                }
        }
 
-       top = CURSOR_TOP(cursor);
-       mp = top->mp_page;
-
        DPRINTF("cursor_prev: top page is %lu in cursor %p", mp->mp_pgno, (void *) cursor);
 
        if (top->mp_ki == 0)  {
@@ -1857,12 +1948,14 @@ mdb_cursor_prev(MDB_cursor *cursor, MDB_val *key, MDB_val *data, MDB_cursor_op o
        assert(IS_LEAF(mp));
        leaf = NODEPTR(mp, top->mp_ki);
 
+       if (F_ISSET(leaf->mn_flags, F_DUPDATA)) {
+               mdb_xcursor_init1(cursor->mc_txn, cursor->mc_dbi, cursor->mc_xcursor, leaf);
+       }
        if (data) {
                if ((rc = mdb_read_data(cursor->mc_txn, leaf, data) != MDB_SUCCESS))
                        return rc;
 
-               if (cursor->mc_txn->mt_dbs[cursor->mc_dbi].md_flags & MDB_DUPSORT) {
-                       mdb_xcursor_init1(cursor->mc_txn, cursor->mc_dbi, cursor->mc_xcursor, leaf);
+               if (F_ISSET(leaf->mn_flags, F_DUPDATA)) {
                        rc = mdb_cursor_last(&cursor->mc_xcursor->mx_cursor, data, NULL);
                        if (rc != MDB_SUCCESS)
                                return rc;
@@ -1885,8 +1978,7 @@ mdb_cursor_set(MDB_cursor *cursor, MDB_val *key, MDB_val *data,
        assert(key);
        assert(key->mv_size > 0);
 
-       while (CURSOR_TOP(cursor) != NULL)
-               cursor_pop_page(cursor);
+       cursor->mc_snum = 0;
 
        rc = mdb_search_page(cursor->mc_txn, cursor->mc_dbi, key, cursor, 0, &mpp);
        if (rc != MDB_SUCCESS)
@@ -1914,12 +2006,11 @@ mdb_cursor_set(MDB_cursor *cursor, MDB_val *key, MDB_val *data,
        cursor->mc_initialized = 1;
        cursor->mc_eof = 0;
 
+       if (F_ISSET(leaf->mn_flags, F_DUPDATA)) {
+               mdb_xcursor_init1(cursor->mc_txn, cursor->mc_dbi, cursor->mc_xcursor, leaf);
+       }
        if (data) {
-               if ((rc = mdb_read_data(cursor->mc_txn, leaf, data)) != MDB_SUCCESS)
-                       return rc;
-
-               if (cursor->mc_txn->mt_dbs[cursor->mc_dbi].md_flags & MDB_DUPSORT) {
-                       mdb_xcursor_init1(cursor->mc_txn, cursor->mc_dbi, cursor->mc_xcursor, leaf);
+               if (F_ISSET(leaf->mn_flags, F_DUPDATA)) {
                        if (op == MDB_SET || op == MDB_SET_RANGE) {
                                rc = mdb_cursor_first(&cursor->mc_xcursor->mx_cursor, data, NULL);
                        } else {
@@ -1936,6 +2027,9 @@ mdb_cursor_set(MDB_cursor *cursor, MDB_val *key, MDB_val *data,
                                if (rc != MDB_SUCCESS)
                                        return rc;
                        }
+               } else {
+                       if ((rc = mdb_read_data(cursor->mc_txn, leaf, data)) != MDB_SUCCESS)
+                               return rc;
                }
        }
 
@@ -1956,8 +2050,7 @@ mdb_cursor_first(MDB_cursor *cursor, MDB_val *key, MDB_val *data)
        MDB_pageparent  mpp;
        MDB_node        *leaf;
 
-       while (CURSOR_TOP(cursor) != NULL)
-               cursor_pop_page(cursor);
+       cursor->mc_snum = 0;
 
        rc = mdb_search_page(cursor->mc_txn, cursor->mc_dbi, NULL, cursor, 0, &mpp);
        if (rc != MDB_SUCCESS)
@@ -1969,14 +2062,14 @@ mdb_cursor_first(MDB_cursor *cursor, MDB_val *key, MDB_val *data)
        cursor->mc_eof = 0;
 
        if (data) {
-               if ((rc = mdb_read_data(cursor->mc_txn, leaf, data)) != MDB_SUCCESS)
-                       return rc;
-
-               if (cursor->mc_txn->mt_dbs[cursor->mc_dbi].md_flags & MDB_DUPSORT) {
+               if (F_ISSET(leaf->mn_flags, F_DUPDATA)) {
                        mdb_xcursor_init1(cursor->mc_txn, cursor->mc_dbi, cursor->mc_xcursor, leaf);
                        rc = mdb_cursor_first(&cursor->mc_xcursor->mx_cursor, data, NULL);
                        if (rc)
                                return rc;
+               } else {
+                       if ((rc = mdb_read_data(cursor->mc_txn, leaf, data)) != MDB_SUCCESS)
+                               return rc;
                }
        }
        return mdb_set_key(leaf, key);
@@ -1991,8 +2084,7 @@ mdb_cursor_last(MDB_cursor *cursor, MDB_val *key, MDB_val *data)
        MDB_node        *leaf;
        MDB_val lkey;
 
-       while (CURSOR_TOP(cursor) != NULL)
-               cursor_pop_page(cursor);
+       cursor->mc_snum = 0;
 
        lkey.mv_size = MAXKEYSIZE+1;
        lkey.mv_data = NULL;
@@ -2010,14 +2102,14 @@ mdb_cursor_last(MDB_cursor *cursor, MDB_val *key, MDB_val *data)
        top->mp_ki = NUMKEYS(top->mp_page) - 1;
 
        if (data) {
-               if ((rc = mdb_read_data(cursor->mc_txn, leaf, data)) != MDB_SUCCESS)
-                       return rc;
-
-               if (cursor->mc_txn->mt_dbs[cursor->mc_dbi].md_flags & MDB_DUPSORT) {
+               if (F_ISSET(leaf->mn_flags, F_DUPDATA)) {
                        mdb_xcursor_init1(cursor->mc_txn, cursor->mc_dbi, cursor->mc_xcursor, leaf);
                        rc = mdb_cursor_last(&cursor->mc_xcursor->mx_cursor, data, NULL);
                        if (rc)
                                return rc;
+               } else {
+                       if ((rc = mdb_read_data(cursor->mc_txn, leaf, data)) != MDB_SUCCESS)
+                               return rc;
                }
        }
 
@@ -2169,7 +2261,7 @@ mdb_add_node(MDB_txn *txn, MDB_dbi dbi, MDB_page *mp, indx_t indx,
                            data->mv_size);
                        node_size += sizeof(pgno_t);
                        if ((ofp = mdb_new_page(txn, dbi, P_OVERFLOW, ovpages)) == NULL)
-                               return MDB_FAIL;
+                               return ENOMEM;
                        DPRINTF("allocated overflow page %lu", ofp->p.mp_pgno);
                        flags |= F_BIGDATA;
                } else {
@@ -2289,7 +2381,7 @@ mdb_xcursor_init0(MDB_txn *txn, MDB_dbi dbi, MDB_xcursor *mx)
        mx->mx_dbxs[dbn+1].md_dirty = 0;
        mx->mx_txn.mt_numdbs = dbn+2;
 
-       SLIST_INIT(&mx->mx_cursor.mc_stack);
+       mx->mx_cursor.mc_snum = 0;
        mx->mx_cursor.mc_txn = &mx->mx_txn;
        mx->mx_cursor.mc_dbi = dbn+1;
 }
@@ -2344,7 +2436,6 @@ mdb_cursor_open(MDB_txn *txn, MDB_dbi dbi, MDB_cursor **ret)
                size += sizeof(MDB_xcursor);
 
        if ((cursor = calloc(1, size)) != NULL) {
-               SLIST_INIT(&cursor->mc_stack);
                cursor->mc_dbi = dbi;
                cursor->mc_txn = txn;
                if (txn->mt_dbs[dbi].md_flags & MDB_DUPSORT) {
@@ -2365,16 +2456,25 @@ mdb_cursor_open(MDB_txn *txn, MDB_dbi dbi, MDB_cursor **ret)
 int
 mdb_cursor_count(MDB_cursor *mc, unsigned long *countp)
 {
+       MDB_ppage       *top;
+       MDB_node        *leaf;
+
        if (mc == NULL || countp == NULL)
                return EINVAL;
 
        if (!(mc->mc_txn->mt_dbs[mc->mc_dbi].md_flags & MDB_DUPSORT))
                return EINVAL;
 
-       if (!mc->mc_xcursor->mx_cursor.mc_initialized)
-               return EINVAL;
+       top = CURSOR_TOP(mc);
+       leaf = NODEPTR(top->mp_page, top->mp_ki);
+       if (!F_ISSET(leaf->mn_flags, F_DUPDATA)) {
+               *countp = 1;
+       } else {
+               if (!mc->mc_xcursor->mx_cursor.mc_initialized)
+                       return EINVAL;
 
-       *countp = mc->mc_xcursor->mx_txn.mt_dbs[mc->mc_xcursor->mx_cursor.mc_dbi].md_entries;
+               *countp = mc->mc_xcursor->mx_txn.mt_dbs[mc->mc_xcursor->mx_cursor.mc_dbi].md_entries;
+       }
        return MDB_SUCCESS;
 }
 
@@ -2382,14 +2482,6 @@ void
 mdb_cursor_close(MDB_cursor *cursor)
 {
        if (cursor != NULL) {
-               while(!CURSOR_EMPTY(cursor))
-                       cursor_pop_page(cursor);
-               if (cursor->mc_txn->mt_dbs[cursor->mc_dbi].md_flags & MDB_DUPSORT) {
-                       mdb_xcursor_fini(cursor->mc_txn, cursor->mc_dbi, cursor->mc_xcursor);
-                       while(!CURSOR_EMPTY(&cursor->mc_xcursor->mx_cursor))
-                               cursor_pop_page(&cursor->mc_xcursor->mx_cursor);
-               }
-
                free(cursor);
        }
 }
@@ -2604,7 +2696,7 @@ mdb_rebalance(MDB_txn *txn, MDB_dbi dbi, MDB_pageparent *mpp)
                        DPRINTF("collapsing root page!");
                        txn->mt_dbs[dbi].md_root = NODEPGNO(NODEPTR(mpp->mp_page, 0));
                        if ((root = mdb_get_page(txn, txn->mt_dbs[dbi].md_root)) == NULL)
-                               return MDB_FAIL;
+                               return MDB_PAGE_NOTFOUND;
                        txn->mt_dbs[dbi].md_depth--;
                        txn->mt_dbs[dbi].md_branch_pages--;
                } else
@@ -2630,7 +2722,7 @@ mdb_rebalance(MDB_txn *txn, MDB_dbi dbi, MDB_pageparent *mpp)
                DPRINTF("reading right neighbor");
                node = NODEPTR(mpp->mp_parent, mpp->mp_pi + 1);
                if ((npp.mp_page = mdb_get_page(txn, NODEPGNO(node))) == NULL)
-                       return MDB_FAIL;
+                       return MDB_PAGE_NOTFOUND;
                npp.mp_pi = mpp->mp_pi + 1;
                si = 0;
                di = NUMKEYS(mpp->mp_page);
@@ -2640,7 +2732,7 @@ mdb_rebalance(MDB_txn *txn, MDB_dbi dbi, MDB_pageparent *mpp)
                DPRINTF("reading left neighbor");
                node = NODEPTR(mpp->mp_parent, mpp->mp_pi - 1);
                if ((npp.mp_page = mdb_get_page(txn, NODEPGNO(node))) == NULL)
-                       return MDB_FAIL;
+                       return MDB_PAGE_NOTFOUND;
                npp.mp_pi = mpp->mp_pi - 1;
                si = NUMKEYS(npp.mp_page) - 1;
                di = 0;
@@ -2679,7 +2771,7 @@ mdb_del0(MDB_txn *txn, MDB_dbi dbi, unsigned int ki, MDB_pageparent *mpp, MDB_no
                ovpages = OVPAGES(NODEDSZ(leaf), txn->mt_env->me_psize);
                for (i=0; i<ovpages; i++) {
                        DPRINTF("freed ov page %lu", pg);
-                       mdb_idl_insert(txn->mt_free_pgs, pg);
+                       mdb_midl_insert(txn->mt_free_pgs, pg);
                        pg++;
                }
        }
@@ -2727,7 +2819,7 @@ mdb_del(MDB_txn *txn, MDB_dbi dbi,
                return MDB_NOTFOUND;
        }
 
-       if (F_ISSET(txn->mt_dbs[dbi].md_flags, MDB_DUPSORT)) {
+       if (F_ISSET(leaf->mn_flags, F_DUPDATA)) {
                MDB_xcursor mx;
                MDB_pageparent mp2;
 
@@ -2755,28 +2847,25 @@ mdb_del(MDB_txn *txn, MDB_dbi dbi,
                                unsigned int i;
 
                                cursor_pop_page(&mx.mx_cursor);
-                               top = CURSOR_TOP(&mx.mx_cursor);
-                               if (top != NULL) {
-                                       parent = SLIST_NEXT(top, mp_entry);
-                                       while (parent != NULL) {
+                               if (mx.mx_cursor.mc_snum) {
+                                       top = CURSOR_TOP(&mx.mx_cursor);
+                                       while (mx.mx_cursor.mc_snum > 1) {
+                                               parent = CURSOR_PARENT(&mx.mx_cursor);
                                                for (i=0; i<NUMKEYS(top->mp_page); i++) {
                                                        ni = NODEPTR(top->mp_page, i);
-                                                       mdb_idl_insert(txn->mt_free_pgs, ni->mn_pgno);
+                                                       mdb_midl_insert(txn->mt_free_pgs, ni->mn_pgno);
                                                }
-                                               if (parent) {
-                                                       parent->mp_ki++;
-                                                       if (parent->mp_ki >= NUMKEYS(parent->mp_page)) {
-                                                               cursor_pop_page(&mx.mx_cursor);
-                                                               top = CURSOR_TOP(&mx.mx_cursor);
-                                                               parent = SLIST_NEXT(top, mp_entry);
-                                                       } else {
-                                                               ni = NODEPTR(parent->mp_page, parent->mp_ki);
-                                                               top->mp_page = mdb_get_page(&mx.mx_txn, ni->mn_pgno);
-                                                       }
+                                               parent->mp_ki++;
+                                               if (parent->mp_ki >= NUMKEYS(parent->mp_page)) {
+                                                       cursor_pop_page(&mx.mx_cursor);
+                                                       top = parent;
+                                               } else {
+                                                       ni = NODEPTR(parent->mp_page, parent->mp_ki);
+                                                       top->mp_page = mdb_get_page(&mx.mx_txn, ni->mn_pgno);
                                                }
                                        }
                                }
-                               mdb_idl_insert(txn->mt_free_pgs, mx.mx_txn.mt_dbs[mx.mx_cursor.mc_dbi].md_root);
+                               mdb_midl_insert(txn->mt_free_pgs, mx.mx_txn.mt_dbs[mx.mx_cursor.mc_dbi].md_root);
                        }
                }
        }
@@ -2819,7 +2908,7 @@ mdb_split(MDB_txn *txn, MDB_dbi dbi, MDB_page **mpp, unsigned int *newindxp,
 
        if (mdp->h.md_parent == NULL) {
                if ((pdp = mdb_new_page(txn, dbi, P_BRANCH, 1)) == NULL)
-                       return MDB_FAIL;
+                       return ENOMEM;
                mdp->h.md_pi = 0;
                mdp->h.md_parent = &pdp->p;
                txn->mt_dbs[dbi].md_root = pdp->p.mp_pgno;
@@ -2827,23 +2916,23 @@ mdb_split(MDB_txn *txn, MDB_dbi dbi, MDB_page **mpp, unsigned int *newindxp,
                txn->mt_dbs[dbi].md_depth++;
 
                /* Add left (implicit) pointer. */
-               if (mdb_add_node(txn, dbi, &pdp->p, 0, NULL, NULL,
-                   mdp->p.mp_pgno, 0) != MDB_SUCCESS)
-                       return MDB_FAIL;
+               if ((rc = mdb_add_node(txn, dbi, &pdp->p, 0, NULL, NULL,
+                   mdp->p.mp_pgno, 0)) != MDB_SUCCESS)
+                       return rc;
        } else {
                DPRINTF("parent branch page is %lu", mdp->h.md_parent->mp_pgno);
        }
 
        /* Create a right sibling. */
        if ((rdp = mdb_new_page(txn, dbi, mdp->p.mp_flags, 1)) == NULL)
-               return MDB_FAIL;
+               return ENOMEM;
        rdp->h.md_parent = mdp->h.md_parent;
        rdp->h.md_pi = mdp->h.md_pi + 1;
        DPRINTF("new right sibling: page %lu", rdp->p.mp_pgno);
 
        /* Move half of the keys to the right sibling. */
        if ((copy = malloc(txn->mt_env->me_psize)) == NULL)
-               return MDB_FAIL;
+               return ENOMEM;
        memcpy(copy, &mdp->p, txn->mt_env->me_psize);
        memset(&mdp->p.mp_ptrs, 0, txn->mt_env->me_psize - PAGEHDRSZ);
        mdp->p.mp_lower = PAGEHDRSZ;
@@ -2885,7 +2974,7 @@ mdb_split(MDB_txn *txn, MDB_dbi dbi, MDB_page **mpp, unsigned int *newindxp,
        }
        if (rc != MDB_SUCCESS) {
                free(copy);
-               return MDB_FAIL;
+               return rc;
        }
 
        for (i = j = 0; i <= NUMKEYS(copy); j++) {
@@ -2952,26 +3041,46 @@ mdb_put0(MDB_txn *txn, MDB_dbi dbi,
        unsigned int     ki;
        MDB_node        *leaf;
        MDB_pageparent  mpp;
-       MDB_val xdata, *rdata;
+       MDB_val xdata, *rdata, dkey;
        MDB_db dummy;
+       char dbuf[PAGESIZE];
+       int do_sub = 0;
 
        DPRINTF("==> put key %.*s, size %zu, data size %zu",
                (int)key->mv_size, (char *)key->mv_data, key->mv_size, data->mv_size);
 
+       dkey.mv_size = 0;
        mpp.mp_parent = NULL;
        mpp.mp_pi = 0;
        rc = mdb_search_page(txn, dbi, key, NULL, 1, &mpp);
        if (rc == MDB_SUCCESS) {
                leaf = mdb_search_node(txn, dbi, mpp.mp_page, key, &exact, &ki);
                if (leaf && exact) {
-                       if (F_ISSET(txn->mt_dbs[dbi].md_flags, MDB_DUPSORT)) {
-                               goto put_sub;
-                       }
                        if (flags == MDB_NOOVERWRITE) {
                                DPRINTF("duplicate key %.*s",
                                    (int)key->mv_size, (char *)key->mv_data);
                                return MDB_KEYEXIST;
                        }
+                       if (F_ISSET(txn->mt_dbs[dbi].md_flags, MDB_DUPSORT)) {
+                               /* Was a single item before, must convert now */
+                               if (!F_ISSET(leaf->mn_flags, F_DUPDATA)) {
+                                       dkey.mv_size = NODEDSZ(leaf);
+                                       memcpy(dbuf, NODEDATA(leaf), dkey.mv_size);
+                                       memset(&dummy, 0, sizeof(dummy));
+                                       dummy.md_root = P_INVALID;
+                                       if (dkey.mv_size == sizeof(MDB_db)) {
+                                               memcpy(NODEDATA(leaf), &dummy, sizeof(dummy));
+                                               goto put_sub;
+                                       }
+                                       mdb_del_node(mpp.mp_page, ki);
+                                       do_sub = 1;
+                                       rdata = &xdata;
+                                       xdata.mv_size = sizeof(MDB_db);
+                                       xdata.mv_data = &dummy;
+                                       goto new_sub;
+                               }
+                               goto put_sub;
+                       }
                        /* same size, just replace it */
                        if (NODEDSZ(leaf) == data->mv_size) {
                                memcpy(NODEDATA(leaf), data->mv_data, data->mv_size);
@@ -2993,6 +3102,7 @@ mdb_put0(MDB_txn *txn, MDB_dbi dbi,
                mpp.mp_page = &dp->p;
                txn->mt_dbs[dbi].md_root = mpp.mp_page->mp_pgno;
                txn->mt_dbs[dbi].md_depth++;
+               txn->mt_dbxs[dbi].md_dirty = 1;
                ki = 0;
        }
        else
@@ -3002,20 +3112,9 @@ mdb_put0(MDB_txn *txn, MDB_dbi dbi,
        DPRINTF("there are %u keys, should insert new key at index %i",
                NUMKEYS(mpp.mp_page), ki);
 
-       /* For sorted dups, the data item at this level is a DB record
-        * for a child DB; the actual data elements are stored as keys
-        * in the child DB.
-        */
-       if (F_ISSET(txn->mt_dbs[dbi].md_flags, MDB_DUPSORT)) {
-               rdata = &xdata;
-               xdata.mv_size = sizeof(MDB_db);
-               xdata.mv_data = &dummy;
-               memset(&dummy, 0, sizeof(dummy));
-               dummy.md_root = P_INVALID;
-       } else {
-               rdata = data;
-       }
+       rdata = data;
 
+new_sub:
        if (SIZELEFT(mpp.mp_page) < mdb_leaf_size(txn->mt_env, key, rdata)) {
                rc = mdb_split(txn, dbi, &mpp.mp_page, &ki, key, rdata, P_INVALID);
        } else {
@@ -3039,7 +3138,7 @@ mdb_put0(MDB_txn *txn, MDB_dbi dbi,
                 * size limits on dupdata. The actual data fields of the child
                 * DB are all zero size.
                 */
-               if (F_ISSET(txn->mt_dbs[dbi].md_flags, MDB_DUPSORT)) {
+               if (do_sub) {
                        MDB_xcursor mx;
 
                        leaf = NODEPTR(mpp.mp_page, ki);
@@ -3050,6 +3149,13 @@ put_sub:
                        xdata.mv_data = "";
                        if (flags == MDB_NODUPDATA)
                                flags = MDB_NOOVERWRITE;
+                       /* converted, write the original data first */
+                       if (dkey.mv_size) {
+                               dkey.mv_data = dbuf;
+                               rc = mdb_put0(&mx.mx_txn, mx.mx_cursor.mc_dbi, &dkey, &xdata, flags);
+                               if (rc) return rc;
+                               leaf->mn_flags |= F_DUPDATA;
+                       }
                        rc = mdb_put0(&mx.mx_txn, mx.mx_cursor.mc_dbi, data, &xdata, flags);
                        mdb_xcursor_fini(txn, dbi, &mx);
                        memcpy(NODEDATA(leaf), &mx.mx_txn.mt_dbs[mx.mx_cursor.mc_dbi],
@@ -3086,7 +3192,20 @@ mdb_put(MDB_txn *txn, MDB_dbi dbi,
 }
 
 int
-mdbenv_get_flags(MDB_env *env, unsigned int *arg)
+mdb_env_set_flags(MDB_env *env, unsigned int flag, int onoff)
+{
+#define        CHANGEABLE      (MDB_NOSYNC)
+       if ((flag & CHANGEABLE) != flag)
+               return EINVAL;
+       if (onoff)
+               env->me_flags |= flag;
+       else
+               env->me_flags &= ~flag;
+       return MDB_SUCCESS;
+}
+
+int
+mdb_env_get_flags(MDB_env *env, unsigned int *arg)
 {
        if (!env || !arg)
                return EINVAL;
@@ -3096,7 +3215,7 @@ mdbenv_get_flags(MDB_env *env, unsigned int *arg)
 }
 
 int
-mdbenv_get_path(MDB_env *env, const char **arg)
+mdb_env_get_path(MDB_env *env, const char **arg)
 {
        if (!env || !arg)
                return EINVAL;
@@ -3118,7 +3237,7 @@ mdb_stat0(MDB_env *env, MDB_db *db, MDB_stat *arg)
        return MDB_SUCCESS;
 }
 int
-mdbenv_stat(MDB_env *env, MDB_stat *arg)
+mdb_env_stat(MDB_env *env, MDB_stat *arg)
 {
        if (env == NULL || arg == NULL)
                return EINVAL;
@@ -3182,6 +3301,8 @@ int mdb_open(MDB_txn *txn, const char *name, unsigned int flags, MDB_dbi *dbi)
                txn->mt_dbxs[txn->mt_numdbs].md_dirty = dirty;
                memcpy(&txn->mt_dbs[txn->mt_numdbs], data.mv_data, sizeof(MDB_db));
                *dbi = txn->mt_numdbs;
+               txn->mt_env->me_dbs[0][txn->mt_numdbs] = txn->mt_dbs[txn->mt_numdbs];
+               txn->mt_env->me_dbs[1][txn->mt_numdbs] = txn->mt_dbs[txn->mt_numdbs];
                txn->mt_numdbs++;
        }