Issue 8489 - LMDB: C_EOF not cleared on cursor in mdb_cursor_put
Summary: LMDB: C_EOF not cleared on cursor in mdb_cursor_put
Status: VERIFIED FIXED
Alias: None
Product: LMDB
Classification: Unclassified
Component: liblmdb (show other issues)
Version: unspecified
Hardware: All All
: --- normal
Target Milestone: ---
Assignee: OpenLDAP project
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2016-08-31 13:10 UTC by juerg.bircher@gmail.com
Modified: 2020-03-12 15:56 UTC (History)
0 users

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this issue.
Description juerg.bircher@gmail.com 2016-08-31 13:10:58 UTC
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();
} 
    
Comment 1 Howard Chu 2016-08-31 23:44:19 UTC
juerg.bircher@gmail.com wrote:
> 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:

Thanks for the report, fixed now in mdb.master


-- 
   -- Howard Chu
   CTO, Symas Corp.           http://www.symas.com
   Director, Highland Sun     http://highlandsun.com/hyc/
   Chief Architect, OpenLDAP  http://www.openldap.org/project/

Comment 2 Howard Chu 2016-08-31 23:47:54 UTC
changed notes
changed state Open to Test
moved from Incoming to Software Bugs
Comment 3 OpenLDAP project 2018-02-09 18:58:25 UTC
fixed in mdb.master
fixed in 0.9.19
Comment 4 Quanah Gibson-Mount 2018-02-09 18:58:25 UTC
changed notes
changed state Test to Closed