]> git.ipfire.org Git - thirdparty/openldap.git/commitdiff
Add test code for remap / encryption
authorHoward Chu <hyc@openldap.org>
Thu, 29 Jun 2017 17:22:34 +0000 (18:22 +0100)
committerHoward Chu <hyc@openldap.org>
Sat, 10 Oct 2020 11:52:10 +0000 (12:52 +0100)
libraries/liblmdb/Makefile
libraries/liblmdb/chacha8.c [new file with mode: 0644]
libraries/liblmdb/chacha8.h [new file with mode: 0644]
libraries/liblmdb/mdb.c
libraries/liblmdb/mtest_enc.c [new file with mode: 0644]
libraries/liblmdb/mtest_remap.c [new file with mode: 0644]

index 72d0984607c15ec79d77448ba6f496eb86d50864..2ff32345ced998b5c487eb0aba27a5c56ec68260 100644 (file)
@@ -42,7 +42,11 @@ ILIBS        = liblmdb.a liblmdb$(SOEXT)
 IPROGS = mdb_stat mdb_copy mdb_dump mdb_load
 IDOCS  = mdb_stat.1 mdb_copy.1 mdb_dump.1 mdb_load.1
 PROGS  = $(IPROGS) mtest mtest2 mtest3 mtest4 mtest5
+RPROGS = mtest_remap mtest_enc
+
 all:   $(ILIBS) $(PROGS)
+# Requires CPPFLAGS=-DMDB_VL32 and/or -DMDB_RPAGE_CACHE
+rall:  all $(RPROGS)
 
 install: $(ILIBS) $(IPROGS) $(IHDRS)
        mkdir -p $(DESTDIR)$(bindir)
@@ -55,7 +59,7 @@ install: $(ILIBS) $(IPROGS) $(IHDRS)
        for f in $(IDOCS); do cp $$f $(DESTDIR)$(mandir)/man1; done
 
 clean:
-       rm -rf $(PROGS) *.[ao] *.[ls]o *~ testdb
+       rm -rf $(PROGS) $(RPROGS) *.[ao] *.[ls]o *~ testdb
 
 test:  all
        rm -rf testdb && mkdir testdb
@@ -78,6 +82,8 @@ mtest3:       mtest3.o liblmdb.a
 mtest4:        mtest4.o liblmdb.a
 mtest5:        mtest5.o liblmdb.a
 mtest6:        mtest6.o liblmdb.a
+mtest_remap:  mtest_remap.o liblmdb.a
+mtest_enc:    mtest_enc.o chacha8.o liblmdb.a
 
 mdb.o: mdb.c lmdb.h midl.h
        $(CC) $(CFLAGS) $(CPPFLAGS) -c mdb.c
