diff --git a/CHANGES.lmdb.txt b/CHANGES.lmdb.txt index 76dd591..20babf3 100644 --- a/CHANGES.lmdb.txt +++ b/CHANGES.lmdb.txt @@ -1,5 +1,16 @@ LMDB 0.9 Change Log +LMDB 0.9.33 Release (2024/05/21) + ITS#9037 mdb_page_search: fix error code when DBI record is missing + ITS#10198 For win32, stop passing ignored parameter + ITS#10212 Fix meta page usage by read only tools + +LMDB 0.9.32 Release (2024/01/29) + ITS#9378 - Add ability to replay log and replay log tool + ITS#10095 - partial revert of ITS#9278. The patch was incorrect and introduced numerous race conditions. + ITS#10125 - mdb_load: fix cursor reinit in Append mode + ITS#10137 - Allow users to define MDB_IDL_LOGN + LMDB 0.9.31 Release (2023/07/10) ITS#8447 - Fix cursor_put(MDB_CURRENT) on DUPSORT DB with different sized data diff --git a/lmdb/lmdb.h b/lmdb/lmdb.h index ff03c22..199382a 100644 --- a/lmdb/lmdb.h +++ b/lmdb/lmdb.h @@ -200,7 +200,7 @@ typedef int mdb_filehandle_t; /** Library minor version */ #define MDB_VERSION_MINOR 9 /** Library patch version */ -#define MDB_VERSION_PATCH 31 +#define MDB_VERSION_PATCH 33 /** Combine args a,b,c into a single integer for easy version comparisons */ #define MDB_VERINT(a,b,c) (((a) << 24) | ((b) << 16) | (c)) @@ -210,7 +210,7 @@ typedef int mdb_filehandle_t; MDB_VERINT(MDB_VERSION_MAJOR,MDB_VERSION_MINOR,MDB_VERSION_PATCH) /** The release date of this library version */ -#define MDB_VERSION_DATE "July 10, 2023" +#define MDB_VERSION_DATE "May 21, 2024" /** A stringifier for the version info */ #define MDB_VERSTR(a,b,c,d) "LMDB " #a "." #b "." #c ": (" d ")" diff --git a/lmdb/mdb.c b/lmdb/mdb.c index deb6779..668f966 100644 --- a/lmdb/mdb.c +++ b/lmdb/mdb.c @@ -481,18 +481,26 @@ typedef MDB_ID txnid_t; #define MDB_DEBUG 0 #endif +#define MDB_DBG_INFO 1 +#define MDB_DBG_TRACE 2 + #if MDB_DEBUG -static int mdb_debug; +static int mdb_debug = MDB_DBG_TRACE; static txnid_t mdb_debug_start; /** Print a debug message with printf formatting. * Requires double parenthesis around 2 or more args. */ -# define DPRINTF(args) ((void) ((mdb_debug) && DPRINTF0 args)) +# define DPRINTF(args) ((void) ((mdb_debug & MDB_DBG_INFO) && DPRINTF0 args)) # define DPRINTF0(fmt, ...) \ fprintf(stderr, "%s:%d " fmt "\n", mdb_func_, __LINE__, __VA_ARGS__) + /** Trace info for replaying */ +# define MDB_TRACE(args) ((void) ((mdb_debug & MDB_DBG_TRACE) && DPRINTF1 args)) +# define DPRINTF1(fmt, ...) \ + fprintf(stderr, ">%d:%s: " fmt "\n", getpid(), mdb_func_, __VA_ARGS__) #else # define DPRINTF(args) ((void) 0) +# define MDB_TRACE(args) ((void) 0) #endif /** Print a debug string. * The string is printed literally, with no format processing. @@ -589,6 +597,11 @@ static txnid_t mdb_debug_start; * This is used for printing a hex dump of a key's contents. */ #define DKBUF char kbuf[DKBUF_MAXKEYSIZE*2+1] + /** A data value buffer. + * @ingroup debug + * This is used for printing a hex dump of a #MDB_DUPSORT value's contents. + */ +#define DDBUF char dbuf[DKBUF_MAXKEYSIZE*2+1+2] /** Display a key in hex. * @ingroup debug * Invoke a function to display a key in hex. @@ -596,6 +609,7 @@ static txnid_t mdb_debug_start; #define DKEY(x) mdb_dkey(x, kbuf) #else #define DKBUF +#define DDBUF #define DKEY(x) 0 #endif @@ -1423,6 +1437,9 @@ static int mdb_update_key(MDB_cursor *mc, MDB_val *key); static void mdb_cursor_pop(MDB_cursor *mc); static int mdb_cursor_push(MDB_cursor *mc, MDB_page *mp); +static int _mdb_cursor_del(MDB_cursor *mc, unsigned int flags); +static int _mdb_cursor_put(MDB_cursor *mc, MDB_val *key, MDB_val *data, unsigned int flags); + static int mdb_cursor_del0(MDB_cursor *mc); static int mdb_del0(MDB_txn *txn, MDB_dbi dbi, MDB_val *key, MDB_val *data, unsigned flags); static int mdb_cursor_sibling(MDB_cursor *mc, int move_right); @@ -1538,7 +1555,7 @@ mdb_strerror(int err) buf[0] = 0; FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, err, 0, ptr, MSGSIZE, (va_list *)buf+MSGSIZE); + NULL, err, 0, ptr, MSGSIZE, NULL); return ptr; #else if (err < 0) @@ -1614,6 +1631,18 @@ mdb_dkey(MDB_val *key, char *buf) return buf; } +static char * +mdb_dval(MDB_txn *txn, MDB_dbi dbi, MDB_val *data, char *buf) +{ + if (txn->mt_dbs[dbi].md_flags & MDB_DUPSORT) { + mdb_dkey(data, buf+1); + *buf = '['; + strcpy(buf + data->mv_size * 2 + 1, "]"); + } else + *buf = '\0'; + return buf; +} + static const char * mdb_leafnode_type(MDB_node *n) { @@ -2767,9 +2796,14 @@ mdb_txn_renew0(MDB_txn *txn) do /* LY: Retry on a race, ITS#7970. */ r->mr_txnid = ti->mti_txnid; while(r->mr_txnid != ti->mti_txnid); + if (!r->mr_txnid && (env->me_flags & MDB_RDONLY)) { + meta = mdb_env_pick_meta(env); + r->mr_txnid = meta->mm_txnid; + } else { + meta = env->me_metas[r->mr_txnid & 1]; + } txn->mt_txnid = r->mr_txnid; txn->mt_u.reader = r; - meta = env->me_metas[txn->mt_txnid & 1]; } } else { @@ -2786,7 +2820,7 @@ mdb_txn_renew0(MDB_txn *txn) txn->mt_txnid++; #if MDB_DEBUG if (txn->mt_txnid == mdb_debug_start) - mdb_debug = 1; + mdb_debug = MDB_DBG_INFO; #endif txn->mt_child = NULL; txn->mt_loose_pgs = NULL; @@ -2945,6 +2979,7 @@ mdb_txn_begin(MDB_env *env, MDB_txn *parent, unsigned int flags, MDB_txn **ret) txn->mt_txnid, (flags & MDB_RDONLY) ? 'r' : 'w', (void *) txn, (void *) env, txn->mt_dbs[MAIN_DBI].md_root)); } + MDB_TRACE(("%p, %p, %u = %p", env, parent, flags, txn)); return rc; } @@ -3080,18 +3115,25 @@ mdb_txn_reset(MDB_txn *txn) mdb_txn_end(txn, MDB_END_RESET); } -void -mdb_txn_abort(MDB_txn *txn) +static void +_mdb_txn_abort(MDB_txn *txn) { if (txn == NULL) return; if (txn->mt_child) - mdb_txn_abort(txn->mt_child); + _mdb_txn_abort(txn->mt_child); mdb_txn_end(txn, MDB_END_ABORT|MDB_END_SLOT|MDB_END_FREE); } +void +mdb_txn_abort(MDB_txn *txn) +{ + MDB_TRACE(("%p", txn)); + _mdb_txn_abort(txn); +} + /** Save the freelist as of this transaction to the freeDB. * This changes the freelist. Keep trying until it stabilizes. */ @@ -3182,7 +3224,7 @@ mdb_freelist_save(MDB_txn *txn) pglast = head_id = *(txnid_t *)key.mv_data; total_room = head_room = 0; mdb_tassert(txn, pglast <= env->me_pglast); - rc = mdb_cursor_del(&mc, 0); + rc = _mdb_cursor_del(&mc, 0); if (rc) return rc; } @@ -3202,7 +3244,7 @@ mdb_freelist_save(MDB_txn *txn) do { freecnt = free_pgs[0]; data.mv_size = MDB_IDL_SIZEOF(free_pgs); - rc = mdb_cursor_put(&mc, &key, &data, MDB_RESERVE); + rc = _mdb_cursor_put(&mc, &key, &data, MDB_RESERVE); if (rc) return rc; /* Retry if mt_free_pgs[] grew during the Put() */ @@ -3251,7 +3293,7 @@ mdb_freelist_save(MDB_txn *txn) key.mv_size = sizeof(head_id); key.mv_data = &head_id; data.mv_size = (head_room + 1) * sizeof(pgno_t); - rc = mdb_cursor_put(&mc, &key, &data, MDB_RESERVE); + rc = _mdb_cursor_put(&mc, &key, &data, MDB_RESERVE); if (rc) return rc; /* IDL is initially empty, zero out at least the length */ @@ -3306,7 +3348,7 @@ mdb_freelist_save(MDB_txn *txn) data.mv_data = mop -= len; save = mop[0]; mop[0] = len; - rc = mdb_cursor_put(&mc, &key, &data, MDB_CURRENT); + rc = _mdb_cursor_put(&mc, &key, &data, MDB_CURRENT); mop[0] = save; if (rc || !(mop_len -= len)) break; @@ -3467,8 +3509,8 @@ mdb_page_flush(MDB_txn *txn, int keep) return MDB_SUCCESS; } -int -mdb_txn_commit(MDB_txn *txn) +static int +_mdb_txn_commit(MDB_txn *txn) { int rc; unsigned int i, end_mode; @@ -3481,7 +3523,7 @@ mdb_txn_commit(MDB_txn *txn) end_mode = MDB_END_EMPTY_COMMIT|MDB_END_UPDATE|MDB_END_SLOT|MDB_END_FREE; if (txn->mt_child) { - rc = mdb_txn_commit(txn->mt_child); + rc = _mdb_txn_commit(txn->mt_child); if (rc) goto fail; } @@ -3661,7 +3703,7 @@ mdb_txn_commit(MDB_txn *txn) goto fail; } data.mv_data = &txn->mt_dbs[i]; - rc = mdb_cursor_put(&mc, &txn->mt_dbxs[i].md_name, &data, + rc = _mdb_cursor_put(&mc, &txn->mt_dbxs[i].md_name, &data, F_SUBDATA); if (rc) goto fail; @@ -3692,10 +3734,17 @@ mdb_txn_commit(MDB_txn *txn) return MDB_SUCCESS; fail: - mdb_txn_abort(txn); + _mdb_txn_abort(txn); return rc; } +int +mdb_txn_commit(MDB_txn *txn) +{ + MDB_TRACE(("%p", txn)); + return _mdb_txn_commit(txn); +} + /** Read the environment parameters of a DB environment before * mapping it into memory. * @param[in] env the environment handle @@ -3992,6 +4041,7 @@ mdb_env_create(MDB_env **env) GET_PAGESIZE(e->me_os_psize); VGMEMP_CREATE(e,0,0); *env = e; + MDB_TRACE(("%p", e)); return MDB_SUCCESS; } @@ -4115,6 +4165,7 @@ mdb_env_set_mapsize(MDB_env *env, size_t size) env->me_mapsize = size; if (env->me_psize) env->me_maxpg = env->me_mapsize / env->me_psize; + MDB_TRACE(("%p, %"Yu"", env, size)); return MDB_SUCCESS; } @@ -4124,6 +4175,7 @@ mdb_env_set_maxdbs(MDB_env *env, MDB_dbi dbs) if (env->me_map) return EINVAL; env->me_maxdbs = dbs + CORE_DBS; + MDB_TRACE(("%p, %u", env, dbs)); return MDB_SUCCESS; } @@ -4133,6 +4185,7 @@ mdb_env_set_maxreaders(MDB_env *env, unsigned int readers) if (env->me_map || readers < 1) return EINVAL; env->me_maxreaders = readers; + MDB_TRACE(("%p, %u", env, readers)); return MDB_SUCCESS; } @@ -5076,6 +5129,7 @@ mdb_env_open(MDB_env *env, const char *path, unsigned int flags, mdb_mode_t mode } leave: + MDB_TRACE(("%p, %s, %u, %04o", env, path, flags & (CHANGEABLE|CHANGELESS), mode)); if (rc) { mdb_env_close0(env, excl); } @@ -5162,17 +5216,6 @@ mdb_env_close0(MDB_env *env, int excl) sem_unlink(env->me_txns->mti_wmname); } } -#elif defined(MDB_ROBUST_SUPPORTED) - /* If we have the filelock: If we are the - * only remaining user, clean up robust - * mutexes. - */ - if (excl == 0) - mdb_env_excl_lock(env, &excl); - if (excl > 0) { - pthread_mutex_destroy(env->me_txns->mti_rmutex); - pthread_mutex_destroy(env->me_txns->mti_wmutex); - } #endif munmap((void *)env->me_txns, (env->me_maxreaders-1)*sizeof(MDB_reader)+sizeof(MDB_txninfo)); } @@ -5199,6 +5242,7 @@ mdb_env_close(MDB_env *env) if (env == NULL) return; + MDB_TRACE(("%p", env)); VGMEMP_DESTROY(env); while ((dp = env->me_dpages) != NULL) { VGMEMP_DEFINED(&dp->mp_next, sizeof(dp->mp_next)); @@ -5662,7 +5706,7 @@ mdb_page_search(MDB_cursor *mc, MDB_val *key, int flags) MDB_node *leaf = mdb_node_search(&mc2, &mc->mc_dbx->md_name, &exact); if (!exact) - return MDB_NOTFOUND; + return MDB_BAD_DBI; if ((leaf->mn_flags & (F_DUPDATA|F_SUBDATA)) != F_SUBDATA) return MDB_INCOMPATIBLE; /* not a named DB */ rc = mdb_node_read(&mc2, leaf, &data); @@ -6570,8 +6614,8 @@ mdb_cursor_touch(MDB_cursor *mc) /** Do not spill pages to disk if txn is getting full, may fail instead */ #define MDB_NOSPILL 0x8000 -int -mdb_cursor_put(MDB_cursor *mc, MDB_val *key, MDB_val *data, +static int +_mdb_cursor_put(MDB_cursor *mc, MDB_val *key, MDB_val *data, unsigned int flags) { MDB_env *env; @@ -7034,7 +7078,7 @@ mdb_cursor_put(MDB_cursor *mc, MDB_val *key, MDB_val *data, new_dupdata = (int)dkey.mv_size; /* converted, write the original data first */ if (dkey.mv_size) { - rc = mdb_cursor_put(&mc->mc_xcursor->mx_cursor, &dkey, &xdata, xflags); + rc = _mdb_cursor_put(&mc->mc_xcursor->mx_cursor, &dkey, &xdata, xflags); if (rc) goto bad_sub; /* we've done our job */ @@ -7062,7 +7106,7 @@ mdb_cursor_put(MDB_cursor *mc, MDB_val *key, MDB_val *data, ecount = mc->mc_xcursor->mx_db.md_entries; if (flags & MDB_APPENDDUP) xflags |= MDB_APPEND; - rc = mdb_cursor_put(&mc->mc_xcursor->mx_cursor, data, &xdata, xflags); + rc = _mdb_cursor_put(&mc->mc_xcursor->mx_cursor, data, &xdata, xflags); if (flags & F_SUBDATA) { void *db = NODEDATA(leaf); memcpy(db, &mc->mc_xcursor->mx_db, sizeof(MDB_db)); @@ -7103,7 +7147,20 @@ mdb_cursor_put(MDB_cursor *mc, MDB_val *key, MDB_val *data, } int -mdb_cursor_del(MDB_cursor *mc, unsigned int flags) +mdb_cursor_put(MDB_cursor *mc, MDB_val *key, MDB_val *data, + unsigned int flags) +{ + DKBUF; + DDBUF; + int rc = _mdb_cursor_put(mc, key, data, flags); + MDB_TRACE(("%p, %"Z"u[%s], %"Z"u%s, %u", + mc, key ? key->mv_size:0, DKEY(key), data ? data->mv_size:0, + data ? mdb_dval(mc->mc_txn, mc->mc_dbi, data, dbuf):"", flags)); + return rc; +} + +static int +_mdb_cursor_del(MDB_cursor *mc, unsigned int flags) { MDB_node *leaf; MDB_page *mp; @@ -7141,7 +7198,7 @@ mdb_cursor_del(MDB_cursor *mc, unsigned int flags) if (!F_ISSET(leaf->mn_flags, F_SUBDATA)) { mc->mc_xcursor->mx_cursor.mc_pg[0] = NODEDATA(leaf); } - rc = mdb_cursor_del(&mc->mc_xcursor->mx_cursor, MDB_NOSPILL); + rc = _mdb_cursor_del(&mc->mc_xcursor->mx_cursor, MDB_NOSPILL); if (rc) return rc; /* If sub-DB still has entries, we're done */ @@ -7205,6 +7262,14 @@ mdb_cursor_del(MDB_cursor *mc, unsigned int flags) return rc; } +int +mdb_cursor_del(MDB_cursor *mc, unsigned int flags) +{ + MDB_TRACE(("%p, %u", + mc, flags)); + return _mdb_cursor_del(mc, flags); +} + /** Allocate and initialize new pages for a database. * Set #MDB_TXN_ERROR on failure. * @param[in] mc a cursor on the database being added to. @@ -7698,6 +7763,7 @@ mdb_cursor_open(MDB_txn *txn, MDB_dbi dbi, MDB_cursor **ret) return ENOMEM; } + MDB_TRACE(("%p, %u = %p", txn, dbi, mc)); *ret = mc; return MDB_SUCCESS; @@ -7761,6 +7827,7 @@ mdb_cursor_count(MDB_cursor *mc, size_t *countp) void mdb_cursor_close(MDB_cursor *mc) { + MDB_TRACE(("%p", mc)); if (mc && !mc->mc_backup) { /* remove from txn, if tracked */ if ((mc->mc_flags & C_UNTRACK) && mc->mc_txn->mt_cursors) { @@ -8570,6 +8637,8 @@ int mdb_del(MDB_txn *txn, MDB_dbi dbi, MDB_val *key, MDB_val *data) { + DKBUF; + DDBUF; if (!key || !TXN_DBI_EXIST(txn, dbi, DB_USRVALID)) return EINVAL; @@ -8581,6 +8650,9 @@ mdb_del(MDB_txn *txn, MDB_dbi dbi, data = NULL; } + MDB_TRACE(("%p, %u, %"Z"u[%s], %"Z"u%s", + txn, dbi, key ? key->mv_size:0, DKEY(key), data ? data->mv_size:0, + data ? mdb_dval(txn, dbi, data, dbuf):"")); return mdb_del0(txn, dbi, key, data, 0); } @@ -8621,7 +8693,7 @@ mdb_del0(MDB_txn *txn, MDB_dbi dbi, mc.mc_flags |= C_UNTRACK; mc.mc_next = txn->mt_cursors[dbi]; txn->mt_cursors[dbi] = &mc; - rc = mdb_cursor_del(&mc, flags); + rc = _mdb_cursor_del(&mc, flags); txn->mt_cursors[dbi] = mc.mc_next; } return rc; @@ -9063,6 +9135,8 @@ mdb_put(MDB_txn *txn, MDB_dbi dbi, MDB_cursor mc; MDB_xcursor mx; int rc; + DKBUF; + DDBUF; if (!key || !data || !TXN_DBI_EXIST(txn, dbi, DB_USRVALID)) return EINVAL; @@ -9073,10 +9147,12 @@ mdb_put(MDB_txn *txn, MDB_dbi dbi, if (txn->mt_flags & (MDB_TXN_RDONLY|MDB_TXN_BLOCKED)) return (txn->mt_flags & MDB_TXN_RDONLY) ? EACCES : MDB_BAD_TXN; + MDB_TRACE(("%p, %u, %"Z"u[%s], %"Z"u%s, %u", + txn, dbi, key ? key->mv_size:0, DKEY(key), data->mv_size, mdb_dval(txn, dbi, data, dbuf), flags)); mdb_cursor_init(&mc, txn, dbi, &mx); mc.mc_next = txn->mt_cursors[dbi]; txn->mt_cursors[dbi] = &mc; - rc = mdb_cursor_put(&mc, key, data, flags); + rc = _mdb_cursor_put(&mc, key, data, flags); txn->mt_cursors[dbi] = mc.mc_next; return rc; } @@ -9479,7 +9555,7 @@ mdb_env_copyfd1(MDB_env *env, HANDLE fd) my.mc_error = rc; mdb_env_cthr_toggle(&my, 1 | MDB_EOF); rc = THREAD_FINISH(thr); - mdb_txn_abort(txn); + _mdb_txn_abort(txn); done: #ifdef _WIN32 @@ -9591,7 +9667,7 @@ mdb_env_copyfd0(MDB_env *env, HANDLE fd) } leave: - mdb_txn_abort(txn); + _mdb_txn_abort(txn); return rc; } @@ -9806,6 +9882,7 @@ int mdb_dbi_open(MDB_txn *txn, const char *name, unsigned int flags, MDB_dbi *db } } mdb_default_cmp(txn, MAIN_DBI); + MDB_TRACE(("%p, (null), %u = %u", txn, flags, MAIN_DBI)); return MDB_SUCCESS; } @@ -9867,7 +9944,7 @@ int mdb_dbi_open(MDB_txn *txn, const char *name, unsigned int flags, MDB_dbi *db dummy.md_root = P_INVALID; dummy.md_flags = flags & PERSISTENT_FLAGS; WITH_CURSOR_TRACKING(mc, - rc = mdb_cursor_put(&mc, &key, &data, F_SUBDATA)); + rc = _mdb_cursor_put(&mc, &key, &data, F_SUBDATA)); dbflag |= DB_DIRTY; } @@ -9892,6 +9969,7 @@ int mdb_dbi_open(MDB_txn *txn, const char *name, unsigned int flags, MDB_dbi *db if (!unused) { txn->mt_numdbs++; } + MDB_TRACE(("%p, %s, %u = %u", txn, name, flags, slot)); } return rc; @@ -9923,6 +10001,7 @@ void mdb_dbi_close(MDB_env *env, MDB_dbi dbi) ptr = env->me_dbxs[dbi].md_name.mv_data; /* If there was no name, this was already closed */ if (ptr) { + MDB_TRACE(("%p, %u", env, dbi)); env->me_dbxs[dbi].md_name.mv_data = NULL; env->me_dbxs[dbi].md_name.mv_size = 0; env->me_dbflags[dbi] = 0; @@ -10057,6 +10136,7 @@ int mdb_drop(MDB_txn *txn, MDB_dbi dbi, int del) if (rc) return rc; + MDB_TRACE(("%u, %d", dbi, del)); rc = mdb_drop0(mc, mc->mc_db->md_flags & MDB_DUPSORT); /* Invalidate the dropped DB's cursors */ for (m2 = txn->mt_cursors[dbi]; m2; m2 = m2->mc_next) diff --git a/update-lmdb.sh b/update-lmdb.sh index 7ded309..8235c5c 100755 --- a/update-lmdb.sh +++ b/update-lmdb.sh @@ -1,7 +1,7 @@ #!/bin/sh # # Script to update our copy of the LMDB library and headers -# Releases can be found here: https://github.com/LMDB/lmdb/releases +# Releases can be found here: https://git.openldap.org/openldap/openldap/-/tags?sort=updated_desc&search=LMDB_ # function get_define { @@ -20,7 +20,7 @@ version="$1" if [ -z "$version" ]; then echo "USAGE: $0 " - echo "Check https://github.com/LMDB/lmdb/releases for available versions" + echo "Check https://git.openldap.org/openldap/openldap/-/tags?sort=updated_desc&search=LMDB_ for available versions" exit 1 fi @@ -29,10 +29,10 @@ set -ex tmp_dir=$(mktemp -d -t lmdb-update) echo "Temp dir: $tmp_dir" -curl -L "https://github.com/LMDB/lmdb/archive/refs/tags/LMDB_${version}.tar.gz" | tar -C "$tmp_dir" -xvz -cp "$tmp_dir/lmdb-LMDB_${version}/libraries/liblmdb/mdb.c" lmdb/mdb.c -cp "$tmp_dir/lmdb-LMDB_${version}/libraries/liblmdb/lmdb.h" lmdb/lmdb.h -cp "$tmp_dir/lmdb-LMDB_${version}/libraries/liblmdb/CHANGES" CHANGES.lmdb.txt +curl -L "https://git.openldap.org/openldap/openldap/-/archive/LMDB_${version}/openldap-LMDB_${version}.tar.gz" | tar -C "$tmp_dir" -xvz +cp "$tmp_dir/openldap-LMDB_${version}/libraries/liblmdb/mdb.c" lmdb/mdb.c +cp "$tmp_dir/openldap-LMDB_${version}/libraries/liblmdb/lmdb.h" lmdb/lmdb.h +cp "$tmp_dir/openldap-LMDB_${version}/libraries/liblmdb/CHANGES" CHANGES.lmdb.txt if [ ! -z "$tmp_dir" ]; then echo "Removing temp dir: $tmp_dir"