From 6498e5a4e399a94ff2fdb56e2971a424c9a2e62b Mon Sep 17 00:00:00 2001 From: younes Date: Sun, 30 Sep 2012 19:42:44 +0200 Subject: [PATCH] initial release --- 3rdLibs | 8 + ChangeLog | 3 + LICENSE | 49 + Makefile | 24 + README.md | 97 +- c_src/emdb_drv.c | 469 ++++ c_src/mdb.c | 6763 ++++++++++++++++++++++++++++++++++++++++++++++ c_src/mdb.h | 1065 ++++++++ c_src/midl.c | 327 +++ c_src/midl.h | 195 ++ c_src/uthash.h | 915 +++++++ rebar | Bin 0 -> 119018 bytes rebar.config | 17 + src/emdb.app.src | 11 + src/emdb.erl | 73 + src/emdb_drv.erl | 137 + src/emdb_oop.erl | 97 + start.sh | 4 + 18 files changed, 10251 insertions(+), 3 deletions(-) create mode 100644 3rdLibs create mode 100644 ChangeLog create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 c_src/emdb_drv.c create mode 100644 c_src/mdb.c create mode 100644 c_src/mdb.h create mode 100644 c_src/midl.c create mode 100644 c_src/midl.h create mode 100644 c_src/uthash.h create mode 100755 rebar create mode 100644 rebar.config create mode 100644 src/emdb.app.src create mode 100644 src/emdb.erl create mode 100644 src/emdb_drv.erl create mode 100644 src/emdb_oop.erl create mode 100755 start.sh diff --git a/3rdLibs b/3rdLibs new file mode 100644 index 0000000..c27a5df --- /dev/null +++ b/3rdLibs @@ -0,0 +1,8 @@ +o MDB: http://highlandsun.com/hyc/mdb/ + source : from OpenLDAP git repository + commit : 7333b6bdc90c49c332d4728c62c7904e64c95077 + license: OpenLDAP Public License + +o UTHash: http://uthash.sourceforge.net/ + version: 1.9.6 + license: BSD revised diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..b09392f --- /dev/null +++ b/ChangeLog @@ -0,0 +1,3 @@ +o Sep 29th, 2012 + initial release + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9060db6 --- /dev/null +++ b/LICENSE @@ -0,0 +1,49 @@ +------------------------------------------------------------------------- +The OpenLDAP Public License + Version 2.8, 17 August 2003 + +Redistribution and use of this software and associated documentation +("Software"), with or without modification, are permitted provided +that the following conditions are met: + +1. Redistributions in source form must retain copyright statements + and notices, + +2. Redistributions in binary form must reproduce applicable copyright + statements and notices, this list of conditions, and the following + disclaimer in the documentation and/or other materials provided + with the distribution, and + +3. Redistributions must contain a verbatim copy of this document. + +The OpenLDAP Foundation may revise this license from time to time. +Each revision is distinguished by a version number. You may use +this Software under terms of this license revision or under the +terms of any subsequent revision of the license. + +THIS SOFTWARE IS PROVIDED BY THE OPENLDAP FOUNDATION AND ITS +CONTRIBUTORS ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +SHALL THE OPENLDAP FOUNDATION, ITS CONTRIBUTORS, OR THE AUTHOR(S) +OR OWNER(S) OF THE SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +The names of the authors and copyright holders must not be used in +advertising or otherwise to promote the sale, use or other dealing +in this Software without specific, written prior permission. Title +to copyright in this Software shall at all times remain with copyright +holders. + +OpenLDAP is a registered trademark of the OpenLDAP Foundation. + +Copyright 1999-2003 The OpenLDAP Foundation, Redwood City, +California, USA. All Rights Reserved. Permission to copy and +distribute verbatim copies of this document is granted. +------------------------------------------------------------------------- diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..db712e7 --- /dev/null +++ b/Makefile @@ -0,0 +1,24 @@ +# See LICENSE for licensing information. + +MODULE = emdb + +DIALYZER = dialyzer +REBAR = rebar + +.PHONY: build clean + +all: ebin priv build + +ebin: + @mkdir -p $@ + +priv: + @mkdir -p $@ + +build: + @$(REBAR) compile + +clean: + @$(REBAR) clean + @rm -f *~ */*~ erl_crash.dump + @rm -rf ebin priv diff --git a/README.md b/README.md index f68134e..c774e6f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,95 @@ -emdb -==== +EMDB ==== EMDB is a NIF library for the [Memory-Mapped Database](http://highlandsun.com/hyc/mdb/) database, aka. MDB. The main purpose of this package is to provide a **very fast** Riak [backend](http://wiki.basho.com/Storage-Backends.html). + +But this module could also be used as a general key-value store to replace: + +* [DETS](http://www.erlang.org/doc/man/dets.html) +* TokyoCabinet: [TCERL](http://code.google.com/p/tcerl/) +* [QDBM](http://fallabs.com/qdbm/) +* [Bitcask](https://github.com/basho/bitcask) +* [eLevelDB](https://github.com/basho/eleveldb) +* [BerkleyDB](http://www.oracle.com/technetwork/products/berkeleydb/overview/index.html) +* ... Requirements ------------ +* Erlang R14B04+ * GCC 4.2+ or MS VisualStudio 2010+ Build ----- $ make API --- + The following functions were implemented: + +* `open/1`: same as emdb:open(DBName, []). +* `open/2`: creates a new MDB database. This call also re-open an already existing one. The second argument lets you tune the database. See [mdb.h](http://gitorious.org/mdb/mdb/blobs/master/libraries/libmdb/mdb.h) +* `close/2`: closes the database +* `put/2`: inserts Key with value Val into the database. Assumes that the key is not present, 'key_exit' is returned otherwise. +* `get/1`: retrieves the value stored with Key in the database. +* `del/1`: Removes the key-value with key Key from database. +* `update/2`: inserts Key with value Val into the database if the key is not present, otherwise updates Key to value Val. +* `drop/1`: deletes all key-value pairs in the database. + + +Usage ----- $ make + +$ ./start.sh + + %% create a new database 1> {ok, Handle} = emdb:open("/tmp/emdb1"). + + %% insert the key <<"a">> with value <<"1">> 2> ok = Handle:put(<<"a">>, <<"1">>). + + %% try to re-insert the same key <<"a">> 3> key_exist = Handle:put(<<"a">>, <<"2">>). + + %% add a new key-value pair 4> ok = Handle:put(<<"b">>, <<"2">>). + + %% search a non-existing key <<"c">> 5> none = Handle:get(<<"c">>). + + %% retrieve the value for key <<"b">> 6> {ok, <<"2">>} = Handle:get(<<"b">>). + + %% retrieve the value for key <<"a">> 7> {ok, <<"1">>} = Handle:get(<<"a">>). + + %% delete key <<"b">> 8> ok = Handle:del(<<"b">>). + + %% search a non-existing key <<"b">> + 9> none = Handle:get(<<"b">>). + + %% delete a non-existing key <<"z">> 10> none = Handle:del(<<"z">>). + 11> {ok, <<"1">>} = Handle:get(<<"a">>). + %% update the value for key <<"a">> + 12> ok = Handle:update(<<"a">>, <<"7">>). + + %% check the new value for key <<"a">> + 13> {ok, <<"7">>} = Handle:get(<<"a">>). + + %% delete all key-value pairs in the database 14> ok = Handle:drop(). + + %% try to retrieve key <<"a">> value 15> none = Handle:get(<<"a">>). + + %% close the database 16> ok = Handle:close(). + + 17> q(). + + + Performance ----------- For maximum speed, this library use only binaries for both keys and values. + See the impressive [microbench](http://highlandsun.com/hyc/mdb/microbench/) against: + +* Google's LevelDB +* SQLite +* Kyoto TreeDB +* BerkeleyDB + +MDB performs better on 64-bit arch. + + +Supported OSes -------------- + +Should work on 32/64-bit architectures: + +* Linux +* OSX +* FreeBSD +* Windows + + TODO ---- + +* Unit tests * PropEr testing +* Bulk "writing" + +Volunteers are always welcome! Status +------ + #### Work in progress. Don't use it in production! + LICENSE ------- + EMDB is Copyright (C) 2012 by Aleph Archives, and released under the [OpenLDAP](http://www.OpenLDAP.org/license.html) License. -EMDB is a NIF library for the Memory-Mapped Database database \ No newline at end of file diff --git a/c_src/emdb_drv.c b/c_src/emdb_drv.c new file mode 100644 index 0000000..410a8b0 --- /dev/null +++ b/c_src/emdb_drv.c @@ -0,0 +1,469 @@ +/* ------------------------------------------------------------------------- + * This file is part of EMDB - Erlang MDB API + * + * Copyright (c) 2012 by Aleph Archives. All rights reserved. + * + * ------------------------------------------------------------------------- + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * -------------------------------------------------------------------------*/ + +/* + * C headers + */ + +#include /* for MAXPATHLEN constant */ +#include /* for Erlang NIF interface */ +#include "uthash.h" /* for uthash */ +#include "mdb.h" /* for MDB interface */ + + + +#define FREE(p) (NULL == (p) ? 0 : (free(p), p = NULL)) + +#define FAIL_FAST(Error, Goto) \ + do{ \ + err = Error; \ + goto Goto; \ +}while(0) + + +struct emdb_map_t { + MDB_env * env; + MDB_dbi dbi; + + UT_hash_handle hh; +}; + + +static ERL_NIF_TERM atom_ok; +static ERL_NIF_TERM atom_none; + + +static struct emdb_map_t * emdb_map = NULL; + + +/* emdb ret */ +#define EMDB_RET_KEY_EXIST "key_exist" + +/* emdb errors */ +#define EMDB_MALLOC_ERR "error_malloc" +#define EMDB_CREATE_ERR "error_create" +#define EMDB_MAPSIZE_ERR "error_mapsize" +#define EMDB_OPEN_ERR "error_open" +#define EMDB_TXN_BEGIN_ERR "error_txn_begin" +#define EMDB_TXN_COMMIT_ERR "error_txn_commit" +#define EMDB_OPEN_DBI_ERR "error_open_dbi" +#define EMDB_INVALID_HANDLE_ERR "error_invalid_handle" +#define EMDB_PUT_ERR "error_put" +#define EMDB_UPDATE_ERR "error_update" +#define EMDB_KEY_NOT_FOUND "error_key_not_found" +#define EMDB_DROP_ERR "error_drop" + + + +/* + * Error handling callbacks + */ + +static void emdb_free (struct emdb_map_t * emdb_obj) +{ + FREE(emdb_obj); +} + + +/* + * Driver callbacks + */ + +static ERL_NIF_TERM emdb_open_nif (ErlNifEnv * env, + int argc, const ERL_NIF_TERM argv[]) +{ + char dirname [MAXPATHLEN]; + struct emdb_map_t * node; + MDB_txn * txn; + char * err; + + if (enif_get_string(env, argv[0], dirname, MAXPATHLEN, ERL_NIF_LATIN1) <= 0) + return enif_make_badarg(env); + + if(! (node = calloc(1, sizeof(struct emdb_map_t)))) + FAIL_FAST(EMDB_MALLOC_ERR, err3); + + if (mdb_env_create(& (node -> env))) + FAIL_FAST(EMDB_CREATE_ERR, err2); + + if (mdb_env_set_mapsize(node -> env, 10485760)) + FAIL_FAST(EMDB_MAPSIZE_ERR, err2); + + if (mdb_env_open(node -> env, dirname, MDB_FIXEDMAP, 0664)) + FAIL_FAST(EMDB_OPEN_ERR, err2); + + if (mdb_txn_begin(node -> env, NULL, 0, & txn)) + FAIL_FAST(EMDB_TXN_BEGIN_ERR, err2); + + if (mdb_open(txn, NULL, 0, & (node -> dbi))) + FAIL_FAST(EMDB_OPEN_DBI_ERR, err1); + + if (mdb_txn_commit(txn)) + FAIL_FAST(EMDB_TXN_COMMIT_ERR, err1); + + HASH_ADD_PTR(emdb_map, env, node); + + return enif_make_tuple(env, 2, + atom_ok, + enif_make_ulong(env, (unsigned long) node -> env)); + + err1: + mdb_txn_abort(txn); + err2: + mdb_env_close(node -> env); + err3: + emdb_free(node); + + return enif_make_atom(env, err); +} + +static ERL_NIF_TERM emdb_close_nif (ErlNifEnv * env, + int argc, const ERL_NIF_TERM argv[]) +{ + MDB_env * handle; + struct emdb_map_t * node; + unsigned long addr; + + if (! enif_get_ulong(env, argv[0], & addr)) + return enif_make_badarg(env); + + handle = (MDB_env *) addr; + + HASH_FIND_PTR(emdb_map, & handle, node); + if (NULL == node) + return enif_make_atom(env, EMDB_INVALID_HANDLE_ERR); + + HASH_DEL(emdb_map, node); + + mdb_env_close(node -> env); + emdb_free(node); + + return atom_ok; + } + + +static ERL_NIF_TERM emdb_put_nif (ErlNifEnv * env, + int argc, const ERL_NIF_TERM argv[]) +{ + ErlNifBinary key; + ErlNifBinary val; + + MDB_val mkey; + MDB_val mdata; + + MDB_env * handle; + MDB_txn * txn; + + struct emdb_map_t * node; + unsigned long addr; + char * err; + int ret; + + if (! enif_get_ulong(env, argv[0], & addr)) + return enif_make_badarg(env); + + handle = (MDB_env *) addr; + + HASH_FIND_PTR(emdb_map, & handle, node); + if (NULL == node) + return enif_make_atom(env, EMDB_INVALID_HANDLE_ERR); + + if (! enif_inspect_iolist_as_binary(env, argv[1], &key)) + return enif_make_badarg(env); + + if (! enif_inspect_iolist_as_binary(env, argv[2], &val)) + return enif_make_badarg(env); + + if (mdb_txn_begin(node -> env, NULL, 0, & txn)) + FAIL_FAST(EMDB_TXN_BEGIN_ERR, err2); + + mkey.mv_size = key.size; + mkey.mv_data = key.data; + mdata.mv_size = val.size; + mdata.mv_data = val.data; + + ret = mdb_put(txn, node -> dbi, & mkey, & mdata, MDB_NOOVERWRITE); + if (MDB_KEYEXIST == ret) + FAIL_FAST(EMDB_RET_KEY_EXIST, err1); + if (ret) + FAIL_FAST(EMDB_PUT_ERR, err1); + + if (mdb_txn_commit(txn)) + FAIL_FAST(EMDB_TXN_COMMIT_ERR, err1); + + return atom_ok; + + err1: + mdb_txn_abort(txn); + err2: + return enif_make_atom(env, err); +} + + +static ERL_NIF_TERM emdb_get_nif (ErlNifEnv * env, + int argc, const ERL_NIF_TERM argv[]) +{ + ErlNifBinary key; + ErlNifBinary val; + + MDB_val mkey; + MDB_val mdata; + + MDB_env * handle; + MDB_txn * txn; + + struct emdb_map_t * node; + char * err; + unsigned long addr; + + if (! enif_get_ulong(env, argv[0], & addr)) + return enif_make_badarg(env); + + handle = (MDB_env *) addr; + + HASH_FIND_PTR(emdb_map, & handle, node); + if (NULL == node) + return enif_make_atom(env, EMDB_INVALID_HANDLE_ERR); + + if (! enif_inspect_iolist_as_binary(env, argv[1], &key)) + return enif_make_badarg(env); + + mkey.mv_size = key.size; + mkey.mv_data = key.data; + + if (mdb_txn_begin(node -> env, NULL, 0, & txn)) + FAIL_FAST(EMDB_TXN_BEGIN_ERR, err1); + + if(mdb_get(txn, node -> dbi, & mkey, & mdata)) + { + mdb_txn_abort(txn); + return atom_none; + } + + if (! enif_alloc_binary(mdata.mv_size, & val)) + FAIL_FAST(EMDB_MALLOC_ERR, err2); + + memcpy(val.data, mdata.mv_data, mdata.mv_size); + + mdb_txn_abort(txn); + + return enif_make_tuple(env, 2, + atom_ok, + enif_make_binary(env, & val)); + + err2: + mdb_txn_abort(txn); + err1: + return enif_make_atom(env, err); +} + + +static ERL_NIF_TERM emdb_del_nif (ErlNifEnv * env, + int argc, const ERL_NIF_TERM argv[]) +{ + ErlNifBinary key; + + MDB_val mkey; + + MDB_env * handle; + MDB_txn * txn; + + struct emdb_map_t * node; + char * err; + unsigned long addr; + int ret; + + if (! enif_get_ulong(env, argv[0], & addr)) + return enif_make_badarg(env); + + handle = (MDB_env *) addr; + + HASH_FIND_PTR(emdb_map, & handle, node); + if (NULL == node) + return enif_make_atom(env, EMDB_INVALID_HANDLE_ERR); + + if (! enif_inspect_iolist_as_binary(env, argv[1], &key)) + return enif_make_badarg(env); + + mkey.mv_size = key.size; + mkey.mv_data = key.data; + + if (mdb_txn_begin(node -> env, NULL, 0, & txn)) + FAIL_FAST(EMDB_TXN_BEGIN_ERR, err); + + ret = mdb_del(txn, node -> dbi, & mkey, NULL); + + if (mdb_txn_commit(txn)) + FAIL_FAST(EMDB_TXN_COMMIT_ERR, err); + + if(ret) + return atom_none; + + return atom_ok; + + err: + return enif_make_atom(env, err); +} + + +static ERL_NIF_TERM emdb_update_nif (ErlNifEnv * env, + int argc, const ERL_NIF_TERM argv[]) +{ + ErlNifBinary key; + ErlNifBinary val; + + MDB_val mkey; + MDB_val mdata; + + MDB_env * handle; + MDB_txn * txn; + + struct emdb_map_t * node; + unsigned long addr; + char * err; + + if (! enif_get_ulong(env, argv[0], & addr)) + return enif_make_badarg(env); + + handle = (MDB_env *) addr; + + HASH_FIND_PTR(emdb_map, & handle, node); + if (NULL == node) + return enif_make_atom(env, EMDB_INVALID_HANDLE_ERR); + + if (! enif_inspect_iolist_as_binary(env, argv[1], &key)) + return enif_make_badarg(env); + + if (! enif_inspect_iolist_as_binary(env, argv[2], &val)) + return enif_make_badarg(env); + + if (mdb_txn_begin(node -> env, NULL, 0, & txn)) + FAIL_FAST(EMDB_TXN_BEGIN_ERR, err2); + + mkey.mv_size = key.size; + mkey.mv_data = key.data; + mdata.mv_size = val.size; + mdata.mv_data = val.data; + + if (mdb_put(txn, node -> dbi, & mkey, & mdata, 0)) + FAIL_FAST(EMDB_UPDATE_ERR, err1); + + if (mdb_txn_commit(txn)) + FAIL_FAST(EMDB_TXN_COMMIT_ERR, err1); + + return atom_ok; + + err1: + mdb_txn_abort(txn); + err2: + return enif_make_atom(env, err); +} + + +static ERL_NIF_TERM emdb_drop_nif (ErlNifEnv * env, + int argc, const ERL_NIF_TERM argv[]) +{ + MDB_env * handle; + MDB_txn * txn; + struct emdb_map_t * node; + unsigned long addr; + char * err; + int ret; + + if (! enif_get_ulong(env, argv[0], & addr)) + return enif_make_badarg(env); + + handle = (MDB_env *) addr; + + HASH_FIND_PTR(emdb_map, & handle, node); + if (NULL == node) + return enif_make_atom(env, EMDB_INVALID_HANDLE_ERR); + + if (mdb_txn_begin(node -> env, NULL, 0, & txn)) + FAIL_FAST(EMDB_TXN_BEGIN_ERR, err2); + + ret = mdb_drop(txn, node -> dbi, 0); + if (ret) + FAIL_FAST(EMDB_DROP_ERR, err1); + + if (mdb_txn_commit(txn)) + FAIL_FAST(EMDB_TXN_COMMIT_ERR, err1); + + return atom_ok; + + err1: + mdb_txn_abort(txn); + + err2: + return enif_make_atom(env, err); + } + + +static int emdb_load(ErlNifEnv* env, void** priv, ERL_NIF_TERM load_info) + { + atom_ok = enif_make_atom(env, "ok"); + atom_none = enif_make_atom(env, "none"); + + return (0); + } + +static int emdb_reload(ErlNifEnv* env, void** priv, ERL_NIF_TERM info) +{ + return (0); +} + + +static int emdb_upgrade(ErlNifEnv* env, void** priv, void** old_priv, ERL_NIF_TERM load_info) +{ + return (0); +} + + +static void emdb_unload(ErlNifEnv* env, void* priv) +{ + return; +} + + + +static ErlNifFunc nif_funcs [] = { + {"open", 1, emdb_open_nif}, + {"close", 1, emdb_close_nif}, + {"put", 3, emdb_put_nif}, + {"get", 2, emdb_get_nif}, + {"del", 2, emdb_del_nif}, + {"update", 3, emdb_update_nif}, + {"drop", 1, emdb_drop_nif} +}; + +/* driver entry point */ +ERL_NIF_INIT(emdb_drv, + nif_funcs, + & emdb_load, + & emdb_reload, + & emdb_upgrade, + & emdb_unload) diff --git a/c_src/mdb.c b/c_src/mdb.c new file mode 100644 index 0000000..42e3382 --- /dev/null +++ b/c_src/mdb.c @@ -0,0 +1,6763 @@ +/** @file mdb.c + * @brief memory-mapped database library + * + * A Btree-based database management library modeled loosely on the + * BerkeleyDB API, but much simplified. + */ +/* + * Copyright 2011-2012 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 OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . + * + * This code is derived from btree.c written by Martin Hedenfalk. + * + * Copyright (c) 2009, 2010 Martin Hedenfalk + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include +#include +#include +#ifdef _WIN32 +#include +#else +#include +#include +#ifdef HAVE_SYS_FILE_H +#include +#endif +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if !(defined(BYTE_ORDER) || defined(__BYTE_ORDER)) +#include /* defines BYTE_ORDER on HPUX and Solaris */ +#endif + +#if defined(__APPLE__) || defined (BSD) +# define MDB_USE_POSIX_SEM 1 +# define MDB_FDATASYNC fsync +#elif defined(ANDROID) +# define MDB_FDATASYNC fsync +#endif + +#ifndef _WIN32 +#include +#ifdef MDB_USE_POSIX_SEM +#include +#endif +#endif + +#ifdef USE_VALGRIND +#include +#define VGMEMP_CREATE(h,r,z) VALGRIND_CREATE_MEMPOOL(h,r,z) +#define VGMEMP_ALLOC(h,a,s) VALGRIND_MEMPOOL_ALLOC(h,a,s) +#define VGMEMP_FREE(h,a) VALGRIND_MEMPOOL_FREE(h,a) +#define VGMEMP_DESTROY(h) VALGRIND_DESTROY_MEMPOOL(h) +#define VGMEMP_DEFINED(a,s) VALGRIND_MAKE_MEM_DEFINED(a,s) +#else +#define VGMEMP_CREATE(h,r,z) +#define VGMEMP_ALLOC(h,a,s) +#define VGMEMP_FREE(h,a) +#define VGMEMP_DESTROY(h) +#define VGMEMP_DEFINED(a,s) +#endif + +#ifndef BYTE_ORDER +# if (defined(_LITTLE_ENDIAN) || defined(_BIG_ENDIAN)) && !(defined(_LITTLE_ENDIAN) && defined(_BIG_ENDIAN)) +/* Solaris just defines one or the other */ +# define LITTLE_ENDIAN 1234 +# define BIG_ENDIAN 4321 +# ifdef _LITTLE_ENDIAN +# define BYTE_ORDER LITTLE_ENDIAN +# else +# define BYTE_ORDER BIG_ENDIAN +# endif +# else +# define BYTE_ORDER __BYTE_ORDER +# endif +#endif + +#ifndef LITTLE_ENDIAN +#define LITTLE_ENDIAN __LITTLE_ENDIAN +#endif +#ifndef BIG_ENDIAN +#define BIG_ENDIAN __BIG_ENDIAN +#endif + +#if defined(__i386) || defined(__x86_64) +#define MISALIGNED_OK 1 +#endif + +#include "mdb.h" +#include "midl.h" + +#if (BYTE_ORDER == LITTLE_ENDIAN) == (BYTE_ORDER == BIG_ENDIAN) +# error "Unknown or unsupported endianness (BYTE_ORDER)" +#elif (-6 & 5) || CHAR_BIT != 8 || UINT_MAX < 0xffffffff || ULONG_MAX % 0xFFFF +# error "Two's complement, reasonably sized integer types, please" +#endif + +/** @defgroup internal MDB Internals + * @{ + */ +/** @defgroup compat Windows Compatibility Macros + * A bunch of macros to minimize the amount of platform-specific ifdefs + * needed throughout the rest of the code. When the features this library + * needs are similar enough to POSIX to be hidden in a one-or-two line + * replacement, this macro approach is used. + * @{ + */ +#ifdef _WIN32 +#define pthread_t DWORD +#define pthread_mutex_t HANDLE +#define pthread_key_t DWORD +#define pthread_self() GetCurrentThreadId() +#define pthread_key_create(x,y) (*(x) = TlsAlloc()) +#define pthread_key_delete(x) TlsFree(x) +#define pthread_getspecific(x) TlsGetValue(x) +#define pthread_setspecific(x,y) TlsSetValue(x,y) +#define pthread_mutex_unlock(x) ReleaseMutex(x) +#define pthread_mutex_lock(x) WaitForSingleObject(x, INFINITE) +#define LOCK_MUTEX_R(env) pthread_mutex_lock((env)->me_rmutex) +#define UNLOCK_MUTEX_R(env) pthread_mutex_unlock((env)->me_rmutex) +#define LOCK_MUTEX_W(env) pthread_mutex_lock((env)->me_wmutex) +#define UNLOCK_MUTEX_W(env) pthread_mutex_unlock((env)->me_wmutex) +#define getpid() GetCurrentProcessId() +#define MDB_FDATASYNC(fd) (!FlushFileBuffers(fd)) +#define MDB_MSYNC(addr,len,flags) (!FlushViewOfFile(addr,len)) +#define ErrCode() GetLastError() +#define GET_PAGESIZE(x) {SYSTEM_INFO si; GetSystemInfo(&si); (x) = si.dwPageSize;} +#define close(fd) CloseHandle(fd) +#define munmap(ptr,len) UnmapViewOfFile(ptr) +#else + +#ifdef MDB_USE_POSIX_SEM + +#define LOCK_MUTEX_R(env) mdb_sem_wait((env)->me_rmutex) +#define UNLOCK_MUTEX_R(env) sem_post((env)->me_rmutex) +#define LOCK_MUTEX_W(env) mdb_sem_wait((env)->me_wmutex) +#define UNLOCK_MUTEX_W(env) sem_post((env)->me_wmutex) + +static int +mdb_sem_wait(sem_t *sem) +{ + int rc; + while ((rc = sem_wait(sem)) && (rc = errno) == EINTR) ; + return rc; +} + +#else + /** Lock the reader mutex. + */ +#define LOCK_MUTEX_R(env) pthread_mutex_lock(&(env)->me_txns->mti_mutex) + /** Unlock the reader mutex. + */ +#define UNLOCK_MUTEX_R(env) pthread_mutex_unlock(&(env)->me_txns->mti_mutex) + + /** Lock the writer mutex. + * Only a single write transaction is allowed at a time. Other writers + * will block waiting for this mutex. + */ +#define LOCK_MUTEX_W(env) pthread_mutex_lock(&(env)->me_txns->mti_wmutex) + /** Unlock the writer mutex. + */ +#define UNLOCK_MUTEX_W(env) pthread_mutex_unlock(&(env)->me_txns->mti_wmutex) +#endif /* MDB_USE_POSIX_SEM */ + + /** Get the error code for the last failed system function. + */ +#define ErrCode() errno + + /** An abstraction for a file handle. + * On POSIX systems file handles are small integers. On Windows + * they're opaque pointers. + */ +#define HANDLE int + + /** A value for an invalid file handle. + * Mainly used to initialize file variables and signify that they are + * unused. + */ +#define INVALID_HANDLE_VALUE (-1) + + /** Get the size of a memory page for the system. + * This is the basic size that the platform's memory manager uses, and is + * fundamental to the use of memory-mapped files. + */ +#define GET_PAGESIZE(x) ((x) = sysconf(_SC_PAGE_SIZE)) +#endif + +#if defined(_WIN32) || defined(MDB_USE_POSIX_SEM) +#define MNAME_LEN 32 +#else +#define MNAME_LEN (sizeof(pthread_mutex_t)) +#endif + +/** @} */ + +#ifndef _WIN32 +/** A flag for opening a file and requesting synchronous data writes. + * This is only used when writing a meta page. It's not strictly needed; + * we could just do a normal write and then immediately perform a flush. + * But if this flag is available it saves us an extra system call. + * + * @note If O_DSYNC is undefined but exists in /usr/include, + * preferably set some compiler flag to get the definition. + * Otherwise compile with the less efficient -DMDB_DSYNC=O_SYNC. + */ +#ifndef MDB_DSYNC +# define MDB_DSYNC O_DSYNC +#endif +#endif + +/** Function for flushing the data of a file. Define this to fsync + * if fdatasync() is not supported. + */ +#ifndef MDB_FDATASYNC +# define MDB_FDATASYNC fdatasync +#endif + +#ifndef MDB_MSYNC +# define MDB_MSYNC(addr,len,flags) msync(addr,len,flags) +#endif + +#ifndef MS_SYNC +#define MS_SYNC 1 +#endif + +#ifndef MS_ASYNC +#define MS_ASYNC 0 +#endif + + /** A page number in the database. + * Note that 64 bit page numbers are overkill, since pages themselves + * already represent 12-13 bits of addressable memory, and the OS will + * always limit applications to a maximum of 63 bits of address space. + * + * @note In the #MDB_node structure, we only store 48 bits of this value, + * which thus limits us to only 60 bits of addressable data. + */ +typedef MDB_ID pgno_t; + + /** A transaction ID. + * See struct MDB_txn.mt_txnid for details. + */ +typedef MDB_ID txnid_t; + +/** @defgroup debug Debug Macros + * @{ + */ +#ifndef MDB_DEBUG + /** Enable debug output. + * Set this to 1 for copious tracing. Set to 2 to add dumps of all IDLs + * read from and written to the database (used for free space management). + */ +#define MDB_DEBUG 0 +#endif + +#if !(__STDC_VERSION__ >= 199901L || defined(__GNUC__)) +# define DPRINTF (void) /* Vararg macros may be unsupported */ +#elif MDB_DEBUG +static int mdb_debug; +static txnid_t mdb_debug_start; + + /** Print a debug message with printf formatting. */ +# define DPRINTF(fmt, ...) /**< Requires 2 or more args */ \ + ((void) ((mdb_debug) && \ + fprintf(stderr, "%s:%d " fmt "\n", __func__, __LINE__, __VA_ARGS__))) +#else +# define DPRINTF(fmt, ...) ((void) 0) +#endif + /** Print a debug string. + * The string is printed literally, with no format processing. + */ +#define DPUTS(arg) DPRINTF("%s", arg) +/** @} */ + + /** A default memory page size. + * The actual size is platform-dependent, but we use this for + * boot-strapping. We probably should not be using this any more. + * The #GET_PAGESIZE() macro is used to get the actual size. + * + * Note that we don't currently support Huge pages. On Linux, + * regular data files cannot use Huge pages, and in general + * Huge pages aren't actually pageable. We rely on the OS + * demand-pager to read our data and page it out when memory + * pressure from other processes is high. So until OSs have + * actual paging support for Huge pages, they're not viable. + */ +#define MDB_PAGESIZE 4096 + + /** The minimum number of keys required in a database page. + * Setting this to a larger value will place a smaller bound on the + * maximum size of a data item. Data items larger than this size will + * be pushed into overflow pages instead of being stored directly in + * the B-tree node. This value used to default to 4. With a page size + * of 4096 bytes that meant that any item larger than 1024 bytes would + * go into an overflow page. That also meant that on average 2-3KB of + * each overflow page was wasted space. The value cannot be lower than + * 2 because then there would no longer be a tree structure. With this + * value, items larger than 2KB will go into overflow pages, and on + * average only 1KB will be wasted. + */ +#define MDB_MINKEYS 2 + + /** A stamp that identifies a file as an MDB file. + * There's nothing special about this value other than that it is easily + * recognizable, and it will reflect any byte order mismatches. + */ +#define MDB_MAGIC 0xBEEFC0DE + + /** The version number for a database's file format. */ +#define MDB_VERSION 1 + + /** The maximum size of a key in the database. + * While data items have essentially unbounded size, we require that + * keys all fit onto a regular page. This limit could be raised a bit + * further if needed; to something just under #MDB_PAGESIZE / #MDB_MINKEYS. + */ +#define MAXKEYSIZE 511 + +#if MDB_DEBUG + /** A key buffer. + * @ingroup debug + * This is used for printing a hex dump of a key's contents. + */ +#define DKBUF char kbuf[(MAXKEYSIZE*2+1)] + /** Display a key in hex. + * @ingroup debug + * Invoke a function to display a key in hex. + */ +#define DKEY(x) mdb_dkey(x, kbuf) +#else +#define DKBUF typedef int dummy_kbuf /* so we can put ';' after */ +#define DKEY(x) 0 +#endif + + /** An invalid page number. + * Mainly used to denote an empty tree. + */ +#define P_INVALID (~(pgno_t)0) + + /** Test if a flag \b f is set in a flag word \b w. */ +#define F_ISSET(w, f) (((w) & (f)) == (f)) + + /** Used for offsets within a single page. + * Since memory pages are typically 4 or 8KB in size, 12-13 bits, + * this is plenty. + */ +typedef uint16_t indx_t; + + /** Default size of memory map. + * This is certainly too small for any actual applications. Apps should always set + * the size explicitly using #mdb_env_set_mapsize(). + */ +#define DEFAULT_MAPSIZE 1048576 + +/** @defgroup readers Reader Lock Table + * Readers don't acquire any locks for their data access. Instead, they + * simply record their transaction ID in the reader table. The reader + * mutex is needed just to find an empty slot in the reader table. The + * slot's address is saved in thread-specific data so that subsequent read + * transactions started by the same thread need no further locking to proceed. + * + * Since the database uses multi-version concurrency control, readers don't + * actually need any locking. This table is used to keep track of which + * readers are using data from which old transactions, so that we'll know + * when a particular old transaction is no longer in use. Old transactions + * that have discarded any data pages can then have those pages reclaimed + * for use by a later write transaction. + * + * The lock table is constructed such that reader slots are aligned with the + * processor's cache line size. Any slot is only ever used by one thread. + * This alignment guarantees that there will be no contention or cache + * thrashing as threads update their own slot info, and also eliminates + * any need for locking when accessing a slot. + * + * A writer thread will scan every slot in the table to determine the oldest + * outstanding reader transaction. Any freed pages older than this will be + * reclaimed by the writer. The writer doesn't use any locks when scanning + * this table. This means that there's no guarantee that the writer will + * see the most up-to-date reader info, but that's not required for correct + * operation - all we need is to know the upper bound on the oldest reader, + * we don't care at all about the newest reader. So the only consequence of + * reading stale information here is that old pages might hang around a + * while longer before being reclaimed. That's actually good anyway, because + * the longer we delay reclaiming old pages, the more likely it is that a + * string of contiguous pages can be found after coalescing old pages from + * many old transactions together. + * + * @todo We don't actually do such coalescing yet, we grab pages from one + * old transaction at a time. + * @{ + */ + /** Number of slots in the reader table. + * This value was chosen somewhat arbitrarily. 126 readers plus a + * couple mutexes fit exactly into 8KB on my development machine. + * Applications should set the table size using #mdb_env_set_maxreaders(). + */ +#define DEFAULT_READERS 126 + + /** The size of a CPU cache line in bytes. We want our lock structures + * aligned to this size to avoid false cache line sharing in the + * lock table. + * This value works for most CPUs. For Itanium this should be 128. + */ +#ifndef CACHELINE +#define CACHELINE 64 +#endif + + /** The information we store in a single slot of the reader table. + * In addition to a transaction ID, we also record the process and + * thread ID that owns a slot, so that we can detect stale information, + * e.g. threads or processes that went away without cleaning up. + * @note We currently don't check for stale records. We simply re-init + * the table when we know that we're the only process opening the + * lock file. + */ +typedef struct MDB_rxbody { + /** Current Transaction ID when this transaction began, or (txnid_t)-1. + * Multiple readers that start at the same time will probably have the + * same ID here. Again, it's not important to exclude them from + * anything; all we need to know is which version of the DB they + * started from so we can avoid overwriting any data used in that + * particular version. + */ + txnid_t mrb_txnid; + /** The process ID of the process owning this reader txn. */ + pid_t mrb_pid; + /** The thread ID of the thread owning this txn. */ + pthread_t mrb_tid; +} MDB_rxbody; + + /** The actual reader record, with cacheline padding. */ +typedef struct MDB_reader { + union { + MDB_rxbody mrx; + /** shorthand for mrb_txnid */ +#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; + + /** The header for the reader table. + * The table resides in a memory-mapped file. (This is a different file + * than is used for the main database.) + * + * For POSIX the actual mutexes reside in the shared memory of this + * mapped file. On Windows, mutexes are named objects allocated by the + * kernel; we store the mutex names in this mapped file so that other + * processes can grab them. This same approach is also used on + * MacOSX/Darwin (using named semaphores) since MacOSX doesn't support + * process-shared POSIX mutexes. For these cases where a named object + * is used, the object name is derived from a 64 bit FNV hash of the + * environment pathname. As such, naming collisions are extremely + * unlikely. If a collision occurs, the results are unpredictable. + */ +typedef struct MDB_txbody { + /** Stamp identifying this as an MDB file. It must be set + * to #MDB_MAGIC. */ + uint32_t mtb_magic; + /** Version number of this lock file. Must be set to #MDB_VERSION. */ + uint32_t mtb_version; +#if defined(_WIN32) || defined(MDB_USE_POSIX_SEM) + char mtb_rmname[MNAME_LEN]; +#else + /** Mutex protecting access to this table. + * This is the reader lock that #LOCK_MUTEX_R acquires. + */ + pthread_mutex_t mtb_mutex; +#endif + /** The ID of the last transaction committed to the database. + * This is recorded here only for convenience; the value can always + * be determined by reading the main database meta pages. + */ + txnid_t mtb_txnid; + /** The number of slots that have been used in the reader table. + * This always records the maximum count, it is not decremented + * when readers release their slots. + */ + unsigned mtb_numreaders; +} MDB_txbody; + + /** The actual reader table definition. */ +typedef struct MDB_txninfo { + 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_rmname mt1.mtb.mtb_rmname +#define mti_txnid mt1.mtb.mtb_txnid +#define mti_numreaders mt1.mtb.mtb_numreaders + char pad[(sizeof(MDB_txbody)+CACHELINE-1) & ~(CACHELINE-1)]; + } mt1; + union { +#if defined(_WIN32) || defined(MDB_USE_POSIX_SEM) + char mt2_wmname[MNAME_LEN]; +#define mti_wmname mt2.mt2_wmname +#else + pthread_mutex_t mt2_wmutex; +#define mti_wmutex mt2.mt2_wmutex +#endif + char pad[(MNAME_LEN+CACHELINE-1) & ~(CACHELINE-1)]; + } mt2; + MDB_reader mti_readers[1]; +} MDB_txninfo; +/** @} */ + +/** Common header for all page types. + * Overflow records occupy a number of contiguous pages with no + * headers on any page after the first. + */ +typedef struct MDB_page { +#define mp_pgno mp_p.p_pgno +#define mp_next mp_p.p_next + union { + pgno_t p_pgno; /**< page number */ + void * p_next; /**< for in-memory list of freed structs */ + } mp_p; + uint16_t mp_pad; +/** @defgroup mdb_page Page Flags + * @ingroup internal + * Flags for the page headers. + * @{ + */ +#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 /**< for #MDB_DUPFIXED records */ +#define P_SUBP 0x40 /**< for #MDB_DUPSORT sub-pages */ +/** @} */ + uint16_t mp_flags; /**< @ref mdb_page */ +#define mp_lower mp_pb.pb.pb_lower +#define mp_upper mp_pb.pb.pb_upper +#define mp_pages mp_pb.pb_pages + union { + struct { + indx_t pb_lower; /**< lower bound of free space */ + indx_t pb_upper; /**< upper bound of free space */ + } pb; + uint32_t pb_pages; /**< number of overflow pages */ + } mp_pb; + indx_t mp_ptrs[1]; /**< dynamic size */ +} MDB_page; + + /** Size of the page header, excluding dynamic data at the end */ +#define PAGEHDRSZ ((unsigned) offsetof(MDB_page, mp_ptrs)) + + /** Address of first usable data byte in a page, after the header */ +#define METADATA(p) ((void *)((char *)(p) + PAGEHDRSZ)) + + /** Number of nodes on a page */ +#define NUMKEYS(p) (((p)->mp_lower - PAGEHDRSZ) >> 1) + + /** The amount of space remaining in the page */ +#define SIZELEFT(p) (indx_t)((p)->mp_upper - (p)->mp_lower) + + /** The percentage of space used in the page, in tenths of a percent. */ +#define PAGEFILL(env, p) (1000L * ((env)->me_psize - PAGEHDRSZ - SIZELEFT(p)) / \ + ((env)->me_psize - PAGEHDRSZ)) + /** The minimum page fill factor, in tenths of a percent. + * Pages emptier than this are candidates for merging. + */ +#define FILL_THRESHOLD 250 + + /** Test if a page is a leaf page */ +#define IS_LEAF(p) F_ISSET((p)->mp_flags, P_LEAF) + /** Test if a page is a LEAF2 page */ +#define IS_LEAF2(p) F_ISSET((p)->mp_flags, P_LEAF2) + /** Test if a page is a branch page */ +#define IS_BRANCH(p) F_ISSET((p)->mp_flags, P_BRANCH) + /** Test if a page is an overflow page */ +#define IS_OVERFLOW(p) F_ISSET((p)->mp_flags, P_OVERFLOW) + /** Test if a page is a sub page */ +#define IS_SUBP(p) F_ISSET((p)->mp_flags, P_SUBP) + + /** The number of overflow pages needed to store the given size. */ +#define OVPAGES(size, psize) ((PAGEHDRSZ-1 + (size)) / (psize) + 1) + + /** Header for a single key/data pair within a page. + * We guarantee 2-byte alignment for nodes. + */ +typedef struct MDB_node { + /** lo and hi are used for data size on leaf nodes and for + * child pgno on branch nodes. On 64 bit platforms, flags + * is also used for pgno. (Branch nodes have no flags). + * They are in host byte order in case that lets some + * accesses be optimized into a 32-bit word access. + */ +#define mn_lo mn_offset[BYTE_ORDER!=LITTLE_ENDIAN] +#define mn_hi mn_offset[BYTE_ORDER==LITTLE_ENDIAN] /**< part of dsize or pgno */ + unsigned short mn_offset[2]; /**< storage for #mn_lo and #mn_hi */ +/** @defgroup mdb_node Node Flags + * @ingroup internal + * Flags for node headers. + * @{ + */ +#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 */ + +/** valid flags for #mdb_node_add() */ +#define NODE_ADD_FLAGS (F_DUPDATA|F_SUBDATA|MDB_RESERVE|MDB_APPEND) + +/** @} */ + unsigned short mn_flags; /**< @ref mdb_node */ + unsigned short mn_ksize; /**< key size */ + char mn_data[1]; /**< key and data are appended here */ +} MDB_node; + + /** Size of the node header, excluding dynamic data at the end */ +#define NODESIZE offsetof(MDB_node, mn_data) + + /** Bit position of top word in page number, for shifting mn_flags */ +#define PGNO_TOPWORD ((pgno_t)-1 > 0xffffffffu ? 32 : 0) + + /** Size of a node in a branch page with a given key. + * This is just the node header plus the key, there is no data. + */ +#define INDXSIZE(k) (NODESIZE + ((k) == NULL ? 0 : (k)->mv_size)) + + /** Size of a node in a leaf page with a given key and data. + * This is node header plus key plus data size. + */ +#define LEAFSIZE(k, d) (NODESIZE + (k)->mv_size + (d)->mv_size) + + /** Address of node \b i in page \b p */ +#define NODEPTR(p, i) ((MDB_node *)((char *)(p) + (p)->mp_ptrs[i])) + + /** Address of the key for the node */ +#define NODEKEY(node) (void *)((node)->mn_data) + + /** Address of the data for a node */ +#define NODEDATA(node) (void *)((char *)(node)->mn_data + (node)->mn_ksize) + + /** Get the page number pointed to by a branch node */ +#define NODEPGNO(node) \ + ((node)->mn_lo | ((pgno_t) (node)->mn_hi << 16) | \ + (PGNO_TOPWORD ? ((pgno_t) (node)->mn_flags << PGNO_TOPWORD) : 0)) + /** Set the page number in a branch node */ +#define SETPGNO(node,pgno) do { \ + (node)->mn_lo = (pgno) & 0xffff; (node)->mn_hi = (pgno) >> 16; \ + if (PGNO_TOPWORD) (node)->mn_flags = (pgno) >> PGNO_TOPWORD; } while(0) + + /** Get the size of the data in a leaf node */ +#define NODEDSZ(node) ((node)->mn_lo | ((unsigned)(node)->mn_hi << 16)) + /** Set the size of the data for a leaf node */ +#define SETDSZ(node,size) do { \ + (node)->mn_lo = (size) & 0xffff; (node)->mn_hi = (size) >> 16;} while(0) + /** The size of a key in a node */ +#define NODEKSZ(node) ((node)->mn_ksize) + + /** Copy a page number from src to dst */ +#ifdef MISALIGNED_OK +#define COPY_PGNO(dst,src) dst = src +#else +#if SIZE_MAX > 4294967295UL +#define COPY_PGNO(dst,src) do { \ + unsigned short *s, *d; \ + s = (unsigned short *)&(src); \ + d = (unsigned short *)&(dst); \ + *d++ = *s++; \ + *d++ = *s++; \ + *d++ = *s++; \ + *d = *s; \ +} while (0) +#else +#define COPY_PGNO(dst,src) do { \ + unsigned short *s, *d; \ + s = (unsigned short *)&(src); \ + d = (unsigned short *)&(dst); \ + *d++ = *s++; \ + *d = *s; \ +} while (0) +#endif +#endif + /** The address of a key in a LEAF2 page. + * LEAF2 pages are used for #MDB_DUPFIXED sorted-duplicate sub-DBs. + * There are no node headers, keys are stored contiguously. + */ +#define LEAF2KEY(p, i, ks) ((char *)(p) + PAGEHDRSZ + ((i)*(ks))) + + /** Set the \b node's key into \b key, if requested. */ +#define MDB_GET_KEY(node, key) { if ((key) != NULL) { \ + (key)->mv_size = NODEKSZ(node); (key)->mv_data = NODEKEY(node); } } + + /** Information about a single database in the environment. */ +typedef struct MDB_db { + uint32_t md_pad; /**< also ksize for LEAF2 pages */ + uint16_t md_flags; /**< @ref mdb_open */ + uint16_t md_depth; /**< depth of this tree */ + pgno_t md_branch_pages; /**< number of internal pages */ + pgno_t md_leaf_pages; /**< number of leaf pages */ + pgno_t md_overflow_pages; /**< number of overflow pages */ + size_t md_entries; /**< number of data items */ + pgno_t md_root; /**< the root page of this tree */ +} MDB_db; + + /** Handle for the DB used to track free pages. */ +#define FREE_DBI 0 + /** Handle for the default DB. */ +#define MAIN_DBI 1 + + /** Meta page content. */ +typedef struct MDB_meta { + /** Stamp identifying this as an MDB file. It must be set + * to #MDB_MAGIC. */ + uint32_t mm_magic; + /** Version number of this lock file. Must be set to #MDB_VERSION. */ + uint32_t mm_version; + void *mm_address; /**< address for fixed mapping */ + size_t mm_mapsize; /**< size of mmap region */ + MDB_db mm_dbs[2]; /**< first is free space, 2nd is main db */ + /** The size of pages used in this DB */ +#define mm_psize mm_dbs[0].md_pad + /** Any persistent environment flags. @ref mdb_env */ +#define mm_flags mm_dbs[0].md_flags + pgno_t mm_last_pg; /**< last used page in file */ + txnid_t mm_txnid; /**< txnid that committed this page */ +} MDB_meta; + + /** Buffer for a stack-allocated dirty page. + * The members define size and alignment, and silence type + * aliasing warnings. They are not used directly; that could + * mean incorrectly using several union members in parallel. + */ +typedef union MDB_pagebuf { + char mb_raw[MDB_PAGESIZE]; + MDB_page mb_page; + struct { + char mm_pad[PAGEHDRSZ]; + MDB_meta mm_meta; + } mb_metabuf; +} MDB_pagebuf; + + /** Auxiliary DB info. + * The information here is mostly static/read-only. There is + * only a single copy of this record in the environment. + */ +typedef struct MDB_dbx { + MDB_val md_name; /**< name of the database */ + MDB_cmp_func *md_cmp; /**< function for comparing keys */ + MDB_cmp_func *md_dcmp; /**< function for comparing data items */ + MDB_rel_func *md_rel; /**< user relocate function */ + void *md_relctx; /**< user-provided context for md_rel */ +} MDB_dbx; + + /** A database transaction. + * Every operation requires a transaction handle. + */ +struct MDB_txn { + MDB_txn *mt_parent; /**< parent of a nested txn */ + MDB_txn *mt_child; /**< nested txn under this txn */ + pgno_t mt_next_pgno; /**< next unallocated page */ + /** The ID of this transaction. IDs are integers incrementing from 1. + * Only committed write transactions increment the ID. If a transaction + * aborts, the ID may be re-used by the next writer. + */ + txnid_t mt_txnid; + MDB_env *mt_env; /**< the DB environment */ + /** The list of pages that became unused during this transaction. + */ + MDB_IDL mt_free_pgs; + union { + MDB_ID2L dirty_list; /**< modified pages */ + MDB_reader *reader; /**< this thread's slot in the reader table */ + } mt_u; + /** Array of records for each DB known in the environment. */ + MDB_dbx *mt_dbxs; + /** Array of MDB_db records for each known DB */ + MDB_db *mt_dbs; +/** @defgroup mt_dbflag Transaction DB Flags + * @ingroup internal + * @{ + */ +#define DB_DIRTY 0x01 /**< DB was written in this txn */ +#define DB_STALE 0x02 /**< DB record is older than txnID */ +/** @} */ + /** Array of cursors for each DB */ + MDB_cursor **mt_cursors; + /** Array of flags for each DB */ + unsigned char *mt_dbflags; + /** Number of DB records in use. This number only ever increments; + * we don't decrement it when individual DB handles are closed. + */ + MDB_dbi mt_numdbs; + +/** @defgroup mdb_txn Transaction Flags + * @ingroup internal + * @{ + */ +#define MDB_TXN_RDONLY 0x01 /**< read-only transaction */ +#define MDB_TXN_ERROR 0x02 /**< an error has occurred */ +/** @} */ + unsigned int mt_flags; /**< @ref mdb_txn */ + /** Tracks which of the two meta pages was used at the start + * of this transaction. + */ + unsigned int mt_toggle; +}; + +/** Enough space for 2^32 nodes with minimum of 2 keys per node. I.e., plenty. + * At 4 keys per node, enough for 2^64 nodes, so there's probably no need to + * raise this on a 64 bit machine. + */ +#define CURSOR_STACK 32 + +struct MDB_xcursor; + + /** Cursors are used for all DB operations */ +struct MDB_cursor { + /** Next cursor on this DB in this txn */ + MDB_cursor *mc_next; + /** Original cursor if this is a shadow */ + MDB_cursor *mc_orig; + /** Context used for databases with #MDB_DUPSORT, otherwise NULL */ + struct MDB_xcursor *mc_xcursor; + /** The transaction that owns this cursor */ + MDB_txn *mc_txn; + /** The database handle this cursor operates on */ + MDB_dbi mc_dbi; + /** The database record for this cursor */ + MDB_db *mc_db; + /** The database auxiliary record for this cursor */ + MDB_dbx *mc_dbx; + /** The @ref mt_dbflag for this database */ + unsigned char *mc_dbflag; + unsigned short mc_snum; /**< number of pushed pages */ + unsigned short mc_top; /**< index of top page, normally mc_snum-1 */ +/** @defgroup mdb_cursor Cursor Flags + * @ingroup internal + * Cursor state flags. + * @{ + */ +#define C_INITIALIZED 0x01 /**< cursor has been initialized and is valid */ +#define C_EOF 0x02 /**< No more data */ +#define C_SUB 0x04 /**< Cursor is a sub-cursor */ +#define C_SHADOW 0x08 /**< Cursor is a dup from a parent txn */ +#define C_ALLOCD 0x10 /**< Cursor was malloc'd */ +#define C_SPLITTING 0x20 /**< Cursor is in page_split */ +/** @} */ + unsigned int mc_flags; /**< @ref mdb_cursor */ + MDB_page *mc_pg[CURSOR_STACK]; /**< stack of pushed pages */ + indx_t mc_ki[CURSOR_STACK]; /**< stack of page indices */ +}; + + /** Context for sorted-dup records. + * We could have gone to a fully recursive design, with arbitrarily + * deep nesting of sub-databases. But for now we only handle these + * levels - main DB, optional sub-DB, sorted-duplicate DB. + */ +typedef struct MDB_xcursor { + /** A sub-cursor for traversing the Dup DB */ + MDB_cursor mx_cursor; + /** The database record for this Dup DB */ + MDB_db mx_db; + /** The auxiliary DB record for this Dup DB */ + MDB_dbx mx_dbx; + /** The @ref mt_dbflag for this Dup DB */ + unsigned char mx_dbflag; +} MDB_xcursor; + + /** A set of pages freed by an earlier transaction. */ +typedef struct MDB_oldpages { + /** Usually we only read one record from the FREEDB at a time, but + * in case we read more, this will chain them together. + */ + struct MDB_oldpages *mo_next; + /** The ID of the transaction in which these pages were freed. */ + txnid_t mo_txnid; + /** An #MDB_IDL of the pages */ + pgno_t mo_pages[1]; /* dynamic */ +} MDB_oldpages; + + /** The database environment. */ +struct MDB_env { + HANDLE me_fd; /**< The main data file */ + HANDLE me_lfd; /**< The lock file */ + HANDLE me_mfd; /**< just for writing the meta pages */ + /** Failed to update the meta page. Probably an I/O error. */ +#define MDB_FATAL_ERROR 0x80000000U + uint32_t me_flags; /**< @ref mdb_env */ + unsigned int me_psize; /**< size of a page, from #GET_PAGESIZE */ + unsigned int me_maxreaders; /**< size of the reader table */ + unsigned int me_numreaders; /**< max numreaders set by this env */ + MDB_dbi me_numdbs; /**< number of DBs opened */ + MDB_dbi me_maxdbs; /**< size of the DB table */ + pid_t me_pid; /**< process ID of this env */ + char *me_path; /**< path to the DB files */ + char *me_map; /**< the memory map of the data file */ + MDB_txninfo *me_txns; /**< the memory map of the lock file */ + MDB_meta *me_metas[2]; /**< pointers to the two meta pages */ + MDB_txn *me_txn; /**< current write transaction */ + size_t me_mapsize; /**< size of the data memory map */ + off_t me_size; /**< current file size */ + pgno_t me_maxpg; /**< me_mapsize / me_psize */ + txnid_t me_pgfirst; /**< ID of first old page record we used */ + txnid_t me_pglast; /**< ID of last old page record we used */ + MDB_dbx *me_dbxs; /**< array of static DB info */ + uint16_t *me_dbflags; /**< array of DB flags */ + MDB_oldpages *me_pghead; /**< list of old page records */ + MDB_oldpages *me_pgfree; /**< list of page records to free */ + pthread_key_t me_txkey; /**< thread-key for readers */ + MDB_page *me_dpages; /**< list of malloc'd blocks for re-use */ + /** IDL of pages that became unused in a write txn */ + MDB_IDL me_free_pgs; + /** ID2L of pages that were written during a write txn */ + MDB_ID2 me_dirty_list[MDB_IDL_UM_SIZE]; +#ifdef _WIN32 + HANDLE me_rmutex; /* Windows mutexes don't reside in shared mem */ + HANDLE me_wmutex; +#elif defined(MDB_USE_POSIX_SEM) + sem_t *me_rmutex; /* Shared mutexes are not supported */ + sem_t *me_wmutex; +#endif +}; + /** max number of pages to commit in one writev() call */ +#define MDB_COMMIT_PAGES 64 +#if defined(IOV_MAX) && IOV_MAX < MDB_COMMIT_PAGES +#undef MDB_COMMIT_PAGES +#define MDB_COMMIT_PAGES IOV_MAX +#endif + +static int mdb_page_alloc(MDB_cursor *mc, int num, MDB_page **mp); +static int mdb_page_new(MDB_cursor *mc, uint32_t flags, int num, MDB_page **mp); +static int mdb_page_touch(MDB_cursor *mc); + +static int mdb_page_get(MDB_txn *txn, pgno_t pgno, MDB_page **mp); +static int mdb_page_search_root(MDB_cursor *mc, + MDB_val *key, int modify); +#define MDB_PS_MODIFY 1 +#define MDB_PS_ROOTONLY 2 +static int mdb_page_search(MDB_cursor *mc, + MDB_val *key, int flags); +static int mdb_page_merge(MDB_cursor *csrc, MDB_cursor *cdst); + +#define MDB_SPLIT_REPLACE MDB_APPENDDUP /**< newkey is not new */ +static int mdb_page_split(MDB_cursor *mc, MDB_val *newkey, MDB_val *newdata, + pgno_t newpgno, unsigned int nflags); + +static int mdb_env_read_header(MDB_env *env, MDB_meta *meta); +static int mdb_env_pick_meta(const MDB_env *env); +static int mdb_env_write_meta(MDB_txn *txn); +static void mdb_env_close0(MDB_env *env, int excl); + +static MDB_node *mdb_node_search(MDB_cursor *mc, MDB_val *key, int *exactp); +static int mdb_node_add(MDB_cursor *mc, indx_t indx, + MDB_val *key, MDB_val *data, pgno_t pgno, unsigned int flags); +static void mdb_node_del(MDB_page *mp, indx_t indx, int ksize); +static void mdb_node_shrink(MDB_page *mp, indx_t indx); +static int mdb_node_move(MDB_cursor *csrc, MDB_cursor *cdst); +static int mdb_node_read(MDB_txn *txn, MDB_node *leaf, MDB_val *data); +static size_t mdb_leaf_size(MDB_env *env, MDB_val *key, MDB_val *data); +static size_t mdb_branch_size(MDB_env *env, MDB_val *key); + +static int mdb_rebalance(MDB_cursor *mc); +static int mdb_update_key(MDB_page *mp, indx_t indx, 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_del0(MDB_cursor *mc, MDB_node *leaf); +static int mdb_cursor_sibling(MDB_cursor *mc, int move_right); +static int mdb_cursor_next(MDB_cursor *mc, MDB_val *key, MDB_val *data, MDB_cursor_op op); +static int mdb_cursor_prev(MDB_cursor *mc, MDB_val *key, MDB_val *data, MDB_cursor_op op); +static int mdb_cursor_set(MDB_cursor *mc, MDB_val *key, MDB_val *data, MDB_cursor_op op, + int *exactp); +static int mdb_cursor_first(MDB_cursor *mc, MDB_val *key, MDB_val *data); +static int mdb_cursor_last(MDB_cursor *mc, MDB_val *key, MDB_val *data); + +static void mdb_cursor_init(MDB_cursor *mc, MDB_txn *txn, MDB_dbi dbi, MDB_xcursor *mx); +static void mdb_xcursor_init0(MDB_cursor *mc); +static void mdb_xcursor_init1(MDB_cursor *mc, MDB_node *node); + +static int mdb_drop0(MDB_cursor *mc, int subs); +static void mdb_default_cmp(MDB_txn *txn, MDB_dbi dbi); + +/** @cond */ +static MDB_cmp_func mdb_cmp_memn, mdb_cmp_memnr, mdb_cmp_int, mdb_cmp_cint, mdb_cmp_long; +/** @endcond */ + +#ifdef _WIN32 +static SECURITY_DESCRIPTOR mdb_null_sd; +static SECURITY_ATTRIBUTES mdb_all_sa; +static int mdb_sec_inited; +#endif + +/** Return the library version info. */ +char * +mdb_version(int *major, int *minor, int *patch) +{ + if (major) *major = MDB_VERSION_MAJOR; + if (minor) *minor = MDB_VERSION_MINOR; + if (patch) *patch = MDB_VERSION_PATCH; + return MDB_VERSION_STRING; +} + +/** Table of descriptions for MDB @ref errors */ +static char *const mdb_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", + "MDB_INVALID: File is not an MDB file", + "MDB_MAP_FULL: Environment mapsize limit reached", + "MDB_DBS_FULL: Environment maxdbs limit reached", + "MDB_READERS_FULL: Environment maxreaders limit reached", + "MDB_TLS_FULL: Thread-local storage keys full - too many environments open", + "MDB_TXN_FULL: Nested transaction has too many dirty pages - transaction too big", + "MDB_CURSOR_FULL: Internal error - cursor stack limit reached", + "MDB_PAGE_FULL: Internal error - page has no more space" +}; + +char * +mdb_strerror(int err) +{ + int i; + if (!err) + return ("Successful return: 0"); + + if (err >= MDB_KEYEXIST && err <= MDB_LAST_ERRCODE) { + i = err - MDB_KEYEXIST; + return mdb_errstr[i]; + } + + return strerror(err); +} + +#if MDB_DEBUG +/** Display a key in hexadecimal and return the address of the result. + * @param[in] key the key to display + * @param[in] buf the buffer to write into. Should always be #DKBUF. + * @return The key in hexadecimal form. + */ +char * +mdb_dkey(MDB_val *key, char *buf) +{ + char *ptr = buf; + unsigned char *c = key->mv_data; + unsigned int i; + if (key->mv_size > MAXKEYSIZE) + return "MAXKEYSIZE"; + /* may want to make this a dynamic check: if the key is mostly + * printable characters, print it as-is instead of converting to hex. + */ +#if 1 + buf[0] = '\0'; + for (i=0; imv_size; i++) + ptr += sprintf(ptr, "%02x", *c++); +#else + sprintf(buf, "%.*s", key->mv_size, key->mv_data); +#endif + return buf; +} + +/** Display all the keys in the page. */ +static void +mdb_page_list(MDB_page *mp) +{ + MDB_node *node; + unsigned int i, nkeys, nsize; + MDB_val key; + DKBUF; + + nkeys = NUMKEYS(mp); + fprintf(stderr, "numkeys %d\n", nkeys); + for (i=0; imn_ksize; + key.mv_data = node->mn_data; + nsize = NODESIZE + NODEKSZ(node) + sizeof(indx_t); + if (F_ISSET(node->mn_flags, F_BIGDATA)) + nsize += sizeof(pgno_t); + else + nsize += NODEDSZ(node); + fprintf(stderr, "key %d: nsize %d, %s\n", i, nsize, DKEY(&key)); + } +} + +void +mdb_cursor_chk(MDB_cursor *mc) +{ + unsigned int i; + MDB_node *node; + MDB_page *mp; + + if (!mc->mc_snum && !(mc->mc_flags & C_INITIALIZED)) return; + for (i=0; imc_top; i++) { + mp = mc->mc_pg[i]; + node = NODEPTR(mp, mc->mc_ki[i]); + if (NODEPGNO(node) != mc->mc_pg[i+1]->mp_pgno) + printf("oops!\n"); + } + if (mc->mc_ki[i] >= NUMKEYS(mc->mc_pg[i])) + printf("ack!\n"); +} +#endif + +#if MDB_DEBUG > 2 +/** Count all the pages in each DB and in the freelist + * and make sure it matches the actual number of pages + * being used. + */ +static void mdb_audit(MDB_txn *txn) +{ + MDB_cursor mc; + MDB_val key, data; + MDB_ID freecount, count; + MDB_dbi i; + int rc; + + freecount = 0; + mdb_cursor_init(&mc, txn, FREE_DBI, NULL); + while ((rc = mdb_cursor_get(&mc, &key, &data, MDB_NEXT)) == 0) + freecount += *(MDB_ID *)data.mv_data; + + count = 0; + for (i = 0; imt_numdbs; i++) { + MDB_xcursor mx, *mxp; + mxp = (txn->mt_dbs[i].md_flags & MDB_DUPSORT) ? &mx : NULL; + mdb_cursor_init(&mc, txn, i, mxp); + if (txn->mt_dbs[i].md_root == P_INVALID) + continue; + count += txn->mt_dbs[i].md_branch_pages + + txn->mt_dbs[i].md_leaf_pages + + txn->mt_dbs[i].md_overflow_pages; + if (txn->mt_dbs[i].md_flags & MDB_DUPSORT) { + mdb_page_search(&mc, NULL, 0); + do { + unsigned j; + MDB_page *mp; + mp = mc.mc_pg[mc.mc_top]; + for (j=0; jmn_flags & F_SUBDATA) { + MDB_db db; + memcpy(&db, NODEDATA(leaf), sizeof(db)); + count += db.md_branch_pages + db.md_leaf_pages + + db.md_overflow_pages; + } + } + } + while (mdb_cursor_sibling(&mc, 1) == 0); + } + } + if (freecount + count + 2 /* metapages */ != txn->mt_next_pgno) { + fprintf(stderr, "audit: %lu freecount: %lu count: %lu total: %lu next_pgno: %lu\n", + txn->mt_txnid, freecount, count+2, freecount+count+2, txn->mt_next_pgno); + } +} +#endif + +int +mdb_cmp(MDB_txn *txn, MDB_dbi dbi, const MDB_val *a, const MDB_val *b) +{ + return txn->mt_dbxs[dbi].md_cmp(a, b); +} + +int +mdb_dcmp(MDB_txn *txn, MDB_dbi dbi, const MDB_val *a, const MDB_val *b) +{ + if (txn->mt_dbxs[dbi].md_dcmp) + return txn->mt_dbxs[dbi].md_dcmp(a, b); + else + return EINVAL; /* too bad you can't distinguish this from a valid result */ +} + +/** Allocate a single page. + * Re-use old malloc'd pages first, otherwise just malloc. + */ +static MDB_page * +mdb_page_malloc(MDB_cursor *mc) { + MDB_page *ret; + size_t sz = mc->mc_txn->mt_env->me_psize; + if ((ret = mc->mc_txn->mt_env->me_dpages) != NULL) { + VGMEMP_ALLOC(mc->mc_txn->mt_env, ret, sz); + VGMEMP_DEFINED(ret, sizeof(ret->mp_next)); + mc->mc_txn->mt_env->me_dpages = ret->mp_next; + } else if ((ret = malloc(sz)) != NULL) { + VGMEMP_ALLOC(mc->mc_txn->mt_env, ret, sz); + } + return ret; +} + +/** Allocate pages for writing. + * If there are free pages available from older transactions, they + * will be re-used first. Otherwise a new page will be allocated. + * @param[in] mc cursor A cursor handle identifying the transaction and + * database for which we are allocating. + * @param[in] num the number of pages to allocate. + * @param[out] mp Address of the allocated page(s). Requests for multiple pages + * will always be satisfied by a single contiguous chunk of memory. + * @return 0 on success, non-zero on failure. + */ +static int +mdb_page_alloc(MDB_cursor *mc, int num, MDB_page **mp) +{ + MDB_txn *txn = mc->mc_txn; + MDB_page *np; + pgno_t pgno = P_INVALID; + MDB_ID2 mid; + int rc; + + *mp = NULL; + /* The free list won't have any content at all until txn 2 has + * committed. The pages freed by txn 2 will be unreferenced + * after txn 3 commits, and so will be safe to re-use in txn 4. + */ + if (txn->mt_txnid > 3) { + + if (!txn->mt_env->me_pghead && + txn->mt_dbs[FREE_DBI].md_root != P_INVALID) { + /* See if there's anything in the free DB */ + int j; + MDB_reader *r; + MDB_cursor m2; + MDB_node *leaf; + MDB_val data; + txnid_t *kptr, last; + + mdb_cursor_init(&m2, txn, FREE_DBI, NULL); + if (!txn->mt_env->me_pgfirst) { + mdb_page_search(&m2, NULL, 0); + leaf = NODEPTR(m2.mc_pg[m2.mc_top], 0); + kptr = (txnid_t *)NODEKEY(leaf); + last = *kptr; + } else { + MDB_val key; + int exact; +again: + exact = 0; + last = txn->mt_env->me_pglast + 1; + leaf = NULL; + key.mv_data = &last; + key.mv_size = sizeof(last); + rc = mdb_cursor_set(&m2, &key, &data, MDB_SET, &exact); + if (rc) + goto none; + last = *(txnid_t *)key.mv_data; + } + + /* Unusable if referred by a meta page or reader... */ + j = 1; + if (last < txn->mt_txnid-1) { + j = txn->mt_env->me_txns->mti_numreaders; + r = txn->mt_env->me_txns->mti_readers + j; + for (j = -j; j && (lastmt_env->me_pgfirst) { + mdb_node_read(txn, leaf, &data); + } + txn->mt_env->me_pglast = last; + if (!txn->mt_env->me_pgfirst) + txn->mt_env->me_pgfirst = last; + idl = (MDB_ID *) data.mv_data; + /* We might have a zero-length IDL due to freelist growth + * during a prior commit + */ + if (!idl[0]) goto again; + mop = malloc(sizeof(MDB_oldpages) + MDB_IDL_SIZEOF(idl) - sizeof(pgno_t)); + if (!mop) + return ENOMEM; + mop->mo_next = txn->mt_env->me_pghead; + mop->mo_txnid = last; + txn->mt_env->me_pghead = mop; + memcpy(mop->mo_pages, idl, MDB_IDL_SIZEOF(idl)); + +#if MDB_DEBUG > 1 + { + unsigned int i; + DPRINTF("IDL read txn %zu root %zu num %zu", + mop->mo_txnid, txn->mt_dbs[FREE_DBI].md_root, idl[0]); + for (i=0; imt_env->me_pghead) { + MDB_oldpages *mop = txn->mt_env->me_pghead; + if (num > 1) { + /* FIXME: For now, always use fresh pages. We + * really ought to search the free list for a + * contiguous range. + */ + ; + } else { + /* peel pages off tail, so we only have to truncate the list */ + pgno = MDB_IDL_LAST(mop->mo_pages); + if (MDB_IDL_IS_RANGE(mop->mo_pages)) { + mop->mo_pages[2]++; + if (mop->mo_pages[2] > mop->mo_pages[1]) + mop->mo_pages[0] = 0; + } else { + mop->mo_pages[0]--; + } + if (MDB_IDL_IS_ZERO(mop->mo_pages)) { + txn->mt_env->me_pghead = mop->mo_next; + if (mc->mc_dbi == FREE_DBI) { + mop->mo_next = txn->mt_env->me_pgfree; + txn->mt_env->me_pgfree = mop; + } else { + free(mop); + } + } + } + } + } + + if (pgno == P_INVALID) { + /* DB size is maxed out */ + if (txn->mt_next_pgno + num >= txn->mt_env->me_maxpg) { + DPUTS("DB size maxed out"); + return MDB_MAP_FULL; + } + } + if (txn->mt_env->me_flags & MDB_WRITEMAP) { + if (pgno == P_INVALID) { + pgno = txn->mt_next_pgno; + txn->mt_next_pgno += num; + } + np = (MDB_page *)(txn->mt_env->me_map + txn->mt_env->me_psize * pgno); + np->mp_pgno = pgno; + } else { + if (txn->mt_env->me_dpages && num == 1) { + np = txn->mt_env->me_dpages; + VGMEMP_ALLOC(txn->mt_env, np, txn->mt_env->me_psize); + VGMEMP_DEFINED(np, sizeof(np->mp_next)); + txn->mt_env->me_dpages = np->mp_next; + } else { + size_t sz = txn->mt_env->me_psize * num; + if ((np = malloc(sz)) == NULL) + return ENOMEM; + VGMEMP_ALLOC(txn->mt_env, np, sz); + } + if (pgno == P_INVALID) { + np->mp_pgno = txn->mt_next_pgno; + txn->mt_next_pgno += num; + } else { + np->mp_pgno = pgno; + } + } + mid.mid = np->mp_pgno; + mid.mptr = np; + if (txn->mt_env->me_flags & MDB_WRITEMAP) { + mdb_mid2l_append(txn->mt_u.dirty_list, &mid); + } else { + mdb_mid2l_insert(txn->mt_u.dirty_list, &mid); + } + *mp = np; + + return MDB_SUCCESS; +} + +/** Copy a page: avoid copying unused portions of the page. + * @param[in] dst page to copy into + * @param[in] src page to copy from + */ +static void +mdb_page_copy(MDB_page *dst, MDB_page *src, unsigned int psize) +{ + dst->mp_flags = src->mp_flags | P_DIRTY; + dst->mp_pages = src->mp_pages; + + if (IS_LEAF2(src)) { + memcpy(dst->mp_ptrs, src->mp_ptrs, psize - PAGEHDRSZ - SIZELEFT(src)); + } else { + unsigned int i, nkeys = NUMKEYS(src); + for (i=0; imp_ptrs[i] = src->mp_ptrs[i]; + memcpy((char *)dst+src->mp_upper, (char *)src+src->mp_upper, + psize - src->mp_upper); + } +} + +/** Touch a page: make it dirty and re-insert into tree with updated pgno. + * @param[in] mc cursor pointing to the page to be touched + * @return 0 on success, non-zero on failure. + */ +static int +mdb_page_touch(MDB_cursor *mc) +{ + MDB_page *mp = mc->mc_pg[mc->mc_top]; + pgno_t pgno; + int rc; + + if (!F_ISSET(mp->mp_flags, P_DIRTY)) { + MDB_page *np; + if ((rc = mdb_page_alloc(mc, 1, &np))) + return rc; + DPRINTF("touched db %u page %zu -> %zu", mc->mc_dbi, mp->mp_pgno, np->mp_pgno); + assert(mp->mp_pgno != np->mp_pgno); + mdb_midl_append(&mc->mc_txn->mt_free_pgs, mp->mp_pgno); + if (SIZELEFT(mp)) { + /* If page isn't full, just copy the used portion */ + mdb_page_copy(np, mp, mc->mc_txn->mt_env->me_psize); + } else { + pgno = np->mp_pgno; + memcpy(np, mp, mc->mc_txn->mt_env->me_psize); + np->mp_pgno = pgno; + np->mp_flags |= P_DIRTY; + } + mp = np; + +finish: + /* Adjust other cursors pointing to mp */ + if (mc->mc_flags & C_SUB) { + MDB_cursor *m2, *m3; + MDB_dbi dbi = mc->mc_dbi-1; + + for (m2 = mc->mc_txn->mt_cursors[dbi]; m2; m2=m2->mc_next) { + if (m2 == mc) continue; + m3 = &m2->mc_xcursor->mx_cursor; + if (m3->mc_snum < mc->mc_snum) continue; + if (m3->mc_pg[mc->mc_top] == mc->mc_pg[mc->mc_top]) { + m3->mc_pg[mc->mc_top] = mp; + } + } + } else { + MDB_cursor *m2; + + for (m2 = mc->mc_txn->mt_cursors[mc->mc_dbi]; m2; m2=m2->mc_next) { + if (m2 == mc || m2->mc_snum < mc->mc_snum) continue; + if (m2->mc_pg[mc->mc_top] == mc->mc_pg[mc->mc_top]) { + m2->mc_pg[mc->mc_top] = mp; + } + } + } + mc->mc_pg[mc->mc_top] = mp; + /** If this page has a parent, update the parent to point to + * this new page. + */ + if (mc->mc_top) + SETPGNO(NODEPTR(mc->mc_pg[mc->mc_top-1], mc->mc_ki[mc->mc_top-1]), mp->mp_pgno); + else + mc->mc_db->md_root = mp->mp_pgno; + } else if (mc->mc_txn->mt_parent) { + MDB_page *np; + MDB_ID2 mid; + /* If txn has a parent, make sure the page is in our + * dirty list. + */ + if (mc->mc_txn->mt_u.dirty_list[0].mid) { + unsigned x = mdb_mid2l_search(mc->mc_txn->mt_u.dirty_list, mp->mp_pgno); + if (x <= mc->mc_txn->mt_u.dirty_list[0].mid && + mc->mc_txn->mt_u.dirty_list[x].mid == mp->mp_pgno) { + if (mc->mc_txn->mt_u.dirty_list[x].mptr != mp) { + mp = mc->mc_txn->mt_u.dirty_list[x].mptr; + mc->mc_pg[mc->mc_top] = mp; + } + return 0; + } + } + /* No - copy it */ + np = mdb_page_malloc(mc); + if (!np) + return ENOMEM; + memcpy(np, mp, mc->mc_txn->mt_env->me_psize); + mid.mid = np->mp_pgno; + mid.mptr = np; + mdb_mid2l_insert(mc->mc_txn->mt_u.dirty_list, &mid); + mp = np; + goto finish; + } + return 0; +} + +int +mdb_env_sync(MDB_env *env, int force) +{ + int rc = 0; + if (force || !F_ISSET(env->me_flags, MDB_NOSYNC)) { + if (env->me_flags & MDB_WRITEMAP) { + int flags = (env->me_flags & MDB_MAPASYNC) ? MS_ASYNC : MS_SYNC; + if (MDB_MSYNC(env->me_map, env->me_mapsize, flags)) + rc = ErrCode(); +#ifdef _WIN32 + else if (flags == MS_SYNC && MDB_FDATASYNC(env->me_fd)) + rc = ErrCode(); +#endif + } else { + if (MDB_FDATASYNC(env->me_fd)) + rc = ErrCode(); + } + } + return rc; +} + +/** Make shadow copies of all of parent txn's cursors */ +static int +mdb_cursor_shadow(MDB_txn *src, MDB_txn *dst) +{ + MDB_cursor *mc, *m2; + unsigned int i, j, size; + + for (i=0;imt_numdbs; i++) { + if (src->mt_cursors[i]) { + size = sizeof(MDB_cursor); + if (src->mt_cursors[i]->mc_xcursor) + size += sizeof(MDB_xcursor); + for (m2 = src->mt_cursors[i]; m2; m2=m2->mc_next) { + mc = malloc(size); + if (!mc) + return ENOMEM; + mc->mc_orig = m2; + mc->mc_txn = dst; + mc->mc_dbi = i; + mc->mc_db = &dst->mt_dbs[i]; + mc->mc_dbx = m2->mc_dbx; + mc->mc_dbflag = &dst->mt_dbflags[i]; + mc->mc_snum = m2->mc_snum; + mc->mc_top = m2->mc_top; + mc->mc_flags = m2->mc_flags | C_SHADOW; + for (j=0; jmc_snum; j++) { + mc->mc_pg[j] = m2->mc_pg[j]; + mc->mc_ki[j] = m2->mc_ki[j]; + } + if (m2->mc_xcursor) { + MDB_xcursor *mx, *mx2; + mx = (MDB_xcursor *)(mc+1); + mc->mc_xcursor = mx; + mx2 = m2->mc_xcursor; + mx->mx_db = mx2->mx_db; + mx->mx_dbx = mx2->mx_dbx; + mx->mx_dbflag = mx2->mx_dbflag; + mx->mx_cursor.mc_txn = dst; + mx->mx_cursor.mc_dbi = mx2->mx_cursor.mc_dbi; + mx->mx_cursor.mc_db = &mx->mx_db; + mx->mx_cursor.mc_dbx = &mx->mx_dbx; + mx->mx_cursor.mc_dbflag = &mx->mx_dbflag; + mx->mx_cursor.mc_snum = mx2->mx_cursor.mc_snum; + mx->mx_cursor.mc_top = mx2->mx_cursor.mc_top; + mx->mx_cursor.mc_flags = mx2->mx_cursor.mc_flags | C_SHADOW; + for (j=0; jmx_cursor.mc_snum; j++) { + mx->mx_cursor.mc_pg[j] = mx2->mx_cursor.mc_pg[j]; + mx->mx_cursor.mc_ki[j] = mx2->mx_cursor.mc_ki[j]; + } + } else { + mc->mc_xcursor = NULL; + } + mc->mc_next = dst->mt_cursors[i]; + dst->mt_cursors[i] = mc; + } + } + } + return MDB_SUCCESS; +} + +/** Merge shadow cursors back into parent's */ +static void +mdb_cursor_merge(MDB_txn *txn) +{ + MDB_dbi i; + for (i=0; imt_numdbs; i++) { + if (txn->mt_cursors[i]) { + MDB_cursor *mc; + while ((mc = txn->mt_cursors[i])) { + txn->mt_cursors[i] = mc->mc_next; + if (mc->mc_flags & C_SHADOW) { + MDB_cursor *m2 = mc->mc_orig; + unsigned int j; + m2->mc_snum = mc->mc_snum; + m2->mc_top = mc->mc_top; + for (j=0; jmc_snum; j++) { + m2->mc_pg[j] = mc->mc_pg[j]; + m2->mc_ki[j] = mc->mc_ki[j]; + } + } + if (mc->mc_flags & C_ALLOCD) + free(mc); + } + } + } +} + +static void +mdb_txn_reset0(MDB_txn *txn); + +/** Common code for #mdb_txn_begin() and #mdb_txn_renew(). + * @param[in] txn the transaction handle to initialize + * @return 0 on success, non-zero on failure. This can only + * fail for read-only transactions, and then only if the + * reader table is full. + */ +static int +mdb_txn_renew0(MDB_txn *txn) +{ + MDB_env *env = txn->mt_env; + unsigned int i; + + /* Setup db info */ + txn->mt_numdbs = env->me_numdbs; + txn->mt_dbxs = env->me_dbxs; /* mostly static anyway */ + + if (txn->mt_flags & MDB_TXN_RDONLY) { + MDB_reader *r = pthread_getspecific(env->me_txkey); + if (!r) { + pid_t pid = env->me_pid; + pthread_t tid = pthread_self(); + + LOCK_MUTEX_R(env); + for (i=0; ime_txns->mti_numreaders; i++) + if (env->me_txns->mti_readers[i].mr_pid == 0) + break; + if (i == env->me_maxreaders) { + UNLOCK_MUTEX_R(env); + return MDB_READERS_FULL; + } + env->me_txns->mti_readers[i].mr_pid = pid; + env->me_txns->mti_readers[i].mr_tid = tid; + if (i >= env->me_txns->mti_numreaders) + env->me_txns->mti_numreaders = i+1; + /* Save numreaders for un-mutexed mdb_env_close() */ + env->me_numreaders = env->me_txns->mti_numreaders; + UNLOCK_MUTEX_R(env); + r = &env->me_txns->mti_readers[i]; + pthread_setspecific(env->me_txkey, r); + } + txn->mt_txnid = r->mr_txnid = env->me_txns->mti_txnid; + txn->mt_toggle = txn->mt_txnid & 1; + txn->mt_next_pgno = env->me_metas[txn->mt_toggle]->mm_last_pg+1; + txn->mt_u.reader = r; + } else { + LOCK_MUTEX_W(env); + + txn->mt_txnid = env->me_txns->mti_txnid; + txn->mt_toggle = txn->mt_txnid & 1; + txn->mt_next_pgno = env->me_metas[txn->mt_toggle]->mm_last_pg+1; + txn->mt_txnid++; +#if MDB_DEBUG + if (txn->mt_txnid == mdb_debug_start) + mdb_debug = 1; +#endif + 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; + env->me_txn = txn; + } + + /* Copy the DB info and flags */ + memcpy(txn->mt_dbs, env->me_metas[txn->mt_toggle]->mm_dbs, 2 * sizeof(MDB_db)); + for (i=2; imt_numdbs; i++) + txn->mt_dbs[i].md_flags = env->me_dbflags[i]; + txn->mt_dbflags[0] = txn->mt_dbflags[1] = 0; + if (txn->mt_numdbs > 2) + memset(txn->mt_dbflags+2, DB_STALE, txn->mt_numdbs-2); + + return MDB_SUCCESS; +} + +int +mdb_txn_renew(MDB_txn *txn) +{ + int rc; + + if (! (txn && txn->mt_flags & MDB_TXN_RDONLY)) + return EINVAL; + + if (txn->mt_env->me_flags & MDB_FATAL_ERROR) { + DPUTS("environment had fatal error, must shutdown!"); + return MDB_PANIC; + } + + rc = mdb_txn_renew0(txn); + if (rc == MDB_SUCCESS) { + DPRINTF("renew txn %zu%c %p on mdbenv %p, root page %zu", + txn->mt_txnid, (txn->mt_flags & MDB_TXN_RDONLY) ? 'r' : 'w', + (void *)txn, (void *)txn->mt_env, txn->mt_dbs[MAIN_DBI].md_root); + } + return rc; +} + +int +mdb_txn_begin(MDB_env *env, MDB_txn *parent, unsigned int flags, MDB_txn **ret) +{ + MDB_txn *txn; + int rc, size; + + if (env->me_flags & MDB_FATAL_ERROR) { + DPUTS("environment had fatal error, must shutdown!"); + return MDB_PANIC; + } + if ((env->me_flags & MDB_RDONLY) && !(flags & MDB_RDONLY)) + return EACCES; + if (parent) { + /* Nested transactions: Max 1 child, write txns only, no writemap */ + if (parent->mt_child || + (flags & MDB_RDONLY) || (parent->mt_flags & MDB_TXN_RDONLY) || + (env->me_flags & MDB_WRITEMAP)) + { + return EINVAL; + } + } + size = sizeof(MDB_txn) + env->me_maxdbs * (sizeof(MDB_db)+1); + if (!(flags & MDB_RDONLY)) + size += env->me_maxdbs * sizeof(MDB_cursor *); + + if ((txn = calloc(1, size)) == NULL) { + DPRINTF("calloc: %s", strerror(ErrCode())); + return ENOMEM; + } + txn->mt_dbs = (MDB_db *)(txn+1); + if (flags & MDB_RDONLY) { + txn->mt_flags |= MDB_TXN_RDONLY; + txn->mt_dbflags = (unsigned char *)(txn->mt_dbs + env->me_maxdbs); + } else { + txn->mt_cursors = (MDB_cursor **)(txn->mt_dbs + env->me_maxdbs); + txn->mt_dbflags = (unsigned char *)(txn->mt_cursors + env->me_maxdbs); + } + txn->mt_env = env; + + if (parent) { + txn->mt_free_pgs = mdb_midl_alloc(); + if (!txn->mt_free_pgs) { + free(txn); + return ENOMEM; + } + txn->mt_u.dirty_list = malloc(sizeof(MDB_ID2)*MDB_IDL_UM_SIZE); + if (!txn->mt_u.dirty_list) { + free(txn->mt_free_pgs); + free(txn); + return ENOMEM; + } + txn->mt_txnid = parent->mt_txnid; + txn->mt_toggle = parent->mt_toggle; + txn->mt_u.dirty_list[0].mid = 0; + txn->mt_free_pgs[0] = 0; + txn->mt_next_pgno = parent->mt_next_pgno; + parent->mt_child = txn; + txn->mt_parent = parent; + txn->mt_numdbs = parent->mt_numdbs; + txn->mt_dbxs = parent->mt_dbxs; + memcpy(txn->mt_dbs, parent->mt_dbs, txn->mt_numdbs * sizeof(MDB_db)); + memcpy(txn->mt_dbflags, parent->mt_dbflags, txn->mt_numdbs); + mdb_cursor_shadow(parent, txn); + rc = 0; + } else { + rc = mdb_txn_renew0(txn); + } + if (rc) + free(txn); + else { + *ret = txn; + DPRINTF("begin txn %zu%c %p on mdbenv %p, root page %zu", + txn->mt_txnid, (txn->mt_flags & MDB_TXN_RDONLY) ? 'r' : 'w', + (void *) txn, (void *) env, txn->mt_dbs[MAIN_DBI].md_root); + } + + return rc; +} + +/** Common code for #mdb_txn_reset() and #mdb_txn_abort(). + * @param[in] txn the transaction handle to reset + */ +static void +mdb_txn_reset0(MDB_txn *txn) +{ + MDB_env *env = txn->mt_env; + + if (F_ISSET(txn->mt_flags, MDB_TXN_RDONLY)) { + txn->mt_u.reader->mr_txnid = (txnid_t)-1; + } else { + MDB_oldpages *mop; + MDB_page *dp; + unsigned int i; + + /* close(free) all cursors */ + for (i=0; imt_numdbs; i++) { + if (txn->mt_cursors[i]) { + MDB_cursor *mc; + while ((mc = txn->mt_cursors[i])) { + txn->mt_cursors[i] = mc->mc_next; + if (mc->mc_flags & C_ALLOCD) + free(mc); + } + } + } + + if (!(env->me_flags & MDB_WRITEMAP)) { + /* return all dirty pages to dpage list */ + for (i=1; i<=txn->mt_u.dirty_list[0].mid; i++) { + dp = txn->mt_u.dirty_list[i].mptr; + if (!IS_OVERFLOW(dp) || dp->mp_pages == 1) { + dp->mp_next = txn->mt_env->me_dpages; + VGMEMP_FREE(txn->mt_env, dp); + txn->mt_env->me_dpages = dp; + } else { + /* large pages just get freed directly */ + VGMEMP_FREE(txn->mt_env, dp); + free(dp); + } + } + } + + if (txn->mt_parent) { + txn->mt_parent->mt_child = NULL; + mdb_midl_free(txn->mt_free_pgs); + free(txn->mt_u.dirty_list); + return; + } else { + if (mdb_midl_shrink(&txn->mt_free_pgs)) + env->me_free_pgs = txn->mt_free_pgs; + } + + while ((mop = txn->mt_env->me_pghead)) { + txn->mt_env->me_pghead = mop->mo_next; + free(mop); + } + txn->mt_env->me_pgfirst = 0; + txn->mt_env->me_pglast = 0; + + env->me_txn = NULL; + /* The writer mutex was locked in mdb_txn_begin. */ + UNLOCK_MUTEX_W(env); + } +} + +void +mdb_txn_reset(MDB_txn *txn) +{ + if (txn == NULL) + return; + + DPRINTF("reset txn %zu%c %p on mdbenv %p, root page %zu", + txn->mt_txnid, (txn->mt_flags & MDB_TXN_RDONLY) ? 'r' : 'w', + (void *) txn, (void *)txn->mt_env, txn->mt_dbs[MAIN_DBI].md_root); + + mdb_txn_reset0(txn); +} + +void +mdb_txn_abort(MDB_txn *txn) +{ + if (txn == NULL) + return; + + DPRINTF("abort txn %zu%c %p on mdbenv %p, root page %zu", + txn->mt_txnid, (txn->mt_flags & MDB_TXN_RDONLY) ? 'r' : 'w', + (void *)txn, (void *)txn->mt_env, txn->mt_dbs[MAIN_DBI].md_root); + + if (txn->mt_child) + mdb_txn_abort(txn->mt_child); + + mdb_txn_reset0(txn); + free(txn); +} + +int +mdb_txn_commit(MDB_txn *txn) +{ + int n, done; + unsigned int i; + ssize_t rc; + off_t size; + MDB_page *dp; + MDB_env *env; + pgno_t next, freecnt; + MDB_cursor mc; + + assert(txn != NULL); + assert(txn->mt_env != NULL); + + if (txn->mt_child) { + mdb_txn_commit(txn->mt_child); + txn->mt_child = NULL; + } + + env = txn->mt_env; + + if (F_ISSET(txn->mt_flags, MDB_TXN_RDONLY)) { + if (txn->mt_numdbs > env->me_numdbs) { + /* update the DB flags */ + MDB_dbi i; + for (i = env->me_numdbs; imt_numdbs; i++) + env->me_dbflags[i] = txn->mt_dbs[i].md_flags; + env->me_numdbs = i; + } + mdb_txn_abort(txn); + return MDB_SUCCESS; + } + + if (F_ISSET(txn->mt_flags, MDB_TXN_ERROR)) { + DPUTS("error flag is set, can't commit"); + if (txn->mt_parent) + txn->mt_parent->mt_flags |= MDB_TXN_ERROR; + mdb_txn_abort(txn); + return EINVAL; + } + + /* Merge (and close) our cursors with parent's */ + mdb_cursor_merge(txn); + + if (txn->mt_parent) { + MDB_db *ip, *jp; + MDB_dbi i; + unsigned x, y; + MDB_ID2L dst, src; + + /* Update parent's DB table */ + ip = &txn->mt_parent->mt_dbs[2]; + jp = &txn->mt_dbs[2]; + for (i = 2; i < txn->mt_numdbs; i++) { + if (ip->md_root != jp->md_root) + *ip = *jp; + ip++; jp++; + } + txn->mt_parent->mt_numdbs = txn->mt_numdbs; + + /* Append our free list to parent's */ + mdb_midl_append_list(&txn->mt_parent->mt_free_pgs, + txn->mt_free_pgs); + mdb_midl_free(txn->mt_free_pgs); + + /* Merge our dirty list with parent's */ + dst = txn->mt_parent->mt_u.dirty_list; + src = txn->mt_u.dirty_list; + x = mdb_mid2l_search(dst, src[1].mid); + for (y=1; y<=src[0].mid; y++) { + while (x <= dst[0].mid && dst[x].mid != src[y].mid) x++; + if (x > dst[0].mid) + break; + free(dst[x].mptr); + dst[x].mptr = src[y].mptr; + } + x = dst[0].mid; + for (; y<=src[0].mid; y++) { + if (++x >= MDB_IDL_UM_MAX) { + mdb_txn_abort(txn); + return MDB_TXN_FULL; + } + dst[x] = src[y]; + } + dst[0].mid = x; + free(txn->mt_u.dirty_list); + txn->mt_parent->mt_child = NULL; + free(txn); + return MDB_SUCCESS; + } + + if (txn != env->me_txn) { + DPUTS("attempt to commit unknown transaction"); + mdb_txn_abort(txn); + return EINVAL; + } + + if (!txn->mt_u.dirty_list[0].mid) + goto done; + + DPRINTF("committing txn %zu %p on mdbenv %p, root page %zu", + txn->mt_txnid, (void *)txn, (void *)env, txn->mt_dbs[MAIN_DBI].md_root); + + /* Update DB root pointers. Their pages have already been + * touched so this is all in-place and cannot fail. + */ + if (txn->mt_numdbs > 2) { + MDB_dbi i; + MDB_val data; + data.mv_size = sizeof(MDB_db); + + mdb_cursor_init(&mc, txn, MAIN_DBI, NULL); + for (i = 2; i < txn->mt_numdbs; i++) { + if (txn->mt_dbflags[i] & DB_DIRTY) { + data.mv_data = &txn->mt_dbs[i]; + mdb_cursor_put(&mc, &txn->mt_dbxs[i].md_name, &data, 0); + } + } + } + + mdb_cursor_init(&mc, txn, FREE_DBI, NULL); + + /* should only be one record now */ + if (env->me_pghead) { + /* make sure first page of freeDB is touched and on freelist */ + mdb_page_search(&mc, NULL, MDB_PS_MODIFY); + } + + /* Delete IDLs we used from the free list */ + if (env->me_pgfirst) { + txnid_t cur; + MDB_val key; + int exact = 0; + + key.mv_size = sizeof(cur); + for (cur = env->me_pgfirst; cur <= env->me_pglast; cur++) { + key.mv_data = &cur; + + mdb_cursor_set(&mc, &key, NULL, MDB_SET, &exact); + rc = mdb_cursor_del(&mc, 0); + if (rc) { + mdb_txn_abort(txn); + return rc; + } + } + env->me_pgfirst = 0; + env->me_pglast = 0; + } + + /* save to free list */ +free2: + freecnt = txn->mt_free_pgs[0]; + if (!MDB_IDL_IS_ZERO(txn->mt_free_pgs)) { + MDB_val key, data; + + /* make sure last page of freeDB is touched and on freelist */ + key.mv_size = MAXKEYSIZE+1; + key.mv_data = NULL; + mdb_page_search(&mc, &key, MDB_PS_MODIFY); + + mdb_midl_sort(txn->mt_free_pgs); +#if MDB_DEBUG > 1 + { + unsigned int i; + MDB_IDL idl = txn->mt_free_pgs; + DPRINTF("IDL write txn %zu root %zu num %zu", + txn->mt_txnid, txn->mt_dbs[FREE_DBI].md_root, idl[0]); + for (i=0; imt_txnid; + data.mv_data = txn->mt_free_pgs; + /* The free list can still grow during this call, + * despite the pre-emptive touches above. So check + * and make sure the entire thing got written. + */ + do { + freecnt = txn->mt_free_pgs[0]; + data.mv_size = MDB_IDL_SIZEOF(txn->mt_free_pgs); + rc = mdb_cursor_put(&mc, &key, &data, 0); + if (rc) { + mdb_txn_abort(txn); + return rc; + } + } while (freecnt != txn->mt_free_pgs[0]); + } + /* should only be one record now */ +again: + if (env->me_pghead) { + MDB_val key, data; + MDB_oldpages *mop; + pgno_t orig; + txnid_t id; + + mop = env->me_pghead; + id = mop->mo_txnid; + key.mv_size = sizeof(id); + key.mv_data = &id; + data.mv_size = MDB_IDL_SIZEOF(mop->mo_pages); + data.mv_data = mop->mo_pages; + orig = mop->mo_pages[0]; + /* These steps may grow the freelist again + * due to freed overflow pages... + */ + mdb_cursor_put(&mc, &key, &data, 0); + if (mop == env->me_pghead && env->me_pghead->mo_txnid == id) { + /* could have been used again here */ + if (mop->mo_pages[0] != orig) { + data.mv_size = MDB_IDL_SIZEOF(mop->mo_pages); + data.mv_data = mop->mo_pages; + id = mop->mo_txnid; + mdb_cursor_put(&mc, &key, &data, 0); + } + env->me_pghead = NULL; + free(mop); + } else { + /* was completely used up */ + mdb_cursor_del(&mc, 0); + if (env->me_pghead) + goto again; + } + env->me_pgfirst = 0; + env->me_pglast = 0; + } + + while (env->me_pgfree) { + MDB_oldpages *mop = env->me_pgfree; + env->me_pgfree = mop->mo_next; + free(mop);; + } + + /* Check for growth of freelist again */ + if (freecnt != txn->mt_free_pgs[0]) + goto free2; + + if (!MDB_IDL_IS_ZERO(txn->mt_free_pgs)) { + if (mdb_midl_shrink(&txn->mt_free_pgs)) + env->me_free_pgs = txn->mt_free_pgs; + } + +#if MDB_DEBUG > 2 + mdb_audit(txn); +#endif + + if (env->me_flags & MDB_WRITEMAP) { + for (i=1; i<=txn->mt_u.dirty_list[0].mid; i++) { + dp = txn->mt_u.dirty_list[i].mptr; + /* clear dirty flag */ + dp->mp_flags &= ~P_DIRTY; + txn->mt_u.dirty_list[i].mid = 0; + } + txn->mt_u.dirty_list[0].mid = 0; + goto sync; + } + + /* Commit up to MDB_COMMIT_PAGES dirty pages to disk until done. + */ + next = 0; + i = 1; + do { +#ifdef _WIN32 + /* Windows actually supports scatter/gather I/O, but only on + * unbuffered file handles. Since we're relying on the OS page + * cache for all our data, that's self-defeating. So we just + * write pages one at a time. We use the ov structure to set + * the write offset, to at least save the overhead of a Seek + * system call. + */ + OVERLAPPED ov; + memset(&ov, 0, sizeof(ov)); + for (; i<=txn->mt_u.dirty_list[0].mid; i++) { + size_t wsize; + dp = txn->mt_u.dirty_list[i].mptr; + DPRINTF("committing page %zu", dp->mp_pgno); + size = dp->mp_pgno * env->me_psize; + ov.Offset = size & 0xffffffff; + ov.OffsetHigh = size >> 16; + ov.OffsetHigh >>= 16; + /* clear dirty flag */ + dp->mp_flags &= ~P_DIRTY; + wsize = env->me_psize; + if (IS_OVERFLOW(dp)) wsize *= dp->mp_pages; + rc = WriteFile(env->me_fd, dp, wsize, NULL, &ov); + if (!rc) { + n = ErrCode(); + DPRINTF("WriteFile: %d", n); + mdb_txn_abort(txn); + return n; + } + } + done = 1; +#else + struct iovec iov[MDB_COMMIT_PAGES]; + n = 0; + done = 1; + size = 0; + for (; i<=txn->mt_u.dirty_list[0].mid; i++) { + dp = txn->mt_u.dirty_list[i].mptr; + if (dp->mp_pgno != next) { + if (n) { + rc = writev(env->me_fd, iov, n); + if (rc != size) { + n = ErrCode(); + if (rc > 0) + DPUTS("short write, filesystem full?"); + else + DPRINTF("writev: %s", strerror(n)); + mdb_txn_abort(txn); + return n; + } + n = 0; + size = 0; + } + lseek(env->me_fd, dp->mp_pgno * env->me_psize, SEEK_SET); + next = dp->mp_pgno; + } + DPRINTF("committing page %zu", dp->mp_pgno); + iov[n].iov_len = env->me_psize; + if (IS_OVERFLOW(dp)) iov[n].iov_len *= dp->mp_pages; + iov[n].iov_base = (char *)dp; + size += iov[n].iov_len; + next = dp->mp_pgno + (IS_OVERFLOW(dp) ? dp->mp_pages : 1); + /* clear dirty flag */ + dp->mp_flags &= ~P_DIRTY; + if (++n >= MDB_COMMIT_PAGES) { + done = 0; + i++; + break; + } + } + + if (n == 0) + break; + + rc = writev(env->me_fd, iov, n); + if (rc != size) { + n = ErrCode(); + if (rc > 0) + DPUTS("short write, filesystem full?"); + else + DPRINTF("writev: %s", strerror(n)); + mdb_txn_abort(txn); + return n; + } +#endif + } while (!done); + + /* Drop the dirty pages. + */ + for (i=1; i<=txn->mt_u.dirty_list[0].mid; i++) { + dp = txn->mt_u.dirty_list[i].mptr; + if (!IS_OVERFLOW(dp) || dp->mp_pages == 1) { + dp->mp_next = txn->mt_env->me_dpages; + VGMEMP_FREE(txn->mt_env, dp); + txn->mt_env->me_dpages = dp; + } else { + VGMEMP_FREE(txn->mt_env, dp); + free(dp); + } + txn->mt_u.dirty_list[i].mid = 0; + } + txn->mt_u.dirty_list[0].mid = 0; + +sync: + if ((n = mdb_env_sync(env, 0)) != 0 || + (n = mdb_env_write_meta(txn)) != MDB_SUCCESS) { + mdb_txn_abort(txn); + return n; + } + +done: + env->me_txn = NULL; + if (txn->mt_numdbs > env->me_numdbs) { + /* update the DB flags */ + MDB_dbi i; + for (i = env->me_numdbs; imt_numdbs; i++) + env->me_dbflags[i] = txn->mt_dbs[i].md_flags; + env->me_numdbs = i; + } + + UNLOCK_MUTEX_W(env); + free(txn); + + return MDB_SUCCESS; +} + +/** Read the environment parameters of a DB environment before + * mapping it into memory. + * @param[in] env the environment handle + * @param[out] meta address of where to store the meta information + * @return 0 on success, non-zero on failure. + */ +static int +mdb_env_read_header(MDB_env *env, MDB_meta *meta) +{ + MDB_pagebuf pbuf; + MDB_page *p; + MDB_meta *m; + int rc, err; + + /* We don't know the page size yet, so use a minimum value. + */ + +#ifdef _WIN32 + if (!ReadFile(env->me_fd, &pbuf, MDB_PAGESIZE, (DWORD *)&rc, NULL) || rc == 0) +#else + if ((rc = read(env->me_fd, &pbuf, MDB_PAGESIZE)) == 0) +#endif + { + return ENOENT; + } + else if (rc != MDB_PAGESIZE) { + err = ErrCode(); + if (rc > 0) + err = MDB_INVALID; + DPRINTF("read: %s", strerror(err)); + return err; + } + + p = (MDB_page *)&pbuf; + + if (!F_ISSET(p->mp_flags, P_META)) { + DPRINTF("page %zu not a meta page", p->mp_pgno); + return MDB_INVALID; + } + + m = METADATA(p); + if (m->mm_magic != MDB_MAGIC) { + DPUTS("meta has invalid magic"); + return MDB_INVALID; + } + + if (m->mm_version != MDB_VERSION) { + DPRINTF("database is version %u, expected version %u", + m->mm_version, MDB_VERSION); + return MDB_VERSION_MISMATCH; + } + + memcpy(meta, m, sizeof(*m)); + return 0; +} + +/** Write the environment parameters of a freshly created DB environment. + * @param[in] env the environment handle + * @param[out] meta address of where to store the meta information + * @return 0 on success, non-zero on failure. + */ +static int +mdb_env_init_meta(MDB_env *env, MDB_meta *meta) +{ + MDB_page *p, *q; + MDB_meta *m; + int rc; + unsigned int psize; + + DPUTS("writing new meta page"); + + GET_PAGESIZE(psize); + + meta->mm_magic = MDB_MAGIC; + meta->mm_version = MDB_VERSION; + meta->mm_psize = psize; + meta->mm_last_pg = 1; + meta->mm_flags = env->me_flags & 0xffff; + meta->mm_flags |= MDB_INTEGERKEY; + meta->mm_dbs[0].md_root = P_INVALID; + meta->mm_dbs[1].md_root = P_INVALID; + + p = calloc(2, psize); + p->mp_pgno = 0; + p->mp_flags = P_META; + + m = METADATA(p); + memcpy(m, meta, sizeof(*meta)); + + q = (MDB_page *)((char *)p + psize); + + q->mp_pgno = 1; + q->mp_flags = P_META; + + m = METADATA(q); + memcpy(m, meta, sizeof(*meta)); + +#ifdef _WIN32 + { + DWORD len; + rc = WriteFile(env->me_fd, p, psize * 2, &len, NULL); + rc = (len == psize * 2) ? MDB_SUCCESS : ErrCode(); + } +#else + rc = write(env->me_fd, p, psize * 2); + rc = (rc == (int)psize * 2) ? MDB_SUCCESS : ErrCode(); +#endif + free(p); + return rc; +} + +/** Update the environment info to commit a transaction. + * @param[in] txn the transaction that's being committed + * @return 0 on success, non-zero on failure. + */ +static int +mdb_env_write_meta(MDB_txn *txn) +{ + MDB_env *env; + MDB_meta meta, metab; + off_t off; + int rc, len, toggle; + char *ptr; +#ifdef _WIN32 + OVERLAPPED ov; +#endif + + assert(txn != NULL); + assert(txn->mt_env != NULL); + + toggle = !txn->mt_toggle; + DPRINTF("writing meta page %d for root page %zu", + toggle, txn->mt_dbs[MAIN_DBI].md_root); + + env = txn->mt_env; + + if (env->me_flags & MDB_WRITEMAP) { + MDB_meta *mp = env->me_metas[toggle]; + mp->mm_dbs[0] = txn->mt_dbs[0]; + mp->mm_dbs[1] = txn->mt_dbs[1]; + mp->mm_last_pg = txn->mt_next_pgno - 1; + mp->mm_txnid = txn->mt_txnid; + if (!(env->me_flags & (MDB_NOMETASYNC|MDB_NOSYNC))) { + rc = (env->me_flags & MDB_MAPASYNC) ? MS_ASYNC : MS_SYNC; + ptr = env->me_map; + if (toggle) + ptr += env->me_psize; + if (MDB_MSYNC(ptr, env->me_psize, rc)) { + rc = ErrCode(); + goto fail; + } + } + goto done; + } + 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; + + ptr += off; + meta.mm_dbs[0] = txn->mt_dbs[0]; + meta.mm_dbs[1] = txn->mt_dbs[1]; + meta.mm_last_pg = txn->mt_next_pgno - 1; + meta.mm_txnid = txn->mt_txnid; + + if (toggle) + off += env->me_psize; + off += PAGEHDRSZ; + + /* Write to the SYNC fd */ +#ifdef _WIN32 + { + memset(&ov, 0, sizeof(ov)); + ov.Offset = off; + WriteFile(env->me_mfd, ptr, len, (DWORD *)&rc, &ov); + } +#else + rc = pwrite(env->me_mfd, ptr, len, off); +#endif + if (rc != len) { + int r2; + rc = ErrCode(); + DPUTS("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; +#ifdef _WIN32 + WriteFile(env->me_fd, ptr, len, NULL, &ov); +#else + r2 = pwrite(env->me_fd, ptr, len, off); +#endif +fail: + env->me_flags |= MDB_FATAL_ERROR; + return rc; + } +done: + /* Memory ordering issues are irrelevant; since the entire writer + * is wrapped by wmutex, all of these changes will become visible + * after the wmutex is unlocked. Since the DB is multi-version, + * readers will get consistent data regardless of how fresh or + * how stale their view of these values is. + */ + txn->mt_env->me_txns->mti_txnid = txn->mt_txnid; + + return MDB_SUCCESS; +} + +/** Check both meta pages to see which one is newer. + * @param[in] env the environment handle + * @return meta toggle (0 or 1). + */ +static int +mdb_env_pick_meta(const MDB_env *env) +{ + return (env->me_metas[0]->mm_txnid < env->me_metas[1]->mm_txnid); +} + +int +mdb_env_create(MDB_env **env) +{ + MDB_env *e; + + e = calloc(1, sizeof(MDB_env)); + if (!e) + return ENOMEM; + + e->me_free_pgs = mdb_midl_alloc(); + if (!e->me_free_pgs) { + free(e); + return ENOMEM; + } + e->me_maxreaders = DEFAULT_READERS; + e->me_maxdbs = 2; + e->me_fd = INVALID_HANDLE_VALUE; + e->me_lfd = INVALID_HANDLE_VALUE; + e->me_mfd = INVALID_HANDLE_VALUE; +#ifdef MDB_USE_POSIX_SEM + e->me_rmutex = SEM_FAILED; + e->me_wmutex = SEM_FAILED; +#endif + e->me_pid = getpid(); + VGMEMP_CREATE(e,0,0); + *env = e; + return MDB_SUCCESS; +} + +int +mdb_env_set_mapsize(MDB_env *env, size_t size) +{ + if (env->me_map) + return EINVAL; + env->me_mapsize = size; + if (env->me_psize) + env->me_maxpg = env->me_mapsize / env->me_psize; + return MDB_SUCCESS; +} + +int +mdb_env_set_maxdbs(MDB_env *env, MDB_dbi dbs) +{ + if (env->me_map) + return EINVAL; + env->me_maxdbs = dbs; + return MDB_SUCCESS; +} + +int +mdb_env_set_maxreaders(MDB_env *env, unsigned int readers) +{ + if (env->me_map || readers < 1) + return EINVAL; + env->me_maxreaders = readers; + return MDB_SUCCESS; +} + +int +mdb_env_get_maxreaders(MDB_env *env, unsigned int *readers) +{ + if (!env || !readers) + return EINVAL; + *readers = env->me_maxreaders; + return MDB_SUCCESS; +} + +/** Further setup required for opening an MDB environment + */ +static int +mdb_env_open2(MDB_env *env, unsigned int flags) +{ + int i, newenv = 0, prot; + MDB_meta meta; + MDB_page *p; + + env->me_flags = flags; + + memset(&meta, 0, sizeof(meta)); + + if ((i = mdb_env_read_header(env, &meta)) != 0) { + if (i != ENOENT) + return i; + DPUTS("new mdbenv"); + newenv = 1; + } + + if (!env->me_mapsize) { + env->me_mapsize = newenv ? DEFAULT_MAPSIZE : meta.mm_mapsize; + } + +#ifdef _WIN32 + { + HANDLE mh; + LONG sizelo, sizehi; + sizelo = env->me_mapsize & 0xffffffff; + sizehi = env->me_mapsize >> 16; /* pointless on WIN32, only needed on W64 */ + sizehi >>= 16; + /* Windows won't create mappings for zero length files. + * Just allocate the maxsize right now. + */ + if (newenv) { + SetFilePointer(env->me_fd, sizelo, sizehi ? &sizehi : NULL, 0); + if (!SetEndOfFile(env->me_fd)) + return ErrCode(); + SetFilePointer(env->me_fd, 0, NULL, 0); + } + mh = CreateFileMapping(env->me_fd, NULL, flags & MDB_WRITEMAP ? + PAGE_READWRITE : PAGE_READONLY, + sizehi, sizelo, NULL); + if (!mh) + return ErrCode(); + env->me_map = MapViewOfFileEx(mh, flags & MDB_WRITEMAP ? + FILE_MAP_WRITE : FILE_MAP_READ, + 0, 0, env->me_mapsize, meta.mm_address); + CloseHandle(mh); + if (!env->me_map) + return ErrCode(); + } +#else + i = MAP_SHARED; + if (meta.mm_address && (flags & MDB_FIXEDMAP)) + i |= MAP_FIXED; + prot = PROT_READ; + if (flags & MDB_WRITEMAP) { + prot |= PROT_WRITE; + ftruncate(env->me_fd, env->me_mapsize); + } + env->me_map = mmap(meta.mm_address, env->me_mapsize, prot, i, + env->me_fd, 0); + if (env->me_map == MAP_FAILED) { + env->me_map = NULL; + return ErrCode(); + } +#endif + + if (newenv) { + meta.mm_mapsize = env->me_mapsize; + if (flags & MDB_FIXEDMAP) + meta.mm_address = env->me_map; + i = mdb_env_init_meta(env, &meta); + if (i != MDB_SUCCESS) { + return i; + } + } + env->me_psize = meta.mm_psize; + + 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 MDB_DEBUG + { + int toggle = mdb_env_pick_meta(env); + MDB_db *db = &env->me_metas[toggle]->mm_dbs[MAIN_DBI]; + + DPRINTF("opened database version %u, pagesize %u", + env->me_metas[0]->mm_version, env->me_psize); + DPRINTF("using meta page %d", toggle); + DPRINTF("depth: %u", db->md_depth); + DPRINTF("entries: %zu", db->md_entries); + DPRINTF("branch pages: %zu", db->md_branch_pages); + DPRINTF("leaf pages: %zu", db->md_leaf_pages); + DPRINTF("overflow pages: %zu", db->md_overflow_pages); + DPRINTF("root: %zu", db->md_root); + } +#endif + + return MDB_SUCCESS; +} + + +/** Release a reader thread's slot in the reader lock table. + * This function is called automatically when a thread exits. + * @param[in] ptr This points to the slot in the reader lock table. + */ +static void +mdb_env_reader_dest(void *ptr) +{ + MDB_reader *reader = ptr; + + reader->mr_pid = 0; +} + +#ifdef _WIN32 +/** Junk for arranging thread-specific callbacks on Windows. This is + * necessarily platform and compiler-specific. Windows supports up + * to 1088 keys. Let's assume nobody opens more than 64 environments + * in a single process, for now. They can override this if needed. + */ +#ifndef MAX_TLS_KEYS +#define MAX_TLS_KEYS 64 +#endif +static pthread_key_t mdb_tls_keys[MAX_TLS_KEYS]; +static int mdb_tls_nkeys; + +static void NTAPI mdb_tls_callback(PVOID module, DWORD reason, PVOID ptr) +{ + int i; + switch(reason) { + case DLL_PROCESS_ATTACH: break; + case DLL_THREAD_ATTACH: break; + case DLL_THREAD_DETACH: + for (i=0; ime_txns->mti_txnid = env->me_metas[toggle]->mm_txnid; + +#ifdef _WIN32 + { + OVERLAPPED ov; + /* First acquire a shared lock. The Unlock will + * then release the existing exclusive lock. + */ + memset(&ov, 0, sizeof(ov)); + LockFileEx(env->me_lfd, 0, 0, 1, 0, &ov); + UnlockFile(env->me_lfd, 0, 0, 1, 0); + *excl = 0; + } +#else + { + struct flock lock_info; + /* The shared lock replaces the existing lock */ + memset((void *)&lock_info, 0, sizeof(lock_info)); + lock_info.l_type = F_RDLCK; + lock_info.l_whence = SEEK_SET; + lock_info.l_start = 0; + lock_info.l_len = 1; + while ((rc = fcntl(env->me_lfd, F_SETLK, &lock_info)) && + (rc = ErrCode()) == EINTR) ; + *excl = rc ? -1 : 0; /* error may mean we lost the lock */ + } +#endif + + return rc; +} + +/** Try to get exlusive lock, otherwise shared. + * Maintain *excl = -1: no/unknown lock, 0: shared, 1: exclusive. + */ +static int +mdb_env_excl_lock(MDB_env *env, int *excl) +{ + int rc = 0; +#ifdef _WIN32 + if (LockFile(env->me_lfd, 0, 0, 1, 0)) { + *excl = 1; + } else { + OVERLAPPED ov; + memset(&ov, 0, sizeof(ov)); + if (!LockFileEx(env->me_lfd, 0, 0, 1, 0, &ov)) { + rc = ErrCode(); + } + } +#else + struct flock lock_info; + memset((void *)&lock_info, 0, sizeof(lock_info)); + lock_info.l_type = F_WRLCK; + lock_info.l_whence = SEEK_SET; + lock_info.l_start = 0; + lock_info.l_len = 1; + if (!fcntl(env->me_lfd, F_SETLK, &lock_info)) { + *excl = 1; + } else +# ifdef MDB_USE_POSIX_SEM + if (*excl < 0) /* always true when !MDB_USE_POSIX_SEM */ +# endif + { + lock_info.l_type = F_RDLCK; + while ((rc = fcntl(env->me_lfd, F_SETLKW, &lock_info)) && + (rc = ErrCode()) == EINTR) ; + if (rc == 0) + *excl = 0; + } +#endif + return rc; +} + +#if defined(_WIN32) || defined(MDB_USE_POSIX_SEM) +/* + * hash_64 - 64 bit Fowler/Noll/Vo-0 FNV-1a hash code + * + * @(#) $Revision: 5.1 $ + * @(#) $Id: hash_64a.c,v 5.1 2009/06/30 09:01:38 chongo Exp $ + * @(#) $Source: /usr/local/src/cmd/fnv/RCS/hash_64a.c,v $ + * + * http://www.isthe.com/chongo/tech/comp/fnv/index.html + * + *** + * + * Please do not copyright this code. This code is in the public domain. + * + * LANDON CURT NOLL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO + * EVENT SHALL LANDON CURT NOLL BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF + * USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + * + * By: + * chongo /\oo/\ + * http://www.isthe.com/chongo/ + * + * Share and Enjoy! :-) + */ + +typedef unsigned long long mdb_hash_t; +#define MDB_HASH_INIT ((mdb_hash_t)0xcbf29ce484222325ULL) + +/** perform a 64 bit Fowler/Noll/Vo FNV-1a hash on a buffer + * @param[in] str string to hash + * @param[in] hval initial value for hash + * @return 64 bit hash + * + * NOTE: To use the recommended 64 bit FNV-1a hash, use MDB_HASH_INIT as the + * hval arg on the first call. + */ +static mdb_hash_t +mdb_hash_val(MDB_val *val, mdb_hash_t hval) +{ + unsigned char *s = (unsigned char *)val->mv_data; /* unsigned string */ + unsigned char *end = s + val->mv_size; + /* + * FNV-1a hash each octet of the string + */ + while (s < end) { + /* xor the bottom with the current octet */ + hval ^= (mdb_hash_t)*s++; + + /* multiply by the 64 bit FNV magic prime mod 2^64 */ + hval += (hval << 1) + (hval << 4) + (hval << 5) + + (hval << 7) + (hval << 8) + (hval << 40); + } + /* return our new hash value */ + return hval; +} + +/** Hash the string and output the hash in hex. + * @param[in] str string to hash + * @param[out] hexbuf an array of 17 chars to hold the hash + */ +static void +mdb_hash_hex(MDB_val *val, char *hexbuf) +{ + int i; + mdb_hash_t h = mdb_hash_val(val, MDB_HASH_INIT); + for (i=0; i<8; i++) { + hexbuf += sprintf(hexbuf, "%02x", (unsigned int)h & 0xff); + h >>= 8; + } +} +#endif + +/** Open and/or initialize the lock region for the environment. + * @param[in] env The MDB environment. + * @param[in] lpath The pathname of the file used for the lock region. + * @param[in] mode The Unix permissions for the file, if we create it. + * @param[out] excl Resulting file lock type: -1 none, 0 shared, 1 exclusive + * @return 0 on success, non-zero on failure. + */ +static int +mdb_env_setup_locks(MDB_env *env, char *lpath, int mode, int *excl) +{ + int rc; + off_t size, rsize; + + *excl = -1; + +#ifdef _WIN32 + if ((env->me_lfd = CreateFile(lpath, GENERIC_READ|GENERIC_WRITE, + FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, + FILE_ATTRIBUTE_NORMAL, NULL)) == INVALID_HANDLE_VALUE) { + goto fail_errno; + } + /* Try to get exclusive lock. If we succeed, then + * nobody is using the lock region and we should initialize it. + */ + if ((rc = mdb_env_excl_lock(env, excl))) goto fail; + size = GetFileSize(env->me_lfd, NULL); + +#else +#if !(O_CLOEXEC) + { + int fdflags; + if ((env->me_lfd = open(lpath, O_RDWR|O_CREAT, mode)) == -1) + goto fail_errno; + /* Lose record locks when exec*() */ + if ((fdflags = fcntl(env->me_lfd, F_GETFD) | FD_CLOEXEC) >= 0) + fcntl(env->me_lfd, F_SETFD, fdflags); + } +#else /* O_CLOEXEC on Linux: Open file and set FD_CLOEXEC atomically */ + if ((env->me_lfd = open(lpath, O_RDWR|O_CREAT|O_CLOEXEC, mode)) == -1) + goto fail_errno; +#endif + + /* Try to get exclusive lock. If we succeed, then + * nobody is using the lock region and we should initialize it. + */ + if ((rc = mdb_env_excl_lock(env, excl))) goto fail; + + size = lseek(env->me_lfd, 0, SEEK_END); +#endif + rsize = (env->me_maxreaders-1) * sizeof(MDB_reader) + sizeof(MDB_txninfo); + if (size < rsize && *excl > 0) { +#ifdef _WIN32 + SetFilePointer(env->me_lfd, rsize, NULL, 0); + if (!SetEndOfFile(env->me_lfd)) goto fail_errno; +#else + if (ftruncate(env->me_lfd, rsize) != 0) goto fail_errno; +#endif + } else { + rsize = size; + size = rsize - sizeof(MDB_txninfo); + env->me_maxreaders = size/sizeof(MDB_reader) + 1; + } + { +#ifdef _WIN32 + HANDLE mh; + mh = CreateFileMapping(env->me_lfd, NULL, PAGE_READWRITE, + 0, 0, NULL); + if (!mh) goto fail_errno; + env->me_txns = MapViewOfFileEx(mh, FILE_MAP_WRITE, 0, 0, rsize, NULL); + CloseHandle(mh); + if (!env->me_txns) goto fail_errno; +#else + void *m = mmap(NULL, rsize, PROT_READ|PROT_WRITE, MAP_SHARED, + env->me_lfd, 0); + if (m == MAP_FAILED) goto fail_errno; + env->me_txns = m; +#endif + } + if (*excl > 0) { +#ifdef _WIN32 + BY_HANDLE_FILE_INFORMATION stbuf; + struct { + DWORD volume; + DWORD nhigh; + DWORD nlow; + } idbuf; + MDB_val val; + char hexbuf[17]; + + if (!mdb_sec_inited) { + InitializeSecurityDescriptor(&mdb_null_sd, + SECURITY_DESCRIPTOR_REVISION); + SetSecurityDescriptorDacl(&mdb_null_sd, TRUE, 0, FALSE); + mdb_all_sa.nLength = sizeof(SECURITY_ATTRIBUTES); + mdb_all_sa.bInheritHandle = FALSE; + mdb_all_sa.lpSecurityDescriptor = &mdb_null_sd; + mdb_sec_inited = 1; + } + GetFileInformationByHandle(env->me_lfd, &stbuf); + idbuf.volume = stbuf.dwVolumeSerialNumber; + idbuf.nhigh = stbuf.nFileIndexHigh; + idbuf.nlow = stbuf.nFileIndexLow; + val.mv_data = &idbuf; + val.mv_size = sizeof(idbuf); + mdb_hash_hex(&val, hexbuf); + sprintf(env->me_txns->mti_rmname, "Global\\MDBr%s", hexbuf); + sprintf(env->me_txns->mti_wmname, "Global\\MDBw%s", hexbuf); + env->me_rmutex = CreateMutex(&mdb_all_sa, FALSE, env->me_txns->mti_rmname); + if (!env->me_rmutex) goto fail_errno; + env->me_wmutex = CreateMutex(&mdb_all_sa, FALSE, env->me_txns->mti_wmname); + if (!env->me_wmutex) goto fail_errno; +#elif defined(MDB_USE_POSIX_SEM) + struct stat stbuf; + struct { + dev_t dev; + ino_t ino; + } idbuf; + MDB_val val; + char hexbuf[17]; + + if (fstat(env->me_lfd, &stbuf)) goto fail_errno; + idbuf.dev = stbuf.st_dev; + idbuf.ino = stbuf.st_ino; + val.mv_data = &idbuf; + val.mv_size = sizeof(idbuf); + mdb_hash_hex(&val, hexbuf); + sprintf(env->me_txns->mti_rmname, "/MDBr%s", hexbuf); + sprintf(env->me_txns->mti_wmname, "/MDBw%s", hexbuf); + /* Clean up after a previous run, if needed: Try to + * remove both semaphores before doing anything else. + */ + sem_unlink(env->me_txns->mti_rmname); + sem_unlink(env->me_txns->mti_wmname); + env->me_rmutex = sem_open(env->me_txns->mti_rmname, + O_CREAT|O_EXCL, mode, 1); + if (env->me_rmutex == SEM_FAILED) goto fail_errno; + env->me_wmutex = sem_open(env->me_txns->mti_wmname, + O_CREAT|O_EXCL, mode, 1); + if (env->me_wmutex == SEM_FAILED) goto fail_errno; +#else /* MDB_USE_POSIX_SEM */ + pthread_mutexattr_t mattr; + + if ((rc = pthread_mutexattr_init(&mattr)) + || (rc = pthread_mutexattr_setpshared(&mattr, PTHREAD_PROCESS_SHARED)) + || (rc = pthread_mutex_init(&env->me_txns->mti_mutex, &mattr)) + || (rc = pthread_mutex_init(&env->me_txns->mti_wmutex, &mattr))) + goto fail; + pthread_mutexattr_destroy(&mattr); +#endif /* _WIN32 || MDB_USE_POSIX_SEM */ + + 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; + + } else { + if (env->me_txns->mti_magic != MDB_MAGIC) { + DPUTS("lock region has invalid magic"); + rc = MDB_INVALID; + goto fail; + } + if (env->me_txns->mti_version != MDB_VERSION) { + DPRINTF("lock region is version %u, expected version %u", + env->me_txns->mti_version, MDB_VERSION); + rc = MDB_VERSION_MISMATCH; + goto fail; + } + rc = ErrCode(); + if (rc != EACCES && rc != EAGAIN) { + goto fail; + } +#ifdef _WIN32 + env->me_rmutex = OpenMutex(SYNCHRONIZE, FALSE, env->me_txns->mti_rmname); + if (!env->me_rmutex) goto fail_errno; + env->me_wmutex = OpenMutex(SYNCHRONIZE, FALSE, env->me_txns->mti_wmname); + if (!env->me_wmutex) goto fail_errno; +#elif defined(MDB_USE_POSIX_SEM) + env->me_rmutex = sem_open(env->me_txns->mti_rmname, 0); + if (env->me_rmutex == SEM_FAILED) goto fail_errno; + env->me_wmutex = sem_open(env->me_txns->mti_wmname, 0); + if (env->me_wmutex == SEM_FAILED) goto fail_errno; +#endif + } + return MDB_SUCCESS; + +fail_errno: + rc = ErrCode(); +fail: + return rc; +} + + /** The name of the lock file in the DB environment */ +#define LOCKNAME "/lock.mdb" + /** The name of the data file in the DB environment */ +#define DATANAME "/data.mdb" + /** The suffix of the lock file when no subdir is used */ +#define LOCKSUFF "-lock" + +int +mdb_env_open(MDB_env *env, const char *path, unsigned int flags, mode_t mode) +{ + int oflags, rc, len, excl; + char *lpath, *dpath; + + if (env->me_fd != INVALID_HANDLE_VALUE) + return EINVAL; + + len = strlen(path); + if (flags & MDB_NOSUBDIR) { + rc = len + sizeof(LOCKSUFF) + len + 1; + } else { + rc = len + sizeof(LOCKNAME) + len + sizeof(DATANAME); + } + lpath = malloc(rc); + if (!lpath) + return ENOMEM; + if (flags & MDB_NOSUBDIR) { + dpath = lpath + len + sizeof(LOCKSUFF); + sprintf(lpath, "%s" LOCKSUFF, path); + strcpy(dpath, path); + } else { + dpath = lpath + len + sizeof(LOCKNAME); + sprintf(lpath, "%s" LOCKNAME, path); + sprintf(dpath, "%s" DATANAME, path); + } + + rc = mdb_env_setup_locks(env, lpath, mode, &excl); + if (rc) + goto leave; + + /* silently ignore WRITEMAP if we're only getting read access */ + if (F_ISSET(flags, MDB_RDONLY) && F_ISSET(flags, MDB_WRITEMAP)) + flags ^= MDB_WRITEMAP; + +#ifdef _WIN32 + if (F_ISSET(flags, MDB_RDONLY)) { + oflags = GENERIC_READ; + len = OPEN_EXISTING; + } else { + oflags = GENERIC_READ|GENERIC_WRITE; + len = OPEN_ALWAYS; + } + mode = FILE_ATTRIBUTE_NORMAL; + env->me_fd = CreateFile(dpath, oflags, FILE_SHARE_READ|FILE_SHARE_WRITE, + NULL, len, mode, NULL); +#else + if (F_ISSET(flags, MDB_RDONLY)) + oflags = O_RDONLY; + else + oflags = O_RDWR | O_CREAT; + + env->me_fd = open(dpath, oflags, mode); +#endif + if (env->me_fd == INVALID_HANDLE_VALUE) { + rc = ErrCode(); + goto leave; + } + + if ((rc = mdb_env_open2(env, flags)) == MDB_SUCCESS) { + if (flags & (MDB_RDONLY|MDB_NOSYNC|MDB_NOMETASYNC|MDB_WRITEMAP)) { + env->me_mfd = env->me_fd; + } else { + /* synchronous fd for meta writes */ +#ifdef _WIN32 + env->me_mfd = CreateFile(dpath, oflags, + FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, len, + mode | FILE_FLAG_WRITE_THROUGH, NULL); +#else + env->me_mfd = open(dpath, oflags | MDB_DSYNC, mode); +#endif + if (env->me_mfd == INVALID_HANDLE_VALUE) { + rc = ErrCode(); + goto leave; + } + } + DPRINTF("opened dbenv %p", (void *) env); + pthread_key_create(&env->me_txkey, mdb_env_reader_dest); + env->me_numdbs = 2; /* this notes that me_txkey was set */ +#ifdef _WIN32 + /* Windows TLS callbacks need help finding their TLS info. */ + if (mdb_tls_nkeys < MAX_TLS_KEYS) + mdb_tls_keys[mdb_tls_nkeys++] = env->me_txkey; + else { + rc = MDB_TLS_FULL; + goto leave; + } +#endif + if (excl > 0) { + rc = mdb_env_share_locks(env, &excl); + if (rc) + goto leave; + } + env->me_dbxs = calloc(env->me_maxdbs, sizeof(MDB_dbx)); + env->me_dbflags = calloc(env->me_maxdbs, sizeof(uint16_t)); + env->me_path = strdup(path); + if (!env->me_dbxs || !env->me_dbflags || !env->me_path) + rc = ENOMEM; + } + +leave: + if (rc) { + mdb_env_close0(env, excl); + } + free(lpath); + return rc; +} + +/** Destroy resources from mdb_env_open() and clear our readers */ +static void +mdb_env_close0(MDB_env *env, int excl) +{ + int i; + + if (env->me_lfd == INVALID_HANDLE_VALUE) /* 1st field to get inited */ + return; + + free(env->me_dbflags); + free(env->me_dbxs); + free(env->me_path); + + if (env->me_numdbs) { + pthread_key_delete(env->me_txkey); +#ifdef _WIN32 + /* Delete our key from the global list */ + for (i=0; ime_txkey) { + mdb_tls_keys[i] = mdb_tls_keys[mdb_tls_nkeys-1]; + mdb_tls_nkeys--; + break; + } +#endif + } + + if (env->me_map) { + munmap(env->me_map, env->me_mapsize); + } + if (env->me_mfd != env->me_fd && env->me_mfd != INVALID_HANDLE_VALUE) + close(env->me_mfd); + if (env->me_fd != INVALID_HANDLE_VALUE) + close(env->me_fd); + if (env->me_txns) { + pid_t pid = env->me_pid; + /* Clearing readers is done in this function because + * me_txkey with its destructor must be disabled first. + */ + for (i = env->me_numreaders; --i >= 0; ) + if (env->me_txns->mti_readers[i].mr_pid == pid) + env->me_txns->mti_readers[i].mr_pid = 0; +#ifdef _WIN32 + if (env->me_rmutex) { + CloseHandle(env->me_rmutex); + if (env->me_wmutex) CloseHandle(env->me_wmutex); + } + /* Windows automatically destroys the mutexes when + * the last handle closes. + */ +#elif defined(MDB_USE_POSIX_SEM) + if (env->me_rmutex != SEM_FAILED) { + sem_close(env->me_rmutex); + if (env->me_wmutex != SEM_FAILED) + sem_close(env->me_wmutex); + /* If we have the filelock: If we are the + * only remaining user, clean up semaphores. + */ + if (excl == 0) + mdb_env_excl_lock(env, &excl); + if (excl > 0) { + sem_unlink(env->me_txns->mti_rmname); + sem_unlink(env->me_txns->mti_wmname); + } + } +#endif + munmap((void *)env->me_txns, (env->me_maxreaders-1)*sizeof(MDB_reader)+sizeof(MDB_txninfo)); + } + close(env->me_lfd); + + env->me_lfd = INVALID_HANDLE_VALUE; /* Mark env as reset */ +} + +void +mdb_env_close(MDB_env *env) +{ + MDB_page *dp; + + if (env == NULL) + return; + + VGMEMP_DESTROY(env); + while ((dp = env->me_dpages) != NULL) { + VGMEMP_DEFINED(&dp->mp_next, sizeof(dp->mp_next)); + env->me_dpages = dp->mp_next; + free(dp); + } + + mdb_env_close0(env, 0); + mdb_midl_free(env->me_free_pgs); + free(env); +} + +/** Compare two items pointing at aligned size_t's */ +static int +mdb_cmp_long(const MDB_val *a, const MDB_val *b) +{ + return (*(size_t *)a->mv_data < *(size_t *)b->mv_data) ? -1 : + *(size_t *)a->mv_data > *(size_t *)b->mv_data; +} + +/** Compare two items pointing at aligned int's */ +static int +mdb_cmp_int(const MDB_val *a, const MDB_val *b) +{ + return (*(unsigned int *)a->mv_data < *(unsigned int *)b->mv_data) ? -1 : + *(unsigned int *)a->mv_data > *(unsigned int *)b->mv_data; +} + +/** Compare two items pointing at ints of unknown alignment. + * Nodes and keys are guaranteed to be 2-byte aligned. + */ +static int +mdb_cmp_cint(const MDB_val *a, const MDB_val *b) +{ +#if BYTE_ORDER == LITTLE_ENDIAN + unsigned short *u, *c; + int x; + + u = (unsigned short *) ((char *) a->mv_data + a->mv_size); + c = (unsigned short *) ((char *) b->mv_data + a->mv_size); + do { + x = *--u - *--c; + } while(!x && u > (unsigned short *)a->mv_data); + return x; +#else + return memcmp(a->mv_data, b->mv_data, a->mv_size); +#endif +} + +/** Compare two items lexically */ +static int +mdb_cmp_memn(const MDB_val *a, const MDB_val *b) +{ + int diff; + ssize_t len_diff; + unsigned int len; + + len = a->mv_size; + len_diff = (ssize_t) a->mv_size - (ssize_t) b->mv_size; + if (len_diff > 0) { + len = b->mv_size; + len_diff = 1; + } + + diff = memcmp(a->mv_data, b->mv_data, len); + return diff ? diff : len_diff<0 ? -1 : len_diff; +} + +/** Compare two items in reverse byte order */ +static int +mdb_cmp_memnr(const MDB_val *a, const MDB_val *b) +{ + const unsigned char *p1, *p2, *p1_lim; + ssize_t len_diff; + int diff; + + p1_lim = (const unsigned char *)a->mv_data; + p1 = (const unsigned char *)a->mv_data + a->mv_size; + p2 = (const unsigned char *)b->mv_data + b->mv_size; + + len_diff = (ssize_t) a->mv_size - (ssize_t) b->mv_size; + if (len_diff > 0) { + p1_lim += len_diff; + len_diff = 1; + } + + while (p1 > p1_lim) { + diff = *--p1 - *--p2; + if (diff) + return diff; + } + return len_diff<0 ? -1 : len_diff; +} + +/** Search for key within a page, using binary search. + * Returns the smallest entry larger or equal to the key. + * If exactp is non-null, stores whether the found entry was an exact match + * in *exactp (1 or 0). + * Updates the cursor index with the index of the found entry. + * If no entry larger or equal to the key is found, returns NULL. + */ +static MDB_node * +mdb_node_search(MDB_cursor *mc, MDB_val *key, int *exactp) +{ + unsigned int i = 0, nkeys; + int low, high; + int rc = 0; + MDB_page *mp = mc->mc_pg[mc->mc_top]; + MDB_node *node = NULL; + MDB_val nodekey; + MDB_cmp_func *cmp; + DKBUF; + + nkeys = NUMKEYS(mp); + +#if MDB_DEBUG + { + pgno_t pgno; + COPY_PGNO(pgno, mp->mp_pgno); + DPRINTF("searching %u keys in %s %spage %zu", + nkeys, IS_LEAF(mp) ? "leaf" : "branch", IS_SUBP(mp) ? "sub-" : "", + pgno); + } +#endif + + assert(nkeys > 0); + + low = IS_LEAF(mp) ? 0 : 1; + high = nkeys - 1; + cmp = mc->mc_dbx->md_cmp; + + /* Branch pages have no data, so if using integer keys, + * alignment is guaranteed. Use faster mdb_cmp_int. + */ + if (cmp == mdb_cmp_cint && IS_BRANCH(mp)) { + if (NODEPTR(mp, 1)->mn_ksize == sizeof(size_t)) + cmp = mdb_cmp_long; + else + cmp = mdb_cmp_int; + } + + if (IS_LEAF2(mp)) { + nodekey.mv_size = mc->mc_db->md_pad; + node = NODEPTR(mp, 0); /* fake */ + while (low <= high) { + i = (low + high) >> 1; + nodekey.mv_data = LEAF2KEY(mp, i, nodekey.mv_size); + rc = cmp(key, &nodekey); + DPRINTF("found leaf index %u [%s], rc = %i", + i, DKEY(&nodekey), rc); + if (rc == 0) + break; + if (rc > 0) + low = i + 1; + else + high = i - 1; + } + } else { + while (low <= high) { + i = (low + high) >> 1; + + node = NODEPTR(mp, i); + nodekey.mv_size = NODEKSZ(node); + nodekey.mv_data = NODEKEY(node); + + rc = cmp(key, &nodekey); +#if MDB_DEBUG + if (IS_LEAF(mp)) + DPRINTF("found leaf index %u [%s], rc = %i", + i, DKEY(&nodekey), rc); + else + DPRINTF("found branch index %u [%s -> %zu], rc = %i", + i, DKEY(&nodekey), NODEPGNO(node), rc); +#endif + if (rc == 0) + break; + if (rc > 0) + low = i + 1; + else + high = i - 1; + } + } + + if (rc > 0) { /* Found entry is less than the key. */ + i++; /* Skip to get the smallest entry larger than key. */ + if (!IS_LEAF2(mp)) + node = NODEPTR(mp, i); + } + if (exactp) + *exactp = (rc == 0); + /* store the key index */ + mc->mc_ki[mc->mc_top] = i; + if (i >= nkeys) + /* There is no entry larger or equal to the key. */ + return NULL; + + /* nodeptr is fake for LEAF2 */ + return node; +} + +#if 0 +static void +mdb_cursor_adjust(MDB_cursor *mc, func) +{ + MDB_cursor *m2; + + for (m2 = mc->mc_txn->mt_cursors[mc->mc_dbi]; m2; m2=m2->mc_next) { + if (m2->mc_pg[m2->mc_top] == mc->mc_pg[mc->mc_top]) { + func(mc, m2); + } + } +} +#endif + +/** Pop a page off the top of the cursor's stack. */ +static void +mdb_cursor_pop(MDB_cursor *mc) +{ + if (mc->mc_snum) { +#if MDB_DEBUG + MDB_page *top = mc->mc_pg[mc->mc_top]; +#endif + mc->mc_snum--; + if (mc->mc_snum) + mc->mc_top--; + + DPRINTF("popped page %zu off db %u cursor %p", top->mp_pgno, + mc->mc_dbi, (void *) mc); + } +} + +/** Push a page onto the top of the cursor's stack. */ +static int +mdb_cursor_push(MDB_cursor *mc, MDB_page *mp) +{ + DPRINTF("pushing page %zu on db %u cursor %p", mp->mp_pgno, + mc->mc_dbi, (void *) mc); + + if (mc->mc_snum >= CURSOR_STACK) { + assert(mc->mc_snum < CURSOR_STACK); + return MDB_CURSOR_FULL; + } + + mc->mc_top = mc->mc_snum++; + mc->mc_pg[mc->mc_top] = mp; + mc->mc_ki[mc->mc_top] = 0; + + return MDB_SUCCESS; +} + +/** Find the address of the page corresponding to a given page number. + * @param[in] txn the transaction for this access. + * @param[in] pgno the page number for the page to retrieve. + * @param[out] ret address of a pointer where the page's address will be stored. + * @return 0 on success, non-zero on failure. + */ +static int +mdb_page_get(MDB_txn *txn, pgno_t pgno, MDB_page **ret) +{ + MDB_page *p = NULL; + + if (txn->mt_env->me_flags & MDB_WRITEMAP) { + if (pgno < txn->mt_next_pgno) + p = (MDB_page *)(txn->mt_env->me_map + txn->mt_env->me_psize * pgno); + goto done; + } + if (!F_ISSET(txn->mt_flags, MDB_TXN_RDONLY) && txn->mt_u.dirty_list[0].mid) { + unsigned x; + x = mdb_mid2l_search(txn->mt_u.dirty_list, pgno); + if (x <= txn->mt_u.dirty_list[0].mid && txn->mt_u.dirty_list[x].mid == pgno) { + p = txn->mt_u.dirty_list[x].mptr; + } + } + if (!p) { + if (pgno < txn->mt_next_pgno) + p = (MDB_page *)(txn->mt_env->me_map + txn->mt_env->me_psize * pgno); + } +done: + *ret = p; + if (!p) { + DPRINTF("page %zu not found", pgno); + assert(p != NULL); + } + return (p != NULL) ? MDB_SUCCESS : MDB_PAGE_NOTFOUND; +} + +/** Search for the page a given key should be in. + * Pushes parent pages on the cursor stack. This function continues a + * search on a cursor that has already been initialized. (Usually by + * #mdb_page_search() but also by #mdb_node_move().) + * @param[in,out] mc the cursor for this operation. + * @param[in] key the key to search for. If NULL, search for the lowest + * page. (This is used by #mdb_cursor_first().) + * @param[in] flags If MDB_PS_MODIFY set, visited pages are updated with new page numbers. + * If MDB_PS_ROOTONLY set, just fetch root node, no further lookups. + * @return 0 on success, non-zero on failure. + */ +static int +mdb_page_search_root(MDB_cursor *mc, MDB_val *key, int modify) +{ + MDB_page *mp = mc->mc_pg[mc->mc_top]; + DKBUF; + int rc; + + + while (IS_BRANCH(mp)) { + MDB_node *node; + indx_t i; + + DPRINTF("branch page %zu has %u keys", mp->mp_pgno, NUMKEYS(mp)); + assert(NUMKEYS(mp) > 1); + DPRINTF("found index 0 to page %zu", NODEPGNO(NODEPTR(mp, 0))); + + if (key == NULL) /* Initialize cursor to first page. */ + i = 0; + else if (key->mv_size > MAXKEYSIZE && key->mv_data == NULL) { + /* cursor to last page */ + i = NUMKEYS(mp)-1; + } else { + int exact; + node = mdb_node_search(mc, key, &exact); + if (node == NULL) + i = NUMKEYS(mp) - 1; + else { + i = mc->mc_ki[mc->mc_top]; + if (!exact) { + assert(i > 0); + i--; + } + } + } + + if (key) + DPRINTF("following index %u for key [%s]", + i, DKEY(key)); + assert(i < NUMKEYS(mp)); + node = NODEPTR(mp, i); + + if ((rc = mdb_page_get(mc->mc_txn, NODEPGNO(node), &mp))) + return rc; + + mc->mc_ki[mc->mc_top] = i; + if ((rc = mdb_cursor_push(mc, mp))) + return rc; + + if (modify) { + if ((rc = mdb_page_touch(mc)) != 0) + return rc; + mp = mc->mc_pg[mc->mc_top]; + } + } + + if (!IS_LEAF(mp)) { + DPRINTF("internal error, index points to a %02X page!?", + mp->mp_flags); + return MDB_CORRUPTED; + } + + DPRINTF("found leaf page %zu for key [%s]", mp->mp_pgno, + key ? DKEY(key) : NULL); + + return MDB_SUCCESS; +} + +/** Search for the page a given key should be in. + * Pushes parent pages on the cursor stack. This function just sets up + * the search; it finds the root page for \b mc's database and sets this + * as the root of the cursor's stack. Then #mdb_page_search_root() is + * called to complete the search. + * @param[in,out] mc the cursor for this operation. + * @param[in] key the key to search for. If NULL, search for the lowest + * page. (This is used by #mdb_cursor_first().) + * @param[in] modify If true, visited pages are updated with new page numbers. + * @return 0 on success, non-zero on failure. + */ +static int +mdb_page_search(MDB_cursor *mc, MDB_val *key, int flags) +{ + int rc; + pgno_t root; + + /* Make sure the txn is still viable, then find the root from + * the txn's db table. + */ + if (F_ISSET(mc->mc_txn->mt_flags, MDB_TXN_ERROR)) { + DPUTS("transaction has failed, must abort"); + return EINVAL; + } else { + /* Make sure we're using an up-to-date root */ + if (mc->mc_dbi > MAIN_DBI) { + if ((*mc->mc_dbflag & DB_STALE) || + ((flags & MDB_PS_MODIFY) && !(*mc->mc_dbflag & DB_DIRTY))) { + MDB_cursor mc2; + unsigned char dbflag = 0; + mdb_cursor_init(&mc2, mc->mc_txn, MAIN_DBI, NULL); + rc = mdb_page_search(&mc2, &mc->mc_dbx->md_name, flags & MDB_PS_MODIFY); + if (rc) + return rc; + if (*mc->mc_dbflag & DB_STALE) { + MDB_val data; + int exact = 0; + MDB_node *leaf = mdb_node_search(&mc2, + &mc->mc_dbx->md_name, &exact); + if (!exact) + return MDB_NOTFOUND; + mdb_node_read(mc->mc_txn, leaf, &data); + memcpy(mc->mc_db, data.mv_data, sizeof(MDB_db)); + } + if (flags & MDB_PS_MODIFY) + dbflag = DB_DIRTY; + *mc->mc_dbflag = dbflag; + } + } + root = mc->mc_db->md_root; + + if (root == P_INVALID) { /* Tree is empty. */ + DPUTS("tree is empty"); + return MDB_NOTFOUND; + } + } + + assert(root > 1); + if (!mc->mc_pg[0] || mc->mc_pg[0]->mp_pgno != root) + if ((rc = mdb_page_get(mc->mc_txn, root, &mc->mc_pg[0]))) + return rc; + + mc->mc_snum = 1; + mc->mc_top = 0; + + DPRINTF("db %u root page %zu has flags 0x%X", + mc->mc_dbi, root, mc->mc_pg[0]->mp_flags); + + if (flags & MDB_PS_MODIFY) { + if ((rc = mdb_page_touch(mc))) + return rc; + } + + if (flags & MDB_PS_ROOTONLY) + return MDB_SUCCESS; + + return mdb_page_search_root(mc, key, flags); +} + +/** Return the data associated with a given node. + * @param[in] txn The transaction for this operation. + * @param[in] leaf The node being read. + * @param[out] data Updated to point to the node's data. + * @return 0 on success, non-zero on failure. + */ +static int +mdb_node_read(MDB_txn *txn, MDB_node *leaf, MDB_val *data) +{ + MDB_page *omp; /* overflow page */ + pgno_t pgno; + int rc; + + if (!F_ISSET(leaf->mn_flags, F_BIGDATA)) { + data->mv_size = NODEDSZ(leaf); + data->mv_data = NODEDATA(leaf); + return MDB_SUCCESS; + } + + /* Read overflow data. + */ + data->mv_size = NODEDSZ(leaf); + memcpy(&pgno, NODEDATA(leaf), sizeof(pgno)); + if ((rc = mdb_page_get(txn, pgno, &omp))) { + DPRINTF("read overflow page %zu failed", pgno); + return rc; + } + data->mv_data = METADATA(omp); + + return MDB_SUCCESS; +} + +int +mdb_get(MDB_txn *txn, MDB_dbi dbi, + MDB_val *key, MDB_val *data) +{ + MDB_cursor mc; + MDB_xcursor mx; + int exact = 0; + DKBUF; + + assert(key); + assert(data); + DPRINTF("===> get db %u key [%s]", dbi, DKEY(key)); + + if (txn == NULL || !dbi || dbi >= txn->mt_numdbs) + return EINVAL; + + if (key->mv_size == 0 || key->mv_size > MAXKEYSIZE) { + return EINVAL; + } + + mdb_cursor_init(&mc, txn, dbi, &mx); + return mdb_cursor_set(&mc, key, data, MDB_SET, &exact); +} + +/** Find a sibling for a page. + * Replaces the page at the top of the cursor's stack with the + * specified sibling, if one exists. + * @param[in] mc The cursor for this operation. + * @param[in] move_right Non-zero if the right sibling is requested, + * otherwise the left sibling. + * @return 0 on success, non-zero on failure. + */ +static int +mdb_cursor_sibling(MDB_cursor *mc, int move_right) +{ + int rc; + MDB_node *indx; + MDB_page *mp; + + if (mc->mc_snum < 2) { + return MDB_NOTFOUND; /* root has no siblings */ + } + + mdb_cursor_pop(mc); + DPRINTF("parent page is page %zu, index %u", + mc->mc_pg[mc->mc_top]->mp_pgno, mc->mc_ki[mc->mc_top]); + + if (move_right ? (mc->mc_ki[mc->mc_top] + 1u >= NUMKEYS(mc->mc_pg[mc->mc_top])) + : (mc->mc_ki[mc->mc_top] == 0)) { + DPRINTF("no more keys left, moving to %s sibling", + move_right ? "right" : "left"); + if ((rc = mdb_cursor_sibling(mc, move_right)) != MDB_SUCCESS) + return rc; + } else { + if (move_right) + mc->mc_ki[mc->mc_top]++; + else + mc->mc_ki[mc->mc_top]--; + DPRINTF("just moving to %s index key %u", + move_right ? "right" : "left", mc->mc_ki[mc->mc_top]); + } + assert(IS_BRANCH(mc->mc_pg[mc->mc_top])); + + indx = NODEPTR(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top]); + if ((rc = mdb_page_get(mc->mc_txn, NODEPGNO(indx), &mp))) + return rc;; + + mdb_cursor_push(mc, mp); + + return MDB_SUCCESS; +} + +/** Move the cursor to the next data item. */ +static int +mdb_cursor_next(MDB_cursor *mc, MDB_val *key, MDB_val *data, MDB_cursor_op op) +{ + MDB_page *mp; + MDB_node *leaf; + int rc; + + if (mc->mc_flags & C_EOF) { + return MDB_NOTFOUND; + } + + assert(mc->mc_flags & C_INITIALIZED); + + mp = mc->mc_pg[mc->mc_top]; + + if (mc->mc_db->md_flags & MDB_DUPSORT) { + leaf = NODEPTR(mp, mc->mc_ki[mc->mc_top]); + if (F_ISSET(leaf->mn_flags, F_DUPDATA)) { + if (op == MDB_NEXT || op == MDB_NEXT_DUP) { + rc = mdb_cursor_next(&mc->mc_xcursor->mx_cursor, data, NULL, MDB_NEXT); + if (op != MDB_NEXT || rc == MDB_SUCCESS) + return rc; + } + } else { + mc->mc_xcursor->mx_cursor.mc_flags &= ~C_INITIALIZED; + if (op == MDB_NEXT_DUP) + return MDB_NOTFOUND; + } + } + + DPRINTF("cursor_next: top page is %zu in cursor %p", mp->mp_pgno, (void *) mc); + + if (mc->mc_ki[mc->mc_top] + 1u >= NUMKEYS(mp)) { + DPUTS("=====> move to next sibling page"); + if (mdb_cursor_sibling(mc, 1) != MDB_SUCCESS) { + mc->mc_flags |= C_EOF; + mc->mc_flags &= ~C_INITIALIZED; + return MDB_NOTFOUND; + } + mp = mc->mc_pg[mc->mc_top]; + DPRINTF("next page is %zu, key index %u", mp->mp_pgno, mc->mc_ki[mc->mc_top]); + } else + mc->mc_ki[mc->mc_top]++; + + DPRINTF("==> cursor points to page %zu with %u keys, key index %u", + mp->mp_pgno, NUMKEYS(mp), mc->mc_ki[mc->mc_top]); + + if (IS_LEAF2(mp)) { + key->mv_size = mc->mc_db->md_pad; + key->mv_data = LEAF2KEY(mp, mc->mc_ki[mc->mc_top], key->mv_size); + return MDB_SUCCESS; + } + + assert(IS_LEAF(mp)); + leaf = NODEPTR(mp, mc->mc_ki[mc->mc_top]); + + if (F_ISSET(leaf->mn_flags, F_DUPDATA)) { + mdb_xcursor_init1(mc, leaf); + } + if (data) { + if ((rc = mdb_node_read(mc->mc_txn, leaf, data) != MDB_SUCCESS)) + return rc; + + if (F_ISSET(leaf->mn_flags, F_DUPDATA)) { + rc = mdb_cursor_first(&mc->mc_xcursor->mx_cursor, data, NULL); + if (rc != MDB_SUCCESS) + return rc; + } + } + + MDB_GET_KEY(leaf, key); + return MDB_SUCCESS; +} + +/** Move the cursor to the previous data item. */ +static int +mdb_cursor_prev(MDB_cursor *mc, MDB_val *key, MDB_val *data, MDB_cursor_op op) +{ + MDB_page *mp; + MDB_node *leaf; + int rc; + + assert(mc->mc_flags & C_INITIALIZED); + + mp = mc->mc_pg[mc->mc_top]; + + if (mc->mc_db->md_flags & MDB_DUPSORT) { + leaf = NODEPTR(mp, mc->mc_ki[mc->mc_top]); + if (op == MDB_PREV || op == MDB_PREV_DUP) { + if (F_ISSET(leaf->mn_flags, F_DUPDATA)) { + rc = mdb_cursor_prev(&mc->mc_xcursor->mx_cursor, data, NULL, MDB_PREV); + if (op != MDB_PREV || rc == MDB_SUCCESS) + return rc; + } else { + mc->mc_xcursor->mx_cursor.mc_flags &= ~C_INITIALIZED; + if (op == MDB_PREV_DUP) + return MDB_NOTFOUND; + } + } + } + + DPRINTF("cursor_prev: top page is %zu in cursor %p", mp->mp_pgno, (void *) mc); + + if (mc->mc_ki[mc->mc_top] == 0) { + DPUTS("=====> move to prev sibling page"); + if (mdb_cursor_sibling(mc, 0) != MDB_SUCCESS) { + mc->mc_flags &= ~C_INITIALIZED; + return MDB_NOTFOUND; + } + mp = mc->mc_pg[mc->mc_top]; + mc->mc_ki[mc->mc_top] = NUMKEYS(mp) - 1; + DPRINTF("prev page is %zu, key index %u", mp->mp_pgno, mc->mc_ki[mc->mc_top]); + } else + mc->mc_ki[mc->mc_top]--; + + mc->mc_flags &= ~C_EOF; + + DPRINTF("==> cursor points to page %zu with %u keys, key index %u", + mp->mp_pgno, NUMKEYS(mp), mc->mc_ki[mc->mc_top]); + + if (IS_LEAF2(mp)) { + key->mv_size = mc->mc_db->md_pad; + key->mv_data = LEAF2KEY(mp, mc->mc_ki[mc->mc_top], key->mv_size); + return MDB_SUCCESS; + } + + assert(IS_LEAF(mp)); + leaf = NODEPTR(mp, mc->mc_ki[mc->mc_top]); + + if (F_ISSET(leaf->mn_flags, F_DUPDATA)) { + mdb_xcursor_init1(mc, leaf); + } + if (data) { + if ((rc = mdb_node_read(mc->mc_txn, leaf, data) != MDB_SUCCESS)) + return rc; + + if (F_ISSET(leaf->mn_flags, F_DUPDATA)) { + rc = mdb_cursor_last(&mc->mc_xcursor->mx_cursor, data, NULL); + if (rc != MDB_SUCCESS) + return rc; + } + } + + MDB_GET_KEY(leaf, key); + return MDB_SUCCESS; +} + +/** Set the cursor on a specific data item. */ +static int +mdb_cursor_set(MDB_cursor *mc, MDB_val *key, MDB_val *data, + MDB_cursor_op op, int *exactp) +{ + int rc; + MDB_page *mp; + MDB_node *leaf = NULL; + DKBUF; + + assert(mc); + assert(key); + assert(key->mv_size > 0); + + /* See if we're already on the right page */ + if (mc->mc_flags & C_INITIALIZED) { + MDB_val nodekey; + + mp = mc->mc_pg[mc->mc_top]; + if (!NUMKEYS(mp)) { + mc->mc_ki[mc->mc_top] = 0; + return MDB_NOTFOUND; + } + if (mp->mp_flags & P_LEAF2) { + nodekey.mv_size = mc->mc_db->md_pad; + nodekey.mv_data = LEAF2KEY(mp, 0, nodekey.mv_size); + } else { + leaf = NODEPTR(mp, 0); + MDB_GET_KEY(leaf, &nodekey); + } + rc = mc->mc_dbx->md_cmp(key, &nodekey); + if (rc == 0) { + /* Probably happens rarely, but first node on the page + * was the one we wanted. + */ + mc->mc_ki[mc->mc_top] = 0; + if (exactp) + *exactp = 1; + goto set1; + } + if (rc > 0) { + unsigned int i; + unsigned int nkeys = NUMKEYS(mp); + if (nkeys > 1) { + if (mp->mp_flags & P_LEAF2) { + nodekey.mv_data = LEAF2KEY(mp, + nkeys-1, nodekey.mv_size); + } else { + leaf = NODEPTR(mp, nkeys-1); + MDB_GET_KEY(leaf, &nodekey); + } + rc = mc->mc_dbx->md_cmp(key, &nodekey); + if (rc == 0) { + /* last node was the one we wanted */ + mc->mc_ki[mc->mc_top] = nkeys-1; + if (exactp) + *exactp = 1; + goto set1; + } + if (rc < 0) { + if (mc->mc_ki[mc->mc_top] < NUMKEYS(mp)) { + /* This is definitely the right page, skip search_page */ + if (mp->mp_flags & P_LEAF2) { + nodekey.mv_data = LEAF2KEY(mp, + mc->mc_ki[mc->mc_top], nodekey.mv_size); + } else { + leaf = NODEPTR(mp, mc->mc_ki[mc->mc_top]); + MDB_GET_KEY(leaf, &nodekey); + } + rc = mc->mc_dbx->md_cmp(key, &nodekey); + if (rc == 0) { + /* current node was the one we wanted */ + if (exactp) + *exactp = 1; + goto set1; + } + } + rc = 0; + goto set2; + } + } + /* If any parents have right-sibs, search. + * Otherwise, there's nothing further. + */ + for (i=0; imc_top; i++) + if (mc->mc_ki[i] < + NUMKEYS(mc->mc_pg[i])-1) + break; + if (i == mc->mc_top) { + /* There are no other pages */ + mc->mc_ki[mc->mc_top] = nkeys; + return MDB_NOTFOUND; + } + } + if (!mc->mc_top) { + /* There are no other pages */ + mc->mc_ki[mc->mc_top] = 0; + return MDB_NOTFOUND; + } + } + + rc = mdb_page_search(mc, key, 0); + if (rc != MDB_SUCCESS) + return rc; + + mp = mc->mc_pg[mc->mc_top]; + assert(IS_LEAF(mp)); + +set2: + leaf = mdb_node_search(mc, key, exactp); + if (exactp != NULL && !*exactp) { + /* MDB_SET specified and not an exact match. */ + return MDB_NOTFOUND; + } + + if (leaf == NULL) { + DPUTS("===> inexact leaf not found, goto sibling"); + if ((rc = mdb_cursor_sibling(mc, 1)) != MDB_SUCCESS) + return rc; /* no entries matched */ + mp = mc->mc_pg[mc->mc_top]; + assert(IS_LEAF(mp)); + leaf = NODEPTR(mp, 0); + } + +set1: + mc->mc_flags |= C_INITIALIZED; + mc->mc_flags &= ~C_EOF; + + if (IS_LEAF2(mp)) { + key->mv_size = mc->mc_db->md_pad; + key->mv_data = LEAF2KEY(mp, mc->mc_ki[mc->mc_top], key->mv_size); + return MDB_SUCCESS; + } + + if (F_ISSET(leaf->mn_flags, F_DUPDATA)) { + mdb_xcursor_init1(mc, leaf); + } + if (data) { + if (F_ISSET(leaf->mn_flags, F_DUPDATA)) { + if (op == MDB_SET || op == MDB_SET_KEY || op == MDB_SET_RANGE) { + rc = mdb_cursor_first(&mc->mc_xcursor->mx_cursor, data, NULL); + } else { + int ex2, *ex2p; + if (op == MDB_GET_BOTH) { + ex2p = &ex2; + ex2 = 0; + } else { + ex2p = NULL; + } + rc = mdb_cursor_set(&mc->mc_xcursor->mx_cursor, data, NULL, MDB_SET_RANGE, ex2p); + if (rc != MDB_SUCCESS) + return rc; + } + } else if (op == MDB_GET_BOTH || op == MDB_GET_BOTH_RANGE) { + MDB_val d2; + if ((rc = mdb_node_read(mc->mc_txn, leaf, &d2)) != MDB_SUCCESS) + return rc; + rc = mc->mc_dbx->md_dcmp(data, &d2); + if (rc) { + if (op == MDB_GET_BOTH || rc > 0) + return MDB_NOTFOUND; + } + + } else { + if (mc->mc_xcursor) + mc->mc_xcursor->mx_cursor.mc_flags &= ~C_INITIALIZED; + if ((rc = mdb_node_read(mc->mc_txn, leaf, data)) != MDB_SUCCESS) + return rc; + } + } + + /* The key already matches in all other cases */ + if (op == MDB_SET_RANGE || op == MDB_SET_KEY) + MDB_GET_KEY(leaf, key); + DPRINTF("==> cursor placed on key [%s]", DKEY(key)); + + return rc; +} + +/** Move the cursor to the first item in the database. */ +static int +mdb_cursor_first(MDB_cursor *mc, MDB_val *key, MDB_val *data) +{ + int rc; + MDB_node *leaf; + + if (!(mc->mc_flags & C_INITIALIZED) || mc->mc_top) { + rc = mdb_page_search(mc, NULL, 0); + if (rc != MDB_SUCCESS) + return rc; + } + assert(IS_LEAF(mc->mc_pg[mc->mc_top])); + + leaf = NODEPTR(mc->mc_pg[mc->mc_top], 0); + mc->mc_flags |= C_INITIALIZED; + mc->mc_flags &= ~C_EOF; + + mc->mc_ki[mc->mc_top] = 0; + + if (IS_LEAF2(mc->mc_pg[mc->mc_top])) { + key->mv_size = mc->mc_db->md_pad; + key->mv_data = LEAF2KEY(mc->mc_pg[mc->mc_top], 0, key->mv_size); + return MDB_SUCCESS; + } + + if (data) { + if (F_ISSET(leaf->mn_flags, F_DUPDATA)) { + mdb_xcursor_init1(mc, leaf); + rc = mdb_cursor_first(&mc->mc_xcursor->mx_cursor, data, NULL); + if (rc) + return rc; + } else { + if (mc->mc_xcursor) + mc->mc_xcursor->mx_cursor.mc_flags &= ~C_INITIALIZED; + if ((rc = mdb_node_read(mc->mc_txn, leaf, data)) != MDB_SUCCESS) + return rc; + } + } + MDB_GET_KEY(leaf, key); + return MDB_SUCCESS; +} + +/** Move the cursor to the last item in the database. */ +static int +mdb_cursor_last(MDB_cursor *mc, MDB_val *key, MDB_val *data) +{ + int rc; + MDB_node *leaf; + + if (!(mc->mc_flags & C_EOF)) { + + if (!(mc->mc_flags & C_INITIALIZED) || mc->mc_top) { + MDB_val lkey; + + lkey.mv_size = MAXKEYSIZE+1; + lkey.mv_data = NULL; + rc = mdb_page_search(mc, &lkey, 0); + if (rc != MDB_SUCCESS) + return rc; + } + assert(IS_LEAF(mc->mc_pg[mc->mc_top])); + + mc->mc_ki[mc->mc_top] = NUMKEYS(mc->mc_pg[mc->mc_top]) - 1; + mc->mc_flags |= C_INITIALIZED|C_EOF; + } + leaf = NODEPTR(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top]); + + if (IS_LEAF2(mc->mc_pg[mc->mc_top])) { + key->mv_size = mc->mc_db->md_pad; + key->mv_data = LEAF2KEY(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top], key->mv_size); + return MDB_SUCCESS; + } + + if (data) { + if (F_ISSET(leaf->mn_flags, F_DUPDATA)) { + mdb_xcursor_init1(mc, leaf); + rc = mdb_cursor_last(&mc->mc_xcursor->mx_cursor, data, NULL); + if (rc) + return rc; + } else { + if (mc->mc_xcursor) + mc->mc_xcursor->mx_cursor.mc_flags &= ~C_INITIALIZED; + if ((rc = mdb_node_read(mc->mc_txn, leaf, data)) != MDB_SUCCESS) + return rc; + } + } + + MDB_GET_KEY(leaf, key); + return MDB_SUCCESS; +} + +int +mdb_cursor_get(MDB_cursor *mc, MDB_val *key, MDB_val *data, + MDB_cursor_op op) +{ + int rc; + int exact = 0; + + assert(mc); + + switch (op) { + case MDB_GET_CURRENT: + if (!mc->mc_flags & C_INITIALIZED) { + rc = EINVAL; + } else { + MDB_page *mp = mc->mc_pg[mc->mc_top]; + if (!NUMKEYS(mp)) { + mc->mc_ki[mc->mc_top] = 0; + rc = MDB_NOTFOUND; + break; + } + rc = MDB_SUCCESS; + if (IS_LEAF2(mp)) { + key->mv_size = mc->mc_db->md_pad; + key->mv_data = LEAF2KEY(mp, mc->mc_ki[mc->mc_top], key->mv_size); + } else { + MDB_node *leaf = NODEPTR(mp, mc->mc_ki[mc->mc_top]); + MDB_GET_KEY(leaf, key); + if (data) { + if (F_ISSET(leaf->mn_flags, F_DUPDATA)) { + rc = mdb_cursor_get(&mc->mc_xcursor->mx_cursor, data, NULL, MDB_GET_CURRENT); + } else { + rc = mdb_node_read(mc->mc_txn, leaf, data); + } + } + } + } + break; + case MDB_GET_BOTH: + case MDB_GET_BOTH_RANGE: + if (data == NULL || mc->mc_xcursor == NULL) { + rc = EINVAL; + break; + } + /* FALLTHRU */ + case MDB_SET: + case MDB_SET_KEY: + case MDB_SET_RANGE: + if (key == NULL || key->mv_size == 0 || key->mv_size > MAXKEYSIZE) { + rc = EINVAL; + } else if (op == MDB_SET_RANGE) + rc = mdb_cursor_set(mc, key, data, op, NULL); + else + rc = mdb_cursor_set(mc, key, data, op, &exact); + break; + case MDB_GET_MULTIPLE: + if (data == NULL || + !(mc->mc_db->md_flags & MDB_DUPFIXED) || + !(mc->mc_flags & C_INITIALIZED)) { + rc = EINVAL; + break; + } + rc = MDB_SUCCESS; + if (!(mc->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED) || + (mc->mc_xcursor->mx_cursor.mc_flags & C_EOF)) + break; + goto fetchm; + case MDB_NEXT_MULTIPLE: + if (data == NULL || + !(mc->mc_db->md_flags & MDB_DUPFIXED)) { + rc = EINVAL; + break; + } + if (!(mc->mc_flags & C_INITIALIZED)) + rc = mdb_cursor_first(mc, key, data); + else + rc = mdb_cursor_next(mc, key, data, MDB_NEXT_DUP); + if (rc == MDB_SUCCESS) { + if (mc->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED) { + MDB_cursor *mx; +fetchm: + mx = &mc->mc_xcursor->mx_cursor; + data->mv_size = NUMKEYS(mx->mc_pg[mx->mc_top]) * + mx->mc_db->md_pad; + data->mv_data = METADATA(mx->mc_pg[mx->mc_top]); + mx->mc_ki[mx->mc_top] = NUMKEYS(mx->mc_pg[mx->mc_top])-1; + } else { + rc = MDB_NOTFOUND; + } + } + break; + case MDB_NEXT: + case MDB_NEXT_DUP: + case MDB_NEXT_NODUP: + if (!(mc->mc_flags & C_INITIALIZED)) + rc = mdb_cursor_first(mc, key, data); + else + rc = mdb_cursor_next(mc, key, data, op); + break; + case MDB_PREV: + case MDB_PREV_DUP: + case MDB_PREV_NODUP: + if (!(mc->mc_flags & C_INITIALIZED) || (mc->mc_flags & C_EOF)) { + rc = mdb_cursor_last(mc, key, data); + mc->mc_flags &= ~C_EOF; + } else + rc = mdb_cursor_prev(mc, key, data, op); + break; + case MDB_FIRST: + rc = mdb_cursor_first(mc, key, data); + break; + case MDB_FIRST_DUP: + if (data == NULL || + !(mc->mc_db->md_flags & MDB_DUPSORT) || + !(mc->mc_flags & C_INITIALIZED) || + !(mc->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED)) { + rc = EINVAL; + break; + } + rc = mdb_cursor_first(&mc->mc_xcursor->mx_cursor, data, NULL); + break; + case MDB_LAST: + rc = mdb_cursor_last(mc, key, data); + break; + case MDB_LAST_DUP: + if (data == NULL || + !(mc->mc_db->md_flags & MDB_DUPSORT) || + !(mc->mc_flags & C_INITIALIZED) || + !(mc->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED)) { + rc = EINVAL; + break; + } + rc = mdb_cursor_last(&mc->mc_xcursor->mx_cursor, data, NULL); + break; + default: + DPRINTF("unhandled/unimplemented cursor operation %u", op); + rc = EINVAL; + break; + } + + return rc; +} + +/** Touch all the pages in the cursor stack. + * Makes sure all the pages are writable, before attempting a write operation. + * @param[in] mc The cursor to operate on. + */ +static int +mdb_cursor_touch(MDB_cursor *mc) +{ + int rc; + + if (mc->mc_dbi > MAIN_DBI && !(*mc->mc_dbflag & DB_DIRTY)) { + MDB_cursor mc2; + mdb_cursor_init(&mc2, mc->mc_txn, MAIN_DBI, NULL); + rc = mdb_page_search(&mc2, &mc->mc_dbx->md_name, MDB_PS_MODIFY); + if (rc) + return rc; + *mc->mc_dbflag = DB_DIRTY; + } + for (mc->mc_top = 0; mc->mc_top < mc->mc_snum; mc->mc_top++) { + rc = mdb_page_touch(mc); + if (rc) + return rc; + } + mc->mc_top = mc->mc_snum-1; + return MDB_SUCCESS; +} + +int +mdb_cursor_put(MDB_cursor *mc, MDB_val *key, MDB_val *data, + unsigned int flags) +{ + MDB_node *leaf = NULL; + MDB_val xdata, *rdata, dkey; + MDB_page *fp; + MDB_db dummy; + int do_sub = 0, insert = 0; + unsigned int mcount = 0; + size_t nsize; + int rc, rc2; + MDB_pagebuf pbuf; + char dbuf[MAXKEYSIZE+1]; + unsigned int nflags; + DKBUF; + + if (F_ISSET(mc->mc_txn->mt_flags, MDB_TXN_RDONLY)) + return EACCES; + + DPRINTF("==> put db %u key [%s], size %zu, data size %zu", + mc->mc_dbi, DKEY(key), key ? key->mv_size:0, data->mv_size); + + dkey.mv_size = 0; + + if (flags == MDB_CURRENT) { + if (!(mc->mc_flags & C_INITIALIZED)) + return EINVAL; + rc = MDB_SUCCESS; + } else if (mc->mc_db->md_root == P_INVALID) { + MDB_page *np; + /* new database, write a root leaf page */ + DPUTS("allocating new root leaf page"); + if ((rc = mdb_page_new(mc, P_LEAF, 1, &np))) { + return rc; + } + mc->mc_snum = 0; + mdb_cursor_push(mc, np); + mc->mc_db->md_root = np->mp_pgno; + mc->mc_db->md_depth++; + *mc->mc_dbflag = DB_DIRTY; + if ((mc->mc_db->md_flags & (MDB_DUPSORT|MDB_DUPFIXED)) + == MDB_DUPFIXED) + np->mp_flags |= P_LEAF2; + mc->mc_flags |= C_INITIALIZED; + rc = MDB_NOTFOUND; + goto top; + } else { + int exact = 0; + MDB_val d2; + if (flags & MDB_APPEND) { + MDB_val k2; + rc = mdb_cursor_last(mc, &k2, &d2); + if (rc == 0) { + rc = mc->mc_dbx->md_cmp(key, &k2); + if (rc > 0) { + rc = MDB_NOTFOUND; + mc->mc_ki[mc->mc_top]++; + } else { + rc = 0; + } + } + } else { + rc = mdb_cursor_set(mc, key, &d2, MDB_SET, &exact); + } + if ((flags & MDB_NOOVERWRITE) && rc == 0) { + DPRINTF("duplicate key [%s]", DKEY(key)); + *data = d2; + return MDB_KEYEXIST; + } + if (rc && rc != MDB_NOTFOUND) + return rc; + } + + /* Cursor is positioned, now make sure all pages are writable */ + rc2 = mdb_cursor_touch(mc); + if (rc2) + return rc2; + +top: + /* The key already exists */ + if (rc == MDB_SUCCESS) { + /* there's only a key anyway, so this is a no-op */ + if (IS_LEAF2(mc->mc_pg[mc->mc_top])) { + unsigned int ksize = mc->mc_db->md_pad; + if (key->mv_size != ksize) + return EINVAL; + if (flags == MDB_CURRENT) { + char *ptr = LEAF2KEY(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top], ksize); + memcpy(ptr, key->mv_data, ksize); + } + return MDB_SUCCESS; + } + + leaf = NODEPTR(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top]); + + /* DB has dups? */ + if (F_ISSET(mc->mc_db->md_flags, MDB_DUPSORT)) { + /* Was a single item before, must convert now */ +more: + if (!F_ISSET(leaf->mn_flags, F_DUPDATA)) { + /* Just overwrite the current item */ + if (flags == MDB_CURRENT) + goto current; + + dkey.mv_size = NODEDSZ(leaf); + dkey.mv_data = NODEDATA(leaf); +#if UINT_MAX < SIZE_MAX + if (mc->mc_dbx->md_dcmp == mdb_cmp_int && dkey.mv_size == sizeof(size_t)) +#ifdef MISALIGNED_OK + mc->mc_dbx->md_dcmp = mdb_cmp_long; +#else + mc->mc_dbx->md_dcmp = mdb_cmp_cint; +#endif +#endif + /* if data matches, ignore it */ + if (!mc->mc_dbx->md_dcmp(data, &dkey)) + return (flags == MDB_NODUPDATA) ? MDB_KEYEXIST : MDB_SUCCESS; + + /* create a fake page for the dup items */ + memcpy(dbuf, dkey.mv_data, dkey.mv_size); + dkey.mv_data = dbuf; + fp = (MDB_page *)&pbuf; + fp->mp_pgno = mc->mc_pg[mc->mc_top]->mp_pgno; + fp->mp_flags = P_LEAF|P_DIRTY|P_SUBP; + fp->mp_lower = PAGEHDRSZ; + fp->mp_upper = PAGEHDRSZ + dkey.mv_size + data->mv_size; + if (mc->mc_db->md_flags & MDB_DUPFIXED) { + fp->mp_flags |= P_LEAF2; + fp->mp_pad = data->mv_size; + fp->mp_upper += 2 * data->mv_size; /* leave space for 2 more */ + } else { + fp->mp_upper += 2 * sizeof(indx_t) + 2 * NODESIZE + + (dkey.mv_size & 1) + (data->mv_size & 1); + } + mdb_node_del(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top], 0); + do_sub = 1; + rdata = &xdata; + xdata.mv_size = fp->mp_upper; + xdata.mv_data = fp; + flags |= F_DUPDATA; + goto new_sub; + } + if (!F_ISSET(leaf->mn_flags, F_SUBDATA)) { + /* See if we need to convert from fake page to subDB */ + MDB_page *mp; + unsigned int offset; + unsigned int i; + + fp = NODEDATA(leaf); + if (flags == MDB_CURRENT) { +reuse: + fp->mp_flags |= P_DIRTY; + COPY_PGNO(fp->mp_pgno, mc->mc_pg[mc->mc_top]->mp_pgno); + mc->mc_xcursor->mx_cursor.mc_pg[0] = fp; + flags |= F_DUPDATA; + goto put_sub; + } + if (mc->mc_db->md_flags & MDB_DUPFIXED) { + offset = fp->mp_pad; + if (SIZELEFT(fp) >= offset) + goto reuse; + offset *= 4; /* space for 4 more */ + } else { + offset = NODESIZE + sizeof(indx_t) + data->mv_size; + } + offset += offset & 1; + if (NODESIZE + sizeof(indx_t) + NODEKSZ(leaf) + NODEDSZ(leaf) + + offset >= (mc->mc_txn->mt_env->me_psize - PAGEHDRSZ) / + MDB_MINKEYS) { + /* yes, convert it */ + dummy.md_flags = 0; + if (mc->mc_db->md_flags & MDB_DUPFIXED) { + dummy.md_pad = fp->mp_pad; + dummy.md_flags = MDB_DUPFIXED; + if (mc->mc_db->md_flags & MDB_INTEGERDUP) + dummy.md_flags |= MDB_INTEGERKEY; + } + dummy.md_depth = 1; + dummy.md_branch_pages = 0; + dummy.md_leaf_pages = 1; + dummy.md_overflow_pages = 0; + dummy.md_entries = NUMKEYS(fp); + rdata = &xdata; + xdata.mv_size = sizeof(MDB_db); + xdata.mv_data = &dummy; + if ((rc = mdb_page_alloc(mc, 1, &mp))) + return rc; + offset = mc->mc_txn->mt_env->me_psize - NODEDSZ(leaf); + flags |= F_DUPDATA|F_SUBDATA; + dummy.md_root = mp->mp_pgno; + } else { + /* no, just grow it */ + rdata = &xdata; + xdata.mv_size = NODEDSZ(leaf) + offset; + xdata.mv_data = &pbuf; + mp = (MDB_page *)&pbuf; + mp->mp_pgno = mc->mc_pg[mc->mc_top]->mp_pgno; + flags |= F_DUPDATA; + } + mp->mp_flags = fp->mp_flags | P_DIRTY; + mp->mp_pad = fp->mp_pad; + mp->mp_lower = fp->mp_lower; + mp->mp_upper = fp->mp_upper + offset; + if (IS_LEAF2(fp)) { + memcpy(METADATA(mp), METADATA(fp), NUMKEYS(fp) * fp->mp_pad); + } else { + nsize = NODEDSZ(leaf) - fp->mp_upper; + memcpy((char *)mp + mp->mp_upper, (char *)fp + fp->mp_upper, nsize); + for (i=0; imp_ptrs[i] = fp->mp_ptrs[i] + offset; + } + mdb_node_del(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top], 0); + do_sub = 1; + goto new_sub; + } + /* data is on sub-DB, just store it */ + flags |= F_DUPDATA|F_SUBDATA; + goto put_sub; + } +current: + /* overflow page overwrites need special handling */ + if (F_ISSET(leaf->mn_flags, F_BIGDATA)) { + MDB_page *omp; + pgno_t pg; + int ovpages, dpages; + + ovpages = OVPAGES(NODEDSZ(leaf), mc->mc_txn->mt_env->me_psize); + dpages = OVPAGES(data->mv_size, mc->mc_txn->mt_env->me_psize); + memcpy(&pg, NODEDATA(leaf), sizeof(pg)); + mdb_page_get(mc->mc_txn, pg, &omp); + /* Is the ov page writable and large enough? */ + if ((omp->mp_flags & P_DIRTY) && ovpages >= dpages) { + /* yes, overwrite it. Note in this case we don't + * bother to try shrinking the node if the new data + * is smaller than the overflow threshold. + */ + if (F_ISSET(flags, MDB_RESERVE)) + data->mv_data = METADATA(omp); + else + memcpy(METADATA(omp), data->mv_data, data->mv_size); + goto done; + } else { + /* no, free ovpages */ + int i; + mc->mc_db->md_overflow_pages -= ovpages; + for (i=0; imc_txn->mt_free_pgs, pg); + pg++; + } + } + } else if (NODEDSZ(leaf) == data->mv_size) { + /* same size, just replace it. Note that we could + * also reuse this node if the new data is smaller, + * but instead we opt to shrink the node in that case. + */ + if (F_ISSET(flags, MDB_RESERVE)) + data->mv_data = NODEDATA(leaf); + else + memcpy(NODEDATA(leaf), data->mv_data, data->mv_size); + goto done; + } + mdb_node_del(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top], 0); + mc->mc_db->md_entries--; + } else { + DPRINTF("inserting key at index %i", mc->mc_ki[mc->mc_top]); + insert = 1; + } + + rdata = data; + +new_sub: + nflags = flags & NODE_ADD_FLAGS; + nsize = IS_LEAF2(mc->mc_pg[mc->mc_top]) ? key->mv_size : mdb_leaf_size(mc->mc_txn->mt_env, key, rdata); + if (SIZELEFT(mc->mc_pg[mc->mc_top]) < nsize) { + if (( flags & (F_DUPDATA|F_SUBDATA)) == F_DUPDATA ) + nflags &= ~MDB_APPEND; + if (!insert) + nflags |= MDB_SPLIT_REPLACE; + rc = mdb_page_split(mc, key, rdata, P_INVALID, nflags); + } else { + /* There is room already in this leaf page. */ + rc = mdb_node_add(mc, mc->mc_ki[mc->mc_top], key, rdata, 0, nflags); + if (rc == 0 && !do_sub && insert) { + /* Adjust other cursors pointing to mp */ + MDB_cursor *m2, *m3; + MDB_dbi dbi = mc->mc_dbi; + unsigned i = mc->mc_top; + MDB_page *mp = mc->mc_pg[i]; + + if (mc->mc_flags & C_SUB) + dbi--; + + for (m2 = mc->mc_txn->mt_cursors[dbi]; m2; m2=m2->mc_next) { + if (mc->mc_flags & C_SUB) + m3 = &m2->mc_xcursor->mx_cursor; + else + m3 = m2; + if (m3 == mc || m3->mc_snum < mc->mc_snum) continue; + if (m3->mc_pg[i] == mp && m3->mc_ki[i] >= mc->mc_ki[i]) { + m3->mc_ki[i]++; + } + } + } + } + + if (rc != MDB_SUCCESS) + mc->mc_txn->mt_flags |= MDB_TXN_ERROR; + else { + /* Now store the actual data in the child DB. Note that we're + * storing the user data in the keys field, so there are strict + * size limits on dupdata. The actual data fields of the child + * DB are all zero size. + */ + if (do_sub) { + int xflags; +put_sub: + xdata.mv_size = 0; + xdata.mv_data = ""; + leaf = NODEPTR(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top]); + if (flags & MDB_CURRENT) { + xflags = MDB_CURRENT; + } else { + mdb_xcursor_init1(mc, leaf); + xflags = (flags & MDB_NODUPDATA) ? MDB_NOOVERWRITE : 0; + } + /* converted, write the original data first */ + if (dkey.mv_size) { + rc = mdb_cursor_put(&mc->mc_xcursor->mx_cursor, &dkey, &xdata, xflags); + if (rc) + return rc; + { + /* Adjust other cursors pointing to mp */ + MDB_cursor *m2; + unsigned i = mc->mc_top; + MDB_page *mp = mc->mc_pg[i]; + + for (m2 = mc->mc_txn->mt_cursors[mc->mc_dbi]; m2; m2=m2->mc_next) { + if (m2 == mc || m2->mc_snum < mc->mc_snum) continue; + if (m2->mc_pg[i] == mp && m2->mc_ki[i] == mc->mc_ki[i]) { + mdb_xcursor_init1(m2, leaf); + } + } + } + } + if (flags & MDB_APPENDDUP) + xflags |= MDB_APPEND; + 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)); + } + } + /* sub-writes might have failed so check rc again. + * Don't increment count if we just replaced an existing item. + */ + if (!rc && !(flags & MDB_CURRENT)) + mc->mc_db->md_entries++; + if (flags & MDB_MULTIPLE) { + mcount++; + if (mcount < data[1].mv_size) { + data[0].mv_data = (char *)data[0].mv_data + data[0].mv_size; + leaf = NODEPTR(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top]); + goto more; + } + } + } +done: + return rc; +} + +int +mdb_cursor_del(MDB_cursor *mc, unsigned int flags) +{ + MDB_node *leaf; + int rc; + + if (F_ISSET(mc->mc_txn->mt_flags, MDB_TXN_RDONLY)) + return EACCES; + + if (!mc->mc_flags & C_INITIALIZED) + return EINVAL; + + rc = mdb_cursor_touch(mc); + if (rc) + return rc; + + leaf = NODEPTR(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top]); + + if (!IS_LEAF2(mc->mc_pg[mc->mc_top]) && F_ISSET(leaf->mn_flags, F_DUPDATA)) { + if (flags != MDB_NODUPDATA) { + 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, 0); + /* If sub-DB still has entries, we're done */ + if (mc->mc_xcursor->mx_db.md_entries) { + if (leaf->mn_flags & F_SUBDATA) { + /* update subDB info */ + void *db = NODEDATA(leaf); + memcpy(db, &mc->mc_xcursor->mx_db, sizeof(MDB_db)); + } else { + /* shrink fake page */ + mdb_node_shrink(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top]); + } + mc->mc_db->md_entries--; + return rc; + } + /* otherwise fall thru and delete the sub-DB */ + } + + if (leaf->mn_flags & F_SUBDATA) { + /* add all the child DB's pages to the free list */ + rc = mdb_drop0(&mc->mc_xcursor->mx_cursor, 0); + if (rc == MDB_SUCCESS) { + mc->mc_db->md_entries -= + mc->mc_xcursor->mx_db.md_entries; + } + } + } + + return mdb_cursor_del0(mc, leaf); +} + +/** Allocate and initialize new pages for a database. + * @param[in] mc a cursor on the database being added to. + * @param[in] flags flags defining what type of page is being allocated. + * @param[in] num the number of pages to allocate. This is usually 1, + * unless allocating overflow pages for a large record. + * @param[out] mp Address of a page, or NULL on failure. + * @return 0 on success, non-zero on failure. + */ +static int +mdb_page_new(MDB_cursor *mc, uint32_t flags, int num, MDB_page **mp) +{ + MDB_page *np; + int rc; + + if ((rc = mdb_page_alloc(mc, num, &np))) + return rc; + DPRINTF("allocated new mpage %zu, page size %u", + np->mp_pgno, mc->mc_txn->mt_env->me_psize); + np->mp_flags = flags | P_DIRTY; + np->mp_lower = PAGEHDRSZ; + np->mp_upper = mc->mc_txn->mt_env->me_psize; + + if (IS_BRANCH(np)) + mc->mc_db->md_branch_pages++; + else if (IS_LEAF(np)) + mc->mc_db->md_leaf_pages++; + else if (IS_OVERFLOW(np)) { + mc->mc_db->md_overflow_pages += num; + np->mp_pages = num; + } + *mp = np; + + return 0; +} + +/** Calculate the size of a leaf node. + * The size depends on the environment's page size; if a data item + * is too large it will be put onto an overflow page and the node + * size will only include the key and not the data. Sizes are always + * rounded up to an even number of bytes, to guarantee 2-byte alignment + * of the #MDB_node headers. + * @param[in] env The environment handle. + * @param[in] key The key for the node. + * @param[in] data The data for the node. + * @return The number of bytes needed to store the node. + */ +static size_t +mdb_leaf_size(MDB_env *env, MDB_val *key, MDB_val *data) +{ + size_t sz; + + sz = LEAFSIZE(key, data); + if (sz >= env->me_psize / MDB_MINKEYS) { + /* put on overflow page */ + sz -= data->mv_size - sizeof(pgno_t); + } + sz += sz & 1; + + return sz + sizeof(indx_t); +} + +/** Calculate the size of a branch node. + * The size should depend on the environment's page size but since + * we currently don't support spilling large keys onto overflow + * pages, it's simply the size of the #MDB_node header plus the + * size of the key. Sizes are always rounded up to an even number + * of bytes, to guarantee 2-byte alignment of the #MDB_node headers. + * @param[in] env The environment handle. + * @param[in] key The key for the node. + * @return The number of bytes needed to store the node. + */ +static size_t +mdb_branch_size(MDB_env *env, MDB_val *key) +{ + size_t sz; + + sz = INDXSIZE(key); + if (sz >= env->me_psize / MDB_MINKEYS) { + /* put on overflow page */ + /* not implemented */ + /* sz -= key->size - sizeof(pgno_t); */ + } + + return sz + sizeof(indx_t); +} + +/** Add a node to the page pointed to by the cursor. + * @param[in] mc The cursor for this operation. + * @param[in] indx The index on the page where the new node should be added. + * @param[in] key The key for the new node. + * @param[in] data The data for the new node, if any. + * @param[in] pgno The page number, if adding a branch node. + * @param[in] flags Flags for the node. + * @return 0 on success, non-zero on failure. Possible errors are: + *
    + *
  • ENOMEM - failed to allocate overflow pages for the node. + *
  • MDB_PAGE_FULL - there is insufficient room in the page. This error + * should never happen since all callers already calculate the + * page's free space before calling this function. + *
+ */ +static int +mdb_node_add(MDB_cursor *mc, indx_t indx, + MDB_val *key, MDB_val *data, pgno_t pgno, unsigned int flags) +{ + unsigned int i; + size_t node_size = NODESIZE; + indx_t ofs; + MDB_node *node; + MDB_page *mp = mc->mc_pg[mc->mc_top]; + MDB_page *ofp = NULL; /* overflow page */ + DKBUF; + + assert(mp->mp_upper >= mp->mp_lower); + + DPRINTF("add to %s %spage %zu index %i, data size %zu key size %zu [%s]", + IS_LEAF(mp) ? "leaf" : "branch", + IS_SUBP(mp) ? "sub-" : "", + mp->mp_pgno, indx, data ? data->mv_size : 0, + key ? key->mv_size : 0, key ? DKEY(key) : NULL); + + if (IS_LEAF2(mp)) { + /* Move higher keys up one slot. */ + int ksize = mc->mc_db->md_pad, dif; + char *ptr = LEAF2KEY(mp, indx, ksize); + dif = NUMKEYS(mp) - indx; + if (dif > 0) + memmove(ptr+ksize, ptr, dif*ksize); + /* insert new key */ + memcpy(ptr, key->mv_data, ksize); + + /* Just using these for counting */ + mp->mp_lower += sizeof(indx_t); + mp->mp_upper -= ksize - sizeof(indx_t); + return MDB_SUCCESS; + } + + if (key != NULL) + node_size += key->mv_size; + + if (IS_LEAF(mp)) { + assert(data); + if (F_ISSET(flags, F_BIGDATA)) { + /* Data already on overflow page. */ + node_size += sizeof(pgno_t); + } else if (node_size + data->mv_size >= mc->mc_txn->mt_env->me_psize / MDB_MINKEYS) { + int ovpages = OVPAGES(data->mv_size, mc->mc_txn->mt_env->me_psize); + int rc; + /* Put data on overflow page. */ + DPRINTF("data size is %zu, node would be %zu, put data on overflow page", + data->mv_size, node_size+data->mv_size); + node_size += sizeof(pgno_t); + if ((rc = mdb_page_new(mc, P_OVERFLOW, ovpages, &ofp))) + return rc; + DPRINTF("allocated overflow page %zu", ofp->mp_pgno); + flags |= F_BIGDATA; + } else { + node_size += data->mv_size; + } + } + node_size += node_size & 1; + + if (node_size + sizeof(indx_t) > SIZELEFT(mp)) { + DPRINTF("not enough room in page %zu, got %u ptrs", + mp->mp_pgno, NUMKEYS(mp)); + DPRINTF("upper - lower = %u - %u = %u", mp->mp_upper, mp->mp_lower, + mp->mp_upper - mp->mp_lower); + DPRINTF("node size = %zu", node_size); + return MDB_PAGE_FULL; + } + + /* Move higher pointers up one slot. */ + for (i = NUMKEYS(mp); i > indx; i--) + mp->mp_ptrs[i] = mp->mp_ptrs[i - 1]; + + /* Adjust free space offsets. */ + ofs = mp->mp_upper - node_size; + assert(ofs >= mp->mp_lower + sizeof(indx_t)); + mp->mp_ptrs[indx] = ofs; + mp->mp_upper = ofs; + mp->mp_lower += sizeof(indx_t); + + /* Write the node data. */ + node = NODEPTR(mp, indx); + node->mn_ksize = (key == NULL) ? 0 : key->mv_size; + node->mn_flags = flags; + if (IS_LEAF(mp)) + SETDSZ(node,data->mv_size); + else + SETPGNO(node,pgno); + + if (key) + memcpy(NODEKEY(node), key->mv_data, key->mv_size); + + if (IS_LEAF(mp)) { + assert(key); + if (ofp == NULL) { + if (F_ISSET(flags, F_BIGDATA)) + memcpy(node->mn_data + key->mv_size, data->mv_data, + sizeof(pgno_t)); + else if (F_ISSET(flags, MDB_RESERVE)) + data->mv_data = node->mn_data + key->mv_size; + else + memcpy(node->mn_data + key->mv_size, data->mv_data, + data->mv_size); + } else { + memcpy(node->mn_data + key->mv_size, &ofp->mp_pgno, + sizeof(pgno_t)); + if (F_ISSET(flags, MDB_RESERVE)) + data->mv_data = METADATA(ofp); + else + memcpy(METADATA(ofp), data->mv_data, data->mv_size); + } + } + + return MDB_SUCCESS; +} + +/** Delete the specified node from a page. + * @param[in] mp The page to operate on. + * @param[in] indx The index of the node to delete. + * @param[in] ksize The size of a node. Only used if the page is + * part of a #MDB_DUPFIXED database. + */ +static void +mdb_node_del(MDB_page *mp, indx_t indx, int ksize) +{ + unsigned int sz; + indx_t i, j, numkeys, ptr; + MDB_node *node; + char *base; + +#if MDB_DEBUG + { + pgno_t pgno; + COPY_PGNO(pgno, mp->mp_pgno); + DPRINTF("delete node %u on %s page %zu", indx, + IS_LEAF(mp) ? "leaf" : "branch", pgno); + } +#endif + assert(indx < NUMKEYS(mp)); + + if (IS_LEAF2(mp)) { + int x = NUMKEYS(mp) - 1 - indx; + base = LEAF2KEY(mp, indx, ksize); + if (x) + memmove(base, base + ksize, x * ksize); + mp->mp_lower -= sizeof(indx_t); + mp->mp_upper += ksize - sizeof(indx_t); + return; + } + + node = NODEPTR(mp, indx); + sz = NODESIZE + node->mn_ksize; + if (IS_LEAF(mp)) { + if (F_ISSET(node->mn_flags, F_BIGDATA)) + sz += sizeof(pgno_t); + else + sz += NODEDSZ(node); + } + sz += sz & 1; + + ptr = mp->mp_ptrs[indx]; + numkeys = NUMKEYS(mp); + for (i = j = 0; i < numkeys; i++) { + if (i != indx) { + mp->mp_ptrs[j] = mp->mp_ptrs[i]; + if (mp->mp_ptrs[i] < ptr) + mp->mp_ptrs[j] += sz; + j++; + } + } + + base = (char *)mp + mp->mp_upper; + memmove(base + sz, base, ptr - mp->mp_upper); + + mp->mp_lower -= sizeof(indx_t); + mp->mp_upper += sz; +} + +/** Compact the main page after deleting a node on a subpage. + * @param[in] mp The main page to operate on. + * @param[in] indx The index of the subpage on the main page. + */ +static void +mdb_node_shrink(MDB_page *mp, indx_t indx) +{ + MDB_node *node; + MDB_page *sp, *xp; + char *base; + int osize, nsize; + int delta; + indx_t i, numkeys, ptr; + + node = NODEPTR(mp, indx); + sp = (MDB_page *)NODEDATA(node); + osize = NODEDSZ(node); + + delta = sp->mp_upper - sp->mp_lower; + SETDSZ(node, osize - delta); + xp = (MDB_page *)((char *)sp + delta); + + /* shift subpage upward */ + if (IS_LEAF2(sp)) { + nsize = NUMKEYS(sp) * sp->mp_pad; + memmove(METADATA(xp), METADATA(sp), nsize); + } else { + int i; + nsize = osize - sp->mp_upper; + numkeys = NUMKEYS(sp); + for (i=numkeys-1; i>=0; i--) + xp->mp_ptrs[i] = sp->mp_ptrs[i] - delta; + } + xp->mp_upper = sp->mp_lower; + xp->mp_lower = sp->mp_lower; + xp->mp_flags = sp->mp_flags; + xp->mp_pad = sp->mp_pad; + COPY_PGNO(xp->mp_pgno, mp->mp_pgno); + + /* shift lower nodes upward */ + ptr = mp->mp_ptrs[indx]; + numkeys = NUMKEYS(mp); + for (i = 0; i < numkeys; i++) { + if (mp->mp_ptrs[i] <= ptr) + mp->mp_ptrs[i] += delta; + } + + base = (char *)mp + mp->mp_upper; + memmove(base + delta, base, ptr - mp->mp_upper + NODESIZE + NODEKSZ(node)); + mp->mp_upper += delta; +} + +/** Initial setup of a sorted-dups cursor. + * Sorted duplicates are implemented as a sub-database for the given key. + * The duplicate data items are actually keys of the sub-database. + * Operations on the duplicate data items are performed using a sub-cursor + * initialized when the sub-database is first accessed. This function does + * the preliminary setup of the sub-cursor, filling in the fields that + * depend only on the parent DB. + * @param[in] mc The main cursor whose sorted-dups cursor is to be initialized. + */ +static void +mdb_xcursor_init0(MDB_cursor *mc) +{ + MDB_xcursor *mx = mc->mc_xcursor; + + mx->mx_cursor.mc_xcursor = NULL; + mx->mx_cursor.mc_txn = mc->mc_txn; + mx->mx_cursor.mc_db = &mx->mx_db; + mx->mx_cursor.mc_dbx = &mx->mx_dbx; + mx->mx_cursor.mc_dbi = mc->mc_dbi+1; + mx->mx_cursor.mc_dbflag = &mx->mx_dbflag; + mx->mx_cursor.mc_snum = 0; + mx->mx_cursor.mc_top = 0; + mx->mx_cursor.mc_flags = C_SUB; + mx->mx_dbx.md_cmp = mc->mc_dbx->md_dcmp; + mx->mx_dbx.md_dcmp = NULL; + mx->mx_dbx.md_rel = mc->mc_dbx->md_rel; +} + +/** Final setup of a sorted-dups cursor. + * Sets up the fields that depend on the data from the main cursor. + * @param[in] mc The main cursor whose sorted-dups cursor is to be initialized. + * @param[in] node The data containing the #MDB_db record for the + * sorted-dup database. + */ +static void +mdb_xcursor_init1(MDB_cursor *mc, MDB_node *node) +{ + MDB_xcursor *mx = mc->mc_xcursor; + + if (node->mn_flags & F_SUBDATA) { + memcpy(&mx->mx_db, NODEDATA(node), sizeof(MDB_db)); + mx->mx_cursor.mc_pg[0] = 0; + mx->mx_cursor.mc_snum = 0; + mx->mx_cursor.mc_flags = C_SUB; + } else { + MDB_page *fp = NODEDATA(node); + mx->mx_db.md_pad = mc->mc_pg[mc->mc_top]->mp_pad; + mx->mx_db.md_flags = 0; + mx->mx_db.md_depth = 1; + mx->mx_db.md_branch_pages = 0; + mx->mx_db.md_leaf_pages = 1; + mx->mx_db.md_overflow_pages = 0; + mx->mx_db.md_entries = NUMKEYS(fp); + COPY_PGNO(mx->mx_db.md_root, fp->mp_pgno); + mx->mx_cursor.mc_snum = 1; + mx->mx_cursor.mc_flags = C_INITIALIZED|C_SUB; + mx->mx_cursor.mc_top = 0; + mx->mx_cursor.mc_pg[0] = fp; + mx->mx_cursor.mc_ki[0] = 0; + if (mc->mc_db->md_flags & MDB_DUPFIXED) { + mx->mx_db.md_flags = MDB_DUPFIXED; + mx->mx_db.md_pad = fp->mp_pad; + if (mc->mc_db->md_flags & MDB_INTEGERDUP) + mx->mx_db.md_flags |= MDB_INTEGERKEY; + } + } + DPRINTF("Sub-db %u for db %u root page %zu", mx->mx_cursor.mc_dbi, mc->mc_dbi, + mx->mx_db.md_root); + mx->mx_dbflag = (F_ISSET(mc->mc_pg[mc->mc_top]->mp_flags, P_DIRTY)) ? + DB_DIRTY : 0; + mx->mx_dbx.md_name.mv_data = NODEKEY(node); + mx->mx_dbx.md_name.mv_size = node->mn_ksize; +#if UINT_MAX < SIZE_MAX + if (mx->mx_dbx.md_cmp == mdb_cmp_int && mx->mx_db.md_pad == sizeof(size_t)) +#ifdef MISALIGNED_OK + mx->mx_dbx.md_cmp = mdb_cmp_long; +#else + mx->mx_dbx.md_cmp = mdb_cmp_cint; +#endif +#endif +} + +/** Initialize a cursor for a given transaction and database. */ +static void +mdb_cursor_init(MDB_cursor *mc, MDB_txn *txn, MDB_dbi dbi, MDB_xcursor *mx) +{ + mc->mc_orig = NULL; + mc->mc_dbi = dbi; + mc->mc_txn = txn; + mc->mc_db = &txn->mt_dbs[dbi]; + mc->mc_dbx = &txn->mt_dbxs[dbi]; + mc->mc_dbflag = &txn->mt_dbflags[dbi]; + mc->mc_snum = 0; + mc->mc_top = 0; + mc->mc_pg[0] = 0; + mc->mc_flags = 0; + if (txn->mt_dbs[dbi].md_flags & MDB_DUPSORT) { + assert(mx != NULL); + mc->mc_xcursor = mx; + mdb_xcursor_init0(mc); + } else { + mc->mc_xcursor = NULL; + } + if (*mc->mc_dbflag & DB_STALE) { + mdb_page_search(mc, NULL, MDB_PS_ROOTONLY); + } +} + +int +mdb_cursor_open(MDB_txn *txn, MDB_dbi dbi, MDB_cursor **ret) +{ + MDB_cursor *mc; + MDB_xcursor *mx = NULL; + size_t size = sizeof(MDB_cursor); + + if (txn == NULL || ret == NULL || dbi >= txn->mt_numdbs) + return EINVAL; + + /* Allow read access to the freelist */ + if (!dbi && !F_ISSET(txn->mt_flags, MDB_TXN_RDONLY)) + return EINVAL; + + if (txn->mt_dbs[dbi].md_flags & MDB_DUPSORT) + size += sizeof(MDB_xcursor); + + if ((mc = malloc(size)) != NULL) { + if (txn->mt_dbs[dbi].md_flags & MDB_DUPSORT) { + mx = (MDB_xcursor *)(mc + 1); + } + mdb_cursor_init(mc, txn, dbi, mx); + if (txn->mt_cursors) { + mc->mc_next = txn->mt_cursors[dbi]; + txn->mt_cursors[dbi] = mc; + } + mc->mc_flags |= C_ALLOCD; + } else { + return ENOMEM; + } + + *ret = mc; + + return MDB_SUCCESS; +} + +int +mdb_cursor_renew(MDB_txn *txn, MDB_cursor *mc) +{ + if (txn == NULL || mc == NULL || mc->mc_dbi >= txn->mt_numdbs) + return EINVAL; + + if (txn->mt_cursors) + return EINVAL; + + mdb_cursor_init(mc, txn, mc->mc_dbi, mc->mc_xcursor); + return MDB_SUCCESS; +} + +/* Return the count of duplicate data items for the current key */ +int +mdb_cursor_count(MDB_cursor *mc, size_t *countp) +{ + MDB_node *leaf; + + if (mc == NULL || countp == NULL) + return EINVAL; + + if (!(mc->mc_db->md_flags & MDB_DUPSORT)) + return EINVAL; + + leaf = NODEPTR(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top]); + if (!F_ISSET(leaf->mn_flags, F_DUPDATA)) { + *countp = 1; + } else { + if (!(mc->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED)) + return EINVAL; + + *countp = mc->mc_xcursor->mx_db.md_entries; + } + return MDB_SUCCESS; +} + +void +mdb_cursor_close(MDB_cursor *mc) +{ + if (mc != NULL) { + /* remove from txn, if tracked */ + if (mc->mc_txn->mt_cursors) { + MDB_cursor **prev = &mc->mc_txn->mt_cursors[mc->mc_dbi]; + while (*prev && *prev != mc) prev = &(*prev)->mc_next; + if (*prev == mc) + *prev = mc->mc_next; + } + if (mc->mc_flags & C_ALLOCD) + free(mc); + } +} + +MDB_txn * +mdb_cursor_txn(MDB_cursor *mc) +{ + if (!mc) return NULL; + return mc->mc_txn; +} + +MDB_dbi +mdb_cursor_dbi(MDB_cursor *mc) +{ + if (!mc) return 0; + return mc->mc_dbi; +} + +/** Replace the key for a node with a new key. + * @param[in] mp The page containing the node to operate on. + * @param[in] indx The index of the node to operate on. + * @param[in] key The new key to use. + * @return 0 on success, non-zero on failure. + */ +static int +mdb_update_key(MDB_page *mp, indx_t indx, MDB_val *key) +{ + MDB_node *node; + char *base; + size_t len; + int delta, delta0; + indx_t ptr, i, numkeys; + DKBUF; + + node = NODEPTR(mp, indx); + ptr = mp->mp_ptrs[indx]; +#if MDB_DEBUG + { + MDB_val k2; + char kbuf2[(MAXKEYSIZE*2+1)]; + k2.mv_data = NODEKEY(node); + k2.mv_size = node->mn_ksize; + DPRINTF("update key %u (ofs %u) [%s] to [%s] on page %zu", + indx, ptr, + mdb_dkey(&k2, kbuf2), + DKEY(key), + mp->mp_pgno); + } +#endif + + delta0 = delta = key->mv_size - node->mn_ksize; + + /* Must be 2-byte aligned. If new key is + * shorter by 1, the shift will be skipped. + */ + delta += (delta & 1); + if (delta) { + if (delta > 0 && SIZELEFT(mp) < delta) { + DPRINTF("OUCH! Not enough room, delta = %d", delta); + return MDB_PAGE_FULL; + } + + numkeys = NUMKEYS(mp); + for (i = 0; i < numkeys; i++) { + if (mp->mp_ptrs[i] <= ptr) + mp->mp_ptrs[i] -= delta; + } + + base = (char *)mp + mp->mp_upper; + len = ptr - mp->mp_upper + NODESIZE; + memmove(base - delta, base, len); + mp->mp_upper -= delta; + + node = NODEPTR(mp, indx); + } + + /* But even if no shift was needed, update ksize */ + if (delta0) + node->mn_ksize = key->mv_size; + + if (key->mv_size) + memcpy(NODEKEY(node), key->mv_data, key->mv_size); + + return MDB_SUCCESS; +} + +/** Move a node from csrc to cdst. + */ +static int +mdb_node_move(MDB_cursor *csrc, MDB_cursor *cdst) +{ + int rc; + MDB_node *srcnode; + MDB_val key, data; + pgno_t srcpg; + unsigned short flags; + + DKBUF; + + /* Mark src and dst as dirty. */ + if ((rc = mdb_page_touch(csrc)) || + (rc = mdb_page_touch(cdst))) + return rc; + + if (IS_LEAF2(csrc->mc_pg[csrc->mc_top])) { + srcnode = NODEPTR(csrc->mc_pg[csrc->mc_top], 0); /* fake */ + key.mv_size = csrc->mc_db->md_pad; + key.mv_data = LEAF2KEY(csrc->mc_pg[csrc->mc_top], csrc->mc_ki[csrc->mc_top], key.mv_size); + data.mv_size = 0; + data.mv_data = NULL; + srcpg = 0; + flags = 0; + } else { + srcnode = NODEPTR(csrc->mc_pg[csrc->mc_top], csrc->mc_ki[csrc->mc_top]); + assert(!((long)srcnode&1)); + srcpg = NODEPGNO(srcnode); + flags = srcnode->mn_flags; + if (csrc->mc_ki[csrc->mc_top] == 0 && IS_BRANCH(csrc->mc_pg[csrc->mc_top])) { + unsigned int snum = csrc->mc_snum; + MDB_node *s2; + /* must find the lowest key below src */ + mdb_page_search_root(csrc, NULL, 0); + if (IS_LEAF2(csrc->mc_pg[csrc->mc_top])) { + key.mv_size = csrc->mc_db->md_pad; + key.mv_data = LEAF2KEY(csrc->mc_pg[csrc->mc_top], 0, key.mv_size); + } else { + s2 = NODEPTR(csrc->mc_pg[csrc->mc_top], 0); + key.mv_size = NODEKSZ(s2); + key.mv_data = NODEKEY(s2); + } + csrc->mc_snum = snum--; + csrc->mc_top = snum; + } else { + key.mv_size = NODEKSZ(srcnode); + key.mv_data = NODEKEY(srcnode); + } + data.mv_size = NODEDSZ(srcnode); + data.mv_data = NODEDATA(srcnode); + } + if (IS_BRANCH(cdst->mc_pg[cdst->mc_top]) && cdst->mc_ki[cdst->mc_top] == 0) { + unsigned int snum = cdst->mc_snum; + MDB_node *s2; + MDB_val bkey; + /* must find the lowest key below dst */ + mdb_page_search_root(cdst, NULL, 0); + if (IS_LEAF2(cdst->mc_pg[cdst->mc_top])) { + bkey.mv_size = cdst->mc_db->md_pad; + bkey.mv_data = LEAF2KEY(cdst->mc_pg[cdst->mc_top], 0, bkey.mv_size); + } else { + s2 = NODEPTR(cdst->mc_pg[cdst->mc_top], 0); + bkey.mv_size = NODEKSZ(s2); + bkey.mv_data = NODEKEY(s2); + } + cdst->mc_snum = snum--; + cdst->mc_top = snum; + rc = mdb_update_key(cdst->mc_pg[cdst->mc_top], 0, &bkey); + } + + DPRINTF("moving %s node %u [%s] on page %zu to node %u on page %zu", + IS_LEAF(csrc->mc_pg[csrc->mc_top]) ? "leaf" : "branch", + csrc->mc_ki[csrc->mc_top], + DKEY(&key), + csrc->mc_pg[csrc->mc_top]->mp_pgno, + cdst->mc_ki[cdst->mc_top], cdst->mc_pg[cdst->mc_top]->mp_pgno); + + /* Add the node to the destination page. + */ + rc = mdb_node_add(cdst, cdst->mc_ki[cdst->mc_top], &key, &data, srcpg, flags); + if (rc != MDB_SUCCESS) + return rc; + + /* Delete the node from the source page. + */ + mdb_node_del(csrc->mc_pg[csrc->mc_top], csrc->mc_ki[csrc->mc_top], key.mv_size); + + { + /* Adjust other cursors pointing to mp */ + MDB_cursor *m2, *m3; + MDB_dbi dbi = csrc->mc_dbi; + MDB_page *mp = csrc->mc_pg[csrc->mc_top]; + + if (csrc->mc_flags & C_SUB) + dbi--; + + for (m2 = csrc->mc_txn->mt_cursors[dbi]; m2; m2=m2->mc_next) { + if (m2 == csrc) continue; + if (csrc->mc_flags & C_SUB) + m3 = &m2->mc_xcursor->mx_cursor; + else + m3 = m2; + if (m3->mc_pg[csrc->mc_top] == mp && m3->mc_ki[csrc->mc_top] == + csrc->mc_ki[csrc->mc_top]) { + m3->mc_pg[csrc->mc_top] = cdst->mc_pg[cdst->mc_top]; + m3->mc_ki[csrc->mc_top] = cdst->mc_ki[cdst->mc_top]; + } + } + } + + /* Update the parent separators. + */ + if (csrc->mc_ki[csrc->mc_top] == 0) { + if (csrc->mc_ki[csrc->mc_top-1] != 0) { + if (IS_LEAF2(csrc->mc_pg[csrc->mc_top])) { + key.mv_data = LEAF2KEY(csrc->mc_pg[csrc->mc_top], 0, key.mv_size); + } else { + srcnode = NODEPTR(csrc->mc_pg[csrc->mc_top], 0); + key.mv_size = NODEKSZ(srcnode); + key.mv_data = NODEKEY(srcnode); + } + DPRINTF("update separator for source page %zu to [%s]", + csrc->mc_pg[csrc->mc_top]->mp_pgno, DKEY(&key)); + if ((rc = mdb_update_key(csrc->mc_pg[csrc->mc_top-1], csrc->mc_ki[csrc->mc_top-1], + &key)) != MDB_SUCCESS) + return rc; + } + if (IS_BRANCH(csrc->mc_pg[csrc->mc_top])) { + MDB_val nullkey; + nullkey.mv_size = 0; + rc = mdb_update_key(csrc->mc_pg[csrc->mc_top], 0, &nullkey); + assert(rc == MDB_SUCCESS); + } + } + + if (cdst->mc_ki[cdst->mc_top] == 0) { + if (cdst->mc_ki[cdst->mc_top-1] != 0) { + if (IS_LEAF2(csrc->mc_pg[csrc->mc_top])) { + key.mv_data = LEAF2KEY(cdst->mc_pg[cdst->mc_top], 0, key.mv_size); + } else { + srcnode = NODEPTR(cdst->mc_pg[cdst->mc_top], 0); + key.mv_size = NODEKSZ(srcnode); + key.mv_data = NODEKEY(srcnode); + } + DPRINTF("update separator for destination page %zu to [%s]", + cdst->mc_pg[cdst->mc_top]->mp_pgno, DKEY(&key)); + if ((rc = mdb_update_key(cdst->mc_pg[cdst->mc_top-1], cdst->mc_ki[cdst->mc_top-1], + &key)) != MDB_SUCCESS) + return rc; + } + if (IS_BRANCH(cdst->mc_pg[cdst->mc_top])) { + MDB_val nullkey; + nullkey.mv_size = 0; + rc = mdb_update_key(cdst->mc_pg[cdst->mc_top], 0, &nullkey); + assert(rc == MDB_SUCCESS); + } + } + + return MDB_SUCCESS; +} + +/** Merge one page into another. + * The nodes from the page pointed to by \b csrc will + * be copied to the page pointed to by \b cdst and then + * the \b csrc page will be freed. + * @param[in] csrc Cursor pointing to the source page. + * @param[in] cdst Cursor pointing to the destination page. + */ +static int +mdb_page_merge(MDB_cursor *csrc, MDB_cursor *cdst) +{ + int rc; + indx_t i, j; + MDB_node *srcnode; + MDB_val key, data; + unsigned nkeys; + + DPRINTF("merging page %zu into %zu", csrc->mc_pg[csrc->mc_top]->mp_pgno, + cdst->mc_pg[cdst->mc_top]->mp_pgno); + + assert(csrc->mc_snum > 1); /* can't merge root page */ + assert(cdst->mc_snum > 1); + + /* Mark dst as dirty. */ + if ((rc = mdb_page_touch(cdst))) + return rc; + + /* Move all nodes from src to dst. + */ + j = nkeys = NUMKEYS(cdst->mc_pg[cdst->mc_top]); + if (IS_LEAF2(csrc->mc_pg[csrc->mc_top])) { + key.mv_size = csrc->mc_db->md_pad; + key.mv_data = METADATA(csrc->mc_pg[csrc->mc_top]); + for (i = 0; i < NUMKEYS(csrc->mc_pg[csrc->mc_top]); i++, j++) { + rc = mdb_node_add(cdst, j, &key, NULL, 0, 0); + if (rc != MDB_SUCCESS) + return rc; + key.mv_data = (char *)key.mv_data + key.mv_size; + } + } else { + for (i = 0; i < NUMKEYS(csrc->mc_pg[csrc->mc_top]); i++, j++) { + srcnode = NODEPTR(csrc->mc_pg[csrc->mc_top], i); + if (i == 0 && IS_BRANCH(csrc->mc_pg[csrc->mc_top])) { + unsigned int snum = csrc->mc_snum; + MDB_node *s2; + /* must find the lowest key below src */ + mdb_page_search_root(csrc, NULL, 0); + if (IS_LEAF2(csrc->mc_pg[csrc->mc_top])) { + key.mv_size = csrc->mc_db->md_pad; + key.mv_data = LEAF2KEY(csrc->mc_pg[csrc->mc_top], 0, key.mv_size); + } else { + s2 = NODEPTR(csrc->mc_pg[csrc->mc_top], 0); + key.mv_size = NODEKSZ(s2); + key.mv_data = NODEKEY(s2); + } + csrc->mc_snum = snum--; + csrc->mc_top = snum; + } else { + key.mv_size = srcnode->mn_ksize; + key.mv_data = NODEKEY(srcnode); + } + + data.mv_size = NODEDSZ(srcnode); + data.mv_data = NODEDATA(srcnode); + rc = mdb_node_add(cdst, j, &key, &data, NODEPGNO(srcnode), srcnode->mn_flags); + if (rc != MDB_SUCCESS) + return rc; + } + } + + DPRINTF("dst page %zu now has %u keys (%.1f%% filled)", + cdst->mc_pg[cdst->mc_top]->mp_pgno, NUMKEYS(cdst->mc_pg[cdst->mc_top]), (float)PAGEFILL(cdst->mc_txn->mt_env, cdst->mc_pg[cdst->mc_top]) / 10); + + /* Unlink the src page from parent and add to free list. + */ + mdb_node_del(csrc->mc_pg[csrc->mc_top-1], csrc->mc_ki[csrc->mc_top-1], 0); + if (csrc->mc_ki[csrc->mc_top-1] == 0) { + key.mv_size = 0; + if ((rc = mdb_update_key(csrc->mc_pg[csrc->mc_top-1], 0, &key)) != MDB_SUCCESS) + return rc; + } + + mdb_midl_append(&csrc->mc_txn->mt_free_pgs, csrc->mc_pg[csrc->mc_top]->mp_pgno); + if (IS_LEAF(csrc->mc_pg[csrc->mc_top])) + csrc->mc_db->md_leaf_pages--; + else + csrc->mc_db->md_branch_pages--; + { + /* Adjust other cursors pointing to mp */ + MDB_cursor *m2, *m3; + MDB_dbi dbi = csrc->mc_dbi; + MDB_page *mp = cdst->mc_pg[cdst->mc_top]; + + if (csrc->mc_flags & C_SUB) + dbi--; + + for (m2 = csrc->mc_txn->mt_cursors[dbi]; m2; m2=m2->mc_next) { + if (csrc->mc_flags & C_SUB) + m3 = &m2->mc_xcursor->mx_cursor; + else + m3 = m2; + if (m3 == csrc) continue; + if (m3->mc_snum < csrc->mc_snum) continue; + if (m3->mc_pg[csrc->mc_top] == csrc->mc_pg[csrc->mc_top]) { + m3->mc_pg[csrc->mc_top] = mp; + m3->mc_ki[csrc->mc_top] += nkeys; + } + } + } + mdb_cursor_pop(csrc); + + return mdb_rebalance(csrc); +} + +/** Copy the contents of a cursor. + * @param[in] csrc The cursor to copy from. + * @param[out] cdst The cursor to copy to. + */ +static void +mdb_cursor_copy(const MDB_cursor *csrc, MDB_cursor *cdst) +{ + unsigned int i; + + cdst->mc_txn = csrc->mc_txn; + cdst->mc_dbi = csrc->mc_dbi; + cdst->mc_db = csrc->mc_db; + cdst->mc_dbx = csrc->mc_dbx; + cdst->mc_snum = csrc->mc_snum; + cdst->mc_top = csrc->mc_top; + cdst->mc_flags = csrc->mc_flags; + + for (i=0; imc_snum; i++) { + cdst->mc_pg[i] = csrc->mc_pg[i]; + cdst->mc_ki[i] = csrc->mc_ki[i]; + } +} + +/** Rebalance the tree after a delete operation. + * @param[in] mc Cursor pointing to the page where rebalancing + * should begin. + * @return 0 on success, non-zero on failure. + */ +static int +mdb_rebalance(MDB_cursor *mc) +{ + MDB_node *node; + int rc; + unsigned int ptop; + MDB_cursor mn; + +#if MDB_DEBUG + { + pgno_t pgno; + COPY_PGNO(pgno, mc->mc_pg[mc->mc_top]->mp_pgno); + DPRINTF("rebalancing %s page %zu (has %u keys, %.1f%% full)", + IS_LEAF(mc->mc_pg[mc->mc_top]) ? "leaf" : "branch", + pgno, NUMKEYS(mc->mc_pg[mc->mc_top]), (float)PAGEFILL(mc->mc_txn->mt_env, mc->mc_pg[mc->mc_top]) / 10); + } +#endif + + if (PAGEFILL(mc->mc_txn->mt_env, mc->mc_pg[mc->mc_top]) >= FILL_THRESHOLD) { +#if MDB_DEBUG + pgno_t pgno; + COPY_PGNO(pgno, mc->mc_pg[mc->mc_top]->mp_pgno); + DPRINTF("no need to rebalance page %zu, above fill threshold", + pgno); +#endif + return MDB_SUCCESS; + } + + if (mc->mc_snum < 2) { + MDB_page *mp = mc->mc_pg[0]; + if (NUMKEYS(mp) == 0) { + DPUTS("tree is completely empty"); + mc->mc_db->md_root = P_INVALID; + mc->mc_db->md_depth = 0; + mc->mc_db->md_leaf_pages = 0; + mdb_midl_append(&mc->mc_txn->mt_free_pgs, mp->mp_pgno); + mc->mc_snum = 0; + mc->mc_top = 0; + { + /* Adjust other cursors pointing to mp */ + MDB_cursor *m2, *m3; + MDB_dbi dbi = mc->mc_dbi; + + if (mc->mc_flags & C_SUB) + dbi--; + + for (m2 = mc->mc_txn->mt_cursors[dbi]; m2; m2=m2->mc_next) { + if (m2 == mc) continue; + if (mc->mc_flags & C_SUB) + m3 = &m2->mc_xcursor->mx_cursor; + else + m3 = m2; + if (m3->mc_snum < mc->mc_snum) continue; + if (m3->mc_pg[0] == mp) { + m3->mc_snum = 0; + m3->mc_top = 0; + } + } + } + } else if (IS_BRANCH(mp) && NUMKEYS(mp) == 1) { + DPUTS("collapsing root page!"); + mdb_midl_append(&mc->mc_txn->mt_free_pgs, mp->mp_pgno); + mc->mc_db->md_root = NODEPGNO(NODEPTR(mp, 0)); + if ((rc = mdb_page_get(mc->mc_txn, mc->mc_db->md_root, + &mc->mc_pg[0]))) + return rc; + mc->mc_db->md_depth--; + mc->mc_db->md_branch_pages--; + { + /* Adjust other cursors pointing to mp */ + MDB_cursor *m2, *m3; + MDB_dbi dbi = mc->mc_dbi; + + if (mc->mc_flags & C_SUB) + dbi--; + + for (m2 = mc->mc_txn->mt_cursors[dbi]; m2; m2=m2->mc_next) { + if (m2 == mc) continue; + if (mc->mc_flags & C_SUB) + m3 = &m2->mc_xcursor->mx_cursor; + else + m3 = m2; + if (m3->mc_snum < mc->mc_snum) continue; + if (m3->mc_pg[0] == mp) { + m3->mc_pg[0] = mc->mc_pg[0]; + } + } + } + } else + DPUTS("root page doesn't need rebalancing"); + return MDB_SUCCESS; + } + + /* The parent (branch page) must have at least 2 pointers, + * otherwise the tree is invalid. + */ + ptop = mc->mc_top-1; + assert(NUMKEYS(mc->mc_pg[ptop]) > 1); + + /* Leaf page fill factor is below the threshold. + * Try to move keys from left or right neighbor, or + * merge with a neighbor page. + */ + + /* Find neighbors. + */ + mdb_cursor_copy(mc, &mn); + mn.mc_xcursor = NULL; + + if (mc->mc_ki[ptop] == 0) { + /* We're the leftmost leaf in our parent. + */ + DPUTS("reading right neighbor"); + mn.mc_ki[ptop]++; + node = NODEPTR(mc->mc_pg[ptop], mn.mc_ki[ptop]); + if ((rc = mdb_page_get(mc->mc_txn, NODEPGNO(node), &mn.mc_pg[mn.mc_top]))) + return rc; + mn.mc_ki[mn.mc_top] = 0; + mc->mc_ki[mc->mc_top] = NUMKEYS(mc->mc_pg[mc->mc_top]); + } else { + /* There is at least one neighbor to the left. + */ + DPUTS("reading left neighbor"); + mn.mc_ki[ptop]--; + node = NODEPTR(mc->mc_pg[ptop], mn.mc_ki[ptop]); + if ((rc = mdb_page_get(mc->mc_txn, NODEPGNO(node), &mn.mc_pg[mn.mc_top]))) + return rc; + mn.mc_ki[mn.mc_top] = NUMKEYS(mn.mc_pg[mn.mc_top]) - 1; + mc->mc_ki[mc->mc_top] = 0; + } + + DPRINTF("found neighbor page %zu (%u keys, %.1f%% full)", + mn.mc_pg[mn.mc_top]->mp_pgno, NUMKEYS(mn.mc_pg[mn.mc_top]), (float)PAGEFILL(mc->mc_txn->mt_env, mn.mc_pg[mn.mc_top]) / 10); + + /* If the neighbor page is above threshold and has at least two + * keys, move one key from it. + * + * Otherwise we should try to merge them. + */ + if (PAGEFILL(mc->mc_txn->mt_env, mn.mc_pg[mn.mc_top]) >= FILL_THRESHOLD && NUMKEYS(mn.mc_pg[mn.mc_top]) >= 2) + return mdb_node_move(&mn, mc); + else { /* FIXME: if (has_enough_room()) */ + mc->mc_flags &= ~C_INITIALIZED; + if (mc->mc_ki[ptop] == 0) + return mdb_page_merge(&mn, mc); + else + return mdb_page_merge(mc, &mn); + } +} + +/** Complete a delete operation started by #mdb_cursor_del(). */ +static int +mdb_cursor_del0(MDB_cursor *mc, MDB_node *leaf) +{ + int rc; + + /* add overflow pages to free list */ + if (!IS_LEAF2(mc->mc_pg[mc->mc_top]) && F_ISSET(leaf->mn_flags, F_BIGDATA)) { + int i, ovpages; + pgno_t pg; + + memcpy(&pg, NODEDATA(leaf), sizeof(pg)); + ovpages = OVPAGES(NODEDSZ(leaf), mc->mc_txn->mt_env->me_psize); + mc->mc_db->md_overflow_pages -= ovpages; + for (i=0; imc_txn->mt_free_pgs, pg); + pg++; + } + } + mdb_node_del(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top], mc->mc_db->md_pad); + mc->mc_db->md_entries--; + rc = mdb_rebalance(mc); + if (rc != MDB_SUCCESS) + mc->mc_txn->mt_flags |= MDB_TXN_ERROR; + + return rc; +} + +int +mdb_del(MDB_txn *txn, MDB_dbi dbi, + MDB_val *key, MDB_val *data) +{ + MDB_cursor mc; + MDB_xcursor mx; + MDB_cursor_op op; + MDB_val rdata, *xdata; + int rc, exact; + DKBUF; + + assert(key != NULL); + + DPRINTF("====> delete db %u key [%s]", dbi, DKEY(key)); + + if (txn == NULL || !dbi || dbi >= txn->mt_numdbs) + return EINVAL; + + if (F_ISSET(txn->mt_flags, MDB_TXN_RDONLY)) { + return EACCES; + } + + if (key->mv_size == 0 || key->mv_size > MAXKEYSIZE) { + return EINVAL; + } + + mdb_cursor_init(&mc, txn, dbi, &mx); + + exact = 0; + if (data) { + op = MDB_GET_BOTH; + rdata = *data; + xdata = &rdata; + } else { + op = MDB_SET; + xdata = NULL; + } + rc = mdb_cursor_set(&mc, key, xdata, op, &exact); + if (rc == 0) + rc = mdb_cursor_del(&mc, data ? 0 : MDB_NODUPDATA); + return rc; +} + +/** Split a page and insert a new node. + * @param[in,out] mc Cursor pointing to the page and desired insertion index. + * The cursor will be updated to point to the actual page and index where + * the node got inserted after the split. + * @param[in] newkey The key for the newly inserted node. + * @param[in] newdata The data for the newly inserted node. + * @param[in] newpgno The page number, if the new node is a branch node. + * @param[in] nflags The #NODE_ADD_FLAGS for the new node. + * @return 0 on success, non-zero on failure. + */ +static int +mdb_page_split(MDB_cursor *mc, MDB_val *newkey, MDB_val *newdata, pgno_t newpgno, + unsigned int nflags) +{ + unsigned int flags; + int rc = MDB_SUCCESS, ins_new = 0, new_root = 0, newpos = 1, did_split = 0; + indx_t newindx; + pgno_t pgno = 0; + unsigned int i, j, split_indx, nkeys, pmax; + MDB_node *node; + MDB_val sepkey, rkey, xdata, *rdata = &xdata; + MDB_page *copy; + MDB_page *mp, *rp, *pp; + unsigned int ptop; + MDB_cursor mn; + DKBUF; + + mp = mc->mc_pg[mc->mc_top]; + newindx = mc->mc_ki[mc->mc_top]; + + DPRINTF("-----> splitting %s page %zu and adding [%s] at index %i", + IS_LEAF(mp) ? "leaf" : "branch", mp->mp_pgno, + DKEY(newkey), mc->mc_ki[mc->mc_top]); + + /* Create a right sibling. */ + if ((rc = mdb_page_new(mc, mp->mp_flags, 1, &rp))) + return rc; + DPRINTF("new right sibling: page %zu", rp->mp_pgno); + + if (mc->mc_snum < 2) { + if ((rc = mdb_page_new(mc, P_BRANCH, 1, &pp))) + return rc; + /* shift current top to make room for new parent */ + mc->mc_pg[1] = mc->mc_pg[0]; + mc->mc_ki[1] = mc->mc_ki[0]; + mc->mc_pg[0] = pp; + mc->mc_ki[0] = 0; + mc->mc_db->md_root = pp->mp_pgno; + DPRINTF("root split! new root = %zu", pp->mp_pgno); + mc->mc_db->md_depth++; + new_root = 1; + + /* Add left (implicit) pointer. */ + if ((rc = mdb_node_add(mc, 0, NULL, NULL, mp->mp_pgno, 0)) != MDB_SUCCESS) { + /* undo the pre-push */ + mc->mc_pg[0] = mc->mc_pg[1]; + mc->mc_ki[0] = mc->mc_ki[1]; + mc->mc_db->md_root = mp->mp_pgno; + mc->mc_db->md_depth--; + return rc; + } + mc->mc_snum = 2; + mc->mc_top = 1; + ptop = 0; + } else { + ptop = mc->mc_top-1; + DPRINTF("parent branch page is %zu", mc->mc_pg[ptop]->mp_pgno); + } + + mc->mc_flags |= C_SPLITTING; + mdb_cursor_copy(mc, &mn); + mn.mc_pg[mn.mc_top] = rp; + mn.mc_ki[ptop] = mc->mc_ki[ptop]+1; + + if (nflags & MDB_APPEND) { + mn.mc_ki[mn.mc_top] = 0; + sepkey = *newkey; + split_indx = newindx; + nkeys = 0; + goto newsep; + } + + nkeys = NUMKEYS(mp); + split_indx = nkeys / 2; + if (newindx < split_indx) + newpos = 0; + + if (IS_LEAF2(rp)) { + char *split, *ins; + int x; + unsigned int lsize, rsize, ksize; + /* Move half of the keys to the right sibling */ + copy = NULL; + x = mc->mc_ki[mc->mc_top] - split_indx; + ksize = mc->mc_db->md_pad; + split = LEAF2KEY(mp, split_indx, ksize); + rsize = (nkeys - split_indx) * ksize; + lsize = (nkeys - split_indx) * sizeof(indx_t); + mp->mp_lower -= lsize; + rp->mp_lower += lsize; + mp->mp_upper += rsize - lsize; + rp->mp_upper -= rsize - lsize; + sepkey.mv_size = ksize; + if (newindx == split_indx) { + sepkey.mv_data = newkey->mv_data; + } else { + sepkey.mv_data = split; + } + if (x<0) { + ins = LEAF2KEY(mp, mc->mc_ki[mc->mc_top], ksize); + memcpy(rp->mp_ptrs, split, rsize); + sepkey.mv_data = rp->mp_ptrs; + memmove(ins+ksize, ins, (split_indx - mc->mc_ki[mc->mc_top]) * ksize); + memcpy(ins, newkey->mv_data, ksize); + mp->mp_lower += sizeof(indx_t); + mp->mp_upper -= ksize - sizeof(indx_t); + } else { + if (x) + memcpy(rp->mp_ptrs, split, x * ksize); + ins = LEAF2KEY(rp, x, ksize); + memcpy(ins, newkey->mv_data, ksize); + memcpy(ins+ksize, split + x * ksize, rsize - x * ksize); + rp->mp_lower += sizeof(indx_t); + rp->mp_upper -= ksize - sizeof(indx_t); + mc->mc_ki[mc->mc_top] = x; + mc->mc_pg[mc->mc_top] = rp; + } + goto newsep; + } + + /* For leaf pages, check the split point based on what + * fits where, since otherwise mdb_node_add can fail. + * + * This check is only needed when the data items are + * relatively large, such that being off by one will + * make the difference between success or failure. + * + * It's also relevant if a page happens to be laid out + * such that one half of its nodes are all "small" and + * the other half of its nodes are "large." If the new + * item is also "large" and falls on the half with + * "large" nodes, it also may not fit. + */ + if (IS_LEAF(mp)) { + unsigned int psize, nsize; + /* Maximum free space in an empty page */ + pmax = mc->mc_txn->mt_env->me_psize - PAGEHDRSZ; + nsize = mdb_leaf_size(mc->mc_txn->mt_env, newkey, newdata); + if ((nkeys < 20) || (nsize > pmax/16)) { + if (newindx <= split_indx) { + psize = nsize; + newpos = 0; + for (i=0; imn_flags, F_BIGDATA)) + psize += sizeof(pgno_t); + else + psize += NODEDSZ(node); + psize += psize & 1; + if (psize > pmax) { + if (i <= newindx) { + split_indx = newindx; + if (i < newindx) + newpos = 1; + } + else + split_indx = i; + break; + } + } + } else { + psize = nsize; + for (i=nkeys-1; i>=split_indx; i--) { + node = NODEPTR(mp, i); + psize += NODESIZE + NODEKSZ(node) + sizeof(indx_t); + if (F_ISSET(node->mn_flags, F_BIGDATA)) + psize += sizeof(pgno_t); + else + psize += NODEDSZ(node); + psize += psize & 1; + if (psize > pmax) { + if (i >= newindx) { + split_indx = newindx; + newpos = 0; + } else + split_indx = i+1; + break; + } + } + } + } + } + + /* First find the separating key between the split pages. + * The case where newindx == split_indx is ambiguous; the + * new item could go to the new page or stay on the original + * page. If newpos == 1 it goes to the new page. + */ + if (newindx == split_indx && newpos) { + sepkey.mv_size = newkey->mv_size; + sepkey.mv_data = newkey->mv_data; + } else { + node = NODEPTR(mp, split_indx); + sepkey.mv_size = node->mn_ksize; + sepkey.mv_data = NODEKEY(node); + } + +newsep: + DPRINTF("separator is [%s]", DKEY(&sepkey)); + + /* Copy separator key to the parent. + */ + if (SIZELEFT(mn.mc_pg[ptop]) < mdb_branch_size(mc->mc_txn->mt_env, &sepkey)) { + mn.mc_snum--; + mn.mc_top--; + did_split = 1; + rc = mdb_page_split(&mn, &sepkey, NULL, rp->mp_pgno, 0); + + /* root split? */ + if (mn.mc_snum == mc->mc_snum) { + mc->mc_pg[mc->mc_snum] = mc->mc_pg[mc->mc_top]; + mc->mc_ki[mc->mc_snum] = mc->mc_ki[mc->mc_top]; + mc->mc_pg[mc->mc_top] = mc->mc_pg[ptop]; + mc->mc_ki[mc->mc_top] = mc->mc_ki[ptop]; + mc->mc_snum++; + mc->mc_top++; + ptop++; + } + /* Right page might now have changed parent. + * Check if left page also changed parent. + */ + if (mn.mc_pg[ptop] != mc->mc_pg[ptop] && + mc->mc_ki[ptop] >= NUMKEYS(mc->mc_pg[ptop])) { + for (i=0; imc_pg[i] = mn.mc_pg[i]; + mc->mc_ki[i] = mn.mc_ki[i]; + } + mc->mc_pg[ptop] = mn.mc_pg[ptop]; + mc->mc_ki[ptop] = mn.mc_ki[ptop] - 1; + } + } else { + mn.mc_top--; + rc = mdb_node_add(&mn, mn.mc_ki[ptop], &sepkey, NULL, rp->mp_pgno, 0); + mn.mc_top++; + } + mc->mc_flags ^= C_SPLITTING; + if (rc != MDB_SUCCESS) { + return rc; + } + if (nflags & MDB_APPEND) { + mc->mc_pg[mc->mc_top] = rp; + mc->mc_ki[mc->mc_top] = 0; + rc = mdb_node_add(mc, 0, newkey, newdata, newpgno, nflags); + if (rc) + return rc; + for (i=0; imc_top; i++) + mc->mc_ki[i] = mn.mc_ki[i]; + goto done; + } + if (IS_LEAF2(rp)) { + goto done; + } + + /* Move half of the keys to the right sibling. */ + + /* grab a page to hold a temporary copy */ + copy = mdb_page_malloc(mc); + if (copy == NULL) + return ENOMEM; + + copy->mp_pgno = mp->mp_pgno; + copy->mp_flags = mp->mp_flags; + copy->mp_lower = PAGEHDRSZ; + copy->mp_upper = mc->mc_txn->mt_env->me_psize; + mc->mc_pg[mc->mc_top] = copy; + for (i = j = 0; i <= nkeys; j++) { + if (i == split_indx) { + /* Insert in right sibling. */ + /* Reset insert index for right sibling. */ + if (i != newindx || (newpos ^ ins_new)) { + j = 0; + mc->mc_pg[mc->mc_top] = rp; + } + } + + if (i == newindx && !ins_new) { + /* Insert the original entry that caused the split. */ + rkey.mv_data = newkey->mv_data; + rkey.mv_size = newkey->mv_size; + if (IS_LEAF(mp)) { + rdata = newdata; + } else + pgno = newpgno; + flags = nflags; + + ins_new = 1; + + /* Update index for the new key. */ + mc->mc_ki[mc->mc_top] = j; + } else if (i == nkeys) { + break; + } else { + node = NODEPTR(mp, i); + rkey.mv_data = NODEKEY(node); + rkey.mv_size = node->mn_ksize; + if (IS_LEAF(mp)) { + xdata.mv_data = NODEDATA(node); + xdata.mv_size = NODEDSZ(node); + rdata = &xdata; + } else + pgno = NODEPGNO(node); + flags = node->mn_flags; + + i++; + } + + if (!IS_LEAF(mp) && j == 0) { + /* First branch index doesn't need key data. */ + rkey.mv_size = 0; + } + + rc = mdb_node_add(mc, j, &rkey, rdata, pgno, flags); + if (rc) break; + } + + nkeys = NUMKEYS(copy); + for (i=0; imp_ptrs[i] = copy->mp_ptrs[i]; + mp->mp_lower = copy->mp_lower; + mp->mp_upper = copy->mp_upper; + memcpy(NODEPTR(mp, nkeys-1), NODEPTR(copy, nkeys-1), + mc->mc_txn->mt_env->me_psize - copy->mp_upper); + + /* reset back to original page */ + if (newindx < split_indx || (!newpos && newindx == split_indx)) { + mc->mc_pg[mc->mc_top] = mp; + if (nflags & MDB_RESERVE) { + node = NODEPTR(mp, mc->mc_ki[mc->mc_top]); + if (!(node->mn_flags & F_BIGDATA)) + newdata->mv_data = NODEDATA(node); + } + } else { + mc->mc_ki[ptop]++; + } + + /* return tmp page to freelist */ + copy->mp_next = mc->mc_txn->mt_env->me_dpages; + VGMEMP_FREE(mc->mc_txn->mt_env, copy); + mc->mc_txn->mt_env->me_dpages = copy; +done: + { + /* Adjust other cursors pointing to mp */ + MDB_cursor *m2, *m3; + MDB_dbi dbi = mc->mc_dbi; + int fixup = NUMKEYS(mp); + + if (mc->mc_flags & C_SUB) + dbi--; + + for (m2 = mc->mc_txn->mt_cursors[dbi]; m2; m2=m2->mc_next) { + if (m2 == mc) continue; + if (mc->mc_flags & C_SUB) + m3 = &m2->mc_xcursor->mx_cursor; + else + m3 = m2; + if (!(m3->mc_flags & C_INITIALIZED)) + continue; + if (m3->mc_flags & C_SPLITTING) + continue; + if (new_root) { + int k; + /* root split */ + for (k=m3->mc_top; k>=0; k--) { + m3->mc_ki[k+1] = m3->mc_ki[k]; + m3->mc_pg[k+1] = m3->mc_pg[k]; + } + if (m3->mc_ki[0] >= split_indx) { + m3->mc_ki[0] = 1; + } else { + m3->mc_ki[0] = 0; + } + m3->mc_pg[0] = mc->mc_pg[0]; + m3->mc_snum++; + m3->mc_top++; + } + if (m3->mc_pg[mc->mc_top] == mp) { + if (m3->mc_ki[mc->mc_top] >= newindx && !(nflags & MDB_SPLIT_REPLACE)) + m3->mc_ki[mc->mc_top]++; + if (m3->mc_ki[mc->mc_top] >= fixup) { + m3->mc_pg[mc->mc_top] = rp; + m3->mc_ki[mc->mc_top] -= fixup; + m3->mc_ki[ptop] = mn.mc_ki[ptop]; + } + } else if (!did_split && m3->mc_pg[ptop] == mc->mc_pg[ptop] && + m3->mc_ki[ptop] >= mc->mc_ki[ptop]) { + m3->mc_ki[ptop]++; + } + } + } + return rc; +} + +int +mdb_put(MDB_txn *txn, MDB_dbi dbi, + MDB_val *key, MDB_val *data, unsigned int flags) +{ + MDB_cursor mc; + MDB_xcursor mx; + + assert(key != NULL); + assert(data != NULL); + + if (txn == NULL || !dbi || dbi >= txn->mt_numdbs) + return EINVAL; + + if (F_ISSET(txn->mt_flags, MDB_TXN_RDONLY)) { + return EACCES; + } + + if (key->mv_size == 0 || key->mv_size > MAXKEYSIZE) { + return EINVAL; + } + + if ((flags & (MDB_NOOVERWRITE|MDB_NODUPDATA|MDB_RESERVE|MDB_APPEND)) != flags) + return EINVAL; + + mdb_cursor_init(&mc, txn, dbi, &mx); + return mdb_cursor_put(&mc, key, data, flags); +} + +/** Only a subset of the @ref mdb_env flags can be changed + * at runtime. Changing other flags requires closing the environment + * and re-opening it with the new flags. + */ +#define CHANGEABLE (MDB_NOSYNC|MDB_NOMETASYNC|MDB_MAPASYNC) +int +mdb_env_set_flags(MDB_env *env, unsigned int flag, int onoff) +{ + 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; + + *arg = env->me_flags; + return MDB_SUCCESS; +} + +int +mdb_env_get_path(MDB_env *env, const char **arg) +{ + if (!env || !arg) + return EINVAL; + + *arg = env->me_path; + return MDB_SUCCESS; +} + +/** Common code for #mdb_stat() and #mdb_env_stat(). + * @param[in] env the environment to operate in. + * @param[in] db the #MDB_db record containing the stats to return. + * @param[out] arg the address of an #MDB_stat structure to receive the stats. + * @return 0, this function always succeeds. + */ +static int +mdb_stat0(MDB_env *env, MDB_db *db, MDB_stat *arg) +{ + arg->ms_psize = env->me_psize; + arg->ms_depth = db->md_depth; + arg->ms_branch_pages = db->md_branch_pages; + arg->ms_leaf_pages = db->md_leaf_pages; + arg->ms_overflow_pages = db->md_overflow_pages; + arg->ms_entries = db->md_entries; + + return MDB_SUCCESS; +} +int +mdb_env_stat(MDB_env *env, MDB_stat *arg) +{ + int toggle; + + if (env == NULL || arg == NULL) + return EINVAL; + + toggle = mdb_env_pick_meta(env); + + return mdb_stat0(env, &env->me_metas[toggle]->mm_dbs[MAIN_DBI], arg); +} + +/** Set the default comparison functions for a database. + * Called immediately after a database is opened to set the defaults. + * The user can then override them with #mdb_set_compare() or + * #mdb_set_dupsort(). + * @param[in] txn A transaction handle returned by #mdb_txn_begin() + * @param[in] dbi A database handle returned by #mdb_open() + */ +static void +mdb_default_cmp(MDB_txn *txn, MDB_dbi dbi) +{ + uint16_t f = txn->mt_dbs[dbi].md_flags; + + txn->mt_dbxs[dbi].md_cmp = + (f & MDB_REVERSEKEY) ? mdb_cmp_memnr : + (f & MDB_INTEGERKEY) ? mdb_cmp_cint : mdb_cmp_memn; + + txn->mt_dbxs[dbi].md_dcmp = + !(f & MDB_DUPSORT) ? 0 : + ((f & MDB_INTEGERDUP) + ? ((f & MDB_DUPFIXED) ? mdb_cmp_int : mdb_cmp_cint) + : ((f & MDB_REVERSEDUP) ? mdb_cmp_memnr : mdb_cmp_memn)); +} + +int mdb_open(MDB_txn *txn, const char *name, unsigned int flags, MDB_dbi *dbi) +{ + MDB_val key, data; + MDB_dbi i; + MDB_cursor mc; + int rc, dbflag, exact; + unsigned int unused = 0; + size_t len; + + if (txn->mt_dbxs[FREE_DBI].md_cmp == NULL) { + mdb_default_cmp(txn, FREE_DBI); + } + + /* main DB? */ + if (!name) { + *dbi = MAIN_DBI; + if (flags & (MDB_DUPSORT|MDB_REVERSEKEY|MDB_INTEGERKEY)) + txn->mt_dbs[MAIN_DBI].md_flags |= (flags & (MDB_DUPSORT|MDB_REVERSEKEY|MDB_INTEGERKEY)); + mdb_default_cmp(txn, MAIN_DBI); + return MDB_SUCCESS; + } + + if (txn->mt_dbxs[MAIN_DBI].md_cmp == NULL) { + mdb_default_cmp(txn, MAIN_DBI); + } + + /* Is the DB already open? */ + len = strlen(name); + for (i=2; imt_numdbs; i++) { + if (!txn->mt_dbxs[i].md_name.mv_size) { + /* Remember this free slot */ + if (!unused) unused = i; + continue; + } + if (len == txn->mt_dbxs[i].md_name.mv_size && + !strncmp(name, txn->mt_dbxs[i].md_name.mv_data, len)) { + *dbi = i; + return MDB_SUCCESS; + } + } + + /* If no free slot and max hit, fail */ + if (!unused && txn->mt_numdbs >= txn->mt_env->me_maxdbs - 1) + return MDB_DBS_FULL; + + /* Find the DB info */ + dbflag = 0; + exact = 0; + key.mv_size = len; + key.mv_data = (void *)name; + mdb_cursor_init(&mc, txn, MAIN_DBI, NULL); + rc = mdb_cursor_set(&mc, &key, &data, MDB_SET, &exact); + if (rc == MDB_SUCCESS) { + /* make sure this is actually a DB */ + MDB_node *node = NODEPTR(mc.mc_pg[mc.mc_top], mc.mc_ki[mc.mc_top]); + if (!(node->mn_flags & F_SUBDATA)) + return EINVAL; + } else if (rc == MDB_NOTFOUND && (flags & MDB_CREATE)) { + /* Create if requested */ + MDB_db dummy; + data.mv_size = sizeof(MDB_db); + data.mv_data = &dummy; + memset(&dummy, 0, sizeof(dummy)); + dummy.md_root = P_INVALID; + dummy.md_flags = flags & 0xffff; + rc = mdb_cursor_put(&mc, &key, &data, F_SUBDATA); + dbflag = DB_DIRTY; + } + + /* OK, got info, add to table */ + if (rc == MDB_SUCCESS) { + unsigned int slot = unused ? unused : txn->mt_numdbs; + txn->mt_dbxs[slot].md_name.mv_data = strdup(name); + txn->mt_dbxs[slot].md_name.mv_size = len; + txn->mt_dbxs[slot].md_rel = NULL; + txn->mt_dbflags[slot] = dbflag; + memcpy(&txn->mt_dbs[slot], data.mv_data, sizeof(MDB_db)); + *dbi = slot; + txn->mt_env->me_dbflags[slot] = txn->mt_dbs[slot].md_flags; + mdb_default_cmp(txn, slot); + if (!unused) { + txn->mt_numdbs++; + txn->mt_env->me_numdbs++; + } + } + + return rc; +} + +int mdb_stat(MDB_txn *txn, MDB_dbi dbi, MDB_stat *arg) +{ + if (txn == NULL || arg == NULL || dbi >= txn->mt_numdbs) + return EINVAL; + + return mdb_stat0(txn->mt_env, &txn->mt_dbs[dbi], arg); +} + +void mdb_close(MDB_env *env, MDB_dbi dbi) +{ + char *ptr; + if (dbi <= MAIN_DBI || dbi >= env->me_numdbs) + return; + ptr = env->me_dbxs[dbi].md_name.mv_data; + env->me_dbxs[dbi].md_name.mv_data = NULL; + env->me_dbxs[dbi].md_name.mv_size = 0; + free(ptr); +} + +/** Add all the DB's pages to the free list. + * @param[in] mc Cursor on the DB to free. + * @param[in] subs non-Zero to check for sub-DBs in this DB. + * @return 0 on success, non-zero on failure. + */ +static int +mdb_drop0(MDB_cursor *mc, int subs) +{ + int rc; + + rc = mdb_page_search(mc, NULL, 0); + if (rc == MDB_SUCCESS) { + MDB_node *ni; + MDB_cursor mx; + unsigned int i; + + /* LEAF2 pages have no nodes, cannot have sub-DBs */ + if (!subs || IS_LEAF2(mc->mc_pg[mc->mc_top])) + mdb_cursor_pop(mc); + + mdb_cursor_copy(mc, &mx); + while (mc->mc_snum > 0) { + if (IS_LEAF(mc->mc_pg[mc->mc_top])) { + for (i=0; imc_pg[mc->mc_top]); i++) { + ni = NODEPTR(mc->mc_pg[mc->mc_top], i); + if (ni->mn_flags & F_SUBDATA) { + mdb_xcursor_init1(mc, ni); + rc = mdb_drop0(&mc->mc_xcursor->mx_cursor, 0); + if (rc) + return rc; + } + } + } else { + for (i=0; imc_pg[mc->mc_top]); i++) { + pgno_t pg; + ni = NODEPTR(mc->mc_pg[mc->mc_top], i); + pg = NODEPGNO(ni); + /* free it */ + mdb_midl_append(&mc->mc_txn->mt_free_pgs, pg); + } + } + if (!mc->mc_top) + break; + rc = mdb_cursor_sibling(mc, 1); + if (rc) { + /* no more siblings, go back to beginning + * of previous level. (stack was already popped + * by mdb_cursor_sibling) + */ + for (i=1; imc_top; i++) + mc->mc_pg[i] = mx.mc_pg[i]; + } + } + /* free it */ + mdb_midl_append(&mc->mc_txn->mt_free_pgs, + mc->mc_db->md_root); + } + return 0; +} + +int mdb_drop(MDB_txn *txn, MDB_dbi dbi, int del) +{ + MDB_cursor *mc; + int rc; + + if (!txn || !dbi || dbi >= txn->mt_numdbs) + return EINVAL; + + if (F_ISSET(txn->mt_flags, MDB_TXN_RDONLY)) + return EACCES; + + rc = mdb_cursor_open(txn, dbi, &mc); + if (rc) + return rc; + + rc = mdb_drop0(mc, mc->mc_db->md_flags & MDB_DUPSORT); + if (rc) + goto leave; + + /* Can't delete the main DB */ + if (del && dbi > MAIN_DBI) { + rc = mdb_del(txn, MAIN_DBI, &mc->mc_dbx->md_name, NULL); + if (!rc) + mdb_close(txn->mt_env, dbi); + } else { + /* reset the DB record, mark it dirty */ + txn->mt_dbflags[dbi] |= DB_DIRTY; + txn->mt_dbs[dbi].md_depth = 0; + txn->mt_dbs[dbi].md_branch_pages = 0; + txn->mt_dbs[dbi].md_leaf_pages = 0; + txn->mt_dbs[dbi].md_overflow_pages = 0; + txn->mt_dbs[dbi].md_entries = 0; + txn->mt_dbs[dbi].md_root = P_INVALID; + + if (!txn->mt_u.dirty_list[0].mid) { + MDB_cursor m2; + MDB_val key, data; + /* make sure we have at least one dirty page in this txn + * otherwise these changes will be ignored. + */ + key.mv_size = sizeof(txnid_t); + key.mv_data = &txn->mt_txnid; + data.mv_size = sizeof(MDB_ID); + data.mv_data = txn->mt_free_pgs; + mdb_cursor_init(&m2, txn, FREE_DBI, NULL); + rc = mdb_cursor_put(&m2, &key, &data, 0); + } + } +leave: + mdb_cursor_close(mc); + return rc; +} + +int mdb_set_compare(MDB_txn *txn, MDB_dbi dbi, MDB_cmp_func *cmp) +{ + if (txn == NULL || !dbi || dbi >= txn->mt_numdbs) + return EINVAL; + + txn->mt_dbxs[dbi].md_cmp = cmp; + return MDB_SUCCESS; +} + +int mdb_set_dupsort(MDB_txn *txn, MDB_dbi dbi, MDB_cmp_func *cmp) +{ + if (txn == NULL || !dbi || dbi >= txn->mt_numdbs) + return EINVAL; + + txn->mt_dbxs[dbi].md_dcmp = cmp; + return MDB_SUCCESS; +} + +int mdb_set_relfunc(MDB_txn *txn, MDB_dbi dbi, MDB_rel_func *rel) +{ + if (txn == NULL || !dbi || dbi >= txn->mt_numdbs) + return EINVAL; + + txn->mt_dbxs[dbi].md_rel = rel; + return MDB_SUCCESS; +} + +int mdb_set_relctx(MDB_txn *txn, MDB_dbi dbi, void *ctx) +{ + if (txn == NULL || !dbi || dbi >= txn->mt_numdbs) + return EINVAL; + + txn->mt_dbxs[dbi].md_relctx = ctx; + return MDB_SUCCESS; +} + +/** @} */ diff --git a/c_src/mdb.h b/c_src/mdb.h new file mode 100644 index 0000000..925bf17 --- /dev/null +++ b/c_src/mdb.h @@ -0,0 +1,1065 @@ +/** @file mdb.h + * @brief memory-mapped database library + * + * @mainpage MDB Memory-Mapped Database Manager + * MDB is a Btree-based database management library modeled loosely on the + * BerkeleyDB API, but much simplified. The entire database is exposed + * in a memory map, and all data fetches return data directly + * from the mapped memory, so no malloc's or memcpy's occur during + * data fetches. As such, the library is extremely simple because it + * requires no page caching layer of its own, and it is extremely high + * performance and memory-efficient. It is also fully transactional with + * full ACID semantics, and when the memory map is read-only, the + * database integrity cannot be corrupted by stray pointer writes from + * application code. + * + * The library is fully thread-aware and supports concurrent read/write + * access from multiple processes and threads. Data pages use a copy-on- + * write strategy so no active data pages are ever overwritten, which + * also provides resistance to corruption and eliminates the need of any + * special recovery procedures after a system crash. Writes are fully + * serialized; only one write transaction may be active at a time, which + * guarantees that writers can never deadlock. The database structure is + * multi-versioned so readers run with no locks; writers cannot block + * readers, and readers don't block writers. + * + * Unlike other well-known database mechanisms which use either write-ahead + * transaction logs or append-only data writes, MDB requires no maintenance + * during operation. Both write-ahead loggers and append-only databases + * require periodic checkpointing and/or compaction of their log or database + * files otherwise they grow without bound. MDB tracks free pages within + * the database and re-uses them for new write operations, so the database + * size does not grow without bound in normal use. + * + * The memory map can be used as a read-only or read-write map. It is + * read-only by default as this provides total immunity to corruption. + * Using read-write mode offers much higher write performance, but adds + * the possibility for stray application writes thru pointers to silently + * corrupt the database. Of course if your application code is known to + * be bug-free (...) then this is not an issue. + * + * @author Howard Chu, Symas Corporation. + * + * @copyright Copyright 2011-2012 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 OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . + * + * @par Derived From: + * This code is derived from btree.c written by Martin Hedenfalk. + * + * Copyright (c) 2009, 2010 Martin Hedenfalk + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifndef _MDB_H_ +#define _MDB_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @defgroup public Public API + * @{ + */ +/** @defgroup Version Version Macros + * @{ + */ +/** Library major version */ +#define MDB_VERSION_MAJOR 0 +/** Library minor version */ +#define MDB_VERSION_MINOR 9 +/** Library patch version */ +#define MDB_VERSION_PATCH 4 + +/** Combine args a,b,c into a single integer for easy version comparisons */ +#define MDB_VERINT(a,b,c) (((a) << 24) | ((b) << 16) | (c)) + +/** The full library version as a single integer */ +#define MDB_VERSION_FULL \ + MDB_VERINT(MDB_VERSION_MAJOR,MDB_VERSION_MINOR,MDB_VERSION_PATCH) + +/** The release date of this library version */ +#define MDB_VERSION_DATE "September 14, 2012" + +/** A stringifier for the version info */ +#define MDB_VERSTR(a,b,c,d) "MDB " #a "." #b "." #c ": (" d ")" + +/** A helper for the stringifier macro */ +#define MDB_VERFOO(a,b,c,d) MDB_VERSTR(a,b,c,d) + +/** The full library version as a C string */ +#define MDB_VERSION_STRING \ + MDB_VERFOO(MDB_VERSION_MAJOR,MDB_VERSION_MINOR,MDB_VERSION_PATCH,MDB_VERSION_DATE) +/** @} */ + +/** @brief Opaque structure for a database environment. + * + * A DB environment supports multiple databases, all residing in the same + * shared-memory map. + */ +typedef struct MDB_env MDB_env; + +/** @brief Opaque structure for a transaction handle. + * + * All database operations require a transaction handle. Transactions may be + * read-only or read-write. + */ +typedef struct MDB_txn MDB_txn; + +/** @brief A handle for an individual database in the DB environment. */ +typedef unsigned int MDB_dbi; + +/** @brief Opaque structure for navigating through a database */ +typedef struct MDB_cursor MDB_cursor; + +/** @brief Generic structure used for passing keys and data in and out of the database. */ +typedef struct MDB_val { + size_t mv_size; /**< size of the data item */ + void *mv_data; /**< address of the data item */ +} MDB_val; + +/** @brief A callback function used to compare two keys in a database */ +typedef int (MDB_cmp_func)(const MDB_val *a, const MDB_val *b); + +/** @brief A callback function used to relocate a position-dependent data item + * in a fixed-address database. + * + * The \b newptr gives the item's desired address in + * the memory map, and \b oldptr gives its previous address. The item's actual + * data resides at the address in \b item. This callback is expected to walk + * through the fields of the record in \b item and modify any + * values based at the \b oldptr address to be relative to the \b newptr address. + * @param[in,out] item The item that is to be relocated. + * @param[in] oldptr The previous address. + * @param[in] newptr The new address to relocate to. + * @param[in] relctx An application-provided context, set by #mdb_set_relctx(). + * @todo This feature is currently unimplemented. + */ +typedef void (MDB_rel_func)(MDB_val *item, void *oldptr, void *newptr, void *relctx); + +/** @defgroup mdb_env Environment Flags + * @{ + */ + /** mmap at a fixed address */ +#define MDB_FIXEDMAP 0x01 + /** no environment directory */ +#define MDB_NOSUBDIR 0x02 + /** don't fsync after commit */ +#define MDB_NOSYNC 0x10000 + /** read only */ +#define MDB_RDONLY 0x20000 + /** don't fsync metapage after commit */ +#define MDB_NOMETASYNC 0x40000 + /** use writable mmap */ +#define MDB_WRITEMAP 0x80000 + /** use asynchronous msync */ +#define MDB_MAPASYNC 0x100000 +/** @} */ + +/** @defgroup mdb_open Database Flags + * @{ + */ + /** use reverse string keys */ +#define MDB_REVERSEKEY 0x02 + /** use sorted duplicates */ +#define MDB_DUPSORT 0x04 + /** numeric keys in native byte order. + * The keys must all be of the same size. */ +#define MDB_INTEGERKEY 0x08 + /** with #MDB_DUPSORT, sorted dup items have fixed size */ +#define MDB_DUPFIXED 0x10 + /** with #MDB_DUPSORT, dups are numeric in native byte order */ +#define MDB_INTEGERDUP 0x20 + /** with #MDB_DUPSORT, use reverse string dups */ +#define MDB_REVERSEDUP 0x40 + /** create DB if not already existing */ +#define MDB_CREATE 0x40000 +/** @} */ + +/** @defgroup mdb_put Write Flags + * @{ + */ +/** For put: Don't write if the key already exists. */ +#define MDB_NOOVERWRITE 0x10 +/** Only for #MDB_DUPSORT
+ * For put: don't write if the key and data pair already exist.
+ * For mdb_cursor_del: remove all duplicate data items. + */ +#define MDB_NODUPDATA 0x20 +/** For mdb_cursor_put: overwrite the current key/data pair */ +#define MDB_CURRENT 0x40 +/** For put: Just reserve space for data, don't copy it. Return a + * pointer to the reserved space. + */ +#define MDB_RESERVE 0x10000 +/** Data is being appended, don't split full pages. */ +#define MDB_APPEND 0x20000 +/** Duplicate data is being appended, don't split full pages. */ +#define MDB_APPENDDUP 0x40000 +/** Store multiple data items in one call. */ +#define MDB_MULTIPLE 0x80000 +/* @} */ + +/** @brief Cursor Get operations. + * + * This is the set of all operations for retrieving data + * using a cursor. + */ +typedef enum MDB_cursor_op { + MDB_FIRST, /**< Position at first key/data item */ + MDB_FIRST_DUP, /**< Position at first data item of current key. + Only for #MDB_DUPSORT */ + MDB_GET_BOTH, /**< Position at key/data pair. Only for #MDB_DUPSORT */ + MDB_GET_BOTH_RANGE, /**< position at key, nearest data. Only for #MDB_DUPSORT */ + MDB_GET_CURRENT, /**< Return key/data at current cursor position */ + MDB_GET_MULTIPLE, /**< Return all the duplicate data items at the current + cursor position. Only for #MDB_DUPFIXED */ + MDB_LAST, /**< Position at last key/data item */ + MDB_LAST_DUP, /**< Position at last data item of current key. + Only for #MDB_DUPSORT */ + MDB_NEXT, /**< Position at next data item */ + MDB_NEXT_DUP, /**< Position at next data item of current key. + Only for #MDB_DUPSORT */ + MDB_NEXT_MULTIPLE, /**< Return all duplicate data items at the next + cursor position. Only for #MDB_DUPFIXED */ + MDB_NEXT_NODUP, /**< Position at first data item of next key. + Only for #MDB_DUPSORT */ + MDB_PREV, /**< Position at previous data item */ + MDB_PREV_DUP, /**< Position at previous data item of current key. + Only for #MDB_DUPSORT */ + MDB_PREV_NODUP, /**< Position at last data item of previous key. + Only for #MDB_DUPSORT */ + MDB_SET, /**< Position at specified key */ + MDB_SET_KEY, /**< Position at specified key, return key + data */ + MDB_SET_RANGE /**< Position at first key greater than or equal to specified key. */ +} MDB_cursor_op; + +/** @defgroup errors Return Codes + * + * BerkeleyDB uses -30800 to -30999, we'll go under them + * @{ + */ + /** Successful result */ +#define MDB_SUCCESS 0 + /** key/data pair already exists */ +#define MDB_KEYEXIST (-30799) + /** key/data pair not found (EOF) */ +#define MDB_NOTFOUND (-30798) + /** Requested page not found - this usually indicates corruption */ +#define MDB_PAGE_NOTFOUND (-30797) + /** Located page was wrong type */ +#define MDB_CORRUPTED (-30796) + /** Update of meta page failed, probably I/O error */ +#define MDB_PANIC (-30795) + /** Environment version mismatch */ +#define MDB_VERSION_MISMATCH (-30794) + /** File is not a valid MDB file */ +#define MDB_INVALID (-30793) + /** Environment mapsize reached */ +#define MDB_MAP_FULL (-30792) + /** Environment maxdbs reached */ +#define MDB_DBS_FULL (-30791) + /** Environment maxreaders reached */ +#define MDB_READERS_FULL (-30790) + /** Too many TLS keys in use - Windows only */ +#define MDB_TLS_FULL (-30789) + /** Nested txn has too many dirty pages */ +#define MDB_TXN_FULL (-30788) + /** Cursor stack too deep - internal error */ +#define MDB_CURSOR_FULL (-30787) + /** Page has not enough space - internal error */ +#define MDB_PAGE_FULL (-30786) +#define MDB_LAST_ERRCODE MDB_PAGE_FULL +/** @} */ + +/** @brief Statistics for a database in the environment */ +typedef struct MDB_stat { + unsigned int ms_psize; /**< Size of a database page. + This is currently the same for all databases. */ + unsigned int ms_depth; /**< Depth (height) of the B-tree */ + size_t ms_branch_pages; /**< Number of internal (non-leaf) pages */ + size_t ms_leaf_pages; /**< Number of leaf pages */ + size_t ms_overflow_pages; /**< Number of overflow pages */ + size_t ms_entries; /**< Number of data items */ +} MDB_stat; + + /** @brief Return the mdb library version information. + * + * @param[out] major if non-NULL, the library major version number is copied here + * @param[out] minor if non-NULL, the library minor version number is copied here + * @param[out] patch if non-NULL, the library patch version number is copied here + * @retval "version string" The library version as a string + */ +char *mdb_version(int *major, int *minor, int *patch); + + /** @brief Return a string describing a given error code. + * + * This function is a superset of the ANSI C X3.159-1989 (ANSI C) strerror(3) + * function. If the error code is greater than or equal to 0, then the string + * returned by the system function strerror(3) is returned. If the error code + * is less than 0, an error string corresponding to the MDB library error is + * returned. See @ref errors for a list of MDB-specific error codes. + * @param[in] err The error code + * @retval "error message" The description of the error + */ +char *mdb_strerror(int err); + + /** @brief Create an MDB environment handle. + * + * This function allocates memory for a #MDB_env structure. To release + * the allocated memory and discard the handle, call #mdb_env_close(). + * Before the handle may be used, it must be opened using #mdb_env_open(). + * Various other options may also need to be set before opening the handle, + * e.g. #mdb_env_set_mapsize(), #mdb_env_set_maxreaders(), #mdb_env_set_maxdbs(), + * depending on usage requirements. + * @param[out] env The address where the new handle will be stored + * @return A non-zero error value on failure and 0 on success. + */ +int mdb_env_create(MDB_env **env); + + /** @brief Open an environment handle. + * + * If this function fails, #mdb_env_close() must be called to discard the #MDB_env handle. + * @param[in] env An environment handle returned by #mdb_env_create() + * @param[in] path The directory in which the database files reside. This + * directory must already exist and be writable. + * @param[in] flags Special options for this environment. This parameter + * must be set to 0 or by bitwise OR'ing together one or more of the + * values described here. + *
    + *
  • #MDB_FIXEDMAP + * use a fixed address for the mmap region. This flag must be specified + * when creating the environment, and is stored persistently in the environment. + * If successful, the memory map will always reside at the same virtual address + * and pointers used to reference data items in the database will be constant + * across multiple invocations. This option may not always work, depending on + * how the operating system has allocated memory to shared libraries and other uses. + * The feature is highly experimental. + *
  • #MDB_NOSUBDIR + * By default, MDB creates its environment in a directory whose + * pathname is given in \b path, and creates its data and lock files + * under that directory. With this option, \b path is used as-is for + * the database main data file. The database lock file is the \b path + * with "-lock" appended. + *
  • #MDB_NOSYNC + * Don't perform a synchronous flush after committing a transaction. This means + * transactions will exhibit the ACI (atomicity, consistency, and isolation) + * properties, but not D (durability); that is database integrity will be + * maintained but it is possible some number of the most recently committed + * transactions may be undone after a system crash. The number of transactions + * at risk is governed by how often the system flushes dirty buffers to disk + * and how often #mdb_env_sync() is called. This flag may be changed + * at any time using #mdb_env_set_flags(). + *
  • #MDB_NOMETASYNC + * Don't perform a synchronous flush of the meta page after committing + * a transaction. This is similar to the #MDB_NOSYNC case, but safer + * because the transaction data is still flushed. The meta page for any + * transaction N will be flushed by the data flush of transaction N+1. + * In case of a system crash, the last committed transaction may be + * lost. This flag may be changed at any time using #mdb_env_set_flags(). + *
  • #MDB_RDONLY + * Open the environment in read-only mode. No write operations will be allowed. + *
+ * @param[in] mode The UNIX permissions to set on created files. This parameter + * is ignored on Windows. + * @return A non-zero error value on failure and 0 on success. Some possible + * errors are: + *
    + *
  • #MDB_VERSION_MISMATCH - the version of the MDB library doesn't match the + * version that created the database environment. + *
  • EINVAL - the environment file headers are corrupted. + *
  • ENOENT - the directory specified by the path parameter doesn't exist. + *
  • EACCES - the user didn't have permission to access the environment files. + *
  • EAGAIN - the environment was locked by another process. + *
+ */ +int mdb_env_open(MDB_env *env, const char *path, unsigned int flags, mode_t mode); + + /** @brief Return statistics about the MDB environment. + * + * @param[in] env An environment handle returned by #mdb_env_create() + * @param[out] stat The address of an #MDB_stat structure + * where the statistics will be copied + */ +int mdb_env_stat(MDB_env *env, MDB_stat *stat); + + /** @brief Flush the data buffers to disk. + * + * Data is always written to disk when #mdb_txn_commit() is called, + * but the operating system may keep it buffered. MDB always flushes + * the OS buffers upon commit as well, unless the environment was + * opened with #MDB_NOSYNC. + * @param[in] env An environment handle returned by #mdb_env_create() + * @param[in] force If non-zero, force the flush to occur. Otherwise + * if the environment has the #MDB_NOSYNC flag set the flushes + * will be omitted. + * @return A non-zero error value on failure and 0 on success. Some possible + * errors are: + *
    + *
  • EINVAL - an invalid parameter was specified. + *
  • EIO - an error occurred during synchronization. + *
+ */ +int mdb_env_sync(MDB_env *env, int force); + + /** @brief Close the environment and release the memory map. + * + * Only a single thread may call this function. All transactions, databases, + * and cursors must already be closed before calling this function. Attempts to + * use any such handles after calling this function will cause a SIGSEGV. + * The environment handle will be freed and must not be used again after this call. + * @param[in] env An environment handle returned by #mdb_env_create() + */ +void mdb_env_close(MDB_env *env); + + /** @brief Set environment flags. + * + * This may be used to set some flags that weren't already set during + * #mdb_env_open(), or to unset these flags. + * @param[in] env An environment handle returned by #mdb_env_create() + * @param[in] flags The flags to change, bitwise OR'ed together + * @param[in] onoff A non-zero value sets the flags, zero clears them. + * @return A non-zero error value on failure and 0 on success. Some possible + * errors are: + *
    + *
  • EINVAL - an invalid parameter was specified. + *
+ */ +int mdb_env_set_flags(MDB_env *env, unsigned int flags, int onoff); + + /** @brief Get environment flags. + * + * @param[in] env An environment handle returned by #mdb_env_create() + * @param[out] flags The address of an integer to store the flags + * @return A non-zero error value on failure and 0 on success. Some possible + * errors are: + *
    + *
  • EINVAL - an invalid parameter was specified. + *
+ */ +int mdb_env_get_flags(MDB_env *env, unsigned int *flags); + + /** @brief Return the path that was used in #mdb_env_open(). + * + * @param[in] env An environment handle returned by #mdb_env_create() + * @param[out] path Address of a string pointer to contain the path. This + * is the actual string in the environment, not a copy. It should not be + * altered in any way. + * @return A non-zero error value on failure and 0 on success. Some possible + * errors are: + *
    + *
  • EINVAL - an invalid parameter was specified. + *
+ */ +int mdb_env_get_path(MDB_env *env, const char **path); + + /** @brief Set the size of the memory map to use for this environment. + * + * The size should be a multiple of the OS page size. The default is + * 10485760 bytes. The size of the memory map is also the maximum size + * of the database. The value should be chosen as large as possible, + * to accommodate future growth of the database. + * This function may only be called after #mdb_env_create() and before #mdb_env_open(). + * @param[in] env An environment handle returned by #mdb_env_create() + * @param[in] size The size in bytes + * @return A non-zero error value on failure and 0 on success. Some possible + * errors are: + *
    + *
  • EINVAL - an invalid parameter was specified, or the environment is already open. + *
+ */ +int mdb_env_set_mapsize(MDB_env *env, size_t size); + + /** @brief Set the maximum number of threads for the environment. + * + * This defines the number of slots in the lock table that is used to track readers in the + * the environment. The default is 126. + * This function may only be called after #mdb_env_create() and before #mdb_env_open(). + * @param[in] env An environment handle returned by #mdb_env_create() + * @param[in] readers The maximum number of threads + * @return A non-zero error value on failure and 0 on success. Some possible + * errors are: + *
    + *
  • EINVAL - an invalid parameter was specified, or the environment is already open. + *
+ */ +int mdb_env_set_maxreaders(MDB_env *env, unsigned int readers); + + /** @brief Get the maximum number of threads for the environment. + * + * @param[in] env An environment handle returned by #mdb_env_create() + * @param[out] readers Address of an integer to store the number of readers + * @return A non-zero error value on failure and 0 on success. Some possible + * errors are: + *
    + *
  • EINVAL - an invalid parameter was specified. + *
+ */ +int mdb_env_get_maxreaders(MDB_env *env, unsigned int *readers); + + /** @brief Set the maximum number of databases for the environment. + * + * This function is only needed if multiple databases will be used in the + * environment. Simpler applications that only use a single database can ignore + * this option. + * This function may only be called after #mdb_env_create() and before #mdb_env_open(). + * @param[in] env An environment handle returned by #mdb_env_create() + * @param[in] dbs The maximum number of databases + * @return A non-zero error value on failure and 0 on success. Some possible + * errors are: + *
    + *
  • EINVAL - an invalid parameter was specified, or the environment is already open. + *
+ */ +int mdb_env_set_maxdbs(MDB_env *env, MDB_dbi dbs); + + /** @brief Create a transaction for use with the environment. + * + * The transaction handle may be discarded using #mdb_txn_abort() or #mdb_txn_commit(). + * @note Transactions may not span threads; a transaction must only be used by a + * single thread. Also, a thread may only have a single transaction. + * @note Cursors may not span transactions; each cursor must be opened and closed + * within a single transaction. + * @param[in] env An environment handle returned by #mdb_env_create() + * @param[in] parent If this parameter is non-NULL, the new transaction + * will be a nested transaction, with the transaction indicated by \b parent + * as its parent. Transactions may be nested to any level. A parent + * transaction may not issue any other operations besides mdb_txn_begin, + * mdb_txn_abort, or mdb_txn_commit while it has active child transactions. + * @param[in] flags Special options for this transaction. This parameter + * must be set to 0 or by bitwise OR'ing together one or more of the + * values described here. + *
    + *
  • #MDB_RDONLY + * This transaction will not perform any write operations. + *
+ * @param[out] txn Address where the new #MDB_txn handle will be stored + * @return A non-zero error value on failure and 0 on success. Some possible + * errors are: + *
    + *
  • #MDB_PANIC - a fatal error occurred earlier and the environment + * must be shut down. + *
  • ENOMEM - out of memory, or a read-only transaction was requested and + * the reader lock table is full. See #mdb_env_set_maxreaders(). + *
+ */ +int mdb_txn_begin(MDB_env *env, MDB_txn *parent, unsigned int flags, MDB_txn **txn); + + /** @brief Commit all the operations of a transaction into the database. + * + * All cursors opened within the transaction will be closed by this call. The cursors + * and transaction handle will be freed and must not be used again after this call. + * @param[in] txn A transaction handle returned by #mdb_txn_begin() + * @return A non-zero error value on failure and 0 on success. Some possible + * errors are: + *
    + *
  • EINVAL - an invalid parameter was specified. + *
  • ENOSPC - no more disk space. + *
  • EIO - a low-level I/O error occurred while writing. + *
  • ENOMEM - the transaction is nested and could not be merged into its parent. + *
+ */ +int mdb_txn_commit(MDB_txn *txn); + + /** @brief Abandon all the operations of the transaction instead of saving them. + * + * All cursors opened within the transaction will be closed by this call. The cursors + * and transaction handle will be freed and must not be used again after this call. + * @param[in] txn A transaction handle returned by #mdb_txn_begin() + */ +void mdb_txn_abort(MDB_txn *txn); + + /** @brief Reset a read-only transaction. + * + * This releases the current reader lock but doesn't free the + * transaction handle, allowing it to be used again later by #mdb_txn_renew(). + * It otherwise has the same effect as #mdb_txn_abort() but saves some memory + * allocation/deallocation overhead if a thread is going to start a new + * read-only transaction again soon. + * All cursors opened within the transaction must be closed before the transaction + * is reset. + * Reader locks generally don't interfere with writers, but they keep old + * versions of database pages allocated. Thus they prevent the old pages + * from being reused when writers commit new data, and so under heavy load + * the database size may grow much more rapidly than otherwise. + * @param[in] txn A transaction handle returned by #mdb_txn_begin() + */ +void mdb_txn_reset(MDB_txn *txn); + + /** @brief Renew a read-only transaction. + * + * This acquires a new reader lock for a transaction handle that had been + * released by #mdb_txn_reset(). It must be called before a reset transaction + * may be used again. + * @param[in] txn A transaction handle returned by #mdb_txn_begin() + * @return A non-zero error value on failure and 0 on success. Some possible + * errors are: + *
    + *
  • #MDB_PANIC - a fatal error occurred earlier and the environment + * must be shut down. + *
  • EINVAL - an invalid parameter was specified. + *
+ */ +int mdb_txn_renew(MDB_txn *txn); + + /** @brief Open a database in the environment. + * + * The database handle may be discarded by calling #mdb_close(). The + * database handle resides in the shared environment, it is not owned + * by the given transaction. Only one thread should call this function; + * it is not mutex-protected in a read-only transaction. + * @param[in] txn A transaction handle returned by #mdb_txn_begin() + * @param[in] name The name of the database to open. If only a single + * database is needed in the environment, this value may be NULL. + * @param[in] flags Special options for this database. This parameter + * must be set to 0 or by bitwise OR'ing together one or more of the + * values described here. + *
    + *
  • #MDB_REVERSEKEY + * Keys are strings to be compared in reverse order, from the end + * of the strings to the beginning. By default, Keys are treated as strings and + * compared from beginning to end. + *
  • #MDB_DUPSORT + * Duplicate keys may be used in the database. (Or, from another perspective, + * keys may have multiple data items, stored in sorted order.) By default + * keys must be unique and may have only a single data item. + *
  • #MDB_INTEGERKEY + * Keys are binary integers in native byte order. Setting this option + * requires all keys to be the same size, typically sizeof(int) + * or sizeof(size_t). + *
  • #MDB_DUPFIXED + * This flag may only be used in combination with #MDB_DUPSORT. This option + * tells the library that the data items for this database are all the same + * size, which allows further optimizations in storage and retrieval. When + * all data items are the same size, the #MDB_GET_MULTIPLE and #MDB_NEXT_MULTIPLE + * cursor operations may be used to retrieve multiple items at once. + *
  • #MDB_INTEGERDUP + * This option specifies that duplicate data items are also integers, and + * should be sorted as such. + *
  • #MDB_REVERSEDUP + * This option specifies that duplicate data items should be compared as + * strings in reverse order. + *
  • #MDB_CREATE + * Create the named database if it doesn't exist. This option is not + * allowed in a read-only transaction or a read-only environment. + *
+ * @param[out] dbi Address where the new #MDB_dbi handle will be stored + * @return A non-zero error value on failure and 0 on success. Some possible + * errors are: + *
    + *
  • #MDB_NOTFOUND - the specified database doesn't exist in the environment + * and #MDB_CREATE was not specified. + *
  • ENFILE - too many databases have been opened. See #mdb_env_set_maxdbs(). + *
+ */ +int mdb_open(MDB_txn *txn, const char *name, unsigned int flags, MDB_dbi *dbi); + + /** @brief Retrieve statistics for a database. + * + * @param[in] txn A transaction handle returned by #mdb_txn_begin() + * @param[in] dbi A database handle returned by #mdb_open() + * @param[out] stat The address of an #MDB_stat structure + * where the statistics will be copied + * @return A non-zero error value on failure and 0 on success. Some possible + * errors are: + *
    + *
  • EINVAL - an invalid parameter was specified. + *
+ */ +int mdb_stat(MDB_txn *txn, MDB_dbi dbi, MDB_stat *stat); + + /** @brief Close a database handle. + * + * This call is not mutex protected. Handles should only be closed by + * a single thread, and only if no other threads are going to reference + * the database handle any further. + * @param[in] env An environment handle returned by #mdb_env_create() + * @param[in] dbi A database handle returned by #mdb_open() + */ +void mdb_close(MDB_env *env, MDB_dbi dbi); + + /** @brief Delete a database and/or free all its pages. + * + * If the \b del parameter is non-zero the DB handle will be closed + * and the DB will be deleted. + * @param[in] txn A transaction handle returned by #mdb_txn_begin() + * @param[in] dbi A database handle returned by #mdb_open() + * @param[in] del non-zero to delete the DB from the environment, + * otherwise just free its pages. + * @return A non-zero error value on failure and 0 on success. + */ +int mdb_drop(MDB_txn *txn, MDB_dbi dbi, int del); + + /** @brief Set a custom key comparison function for a database. + * + * The comparison function is called whenever it is necessary to compare a + * key specified by the application with a key currently stored in the database. + * If no comparison function is specified, and no special key flags were specified + * with #mdb_open(), the keys are compared lexically, with shorter keys collating + * before longer keys. + * @warning This function must be called before any data access functions are used, + * otherwise data corruption may occur. The same comparison function must be used by every + * program accessing the database, every time the database is used. + * @param[in] txn A transaction handle returned by #mdb_txn_begin() + * @param[in] dbi A database handle returned by #mdb_open() + * @param[in] cmp A #MDB_cmp_func function + * @return A non-zero error value on failure and 0 on success. Some possible + * errors are: + *
    + *
  • EINVAL - an invalid parameter was specified. + *
+ */ +int mdb_set_compare(MDB_txn *txn, MDB_dbi dbi, MDB_cmp_func *cmp); + + /** @brief Set a custom data comparison function for a #MDB_DUPSORT database. + * + * This comparison function is called whenever it is necessary to compare a data + * item specified by the application with a data item currently stored in the database. + * This function only takes effect if the database was opened with the #MDB_DUPSORT + * flag. + * If no comparison function is specified, and no special key flags were specified + * with #mdb_open(), the data items are compared lexically, with shorter items collating + * before longer items. + * @warning This function must be called before any data access functions are used, + * otherwise data corruption may occur. The same comparison function must be used by every + * program accessing the database, every time the database is used. + * @param[in] txn A transaction handle returned by #mdb_txn_begin() + * @param[in] dbi A database handle returned by #mdb_open() + * @param[in] cmp A #MDB_cmp_func function + * @return A non-zero error value on failure and 0 on success. Some possible + * errors are: + *
    + *
  • EINVAL - an invalid parameter was specified. + *
+ */ +int mdb_set_dupsort(MDB_txn *txn, MDB_dbi dbi, MDB_cmp_func *cmp); + + /** @brief Set a relocation function for a #MDB_FIXEDMAP database. + * + * @todo The relocation function is called whenever it is necessary to move the data + * of an item to a different position in the database (e.g. through tree + * balancing operations, shifts as a result of adds or deletes, etc.). It is + * intended to allow address/position-dependent data items to be stored in + * a database in an environment opened with the #MDB_FIXEDMAP option. + * Currently the relocation feature is unimplemented and setting + * this function has no effect. + * @param[in] txn A transaction handle returned by #mdb_txn_begin() + * @param[in] dbi A database handle returned by #mdb_open() + * @param[in] rel A #MDB_rel_func function + * @return A non-zero error value on failure and 0 on success. Some possible + * errors are: + *
    + *
  • EINVAL - an invalid parameter was specified. + *
+ */ +int mdb_set_relfunc(MDB_txn *txn, MDB_dbi dbi, MDB_rel_func *rel); + + /** @brief Set a context pointer for a #MDB_FIXEDMAP database's relocation function. + * + * See #mdb_set_relfunc and #MDB_rel_func for more details. + * @param[in] txn A transaction handle returned by #mdb_txn_begin() + * @param[in] dbi A database handle returned by #mdb_open() + * @param[in] ctx An arbitrary pointer for whatever the application needs. + * It will be passed to the callback function set by #mdb_set_relfunc + * as its \b relctx parameter whenever the callback is invoked. + * @return A non-zero error value on failure and 0 on success. Some possible + * errors are: + *
    + *
  • EINVAL - an invalid parameter was specified. + *
+ */ +int mdb_set_relctx(MDB_txn *txn, MDB_dbi dbi, void *ctx); + + /** @brief Get items from a database. + * + * This function retrieves key/data pairs from the database. The address + * and length of the data associated with the specified \b key are returned + * in the structure to which \b data refers. + * If the database supports duplicate keys (#MDB_DUPSORT) then the + * first data item for the key will be returned. Retrieval of other + * items requires the use of #mdb_cursor_get(). + * + * @note The memory pointed to by the returned values is owned by the + * database. The caller need not dispose of the memory, and may not + * modify it in any way. For values returned in a read-only transaction + * any modification attempts will cause a SIGSEGV. + * @param[in] txn A transaction handle returned by #mdb_txn_begin() + * @param[in] dbi A database handle returned by #mdb_open() + * @param[in] key The key to search for in the database + * @param[out] data The data corresponding to the key + * @return A non-zero error value on failure and 0 on success. Some possible + * errors are: + *
    + *
  • #MDB_NOTFOUND - the key was not in the database. + *
  • EINVAL - an invalid parameter was specified. + *
+ */ +int mdb_get(MDB_txn *txn, MDB_dbi dbi, MDB_val *key, MDB_val *data); + + /** @brief Store items into a database. + * + * This function stores key/data pairs in the database. The default behavior + * is to enter the new key/data pair, replacing any previously existing key + * if duplicates are disallowed, or adding a duplicate data item if + * duplicates are allowed (#MDB_DUPSORT). + * @param[in] txn A transaction handle returned by #mdb_txn_begin() + * @param[in] dbi A database handle returned by #mdb_open() + * @param[in] key The key to store in the database + * @param[in,out] data The data to store + * @param[in] flags Special options for this operation. This parameter + * must be set to 0 or by bitwise OR'ing together one or more of the + * values described here. + *
    + *
  • #MDB_NODUPDATA - enter the new key/data pair only if it does not + * already appear in the database. This flag may only be specified + * if the database was opened with #MDB_DUPSORT. The function will + * return #MDB_KEYEXIST if the key/data pair already appears in the + * database. + *
  • #MDB_NOOVERWRITE - enter the new key/data pair only if the key + * does not already appear in the database. The function will return + * #MDB_KEYEXIST if the key already appears in the database, even if + * the database supports duplicates (#MDB_DUPSORT). The \b data + * parameter will be set to point to the existing item. + *
  • #MDB_RESERVE - reserve space for data of the given size, but + * don't copy the given data. Instead, return a pointer to the + * reserved space, which the caller can fill in later. This saves + * an extra memcpy if the data is being generated later. + *
  • #MDB_APPEND - append the given key/data pair to the end of the + * database. No key comparisons are performed. This option allows + * fast bulk loading when keys are already known to be in the + * correct order. Loading unsorted keys with this flag will cause + * data corruption. + *
  • #MDB_APPENDDUP - as above, but for sorted dup data. + *
+ * @return A non-zero error value on failure and 0 on success. Some possible + * errors are: + *
    + *
  • EACCES - an attempt was made to write in a read-only transaction. + *
  • EINVAL - an invalid parameter was specified. + *
  • ENOMEM - the database is full, see #mdb_env_set_mapsize(). + *
+ */ +int mdb_put(MDB_txn *txn, MDB_dbi dbi, MDB_val *key, MDB_val *data, + unsigned int flags); + + /** @brief Delete items from a database. + * + * This function removes key/data pairs from the database. + * If the database does not support sorted duplicate data items + * (#MDB_DUPSORT) the data parameter is ignored. + * If the database supports sorted duplicates and the data parameter + * is NULL, all of the duplicate data items for the key will be + * deleted. Otherwise, if the data parameter is non-NULL + * only the matching data item will be deleted. + * This function will return #MDB_NOTFOUND if the specified key/data + * pair is not in the database. + * @param[in] txn A transaction handle returned by #mdb_txn_begin() + * @param[in] dbi A database handle returned by #mdb_open() + * @param[in] key The key to delete from the database + * @param[in] data The data to delete + * @return A non-zero error value on failure and 0 on success. Some possible + * errors are: + *
    + *
  • EACCES - an attempt was made to write in a read-only transaction. + *
  • EINVAL - an invalid parameter was specified. + *
+ */ +int mdb_del(MDB_txn *txn, MDB_dbi dbi, MDB_val *key, MDB_val *data); + + /** @brief Create a cursor handle. + * + * Cursors are associated with a specific transaction and database and + * may not span threads. + * @param[in] txn A transaction handle returned by #mdb_txn_begin() + * @param[in] dbi A database handle returned by #mdb_open() + * @param[out] cursor Address where the new #MDB_cursor handle will be stored + * @return A non-zero error value on failure and 0 on success. Some possible + * errors are: + *
    + *
  • EINVAL - an invalid parameter was specified. + *
+ */ +int mdb_cursor_open(MDB_txn *txn, MDB_dbi dbi, MDB_cursor **cursor); + + /** @brief Close a cursor handle. + * + * The cursor handle will be freed and must not be used again after this call. + * @param[in] cursor A cursor handle returned by #mdb_cursor_open() + */ +void mdb_cursor_close(MDB_cursor *cursor); + + /** @brief Renew a cursor handle. + * + * Cursors are associated with a specific transaction and database and + * may not span threads. Cursors that are only used in read-only + * transactions may be re-used, to avoid unnecessary malloc/free overhead. + * The cursor may be associated with a new read-only transaction, and + * referencing the same database handle as it was created with. + * @param[in] txn A transaction handle returned by #mdb_txn_begin() + * @param[in] cursor A cursor handle returned by #mdb_cursor_open() + * @return A non-zero error value on failure and 0 on success. Some possible + * errors are: + *
    + *
  • EINVAL - an invalid parameter was specified. + *
+ */ +int mdb_cursor_renew(MDB_txn *txn, MDB_cursor *cursor); + + /** @brief Return the cursor's transaction handle. + * + * @param[in] cursor A cursor handle returned by #mdb_cursor_open() + */ +MDB_txn *mdb_cursor_txn(MDB_cursor *cursor); + + /** @brief Return the cursor's database handle. + * + * @param[in] cursor A cursor handle returned by #mdb_cursor_open() + */ +MDB_dbi mdb_cursor_dbi(MDB_cursor *cursor); + + /** @brief Retrieve by cursor. + * + * This function retrieves key/data pairs from the database. The address and length + * of the key are returned in the object to which \b key refers (except for the + * case of the #MDB_SET option, in which the \b key object is unchanged), and + * the address and length of the data are returned in the object to which \b data + * refers. + * @param[in] cursor A cursor handle returned by #mdb_cursor_open() + * @param[in,out] key The key for a retrieved item + * @param[in,out] data The data of a retrieved item + * @param[in] op A cursor operation #MDB_cursor_op + * @return A non-zero error value on failure and 0 on success. Some possible + * errors are: + *
    + *
  • #MDB_NOTFOUND - no matching key found. + *
  • EINVAL - an invalid parameter was specified. + *
+ */ +int mdb_cursor_get(MDB_cursor *cursor, MDB_val *key, MDB_val *data, + MDB_cursor_op op); + + /** @brief Store by cursor. + * + * This function stores key/data pairs into the database. + * If the function fails for any reason, the state of the cursor will be + * unchanged. If the function succeeds and an item is inserted into the + * database, the cursor is always positioned to refer to the newly inserted item. + * @param[in] cursor A cursor handle returned by #mdb_cursor_open() + * @param[in] key The key operated on. + * @param[in] data The data operated on. + * @param[in] flags Options for this operation. This parameter + * must be set to 0 or one of the values described here. + *
    + *
  • #MDB_CURRENT - overwrite the data of the key/data pair to which + * the cursor refers with the specified data item. The \b key + * parameter is ignored. + *
  • #MDB_NODUPDATA - enter the new key/data pair only if it does not + * already appear in the database. This flag may only be specified + * if the database was opened with #MDB_DUPSORT. The function will + * return #MDB_KEYEXIST if the key/data pair already appears in the + * database. + *
  • #MDB_NOOVERWRITE - enter the new key/data pair only if the key + * does not already appear in the database. The function will return + * #MDB_KEYEXIST if the key already appears in the database, even if + * the database supports duplicates (#MDB_DUPSORT). + *
  • #MDB_RESERVE - reserve space for data of the given size, but + * don't copy the given data. Instead, return a pointer to the + * reserved space, which the caller can fill in later. This saves + * an extra memcpy if the data is being generated later. + *
  • #MDB_APPEND - append the given key/data pair to the end of the + * database. No key comparisons are performed. This option allows + * fast bulk loading when keys are already known to be in the + * correct order. Loading unsorted keys with this flag will cause + * data corruption. + *
  • #MDB_APPENDDUP - as above, but for sorted dup data. + *
+ * @return A non-zero error value on failure and 0 on success. Some possible + * errors are: + *
    + *
  • EACCES - an attempt was made to modify a read-only database. + *
  • EINVAL - an invalid parameter was specified. + *
+ */ +int mdb_cursor_put(MDB_cursor *cursor, MDB_val *key, MDB_val *data, + unsigned int flags); + + /** @brief Delete current key/data pair + * + * This function deletes the key/data pair to which the cursor refers. + * @param[in] cursor A cursor handle returned by #mdb_cursor_open() + * @param[in] flags Options for this operation. This parameter + * must be set to 0 or one of the values described here. + *
    + *
  • #MDB_NODUPDATA - delete all of the data items for the current key. + * This flag may only be specified if the database was opened with #MDB_DUPSORT. + *
+ * @return A non-zero error value on failure and 0 on success. Some possible + * errors are: + *
    + *
  • EACCES - an attempt was made to modify a read-only database. + *
  • EINVAL - an invalid parameter was specified. + *
+ */ +int mdb_cursor_del(MDB_cursor *cursor, unsigned int flags); + + /** @brief Return count of duplicates for current key. + * + * This call is only valid on databases that support sorted duplicate + * data items #MDB_DUPSORT. + * @param[in] cursor A cursor handle returned by #mdb_cursor_open() + * @param[out] countp Address where the count will be stored + * @return A non-zero error value on failure and 0 on success. Some possible + * errors are: + *
    + *
  • EINVAL - cursor is not initialized, or an invalid parameter was specified. + *
+ */ +int mdb_cursor_count(MDB_cursor *cursor, size_t *countp); + + /** @brief Compare two data items according to a particular database. + * + * This returns a comparison as if the two data items were keys in the + * specified database. + * @param[in] txn A transaction handle returned by #mdb_txn_begin() + * @param[in] dbi A database handle returned by #mdb_open() + * @param[in] a The first item to compare + * @param[in] b The second item to compare + * @return < 0 if a < b, 0 if a == b, > 0 if a > b + */ +int mdb_cmp(MDB_txn *txn, MDB_dbi dbi, const MDB_val *a, const MDB_val *b); + + /** @brief Compare two data items according to a particular database. + * + * This returns a comparison as if the two items were data items of + * a sorted duplicates #MDB_DUPSORT database. + * @param[in] txn A transaction handle returned by #mdb_txn_begin() + * @param[in] dbi A database handle returned by #mdb_open() + * @param[in] a The first item to compare + * @param[in] b The second item to compare + * @return < 0 if a < b, 0 if a == b, > 0 if a > b + */ +int mdb_dcmp(MDB_txn *txn, MDB_dbi dbi, const MDB_val *a, const MDB_val *b); +/** @} */ + +#ifdef __cplusplus +} +#endif +#endif /* _MDB_H_ */ diff --git a/c_src/midl.c b/c_src/midl.c new file mode 100644 index 0000000..9ee100d --- /dev/null +++ b/c_src/midl.c @@ -0,0 +1,327 @@ +/** @file midl.c + * @brief ldap bdb back-end ID List functions */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * Copyright 2000-2012 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . + */ + +#include +#include +#include +#include +#include +#include "midl.h" + +/** @defgroup internal MDB Internals + * @{ + */ +/** @defgroup idls ID List Management + * @{ + */ +#define CMP(x,y) ( (x) < (y) ? -1 : (x) > (y) ) + +#if 0 /* superseded by append/sort */ +static unsigned mdb_midl_search( MDB_IDL ids, MDB_ID id ) +{ + /* + * binary search of id in ids + * if found, returns position of id + * if not found, returns first position greater than id + */ + unsigned base = 0; + unsigned cursor = 1; + int val = 0; + unsigned n = ids[0]; + + while( 0 < n ) { + unsigned pivot = n >> 1; + cursor = base + pivot + 1; + val = CMP( ids[cursor], id ); + + if( val < 0 ) { + n = pivot; + + } else if ( val > 0 ) { + base = cursor; + n -= pivot + 1; + + } else { + return cursor; + } + } + + if( val > 0 ) { + ++cursor; + } + return cursor; +} + +int mdb_midl_insert( MDB_IDL ids, MDB_ID id ) +{ + unsigned x, i; + + if (MDB_IDL_IS_RANGE( ids )) { + /* if already in range, treat as a dup */ + if (id >= MDB_IDL_RANGE_FIRST(ids) && id <= MDB_IDL_RANGE_LAST(ids)) + return -1; + if (id < MDB_IDL_RANGE_FIRST(ids)) + ids[1] = id; + else if (id > MDB_IDL_RANGE_LAST(ids)) + ids[2] = id; + return 0; + } + + x = mdb_midl_search( ids, id ); + assert( x > 0 ); + + if( x < 1 ) { + /* internal error */ + return -2; + } + + if ( x <= ids[0] && ids[x] == id ) { + /* duplicate */ + assert(0); + return -1; + } + + if ( ++ids[0] >= MDB_IDL_DB_MAX ) { + if( id < ids[1] ) { + ids[1] = id; + ids[2] = ids[ids[0]-1]; + } else if ( ids[ids[0]-1] < id ) { + ids[2] = id; + } else { + ids[2] = ids[ids[0]-1]; + } + ids[0] = MDB_NOID; + + } else { + /* insert id */ + for (i=ids[0]; i>x; i--) + ids[i] = ids[i-1]; + ids[x] = id; + } + + return 0; +} +#endif + +MDB_IDL mdb_midl_alloc() +{ + MDB_IDL ids = malloc((MDB_IDL_UM_MAX+1) * sizeof(MDB_ID)); + *ids++ = MDB_IDL_UM_MAX; + return ids; +} + +void mdb_midl_free(MDB_IDL ids) +{ + free(ids-1); +} + +int mdb_midl_shrink( MDB_IDL *idp ) +{ + MDB_IDL ids = *idp; + if (ids[-1] > MDB_IDL_UM_MAX) { + ids = realloc(ids, (MDB_IDL_UM_MAX+1) * sizeof(MDB_ID)); + *ids++ = MDB_IDL_UM_MAX; + *idp = ids; + return 1; + } + return 0; +} + +int mdb_midl_append( MDB_IDL *idp, MDB_ID id ) +{ + MDB_IDL ids = *idp; + /* Too big? */ + if (ids[0] >= ids[-1]) { + MDB_IDL idn = ids-1; + /* grow it */ + idn = realloc(idn, (*idn + MDB_IDL_UM_MAX + 1) * sizeof(MDB_ID)); + if (!idn) + return -1; + *idn++ += MDB_IDL_UM_MAX; + ids = idn; + *idp = ids; + } + ids[0]++; + ids[ids[0]] = id; + return 0; +} + +int mdb_midl_append_list( MDB_IDL *idp, MDB_IDL app ) +{ + MDB_IDL ids = *idp; + /* Too big? */ + if (ids[0] + app[0] >= ids[-1]) { + MDB_IDL idn = ids-1; + /* grow it */ + idn = realloc(idn, (*idn + app[-1]) * sizeof(MDB_ID)); + if (!idn) + return -1; + *idn++ += app[-1]; + ids = idn; + *idp = ids; + } + memcpy(&ids[ids[0]+1], &app[1], app[0] * sizeof(MDB_ID)); + ids[0] += app[0]; + return 0; +} + +/* Quicksort + Insertion sort for small arrays */ + +#define SMALL 8 +#define SWAP(a,b) { itmp=(a); (a)=(b); (b)=itmp; } + +void +mdb_midl_sort( MDB_IDL ids ) +{ + /* Max possible depth of int-indexed tree * 2 items/level */ + int istack[sizeof(int)*CHAR_BIT * 2]; + int i,j,k,l,ir,jstack; + MDB_ID a, itmp; + + ir = ids[0]; + l = 1; + jstack = 0; + for(;;) { + if (ir - l < SMALL) { /* Insertion sort */ + for (j=l+1;j<=ir;j++) { + a = ids[j]; + for (i=j-1;i>=1;i--) { + if (ids[i] >= a) break; + ids[i+1] = ids[i]; + } + ids[i+1] = a; + } + if (jstack == 0) break; + ir = istack[jstack--]; + l = istack[jstack--]; + } else { + k = (l + ir) >> 1; /* Choose median of left, center, right */ + SWAP(ids[k], ids[l+1]); + if (ids[l] < ids[ir]) { + SWAP(ids[l], ids[ir]); + } + if (ids[l+1] < ids[ir]) { + SWAP(ids[l+1], ids[ir]); + } + if (ids[l] < ids[l+1]) { + SWAP(ids[l], ids[l+1]); + } + i = l+1; + j = ir; + a = ids[l+1]; + for(;;) { + do i++; while(ids[i] > a); + do j--; while(ids[j] < a); + if (j < i) break; + SWAP(ids[i],ids[j]); + } + ids[l+1] = ids[j]; + ids[j] = a; + jstack += 2; + if (ir-i+1 >= j-1) { + istack[jstack] = ir; + istack[jstack-1] = i; + ir = j-1; + } else { + istack[jstack] = j-1; + istack[jstack-1] = l; + l = i; + } + } + } +} + +unsigned mdb_mid2l_search( MDB_ID2L ids, MDB_ID id ) +{ + /* + * binary search of id in ids + * if found, returns position of id + * if not found, returns first position greater than id + */ + unsigned base = 0; + unsigned cursor = 1; + int val = 0; + unsigned n = ids[0].mid; + + while( 0 < n ) { + unsigned pivot = n >> 1; + cursor = base + pivot + 1; + val = CMP( id, ids[cursor].mid ); + + if( val < 0 ) { + n = pivot; + + } else if ( val > 0 ) { + base = cursor; + n -= pivot + 1; + + } else { + return cursor; + } + } + + if( val > 0 ) { + ++cursor; + } + return cursor; +} + +int mdb_mid2l_insert( MDB_ID2L ids, MDB_ID2 *id ) +{ + unsigned x, i; + + x = mdb_mid2l_search( ids, id->mid ); + assert( x > 0 ); + + if( x < 1 ) { + /* internal error */ + return -2; + } + + if ( x <= ids[0].mid && ids[x].mid == id->mid ) { + /* duplicate */ + return -1; + } + + if ( ids[0].mid >= MDB_IDL_UM_MAX ) { + /* too big */ + return -2; + + } else { + /* insert id */ + ids[0].mid++; + for (i=ids[0].mid; i>x; i--) + ids[i] = ids[i-1]; + ids[x] = *id; + } + + return 0; +} + +int mdb_mid2l_append( MDB_ID2L ids, MDB_ID2 *id ) +{ + /* Too big? */ + if (ids[0].mid >= MDB_IDL_UM_MAX) { + return -2; + } + ids[0].mid++; + ids[ids[0].mid] = *id; + return 0; +} + +/** @} */ +/** @} */ diff --git a/c_src/midl.h b/c_src/midl.h new file mode 100644 index 0000000..0fd1cfb --- /dev/null +++ b/c_src/midl.h @@ -0,0 +1,195 @@ +/** @file midl.h + * @brief mdb ID List header file. + * + * This file was originally part of back-bdb but has been + * modified for use in libmdb. Most of the macros defined + * in this file are unused, just left over from the original. + * + * This file is only used internally in libmdb and its definitions + * are not exposed publicly. + */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * Copyright 2000-2012 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . + */ + +#ifndef _MDB_MIDL_H_ +#define _MDB_MIDL_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @defgroup internal MDB Internals + * @{ + */ + +/** @defgroup idls ID List Management + * @{ + */ + /** A generic ID number. These were entryIDs in back-bdb. + * Preferably it should have the same size as a pointer. + */ +typedef size_t MDB_ID; + + /** An IDL is an ID List, a sorted array of IDs. The first + * element of the array is a counter for how many actual + * IDs are in the list. In the original back-bdb code, IDLs are + * sorted in ascending order. For libmdb IDLs are sorted in + * descending order. + */ +typedef MDB_ID *MDB_IDL; + +#define MDB_NOID (~(MDB_ID)0) + +/* IDL sizes - likely should be even bigger + * limiting factors: sizeof(ID), thread stack size + */ +#define MDB_IDL_LOGN 16 /* DB_SIZE is 2^16, UM_SIZE is 2^17 */ +#define MDB_IDL_DB_SIZE (1<bi_lastid) ) +#define MDB_IDL_ALL( bdb, ids ) MDB_IDL_RANGE( ids, 1, ((bdb)->bi_lastid) ) + +#define MDB_IDL_FIRST( ids ) ( (ids)[1] ) +#define MDB_IDL_LAST( ids ) ( MDB_IDL_IS_RANGE(ids) \ + ? (ids)[2] : (ids)[(ids)[0]] ) + +#define MDB_IDL_N( ids ) ( MDB_IDL_IS_RANGE(ids) \ + ? ((ids)[2]-(ids)[1])+1 : (ids)[0] ) + +#if 0 /* superseded by append/sort */ + /** Insert an ID into an IDL. + * @param[in,out] ids The IDL to insert into. + * @param[in] id The ID to insert. + * @return 0 on success, -1 if the ID was already present in the IDL. + */ +int mdb_midl_insert( MDB_IDL ids, MDB_ID id ); +#endif + + /** Allocate an IDL. + * Allocates memory for an IDL of a default size. + * @return IDL on success, NULL on failure. + */ +MDB_IDL mdb_midl_alloc(); + + /** Free an IDL. + * @param[in] ids The IDL to free. + */ +void mdb_midl_free(MDB_IDL ids); + + /** Shrink an IDL. + * Return the IDL to the default size if it has grown larger. + * @param[in,out] idp Address of the IDL to shrink. + * @return 0 on no change, non-zero if shrunk. + */ +int mdb_midl_shrink(MDB_IDL *idp); + + /** Append an ID onto an IDL. + * @param[in,out] idp Address of the IDL to append to. + * @param[in] id The ID to append. + * @return 0 on success, -1 if the IDL is too large. + */ +int mdb_midl_append( MDB_IDL *idp, MDB_ID id ); + + /** Append an IDL onto an IDL. + * @param[in,out] idp Address of the IDL to append to. + * @param[in] app The IDL to append. + * @return 0 on success, -1 if the IDL is too large. + */ +int mdb_midl_append_list( MDB_IDL *idp, MDB_IDL app ); + + /** Sort an IDL. + * @param[in,out] ids The IDL to sort. + */ +void mdb_midl_sort( MDB_IDL ids ); + + /** An ID2 is an ID/pointer pair. + */ +typedef struct MDB_ID2 { + MDB_ID mid; /**< The ID */ + void *mptr; /**< The pointer */ +} MDB_ID2; + + /** An ID2L is an ID2 List, a sorted array of ID2s. + * The first element's \b mid member is a count of how many actual + * elements are in the array. The \b mptr member of the first element is unused. + * The array is sorted in ascending order by \b mid. + */ +typedef MDB_ID2 *MDB_ID2L; + + /** Search for an ID in an ID2L. + * @param[in] ids The ID2L to search. + * @param[in] id The ID to search for. + * @return The index of the first ID2 whose \b mid member is greater than or equal to \b id. + */ +unsigned mdb_mid2l_search( MDB_ID2L ids, MDB_ID id ); + + + /** Insert an ID2 into a ID2L. + * @param[in,out] ids The ID2L to insert into. + * @param[in] id The ID2 to insert. + * @return 0 on success, -1 if the ID was already present in the ID2L. + */ +int mdb_mid2l_insert( MDB_ID2L ids, MDB_ID2 *id ); + + /** Append an ID2 into a ID2L. + * @param[in,out] ids The ID2L to append into. + * @param[in] id The ID2 to append. + * @return 0 on success, -2 if the ID2L is too big. + */ +int mdb_mid2l_append( MDB_ID2L ids, MDB_ID2 *id ); + +/** @} */ +/** @} */ +#ifdef __cplusplus +} +#endif +#endif /* _MDB_MIDL_H_ */ diff --git a/c_src/uthash.h b/c_src/uthash.h new file mode 100644 index 0000000..9f83fc3 --- /dev/null +++ b/c_src/uthash.h @@ -0,0 +1,915 @@ +/* +Copyright (c) 2003-2012, Troy D. Hanson http://uthash.sourceforge.net +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER +OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef UTHASH_H +#define UTHASH_H + +#include /* memcmp,strlen */ +#include /* ptrdiff_t */ +#include /* exit() */ + +/* These macros use decltype or the earlier __typeof GNU extension. + As decltype is only available in newer compilers (VS2010 or gcc 4.3+ + when compiling c++ source) this code uses whatever method is needed + or, for VS2008 where neither is available, uses casting workarounds. */ +#ifdef _MSC_VER /* MS compiler */ +#if _MSC_VER >= 1600 && defined(__cplusplus) /* VS2010 or newer in C++ mode */ +#define DECLTYPE(x) (decltype(x)) +#else /* VS2008 or older (or VS2010 in C mode) */ +#define NO_DECLTYPE +#define DECLTYPE(x) +#endif +#else /* GNU, Sun and other compilers */ +#define DECLTYPE(x) (__typeof(x)) +#endif + +#ifdef NO_DECLTYPE +#define DECLTYPE_ASSIGN(dst,src) \ +do { \ + char **_da_dst = (char**)(&(dst)); \ + *_da_dst = (char*)(src); \ +} while(0) +#else +#define DECLTYPE_ASSIGN(dst,src) \ +do { \ + (dst) = DECLTYPE(dst)(src); \ +} while(0) +#endif + +/* a number of the hash function use uint32_t which isn't defined on win32 */ +#ifdef _MSC_VER +typedef unsigned int uint32_t; +typedef unsigned char uint8_t; +#else +#include /* uint32_t */ +#endif + +#define UTHASH_VERSION 1.9.6 + +#ifndef uthash_fatal +#define uthash_fatal(msg) exit(-1) /* fatal error (out of memory,etc) */ +#endif +#ifndef uthash_malloc +#define uthash_malloc(sz) malloc(sz) /* malloc fcn */ +#endif +#ifndef uthash_free +#define uthash_free(ptr,sz) free(ptr) /* free fcn */ +#endif + +#ifndef uthash_noexpand_fyi +#define uthash_noexpand_fyi(tbl) /* can be defined to log noexpand */ +#endif +#ifndef uthash_expand_fyi +#define uthash_expand_fyi(tbl) /* can be defined to log expands */ +#endif + +/* initial number of buckets */ +#define HASH_INITIAL_NUM_BUCKETS 32 /* initial number of buckets */ +#define HASH_INITIAL_NUM_BUCKETS_LOG2 5 /* lg2 of initial number of buckets */ +#define HASH_BKT_CAPACITY_THRESH 10 /* expand when bucket count reaches */ + +/* calculate the element whose hash handle address is hhe */ +#define ELMT_FROM_HH(tbl,hhp) ((void*)(((char*)(hhp)) - ((tbl)->hho))) + +#define HASH_FIND(hh,head,keyptr,keylen,out) \ +do { \ + unsigned _hf_bkt,_hf_hashv; \ + out=NULL; \ + if (head) { \ + HASH_FCN(keyptr,keylen, (head)->hh.tbl->num_buckets, _hf_hashv, _hf_bkt); \ + if (HASH_BLOOM_TEST((head)->hh.tbl, _hf_hashv)) { \ + HASH_FIND_IN_BKT((head)->hh.tbl, hh, (head)->hh.tbl->buckets[ _hf_bkt ], \ + keyptr,keylen,out); \ + } \ + } \ +} while (0) + +#ifdef HASH_BLOOM +#define HASH_BLOOM_BITLEN (1ULL << HASH_BLOOM) +#define HASH_BLOOM_BYTELEN (HASH_BLOOM_BITLEN/8) + ((HASH_BLOOM_BITLEN%8) ? 1:0) +#define HASH_BLOOM_MAKE(tbl) \ +do { \ + (tbl)->bloom_nbits = HASH_BLOOM; \ + (tbl)->bloom_bv = (uint8_t*)uthash_malloc(HASH_BLOOM_BYTELEN); \ + if (!((tbl)->bloom_bv)) { uthash_fatal( "out of memory"); } \ + memset((tbl)->bloom_bv, 0, HASH_BLOOM_BYTELEN); \ + (tbl)->bloom_sig = HASH_BLOOM_SIGNATURE; \ +} while (0) + +#define HASH_BLOOM_FREE(tbl) \ +do { \ + uthash_free((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ +} while (0) + +#define HASH_BLOOM_BITSET(bv,idx) (bv[(idx)/8] |= (1U << ((idx)%8))) +#define HASH_BLOOM_BITTEST(bv,idx) (bv[(idx)/8] & (1U << ((idx)%8))) + +#define HASH_BLOOM_ADD(tbl,hashv) \ + HASH_BLOOM_BITSET((tbl)->bloom_bv, (hashv & (uint32_t)((1ULL << (tbl)->bloom_nbits) - 1))) + +#define HASH_BLOOM_TEST(tbl,hashv) \ + HASH_BLOOM_BITTEST((tbl)->bloom_bv, (hashv & (uint32_t)((1ULL << (tbl)->bloom_nbits) - 1))) + +#else +#define HASH_BLOOM_MAKE(tbl) +#define HASH_BLOOM_FREE(tbl) +#define HASH_BLOOM_ADD(tbl,hashv) +#define HASH_BLOOM_TEST(tbl,hashv) (1) +#endif + +#define HASH_MAKE_TABLE(hh,head) \ +do { \ + (head)->hh.tbl = (UT_hash_table*)uthash_malloc( \ + sizeof(UT_hash_table)); \ + if (!((head)->hh.tbl)) { uthash_fatal( "out of memory"); } \ + memset((head)->hh.tbl, 0, sizeof(UT_hash_table)); \ + (head)->hh.tbl->tail = &((head)->hh); \ + (head)->hh.tbl->num_buckets = HASH_INITIAL_NUM_BUCKETS; \ + (head)->hh.tbl->log2_num_buckets = HASH_INITIAL_NUM_BUCKETS_LOG2; \ + (head)->hh.tbl->hho = (char*)(&(head)->hh) - (char*)(head); \ + (head)->hh.tbl->buckets = (UT_hash_bucket*)uthash_malloc( \ + HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket)); \ + if (! (head)->hh.tbl->buckets) { uthash_fatal( "out of memory"); } \ + memset((head)->hh.tbl->buckets, 0, \ + HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket)); \ + HASH_BLOOM_MAKE((head)->hh.tbl); \ + (head)->hh.tbl->signature = HASH_SIGNATURE; \ +} while(0) + +#define HASH_ADD(hh,head,fieldname,keylen_in,add) \ + HASH_ADD_KEYPTR(hh,head,&((add)->fieldname),keylen_in,add) + +#define HASH_ADD_KEYPTR(hh,head,keyptr,keylen_in,add) \ +do { \ + unsigned _ha_bkt; \ + (add)->hh.next = NULL; \ + (add)->hh.key = (char*)keyptr; \ + (add)->hh.keylen = (unsigned)keylen_in; \ + if (!(head)) { \ + head = (add); \ + (head)->hh.prev = NULL; \ + HASH_MAKE_TABLE(hh,head); \ + } else { \ + (head)->hh.tbl->tail->next = (add); \ + (add)->hh.prev = ELMT_FROM_HH((head)->hh.tbl, (head)->hh.tbl->tail); \ + (head)->hh.tbl->tail = &((add)->hh); \ + } \ + (head)->hh.tbl->num_items++; \ + (add)->hh.tbl = (head)->hh.tbl; \ + HASH_FCN(keyptr,keylen_in, (head)->hh.tbl->num_buckets, \ + (add)->hh.hashv, _ha_bkt); \ + HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt],&(add)->hh); \ + HASH_BLOOM_ADD((head)->hh.tbl,(add)->hh.hashv); \ + HASH_EMIT_KEY(hh,head,keyptr,keylen_in); \ + HASH_FSCK(hh,head); \ +} while(0) + +#define HASH_TO_BKT( hashv, num_bkts, bkt ) \ +do { \ + bkt = ((hashv) & ((num_bkts) - 1)); \ +} while(0) + +/* delete "delptr" from the hash table. + * "the usual" patch-up process for the app-order doubly-linked-list. + * The use of _hd_hh_del below deserves special explanation. + * These used to be expressed using (delptr) but that led to a bug + * if someone used the same symbol for the head and deletee, like + * HASH_DELETE(hh,users,users); + * We want that to work, but by changing the head (users) below + * we were forfeiting our ability to further refer to the deletee (users) + * in the patch-up process. Solution: use scratch space to + * copy the deletee pointer, then the latter references are via that + * scratch pointer rather than through the repointed (users) symbol. + */ +#define HASH_DELETE(hh,head,delptr) \ +do { \ + unsigned _hd_bkt; \ + struct UT_hash_handle *_hd_hh_del; \ + if ( ((delptr)->hh.prev == NULL) && ((delptr)->hh.next == NULL) ) { \ + uthash_free((head)->hh.tbl->buckets, \ + (head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket) ); \ + HASH_BLOOM_FREE((head)->hh.tbl); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + head = NULL; \ + } else { \ + _hd_hh_del = &((delptr)->hh); \ + if ((delptr) == ELMT_FROM_HH((head)->hh.tbl,(head)->hh.tbl->tail)) { \ + (head)->hh.tbl->tail = \ + (UT_hash_handle*)((char*)((delptr)->hh.prev) + \ + (head)->hh.tbl->hho); \ + } \ + if ((delptr)->hh.prev) { \ + ((UT_hash_handle*)((char*)((delptr)->hh.prev) + \ + (head)->hh.tbl->hho))->next = (delptr)->hh.next; \ + } else { \ + DECLTYPE_ASSIGN(head,(delptr)->hh.next); \ + } \ + if (_hd_hh_del->next) { \ + ((UT_hash_handle*)((char*)_hd_hh_del->next + \ + (head)->hh.tbl->hho))->prev = \ + _hd_hh_del->prev; \ + } \ + HASH_TO_BKT( _hd_hh_del->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ + HASH_DEL_IN_BKT(hh,(head)->hh.tbl->buckets[_hd_bkt], _hd_hh_del); \ + (head)->hh.tbl->num_items--; \ + } \ + HASH_FSCK(hh,head); \ +} while (0) + + +/* convenience forms of HASH_FIND/HASH_ADD/HASH_DEL */ +#define HASH_FIND_STR(head,findstr,out) \ + HASH_FIND(hh,head,findstr,strlen(findstr),out) +#define HASH_ADD_STR(head,strfield,add) \ + HASH_ADD(hh,head,strfield,strlen(add->strfield),add) +#define HASH_FIND_INT(head,findint,out) \ + HASH_FIND(hh,head,findint,sizeof(int),out) +#define HASH_ADD_INT(head,intfield,add) \ + HASH_ADD(hh,head,intfield,sizeof(int),add) +#define HASH_FIND_PTR(head,findptr,out) \ + HASH_FIND(hh,head,findptr,sizeof(void *),out) +#define HASH_ADD_PTR(head,ptrfield,add) \ + HASH_ADD(hh,head,ptrfield,sizeof(void *),add) +#define HASH_DEL(head,delptr) \ + HASH_DELETE(hh,head,delptr) + +/* HASH_FSCK checks hash integrity on every add/delete when HASH_DEBUG is defined. + * This is for uthash developer only; it compiles away if HASH_DEBUG isn't defined. + */ +#ifdef HASH_DEBUG +#define HASH_OOPS(...) do { fprintf(stderr,__VA_ARGS__); exit(-1); } while (0) +#define HASH_FSCK(hh,head) \ +do { \ + unsigned _bkt_i; \ + unsigned _count, _bkt_count; \ + char *_prev; \ + struct UT_hash_handle *_thh; \ + if (head) { \ + _count = 0; \ + for( _bkt_i = 0; _bkt_i < (head)->hh.tbl->num_buckets; _bkt_i++) { \ + _bkt_count = 0; \ + _thh = (head)->hh.tbl->buckets[_bkt_i].hh_head; \ + _prev = NULL; \ + while (_thh) { \ + if (_prev != (char*)(_thh->hh_prev)) { \ + HASH_OOPS("invalid hh_prev %p, actual %p\n", \ + _thh->hh_prev, _prev ); \ + } \ + _bkt_count++; \ + _prev = (char*)(_thh); \ + _thh = _thh->hh_next; \ + } \ + _count += _bkt_count; \ + if ((head)->hh.tbl->buckets[_bkt_i].count != _bkt_count) { \ + HASH_OOPS("invalid bucket count %d, actual %d\n", \ + (head)->hh.tbl->buckets[_bkt_i].count, _bkt_count); \ + } \ + } \ + if (_count != (head)->hh.tbl->num_items) { \ + HASH_OOPS("invalid hh item count %d, actual %d\n", \ + (head)->hh.tbl->num_items, _count ); \ + } \ + /* traverse hh in app order; check next/prev integrity, count */ \ + _count = 0; \ + _prev = NULL; \ + _thh = &(head)->hh; \ + while (_thh) { \ + _count++; \ + if (_prev !=(char*)(_thh->prev)) { \ + HASH_OOPS("invalid prev %p, actual %p\n", \ + _thh->prev, _prev ); \ + } \ + _prev = (char*)ELMT_FROM_HH((head)->hh.tbl, _thh); \ + _thh = ( _thh->next ? (UT_hash_handle*)((char*)(_thh->next) + \ + (head)->hh.tbl->hho) : NULL ); \ + } \ + if (_count != (head)->hh.tbl->num_items) { \ + HASH_OOPS("invalid app item count %d, actual %d\n", \ + (head)->hh.tbl->num_items, _count ); \ + } \ + } \ +} while (0) +#else +#define HASH_FSCK(hh,head) +#endif + +/* When compiled with -DHASH_EMIT_KEYS, length-prefixed keys are emitted to + * the descriptor to which this macro is defined for tuning the hash function. + * The app can #include to get the prototype for write(2). */ +#ifdef HASH_EMIT_KEYS +#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) \ +do { \ + unsigned _klen = fieldlen; \ + write(HASH_EMIT_KEYS, &_klen, sizeof(_klen)); \ + write(HASH_EMIT_KEYS, keyptr, fieldlen); \ +} while (0) +#else +#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) +#endif + +/* default to Jenkin's hash unless overridden e.g. DHASH_FUNCTION=HASH_SAX */ +#ifdef HASH_FUNCTION +#define HASH_FCN HASH_FUNCTION +#else +#define HASH_FCN HASH_JEN +#endif + +/* The Bernstein hash function, used in Perl prior to v5.6 */ +#define HASH_BER(key,keylen,num_bkts,hashv,bkt) \ +do { \ + unsigned _hb_keylen=keylen; \ + char *_hb_key=(char*)(key); \ + (hashv) = 0; \ + while (_hb_keylen--) { (hashv) = ((hashv) * 33) + *_hb_key++; } \ + bkt = (hashv) & (num_bkts-1); \ +} while (0) + + +/* SAX/FNV/OAT/JEN hash functions are macro variants of those listed at + * http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx */ +#define HASH_SAX(key,keylen,num_bkts,hashv,bkt) \ +do { \ + unsigned _sx_i; \ + char *_hs_key=(char*)(key); \ + hashv = 0; \ + for(_sx_i=0; _sx_i < keylen; _sx_i++) \ + hashv ^= (hashv << 5) + (hashv >> 2) + _hs_key[_sx_i]; \ + bkt = hashv & (num_bkts-1); \ +} while (0) + +#define HASH_FNV(key,keylen,num_bkts,hashv,bkt) \ +do { \ + unsigned _fn_i; \ + char *_hf_key=(char*)(key); \ + hashv = 2166136261UL; \ + for(_fn_i=0; _fn_i < keylen; _fn_i++) \ + hashv = (hashv * 16777619) ^ _hf_key[_fn_i]; \ + bkt = hashv & (num_bkts-1); \ +} while(0) + +#define HASH_OAT(key,keylen,num_bkts,hashv,bkt) \ +do { \ + unsigned _ho_i; \ + char *_ho_key=(char*)(key); \ + hashv = 0; \ + for(_ho_i=0; _ho_i < keylen; _ho_i++) { \ + hashv += _ho_key[_ho_i]; \ + hashv += (hashv << 10); \ + hashv ^= (hashv >> 6); \ + } \ + hashv += (hashv << 3); \ + hashv ^= (hashv >> 11); \ + hashv += (hashv << 15); \ + bkt = hashv & (num_bkts-1); \ +} while(0) + +#define HASH_JEN_MIX(a,b,c) \ +do { \ + a -= b; a -= c; a ^= ( c >> 13 ); \ + b -= c; b -= a; b ^= ( a << 8 ); \ + c -= a; c -= b; c ^= ( b >> 13 ); \ + a -= b; a -= c; a ^= ( c >> 12 ); \ + b -= c; b -= a; b ^= ( a << 16 ); \ + c -= a; c -= b; c ^= ( b >> 5 ); \ + a -= b; a -= c; a ^= ( c >> 3 ); \ + b -= c; b -= a; b ^= ( a << 10 ); \ + c -= a; c -= b; c ^= ( b >> 15 ); \ +} while (0) + +#define HASH_JEN(key,keylen,num_bkts,hashv,bkt) \ +do { \ + unsigned _hj_i,_hj_j,_hj_k; \ + char *_hj_key=(char*)(key); \ + hashv = 0xfeedbeef; \ + _hj_i = _hj_j = 0x9e3779b9; \ + _hj_k = (unsigned)keylen; \ + while (_hj_k >= 12) { \ + _hj_i += (_hj_key[0] + ( (unsigned)_hj_key[1] << 8 ) \ + + ( (unsigned)_hj_key[2] << 16 ) \ + + ( (unsigned)_hj_key[3] << 24 ) ); \ + _hj_j += (_hj_key[4] + ( (unsigned)_hj_key[5] << 8 ) \ + + ( (unsigned)_hj_key[6] << 16 ) \ + + ( (unsigned)_hj_key[7] << 24 ) ); \ + hashv += (_hj_key[8] + ( (unsigned)_hj_key[9] << 8 ) \ + + ( (unsigned)_hj_key[10] << 16 ) \ + + ( (unsigned)_hj_key[11] << 24 ) ); \ + \ + HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ + \ + _hj_key += 12; \ + _hj_k -= 12; \ + } \ + hashv += keylen; \ + switch ( _hj_k ) { \ + case 11: hashv += ( (unsigned)_hj_key[10] << 24 ); \ + case 10: hashv += ( (unsigned)_hj_key[9] << 16 ); \ + case 9: hashv += ( (unsigned)_hj_key[8] << 8 ); \ + case 8: _hj_j += ( (unsigned)_hj_key[7] << 24 ); \ + case 7: _hj_j += ( (unsigned)_hj_key[6] << 16 ); \ + case 6: _hj_j += ( (unsigned)_hj_key[5] << 8 ); \ + case 5: _hj_j += _hj_key[4]; \ + case 4: _hj_i += ( (unsigned)_hj_key[3] << 24 ); \ + case 3: _hj_i += ( (unsigned)_hj_key[2] << 16 ); \ + case 2: _hj_i += ( (unsigned)_hj_key[1] << 8 ); \ + case 1: _hj_i += _hj_key[0]; \ + } \ + HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ + bkt = hashv & (num_bkts-1); \ +} while(0) + +/* The Paul Hsieh hash function */ +#undef get16bits +#if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__) \ + || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__) +#define get16bits(d) (*((const uint16_t *) (d))) +#endif + +#if !defined (get16bits) +#define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8) \ + +(uint32_t)(((const uint8_t *)(d))[0]) ) +#endif +#define HASH_SFH(key,keylen,num_bkts,hashv,bkt) \ +do { \ + char *_sfh_key=(char*)(key); \ + uint32_t _sfh_tmp, _sfh_len = keylen; \ + \ + int _sfh_rem = _sfh_len & 3; \ + _sfh_len >>= 2; \ + hashv = 0xcafebabe; \ + \ + /* Main loop */ \ + for (;_sfh_len > 0; _sfh_len--) { \ + hashv += get16bits (_sfh_key); \ + _sfh_tmp = (get16bits (_sfh_key+2) << 11) ^ hashv; \ + hashv = (hashv << 16) ^ _sfh_tmp; \ + _sfh_key += 2*sizeof (uint16_t); \ + hashv += hashv >> 11; \ + } \ + \ + /* Handle end cases */ \ + switch (_sfh_rem) { \ + case 3: hashv += get16bits (_sfh_key); \ + hashv ^= hashv << 16; \ + hashv ^= _sfh_key[sizeof (uint16_t)] << 18; \ + hashv += hashv >> 11; \ + break; \ + case 2: hashv += get16bits (_sfh_key); \ + hashv ^= hashv << 11; \ + hashv += hashv >> 17; \ + break; \ + case 1: hashv += *_sfh_key; \ + hashv ^= hashv << 10; \ + hashv += hashv >> 1; \ + } \ + \ + /* Force "avalanching" of final 127 bits */ \ + hashv ^= hashv << 3; \ + hashv += hashv >> 5; \ + hashv ^= hashv << 4; \ + hashv += hashv >> 17; \ + hashv ^= hashv << 25; \ + hashv += hashv >> 6; \ + bkt = hashv & (num_bkts-1); \ +} while(0) + +#ifdef HASH_USING_NO_STRICT_ALIASING +/* The MurmurHash exploits some CPU's (x86,x86_64) tolerance for unaligned reads. + * For other types of CPU's (e.g. Sparc) an unaligned read causes a bus error. + * MurmurHash uses the faster approach only on CPU's where we know it's safe. + * + * Note the preprocessor built-in defines can be emitted using: + * + * gcc -m64 -dM -E - < /dev/null (on gcc) + * cc -## a.c (where a.c is a simple test file) (Sun Studio) + */ +#if (defined(__i386__) || defined(__x86_64__)) +#define MUR_GETBLOCK(p,i) p[i] +#else /* non intel */ +#define MUR_PLUS0_ALIGNED(p) (((unsigned long)p & 0x3) == 0) +#define MUR_PLUS1_ALIGNED(p) (((unsigned long)p & 0x3) == 1) +#define MUR_PLUS2_ALIGNED(p) (((unsigned long)p & 0x3) == 2) +#define MUR_PLUS3_ALIGNED(p) (((unsigned long)p & 0x3) == 3) +#define WP(p) ((uint32_t*)((unsigned long)(p) & ~3UL)) +#if (defined(__BIG_ENDIAN__) || defined(SPARC) || defined(__ppc__) || defined(__ppc64__)) +#define MUR_THREE_ONE(p) ((((*WP(p))&0x00ffffff) << 8) | (((*(WP(p)+1))&0xff000000) >> 24)) +#define MUR_TWO_TWO(p) ((((*WP(p))&0x0000ffff) <<16) | (((*(WP(p)+1))&0xffff0000) >> 16)) +#define MUR_ONE_THREE(p) ((((*WP(p))&0x000000ff) <<24) | (((*(WP(p)+1))&0xffffff00) >> 8)) +#else /* assume little endian non-intel */ +#define MUR_THREE_ONE(p) ((((*WP(p))&0xffffff00) >> 8) | (((*(WP(p)+1))&0x000000ff) << 24)) +#define MUR_TWO_TWO(p) ((((*WP(p))&0xffff0000) >>16) | (((*(WP(p)+1))&0x0000ffff) << 16)) +#define MUR_ONE_THREE(p) ((((*WP(p))&0xff000000) >>24) | (((*(WP(p)+1))&0x00ffffff) << 8)) +#endif +#define MUR_GETBLOCK(p,i) (MUR_PLUS0_ALIGNED(p) ? ((p)[i]) : \ + (MUR_PLUS1_ALIGNED(p) ? MUR_THREE_ONE(p) : \ + (MUR_PLUS2_ALIGNED(p) ? MUR_TWO_TWO(p) : \ + MUR_ONE_THREE(p)))) +#endif +#define MUR_ROTL32(x,r) (((x) << (r)) | ((x) >> (32 - (r)))) +#define MUR_FMIX(_h) \ +do { \ + _h ^= _h >> 16; \ + _h *= 0x85ebca6b; \ + _h ^= _h >> 13; \ + _h *= 0xc2b2ae35l; \ + _h ^= _h >> 16; \ +} while(0) + +#define HASH_MUR(key,keylen,num_bkts,hashv,bkt) \ +do { \ + const uint8_t *_mur_data = (const uint8_t*)(key); \ + const int _mur_nblocks = (keylen) / 4; \ + uint32_t _mur_h1 = 0xf88D5353; \ + uint32_t _mur_c1 = 0xcc9e2d51; \ + uint32_t _mur_c2 = 0x1b873593; \ + const uint32_t *_mur_blocks = (const uint32_t*)(_mur_data+_mur_nblocks*4); \ + int _mur_i; \ + for(_mur_i = -_mur_nblocks; _mur_i; _mur_i++) { \ + uint32_t _mur_k1 = MUR_GETBLOCK(_mur_blocks,_mur_i); \ + _mur_k1 *= _mur_c1; \ + _mur_k1 = MUR_ROTL32(_mur_k1,15); \ + _mur_k1 *= _mur_c2; \ + \ + _mur_h1 ^= _mur_k1; \ + _mur_h1 = MUR_ROTL32(_mur_h1,13); \ + _mur_h1 = _mur_h1*5+0xe6546b64; \ + } \ + const uint8_t *_mur_tail = (const uint8_t*)(_mur_data + _mur_nblocks*4); \ + uint32_t _mur_k1=0; \ + switch((keylen) & 3) { \ + case 3: _mur_k1 ^= _mur_tail[2] << 16; \ + case 2: _mur_k1 ^= _mur_tail[1] << 8; \ + case 1: _mur_k1 ^= _mur_tail[0]; \ + _mur_k1 *= _mur_c1; \ + _mur_k1 = MUR_ROTL32(_mur_k1,15); \ + _mur_k1 *= _mur_c2; \ + _mur_h1 ^= _mur_k1; \ + } \ + _mur_h1 ^= (keylen); \ + MUR_FMIX(_mur_h1); \ + hashv = _mur_h1; \ + bkt = hashv & (num_bkts-1); \ +} while(0) +#endif /* HASH_USING_NO_STRICT_ALIASING */ + +/* key comparison function; return 0 if keys equal */ +#define HASH_KEYCMP(a,b,len) memcmp(a,b,len) + +/* iterate over items in a known bucket to find desired item */ +#define HASH_FIND_IN_BKT(tbl,hh,head,keyptr,keylen_in,out) \ +do { \ + if (head.hh_head) DECLTYPE_ASSIGN(out,ELMT_FROM_HH(tbl,head.hh_head)); \ + else out=NULL; \ + while (out) { \ + if ((out)->hh.keylen == keylen_in) { \ + if ((HASH_KEYCMP((out)->hh.key,keyptr,keylen_in)) == 0) break; \ + } \ + if ((out)->hh.hh_next) DECLTYPE_ASSIGN(out,ELMT_FROM_HH(tbl,(out)->hh.hh_next)); \ + else out = NULL; \ + } \ +} while(0) + +/* add an item to a bucket */ +#define HASH_ADD_TO_BKT(head,addhh) \ +do { \ + head.count++; \ + (addhh)->hh_next = head.hh_head; \ + (addhh)->hh_prev = NULL; \ + if (head.hh_head) { (head).hh_head->hh_prev = (addhh); } \ + (head).hh_head=addhh; \ + if (head.count >= ((head.expand_mult+1) * HASH_BKT_CAPACITY_THRESH) \ + && (addhh)->tbl->noexpand != 1) { \ + HASH_EXPAND_BUCKETS((addhh)->tbl); \ + } \ +} while(0) + +/* remove an item from a given bucket */ +#define HASH_DEL_IN_BKT(hh,head,hh_del) \ + (head).count--; \ + if ((head).hh_head == hh_del) { \ + (head).hh_head = hh_del->hh_next; \ + } \ + if (hh_del->hh_prev) { \ + hh_del->hh_prev->hh_next = hh_del->hh_next; \ + } \ + if (hh_del->hh_next) { \ + hh_del->hh_next->hh_prev = hh_del->hh_prev; \ + } + +/* Bucket expansion has the effect of doubling the number of buckets + * and redistributing the items into the new buckets. Ideally the + * items will distribute more or less evenly into the new buckets + * (the extent to which this is true is a measure of the quality of + * the hash function as it applies to the key domain). + * + * With the items distributed into more buckets, the chain length + * (item count) in each bucket is reduced. Thus by expanding buckets + * the hash keeps a bound on the chain length. This bounded chain + * length is the essence of how a hash provides constant time lookup. + * + * The calculation of tbl->ideal_chain_maxlen below deserves some + * explanation. First, keep in mind that we're calculating the ideal + * maximum chain length based on the *new* (doubled) bucket count. + * In fractions this is just n/b (n=number of items,b=new num buckets). + * Since the ideal chain length is an integer, we want to calculate + * ceil(n/b). We don't depend on floating point arithmetic in this + * hash, so to calculate ceil(n/b) with integers we could write + * + * ceil(n/b) = (n/b) + ((n%b)?1:0) + * + * and in fact a previous version of this hash did just that. + * But now we have improved things a bit by recognizing that b is + * always a power of two. We keep its base 2 log handy (call it lb), + * so now we can write this with a bit shift and logical AND: + * + * ceil(n/b) = (n>>lb) + ( (n & (b-1)) ? 1:0) + * + */ +#define HASH_EXPAND_BUCKETS(tbl) \ +do { \ + unsigned _he_bkt; \ + unsigned _he_bkt_i; \ + struct UT_hash_handle *_he_thh, *_he_hh_nxt; \ + UT_hash_bucket *_he_new_buckets, *_he_newbkt; \ + _he_new_buckets = (UT_hash_bucket*)uthash_malloc( \ + 2 * tbl->num_buckets * sizeof(struct UT_hash_bucket)); \ + if (!_he_new_buckets) { uthash_fatal( "out of memory"); } \ + memset(_he_new_buckets, 0, \ + 2 * tbl->num_buckets * sizeof(struct UT_hash_bucket)); \ + tbl->ideal_chain_maxlen = \ + (tbl->num_items >> (tbl->log2_num_buckets+1)) + \ + ((tbl->num_items & ((tbl->num_buckets*2)-1)) ? 1 : 0); \ + tbl->nonideal_items = 0; \ + for(_he_bkt_i = 0; _he_bkt_i < tbl->num_buckets; _he_bkt_i++) \ + { \ + _he_thh = tbl->buckets[ _he_bkt_i ].hh_head; \ + while (_he_thh) { \ + _he_hh_nxt = _he_thh->hh_next; \ + HASH_TO_BKT( _he_thh->hashv, tbl->num_buckets*2, _he_bkt); \ + _he_newbkt = &(_he_new_buckets[ _he_bkt ]); \ + if (++(_he_newbkt->count) > tbl->ideal_chain_maxlen) { \ + tbl->nonideal_items++; \ + _he_newbkt->expand_mult = _he_newbkt->count / \ + tbl->ideal_chain_maxlen; \ + } \ + _he_thh->hh_prev = NULL; \ + _he_thh->hh_next = _he_newbkt->hh_head; \ + if (_he_newbkt->hh_head) _he_newbkt->hh_head->hh_prev = \ + _he_thh; \ + _he_newbkt->hh_head = _he_thh; \ + _he_thh = _he_hh_nxt; \ + } \ + } \ + uthash_free( tbl->buckets, tbl->num_buckets*sizeof(struct UT_hash_bucket) ); \ + tbl->num_buckets *= 2; \ + tbl->log2_num_buckets++; \ + tbl->buckets = _he_new_buckets; \ + tbl->ineff_expands = (tbl->nonideal_items > (tbl->num_items >> 1)) ? \ + (tbl->ineff_expands+1) : 0; \ + if (tbl->ineff_expands > 1) { \ + tbl->noexpand=1; \ + uthash_noexpand_fyi(tbl); \ + } \ + uthash_expand_fyi(tbl); \ +} while(0) + + +/* This is an adaptation of Simon Tatham's O(n log(n)) mergesort */ +/* Note that HASH_SORT assumes the hash handle name to be hh. + * HASH_SRT was added to allow the hash handle name to be passed in. */ +#define HASH_SORT(head,cmpfcn) HASH_SRT(hh,head,cmpfcn) +#define HASH_SRT(hh,head,cmpfcn) \ +do { \ + unsigned _hs_i; \ + unsigned _hs_looping,_hs_nmerges,_hs_insize,_hs_psize,_hs_qsize; \ + struct UT_hash_handle *_hs_p, *_hs_q, *_hs_e, *_hs_list, *_hs_tail; \ + if (head) { \ + _hs_insize = 1; \ + _hs_looping = 1; \ + _hs_list = &((head)->hh); \ + while (_hs_looping) { \ + _hs_p = _hs_list; \ + _hs_list = NULL; \ + _hs_tail = NULL; \ + _hs_nmerges = 0; \ + while (_hs_p) { \ + _hs_nmerges++; \ + _hs_q = _hs_p; \ + _hs_psize = 0; \ + for ( _hs_i = 0; _hs_i < _hs_insize; _hs_i++ ) { \ + _hs_psize++; \ + _hs_q = (UT_hash_handle*)((_hs_q->next) ? \ + ((void*)((char*)(_hs_q->next) + \ + (head)->hh.tbl->hho)) : NULL); \ + if (! (_hs_q) ) break; \ + } \ + _hs_qsize = _hs_insize; \ + while ((_hs_psize > 0) || ((_hs_qsize > 0) && _hs_q )) { \ + if (_hs_psize == 0) { \ + _hs_e = _hs_q; \ + _hs_q = (UT_hash_handle*)((_hs_q->next) ? \ + ((void*)((char*)(_hs_q->next) + \ + (head)->hh.tbl->hho)) : NULL); \ + _hs_qsize--; \ + } else if ( (_hs_qsize == 0) || !(_hs_q) ) { \ + _hs_e = _hs_p; \ + _hs_p = (UT_hash_handle*)((_hs_p->next) ? \ + ((void*)((char*)(_hs_p->next) + \ + (head)->hh.tbl->hho)) : NULL); \ + _hs_psize--; \ + } else if (( \ + cmpfcn(DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl,_hs_p)), \ + DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl,_hs_q))) \ + ) <= 0) { \ + _hs_e = _hs_p; \ + _hs_p = (UT_hash_handle*)((_hs_p->next) ? \ + ((void*)((char*)(_hs_p->next) + \ + (head)->hh.tbl->hho)) : NULL); \ + _hs_psize--; \ + } else { \ + _hs_e = _hs_q; \ + _hs_q = (UT_hash_handle*)((_hs_q->next) ? \ + ((void*)((char*)(_hs_q->next) + \ + (head)->hh.tbl->hho)) : NULL); \ + _hs_qsize--; \ + } \ + if ( _hs_tail ) { \ + _hs_tail->next = ((_hs_e) ? \ + ELMT_FROM_HH((head)->hh.tbl,_hs_e) : NULL); \ + } else { \ + _hs_list = _hs_e; \ + } \ + _hs_e->prev = ((_hs_tail) ? \ + ELMT_FROM_HH((head)->hh.tbl,_hs_tail) : NULL); \ + _hs_tail = _hs_e; \ + } \ + _hs_p = _hs_q; \ + } \ + _hs_tail->next = NULL; \ + if ( _hs_nmerges <= 1 ) { \ + _hs_looping=0; \ + (head)->hh.tbl->tail = _hs_tail; \ + DECLTYPE_ASSIGN(head,ELMT_FROM_HH((head)->hh.tbl, _hs_list)); \ + } \ + _hs_insize *= 2; \ + } \ + HASH_FSCK(hh,head); \ + } \ +} while (0) + +/* This function selects items from one hash into another hash. + * The end result is that the selected items have dual presence + * in both hashes. There is no copy of the items made; rather + * they are added into the new hash through a secondary hash + * hash handle that must be present in the structure. */ +#define HASH_SELECT(hh_dst, dst, hh_src, src, cond) \ +do { \ + unsigned _src_bkt, _dst_bkt; \ + void *_last_elt=NULL, *_elt; \ + UT_hash_handle *_src_hh, *_dst_hh, *_last_elt_hh=NULL; \ + ptrdiff_t _dst_hho = ((char*)(&(dst)->hh_dst) - (char*)(dst)); \ + if (src) { \ + for(_src_bkt=0; _src_bkt < (src)->hh_src.tbl->num_buckets; _src_bkt++) { \ + for(_src_hh = (src)->hh_src.tbl->buckets[_src_bkt].hh_head; \ + _src_hh; \ + _src_hh = _src_hh->hh_next) { \ + _elt = ELMT_FROM_HH((src)->hh_src.tbl, _src_hh); \ + if (cond(_elt)) { \ + _dst_hh = (UT_hash_handle*)(((char*)_elt) + _dst_hho); \ + _dst_hh->key = _src_hh->key; \ + _dst_hh->keylen = _src_hh->keylen; \ + _dst_hh->hashv = _src_hh->hashv; \ + _dst_hh->prev = _last_elt; \ + _dst_hh->next = NULL; \ + if (_last_elt_hh) { _last_elt_hh->next = _elt; } \ + if (!dst) { \ + DECLTYPE_ASSIGN(dst,_elt); \ + HASH_MAKE_TABLE(hh_dst,dst); \ + } else { \ + _dst_hh->tbl = (dst)->hh_dst.tbl; \ + } \ + HASH_TO_BKT(_dst_hh->hashv, _dst_hh->tbl->num_buckets, _dst_bkt); \ + HASH_ADD_TO_BKT(_dst_hh->tbl->buckets[_dst_bkt],_dst_hh); \ + (dst)->hh_dst.tbl->num_items++; \ + _last_elt = _elt; \ + _last_elt_hh = _dst_hh; \ + } \ + } \ + } \ + } \ + HASH_FSCK(hh_dst,dst); \ +} while (0) + +#define HASH_CLEAR(hh,head) \ +do { \ + if (head) { \ + uthash_free((head)->hh.tbl->buckets, \ + (head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket)); \ + HASH_BLOOM_FREE((head)->hh.tbl); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + (head)=NULL; \ + } \ +} while(0) + +#ifdef NO_DECLTYPE +#define HASH_ITER(hh,head,el,tmp) \ +for((el)=(head), (*(char**)(&(tmp)))=(char*)((head)?(head)->hh.next:NULL); \ + el; (el)=(tmp),(*(char**)(&(tmp)))=(char*)((tmp)?(tmp)->hh.next:NULL)) +#else +#define HASH_ITER(hh,head,el,tmp) \ +for((el)=(head),(tmp)=DECLTYPE(el)((head)?(head)->hh.next:NULL); \ + el; (el)=(tmp),(tmp)=DECLTYPE(el)((tmp)?(tmp)->hh.next:NULL)) +#endif + +/* obtain a count of items in the hash */ +#define HASH_COUNT(head) HASH_CNT(hh,head) +#define HASH_CNT(hh,head) ((head)?((head)->hh.tbl->num_items):0) + +typedef struct UT_hash_bucket { + struct UT_hash_handle *hh_head; + unsigned count; + + /* expand_mult is normally set to 0. In this situation, the max chain length + * threshold is enforced at its default value, HASH_BKT_CAPACITY_THRESH. (If + * the bucket's chain exceeds this length, bucket expansion is triggered). + * However, setting expand_mult to a non-zero value delays bucket expansion + * (that would be triggered by additions to this particular bucket) + * until its chain length reaches a *multiple* of HASH_BKT_CAPACITY_THRESH. + * (The multiplier is simply expand_mult+1). The whole idea of this + * multiplier is to reduce bucket expansions, since they are expensive, in + * situations where we know that a particular bucket tends to be overused. + * It is better to let its chain length grow to a longer yet-still-bounded + * value, than to do an O(n) bucket expansion too often. + */ + unsigned expand_mult; + +} UT_hash_bucket; + +/* random signature used only to find hash tables in external analysis */ +#define HASH_SIGNATURE 0xa0111fe1 +#define HASH_BLOOM_SIGNATURE 0xb12220f2 + +typedef struct UT_hash_table { + UT_hash_bucket *buckets; + unsigned num_buckets, log2_num_buckets; + unsigned num_items; + struct UT_hash_handle *tail; /* tail hh in app order, for fast append */ + ptrdiff_t hho; /* hash handle offset (byte pos of hash handle in element */ + + /* in an ideal situation (all buckets used equally), no bucket would have + * more than ceil(#items/#buckets) items. that's the ideal chain length. */ + unsigned ideal_chain_maxlen; + + /* nonideal_items is the number of items in the hash whose chain position + * exceeds the ideal chain maxlen. these items pay the penalty for an uneven + * hash distribution; reaching them in a chain traversal takes >ideal steps */ + unsigned nonideal_items; + + /* ineffective expands occur when a bucket doubling was performed, but + * afterward, more than half the items in the hash had nonideal chain + * positions. If this happens on two consecutive expansions we inhibit any + * further expansion, as it's not helping; this happens when the hash + * function isn't a good fit for the key domain. When expansion is inhibited + * the hash will still work, albeit no longer in constant time. */ + unsigned ineff_expands, noexpand; + + uint32_t signature; /* used only to find hash tables in external analysis */ +#ifdef HASH_BLOOM + uint32_t bloom_sig; /* used only to test bloom exists in external analysis */ + uint8_t *bloom_bv; + char bloom_nbits; +#endif + +} UT_hash_table; + +typedef struct UT_hash_handle { + struct UT_hash_table *tbl; + void *prev; /* prev element in app order */ + void *next; /* next element in app order */ + struct UT_hash_handle *hh_prev; /* previous hh in bucket order */ + struct UT_hash_handle *hh_next; /* next hh in bucket order */ + void *key; /* ptr to enclosing struct's key */ + unsigned keylen; /* enclosing struct's key len */ + unsigned hashv; /* result of hash-fcn(key) */ +} UT_hash_handle; + +#endif /* UTHASH_H */ diff --git a/rebar b/rebar new file mode 100755 index 0000000000000000000000000000000000000000..44053a5a5324960ff3cc25eccaf5b716d470dcf8 GIT binary patch literal 119018 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 z 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. +%% +%% Permission to use, copy, modify, and distribute this software for any +%% purpose with or without fee is hereby granted, provided that the above +%% copyright notice and this permission notice appear in all copies. +%% +%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +%%------------------------------------------------------------------- + +-module(emdb). + + +%%==================================================================== +%% EXPORTS +%%==================================================================== +-export([ + open/1 + ]). + + +%%==================================================================== +%% Types +%%==================================================================== +-record(emdb_oop, { + handle :: non_neg_integer() + }). + + + +%%==================================================================== +%% PUBLIC API +%%==================================================================== + +%%-------------------------------------------------------------------- +%% @doc Create a new MDB database +%% @end +%%-------------------------------------------------------------------- +-spec open(file:name()) -> #emdb_oop{}. +open(DirName) -> + %% ensure directory exists + ok = filelib:ensure_dir(DirName ++ "/"), + decorate(emdb_drv:open(DirName)). + + +%%==================================================================== +%% PRIVATE API +%%==================================================================== + +%% @private +decorate({ok, Handle}) -> + CDB = emdb_oop:new(Handle), + {ok, CDB}; + +decorate(Error) -> + Error. diff --git a/src/emdb_drv.erl b/src/emdb_drv.erl new file mode 100644 index 0000000..6315a5a --- /dev/null +++ b/src/emdb_drv.erl @@ -0,0 +1,137 @@ +%%------------------------------------------------------------------- +%% This file is part of EMDB - Erlang MDB API +%% +%% Copyright (c) 2012 by Aleph Archives. All rights reserved. +%% +%%------------------------------------------------------------------- +%% Redistribution and use in source and binary forms, with or without +%% modification, are permitted only as authorized by the OpenLDAP +%% Public License. +%% +%% A copy of this license is available in the file LICENSE in the +%% top-level directory of the distribution or, alternatively, at +%% . +%% +%% Permission to use, copy, modify, and distribute this software for any +%% purpose with or without fee is hereby granted, provided that the above +%% copyright notice and this permission notice appear in all copies. +%% +%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +%%------------------------------------------------------------------- + +-module(emdb_drv). + +%%==================================================================== +%% EXPORTS +%%==================================================================== +-export([ + open/1, + close/1, + + put/3, + get/2, + del/2, + + update/3, + + drop/1 + ]). + + +%% internal export (ex. spawn, apply) +-on_load(init/0). + +%%==================================================================== +%% MACROS +%%==================================================================== +-define(EMDB_DRIVER_NAME, "emdb_drv"). +-define(NOT_LOADED, not_loaded(?LINE)). + + +%%==================================================================== +%% PUBLIC API +%%==================================================================== + +%%-------------------------------------------------------------------- +%% @doc +%% @end +%%-------------------------------------------------------------------- +open(_DirName) -> + ?NOT_LOADED. + +%%-------------------------------------------------------------------- +%% @doc +%% @end +%%-------------------------------------------------------------------- +close(_Handle) -> + ?NOT_LOADED. + +%%-------------------------------------------------------------------- +%% @doc +%% @end +%%-------------------------------------------------------------------- +put(_Handle, _Key, _Val) -> + ?NOT_LOADED. + + +%%-------------------------------------------------------------------- +%% @doc +%% @end +%%-------------------------------------------------------------------- +get(_Handle, _Key) -> + ?NOT_LOADED. + +%%-------------------------------------------------------------------- +%% @doc +%% @end +%%-------------------------------------------------------------------- +del(_Handle, _Key) -> + ?NOT_LOADED. + + +%%-------------------------------------------------------------------- +%% @doc +%% @end +%%-------------------------------------------------------------------- +update(_Handle, _Key, _Val) -> + ?NOT_LOADED. + +%%-------------------------------------------------------------------- +%% @doc +%% @end +%%-------------------------------------------------------------------- +drop(_Handle) -> + ?NOT_LOADED. + +%%==================================================================== +%% PRIVATE API +%%==================================================================== + +%%-------------------------------------------------------------------- +%% @doc +%% @end +%%-------------------------------------------------------------------- +init() -> + PrivDir = case code:priv_dir(?MODULE) of + {error, _} -> + EbinDir = filename:dirname(code:which(?MODULE)), + AppPath = filename:dirname(EbinDir), + filename:join(AppPath, "priv"); + Path -> + Path + end, + erlang:load_nif(filename:join(PrivDir, ?EMDB_DRIVER_NAME), 0). + + +%%-------------------------------------------------------------------- +%% @doc +%% @end +%%-------------------------------------------------------------------- +not_loaded(Line) -> + erlang:nif_error({not_loaded, [{module, ?MODULE}, {line, Line}]}). diff --git a/src/emdb_oop.erl b/src/emdb_oop.erl new file mode 100644 index 0000000..800672b --- /dev/null +++ b/src/emdb_oop.erl @@ -0,0 +1,97 @@ +%%------------------------------------------------------------------- +%% This file is part of EMDB - Erlang MDB API +%% +%% Copyright (c) 2012 by Aleph Archives. All rights reserved. +%% +%%------------------------------------------------------------------- +%% Redistribution and use in source and binary forms, with or without +%% modification, are permitted only as authorized by the OpenLDAP +%% Public License. +%% +%% A copy of this license is available in the file LICENSE in the +%% top-level directory of the distribution or, alternatively, at +%% . +%% +%% Permission to use, copy, modify, and distribute this software for any +%% purpose with or without fee is hereby granted, provided that the above +%% copyright notice and this permission notice appear in all copies. +%% +%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +%%------------------------------------------------------------------- + +-module(emdb_oop, [Handle]). + + +%%==================================================================== +%% EXPORTS +%%==================================================================== +-export([ + close/0, + + put/2, + get/1, + del/1, + + update/2, + + drop/0 + ]). + + +%%==================================================================== +%% PUBLIC API +%%==================================================================== + +%%-------------------------------------------------------------------- +%% @doc +%% @end +%%-------------------------------------------------------------------- +close() -> + emdb_drv:close(Handle). + +%%-------------------------------------------------------------------- +%% @doc +%% @end +%%-------------------------------------------------------------------- +put(Key, Val) when is_binary(Key) andalso is_binary(Val) -> + emdb_drv:put(Handle, Key, Val). + + +%%-------------------------------------------------------------------- +%% @doc +%% @end +%%-------------------------------------------------------------------- +get(Key) when is_binary(Key) -> + emdb_drv:get(Handle, Key). + +%%-------------------------------------------------------------------- +%% @doc +%% @end +%%-------------------------------------------------------------------- +del(Key) when is_binary(Key) -> + emdb_drv:del(Handle, Key). + +%%-------------------------------------------------------------------- +%% @doc +%% @end +%%-------------------------------------------------------------------- +update(Key, Val) when is_binary(Key) andalso is_binary(Val) -> + emdb_drv:update(Handle, Key, Val). + + +%%-------------------------------------------------------------------- +%% @doc +%% @end +%%-------------------------------------------------------------------- +drop() -> + emdb_drv:drop(Handle). + +%%==================================================================== +%% INTERNAL FUNCTIONS +%%==================================================================== diff --git a/start.sh b/start.sh new file mode 100755 index 0000000..9e81aa4 --- /dev/null +++ b/start.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +cd `dirname $0` +exec erl -pa $PWD/ebin -pa +B -- $@