diff --git a/libraries/liblmdb/chacha8.c b/libraries/liblmdb/chacha8.c
new file mode 100644 (file)
index 0000000..87201d0
--- /dev/null
@@ -0,0 +1,183 @@
+/*
+chacha-merged.c version 20080118
+D. J. Bernstein
+Public domain.
+*/
+
+#include <memory.h>
+#include <stdio.h>
+#include <sys/param.h>
+
+#include "chacha8.h"
+#if 0
+#include "common/int-util.h"
+#include "warnings.h"
+#endif
+
+#if BYTE_ORDER == LITTLE_ENDIAN
+#define SWAP32LE(x)    (x)
+#else
+#define SWAP32LE(x)  ((((uint32_t) (x) & 0x000000ff) << 24) | \
+  (((uint32_t) (x) & 0x0000ff00) <<  8) | \
+  (((uint32_t) (x) & 0x00ff0000) >>  8) | \
+  (((uint32_t) (x) & 0xff000000) >> 24))
+#endif
+
+/*
+ * The following macros are used to obtain exact-width results.
+ */
+#define U8V(v) ((uint8_t)(v) & UINT8_C(0xFF))
+#define U32V(v) ((uint32_t)(v) & UINT32_C(0xFFFFFFFF))
+
+/*
+ * The following macros load words from an array of bytes with
+ * different types of endianness, and vice versa.
+ */
+#define U8TO32_LITTLE(p) SWAP32LE(((uint32_t*)(p))[0])
+#define U32TO8_LITTLE(p, v) (((uint32_t*)(p))[0] = SWAP32LE(v))
+
+#define ROTATE(v,c) (rol32(v,c))
+#define XOR(v,w) ((v) ^ (w))
+#define PLUS(v,w) (U32V((v) + (w)))
+#define PLUSONE(v) (PLUS((v),1))
+
+#define QUARTERROUND(a,b,c,d) \
+  a = PLUS(a,b); d = ROTATE(XOR(d,a),16); \
+  c = PLUS(c,d); b = ROTATE(XOR(b,c),12); \
+  a = PLUS(a,b); d = ROTATE(XOR(d,a), 8); \
+  c = PLUS(c,d); b = ROTATE(XOR(b,c), 7);
+
+static const char sigma[] = "expand 32-byte k";
+
+static uint32_t rol32(uint32_t x, int r) {
+       return (x << (r & 31)) | (x >> (-r & 31));
+}
+
+void chacha8(const void* data, size_t length, const uint8_t* key, const uint8_t* iv, char* cipher) {
+  uint32_t x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15;
+  uint32_t j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15;
+  char* ctarget = 0;
+  char tmp[64];
+  int i;
+
+  if (!length) return;
+
+  j0  = U8TO32_LITTLE(sigma + 0);
+  j1  = U8TO32_LITTLE(sigma + 4);
+  j2  = U8TO32_LITTLE(sigma + 8);
+  j3  = U8TO32_LITTLE(sigma + 12);
+  j4  = U8TO32_LITTLE(key + 0);
+  j5  = U8TO32_LITTLE(key + 4);
+  j6  = U8TO32_LITTLE(key + 8);
+  j7  = U8TO32_LITTLE(key + 12);
+  j8  = U8TO32_LITTLE(key + 16);
+  j9  = U8TO32_LITTLE(key + 20);
+  j10 = U8TO32_LITTLE(key + 24);
+  j11 = U8TO32_LITTLE(key + 28);
+  j12 = 0;
+  j13 = 0;
+  j14 = U8TO32_LITTLE(iv + 0);
+  j15 = U8TO32_LITTLE(iv + 4);
+
+  for (;;) {
+    if (length < 64) {
+      memcpy(tmp, data, length);
+      data = tmp;
+      ctarget = cipher;
+      cipher = tmp;
+    }
+    x0  = j0;
+    x1  = j1;
+    x2  = j2;
+    x3  = j3;
+    x4  = j4;
+    x5  = j5;
+    x6  = j6;
+    x7  = j7;
+    x8  = j8;
+    x9  = j9;
+    x10 = j10;
+    x11 = j11;
+    x12 = j12;
+    x13 = j13;
+    x14 = j14;
+    x15 = j15;
+    for (i = 8;i > 0;i -= 2) {
+      QUARTERROUND( x0, x4, x8,x12)
+      QUARTERROUND( x1, x5, x9,x13)
+      QUARTERROUND( x2, x6,x10,x14)
+      QUARTERROUND( x3, x7,x11,x15)
+      QUARTERROUND( x0, x5,x10,x15)
+      QUARTERROUND( x1, x6,x11,x12)
+      QUARTERROUND( x2, x7, x8,x13)
+      QUARTERROUND( x3, x4, x9,x14)
+    }
+    x0  = PLUS( x0, j0);
+    x1  = PLUS( x1, j1);
+    x2  = PLUS( x2, j2);
+    x3  = PLUS( x3, j3);
+    x4  = PLUS( x4, j4);
+    x5  = PLUS( x5, j5);
+    x6  = PLUS( x6, j6);
+    x7  = PLUS( x7, j7);
+    x8  = PLUS( x8, j8);
+    x9  = PLUS( x9, j9);
+    x10 = PLUS(x10,j10);
+    x11 = PLUS(x11,j11);
+    x12 = PLUS(x12,j12);
+    x13 = PLUS(x13,j13);
+    x14 = PLUS(x14,j14);
+    x15 = PLUS(x15,j15);
+
+    x0  = XOR( x0,U8TO32_LITTLE((uint8_t*)data +  0));
+    x1  = XOR( x1,U8TO32_LITTLE((uint8_t*)data +  4));
+    x2  = XOR( x2,U8TO32_LITTLE((uint8_t*)data +  8));
+    x3  = XOR( x3,U8TO32_LITTLE((uint8_t*)data + 12));
+    x4  = XOR( x4,U8TO32_LITTLE((uint8_t*)data + 16));
+    x5  = XOR( x5,U8TO32_LITTLE((uint8_t*)data + 20));
+    x6  = XOR( x6,U8TO32_LITTLE((uint8_t*)data + 24));
+    x7  = XOR( x7,U8TO32_LITTLE((uint8_t*)data + 28));
+    x8  = XOR( x8,U8TO32_LITTLE((uint8_t*)data + 32));
+    x9  = XOR( x9,U8TO32_LITTLE((uint8_t*)data + 36));
+    x10 = XOR(x10,U8TO32_LITTLE((uint8_t*)data + 40));
+    x11 = XOR(x11,U8TO32_LITTLE((uint8_t*)data + 44));
+    x12 = XOR(x12,U8TO32_LITTLE((uint8_t*)data + 48));
+    x13 = XOR(x13,U8TO32_LITTLE((uint8_t*)data + 52));
+    x14 = XOR(x14,U8TO32_LITTLE((uint8_t*)data + 56));
+    x15 = XOR(x15,U8TO32_LITTLE((uint8_t*)data + 60));
+
+    j12 = PLUSONE(j12);
+    if (!j12)
+    {
+      j13 = PLUSONE(j13);
+      /* stopping at 2^70 bytes per iv is user's responsibility */
+    }
+
+    U32TO8_LITTLE(cipher +  0,x0);
+    U32TO8_LITTLE(cipher +  4,x1);
+    U32TO8_LITTLE(cipher +  8,x2);
+    U32TO8_LITTLE(cipher + 12,x3);
+    U32TO8_LITTLE(cipher + 16,x4);
+    U32TO8_LITTLE(cipher + 20,x5);
+    U32TO8_LITTLE(cipher + 24,x6);
+    U32TO8_LITTLE(cipher + 28,x7);
+    U32TO8_LITTLE(cipher + 32,x8);
+    U32TO8_LITTLE(cipher + 36,x9);
+    U32TO8_LITTLE(cipher + 40,x10);
+    U32TO8_LITTLE(cipher + 44,x11);
+    U32TO8_LITTLE(cipher + 48,x12);
+    U32TO8_LITTLE(cipher + 52,x13);
+    U32TO8_LITTLE(cipher + 56,x14);
+    U32TO8_LITTLE(cipher + 60,x15);
+
+    if (length <= 64) {
+      if (length < 64) {
+        memcpy(ctarget, cipher, length);
+      }
+      return;
+    }
+    length -= 64;
+    cipher += 64;
+    data = (uint8_t*)data + 64;
+  }
+}
diff --git a/libraries/liblmdb/chacha8.h b/libraries/liblmdb/chacha8.h
new file mode 100644 (file)
index 0000000..fed3f7b
--- /dev/null
@@ -0,0 +1,8 @@
+#include <stdint.h>
+#include <stddef.h>
+
+#define CHACHA8_KEY_SIZE 32
+#define CHACHA8_IV_SIZE 8
+
+void chacha8(const void* data, size_t length, const uint8_t* key, const uint8_t* iv, char* cipher);
+
index f2c3557fcc0d3ab4dcab1edca2e71cd4ed027c7c..6c7d8db4569b68a9f74c7b958127703e2123a1ac 100644 (file)
@@ -5464,9 +5464,14 @@ mdb_env_open(MDB_env *env, const char *path, unsigned int flags, mdb_mode_t mode
        if (MDB_REMAPPING(flags)) {
                /* silently ignore WRITEMAP with REMAP_CHUNKS */
                flags &= ~MDB_WRITEMAP;
+#if (MDB_RPAGE_CACHE) & 2
+               /* TEST: silently ignore FIXEDMAP, so mtest*.c will work */
+               flags &= ~MDB_FIXEDMAP;
+#else
                /* cannot support FIXEDMAP */
                if (flags & MDB_FIXEDMAP)
                        return EINVAL;
+#endif
        }
 
        rc = mdb_fname_init(path, flags, &fname);
diff --git a/libraries/liblmdb/mtest_enc.c b/libraries/liblmdb/mtest_enc.c
new file mode 100644 (file)
index 0000000..ecb0875
--- /dev/null
@@ -0,0 +1,192 @@
+/* mtest_enc.c - memory-mapped database tester/toy with encryption */
+/*
+ * Copyright 2011-2017 Howard Chu, Symas Corp.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the Symas
+ * Dual-Use License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * source distribution.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include "lmdb.h"
+#include "chacha8.h"
+
+#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr)
+#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0))
+#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \
+       "%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort()))
+
+static void encfunc(const MDB_val *src, MDB_val *dst, const MDB_val *key, int encdec)
+{
+       chacha8(src->mv_data, src->mv_size, key[0].mv_data, key[1].mv_data, dst->mv_data);
+}
+
+int main(int argc,char * argv[])
+{
+       int i = 0, j = 0, rc;
+       MDB_env *env;
+       MDB_dbi dbi;
+       MDB_val key, data;
+       MDB_txn *txn;
+       MDB_stat mst;
+       MDB_cursor *cursor, *cur2;
+       MDB_cursor_op op;
+       MDB_val enckey[2];
+       int count;
+       int *values;
+       char sval[32] = "";
+       char eiv[] = {3, 1, 4, 1, 5, 9, 2, 6};
+       char ekey[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
+               17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32};
+
+       srand(time(NULL));
+
+           count = (rand()%384) + 64;
+           values = (int *)malloc(count*sizeof(int));
+
+           for(i = 0;i<count;i++) {
+                       values[i] = rand()%1024;
+           }
+    
+               enckey[0].mv_data = ekey;
+               enckey[0].mv_size = sizeof(ekey);
+               enckey[1].mv_data = eiv;
+               enckey[1].mv_size = sizeof(eiv);
+
+               E(mdb_env_create(&env));
+               E(mdb_env_set_maxreaders(env, 1));
+               E(mdb_env_set_mapsize(env, 10485760));
+               E(mdb_env_set_encrypt(env, encfunc, enckey));
+               E(mdb_env_open(env, "./testdb", 0 /*|MDB_NOSYNC*/, 0664));
+
+               E(mdb_txn_begin(env, NULL, 0, &txn));
+               E(mdb_dbi_open(txn, NULL, 0, &dbi));
+   
+               key.mv_size = sizeof(int);
+               key.mv_data = sval;
+
+               printf("Adding %d values\n", count);
+           for (i=0;i<count;i++) {     
+                       sprintf(sval, "%03x %d foo bar", values[i], values[i]);
+                       /* Set <data> in each iteration, since MDB_NOOVERWRITE may modify it */
+                       data.mv_size = sizeof(sval);
+                       data.mv_data = sval;
+                       if (RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NOOVERWRITE))) {
+                               j++;
+                               data.mv_size = sizeof(sval);
+                               data.mv_data = sval;
+                       }
+           }
+               if (j) printf("%d duplicates skipped\n", j);
+               E(mdb_txn_commit(txn));
+               E(mdb_env_stat(env, &mst));
+
+               E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
+               E(mdb_cursor_open(txn, dbi, &cursor));
+               while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
+                       printf("key: %p %.*s, data: %p %.*s\n",
+                               key.mv_data,  (int) key.mv_size,  (char *) key.mv_data,
+                               data.mv_data, (int) data.mv_size, (char *) data.mv_data);
+               }
+               CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
+               mdb_cursor_close(cursor);
+               mdb_txn_abort(txn);
+
+               j=0;
+               key.mv_data = sval;
+           for (i= count - 1; i > -1; i-= (rand()%5)) {
+                       j++;
+                       txn=NULL;
+                       E(mdb_txn_begin(env, NULL, 0, &txn));
+                       sprintf(sval, "%03x ", values[i]);
+                       if (RES(MDB_NOTFOUND, mdb_del(txn, dbi, &key, NULL))) {
+                               j--;
+                               mdb_txn_abort(txn);
+                       } else {
+                               E(mdb_txn_commit(txn));
+                       }
+           }
+           free(values);
+               printf("Deleted %d values\n", j);
+
+               E(mdb_env_stat(env, &mst));
+               E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
+               E(mdb_cursor_open(txn, dbi, &cursor));
+               printf("Cursor next\n");
+               while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
+                       printf("key: %.*s, data: %.*s\n",
+                               (int) key.mv_size,  (char *) key.mv_data,
+                               (int) data.mv_size, (char *) data.mv_data);
+               }
+               CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
+               printf("Cursor last\n");
+               E(mdb_cursor_get(cursor, &key, &data, MDB_LAST));
+               printf("key: %.*s, data: %.*s\n",
+                       (int) key.mv_size,  (char *) key.mv_data,
+                       (int) data.mv_size, (char *) data.mv_data);
+               printf("Cursor prev\n");
+               while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV)) == 0) {
+                       printf("key: %.*s, data: %.*s\n",
+                               (int) key.mv_size,  (char *) key.mv_data,
+                               (int) data.mv_size, (char *) data.mv_data);
+               }
+               CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
+               printf("Cursor last/prev\n");
+               E(mdb_cursor_get(cursor, &key, &data, MDB_LAST));
+                       printf("key: %.*s, data: %.*s\n",
+                               (int) key.mv_size,  (char *) key.mv_data,
+                               (int) data.mv_size, (char *) data.mv_data);
+               E(mdb_cursor_get(cursor, &key, &data, MDB_PREV));
+                       printf("key: %.*s, data: %.*s\n",
+                               (int) key.mv_size,  (char *) key.mv_data,
+                               (int) data.mv_size, (char *) data.mv_data);
+
+               mdb_cursor_close(cursor);
+               mdb_txn_abort(txn);
+
+               printf("Deleting with cursor\n");
+               E(mdb_txn_begin(env, NULL, 0, &txn));
+               E(mdb_cursor_open(txn, dbi, &cur2));
+               for (i=0; i<50; i++) {
+                       if (RES(MDB_NOTFOUND, mdb_cursor_get(cur2, &key, &data, MDB_NEXT)))
+                               break;
+                       printf("key: %p %.*s, data: %p %.*s\n",
+                               key.mv_data,  (int) key.mv_size,  (char *) key.mv_data,
+                               data.mv_data, (int) data.mv_size, (char *) data.mv_data);
+                       E(mdb_del(txn, dbi, &key, NULL));
+               }
+
+               printf("Restarting cursor in txn\n");
+               for (op=MDB_FIRST, i=0; i<=32; op=MDB_NEXT, i++) {
+                       if (RES(MDB_NOTFOUND, mdb_cursor_get(cur2, &key, &data, op)))
+                               break;
+                       printf("key: %p %.*s, data: %p %.*s\n",
+                               key.mv_data,  (int) key.mv_size,  (char *) key.mv_data,
+                               data.mv_data, (int) data.mv_size, (char *) data.mv_data);
+               }
+               mdb_cursor_close(cur2);
+               E(mdb_txn_commit(txn));
+
+               printf("Restarting cursor outside txn\n");
+               E(mdb_txn_begin(env, NULL, 0, &txn));
+               E(mdb_cursor_open(txn, dbi, &cursor));
+               for (op=MDB_FIRST, i=0; i<=32; op=MDB_NEXT, i++) {
+                       if (RES(MDB_NOTFOUND, mdb_cursor_get(cursor, &key, &data, op)))
+                               break;
+                       printf("key: %p %.*s, data: %p %.*s\n",
+                               key.mv_data,  (int) key.mv_size,  (char *) key.mv_data,
+                               data.mv_data, (int) data.mv_size, (char *) data.mv_data);
+               }
+               mdb_cursor_close(cursor);
+               mdb_txn_abort(txn);
+
+               mdb_dbi_close(env, dbi);
+               mdb_env_close(env);
+
+       return 0;
+}
diff --git a/libraries/liblmdb/mtest_remap.c b/libraries/liblmdb/mtest_remap.c
new file mode 100644 (file)
index 0000000..2e2b12c
--- /dev/null
@@ -0,0 +1,177 @@
+/* mtest_remap.c - memory-mapped database tester/toy with page remapping */
+/*
+ * Copyright 2011-2017 Howard Chu, Symas Corp.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the Symas
+ * Dual-Use License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * source distribution.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include "lmdb.h"
+#include "chacha8.h"
+
+#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr)
+#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0))
+#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \
+       "%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort()))
+
+int main(int argc,char * argv[])
+{
+       int i = 0, j = 0, rc;
+       MDB_env *env;
+       MDB_dbi dbi;
+       MDB_val key, data;
+       MDB_txn *txn;
+       MDB_stat mst;
+       MDB_cursor *cursor, *cur2;
+       MDB_cursor_op op;
+       int count;
+       int *values;
+       char sval[32] = "";
+
+       srand(time(NULL));
+
+           count = (rand()%384) + 64;
+           values = (int *)malloc(count*sizeof(int));
+
+           for(i = 0;i<count;i++) {
+                       values[i] = rand()%1024;
+           }
+    
+               E(mdb_env_create(&env));
+               E(mdb_env_set_maxreaders(env, 1));
+               E(mdb_env_set_mapsize(env, 10485760));
+               E(mdb_env_open(env, "./testdb", MDB_REMAP_CHUNKS /*|MDB_NOSYNC*/, 0664));
+
+               E(mdb_txn_begin(env, NULL, 0, &txn));
+               E(mdb_dbi_open(txn, NULL, 0, &dbi));
+   
+               key.mv_size = sizeof(int);
+               key.mv_data = sval;
+
+               printf("Adding %d values\n", count);
+           for (i=0;i<count;i++) {     
+                       sprintf(sval, "%03x %d foo bar", values[i], values[i]);
+                       /* Set <data> in each iteration, since MDB_NOOVERWRITE may modify it */
+                       data.mv_size = sizeof(sval);
+                       data.mv_data = sval;
+                       if (RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NOOVERWRITE))) {
+                               j++;
+                               data.mv_size = sizeof(sval);
+                               data.mv_data = sval;
+                       }
+           }
+               if (j) printf("%d duplicates skipped\n", j);
+               E(mdb_txn_commit(txn));
+               E(mdb_env_stat(env, &mst));
+
+               E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
+               E(mdb_cursor_open(txn, dbi, &cursor));
+               while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
+                       printf("key: %p %.*s, data: %p %.*s\n",
+                               key.mv_data,  (int) key.mv_size,  (char *) key.mv_data,
+                               data.mv_data, (int) data.mv_size, (char *) data.mv_data);
+               }
+               CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
+               mdb_cursor_close(cursor);
+               mdb_txn_abort(txn);
+
+               j=0;
+               key.mv_data = sval;
+           for (i= count - 1; i > -1; i-= (rand()%5)) {
+                       j++;
+                       txn=NULL;
+                       E(mdb_txn_begin(env, NULL, 0, &txn));
+                       sprintf(sval, "%03x ", values[i]);
+                       if (RES(MDB_NOTFOUND, mdb_del(txn, dbi, &key, NULL))) {
+                               j--;
+                               mdb_txn_abort(txn);
+                       } else {
+                               E(mdb_txn_commit(txn));
+                       }
+           }
+           free(values);
+               printf("Deleted %d values\n", j);
+
+               E(mdb_env_stat(env, &mst));
+               E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
+               E(mdb_cursor_open(txn, dbi, &cursor));
+               printf("Cursor next\n");
+               while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
+                       printf("key: %.*s, data: %.*s\n",
+                               (int) key.mv_size,  (char *) key.mv_data,
+                               (int) data.mv_size, (char *) data.mv_data);
+               }
+               CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
+               printf("Cursor last\n");
+               E(mdb_cursor_get(cursor, &key, &data, MDB_LAST));
+               printf("key: %.*s, data: %.*s\n",
+                       (int) key.mv_size,  (char *) key.mv_data,
+                       (int) data.mv_size, (char *) data.mv_data);
+               printf("Cursor prev\n");
+               while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV)) == 0) {
+                       printf("key: %.*s, data: %.*s\n",
+                               (int) key.mv_size,  (char *) key.mv_data,
+                               (int) data.mv_size, (char *) data.mv_data);
+               }
+               CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
+               printf("Cursor last/prev\n");
+               E(mdb_cursor_get(cursor, &key, &data, MDB_LAST));
+                       printf("key: %.*s, data: %.*s\n",
+                               (int) key.mv_size,  (char *) key.mv_data,
+                               (int) data.mv_size, (char *) data.mv_data);
+               E(mdb_cursor_get(cursor, &key, &data, MDB_PREV));
+                       printf("key: %.*s, data: %.*s\n",
+                               (int) key.mv_size,  (char *) key.mv_data,
+                               (int) data.mv_size, (char *) data.mv_data);
+
+               mdb_cursor_close(cursor);
+               mdb_txn_abort(txn);
+
+               printf("Deleting with cursor\n");
+               E(mdb_txn_begin(env, NULL, 0, &txn));
+               E(mdb_cursor_open(txn, dbi, &cur2));
+               for (i=0; i<50; i++) {
+                       if (RES(MDB_NOTFOUND, mdb_cursor_get(cur2, &key, &data, MDB_NEXT)))
+                               break;
+                       printf("key: %p %.*s, data: %p %.*s\n",
+                               key.mv_data,  (int) key.mv_size,  (char *) key.mv_data,
+                               data.mv_data, (int) data.mv_size, (char *) data.mv_data);
+                       E(mdb_del(txn, dbi, &key, NULL));
+               }
+
+               printf("Restarting cursor in txn\n");
+               for (op=MDB_FIRST, i=0; i<=32; op=MDB_NEXT, i++) {
+                       if (RES(MDB_NOTFOUND, mdb_cursor_get(cur2, &key, &data, op)))
+                               break;
+                       printf("key: %p %.*s, data: %p %.*s\n",
+                               key.mv_data,  (int) key.mv_size,  (char *) key.mv_data,
+                               data.mv_data, (int) data.mv_size, (char *) data.mv_data);
+               }
+               mdb_cursor_close(cur2);
+               E(mdb_txn_commit(txn));
+
+               printf("Restarting cursor outside txn\n");
+               E(mdb_txn_begin(env, NULL, 0, &txn));
+               E(mdb_cursor_open(txn, dbi, &cursor));
+               for (op=MDB_FIRST, i=0; i<=32; op=MDB_NEXT, i++) {
+                       if (RES(MDB_NOTFOUND, mdb_cursor_get(cursor, &key, &data, op)))
+                               break;
+                       printf("key: %p %.*s, data: %p %.*s\n",
+                               key.mv_data,  (int) key.mv_size,  (char *) key.mv_data,
+                               data.mv_data, (int) data.mv_size, (char *) data.mv_data);
+               }
+               mdb_cursor_close(cursor);
+               mdb_txn_abort(txn);
+
+               mdb_dbi_close(env, dbi);
+               mdb_env_close(env);
+
+       return 0;
+}