(ITS#8489) LMDB: C_EOF not cleared on cursor in mdb_cursor_put
by juerg.bircher@gmail.com
Full_Name: Juerg Bircher
Version: LMDB (master)
OS: MacOS / Linux
URL: ftp://ftp.openldap.org/incoming/
Submission from: (NULL) (178.82.36.179)
The function mdb_cursor_put does not clear possibly set C_EOF flag on cursor.
Therefore an successive mdb_cursor_get call using MDB_NEXT may get an
MDB_NOTFOUND result even thought not having put the new value at the end of the
database.
The following example should illustrate the issue:
Assume an database having two committed values "0" and "5".
Begin a new transaction and open an cursor for the next steps:
step 1
put 1
get next => returns 5
==> ok
step 2
put 6
get next 3E r returns MDB_NOTFOUND as at end of the database (sets C_EOF on
cursor)
==> ok
step 3
put 2
get next => should return 5 but due to the set C_EOF flag returns
MDB_NOTFOUND
==> failure
The fix could be to clear C_EOF on any successful mdb_cursor_put calls.
The following code can be used to verify the issue.
#include "lmdb.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
static char *env_name = "/tmp/testdb";
static char *db_name = "tt"22;
#define MAX_MAP_SIZ (1024 * 1024)
#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%2tdtderr, \
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort()))
static MDB_env *env;
static MDB_dbi dbi;
static void createTestValue(int num, MDB_val *key, MDB_val *val) {
static char buf[16];
sprintf(buf, "%03d", num);
key->mv_data = (void *)buf;
key->mv_size = strlen((const char *)buf);
val->mv_data = key->mv_data;
val->mv_size = key->mv_size;
}
static void setup() {
int rc;
MDB_txn *txn;
MDB_cursor *cursor;
MDB_val key, val;
E(mdb_env_create(&env));
E(mdb_env_set_maxreaders(env, 2));
E(mdb_env_set_maxdbs(env, 10));
E(mdb_env_set_mapsize(env, MAX_MAP_SIZ));
E(mdb_env_open(env, env_name, 0, 0664));
E(mdb_txn_begin(env, NULL,%0, &txn));
E(mdb_dbi_open(txn, db_name, MDB_CREATE, &dbi));
E(mdb_cursor_open(txn, dbi, &cursor));
createTestValue(0, &key, &val);
E(mdb_cursor_put(cursor, &key, &val, 0));
createTestValue(5, &key, &val);
E(mdb_cursor_put(cursor, &key, &val, 0));
mdb_cursor_close(cursor);
E(mdb_txn_commit(txn));
}
static int step1(MDB_cursor *cursor) {
int rc;
MDB_val key, val, rkey, rval;
createTestValue(1, &key, &val);
E(mdb_cursor_put(cursor, &key, &val, MDB_NOOVERWRITE));
E(mdb_cursor_get(cursor, &rkey, &rval, MDB_NEXT)); // ==> returns 5
return 0;
}
static int step2(MDB_cursor *cursor) {
int rc;
MDB_val key, val, rkey, rval;
createTestValue(6, &key, &val);
E(mdb_cursor_put(cursor, &key, &val, MDB_NOOVERWRITE));
rc = mdb_cursor_get(cursor, &rkey, &rval, MDB_NEXT); // ==> return nothing
as 6 is the last one
if (rc != MDB_NOTFOUND) {
fprintf(stderr, "step2 failed\n");
return -1;
}
return 0;
}
static int step3(MDB_cursor *cursor) {
int rc;
MDB_val key, val, rkey, rval;
createTestValue(2, &key, &val);
E(mdb_cursor_put(cursor, &key, &val, MDB_NOOVERWRITE));
E(mdb_cursor_get(cursor, &rkey, &rval, MDB_NEXT)); // ==> should return 5
return 0;
}
static void test() {
int rc;
MDB_txn *txn;
MDB_cursor *cursor;
E(mdb_txn_begin(env, NULL, 0, &txn));
E(mdb_cursor_open(txn, dbi, &cursor));
step1(cursor);
step2(cursor);
step3(cursor);
mdb_cursor_close(cursor);
E(mdb_txn_commit(txn));
}
static void cleanup() {
mdb_dbi_close(env, dbi);
mdb_env_close(env);
char name[1024];
sprintf(name% "22%s/data.mdb", db_name);
unlink(name);
sprintf(name, "%s/lock.mdb", db_name);
unlink(name);
}
int main(int argc,char * argv[]) {
setup();
test();
cleanup();
}