summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2018-07-18 06:10:25 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2018-07-18 06:10:37 +0000
commit35dfc39e34225f786c494e440814045c909aaed4 (patch)
tree291d5b7f765277c2413bf0f8c1a06e69bea00721
parentReleasing progress-linux version 2:1.3.3-1~dschinn1. (diff)
downloadldb-35dfc39e34225f786c494e440814045c909aaed4.zip
ldb-35dfc39e34225f786c494e440814045c909aaed4.tar.xz
Merging upstream version 2:1.4.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
-rw-r--r--ABI/ldb-1.4.0.sigs (renamed from ABI/ldb-1.3.3.sigs)0
-rw-r--r--ABI/pyldb-util-1.4.0.sigs (renamed from ABI/pyldb-util-1.3.3.sigs)0
-rw-r--r--ABI/pyldb-util.py3-1.4.0.sigs (renamed from ABI/pyldb-util.py3-1.3.3.sigs)0
-rw-r--r--buildtools/wafsamba/nothreads.py4
-rw-r--r--buildtools/wafsamba/samba_autoconf.py4
-rw-r--r--buildtools/wafsamba/samba_conftests.py2
-rw-r--r--buildtools/wafsamba/samba_dist.py2
-rw-r--r--buildtools/wafsamba/samba_version.py2
-rw-r--r--buildtools/wafsamba/wafsamba.py7
-rw-r--r--common/ldb.c3
-rw-r--r--common/ldb_dn.c3
-rw-r--r--common/ldb_ldif.c6
-rw-r--r--common/ldb_modules.c2
-rw-r--r--common/qsort.c10
-rw-r--r--ldb_ldb/ldb_ldb.c80
-rw-r--r--ldb_map/ldb_map.c6
-rw-r--r--ldb_map/ldb_map_inbound.c6
-rw-r--r--ldb_map/ldb_map_outbound.c3
-rw-r--r--ldb_mdb/ldb_mdb.c887
-rw-r--r--ldb_mdb/ldb_mdb.h60
-rw-r--r--ldb_mdb/ldb_mdb_init.c31
-rw-r--r--ldb_tdb/ldb_cache.c74
-rw-r--r--ldb_tdb/ldb_index.c576
-rw-r--r--ldb_tdb/ldb_search.c91
-rw-r--r--ldb_tdb/ldb_tdb.c556
-rw-r--r--ldb_tdb/ldb_tdb.h77
-rw-r--r--ldb_tdb/ldb_tdb_init.c59
-rw-r--r--ldb_tdb/ldb_tdb_wrap.c18
-rw-r--r--lib/replace/replace.h9
-rw-r--r--lib/replace/strptime.c6
-rw-r--r--lib/replace/system/wscript_configure8
-rw-r--r--lib/replace/wscript42
-rw-r--r--lib/talloc/ABI/pytalloc-util-2.1.12.sigs16
-rw-r--r--lib/talloc/ABI/pytalloc-util-2.1.13.sigs16
-rw-r--r--lib/talloc/ABI/pytalloc-util.py3-2.1.12.sigs15
-rw-r--r--lib/talloc/ABI/pytalloc-util.py3-2.1.13.sigs15
-rw-r--r--lib/talloc/ABI/talloc-2.1.12.sigs65
-rw-r--r--lib/talloc/ABI/talloc-2.1.13.sigs65
-rw-r--r--lib/talloc/pytalloc.c2
-rw-r--r--lib/talloc/talloc.c75
-rw-r--r--lib/talloc/talloc.h6
-rw-r--r--lib/talloc/wscript4
-rw-r--r--lib/tdb/common/check.c17
-rw-r--r--lib/tdb/common/hash.c50
-rw-r--r--lib/tdb/common/io.c28
-rw-r--r--lib/tdb/common/open.c15
-rw-r--r--lib/tdb/common/summary.c3
-rw-r--r--lib/tdb/common/transaction.c25
-rw-r--r--lib/tdb/common/traverse.c13
-rw-r--r--lib/tdb/python/tdbdump.py7
-rw-r--r--lib/tdb/tools/tdbdump.c4
-rw-r--r--lib/tdb/tools/tdbtool.c11
-rw-r--r--lib/tevent/pytevent.c2
-rw-r--r--lib/tevent/testsuite.c5
-rw-r--r--lib/tevent/tevent.h3
-rw-r--r--modules/paged_results.c43
-rw-r--r--pyldb.c16
-rw-r--r--pyldb_util.c2
-rw-r--r--tests/ldb_kv_ops_test.c1576
-rw-r--r--tests/ldb_lmdb_size_test.c210
-rw-r--r--tests/ldb_lmdb_test.c588
-rw-r--r--tests/ldb_mod_op_test.c981
-rw-r--r--tests/ldb_tdb_test.c387
-rwxr-xr-xtests/python/api.py384
-rwxr-xr-xtests/python/index.py320
-rw-r--r--tests/test_ldb_qsort.c64
-rw-r--r--third_party/cmocka/cmocka.c26
-rw-r--r--third_party/cmocka/cmocka.h2
-rw-r--r--third_party/popt/README10
-rw-r--r--third_party/popt/dummy.in0
-rw-r--r--third_party/popt/findme.c50
-rw-r--r--third_party/popt/lookup3.c969
-rw-r--r--third_party/popt/popt.c1428
-rw-r--r--third_party/popt/popt.h333
-rw-r--r--third_party/popt/poptconfig.c598
-rw-r--r--third_party/popt/popthelp.c706
-rw-r--r--third_party/popt/poptint.c199
-rw-r--r--third_party/popt/poptint.h130
-rw-r--r--third_party/popt/poptparse.c69
-rw-r--r--third_party/popt/system.h87
-rw-r--r--third_party/popt/wscript6
-rw-r--r--tools/ldbdump.c126
-rw-r--r--wscript205
83 files changed, 11072 insertions, 1539 deletions
diff --git a/ABI/ldb-1.3.3.sigs b/ABI/ldb-1.4.0.sigs
index a31b84e..a31b84e 100644
--- a/ABI/ldb-1.3.3.sigs
+++ b/ABI/ldb-1.4.0.sigs
diff --git a/ABI/pyldb-util-1.3.3.sigs b/ABI/pyldb-util-1.4.0.sigs
index 74d6719..74d6719 100644
--- a/ABI/pyldb-util-1.3.3.sigs
+++ b/ABI/pyldb-util-1.4.0.sigs
diff --git a/ABI/pyldb-util.py3-1.3.3.sigs b/ABI/pyldb-util.py3-1.4.0.sigs
index 74d6719..74d6719 100644
--- a/ABI/pyldb-util.py3-1.3.3.sigs
+++ b/ABI/pyldb-util.py3-1.4.0.sigs
diff --git a/buildtools/wafsamba/nothreads.py b/buildtools/wafsamba/nothreads.py
index d194eb8..9bd33e8 100644
--- a/buildtools/wafsamba/nothreads.py
+++ b/buildtools/wafsamba/nothreads.py
@@ -43,7 +43,7 @@ def process(tsk):
if tsk.__class__.stat: ret = tsk.__class__.stat(tsk)
# actual call to task's run() function
else: ret = tsk.call_run()
- except Exception, e:
+ except Exception as e:
tsk.err_msg = Utils.ex_stack()
tsk.hasrun = EXCEPTION
@@ -177,7 +177,7 @@ class Parallel(object):
try:
st = tsk.runnable_status()
- except Exception, e:
+ except Exception as e:
self.processed += 1
if self.stop and not Options.options.keep:
tsk.hasrun = SKIPPED
diff --git a/buildtools/wafsamba/samba_autoconf.py b/buildtools/wafsamba/samba_autoconf.py
index cc08e0d..bdd7c8b 100644
--- a/buildtools/wafsamba/samba_autoconf.py
+++ b/buildtools/wafsamba/samba_autoconf.py
@@ -711,6 +711,10 @@ def SAMBA_CONFIG_H(conf, path=None):
testflags=True)
conf.ADD_CFLAGS('-Werror=uninitialized -Wuninitialized',
testflags=True)
+ conf.ADD_CFLAGS('-Wimplicit-fallthrough',
+ testflags=True)
+ conf.ADD_CFLAGS('-Werror=strict-overflow -Wstrict-overflow=2',
+ testflags=True)
conf.ADD_CFLAGS('-Wformat=2 -Wno-format-y2k', testflags=True)
conf.ADD_CFLAGS('-Wno-format-zero-length', testflags=True)
diff --git a/buildtools/wafsamba/samba_conftests.py b/buildtools/wafsamba/samba_conftests.py
index 511176d..b52727b 100644
--- a/buildtools/wafsamba/samba_conftests.py
+++ b/buildtools/wafsamba/samba_conftests.py
@@ -50,7 +50,7 @@ def check(self, *k, **kw):
ret = None
try:
ret = self.run_c_code(*k, **kw)
- except Configure.ConfigurationError, e:
+ except Configure.ConfigurationError as e:
self.check_message_2(kw['errmsg'], 'YELLOW')
if 'mandatory' in kw and kw['mandatory']:
if Logs.verbose > 1:
diff --git a/buildtools/wafsamba/samba_dist.py b/buildtools/wafsamba/samba_dist.py
index 2e52820..8d51632 100644
--- a/buildtools/wafsamba/samba_dist.py
+++ b/buildtools/wafsamba/samba_dist.py
@@ -167,7 +167,7 @@ def dist(appname='', version=''):
absdir = os.path.join(srcdir, dir)
try:
files = vcs_dir_contents(absdir)
- except Exception, e:
+ except Exception as e:
Logs.error('unable to get contents of %s: %s' % (absdir, e))
sys.exit(1)
add_files_to_tarball(tar, srcdir, dir, dist_base, destdir, blacklist, files)
diff --git a/buildtools/wafsamba/samba_version.py b/buildtools/wafsamba/samba_version.py
index 950a855..be26439 100644
--- a/buildtools/wafsamba/samba_version.py
+++ b/buildtools/wafsamba/samba_version.py
@@ -95,7 +95,7 @@ also accepted as dictionary entries here
self.VENDOR_SUFFIX=None
self.VENDOR_PATCH=None
- for a, b in version_dict.iteritems():
+ for a, b in version_dict.items():
if a.startswith("SAMBA_VERSION_"):
setattr(self, a[14:], b)
else:
diff --git a/buildtools/wafsamba/wafsamba.py b/buildtools/wafsamba/wafsamba.py
index 4bb19d0..12d5421 100644
--- a/buildtools/wafsamba/wafsamba.py
+++ b/buildtools/wafsamba/wafsamba.py
@@ -353,6 +353,7 @@ def SAMBA_BINARY(bld, binname, source,
modules=None,
ldflags=None,
cflags='',
+ cflags_end=None,
autoproto=None,
use_hostcc=False,
use_global_deps=True,
@@ -410,6 +411,7 @@ def SAMBA_BINARY(bld, binname, source,
deps = deps,
includes = includes,
cflags = pie_cflags,
+ cflags_end = cflags_end,
group = subsystem_group,
autoproto = autoproto,
subsystem_name = subsystem_name,
@@ -458,6 +460,7 @@ def SAMBA_MODULE(bld, modname, source,
autoproto=None,
autoproto_extra_source='',
cflags='',
+ cflags_end=None,
internal_module=True,
local_include=True,
global_include=True,
@@ -488,6 +491,7 @@ def SAMBA_MODULE(bld, modname, source,
autoproto=autoproto,
autoproto_extra_source=autoproto_extra_source,
cflags=cflags,
+ cflags_end=cflags_end,
local_include=local_include,
global_include=global_include,
allow_warnings=allow_warnings,
@@ -527,6 +531,7 @@ def SAMBA_MODULE(bld, modname, source,
deps=deps,
includes=includes,
cflags=cflags,
+ cflags_end=cflags_end,
realname = realname,
autoproto = autoproto,
local_include=local_include,
@@ -900,7 +905,7 @@ def INSTALL_DIR(bld, path, chmod=0o755, env=None):
try:
os.makedirs(destpath)
os.chmod(destpath, chmod)
- except OSError, e:
+ except OSError as e:
if not os.path.isdir(destpath):
raise Utils.WafError("Cannot create the folder '%s' (error: %s)" % (path, e))
Build.BuildContext.INSTALL_DIR = INSTALL_DIR
diff --git a/common/ldb.c b/common/ldb.c
index a4d9977..5525e70 100644
--- a/common/ldb.c
+++ b/common/ldb.c
@@ -379,6 +379,7 @@ int ldb_transaction_start(struct ldb_context *ldb)
"ldb transaction start: %s (%d)",
ldb_strerror(status),
status);
+ ldb->transaction_active--;
}
if ((next_module && next_module->ldb->flags & LDB_FLG_ENABLE_TRACING)) {
ldb_debug(next_module->ldb, LDB_DEBUG_TRACE, "start ldb transaction error: %s",
@@ -424,6 +425,8 @@ int ldb_transaction_prepare_commit(struct ldb_context *ldb)
return LDB_SUCCESS;
}
+ ldb_reset_err_string(ldb);
+
status = next_module->ops->prepare_commit(next_module);
if (status != LDB_SUCCESS) {
ldb->transaction_active--;
diff --git a/common/ldb_dn.c b/common/ldb_dn.c
index b23ee17..dfeb600 100644
--- a/common/ldb_dn.c
+++ b/common/ldb_dn.c
@@ -629,7 +629,8 @@ static bool ldb_dn_explode(struct ldb_dn *dn)
l++;
break;
}
- /* fall through */
+
+ FALL_THROUGH;
case '\"':
case '<':
case '>':
diff --git a/common/ldb_ldif.c b/common/ldb_ldif.c
index b90d27e..e23b568 100644
--- a/common/ldb_ldif.c
+++ b/common/ldb_ldif.c
@@ -216,7 +216,8 @@ static int fold_string(int (*fprintf_fn)(void *, const char *, ...), void *priva
const char *buf, size_t length, int start_pos)
{
size_t i;
- int total=0, ret;
+ size_t total = 0;
+ int ret;
for (i=0;i<length;i++) {
ret = fprintf_fn(private_data, "%c", buf[i]);
@@ -280,7 +281,8 @@ static int ldb_ldif_write_trace(struct ldb_context *ldb,
{
TALLOC_CTX *mem_ctx;
unsigned int i, j;
- int total=0, ret;
+ size_t total = 0;
+ int ret;
char *p;
const struct ldb_message *msg;
const char * const * secret_attributes = ldb_get_opaque(ldb, LDB_SECRET_ATTRIBUTE_LIST_OPAQUE);
diff --git a/common/ldb_modules.c b/common/ldb_modules.c
index 25551e1..cc067ab 100644
--- a/common/ldb_modules.c
+++ b/common/ldb_modules.c
@@ -1103,7 +1103,7 @@ static int ldb_modules_load_dir(const char *modules_dir, const char *version)
*/
void ldb_set_modules_dir(struct ldb_context *ldb, const char *path)
{
- int ret = ldb_modules_load_path(path, LDB_VERSION);
+ int ret = ldb_modules_load_dir(path, LDB_VERSION);
if (ret != LDB_SUCCESS) {
ldb_asprintf_errstring(ldb, "Failed to load modules from: %s\n", path);
}
diff --git a/common/qsort.c b/common/qsort.c
index 1a0b886..012aaf3 100644
--- a/common/qsort.c
+++ b/common/qsort.c
@@ -59,9 +59,8 @@ typedef struct
#define CHAR_BIT 8
#endif
#define STACK_SIZE (CHAR_BIT * sizeof(size_t))
-#define PUSH(low, high) ((void) ((top->lo = (low)), (top->hi = (high)), ++top))
-#define POP(low, high) ((void) (--top, (low = top->lo), (high = top->hi)))
-#define STACK_NOT_EMPTY (stack < top)
+#define PUSH(low, high) ((void) ((stack[i].lo = (low)), (stack[i].hi = (high)), i++))
+#define POP(low, high) ((void) (i--, (low = stack[i].lo), (high = stack[i].hi)))
/* Order size using quicksort. This implementation incorporates
@@ -104,11 +103,11 @@ void ldb_qsort (void *const pbase, size_t total_elems, size_t size,
char *lo = base_ptr;
char *hi = &lo[size * (total_elems - 1)];
stack_node stack[STACK_SIZE];
- stack_node *top = stack;
+ size_t i = 0;
PUSH (NULL, NULL);
- while (STACK_NOT_EMPTY)
+ do
{
char *left_ptr;
char *right_ptr;
@@ -194,6 +193,7 @@ void ldb_qsort (void *const pbase, size_t total_elems, size_t size,
hi = right_ptr;
}
}
+ while (i > 0 && i < STACK_SIZE);
}
/* Once the BASE_PTR array is partially sorted by quicksort the rest
diff --git a/ldb_ldb/ldb_ldb.c b/ldb_ldb/ldb_ldb.c
new file mode 100644
index 0000000..a5a3612
--- /dev/null
+++ b/ldb_ldb/ldb_ldb.c
@@ -0,0 +1,80 @@
+/*
+ * ldb connection and module initialisation
+ *
+ * Copyright (C) Andrew Bartlett <abartlet@samba.org> 2018
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#include "ldb_private.h"
+#include "../ldb_tdb/ldb_tdb.h"
+#ifdef HAVE_LMDB
+#include "../ldb_mdb/ldb_mdb.h"
+#endif /* HAVE_LMDB */
+
+/*
+ connect to the database
+*/
+static int lldb_connect(struct ldb_context *ldb,
+ const char *url,
+ unsigned int flags,
+ const char *options[],
+ struct ldb_module **module)
+{
+ const char *path;
+ int ret;
+
+ /*
+ * Check and remove the url prefix
+ */
+ if (strchr(url, ':')) {
+ if (strncmp(url, "ldb://", 6) != 0) {
+ ldb_debug(ldb, LDB_DEBUG_ERROR,
+ "Invalid ldb URL '%s'", url);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ path = url+6;
+ } else {
+ path = url;
+ }
+
+ /*
+ * Don't create the database if it's not there
+ */
+ flags |= LDB_FLG_DONT_CREATE_DB;
+#ifdef HAVE_LMDB
+ /*
+ * Try opening the database as an lmdb
+ */
+ ret = lmdb_connect(ldb, path, flags, options, module);
+ if (ret == LDB_SUCCESS) {
+ return ret;
+ }
+ if (ret != LDB_ERR_UNAVAILABLE) {
+ return ret;
+ }
+
+ /*
+ * Not mdb so try as tdb
+ */
+#endif /* HAVE_LMDB */
+ ret = ltdb_connect(ldb, path, flags, options, module);
+ return ret;
+}
+
+int ldb_ldb_init(const char *version)
+{
+ LDB_MODULE_CHECK_VERSION(version);
+ return ldb_register_backend("ldb", lldb_connect, false);
+}
diff --git a/ldb_map/ldb_map.c b/ldb_map/ldb_map.c
index f2a86fe..b453dff 100644
--- a/ldb_map/ldb_map.c
+++ b/ldb_map/ldb_map.c
@@ -523,7 +523,8 @@ struct ldb_dn *ldb_dn_map_local(struct ldb_module *module, void *mem_ctx, struct
"used in DN!", ldb_dn_get_component_name(dn, i));
goto failed;
}
- /* fall through */
+
+ FALL_THROUGH;
case LDB_MAP_KEEP:
case LDB_MAP_RENAME:
case LDB_MAP_RENDROP:
@@ -599,7 +600,8 @@ struct ldb_dn *ldb_dn_map_remote(struct ldb_module *module, void *mem_ctx, struc
"used in DN!", ldb_dn_get_component_name(dn, i));
goto failed;
}
- /* fall through */
+
+ FALL_THROUGH;
case LDB_MAP_KEEP:
case LDB_MAP_RENAME:
case LDB_MAP_RENDROP:
diff --git a/ldb_map/ldb_map_inbound.c b/ldb_map/ldb_map_inbound.c
index 461e681..861c4c1 100644
--- a/ldb_map/ldb_map_inbound.c
+++ b/ldb_map/ldb_map_inbound.c
@@ -87,7 +87,8 @@ static int ldb_msg_el_partition(struct ldb_module *module, enum ldb_request_type
el = ldb_msg_el_map_local(module, remote, map, old);
break;
}
- /* fall through */
+
+ FALL_THROUGH;
case LDB_MAP_IGNORE:
goto local;
@@ -99,7 +100,8 @@ static int ldb_msg_el_partition(struct ldb_module *module, enum ldb_request_type
map->local_name);
goto local;
}
- /* fall through */
+
+ FALL_THROUGH;
case LDB_MAP_KEEP:
case LDB_MAP_RENAME:
el = ldb_msg_el_map_local(module, remote, map, old);
diff --git a/ldb_map/ldb_map_outbound.c b/ldb_map/ldb_map_outbound.c
index fd25c36..1f1a7e8 100644
--- a/ldb_map/ldb_map_outbound.c
+++ b/ldb_map/ldb_map_outbound.c
@@ -330,7 +330,8 @@ static int ldb_msg_el_merge(struct ldb_module *module, struct ldb_message *local
attr_name);
return LDB_SUCCESS;
}
- /* fall through */
+
+ FALL_THROUGH;
case LDB_MAP_KEEP:
case LDB_MAP_RENAME:
case LDB_MAP_RENDROP:
diff --git a/ldb_mdb/ldb_mdb.c b/ldb_mdb/ldb_mdb.c
new file mode 100644
index 0000000..af552fe
--- /dev/null
+++ b/ldb_mdb/ldb_mdb.c
@@ -0,0 +1,887 @@
+/*
+ ldb database library using mdb back end
+
+ Copyright (C) Jakub Hrozek 2014
+ Copyright (C) Catalyst.Net Ltd 2017
+
+ ** NOTE! The following LGPL license applies to the ldb
+ ** library. This does NOT imply that all of Samba is released
+ ** under the LGPL
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "ldb_mdb.h"
+#include "../ldb_tdb/ldb_tdb.h"
+#include "include/dlinklist.h"
+
+#define MDB_URL_PREFIX "mdb://"
+#define MDB_URL_PREFIX_SIZE (sizeof(MDB_URL_PREFIX)-1)
+
+#define LDB_MDB_MAX_KEY_LENGTH 511
+
+#define GIGABYTE (1024*1024*1024)
+
+int ldb_mdb_err_map(int lmdb_err)
+{
+ switch (lmdb_err) {
+ case MDB_SUCCESS:
+ return LDB_SUCCESS;
+ case EIO:
+ return LDB_ERR_OPERATIONS_ERROR;
+ case EBADE:
+ case MDB_INCOMPATIBLE:
+ case MDB_CORRUPTED:
+ case MDB_INVALID:
+ return LDB_ERR_UNAVAILABLE;
+ case MDB_BAD_TXN:
+ case MDB_BAD_VALSIZE:
+#ifdef MDB_BAD_DBI
+ case MDB_BAD_DBI:
+#endif
+ case MDB_PANIC:
+ case EINVAL:
+ return LDB_ERR_PROTOCOL_ERROR;
+ case MDB_MAP_FULL:
+ case MDB_DBS_FULL:
+ case MDB_READERS_FULL:
+ case MDB_TLS_FULL:
+ case MDB_TXN_FULL:
+ case EAGAIN:
+ return LDB_ERR_BUSY;
+ case MDB_KEYEXIST:
+ return LDB_ERR_ENTRY_ALREADY_EXISTS;
+ case MDB_NOTFOUND:
+ case ENOENT:
+ return LDB_ERR_NO_SUCH_OBJECT;
+ case EACCES:
+ return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS;
+ default:
+ break;
+ }
+ return LDB_ERR_OTHER;
+}
+
+#define ldb_mdb_error(ldb, ecode) lmdb_error_at(ldb, ecode, __FILE__, __LINE__)
+static int lmdb_error_at(struct ldb_context *ldb,
+ int ecode,
+ const char *file,
+ int line)
+{
+ int ldb_err = ldb_mdb_err_map(ecode);
+ char *reason = mdb_strerror(ecode);
+ ldb_asprintf_errstring(ldb,
+ "(%d) - %s at %s:%d",
+ ecode,
+ reason,
+ file,
+ line);
+ return ldb_err;
+}
+
+
+static bool lmdb_transaction_active(struct ltdb_private *ltdb)
+{
+ return ltdb->lmdb_private->txlist != NULL;
+}
+
+static MDB_txn *lmdb_trans_get_tx(struct lmdb_trans *ltx)
+{
+ if (ltx == NULL) {
+ return NULL;
+ }
+
+ return ltx->tx;
+}
+
+static void trans_push(struct lmdb_private *lmdb, struct lmdb_trans *ltx)
+{
+ if (lmdb->txlist) {
+ talloc_steal(lmdb->txlist, ltx);
+ }
+
+ DLIST_ADD(lmdb->txlist, ltx);
+}
+
+static void trans_finished(struct lmdb_private *lmdb, struct lmdb_trans *ltx)
+{
+ DLIST_REMOVE(lmdb->txlist, ltx);
+ talloc_free(ltx);
+}
+
+
+static struct lmdb_trans *lmdb_private_trans_head(struct lmdb_private *lmdb)
+{
+ struct lmdb_trans *ltx;
+
+ ltx = lmdb->txlist;
+ return ltx;
+}
+
+
+static MDB_txn *get_current_txn(struct lmdb_private *lmdb)
+{
+ MDB_txn *txn = NULL;
+
+ txn = lmdb_trans_get_tx(lmdb_private_trans_head(lmdb));
+ if (txn != NULL) {
+ return txn;
+ }
+ if (lmdb->read_txn != NULL) {
+ return lmdb->read_txn;
+ }
+ lmdb->error = MDB_BAD_TXN;
+ ldb_set_errstring(lmdb->ldb, __location__":No active transaction\n");
+ return NULL;
+}
+
+static int lmdb_store(struct ltdb_private *ltdb,
+ struct ldb_val key,
+ struct ldb_val data, int flags)
+{
+ struct lmdb_private *lmdb = ltdb->lmdb_private;
+ MDB_val mdb_key;
+ MDB_val mdb_data;
+ int mdb_flags;
+ MDB_txn *txn = NULL;
+ MDB_dbi dbi = 0;
+
+ if (ltdb->read_only) {
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ txn = lmdb_trans_get_tx(lmdb_private_trans_head(lmdb));
+ if (txn == NULL) {
+ ldb_debug(lmdb->ldb, LDB_DEBUG_FATAL, "No transaction");
+ lmdb->error = MDB_PANIC;
+ return ldb_mdb_error(lmdb->ldb, lmdb->error);
+ }
+
+ lmdb->error = mdb_dbi_open(txn, NULL, 0, &dbi);
+ if (lmdb->error != MDB_SUCCESS) {
+ return ldb_mdb_error(lmdb->ldb, lmdb->error);
+ }
+
+ mdb_key.mv_size = key.length;
+ mdb_key.mv_data = key.data;
+
+ mdb_data.mv_size = data.length;
+ mdb_data.mv_data = data.data;
+
+ if (flags == TDB_INSERT) {
+ mdb_flags = MDB_NOOVERWRITE;
+ } else if ((flags == TDB_MODIFY)) {
+ /*
+ * Modifying a record, ensure that it exists.
+ * This mimics the TDB semantics
+ */
+ MDB_val value;
+ lmdb->error = mdb_get(txn, dbi, &mdb_key, &value);
+ if (lmdb->error != MDB_SUCCESS) {
+ return ldb_mdb_error(lmdb->ldb, lmdb->error);
+ }
+ mdb_flags = 0;
+ } else {
+ mdb_flags = 0;
+ }
+
+ lmdb->error = mdb_put(txn, dbi, &mdb_key, &mdb_data, mdb_flags);
+ if (lmdb->error != MDB_SUCCESS) {
+ return ldb_mdb_error(lmdb->ldb, lmdb->error);
+ }
+
+ return ldb_mdb_err_map(lmdb->error);
+}
+
+static int lmdb_delete(struct ltdb_private *ltdb, struct ldb_val key)
+{
+ struct lmdb_private *lmdb = ltdb->lmdb_private;
+ MDB_val mdb_key;
+ MDB_txn *txn = NULL;
+ MDB_dbi dbi = 0;
+
+ if (ltdb->read_only) {
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ txn = lmdb_trans_get_tx(lmdb_private_trans_head(lmdb));
+ if (txn == NULL) {
+ ldb_debug(lmdb->ldb, LDB_DEBUG_FATAL, "No transaction");
+ lmdb->error = MDB_PANIC;
+ return ldb_mdb_error(lmdb->ldb, lmdb->error);
+ }
+
+ lmdb->error = mdb_dbi_open(txn, NULL, 0, &dbi);
+ if (lmdb->error != MDB_SUCCESS) {
+ return ldb_mdb_error(lmdb->ldb, lmdb->error);
+ }
+
+ mdb_key.mv_size = key.length;
+ mdb_key.mv_data = key.data;
+
+ lmdb->error = mdb_del(txn, dbi, &mdb_key, NULL);
+ if (lmdb->error != MDB_SUCCESS) {
+ return ldb_mdb_error(lmdb->ldb, lmdb->error);
+ }
+ return ldb_mdb_err_map(lmdb->error);
+}
+
+static int lmdb_traverse_fn(struct ltdb_private *ltdb,
+ ldb_kv_traverse_fn fn,
+ void *ctx)
+{
+ struct lmdb_private *lmdb = ltdb->lmdb_private;
+ MDB_val mdb_key;
+ MDB_val mdb_data;
+ MDB_txn *txn = NULL;
+ MDB_dbi dbi = 0;
+ MDB_cursor *cursor = NULL;
+ int ret;
+
+ txn = get_current_txn(lmdb);
+ if (txn == NULL) {
+ ldb_debug(lmdb->ldb, LDB_DEBUG_FATAL, "No transaction");
+ lmdb->error = MDB_PANIC;
+ return ldb_mdb_error(lmdb->ldb, lmdb->error);
+ }
+
+ lmdb->error = mdb_dbi_open(txn, NULL, 0, &dbi);
+ if (lmdb->error != MDB_SUCCESS) {
+ return ldb_mdb_error(lmdb->ldb, lmdb->error);
+ }
+
+ lmdb->error = mdb_cursor_open(txn, dbi, &cursor);
+ if (lmdb->error != MDB_SUCCESS) {
+ goto done;
+ }
+
+ while ((lmdb->error = mdb_cursor_get(
+ cursor, &mdb_key,
+ &mdb_data, MDB_NEXT)) == MDB_SUCCESS) {
+
+ struct ldb_val key = {
+ .length = mdb_key.mv_size,
+ .data = mdb_key.mv_data,
+ };
+ struct ldb_val data = {
+ .length = mdb_data.mv_size,
+ .data = mdb_data.mv_data,
+ };
+
+ ret = fn(ltdb, key, data, ctx);
+ if (ret != 0) {
+ goto done;
+ }
+ }
+ if (lmdb->error == MDB_NOTFOUND) {
+ lmdb->error = MDB_SUCCESS;
+ }
+done:
+ if (cursor != NULL) {
+ mdb_cursor_close(cursor);
+ }
+
+ if (lmdb->error != MDB_SUCCESS) {
+ return ldb_mdb_error(lmdb->ldb, lmdb->error);
+ }
+ return ldb_mdb_err_map(lmdb->error);
+}
+
+static int lmdb_update_in_iterate(struct ltdb_private *ltdb,
+ struct ldb_val key,
+ struct ldb_val key2,
+ struct ldb_val data,
+ void *state)
+{
+ struct lmdb_private *lmdb = ltdb->lmdb_private;
+ struct ldb_val copy;
+ int ret = LDB_SUCCESS;
+
+ /*
+ * Need to take a copy of the data as the delete operation alters the
+ * data, as it is in private lmdb memory.
+ */
+ copy.length = data.length;
+ copy.data = talloc_memdup(ltdb, data.data, data.length);
+ if (copy.data == NULL) {
+ lmdb->error = MDB_PANIC;
+ return ldb_oom(lmdb->ldb);
+ }
+
+ lmdb->error = lmdb_delete(ltdb, key);
+ if (lmdb->error != MDB_SUCCESS) {
+ ldb_debug(
+ lmdb->ldb,
+ LDB_DEBUG_ERROR,
+ "Failed to delete %*.*s "
+ "for rekey as %*.*s: %s",
+ (int)key.length, (int)key.length,
+ (const char *)key.data,
+ (int)key2.length, (int)key2.length,
+ (const char *)key.data,
+ mdb_strerror(lmdb->error));
+ ret = ldb_mdb_error(lmdb->ldb, lmdb->error);
+ goto done;
+ }
+
+ lmdb->error = lmdb_store(ltdb, key2, copy, 0);
+ if (lmdb->error != MDB_SUCCESS) {
+ ldb_debug(
+ lmdb->ldb,
+ LDB_DEBUG_ERROR,
+ "Failed to rekey %*.*s as %*.*s: %s",
+ (int)key.length, (int)key.length,
+ (const char *)key.data,
+ (int)key2.length, (int)key2.length,
+ (const char *)key.data,
+ mdb_strerror(lmdb->error));
+ ret = ldb_mdb_error(lmdb->ldb, lmdb->error);
+ goto done;
+ }
+
+done:
+ if (copy.data != NULL) {
+ TALLOC_FREE(copy.data);
+ copy.length = 0;
+ }
+
+ /*
+ * Explicity invalidate the data, as the delete has done this
+ */
+ data.length = 0;
+ data.data = NULL;
+
+ return ret;
+}
+
+/* Handles only a single record */
+static int lmdb_parse_record(struct ltdb_private *ltdb, struct ldb_val key,
+ int (*parser)(struct ldb_val key, struct ldb_val data,
+ void *private_data),
+ void *ctx)
+{
+ struct lmdb_private *lmdb = ltdb->lmdb_private;
+ MDB_val mdb_key;
+ MDB_val mdb_data;
+ MDB_txn *txn = NULL;
+ MDB_dbi dbi;
+ struct ldb_val data;
+
+ txn = get_current_txn(lmdb);
+ if (txn == NULL) {
+ ldb_debug(lmdb->ldb, LDB_DEBUG_FATAL, "No transaction active");
+ lmdb->error = MDB_PANIC;
+ return ldb_mdb_error(lmdb->ldb, lmdb->error);
+ }
+
+ lmdb->error = mdb_dbi_open(txn, NULL, 0, &dbi);
+ if (lmdb->error != MDB_SUCCESS) {
+ return ldb_mdb_error(lmdb->ldb, lmdb->error);
+ }
+
+ mdb_key.mv_size = key.length;
+ mdb_key.mv_data = key.data;
+
+ lmdb->error = mdb_get(txn, dbi, &mdb_key, &mdb_data);
+ if (lmdb->error != MDB_SUCCESS) {
+ /* TODO closing a handle should not even be necessary */
+ mdb_dbi_close(lmdb->env, dbi);
+ if (lmdb->error == MDB_NOTFOUND) {
+ return LDB_ERR_NO_SUCH_OBJECT;
+ }
+ return ldb_mdb_error(lmdb->ldb, lmdb->error);
+ }
+ data.data = mdb_data.mv_data;
+ data.length = mdb_data.mv_size;
+
+ /* TODO closing a handle should not even be necessary */
+ mdb_dbi_close(lmdb->env, dbi);
+
+ return parser(key, data, ctx);
+}
+
+
+static int lmdb_lock_read(struct ldb_module *module)
+{
+ void *data = ldb_module_get_private(module);
+ struct ltdb_private *ltdb = talloc_get_type(data, struct ltdb_private);
+ struct lmdb_private *lmdb = ltdb->lmdb_private;
+ pid_t pid = getpid();
+
+ if (pid != lmdb->pid) {
+ ldb_asprintf_errstring(
+ lmdb->ldb,
+ __location__": Reusing ldb opened by pid %d in "
+ "process %d\n",
+ lmdb->pid,
+ pid);
+ lmdb->error = MDB_BAD_TXN;
+ return LDB_ERR_PROTOCOL_ERROR;
+ }
+
+ lmdb->error = MDB_SUCCESS;
+ if (lmdb_transaction_active(ltdb) == false &&
+ ltdb->read_lock_count == 0) {
+ lmdb->error = mdb_txn_begin(lmdb->env,
+ NULL,
+ MDB_RDONLY,
+ &lmdb->read_txn);
+ }
+ if (lmdb->error != MDB_SUCCESS) {
+ return ldb_mdb_error(lmdb->ldb, lmdb->error);
+ }
+
+ ltdb->read_lock_count++;
+ return ldb_mdb_err_map(lmdb->error);
+}
+
+static int lmdb_unlock_read(struct ldb_module *module)
+{
+ void *data = ldb_module_get_private(module);
+ struct ltdb_private *ltdb = talloc_get_type(data, struct ltdb_private);
+
+ if (lmdb_transaction_active(ltdb) == false && ltdb->read_lock_count == 1) {
+ struct lmdb_private *lmdb = ltdb->lmdb_private;
+ mdb_txn_commit(lmdb->read_txn);
+ lmdb->read_txn = NULL;
+ ltdb->read_lock_count--;
+ return LDB_SUCCESS;
+ }
+ ltdb->read_lock_count--;
+ return LDB_SUCCESS;
+}
+
+static int lmdb_transaction_start(struct ltdb_private *ltdb)
+{
+ struct lmdb_private *lmdb = ltdb->lmdb_private;
+ struct lmdb_trans *ltx;
+ struct lmdb_trans *ltx_head;
+ MDB_txn *tx_parent;
+ pid_t pid = getpid();
+
+ /* Do not take out the transaction lock on a read-only DB */
+ if (ltdb->read_only) {
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ ltx = talloc_zero(lmdb, struct lmdb_trans);
+ if (ltx == NULL) {
+ return ldb_oom(lmdb->ldb);
+ }
+
+ if (pid != lmdb->pid) {
+ ldb_asprintf_errstring(
+ lmdb->ldb,
+ __location__": Reusing ldb opened by pid %d in "
+ "process %d\n",
+ lmdb->pid,
+ pid);
+ lmdb->error = MDB_BAD_TXN;
+ return LDB_ERR_PROTOCOL_ERROR;
+ }
+
+ ltx_head = lmdb_private_trans_head(lmdb);
+
+ tx_parent = lmdb_trans_get_tx(ltx_head);
+
+ lmdb->error = mdb_txn_begin(lmdb->env, tx_parent, 0, &ltx->tx);
+ if (lmdb->error != MDB_SUCCESS) {
+ return ldb_mdb_error(lmdb->ldb, lmdb->error);
+ }
+
+ trans_push(lmdb, ltx);
+
+ return ldb_mdb_err_map(lmdb->error);
+}
+
+static int lmdb_transaction_cancel(struct ltdb_private *ltdb)
+{
+ struct lmdb_trans *ltx;
+ struct lmdb_private *lmdb = ltdb->lmdb_private;
+
+ ltx = lmdb_private_trans_head(lmdb);
+ if (ltx == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ mdb_txn_abort(ltx->tx);
+ trans_finished(lmdb, ltx);
+ return LDB_SUCCESS;
+}
+
+static int lmdb_transaction_prepare_commit(struct ltdb_private *ltdb)
+{
+ /* No need to prepare a commit */
+ return LDB_SUCCESS;
+}
+
+static int lmdb_transaction_commit(struct ltdb_private *ltdb)
+{
+ struct lmdb_trans *ltx;
+ struct lmdb_private *lmdb = ltdb->lmdb_private;
+
+ ltx = lmdb_private_trans_head(lmdb);
+ if (ltx == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ lmdb->error = mdb_txn_commit(ltx->tx);
+ trans_finished(lmdb, ltx);
+
+ return lmdb->error;
+}
+
+static int lmdb_error(struct ltdb_private *ltdb)
+{
+ return ldb_mdb_err_map(ltdb->lmdb_private->error);
+}
+
+static const char *lmdb_errorstr(struct ltdb_private *ltdb)
+{
+ return mdb_strerror(ltdb->lmdb_private->error);
+}
+
+static const char * lmdb_name(struct ltdb_private *ltdb)
+{
+ return "lmdb";
+}
+
+static bool lmdb_changed(struct ltdb_private *ltdb)
+{
+ /*
+ * lmdb does no provide a quick way to determine if the database
+ * has changed. This function always returns true.
+ *
+ * Note that tdb uses a sequence number that allows this function
+ * to be implemented efficiently.
+ */
+ return true;
+}
+
+static struct kv_db_ops lmdb_key_value_ops = {
+ .store = lmdb_store,
+ .delete = lmdb_delete,
+ .iterate = lmdb_traverse_fn,
+ .update_in_iterate = lmdb_update_in_iterate,
+ .fetch_and_parse = lmdb_parse_record,
+ .lock_read = lmdb_lock_read,
+ .unlock_read = lmdb_unlock_read,
+ .begin_write = lmdb_transaction_start,
+ .prepare_write = lmdb_transaction_prepare_commit,
+ .finish_write = lmdb_transaction_commit,
+ .abort_write = lmdb_transaction_cancel,
+ .error = lmdb_error,
+ .errorstr = lmdb_errorstr,
+ .name = lmdb_name,
+ .has_changed = lmdb_changed,
+ .transaction_active = lmdb_transaction_active,
+};
+
+static const char *lmdb_get_path(const char *url)
+{
+ const char *path;
+
+ /* parse the url */
+ if (strchr(url, ':')) {
+ if (strncmp(url, MDB_URL_PREFIX, MDB_URL_PREFIX_SIZE) != 0) {
+ return NULL;
+ }
+ path = url + MDB_URL_PREFIX_SIZE;
+ } else {
+ path = url;
+ }
+
+ return path;
+}
+
+static int lmdb_pvt_destructor(struct lmdb_private *lmdb)
+{
+ struct lmdb_trans *ltx = NULL;
+
+ /* Check if this is a forked child */
+ if (getpid() != lmdb->pid) {
+ int fd = 0;
+ /*
+ * We cannot call mdb_env_close or commit any transactions,
+ * otherwise they might appear finished in the parent.
+ *
+ */
+
+ if (mdb_env_get_fd(lmdb->env, &fd) == 0) {
+ close(fd);
+ }
+
+ /* Remove the pointer, so that no access should occur */
+ lmdb->env = NULL;
+
+ return 0;
+ }
+
+ /*
+ * Close the read transaction if it's open
+ */
+ if (lmdb->read_txn != NULL) {
+ mdb_txn_abort(lmdb->read_txn);
+ }
+
+ if (lmdb->env == NULL) {
+ return 0;
+ }
+
+ /*
+ * Abort any currently active transactions
+ */
+ ltx = lmdb_private_trans_head(lmdb);
+ while (ltx != NULL) {
+ mdb_txn_abort(ltx->tx);
+ trans_finished(lmdb, ltx);
+ ltx = lmdb_private_trans_head(lmdb);
+ }
+ lmdb->env = NULL;
+
+ return 0;
+}
+
+struct mdb_env_wrap {
+ struct mdb_env_wrap *next, *prev;
+ dev_t device;
+ ino_t inode;
+ MDB_env *env;
+ pid_t pid;
+};
+
+static struct mdb_env_wrap *mdb_list;
+
+/* destroy the last connection to an mdb */
+static int mdb_env_wrap_destructor(struct mdb_env_wrap *w)
+{
+ mdb_env_close(w->env);
+ DLIST_REMOVE(mdb_list, w);
+ return 0;
+}
+
+static int lmdb_open_env(TALLOC_CTX *mem_ctx,
+ MDB_env **env,
+ struct ldb_context *ldb,
+ const char *path,
+ unsigned int flags)
+{
+ int ret;
+ const size_t mmap_size = 8LL * GIGABYTE;
+ unsigned int mdb_flags = MDB_NOSUBDIR|MDB_NOTLS;
+ /*
+ * MDB_NOSUBDIR implies there is a separate file called path and a
+ * separate lockfile called path-lock
+ */
+
+ struct mdb_env_wrap *w;
+ struct stat st;
+ pid_t pid = getpid();
+ int fd = 0;
+ unsigned v;
+
+ if (stat(path, &st) == 0) {
+ for (w=mdb_list;w;w=w->next) {
+ if (st.st_dev == w->device &&
+ st.st_ino == w->inode &&
+ pid == w->pid) {
+ /*
+ * We must have only one MDB_env per process
+ */
+ if (!talloc_reference(mem_ctx, w)) {
+ return ldb_oom(ldb);
+ }
+ *env = w->env;
+ return LDB_SUCCESS;
+ }
+ }
+ }
+
+ w = talloc(mem_ctx, struct mdb_env_wrap);
+ if (w == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ ret = mdb_env_create(env);
+ if (ret != 0) {
+ ldb_asprintf_errstring(
+ ldb,
+ "Could not create MDB environment %s: %s\n",
+ path,
+ mdb_strerror(ret));
+ return ldb_mdb_err_map(ret);
+ }
+
+ /*
+ * Currently we set a 8Gb maximum database size
+ * via the constant mmap_size above
+ */
+ ret = mdb_env_set_mapsize(*env, mmap_size);
+ if (ret != 0) {
+ ldb_asprintf_errstring(
+ ldb,
+ "Could not set MDB mmap() size to %llu on %s: %s\n",
+ (unsigned long long)(mmap_size),
+ path,
+ mdb_strerror(ret));
+ TALLOC_FREE(w);
+ return ldb_mdb_err_map(ret);
+ }
+
+ mdb_env_set_maxreaders(*env, 100000);
+ /*
+ * As we ensure that there is only one MDB_env open per database per
+ * process. We can not use the MDB_RDONLY flag, as another ldb may be
+ * opened in read write mode
+ */
+ if (flags & LDB_FLG_NOSYNC) {
+ mdb_flags |= MDB_NOSYNC;
+ }
+ ret = mdb_env_open(*env, path, mdb_flags, 0644);
+ if (ret != 0) {
+ ldb_asprintf_errstring(ldb,
+ "Could not open DB %s: %s\n",
+ path, mdb_strerror(ret));
+ TALLOC_FREE(w);
+ return ldb_mdb_err_map(ret);
+ }
+
+ ret = mdb_env_get_fd(*env, &fd);
+ if (ret != 0) {
+ ldb_asprintf_errstring(ldb,
+ "Could not obtain DB FD %s: %s\n",
+ path, mdb_strerror(ret));
+ TALLOC_FREE(w);
+ return ldb_mdb_err_map(ret);
+ }
+
+ /* Just as for TDB: on exec, don't inherit the fd */
+ v = fcntl(fd, F_GETFD, 0);
+ fcntl(fd, F_SETFD, v | FD_CLOEXEC);
+
+ if (fstat(fd, &st) != 0) {
+ ldb_asprintf_errstring(
+ ldb,
+ "Could not stat %s:\n",
+ path);
+ TALLOC_FREE(w);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ w->env = *env;
+ w->device = st.st_dev;
+ w->inode = st.st_ino;
+ w->pid = pid;
+
+ talloc_set_destructor(w, mdb_env_wrap_destructor);
+
+ DLIST_ADD(mdb_list, w);
+
+ return LDB_SUCCESS;
+
+}
+
+static int lmdb_pvt_open(struct lmdb_private *lmdb,
+ struct ldb_context *ldb,
+ const char *path,
+ unsigned int flags)
+{
+ int ret;
+ int lmdb_max_key_length;
+
+ if (flags & LDB_FLG_DONT_CREATE_DB) {
+ struct stat st;
+ if (stat(path, &st) != 0) {
+ return LDB_ERR_UNAVAILABLE;
+ }
+ }
+
+ ret = lmdb_open_env(lmdb, &lmdb->env, ldb, path, flags);
+ if (ret != 0) {
+ return ret;
+ }
+
+ /* Close when lmdb is released */
+ talloc_set_destructor(lmdb, lmdb_pvt_destructor);
+
+ /* Store the original pid during the LMDB open */
+ lmdb->pid = getpid();
+
+ lmdb_max_key_length = mdb_env_get_maxkeysize(lmdb->env);
+
+ /* This will never happen, but if it does make sure to freak out */
+ if (lmdb_max_key_length < LDB_MDB_MAX_KEY_LENGTH) {
+ return ldb_operr(ldb);
+ }
+
+ return LDB_SUCCESS;
+}
+
+int lmdb_connect(struct ldb_context *ldb,
+ const char *url,
+ unsigned int flags,
+ const char *options[],
+ struct ldb_module **_module)
+{
+ const char *path = NULL;
+ struct lmdb_private *lmdb = NULL;
+ struct ltdb_private *ltdb = NULL;
+ int ret;
+
+ /*
+ * We hold locks, so we must use a private event context
+ * on each returned handle
+ */
+ ldb_set_require_private_event_context(ldb);
+
+ path = lmdb_get_path(url);
+ if (path == NULL) {
+ ldb_debug(ldb, LDB_DEBUG_ERROR, "Invalid mdb URL '%s'", url);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ltdb = talloc_zero(ldb, struct ltdb_private);
+ if (!ltdb) {
+ ldb_oom(ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ lmdb = talloc_zero(ltdb, struct lmdb_private);
+ if (lmdb == NULL) {
+ TALLOC_FREE(ltdb);
+ return ldb_oom(ldb);
+ }
+ lmdb->ldb = ldb;
+ ltdb->kv_ops = &lmdb_key_value_ops;
+
+ ret = lmdb_pvt_open(lmdb, ldb, path, flags);
+ if (ret != LDB_SUCCESS) {
+ TALLOC_FREE(ltdb);
+ return ret;
+ }
+
+ ltdb->lmdb_private = lmdb;
+ if (flags & LDB_FLG_RDONLY) {
+ ltdb->read_only = true;
+ }
+
+ /*
+ * This maximum length becomes encoded in the index values so
+ * must never change even if LMDB starts to allow longer keys.
+ * The override option is max_key_len_for_self_test, and is
+ * used for testing only.
+ */
+ ltdb->max_key_length = LDB_MDB_MAX_KEY_LENGTH;
+
+ return init_store(ltdb, "ldb_mdb backend", ldb, options, _module);
+}
diff --git a/ldb_mdb/ldb_mdb.h b/ldb_mdb/ldb_mdb.h
new file mode 100644
index 0000000..8f21493
--- /dev/null
+++ b/ldb_mdb/ldb_mdb.h
@@ -0,0 +1,60 @@
+/*
+ ldb database library using mdb back end - transaction operations
+
+ Copyright (C) Jakub Hrozek 2015
+ Copyright (C) Catalyst.Net Ltd 2017
+
+ ** NOTE! The following LGPL license applies to the ldb
+ ** library. This does NOT imply that all of Samba is released
+ ** under the LGPL
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _LDB_MDB_H_
+#define _LDB_MDB_H_
+
+#include "ldb_private.h"
+#include <lmdb.h>
+
+struct lmdb_private {
+ struct ldb_context *ldb;
+ MDB_env *env;
+
+ struct lmdb_trans *txlist;
+
+ struct ldb_mdb_metadata {
+ struct ldb_message *attributes;
+ unsigned seqnum;
+ } *meta;
+ int error;
+ MDB_txn *read_txn;
+
+ pid_t pid;
+
+};
+
+struct lmdb_trans {
+ struct lmdb_trans *next;
+ struct lmdb_trans *prev;
+
+ MDB_txn *tx;
+};
+
+int ldb_mdb_err_map(int lmdb_err);
+int lmdb_connect(struct ldb_context *ldb, const char *url,
+ unsigned int flags, const char *options[],
+ struct ldb_module **_module);
+
+#endif /* _LDB_MDB_H_ */
diff --git a/ldb_mdb/ldb_mdb_init.c b/ldb_mdb/ldb_mdb_init.c
new file mode 100644
index 0000000..339c3f2
--- /dev/null
+++ b/ldb_mdb/ldb_mdb_init.c
@@ -0,0 +1,31 @@
+/*
+ ldb database library using mdb back end
+
+ Copyright (C) Jakub Hrozek 2014
+ Copyright (C) Catalyst.Net Ltd 2017
+
+ ** NOTE! The following LGPL license applies to the ldb
+ ** library. This does NOT imply that all of Samba is released
+ ** under the LGPL
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "ldb_mdb.h"
+
+int ldb_mdb_init(const char *version)
+{
+ LDB_MODULE_CHECK_VERSION(version);
+ return ldb_register_backend("mdb", lmdb_connect, false);
+}
diff --git a/ldb_tdb/ldb_cache.c b/ldb_tdb/ldb_cache.c
index 5b90bd9..1856fb1 100644
--- a/ldb_tdb/ldb_cache.c
+++ b/ldb_tdb/ldb_cache.c
@@ -238,7 +238,7 @@ static int ltdb_index_load(struct ldb_module *module,
{
struct ldb_context *ldb = ldb_module_get_ctx(module);
struct ldb_dn *indexlist_dn;
- int r;
+ int r, lmdb_subdb_version;
if (ldb->schema.index_handler_override) {
/*
@@ -291,6 +291,20 @@ static int ltdb_index_load(struct ldb_module *module,
= ldb_msg_find_attr_as_string(ltdb->cache->indexlist,
LTDB_IDX_DN_GUID, NULL);
+ lmdb_subdb_version
+ = ldb_msg_find_attr_as_int(ltdb->cache->indexlist,
+ LTDB_IDX_LMDB_SUBDB, 0);
+
+ if (lmdb_subdb_version != 0) {
+ ldb_set_errstring(ldb,
+ "FATAL: This ldb_mdb database has "
+ "been written in a new verson of LDB "
+ "using a sub-database index that "
+ "is not understood by ldb "
+ LDB_VERSION);
+ return -1;
+ }
+
return 0;
}
@@ -386,13 +400,13 @@ int ltdb_cache_load(struct ldb_module *module)
uint64_t seq;
struct ldb_message *baseinfo = NULL, *options = NULL;
const struct ldb_schema_attribute *a;
+ bool have_write_txn = false;
int r;
ldb = ldb_module_get_ctx(module);
/* a very fast check to avoid extra database reads */
- if (ltdb->cache != NULL &&
- tdb_get_seqnum(ltdb->tdb) == ltdb->tdb_seqnum) {
+ if (ltdb->cache != NULL && !ltdb->kv_ops->has_changed(ltdb)) {
return 0;
}
@@ -407,30 +421,42 @@ int ltdb_cache_load(struct ldb_module *module)
baseinfo_dn = ldb_dn_new(baseinfo, ldb, LTDB_BASEINFO);
if (baseinfo_dn == NULL) goto failed;
+ r = ltdb->kv_ops->lock_read(module);
+ if (r != LDB_SUCCESS) {
+ goto failed;
+ }
r= ltdb_search_dn1(module, baseinfo_dn, baseinfo, 0);
if (r != LDB_SUCCESS && r != LDB_ERR_NO_SUCH_OBJECT) {
- goto failed;
+ goto failed_and_unlock;
}
-
+
/* possibly initialise the baseinfo */
if (r == LDB_ERR_NO_SUCH_OBJECT) {
- if (tdb_transaction_start(ltdb->tdb) != 0) {
+ /* Give up the read lock, try again with a write lock */
+ r = ltdb->kv_ops->unlock_read(module);
+ if (r != LDB_SUCCESS) {
goto failed;
}
+ if (ltdb->kv_ops->begin_write(ltdb) != 0) {
+ goto failed;
+ }
+
+ have_write_txn = true;
+
/* error handling for ltdb_baseinfo_init() is by
looking for the record again. */
ltdb_baseinfo_init(module);
- tdb_transaction_commit(ltdb->tdb);
-
if (ltdb_search_dn1(module, baseinfo_dn, baseinfo, 0) != LDB_SUCCESS) {
- goto failed;
+ goto failed_and_unlock;
}
+
}
- ltdb->tdb_seqnum = tdb_get_seqnum(ltdb->tdb);
+ /* Ignore the result, and update the sequence number */
+ ltdb->kv_ops->has_changed(ltdb);
/* if the current internal sequence number is the same as the one
in the database then assume the rest of the cache is OK */
@@ -441,16 +467,17 @@ int ltdb_cache_load(struct ldb_module *module)
ltdb->sequence_number = seq;
/* Read an interpret database options */
+
options = ldb_msg_new(ltdb->cache);
- if (options == NULL) goto failed;
+ if (options == NULL) goto failed_and_unlock;
options_dn = ldb_dn_new(options, ldb, LTDB_OPTIONS);
- if (options_dn == NULL) goto failed;
+ if (options_dn == NULL) goto failed_and_unlock;
r= ltdb_search_dn1(module, options_dn, options, 0);
talloc_free(options_dn);
if (r != LDB_SUCCESS && r != LDB_ERR_NO_SUCH_OBJECT) {
- goto failed;
+ goto failed_and_unlock;
}
/* set flags if they do exist */
@@ -477,7 +504,7 @@ int ltdb_cache_load(struct ldb_module *module)
ltdb_attributes_unload(module);
if (ltdb_index_load(module, ltdb) == -1) {
- goto failed;
+ goto failed_and_unlock;
}
/*
@@ -486,7 +513,7 @@ int ltdb_cache_load(struct ldb_module *module)
* partition module.
*/
if (ltdb_attributes_load(module) == -1) {
- goto failed;
+ goto failed_and_unlock;
}
ltdb->GUID_index_syntax = NULL;
@@ -501,10 +528,25 @@ int ltdb_cache_load(struct ldb_module *module)
}
done:
+ if (have_write_txn) {
+ if (ltdb->kv_ops->finish_write(ltdb) != 0) {
+ goto failed;
+ }
+ } else {
+ ltdb->kv_ops->unlock_read(module);
+ }
+
talloc_free(options);
talloc_free(baseinfo);
return 0;
+failed_and_unlock:
+ if (have_write_txn) {
+ ltdb->kv_ops->abort_write(ltdb);
+ } else {
+ ltdb->kv_ops->unlock_read(module);
+ }
+
failed:
talloc_free(options);
talloc_free(baseinfo);
@@ -592,7 +634,7 @@ int ltdb_increase_sequence_number(struct ldb_module *module)
/* updating the tdb_seqnum here avoids us reloading the cache
records due to our own modification */
- ltdb->tdb_seqnum = tdb_get_seqnum(ltdb->tdb);
+ ltdb->kv_ops->has_changed(ltdb);
return ret;
}
diff --git a/ldb_tdb/ldb_index.c b/ldb_tdb/ldb_index.c
index ee20273..7bd843e 100644
--- a/ldb_tdb/ldb_index.c
+++ b/ldb_tdb/ldb_index.c
@@ -165,13 +165,19 @@ struct ltdb_idxptr {
int error;
};
+enum key_truncation {
+ KEY_NOT_TRUNCATED,
+ KEY_TRUNCATED,
+};
+
static int ltdb_write_index_dn_guid(struct ldb_module *module,
const struct ldb_message *msg,
int add);
static int ltdb_index_dn_base_dn(struct ldb_module *module,
struct ltdb_private *ltdb,
struct ldb_dn *base_dn,
- struct dn_list *dn_list);
+ struct dn_list *dn_list,
+ enum key_truncation *truncation);
static void ltdb_dn_list_sort(struct ltdb_private *ltdb,
struct dn_list *list);
@@ -183,6 +189,13 @@ static void ltdb_dn_list_sort(struct ltdb_private *ltdb,
#define LTDB_GUID_INDEXING_VERSION 3
+static unsigned ltdb_max_key_length(struct ltdb_private *ltdb) {
+ if (ltdb->max_key_length == 0){
+ return UINT_MAX;
+ }
+ return ltdb->max_key_length;
+}
+
/* enable the idxptr mode when transactions start */
int ltdb_index_transaction_start(struct ldb_module *module)
{
@@ -423,7 +436,7 @@ normal_index:
return LDB_ERR_OPERATIONS_ERROR;
}
- if (el->num_values != 1) {
+ if (el->num_values == 0) {
return LDB_ERR_OPERATIONS_ERROR;
}
@@ -459,13 +472,16 @@ int ltdb_key_dn_from_idx(struct ldb_module *module,
{
struct ldb_context *ldb = ldb_module_get_ctx(module);
int ret;
+ int index = 0;
+ enum key_truncation truncation = KEY_NOT_TRUNCATED;
struct dn_list *list = talloc(mem_ctx, struct dn_list);
if (list == NULL) {
ldb_oom(ldb);
return LDB_ERR_OPERATIONS_ERROR;
}
- ret = ltdb_index_dn_base_dn(module, ltdb, dn, list);
+
+ ret = ltdb_index_dn_base_dn(module, ltdb, dn, list, &truncation);
if (ret != LDB_SUCCESS) {
TALLOC_FREE(list);
return ret;
@@ -475,7 +491,8 @@ int ltdb_key_dn_from_idx(struct ldb_module *module,
TALLOC_FREE(list);
return LDB_ERR_NO_SUCH_OBJECT;
}
- if (list->count > 1) {
+
+ if (list->count > 1 && truncation == KEY_NOT_TRUNCATED) {
const char *dn_str = ldb_dn_get_linearized(dn);
ldb_asprintf_errstring(ldb_module_get_ctx(module),
__location__
@@ -488,9 +505,81 @@ int ltdb_key_dn_from_idx(struct ldb_module *module,
return LDB_ERR_CONSTRAINT_VIOLATION;
}
+ if (list->count > 0 && truncation == KEY_TRUNCATED) {
+ /*
+ * DN key has been truncated, need to inspect the actual
+ * records to locate the actual DN
+ */
+ int i;
+ index = -1;
+ for (i=0; i < list->count; i++) {
+ uint8_t guid_key[LTDB_GUID_KEY_SIZE];
+ TDB_DATA key = {
+ .dptr = guid_key,
+ .dsize = sizeof(guid_key)
+ };
+ const int flags = LDB_UNPACK_DATA_FLAG_NO_ATTRS;
+ struct ldb_message *rec = ldb_msg_new(ldb);
+ if (rec == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = ltdb_idx_to_key(module, ltdb,
+ ldb, &list->dn[i],
+ &key);
+ if (ret != LDB_SUCCESS) {
+ TALLOC_FREE(list);
+ TALLOC_FREE(rec);
+ return ret;
+ }
+
+ ret = ltdb_search_key(module, ltdb, key,
+ rec, flags);
+ if (key.dptr != guid_key) {
+ TALLOC_FREE(key.dptr);
+ }
+ if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+ /*
+ * the record has disappeared?
+ * yes, this can happen
+ */
+ TALLOC_FREE(rec);
+ continue;
+ }
+
+ if (ret != LDB_SUCCESS) {
+ /* an internal error */
+ TALLOC_FREE(rec);
+ TALLOC_FREE(list);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /*
+ * We found the actual DN that we wanted from in the
+ * multiple values that matched the index
+ * (due to truncation), so return that.
+ *
+ */
+ if (ldb_dn_compare(dn, rec->dn) == 0) {
+ index = i;
+ TALLOC_FREE(rec);
+ break;
+ }
+ }
+
+ /*
+ * We matched the index but the actual DN we wanted
+ * was not here.
+ */
+ if (index == -1) {
+ TALLOC_FREE(list);
+ return LDB_ERR_NO_SUCH_OBJECT;
+ }
+ }
+
/* The tdb_key memory is allocated by the caller */
ret = ltdb_guid_to_key(module, ltdb,
- &list->dn[0], tdb_key);
+ &list->dn[index], tdb_key);
TALLOC_FREE(list);
if (ret != LDB_SUCCESS) {
@@ -743,7 +832,8 @@ int ltdb_index_transaction_cancel(struct ldb_module *module)
static struct ldb_dn *ltdb_index_key(struct ldb_context *ldb,
struct ltdb_private *ltdb,
const char *attr, const struct ldb_val *value,
- const struct ldb_schema_attribute **ap)
+ const struct ldb_schema_attribute **ap,
+ enum key_truncation *truncation)
{
struct ldb_dn *ret;
struct ldb_val v;
@@ -753,6 +843,17 @@ static struct ldb_dn *ltdb_index_key(struct ldb_context *ldb,
int r;
bool should_b64_encode;
+ unsigned int max_key_length = ltdb_max_key_length(ltdb);
+ size_t key_len = 0;
+ size_t attr_len = 0;
+ const size_t indx_len = sizeof(LTDB_INDEX) - 1;
+ unsigned frmt_len = 0;
+ const size_t additional_key_length = 4;
+ unsigned int num_separators = 3; /* Estimate for overflow check */
+ const size_t min_data = 1;
+ const size_t min_key_length = additional_key_length
+ + indx_len + num_separators + min_data;
+
if (attr[0] == '@') {
attr_for_dn = attr;
v = *value;
@@ -788,6 +889,30 @@ static struct ldb_dn *ltdb_index_key(struct ldb_context *ldb,
return NULL;
}
}
+ attr_len = strlen(attr_for_dn);
+
+ /*
+ * Check if there is any hope this will fit into the DB.
+ * Overflow here is not actually critical the code below
+ * checks again to make the printf and the DB does another
+ * check for too long keys
+ */
+ if (max_key_length - attr_len < min_key_length) {
+ ldb_asprintf_errstring(
+ ldb,
+ __location__ ": max_key_length "
+ "is too small (%u) < (%u)",
+ max_key_length,
+ (unsigned)(min_key_length + attr_len));
+ talloc_free(attr_folded);
+ return NULL;
+ }
+
+ /*
+ * ltdb_key_dn() makes something 4 bytes longer, it adds a leading
+ * "DN=" and a trailing string terminator
+ */
+ max_key_length -= additional_key_length;
/*
* We do not base 64 encode a DN in a key, it has already been
@@ -812,18 +937,70 @@ static struct ldb_dn *ltdb_index_key(struct ldb_context *ldb,
}
if (should_b64_encode) {
+ size_t vstr_len = 0;
char *vstr = ldb_base64_encode(ldb, (char *)v.data, v.length);
if (!vstr) {
talloc_free(attr_folded);
return NULL;
}
- ret = ldb_dn_new_fmt(ldb, ldb, "%s:%s::%s", LTDB_INDEX,
- attr_for_dn, vstr);
+ vstr_len = strlen(vstr);
+ /*
+ * Overflow here is not critical as we only use this
+ * to choose the printf truncation
+ */
+ key_len = num_separators + indx_len + attr_len + vstr_len;
+ if (key_len > max_key_length) {
+ size_t excess = key_len - max_key_length;
+ frmt_len = vstr_len - excess;
+ *truncation = KEY_TRUNCATED;
+ /*
+ * Truncated keys are placed in a separate key space
+ * from the non truncated keys
+ * Note: the double hash "##" is not a typo and
+ * indicates that the following value is base64 encoded
+ */
+ ret = ldb_dn_new_fmt(ldb, ldb, "%s#%s##%.*s",
+ LTDB_INDEX, attr_for_dn,
+ frmt_len, vstr);
+ } else {
+ frmt_len = vstr_len;
+ *truncation = KEY_NOT_TRUNCATED;
+ /*
+ * Note: the double colon "::" is not a typo and
+ * indicates that the following value is base64 encoded
+ */
+ ret = ldb_dn_new_fmt(ldb, ldb, "%s:%s::%.*s",
+ LTDB_INDEX, attr_for_dn,
+ frmt_len, vstr);
+ }
talloc_free(vstr);
} else {
- ret = ldb_dn_new_fmt(ldb, ldb, "%s:%s:%.*s", LTDB_INDEX,
- attr_for_dn,
- (int)v.length, (char *)v.data);
+ /* Only need two seperators */
+ num_separators = 2;
+
+ /*
+ * Overflow here is not critical as we only use this
+ * to choose the printf truncation
+ */
+ key_len = num_separators + indx_len + attr_len + (int)v.length;
+ if (key_len > max_key_length) {
+ size_t excess = key_len - max_key_length;
+ frmt_len = v.length - excess;
+ *truncation = KEY_TRUNCATED;
+ /*
+ * Truncated keys are placed in a separate key space
+ * from the non truncated keys
+ */
+ ret = ldb_dn_new_fmt(ldb, ldb, "%s#%s#%.*s",
+ LTDB_INDEX, attr_for_dn,
+ frmt_len, (char *)v.data);
+ } else {
+ frmt_len = v.length;
+ *truncation = KEY_NOT_TRUNCATED;
+ ret = ldb_dn_new_fmt(ldb, ldb, "%s:%s:%.*s",
+ LTDB_INDEX, attr_for_dn,
+ frmt_len, (char *)v.data);
+ }
}
if (v.data != value->data) {
@@ -908,6 +1085,7 @@ static int ltdb_index_dn_simple(struct ldb_module *module,
struct ldb_context *ldb;
struct ldb_dn *dn;
int ret;
+ enum key_truncation truncation = KEY_NOT_TRUNCATED;
ldb = ldb_module_get_ctx(module);
@@ -924,7 +1102,12 @@ static int ltdb_index_dn_simple(struct ldb_module *module,
search criterion */
dn = ltdb_index_key(ldb, ltdb,
tree->u.equality.attr,
- &tree->u.equality.value, NULL);
+ &tree->u.equality.value, NULL, &truncation);
+ /*
+ * We ignore truncation here and allow multi-valued matches
+ * as ltdb_search_indexed will filter out the wrong one in
+ * ltdb_index_filter() which calls ldb_match_message().
+ */
if (!dn) return LDB_ERR_OPERATIONS_ERROR;
ret = ltdb_dn_list_load(module, ltdb, dn, list);
@@ -959,6 +1142,7 @@ static int ltdb_index_dn_leaf(struct ldb_module *module,
return LDB_SUCCESS;
}
if (ldb_attr_dn(tree->u.equality.attr) == 0) {
+ enum key_truncation truncation = KEY_NOT_TRUNCATED;
struct ldb_dn *dn
= ldb_dn_from_ldb_val(list,
ldb_module_get_ctx(module),
@@ -977,7 +1161,13 @@ static int ltdb_index_dn_leaf(struct ldb_module *module,
* We can't call TALLOC_FREE(dn) as this must belong
* to list for the memory to remain valid.
*/
- return ltdb_index_dn_base_dn(module, ltdb, dn, list);
+ return ltdb_index_dn_base_dn(module, ltdb, dn, list,
+ &truncation);
+ /*
+ * We ignore truncation here and allow multi-valued matches
+ * as ltdb_search_indexed will filter out the wrong one in
+ * ltdb_index_filter() which calls ldb_match_message().
+ */
} else if ((ltdb->cache->GUID_index_attribute != NULL) &&
(ldb_attr_cmp(tree->u.equality.attr,
@@ -1120,6 +1310,9 @@ static bool list_union(struct ldb_context *ldb,
/*
* Sort the lists (if not in GUID DN mode) so we can do
* the de-duplication during the merge
+ *
+ * NOTE: This can sort the in-memory index values, as list or
+ * list2 might not be a copy!
*/
ltdb_dn_list_sort(ltdb, list);
ltdb_dn_list_sort(ltdb, list2);
@@ -1380,7 +1573,8 @@ static int ltdb_index_dn_attr(struct ldb_module *module,
struct ltdb_private *ltdb,
const char *attr,
struct ldb_dn *dn,
- struct dn_list *list)
+ struct dn_list *list,
+ enum key_truncation *truncation)
{
struct ldb_context *ldb;
struct ldb_dn *key;
@@ -1392,7 +1586,7 @@ static int ltdb_index_dn_attr(struct ldb_module *module,
/* work out the index key from the parent DN */
val.data = (uint8_t *)((uintptr_t)ldb_dn_get_casefold(dn));
val.length = strlen((char *)val.data);
- key = ltdb_index_key(ldb, ltdb, attr, &val, NULL);
+ key = ltdb_index_key(ldb, ltdb, attr, &val, NULL, truncation);
if (!key) {
ldb_oom(ldb);
return LDB_ERR_OPERATIONS_ERROR;
@@ -1417,12 +1611,14 @@ static int ltdb_index_dn_attr(struct ldb_module *module,
static int ltdb_index_dn_one(struct ldb_module *module,
struct ltdb_private *ltdb,
struct ldb_dn *parent_dn,
- struct dn_list *list)
+ struct dn_list *list,
+ enum key_truncation *truncation)
{
/* Ensure we do not shortcut on intersection for this list */
list->strict = true;
return ltdb_index_dn_attr(module, ltdb,
- LTDB_IDXONE, parent_dn, list);
+ LTDB_IDXONE, parent_dn, list, truncation);
+
}
/*
@@ -1431,7 +1627,8 @@ static int ltdb_index_dn_one(struct ldb_module *module,
static int ltdb_index_dn_base_dn(struct ldb_module *module,
struct ltdb_private *ltdb,
struct ldb_dn *base_dn,
- struct dn_list *dn_list)
+ struct dn_list *dn_list,
+ enum key_truncation *truncation)
{
const struct ldb_val *guid_val = NULL;
if (ltdb->cache->GUID_index_attribute == NULL) {
@@ -1468,7 +1665,7 @@ static int ltdb_index_dn_base_dn(struct ldb_module *module,
}
return ltdb_index_dn_attr(module, ltdb,
- LTDB_IDXDN, base_dn, dn_list);
+ LTDB_IDXDN, base_dn, dn_list, truncation);
}
/*
@@ -1520,28 +1717,64 @@ static int ltdb_index_dn(struct ldb_module *module,
static int ltdb_index_filter(struct ltdb_private *ltdb,
const struct dn_list *dn_list,
struct ltdb_context *ac,
- uint32_t *match_count)
+ uint32_t *match_count,
+ enum key_truncation scope_one_truncation)
{
- struct ldb_context *ldb;
+ struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
struct ldb_message *msg;
struct ldb_message *filtered_msg;
unsigned int i;
+ unsigned int num_keys = 0;
uint8_t previous_guid_key[LTDB_GUID_KEY_SIZE] = {};
+ TDB_DATA *keys = NULL;
- ldb = ldb_module_get_ctx(ac->module);
+ /*
+ * We have to allocate the key list (rather than just walk the
+ * caller supplied list) as the callback could change the list
+ * (by modifying an indexed attribute hosted in the in-memory
+ * index cache!)
+ */
+ keys = talloc_array(ac, TDB_DATA, dn_list->count);
+ if (keys == NULL) {
+ return ldb_module_oom(ac->module);
+ }
+
+ if (ltdb->cache->GUID_index_attribute != NULL) {
+ /*
+ * We speculate that the keys will be GUID based and so
+ * pre-fill in enough space for a GUID (avoiding a pile of
+ * small allocations)
+ */
+ struct guid_tdb_key {
+ uint8_t guid_key[LTDB_GUID_KEY_SIZE];
+ } *key_values = NULL;
+
+ key_values = talloc_array(keys,
+ struct guid_tdb_key,
+ dn_list->count);
+
+ for (i = 0; i < dn_list->count; i++) {
+ keys[i].dptr = key_values[i].guid_key;
+ keys[i].dsize = sizeof(key_values[i].guid_key);
+ }
+ if (key_values == NULL) {
+ return ldb_module_oom(ac->module);
+ }
+ } else {
+ for (i = 0; i < dn_list->count; i++) {
+ keys[i].dptr = NULL;
+ keys[i].dsize = 0;
+ }
+ }
for (i = 0; i < dn_list->count; i++) {
- uint8_t guid_key[LTDB_GUID_KEY_SIZE];
- TDB_DATA tdb_key = {
- .dptr = guid_key,
- .dsize = sizeof(guid_key)
- };
int ret;
- bool matched;
- ret = ltdb_idx_to_key(ac->module, ltdb,
- ac, &dn_list->dn[i],
- &tdb_key);
+ ret = ltdb_idx_to_key(ac->module,
+ ltdb,
+ keys,
+ &dn_list->dn[i],
+ &keys[num_keys]);
if (ret != LDB_SUCCESS) {
return ret;
}
@@ -1558,30 +1791,42 @@ static int ltdb_index_filter(struct ltdb_private *ltdb,
* LDB_FLAG_INTERNAL_DISABLE_SINGLE_VALUE_CHECK
*/
- if (memcmp(previous_guid_key, tdb_key.dptr,
+ if (memcmp(previous_guid_key,
+ keys[num_keys].dptr,
sizeof(previous_guid_key)) == 0) {
continue;
}
- memcpy(previous_guid_key, tdb_key.dptr,
+ memcpy(previous_guid_key,
+ keys[num_keys].dptr,
sizeof(previous_guid_key));
}
+ num_keys++;
+ }
+
+ /*
+ * Now that the list is a safe copy, send the callbacks
+ */
+ for (i = 0; i < num_keys; i++) {
+ int ret;
+ bool matched;
msg = ldb_msg_new(ac);
if (!msg) {
return LDB_ERR_OPERATIONS_ERROR;
}
-
ret = ltdb_search_key(ac->module, ltdb,
- tdb_key, msg,
+ keys[i], msg,
LDB_UNPACK_DATA_FLAG_NO_DATA_ALLOC|
LDB_UNPACK_DATA_FLAG_NO_VALUES_ALLOC);
- if (tdb_key.dptr != guid_key) {
- TALLOC_FREE(tdb_key.dptr);
- }
if (ret == LDB_ERR_NO_SUCH_OBJECT) {
- /* the record has disappeared? yes, this can happen */
+ /*
+ * the record has disappeared? yes, this can
+ * happen if the entry is deleted by something
+ * operating in the callback (not another
+ * process, as we have a read lock)
+ */
talloc_free(msg);
continue;
}
@@ -1592,10 +1837,15 @@ static int ltdb_index_filter(struct ltdb_private *ltdb,
return LDB_ERR_OPERATIONS_ERROR;
}
- /* We trust the index for SCOPE_ONELEVEL and SCOPE_BASE */
- if ((ac->scope == LDB_SCOPE_ONELEVEL
- && ltdb->cache->one_level_indexes)
- || ac->scope == LDB_SCOPE_BASE) {
+ /*
+ * We trust the index for LDB_SCOPE_ONELEVEL
+ * unless the index key has been truncated.
+ *
+ * LDB_SCOPE_BASE is not passed in by our only caller.
+ */
+ if (ac->scope == LDB_SCOPE_ONELEVEL
+ && ltdb->cache->one_level_indexes
+ && scope_one_truncation == KEY_NOT_TRUNCATED) {
ret = ldb_match_message(ldb, msg, ac->tree,
ac->scope, &matched);
} else {
@@ -1634,6 +1884,7 @@ static int ltdb_index_filter(struct ltdb_private *ltdb,
(*match_count)++;
}
+ TALLOC_FREE(keys);
return LDB_SUCCESS;
}
@@ -1668,6 +1919,7 @@ int ltdb_search_indexed(struct ltdb_context *ac, uint32_t *match_count)
struct dn_list *dn_list;
int ret;
enum ldb_scope index_scope;
+ enum key_truncation scope_one_truncation = KEY_NOT_TRUNCATED;
/* see if indexing is enabled */
if (!ltdb->cache->attribute_indexes &&
@@ -1697,17 +1949,10 @@ int ltdb_search_indexed(struct ltdb_context *ac, uint32_t *match_count)
switch (index_scope) {
case LDB_SCOPE_BASE:
/*
- * If we ever start to also load the index values for
- * the tree, we must ensure we strictly intersect with
- * this list, as we trust the BASE index
+ * The only caller will have filtered the operation out
+ * so we should never get here
*/
- ret = ltdb_index_dn_base_dn(ac->module, ltdb,
- ac->base, dn_list);
- if (ret != LDB_SUCCESS) {
- talloc_free(dn_list);
- return ret;
- }
- break;
+ return ldb_operr(ldb);
case LDB_SCOPE_ONELEVEL:
/*
@@ -1715,7 +1960,8 @@ int ltdb_search_indexed(struct ltdb_context *ac, uint32_t *match_count)
* the tree, we must ensure we strictly intersect with
* this list, as we trust the ONELEVEL index
*/
- ret = ltdb_index_dn_one(ac->module, ltdb, ac->base, dn_list);
+ ret = ltdb_index_dn_one(ac->module, ltdb, ac->base, dn_list,
+ &scope_one_truncation);
if (ret != LDB_SUCCESS) {
talloc_free(dn_list);
return ret;
@@ -1748,20 +1994,21 @@ int ltdb_search_indexed(struct ltdb_context *ac, uint32_t *match_count)
}
/*
* Here we load the index for the tree.
+ *
+ * We only care if this is successful, if the
+ * index can't trim the result list down then
+ * the ONELEVEL index is still good enough.
*/
ret = ltdb_index_dn(ac->module, ltdb, ac->tree,
idx_one_tree_list);
- if (ret != LDB_SUCCESS) {
- talloc_free(idx_one_tree_list);
- talloc_free(dn_list);
- return ret;
- }
-
- if (!list_intersect(ldb, ltdb,
- dn_list, idx_one_tree_list)) {
- talloc_free(idx_one_tree_list);
- talloc_free(dn_list);
- return LDB_ERR_OPERATIONS_ERROR;
+ if (ret == LDB_SUCCESS) {
+ if (!list_intersect(ldb, ltdb,
+ dn_list,
+ idx_one_tree_list)) {
+ talloc_free(idx_one_tree_list);
+ talloc_free(dn_list);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
}
}
break;
@@ -1784,7 +2031,19 @@ int ltdb_search_indexed(struct ltdb_context *ac, uint32_t *match_count)
break;
}
- ret = ltdb_index_filter(ltdb, dn_list, ac, match_count);
+ /*
+ * It is critical that this function do the re-filter even
+ * on things found by the index as the index can over-match
+ * in cases of truncation (as well as when it decides it is
+ * not worth further filtering)
+ *
+ * If this changes, then the index code above would need to
+ * pass up a flag to say if any index was truncated during
+ * processing as the truncation here refers only to the
+ * SCOPE_ONELEVEL index.
+ */
+ ret = ltdb_index_filter(ltdb, dn_list, ac, match_count,
+ scope_one_truncation);
talloc_free(dn_list);
return ret;
}
@@ -1820,6 +2079,8 @@ static int ltdb_index_add1(struct ldb_module *module,
const struct ldb_schema_attribute *a;
struct dn_list *list;
unsigned alloc_len;
+ enum key_truncation truncation = KEY_TRUNCATED;
+
ldb = ldb_module_get_ctx(module);
@@ -1829,11 +2090,30 @@ static int ltdb_index_add1(struct ldb_module *module,
}
dn_key = ltdb_index_key(ldb, ltdb,
- el->name, &el->values[v_idx], &a);
+ el->name, &el->values[v_idx], &a, &truncation);
if (!dn_key) {
talloc_free(list);
return LDB_ERR_OPERATIONS_ERROR;
}
+ /*
+ * Samba only maintains unique indexes on the objectSID and objectGUID
+ * so if a unique index key exceeds the maximum length there is a
+ * problem.
+ */
+ if ((truncation == KEY_TRUNCATED) && (a != NULL &&
+ (a->flags & LDB_ATTR_FLAG_UNIQUE_INDEX ||
+ (el->flags & LDB_FLAG_INTERNAL_FORCE_UNIQUE_INDEX)))) {
+
+ ldb_asprintf_errstring(
+ ldb,
+ __location__ ": unique index key on %s in %s, "
+ "exceeds maximum key length of %u (encoded).",
+ el->name,
+ ldb_dn_get_linearized(msg->dn),
+ ltdb->max_key_length);
+ talloc_free(list);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
talloc_steal(list, dn_key);
ret = ltdb_dn_list_load(module, ltdb, dn_key, list);
@@ -1850,13 +2130,81 @@ static int ltdb_index_add1(struct ldb_module *module,
* messages.
*/
if (list->count > 0 &&
- ldb_attr_cmp(el->name, LTDB_IDXDN) == 0) {
+ ldb_attr_cmp(el->name, LTDB_IDXDN) == 0 &&
+ truncation == KEY_NOT_TRUNCATED) {
+
talloc_free(list);
return LDB_ERR_CONSTRAINT_VIOLATION;
+
+ } else if (list->count > 0
+ && ldb_attr_cmp(el->name, LTDB_IDXDN) == 0) {
+
+ /*
+ * At least one existing entry in the DN->GUID index, which
+ * arises when the DN indexes have been truncated
+ *
+ * So need to pull the DN's to check if it's really a duplicate
+ */
+ int i;
+ for (i=0; i < list->count; i++) {
+ uint8_t guid_key[LTDB_GUID_KEY_SIZE];
+ TDB_DATA key = {
+ .dptr = guid_key,
+ .dsize = sizeof(guid_key)
+ };
+ const int flags = LDB_UNPACK_DATA_FLAG_NO_ATTRS;
+ struct ldb_message *rec = ldb_msg_new(ldb);
+ if (rec == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = ltdb_idx_to_key(module, ltdb,
+ ldb, &list->dn[i],
+ &key);
+ if (ret != LDB_SUCCESS) {
+ TALLOC_FREE(list);
+ TALLOC_FREE(rec);
+ return ret;
+ }
+
+ ret = ltdb_search_key(module, ltdb, key,
+ rec, flags);
+ if (key.dptr != guid_key) {
+ TALLOC_FREE(key.dptr);
+ }
+ if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+ /*
+ * the record has disappeared?
+ * yes, this can happen
+ */
+ talloc_free(rec);
+ continue;
+ }
+
+ if (ret != LDB_SUCCESS) {
+ /* an internal error */
+ TALLOC_FREE(rec);
+ TALLOC_FREE(list);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ /*
+ * The DN we are trying to add to the DB and index
+ * is already here, so we must deny the addition
+ */
+ if (ldb_dn_compare(msg->dn, rec->dn) == 0) {
+ TALLOC_FREE(rec);
+ TALLOC_FREE(list);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ }
}
/*
* Check for duplicates in unique indexes
+ *
+ * We don't need to do a loop test like the @IDXDN case
+ * above as we have a ban on long unique index values
+ * at the start of this function.
*/
if (list->count > 0 &&
((a != NULL
@@ -1953,7 +2301,7 @@ static int ltdb_index_add1(struct ldb_module *module,
* forcing in the value with
* LDB_FLAG_INTERNAL_DISABLE_SINGLE_VALUE_CHECK
*/
- if (exact != NULL) {
+ if (exact != NULL && truncation == KEY_NOT_TRUNCATED) {
/* This can't fail, gives a default at worst */
const struct ldb_schema_attribute *attr
= ldb_schema_attribute_by_name(
@@ -2253,6 +2601,7 @@ int ltdb_index_del_value(struct ldb_module *module,
unsigned int j;
struct dn_list *list;
struct ldb_dn *dn = msg->dn;
+ enum key_truncation truncation = KEY_NOT_TRUNCATED;
ldb = ldb_module_get_ctx(module);
@@ -2266,7 +2615,14 @@ int ltdb_index_del_value(struct ldb_module *module,
}
dn_key = ltdb_index_key(ldb, ltdb,
- el->name, &el->values[v_idx], NULL);
+ el->name, &el->values[v_idx],
+ NULL, &truncation);
+ /*
+ * We ignore key truncation in ltdb_index_add1() so
+ * match that by ignoring it here as well
+ *
+ * Multiple values are legitimate and accepted
+ */
if (!dn_key) {
return LDB_ERR_OPERATIONS_ERROR;
}
@@ -2290,6 +2646,9 @@ int ltdb_index_del_value(struct ldb_module *module,
return ret;
}
+ /*
+ * Find one of the values matching this message to remove
+ */
i = ltdb_dn_list_find_msg(ltdb, list, msg);
if (i == -1) {
/* nothing to delete */
@@ -2405,17 +2764,16 @@ int ltdb_index_delete(struct ldb_module *module, const struct ldb_message *msg)
commit, which in turn greatly reduces DB churn as we will likely
be able to do a direct update into the old record.
*/
-static int delete_index(struct tdb_context *tdb, TDB_DATA key, TDB_DATA data, void *state)
+static int delete_index(struct ltdb_private *ltdb, struct ldb_val key, struct ldb_val data, void *state)
{
struct ldb_module *module = state;
- struct ltdb_private *ltdb = talloc_get_type(ldb_module_get_private(module), struct ltdb_private);
const char *dnstr = "DN=" LTDB_INDEX ":";
struct dn_list list;
struct ldb_dn *dn;
struct ldb_val v;
int ret;
- if (strncmp((char *)key.dptr, dnstr, strlen(dnstr)) != 0) {
+ if (strncmp((char *)key.data, dnstr, strlen(dnstr)) != 0) {
return 0;
}
/* we need to put a empty list in the internal tdb for this
@@ -2424,8 +2782,8 @@ static int delete_index(struct tdb_context *tdb, TDB_DATA key, TDB_DATA data, vo
list.count = 0;
/* the offset of 3 is to remove the DN= prefix. */
- v.data = key.dptr + 3;
- v.length = strnlen((char *)key.dptr, key.dsize) - 3;
+ v.data = key.data + 3;
+ v.length = strnlen((char *)key.data, key.length) - 3;
dn = ldb_dn_from_ldb_val(ltdb, ldb_module_get_ctx(module), &v);
@@ -2445,29 +2803,23 @@ static int delete_index(struct tdb_context *tdb, TDB_DATA key, TDB_DATA data, vo
return 0;
}
-struct ltdb_reindex_context {
- struct ldb_module *module;
- int error;
- uint32_t count;
-};
-
/*
- traversal function that adds @INDEX records during a re index
+ traversal function that adds @INDEX records during a re index TODO wrong comment
*/
-static int re_key(struct tdb_context *tdb, TDB_DATA key, TDB_DATA data, void *state)
+static int re_key(struct ltdb_private *ltdb, struct ldb_val ldb_key, struct ldb_val val, void *state)
{
struct ldb_context *ldb;
struct ltdb_reindex_context *ctx = (struct ltdb_reindex_context *)state;
struct ldb_module *module = ctx->module;
struct ldb_message *msg;
unsigned int nb_elements_in_db;
- const struct ldb_val val = {
- .data = data.dptr,
- .length = data.dsize,
- };
int ret;
TDB_DATA key2;
bool is_record;
+ TDB_DATA key = {
+ .dptr = ldb_key.data,
+ .dsize = ldb_key.length
+ };
ldb = ldb_module_get_ctx(module);
@@ -2522,32 +2874,11 @@ static int re_key(struct tdb_context *tdb, TDB_DATA key, TDB_DATA data, void *st
}
if (key.dsize != key2.dsize ||
(memcmp(key.dptr, key2.dptr, key.dsize) != 0)) {
- int tdb_ret;
- tdb_ret = tdb_delete(tdb, key);
- if (tdb_ret != 0) {
- ldb_debug(ldb, LDB_DEBUG_ERROR,
- "Failed to delete %*.*s "
- "for rekey as %*.*s: %s",
- (int)key.dsize, (int)key.dsize,
- (const char *)key.dptr,
- (int)key2.dsize, (int)key2.dsize,
- (const char *)key.dptr,
- tdb_errorstr(tdb));
- ctx->error = ltdb_err_map(tdb_error(tdb));
- return -1;
- }
- tdb_ret = tdb_store(tdb, key2, data, 0);
- if (tdb_ret != 0) {
- ldb_debug(ldb, LDB_DEBUG_ERROR,
- "Failed to rekey %*.*s as %*.*s: %s",
- (int)key.dsize, (int)key.dsize,
- (const char *)key.dptr,
- (int)key2.dsize, (int)key2.dsize,
- (const char *)key.dptr,
- tdb_errorstr(tdb));
- ctx->error = ltdb_err_map(tdb_error(tdb));
- return -1;
- }
+ struct ldb_val ldb_key2 = {
+ .data = key2.dptr,
+ .length = key2.dsize
+ };
+ ltdb->kv_ops->update_in_iterate(ltdb, ldb_key, ldb_key2, val, ctx);
}
talloc_free(key2.dptr);
@@ -2566,18 +2897,16 @@ static int re_key(struct tdb_context *tdb, TDB_DATA key, TDB_DATA data, void *st
/*
traversal function that adds @INDEX records during a re index
*/
-static int re_index(struct tdb_context *tdb, TDB_DATA key, TDB_DATA data, void *state)
+static int re_index(struct ltdb_private *ltdb, struct ldb_val ldb_key, struct ldb_val val, void *state)
{
struct ldb_context *ldb;
struct ltdb_reindex_context *ctx = (struct ltdb_reindex_context *)state;
struct ldb_module *module = ctx->module;
- struct ltdb_private *ltdb = talloc_get_type(ldb_module_get_private(module),
- struct ltdb_private);
struct ldb_message *msg;
unsigned int nb_elements_in_db;
- const struct ldb_val val = {
- .data = data.dptr,
- .length = data.dsize,
+ TDB_DATA key = {
+ .dptr = ldb_key.data,
+ .dsize = ldb_key.length
};
int ret;
bool is_record;
@@ -2687,7 +3016,7 @@ int ltdb_reindex(struct ldb_module *module)
/* first traverse the database deleting any @INDEX records by
* putting NULL entries in the in-memory tdb
*/
- ret = tdb_traverse(ltdb->tdb, delete_index, module);
+ ret = ltdb->kv_ops->iterate(ltdb, delete_index, module);
if (ret < 0) {
struct ldb_context *ldb = ldb_module_get_ctx(module);
ldb_asprintf_errstring(ldb, "index deletion traverse failed: %s",
@@ -2699,8 +3028,7 @@ int ltdb_reindex(struct ldb_module *module)
ctx.error = 0;
ctx.count = 0;
- /* now traverse adding any indexes for normal LDB records */
- ret = tdb_traverse(ltdb->tdb, re_key, &ctx);
+ ret = ltdb->kv_ops->iterate(ltdb, re_key, &ctx);
if (ret < 0) {
struct ldb_context *ldb = ldb_module_get_ctx(module);
ldb_asprintf_errstring(ldb, "key correction traverse failed: %s",
@@ -2718,7 +3046,7 @@ int ltdb_reindex(struct ldb_module *module)
ctx.count = 0;
/* now traverse adding any indexes for normal LDB records */
- ret = tdb_traverse(ltdb->tdb, re_index, &ctx);
+ ret = ltdb->kv_ops->iterate(ltdb, re_index, &ctx);
if (ret < 0) {
struct ldb_context *ldb = ldb_module_get_ctx(module);
ldb_asprintf_errstring(ldb, "reindexing traverse failed: %s",
@@ -2736,7 +3064,7 @@ int ltdb_reindex(struct ldb_module *module)
ldb_debug(ldb_module_get_ctx(module),
LDB_DEBUG_WARNING, "Reindexing: re_index successful on %s, "
"final index write-out will be in transaction commit",
- tdb_name(ltdb->tdb));
+ ltdb->kv_ops->name(ltdb));
}
return LDB_SUCCESS;
}
diff --git a/ldb_tdb/ldb_search.c b/ldb_tdb/ldb_search.c
index 0af230f..cfc3714 100644
--- a/ldb_tdb/ldb_search.c
+++ b/ldb_tdb/ldb_search.c
@@ -180,17 +180,15 @@ struct ltdb_parse_data_unpack_ctx {
unsigned int unpack_flags;
};
-static int ltdb_parse_data_unpack(TDB_DATA key, TDB_DATA data,
+static int ltdb_parse_data_unpack(struct ldb_val key,
+ struct ldb_val data,
void *private_data)
{
struct ltdb_parse_data_unpack_ctx *ctx = private_data;
unsigned int nb_elements_in_db;
int ret;
struct ldb_context *ldb = ldb_module_get_ctx(ctx->module);
- struct ldb_val data_parse = {
- .data = data.dptr,
- .length = data.dsize
- };
+ struct ldb_val data_parse = data;
if (ctx->unpack_flags & LDB_UNPACK_DATA_FLAG_NO_DATA_ALLOC) {
/*
@@ -200,13 +198,13 @@ static int ltdb_parse_data_unpack(TDB_DATA key, TDB_DATA data,
* and the caller needs a stable result.
*/
data_parse.data = talloc_memdup(ctx->msg,
- data.dptr,
- data.dsize);
+ data.data,
+ data.length);
if (data_parse.data == NULL) {
ldb_debug(ldb, LDB_DEBUG_ERROR,
"Unable to allocate data(%d) for %*.*s\n",
- (int)data.dsize,
- (int)key.dsize, (int)key.dsize, key.dptr);
+ (int)data.length,
+ (int)key.length, (int)key.length, key.data);
return LDB_ERR_OPERATIONS_ERROR;
}
}
@@ -217,13 +215,13 @@ static int ltdb_parse_data_unpack(TDB_DATA key, TDB_DATA data,
ctx->unpack_flags,
&nb_elements_in_db);
if (ret == -1) {
- if (data_parse.data != data.dptr) {
+ if (data_parse.data != data.data) {
talloc_free(data_parse.data);
}
ldb_debug(ldb, LDB_DEBUG_ERROR, "Invalid data for index %*.*s\n",
- (int)key.dsize, (int)key.dsize, key.dptr);
- return LDB_ERR_OPERATIONS_ERROR;
+ (int)key.length, (int)key.length, key.data);
+ return LDB_ERR_OPERATIONS_ERROR;
}
return ret;
}
@@ -246,17 +244,21 @@ int ltdb_search_key(struct ldb_module *module, struct ltdb_private *ltdb,
.module = module,
.unpack_flags = unpack_flags
};
+ struct ldb_val ldb_key = {
+ .data = tdb_key.dptr,
+ .length = tdb_key.dsize
+ };
memset(msg, 0, sizeof(*msg));
msg->num_elements = 0;
msg->elements = NULL;
- ret = tdb_parse_record(ltdb->tdb, tdb_key,
- ltdb_parse_data_unpack, &ctx);
-
+ ret = ltdb->kv_ops->fetch_and_parse(ltdb, ldb_key,
+ ltdb_parse_data_unpack, &ctx);
+
if (ret == -1) {
- ret = ltdb_err_map(tdb_error(ltdb->tdb));
+ ret = ltdb->kv_ops->error(ltdb);
if (ret == LDB_SUCCESS) {
/*
* Just to be sure we don't turn errors
@@ -292,19 +294,9 @@ int ltdb_search_dn1(struct ldb_module *module, struct ldb_dn *dn, struct ldb_mes
};
TALLOC_CTX *tdb_key_ctx = NULL;
- if (ltdb->cache->GUID_index_attribute == NULL) {
- tdb_key_ctx = talloc_new(msg);
- if (!tdb_key_ctx) {
- return ldb_module_oom(module);
- }
+ if (ltdb->cache->GUID_index_attribute == NULL ||
+ ldb_dn_is_special(dn)) {
- /* form the key */
- tdb_key = ltdb_key_dn(module, tdb_key_ctx, dn);
- if (!tdb_key.dptr) {
- TALLOC_FREE(tdb_key_ctx);
- return LDB_ERR_OPERATIONS_ERROR;
- }
- } else if (ldb_dn_is_special(dn)) {
tdb_key_ctx = talloc_new(msg);
if (!tdb_key_ctx) {
return ldb_module_oom(module);
@@ -497,23 +489,23 @@ failed:
/*
search function for a non-indexed search
*/
-static int search_func(struct tdb_context *tdb, TDB_DATA key, TDB_DATA data, void *state)
+static int search_func(struct ltdb_private *ltdb, struct ldb_val key, struct ldb_val val, void *state)
{
struct ldb_context *ldb;
struct ltdb_context *ac;
struct ldb_message *msg, *filtered_msg;
- const struct ldb_val val = {
- .data = data.dptr,
- .length = data.dsize,
- };
int ret;
bool matched;
unsigned int nb_elements_in_db;
+ TDB_DATA tdb_key = {
+ .dptr = key.data,
+ .dsize = key.length
+ };
ac = talloc_get_type(state, struct ltdb_context);
ldb = ldb_module_get_ctx(ac->module);
- if (ltdb_key_is_record(key) == false) {
+ if (ltdb_key_is_record(tdb_key) == false) {
return 0;
}
@@ -538,7 +530,7 @@ static int search_func(struct tdb_context *tdb, TDB_DATA key, TDB_DATA data, voi
if (!msg->dn) {
msg->dn = ldb_dn_new(msg, ldb,
- (char *)key.dptr + 3);
+ (char *)key.data + 3);
if (msg->dn == NULL) {
talloc_free(msg);
ac->error = LDB_ERR_OPERATIONS_ERROR;
@@ -591,11 +583,7 @@ static int ltdb_search_full(struct ltdb_context *ctx)
int ret;
ctx->error = LDB_SUCCESS;
- if (ltdb->in_transaction != 0) {
- ret = tdb_traverse(ltdb->tdb, search_func, ctx);
- } else {
- ret = tdb_traverse_read(ltdb->tdb, search_func, ctx);
- }
+ ret = ltdb->kv_ops->iterate(ltdb, search_func, ctx);
if (ret < 0) {
return LDB_ERR_OPERATIONS_ERROR;
@@ -721,17 +709,17 @@ int ltdb_search(struct ltdb_context *ctx)
ldb_request_set_state(req, LDB_ASYNC_PENDING);
- if (ltdb_lock_read(module) != 0) {
+ if (ltdb->kv_ops->lock_read(module) != 0) {
return LDB_ERR_OPERATIONS_ERROR;
}
if (ltdb_cache_load(module) != 0) {
- ltdb_unlock_read(module);
+ ltdb->kv_ops->unlock_read(module);
return LDB_ERR_OPERATIONS_ERROR;
}
if (req->op.search.tree == NULL) {
- ltdb_unlock_read(module);
+ ltdb->kv_ops->unlock_read(module);
return LDB_ERR_OPERATIONS_ERROR;
}
@@ -778,7 +766,7 @@ int ltdb_search(struct ltdb_context *ctx)
*/
ret = ltdb_search_and_return_base(ltdb, ctx);
- ltdb_unlock_read(module);
+ ltdb->kv_ops->unlock_read(module);
return ret;
@@ -818,7 +806,7 @@ int ltdb_search(struct ltdb_context *ctx)
* callback error */
if ( ! ctx->request_terminated && ret != LDB_SUCCESS) {
/* Not indexed, so we need to do a full scan */
- if (ltdb->warn_unindexed) {
+ if (ltdb->warn_unindexed || ltdb->disable_full_db_scan) {
/* useful for debugging when slow performance
* is caused by unindexed searches */
char *expression = ldb_filter_from_tree(ctx, ctx->tree);
@@ -831,6 +819,7 @@ int ltdb_search(struct ltdb_context *ctx)
talloc_free(expression);
}
+
if (match_count != 0) {
/* the indexing code gave an error
* after having returned at least one
@@ -840,9 +829,17 @@ int ltdb_search(struct ltdb_context *ctx)
* full search or we may return
* duplicate entries
*/
- ltdb_unlock_read(module);
+ ltdb->kv_ops->unlock_read(module);
return LDB_ERR_OPERATIONS_ERROR;
}
+
+ if (ltdb->disable_full_db_scan) {
+ ldb_set_errstring(ldb,
+ "ldb FULL SEARCH disabled");
+ ltdb->kv_ops->unlock_read(module);
+ return LDB_ERR_INAPPROPRIATE_MATCHING;
+ }
+
ret = ltdb_search_full(ctx);
if (ret != LDB_SUCCESS) {
ldb_set_errstring(ldb, "Indexed and full searches both failed!\n");
@@ -850,7 +847,7 @@ int ltdb_search(struct ltdb_context *ctx)
}
}
- ltdb_unlock_read(module);
+ ltdb->kv_ops->unlock_read(module);
return ret;
}
diff --git a/ldb_tdb/ldb_tdb.c b/ldb_tdb/ldb_tdb.c
index a530a45..8581604 100644
--- a/ldb_tdb/ldb_tdb.c
+++ b/ldb_tdb/ldb_tdb.c
@@ -94,14 +94,25 @@ int ltdb_err_map(enum TDB_ERROR tdb_code)
/*
lock the database for read - use by ltdb_search and ltdb_sequence_number
*/
-int ltdb_lock_read(struct ldb_module *module)
+static int ltdb_lock_read(struct ldb_module *module)
{
void *data = ldb_module_get_private(module);
struct ltdb_private *ltdb = talloc_get_type(data, struct ltdb_private);
int tdb_ret = 0;
int ret;
+ pid_t pid = getpid();
+
+ if (ltdb->pid != pid) {
+ ldb_asprintf_errstring(
+ ldb_module_get_ctx(module),
+ __location__": Reusing ldb opend by pid %d in "
+ "process %d\n",
+ ltdb->pid,
+ pid);
+ return LDB_ERR_PROTOCOL_ERROR;
+ }
- if (ltdb->in_transaction == 0 &&
+ if (tdb_transaction_active(ltdb->tdb) == false &&
ltdb->read_lock_count == 0) {
tdb_ret = tdb_lockall_read(ltdb->tdb);
}
@@ -124,11 +135,22 @@ int ltdb_lock_read(struct ldb_module *module)
/*
unlock the database after a ltdb_lock_read()
*/
-int ltdb_unlock_read(struct ldb_module *module)
+static int ltdb_unlock_read(struct ldb_module *module)
{
void *data = ldb_module_get_private(module);
struct ltdb_private *ltdb = talloc_get_type(data, struct ltdb_private);
- if (ltdb->in_transaction == 0 && ltdb->read_lock_count == 1) {
+ pid_t pid = getpid();
+
+ if (ltdb->pid != pid) {
+ ldb_asprintf_errstring(
+ ldb_module_get_ctx(module),
+ __location__": Reusing ldb opend by pid %d in "
+ "process %d\n",
+ ltdb->pid,
+ pid);
+ return LDB_ERR_PROTOCOL_ERROR;
+ }
+ if (!tdb_transaction_active(ltdb->tdb) && ltdb->read_lock_count == 1) {
tdb_unlockall_read(ltdb->tdb);
ltdb->read_lock_count--;
return 0;
@@ -379,7 +401,7 @@ static int ltdb_modified(struct ldb_module *module, struct ldb_dn *dn)
/* only allow modifies inside a transaction, otherwise the
* ldb is unsafe */
- if (ltdb->in_transaction == 0) {
+ if (ltdb->kv_ops->transaction_active(ltdb) == false) {
ldb_set_errstring(ldb_module_get_ctx(module), "ltdb modify without transaction");
return LDB_ERR_OPERATIONS_ERROR;
}
@@ -391,7 +413,7 @@ static int ltdb_modified(struct ldb_module *module, struct ldb_dn *dn)
if (ltdb->warn_reindex) {
ldb_debug(ldb_module_get_ctx(module),
LDB_DEBUG_ERROR, "Reindexing %s due to modification on %s",
- tdb_name(ltdb->tdb), ldb_dn_get_linearized(dn));
+ ltdb->kv_ops->name(ltdb), ldb_dn_get_linearized(dn));
}
ret = ltdb_reindex(module);
}
@@ -417,6 +439,34 @@ static int ltdb_modified(struct ldb_module *module, struct ldb_dn *dn)
return ret;
}
+static int ltdb_tdb_store(struct ltdb_private *ltdb, struct ldb_val ldb_key,
+ struct ldb_val ldb_data, int flags)
+{
+ TDB_DATA key = {
+ .dptr = ldb_key.data,
+ .dsize = ldb_key.length
+ };
+ TDB_DATA data = {
+ .dptr = ldb_data.data,
+ .dsize = ldb_data.length
+ };
+ bool transaction_active = tdb_transaction_active(ltdb->tdb);
+ if (transaction_active == false){
+ return LDB_ERR_PROTOCOL_ERROR;
+ }
+ return tdb_store(ltdb->tdb, key, data, flags);
+}
+
+static int ltdb_error(struct ltdb_private *ltdb)
+{
+ return ltdb_err_map(tdb_error(ltdb->tdb));
+}
+
+static const char *ltdb_errorstr(struct ltdb_private *ltdb)
+{
+ return tdb_errorstr(ltdb->tdb);
+}
+
/*
store a record into the db
*/
@@ -424,7 +474,8 @@ int ltdb_store(struct ldb_module *module, const struct ldb_message *msg, int flg
{
void *data = ldb_module_get_private(module);
struct ltdb_private *ltdb = talloc_get_type(data, struct ltdb_private);
- TDB_DATA tdb_key, tdb_data;
+ TDB_DATA tdb_key;
+ struct ldb_val ldb_key;
struct ldb_val ldb_data;
int ret = LDB_SUCCESS;
TALLOC_CTX *tdb_key_ctx = talloc_new(module);
@@ -450,13 +501,13 @@ int ltdb_store(struct ldb_module *module, const struct ldb_message *msg, int flg
return LDB_ERR_OTHER;
}
- tdb_data.dptr = ldb_data.data;
- tdb_data.dsize = ldb_data.length;
+ ldb_key.data = tdb_key.dptr;
+ ldb_key.length = tdb_key.dsize;
- ret = tdb_store(ltdb->tdb, tdb_key, tdb_data, flgs);
+ ret = ltdb->kv_ops->store(ltdb, ldb_key, ldb_data, flgs);
if (ret != 0) {
bool is_special = ldb_dn_is_special(msg->dn);
- ret = ltdb_err_map(tdb_error(ltdb->tdb));
+ ret = ltdb->kv_ops->error(ltdb);
/*
* LDB_ERR_ENTRY_ALREADY_EXISTS means the DN, not
@@ -622,6 +673,16 @@ static int ltdb_add(struct ltdb_context *ctx)
struct ltdb_private *ltdb = talloc_get_type(data, struct ltdb_private);
int ret = LDB_SUCCESS;
+ if (ltdb->max_key_length != 0 &&
+ ltdb->cache->GUID_index_attribute == NULL &&
+ !ldb_dn_is_special(req->op.add.message->dn))
+ {
+ ldb_set_errstring(ldb_module_get_ctx(module),
+ "Must operate ldb_mdb in GUID "
+ "index mode, but " LTDB_IDXGUID " not set.");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
ret = ltdb_check_special_dn(module, req->op.add.message);
if (ret != LDB_SUCCESS) {
return ret;
@@ -639,6 +700,19 @@ static int ltdb_add(struct ltdb_context *ctx)
return ret;
}
+static int ltdb_tdb_delete(struct ltdb_private *ltdb, struct ldb_val ldb_key)
+{
+ TDB_DATA tdb_key = {
+ .dptr = ldb_key.data,
+ .dsize = ldb_key.length
+ };
+ bool transaction_active = tdb_transaction_active(ltdb->tdb);
+ if (transaction_active == false){
+ return LDB_ERR_PROTOCOL_ERROR;
+ }
+ return tdb_delete(ltdb->tdb, tdb_key);
+}
+
/*
delete a record from the database, not updating indexes (used for deleting
index records)
@@ -648,6 +722,7 @@ int ltdb_delete_noindex(struct ldb_module *module,
{
void *data = ldb_module_get_private(module);
struct ltdb_private *ltdb = talloc_get_type(data, struct ltdb_private);
+ struct ldb_val ldb_key;
TDB_DATA tdb_key;
int ret;
TALLOC_CTX *tdb_key_ctx = talloc_new(module);
@@ -666,11 +741,14 @@ int ltdb_delete_noindex(struct ldb_module *module,
return LDB_ERR_OTHER;
}
- ret = tdb_delete(ltdb->tdb, tdb_key);
+ ldb_key.data = tdb_key.dptr;
+ ldb_key.length = tdb_key.dsize;
+
+ ret = ltdb->kv_ops->delete(ltdb, ldb_key);
TALLOC_FREE(tdb_key_ctx);
if (ret != 0) {
- ret = ltdb_err_map(tdb_error(ltdb->tdb));
+ ret = ltdb->kv_ops->error(ltdb);
}
return ret;
@@ -910,7 +988,6 @@ static int msg_delete_element(struct ldb_module *module,
return LDB_ERR_NO_SUCH_ATTRIBUTE;
}
-
/*
modify a record - internal interface
@@ -1390,21 +1467,100 @@ static int ltdb_rename(struct ltdb_context *ctx)
return ret;
}
+static int ltdb_tdb_transaction_start(struct ltdb_private *ltdb)
+{
+ pid_t pid = getpid();
+
+ if (ltdb->pid != pid) {
+ ldb_asprintf_errstring(
+ ldb_module_get_ctx(ltdb->module),
+ __location__": Reusing ldb opend by pid %d in "
+ "process %d\n",
+ ltdb->pid,
+ pid);
+ return LDB_ERR_PROTOCOL_ERROR;
+ }
+
+ return tdb_transaction_start(ltdb->tdb);
+}
+
+static int ltdb_tdb_transaction_cancel(struct ltdb_private *ltdb)
+{
+ pid_t pid = getpid();
+
+ if (ltdb->pid != pid) {
+ ldb_asprintf_errstring(
+ ldb_module_get_ctx(ltdb->module),
+ __location__": Reusing ldb opend by pid %d in "
+ "process %d\n",
+ ltdb->pid,
+ pid);
+ return LDB_ERR_PROTOCOL_ERROR;
+ }
+
+ return tdb_transaction_cancel(ltdb->tdb);
+}
+
+static int ltdb_tdb_transaction_prepare_commit(struct ltdb_private *ltdb)
+{
+ pid_t pid = getpid();
+
+ if (ltdb->pid != pid) {
+ ldb_asprintf_errstring(
+ ldb_module_get_ctx(ltdb->module),
+ __location__": Reusing ldb opend by pid %d in "
+ "process %d\n",
+ ltdb->pid,
+ pid);
+ return LDB_ERR_PROTOCOL_ERROR;
+ }
+
+ return tdb_transaction_prepare_commit(ltdb->tdb);
+}
+
+static int ltdb_tdb_transaction_commit(struct ltdb_private *ltdb)
+{
+ pid_t pid = getpid();
+
+ if (ltdb->pid != pid) {
+ ldb_asprintf_errstring(
+ ldb_module_get_ctx(ltdb->module),
+ __location__": Reusing ldb opend by pid %d in "
+ "process %d\n",
+ ltdb->pid,
+ pid);
+ return LDB_ERR_PROTOCOL_ERROR;
+ }
+
+ return tdb_transaction_commit(ltdb->tdb);
+}
+
static int ltdb_start_trans(struct ldb_module *module)
{
void *data = ldb_module_get_private(module);
struct ltdb_private *ltdb = talloc_get_type(data, struct ltdb_private);
+ pid_t pid = getpid();
+
+ if (ltdb->pid != pid) {
+ ldb_asprintf_errstring(
+ ldb_module_get_ctx(ltdb->module),
+ __location__": Reusing ldb opend by pid %d in "
+ "process %d\n",
+ ltdb->pid,
+ pid);
+ return LDB_ERR_PROTOCOL_ERROR;
+ }
+
/* Do not take out the transaction lock on a read-only DB */
if (ltdb->read_only) {
return LDB_ERR_UNWILLING_TO_PERFORM;
}
- if (tdb_transaction_start(ltdb->tdb) != 0) {
- return ltdb_err_map(tdb_error(ltdb->tdb));
+ if (ltdb->kv_ops->begin_write(ltdb) != 0) {
+ return ltdb->kv_ops->error(ltdb);
}
- ltdb->in_transaction++;
ltdb_index_transaction_start(module);
@@ -1424,9 +1580,23 @@ static int ltdb_prepare_commit(struct ldb_module *module)
int ret;
void *data = ldb_module_get_private(module);
struct ltdb_private *ltdb = talloc_get_type(data, struct ltdb_private);
+ pid_t pid = getpid();
+
+ if (ltdb->pid != pid) {
+ ldb_asprintf_errstring(
+ ldb_module_get_ctx(module),
+ __location__": Reusing ldb opend by pid %d in "
+ "process %d\n",
+ ltdb->pid,
+ pid);
+ return LDB_ERR_PROTOCOL_ERROR;
+ }
- if (ltdb->in_transaction != 1) {
- return LDB_SUCCESS;
+ if (!ltdb->kv_ops->transaction_active(ltdb)) {
+ ldb_set_errstring(ldb_module_get_ctx(module),
+ "ltdb_prepare_commit() called "
+ "without transaction active");
+ return LDB_ERR_OPERATIONS_ERROR;
}
/*
@@ -1449,19 +1619,17 @@ static int ltdb_prepare_commit(struct ldb_module *module)
ret = ltdb_index_transaction_commit(module);
if (ret != LDB_SUCCESS) {
- tdb_transaction_cancel(ltdb->tdb);
- ltdb->in_transaction--;
+ ltdb->kv_ops->abort_write(ltdb);
return ret;
}
- if (tdb_transaction_prepare_commit(ltdb->tdb) != 0) {
- ret = ltdb_err_map(tdb_error(ltdb->tdb));
- ltdb->in_transaction--;
+ if (ltdb->kv_ops->prepare_write(ltdb) != 0) {
+ ret = ltdb->kv_ops->error(ltdb);
ldb_debug_set(ldb_module_get_ctx(module),
LDB_DEBUG_FATAL,
"Failure during "
- "tdb_transaction_prepare_commit(): %s -> %s",
- tdb_errorstr(ltdb->tdb),
+ "prepare_write): %s -> %s",
+ ltdb->kv_ops->errorstr(ltdb),
ldb_strerror(ret));
return ret;
}
@@ -1484,14 +1652,13 @@ static int ltdb_end_trans(struct ldb_module *module)
}
}
- ltdb->in_transaction--;
ltdb->prepared_commit = false;
- if (tdb_transaction_commit(ltdb->tdb) != 0) {
- ret = ltdb_err_map(tdb_error(ltdb->tdb));
+ if (ltdb->kv_ops->finish_write(ltdb) != 0) {
+ ret = ltdb->kv_ops->error(ltdb);
ldb_asprintf_errstring(ldb_module_get_ctx(module),
"Failure during tdb_transaction_commit(): %s -> %s",
- tdb_errorstr(ltdb->tdb),
+ ltdb->kv_ops->errorstr(ltdb),
ldb_strerror(ret));
return ret;
}
@@ -1504,14 +1671,13 @@ static int ltdb_del_trans(struct ldb_module *module)
void *data = ldb_module_get_private(module);
struct ltdb_private *ltdb = talloc_get_type(data, struct ltdb_private);
- ltdb->in_transaction--;
if (ltdb_index_transaction_cancel(module) != 0) {
- tdb_transaction_cancel(ltdb->tdb);
- return ltdb_err_map(tdb_error(ltdb->tdb));
+ ltdb->kv_ops->abort_write(ltdb);
+ return ltdb->kv_ops->error(ltdb);
}
- tdb_transaction_cancel(ltdb->tdb);
+ ltdb->kv_ops->abort_write(ltdb);
return LDB_SUCCESS;
}
@@ -1524,6 +1690,8 @@ static int ltdb_sequence_number(struct ltdb_context *ctx,
struct ldb_context *ldb;
struct ldb_module *module = ctx->module;
struct ldb_request *req = ctx->req;
+ void *data = ldb_module_get_private(module);
+ struct ltdb_private *ltdb = talloc_get_type(data, struct ltdb_private);
TALLOC_CTX *tmp_ctx = NULL;
struct ldb_seqnum_request *seq;
struct ldb_seqnum_result *res;
@@ -1542,7 +1710,7 @@ static int ltdb_sequence_number(struct ltdb_context *ctx,
ldb_request_set_state(req, LDB_ASYNC_PENDING);
- if (ltdb_lock_read(module) != 0) {
+ if (ltdb->kv_ops->lock_read(module) != 0) {
return LDB_ERR_OPERATIONS_ERROR;
}
@@ -1604,7 +1772,8 @@ static int ltdb_sequence_number(struct ltdb_context *ctx,
done:
talloc_free(tmp_ctx);
- ltdb_unlock_read(module);
+
+ ltdb->kv_ops->unlock_read(module);
return ret;
}
@@ -1701,6 +1870,181 @@ static void ltdb_handle_extended(struct ltdb_context *ctx)
ltdb_request_extended_done(ctx, ext, ret);
}
+struct kv_ctx {
+ ldb_kv_traverse_fn kv_traverse_fn;
+ void *ctx;
+ struct ltdb_private *ltdb;
+ int (*parser)(struct ldb_val key,
+ struct ldb_val data,
+ void *private_data);
+};
+
+static int ldb_tdb_traverse_fn_wrapper(struct tdb_context *tdb, TDB_DATA tdb_key, TDB_DATA tdb_data, void *ctx)
+{
+ struct kv_ctx *kv_ctx = ctx;
+ struct ldb_val key = {
+ .length = tdb_key.dsize,
+ .data = tdb_key.dptr,
+ };
+ struct ldb_val data = {
+ .length = tdb_data.dsize,
+ .data = tdb_data.dptr,
+ };
+ return kv_ctx->kv_traverse_fn(kv_ctx->ltdb, key, data, kv_ctx->ctx);
+}
+
+static int ltdb_tdb_traverse_fn(struct ltdb_private *ltdb, ldb_kv_traverse_fn fn, void *ctx)
+{
+ struct kv_ctx kv_ctx = {
+ .kv_traverse_fn = fn,
+ .ctx = ctx,
+ .ltdb = ltdb
+ };
+ if (tdb_transaction_active(ltdb->tdb)) {
+ return tdb_traverse(ltdb->tdb, ldb_tdb_traverse_fn_wrapper, &kv_ctx);
+ } else {
+ return tdb_traverse_read(ltdb->tdb, ldb_tdb_traverse_fn_wrapper, &kv_ctx);
+ }
+}
+
+static int ltdb_tdb_update_in_iterate(struct ltdb_private *ltdb,
+ struct ldb_val ldb_key,
+ struct ldb_val ldb_key2,
+ struct ldb_val ldb_data, void *state)
+{
+ int tdb_ret;
+ struct ldb_context *ldb;
+ struct ltdb_reindex_context *ctx = (struct ltdb_reindex_context *)state;
+ struct ldb_module *module = ctx->module;
+ TDB_DATA key = {
+ .dptr = ldb_key.data,
+ .dsize = ldb_key.length
+ };
+ TDB_DATA key2 = {
+ .dptr = ldb_key2.data,
+ .dsize = ldb_key2.length
+ };
+ TDB_DATA data = {
+ .dptr = ldb_data.data,
+ .dsize = ldb_data.length
+ };
+
+ ldb = ldb_module_get_ctx(module);
+
+ tdb_ret = tdb_delete(ltdb->tdb, key);
+ if (tdb_ret != 0) {
+ ldb_debug(ldb, LDB_DEBUG_ERROR,
+ "Failed to delete %*.*s "
+ "for rekey as %*.*s: %s",
+ (int)key.dsize, (int)key.dsize,
+ (const char *)key.dptr,
+ (int)key2.dsize, (int)key2.dsize,
+ (const char *)key.dptr,
+ tdb_errorstr(ltdb->tdb));
+ ctx->error = ltdb_err_map(tdb_error(ltdb->tdb));
+ return -1;
+ }
+ tdb_ret = tdb_store(ltdb->tdb, key2, data, 0);
+ if (tdb_ret != 0) {
+ ldb_debug(ldb, LDB_DEBUG_ERROR,
+ "Failed to rekey %*.*s as %*.*s: %s",
+ (int)key.dsize, (int)key.dsize,
+ (const char *)key.dptr,
+ (int)key2.dsize, (int)key2.dsize,
+ (const char *)key.dptr,
+ tdb_errorstr(ltdb->tdb));
+ ctx->error = ltdb_err_map(tdb_error(ltdb->tdb));
+ return -1;
+ }
+ return tdb_ret;
+}
+
+static int ltdb_tdb_parse_record_wrapper(TDB_DATA tdb_key, TDB_DATA tdb_data,
+ void *ctx)
+{
+ struct kv_ctx *kv_ctx = ctx;
+ struct ldb_val key = {
+ .length = tdb_key.dsize,
+ .data = tdb_key.dptr,
+ };
+ struct ldb_val data = {
+ .length = tdb_data.dsize,
+ .data = tdb_data.dptr,
+ };
+
+ return kv_ctx->parser(key, data, kv_ctx->ctx);
+}
+
+static int ltdb_tdb_parse_record(struct ltdb_private *ltdb,
+ struct ldb_val ldb_key,
+ int (*parser)(struct ldb_val key,
+ struct ldb_val data,
+ void *private_data),
+ void *ctx)
+{
+ struct kv_ctx kv_ctx = {
+ .parser = parser,
+ .ctx = ctx,
+ .ltdb = ltdb
+ };
+ TDB_DATA key = {
+ .dptr = ldb_key.data,
+ .dsize = ldb_key.length
+ };
+ int ret;
+
+ if (tdb_transaction_active(ltdb->tdb) == false &&
+ ltdb->read_lock_count == 0) {
+ return LDB_ERR_PROTOCOL_ERROR;
+ }
+
+ ret = tdb_parse_record(ltdb->tdb, key, ltdb_tdb_parse_record_wrapper,
+ &kv_ctx);
+ if (ret == 0) {
+ return LDB_SUCCESS;
+ }
+ return ltdb_err_map(tdb_error(ltdb->tdb));
+}
+
+static const char * ltdb_tdb_name(struct ltdb_private *ltdb)
+{
+ return tdb_name(ltdb->tdb);
+}
+
+static bool ltdb_tdb_changed(struct ltdb_private *ltdb)
+{
+ int seq = tdb_get_seqnum(ltdb->tdb);
+ bool has_changed = (seq != ltdb->tdb_seqnum);
+
+ ltdb->tdb_seqnum = seq;
+
+ return has_changed;
+}
+
+static bool ltdb_transaction_active(struct ltdb_private *ltdb)
+{
+ return tdb_transaction_active(ltdb->tdb);
+}
+
+static const struct kv_db_ops key_value_ops = {
+ .store = ltdb_tdb_store,
+ .delete = ltdb_tdb_delete,
+ .iterate = ltdb_tdb_traverse_fn,
+ .update_in_iterate = ltdb_tdb_update_in_iterate,
+ .fetch_and_parse = ltdb_tdb_parse_record,
+ .lock_read = ltdb_lock_read,
+ .unlock_read = ltdb_unlock_read,
+ .begin_write = ltdb_tdb_transaction_start,
+ .prepare_write = ltdb_tdb_transaction_prepare_commit,
+ .finish_write = ltdb_tdb_transaction_commit,
+ .abort_write = ltdb_tdb_transaction_cancel,
+ .error = ltdb_error,
+ .errorstr = ltdb_errorstr,
+ .name = ltdb_tdb_name,
+ .has_changed = ltdb_tdb_changed,
+ .transaction_active = ltdb_transaction_active,
+};
+
static void ltdb_callback(struct tevent_context *ev,
struct tevent_timer *te,
struct timeval t,
@@ -1849,6 +2193,21 @@ static int ltdb_init_rootdse(struct ldb_module *module)
return LDB_SUCCESS;
}
+
+static int generic_lock_read(struct ldb_module *module)
+{
+ void *data = ldb_module_get_private(module);
+ struct ltdb_private *ltdb = talloc_get_type(data, struct ltdb_private);
+ return ltdb->kv_ops->lock_read(module);
+}
+
+static int generic_unlock_read(struct ldb_module *module)
+{
+ void *data = ldb_module_get_private(module);
+ struct ltdb_private *ltdb = talloc_get_type(data, struct ltdb_private);
+ return ltdb->kv_ops->unlock_read(module);
+}
+
static const struct ldb_module_ops ltdb_ops = {
.name = "tdb",
.init_context = ltdb_init_rootdse,
@@ -1862,18 +2221,90 @@ static const struct ldb_module_ops ltdb_ops = {
.end_transaction = ltdb_end_trans,
.prepare_commit = ltdb_prepare_commit,
.del_transaction = ltdb_del_trans,
- .read_lock = ltdb_lock_read,
- .read_unlock = ltdb_unlock_read,
+ .read_lock = generic_lock_read,
+ .read_unlock = generic_unlock_read,
};
+int init_store(struct ltdb_private *ltdb,
+ const char *name,
+ struct ldb_context *ldb,
+ const char *options[],
+ struct ldb_module **_module)
+{
+ if (getenv("LDB_WARN_UNINDEXED")) {
+ ltdb->warn_unindexed = true;
+ }
+
+ if (getenv("LDB_WARN_REINDEX")) {
+ ltdb->warn_reindex = true;
+ }
+
+ ltdb->sequence_number = 0;
+
+ ltdb->pid = getpid();
+
+ ltdb->module = ldb_module_new(ldb, ldb, name, &ltdb_ops);
+ if (!ltdb->module) {
+ ldb_oom(ldb);
+ talloc_free(ltdb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ldb_module_set_private(ltdb->module, ltdb);
+ talloc_steal(ltdb->module, ltdb);
+
+ if (ltdb_cache_load(ltdb->module) != 0) {
+ ldb_asprintf_errstring(ldb, "Unable to load ltdb cache "
+ "records for backend '%s'", name);
+ talloc_free(ltdb->module);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ *_module = ltdb->module;
+ /*
+ * Set or override the maximum key length
+ *
+ * The ldb_mdb code will have set this to 511, but our tests
+ * set this even smaller (to make the tests more practical).
+ *
+ * This must only be used for the selftest as the length
+ * becomes encoded in the index keys.
+ */
+ {
+ const char *len_str =
+ ldb_options_find(ldb, options,
+ "max_key_len_for_self_test");
+ if (len_str != NULL) {
+ unsigned len = strtoul(len_str, NULL, 0);
+ ltdb->max_key_length = len;
+ }
+ }
+
+ /*
+ * Override full DB scans
+ *
+ * A full DB scan is expensive on a large database. This
+ * option is for testing to show that the full DB scan is not
+ * triggered.
+ */
+ {
+ const char *len_str =
+ ldb_options_find(ldb, options,
+ "disable_full_db_scan_for_self_test");
+ if (len_str != NULL) {
+ ltdb->disable_full_db_scan = true;
+ }
+ }
+
+ return LDB_SUCCESS;
+}
+
/*
connect to the database
*/
-static int ltdb_connect(struct ldb_context *ldb, const char *url,
- unsigned int flags, const char *options[],
- struct ldb_module **_module)
+int ltdb_connect(struct ldb_context *ldb, const char *url,
+ unsigned int flags, const char *options[],
+ struct ldb_module **_module)
{
- struct ldb_module *module;
const char *path;
int tdb_flags, open_flags;
struct ltdb_private *ltdb;
@@ -1882,7 +2313,6 @@ static int ltdb_connect(struct ldb_context *ldb, const char *url,
* We hold locks, so we must use a private event context
* on each returned handle
*/
-
ldb_set_require_private_event_context(ldb);
/* parse the url */
@@ -1897,7 +2327,7 @@ static int ltdb_connect(struct ldb_context *ldb, const char *url,
path = url;
}
- tdb_flags = TDB_DEFAULT | TDB_SEQNUM;
+ tdb_flags = TDB_DEFAULT | TDB_SEQNUM | TDB_DISALLOW_NESTING;
/* check for the 'nosync' option */
if (flags & LDB_FLG_NOSYNC) {
@@ -1939,6 +2369,9 @@ static int ltdb_connect(struct ldb_context *ldb, const char *url,
open_flags = O_CREAT | O_RDWR;
}
+ ltdb->kv_ops = &key_value_ops;
+
+ errno = 0;
/* note that we use quite a large default hash size */
ltdb->tdb = ltdb_wrap_open(ltdb, path, 10000,
tdb_flags, open_flags,
@@ -1955,38 +2388,5 @@ static int ltdb_connect(struct ldb_context *ldb, const char *url,
return LDB_ERR_OPERATIONS_ERROR;
}
- if (getenv("LDB_WARN_UNINDEXED")) {
- ltdb->warn_unindexed = true;
- }
-
- if (getenv("LDB_WARN_REINDEX")) {
- ltdb->warn_reindex = true;
- }
-
- ltdb->sequence_number = 0;
-
- module = ldb_module_new(ldb, ldb, "ldb_tdb backend", &ltdb_ops);
- if (!module) {
- ldb_oom(ldb);
- talloc_free(ltdb);
- return LDB_ERR_OPERATIONS_ERROR;
- }
- ldb_module_set_private(module, ltdb);
- talloc_steal(module, ltdb);
-
- if (ltdb_cache_load(module) != 0) {
- ldb_asprintf_errstring(ldb,
- "Unable to load ltdb cache records of tdb '%s'", path);
- talloc_free(module);
- return LDB_ERR_OPERATIONS_ERROR;
- }
-
- *_module = module;
- return LDB_SUCCESS;
-}
-
-int ldb_tdb_init(const char *version)
-{
- LDB_MODULE_CHECK_VERSION(version);
- return ldb_register_backend("tdb", ltdb_connect, false);
+ return init_store(ltdb, "ldb_tdb backend", ldb, options, _module);
}
diff --git a/ldb_tdb/ldb_tdb.h b/ldb_tdb/ldb_tdb.h
index 9591ee5..2896c63 100644
--- a/ldb_tdb/ldb_tdb.h
+++ b/ldb_tdb/ldb_tdb.h
@@ -4,10 +4,41 @@
#include "tdb.h"
#include "ldb_module.h"
+struct ltdb_private;
+typedef int (*ldb_kv_traverse_fn)(struct ltdb_private *ltdb,
+ struct ldb_val key, struct ldb_val data,
+ void *ctx);
+
+struct kv_db_ops {
+ int (*store)(struct ltdb_private *ltdb, struct ldb_val key, struct ldb_val data, int flags);
+ int (*delete)(struct ltdb_private *ltdb, struct ldb_val key);
+ int (*iterate)(struct ltdb_private *ltdb, ldb_kv_traverse_fn fn, void *ctx);
+ int (*update_in_iterate)(struct ltdb_private *ltdb, struct ldb_val key,
+ struct ldb_val key2, struct ldb_val data, void *ctx);
+ int (*fetch_and_parse)(struct ltdb_private *ltdb, struct ldb_val key,
+ int (*parser)(struct ldb_val key, struct ldb_val data,
+ void *private_data),
+ void *ctx);
+ int (*lock_read)(struct ldb_module *);
+ int (*unlock_read)(struct ldb_module *);
+ int (*begin_write)(struct ltdb_private *);
+ int (*prepare_write)(struct ltdb_private *);
+ int (*abort_write)(struct ltdb_private *);
+ int (*finish_write)(struct ltdb_private *);
+ int (*error)(struct ltdb_private *ltdb);
+ const char * (*errorstr)(struct ltdb_private *ltdb);
+ const char * (*name)(struct ltdb_private *ltdb);
+ bool (*has_changed)(struct ltdb_private *ltdb);
+ bool (*transaction_active)(struct ltdb_private *ltdb);
+};
+
/* this private structure is used by the ltdb backend in the
ldb_context */
struct ltdb_private {
+ const struct kv_db_ops *kv_ops;
+ struct ldb_module *module;
TDB_CONTEXT *tdb;
+ struct lmdb_private *lmdb_private;
unsigned int connect_flags;
unsigned long long sequence_number;
@@ -24,7 +55,6 @@ struct ltdb_private {
const char *GUID_index_dn_component;
} *cache;
- int in_transaction;
bool check_base;
bool disallow_dn_filter;
@@ -40,6 +70,25 @@ struct ltdb_private {
bool reindex_failed;
const struct ldb_schema_syntax *GUID_index_syntax;
+
+ /*
+ * Maximum index key length. If non zero keys longer than this length
+ * will be truncated for non unique indexes. Keys for unique indexes
+ * greater than this length will be rejected.
+ */
+ unsigned max_key_length;
+
+ /*
+ * To allow testing that ensures the DB does not fall back
+ * to a full scan
+ */
+ bool disable_full_db_scan;
+
+ /*
+ * The PID that opened this database so we don't work in a
+ * fork()ed child.
+ */
+ pid_t pid;
};
struct ltdb_context {
@@ -60,6 +109,13 @@ struct ltdb_context {
int error;
};
+struct ltdb_reindex_context {
+ struct ldb_module *module;
+ int error;
+ uint32_t count;
+};
+
+
/* special record types */
#define LTDB_INDEX "@INDEX"
#define LTDB_INDEXLIST "@INDEXLIST"
@@ -70,6 +126,15 @@ struct ltdb_context {
#define LTDB_IDXDN "@IDXDN"
#define LTDB_IDXGUID "@IDXGUID"
#define LTDB_IDX_DN_GUID "@IDX_DN_GUID"
+
+/*
+ * This will be used to indicate when a new, yet to be developed
+ * sub-database version of the indicies are in use, to ensure we do
+ * not load future databases unintentionally.
+ */
+
+#define LTDB_IDX_LMDB_SUBDB "@IDX_LMDB_SUBDB"
+
#define LTDB_BASEINFO "@BASEINFO"
#define LTDB_OPTIONS "@OPTIONS"
#define LTDB_ATTRIBUTES "@ATTRIBUTES"
@@ -145,8 +210,6 @@ int ltdb_filter_attrs(TALLOC_CTX *mem_ctx,
int ltdb_search(struct ltdb_context *ctx);
/* The following definitions come from lib/ldb/ldb_tdb/ldb_tdb.c */
-int ltdb_lock_read(struct ldb_module *module);
-int ltdb_unlock_read(struct ldb_module *module);
/*
* Determine if this key could hold a record. We allow the new GUID
* index, the old DN index and a possible future ID=
@@ -165,6 +228,7 @@ int ltdb_idx_to_key(struct ldb_module *module,
TALLOC_CTX *mem_ctx,
const struct ldb_val *idx_val,
TDB_DATA *key);
+TDB_DATA ltdb_key(struct ldb_module *module, struct ldb_dn *dn);
int ltdb_store(struct ldb_module *module, const struct ldb_message *msg, int flgs);
int ltdb_modify_internal(struct ldb_module *module, const struct ldb_message *msg, struct ldb_request *req);
int ltdb_delete_noindex(struct ldb_module *module,
@@ -175,3 +239,10 @@ struct tdb_context *ltdb_wrap_open(TALLOC_CTX *mem_ctx,
const char *path, int hash_size, int tdb_flags,
int open_flags, mode_t mode,
struct ldb_context *ldb);
+int init_store(struct ltdb_private *ltdb, const char *name,
+ struct ldb_context *ldb, const char *options[],
+ struct ldb_module **_module);
+
+int ltdb_connect(struct ldb_context *ldb, const char *url,
+ unsigned int flags, const char *options[],
+ struct ldb_module **_module);
diff --git a/ldb_tdb/ldb_tdb_init.c b/ldb_tdb/ldb_tdb_init.c
new file mode 100644
index 0000000..b18c98a
--- /dev/null
+++ b/ldb_tdb/ldb_tdb_init.c
@@ -0,0 +1,59 @@
+/*
+ ldb database library
+
+ Copyright (C) Andrew Tridgell 2004
+ Copyright (C) Stefan Metzmacher 2004
+ Copyright (C) Simo Sorce 2006-2008
+ Copyright (C) Matthias Dieter Wallnöfer 2009-2010
+
+ ** NOTE! The following LGPL license applies to the ldb
+ ** library. This does NOT imply that all of Samba is released
+ ** under the LGPL
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ * Name: ldb_tdb
+ *
+ * Component: ldb tdb backend
+ *
+ * Description: core functions for tdb backend
+ *
+ * Author: Andrew Tridgell
+ * Author: Stefan Metzmacher
+ *
+ * Modifications:
+ *
+ * - description: make the module use asynchronous calls
+ * date: Feb 2006
+ * Author: Simo Sorce
+ *
+ * - description: make it possible to use event contexts
+ * date: Jan 2008
+ * Author: Simo Sorce
+ *
+ * - description: fix up memory leaks and small bugs
+ * date: Oct 2009
+ * Author: Matthias Dieter Wallnöfer
+ */
+
+#include "ldb_tdb.h"
+#include "ldb_private.h"
+
+int ldb_tdb_init(const char *version)
+{
+ LDB_MODULE_CHECK_VERSION(version);
+ return ldb_register_backend("tdb", ltdb_connect, false);
+}
diff --git a/ldb_tdb/ldb_tdb_wrap.c b/ldb_tdb/ldb_tdb_wrap.c
index eb16809..bc702a2 100644
--- a/ldb_tdb/ldb_tdb_wrap.c
+++ b/ldb_tdb/ldb_tdb_wrap.c
@@ -74,6 +74,7 @@ struct ltdb_wrap {
struct tdb_context *tdb;
dev_t device;
ino_t inode;
+ pid_t pid;
};
static struct ltdb_wrap *tdb_list;
@@ -105,9 +106,25 @@ struct tdb_context *ltdb_wrap_open(TALLOC_CTX *mem_ctx,
if (stat(path, &st) == 0) {
for (w=tdb_list;w;w=w->next) {
if (st.st_dev == w->device && st.st_ino == w->inode) {
+ pid_t pid = getpid();
+ int ret;
if (!talloc_reference(mem_ctx, w)) {
return NULL;
}
+ if (w->pid != pid) {
+ ret = tdb_reopen(w->tdb);
+ if (ret != 0) {
+ /*
+ * Avoid use-after-free:
+ * on fail the TDB
+ * is closed!
+ */
+ DLIST_REMOVE(tdb_list,
+ w);
+ return NULL;
+ }
+ w->pid = pid;
+ }
return w->tdb;
}
}
@@ -135,6 +152,7 @@ struct tdb_context *ltdb_wrap_open(TALLOC_CTX *mem_ctx,
w->device = st.st_dev;
w->inode = st.st_ino;
+ w->pid = getpid();
talloc_set_destructor(w, ltdb_wrap_destructor);
diff --git a/lib/replace/replace.h b/lib/replace/replace.h
index 3304cda..626d305 100644
--- a/lib/replace/replace.h
+++ b/lib/replace/replace.h
@@ -924,6 +924,15 @@ void rep_setproctitle(const char *fmt, ...) PRINTF_ATTRIBUTE(1, 2);
#define setproctitle_init rep_setproctitle_init
void rep_setproctitle_init(int argc, char *argv[], char *envp[]);
#endif
+
+#ifndef FALL_THROUGH
+# ifdef HAVE_FALLTHROUGH_ATTRIBUTE
+# define FALL_THROUGH __attribute__ ((fallthrough))
+# else /* HAVE_FALLTHROUGH_ATTRIBUTE */
+# define FALL_THROUGH ((void)0)
+# endif /* HAVE_FALLTHROUGH_ATTRIBUTE */
+#endif /* FALL_THROUGH */
+
bool nss_wrapper_enabled(void);
bool nss_wrapper_hosts_enabled(void);
bool socket_wrapper_enabled(void);
diff --git a/lib/replace/strptime.c b/lib/replace/strptime.c
index 20e5d8c..bbc7422 100644
--- a/lib/replace/strptime.c
+++ b/lib/replace/strptime.c
@@ -462,7 +462,8 @@ strptime_internal (rp, fmt, tm, decided, era_cnt)
*decided = raw;
}
#endif
- /* Fall through. */
+
+ FALL_THROUGH;
case 'D':
/* Match standard day format. */
if (!recursive (HERE_D_FMT))
@@ -611,7 +612,8 @@ strptime_internal (rp, fmt, tm, decided, era_cnt)
*decided = raw;
}
#endif
- /* Fall through. */
+
+ FALL_THROUGH;
case 'T':
if (!recursive (HERE_T_FMT))
return NULL;
diff --git a/lib/replace/system/wscript_configure b/lib/replace/system/wscript_configure
index 2035474..ecd9964 100644
--- a/lib/replace/system/wscript_configure
+++ b/lib/replace/system/wscript_configure
@@ -1,8 +1,5 @@
#!/usr/bin/env python
-conf.CHECK_HEADERS('sys/capability.h')
-conf.CHECK_FUNCS('getpwnam_r getpwuid_r getpwent_r')
-
# solaris varients of getXXent_r
conf.CHECK_C_PROTOTYPE('getpwent_r',
'struct passwd *getpwent_r(struct passwd *src, char *buf, int buflen)',
@@ -19,8 +16,3 @@ conf.CHECK_C_PROTOTYPE('getgrent_r',
'struct group *getgrent_r(struct group *src, char *buf, size_t buflen)',
define='SOLARIS_GETGRENT_R', headers='grp.h')
-conf.CHECK_FUNCS('getgrouplist')
-conf.CHECK_HEADERS('ctype.h locale.h langinfo.h')
-conf.CHECK_HEADERS('fnmatch.h locale.h langinfo.h')
-conf.CHECK_HEADERS('sys/ipc.h sys/mman.h sys/shm.h')
-conf.CHECK_HEADERS('termios.h termio.h sys/termio.h')
diff --git a/lib/replace/wscript b/lib/replace/wscript
index 0e04bf7..541573b 100644
--- a/lib/replace/wscript
+++ b/lib/replace/wscript
@@ -90,8 +90,7 @@ def configure(conf):
conf.CHECK_HEADERS('rpc/rpc.h rpc/nettype.h', lib='tirpc', together=True)
conf.SET_TARGET_TYPE('tirpc', 'SYSLIB')
if not conf.CONFIG_SET('HAVE_RPC_RPC_H'):
- Logs.error('ERROR: No rpc/rpc.h header found, tirpc or libntirpc missing?')
- sys.exit(1)
+ Logs.warn('No rpc/rpc.h header found, tirpc or libntirpc missing?')
conf.SET_TARGET_TYPE('nsl', 'EMPTY')
conf.CHECK_HEADERS('rpc/rpc.h rpcsvc/yp_prot.h', lib='tirpc')
@@ -123,6 +122,9 @@ def configure(conf):
if conf.CHECK_CFLAGS('-Wno-unused-function'):
conf.define('HAVE_WNO_UNUSED_FUNCTION', '1')
+ if conf.CHECK_CFLAGS('-Wno-strict-overflow'):
+ conf.define('HAVE_WNO_STRICT_OVERFLOW', '1')
+
# Check for process set name support
conf.CHECK_CODE('''
#include <sys/prctl.h>
@@ -258,6 +260,42 @@ def configure(conf):
headers='stdint.h sys/atomic.h',
msg='Checking for atomic_add_32 compiler builtin')
+ conf.CHECK_CODE('''
+ #define FALL_THROUGH __attribute__((fallthrough))
+
+ enum direction_e {
+ UP = 0,
+ DOWN,
+ };
+
+ int main(void) {
+ enum direction_e key = UP;
+ int i = 10;
+ int j = 0;
+
+ switch (key) {
+ case UP:
+ i = 5;
+ FALL_THROUGH;
+ case DOWN:
+ j = i * 2;
+ break;
+ default:
+ break;
+ }
+
+ if (j < i) {
+ return 1;
+ }
+
+ return 0;
+ }
+ ''',
+ 'HAVE_FALLTHROUGH_ATTRIBUTE',
+ addmain=False,
+ cflags='-Werror',
+ msg='Checking for fallthrough attribute')
+
# these may be builtins, so we need the link=False strategy
conf.CHECK_FUNCS('strdup memmem printf memset memcpy memmove strcpy strncpy bzero', link=False)
diff --git a/lib/talloc/ABI/pytalloc-util-2.1.12.sigs b/lib/talloc/ABI/pytalloc-util-2.1.12.sigs
new file mode 100644
index 0000000..9d4d4d1
--- /dev/null
+++ b/lib/talloc/ABI/pytalloc-util-2.1.12.sigs
@@ -0,0 +1,16 @@
+_pytalloc_check_type: int (PyObject *, const char *)
+_pytalloc_get_mem_ctx: TALLOC_CTX *(PyObject *)
+_pytalloc_get_ptr: void *(PyObject *)
+_pytalloc_get_type: void *(PyObject *, const char *)
+pytalloc_BaseObject_PyType_Ready: int (PyTypeObject *)
+pytalloc_BaseObject_check: int (PyObject *)
+pytalloc_BaseObject_size: size_t (void)
+pytalloc_CObject_FromTallocPtr: PyObject *(void *)
+pytalloc_Check: int (PyObject *)
+pytalloc_GenericObject_reference_ex: PyObject *(TALLOC_CTX *, void *)
+pytalloc_GenericObject_steal_ex: PyObject *(TALLOC_CTX *, void *)
+pytalloc_GetBaseObjectType: PyTypeObject *(void)
+pytalloc_GetObjectType: PyTypeObject *(void)
+pytalloc_reference_ex: PyObject *(PyTypeObject *, TALLOC_CTX *, void *)
+pytalloc_steal: PyObject *(PyTypeObject *, void *)
+pytalloc_steal_ex: PyObject *(PyTypeObject *, TALLOC_CTX *, void *)
diff --git a/lib/talloc/ABI/pytalloc-util-2.1.13.sigs b/lib/talloc/ABI/pytalloc-util-2.1.13.sigs
new file mode 100644
index 0000000..9d4d4d1
--- /dev/null
+++ b/lib/talloc/ABI/pytalloc-util-2.1.13.sigs
@@ -0,0 +1,16 @@
+_pytalloc_check_type: int (PyObject *, const char *)
+_pytalloc_get_mem_ctx: TALLOC_CTX *(PyObject *)
+_pytalloc_get_ptr: void *(PyObject *)
+_pytalloc_get_type: void *(PyObject *, const char *)
+pytalloc_BaseObject_PyType_Ready: int (PyTypeObject *)
+pytalloc_BaseObject_check: int (PyObject *)
+pytalloc_BaseObject_size: size_t (void)
+pytalloc_CObject_FromTallocPtr: PyObject *(void *)
+pytalloc_Check: int (PyObject *)
+pytalloc_GenericObject_reference_ex: PyObject *(TALLOC_CTX *, void *)
+pytalloc_GenericObject_steal_ex: PyObject *(TALLOC_CTX *, void *)
+pytalloc_GetBaseObjectType: PyTypeObject *(void)
+pytalloc_GetObjectType: PyTypeObject *(void)
+pytalloc_reference_ex: PyObject *(PyTypeObject *, TALLOC_CTX *, void *)
+pytalloc_steal: PyObject *(PyTypeObject *, void *)
+pytalloc_steal_ex: PyObject *(PyTypeObject *, TALLOC_CTX *, void *)
diff --git a/lib/talloc/ABI/pytalloc-util.py3-2.1.12.sigs b/lib/talloc/ABI/pytalloc-util.py3-2.1.12.sigs
new file mode 100644
index 0000000..62f066f
--- /dev/null
+++ b/lib/talloc/ABI/pytalloc-util.py3-2.1.12.sigs
@@ -0,0 +1,15 @@
+_pytalloc_check_type: int (PyObject *, const char *)
+_pytalloc_get_mem_ctx: TALLOC_CTX *(PyObject *)
+_pytalloc_get_ptr: void *(PyObject *)
+_pytalloc_get_type: void *(PyObject *, const char *)
+pytalloc_BaseObject_PyType_Ready: int (PyTypeObject *)
+pytalloc_BaseObject_check: int (PyObject *)
+pytalloc_BaseObject_size: size_t (void)
+pytalloc_Check: int (PyObject *)
+pytalloc_GenericObject_reference_ex: PyObject *(TALLOC_CTX *, void *)
+pytalloc_GenericObject_steal_ex: PyObject *(TALLOC_CTX *, void *)
+pytalloc_GetBaseObjectType: PyTypeObject *(void)
+pytalloc_GetObjectType: PyTypeObject *(void)
+pytalloc_reference_ex: PyObject *(PyTypeObject *, TALLOC_CTX *, void *)
+pytalloc_steal: PyObject *(PyTypeObject *, void *)
+pytalloc_steal_ex: PyObject *(PyTypeObject *, TALLOC_CTX *, void *)
diff --git a/lib/talloc/ABI/pytalloc-util.py3-2.1.13.sigs b/lib/talloc/ABI/pytalloc-util.py3-2.1.13.sigs
new file mode 100644
index 0000000..62f066f
--- /dev/null
+++ b/lib/talloc/ABI/pytalloc-util.py3-2.1.13.sigs
@@ -0,0 +1,15 @@
+_pytalloc_check_type: int (PyObject *, const char *)
+_pytalloc_get_mem_ctx: TALLOC_CTX *(PyObject *)
+_pytalloc_get_ptr: void *(PyObject *)
+_pytalloc_get_type: void *(PyObject *, const char *)
+pytalloc_BaseObject_PyType_Ready: int (PyTypeObject *)
+pytalloc_BaseObject_check: int (PyObject *)
+pytalloc_BaseObject_size: size_t (void)
+pytalloc_Check: int (PyObject *)
+pytalloc_GenericObject_reference_ex: PyObject *(TALLOC_CTX *, void *)
+pytalloc_GenericObject_steal_ex: PyObject *(TALLOC_CTX *, void *)
+pytalloc_GetBaseObjectType: PyTypeObject *(void)
+pytalloc_GetObjectType: PyTypeObject *(void)
+pytalloc_reference_ex: PyObject *(PyTypeObject *, TALLOC_CTX *, void *)
+pytalloc_steal: PyObject *(PyTypeObject *, void *)
+pytalloc_steal_ex: PyObject *(PyTypeObject *, TALLOC_CTX *, void *)
diff --git a/lib/talloc/ABI/talloc-2.1.12.sigs b/lib/talloc/ABI/talloc-2.1.12.sigs
new file mode 100644
index 0000000..9969ce3
--- /dev/null
+++ b/lib/talloc/ABI/talloc-2.1.12.sigs
@@ -0,0 +1,65 @@
+_talloc: void *(const void *, size_t)
+_talloc_array: void *(const void *, size_t, unsigned int, const char *)
+_talloc_free: int (void *, const char *)
+_talloc_get_type_abort: void *(const void *, const char *, const char *)
+_talloc_memdup: void *(const void *, const void *, size_t, const char *)
+_talloc_move: void *(const void *, const void *)
+_talloc_pooled_object: void *(const void *, size_t, const char *, unsigned int, size_t)
+_talloc_realloc: void *(const void *, void *, size_t, const char *)
+_talloc_realloc_array: void *(const void *, void *, size_t, unsigned int, const char *)
+_talloc_reference_loc: void *(const void *, const void *, const char *)
+_talloc_set_destructor: void (const void *, int (*)(void *))
+_talloc_steal_loc: void *(const void *, const void *, const char *)
+_talloc_zero: void *(const void *, size_t, const char *)
+_talloc_zero_array: void *(const void *, size_t, unsigned int, const char *)
+talloc_asprintf: char *(const void *, const char *, ...)
+talloc_asprintf_append: char *(char *, const char *, ...)
+talloc_asprintf_append_buffer: char *(char *, const char *, ...)
+talloc_autofree_context: void *(void)
+talloc_check_name: void *(const void *, const char *)
+talloc_disable_null_tracking: void (void)
+talloc_enable_leak_report: void (void)
+talloc_enable_leak_report_full: void (void)
+talloc_enable_null_tracking: void (void)
+talloc_enable_null_tracking_no_autofree: void (void)
+talloc_find_parent_byname: void *(const void *, const char *)
+talloc_free_children: void (void *)
+talloc_get_name: const char *(const void *)
+talloc_get_size: size_t (const void *)
+talloc_increase_ref_count: int (const void *)
+talloc_init: void *(const char *, ...)
+talloc_is_parent: int (const void *, const void *)
+talloc_named: void *(const void *, size_t, const char *, ...)
+talloc_named_const: void *(const void *, size_t, const char *)
+talloc_parent: void *(const void *)
+talloc_parent_name: const char *(const void *)
+talloc_pool: void *(const void *, size_t)
+talloc_realloc_fn: void *(const void *, void *, size_t)
+talloc_reference_count: size_t (const void *)
+talloc_reparent: void *(const void *, const void *, const void *)
+talloc_report: void (const void *, FILE *)
+talloc_report_depth_cb: void (const void *, int, int, void (*)(const void *, int, int, int, void *), void *)
+talloc_report_depth_file: void (const void *, int, int, FILE *)
+talloc_report_full: void (const void *, FILE *)
+talloc_set_abort_fn: void (void (*)(const char *))
+talloc_set_log_fn: void (void (*)(const char *))
+talloc_set_log_stderr: void (void)
+talloc_set_memlimit: int (const void *, size_t)
+talloc_set_name: const char *(const void *, const char *, ...)
+talloc_set_name_const: void (const void *, const char *)
+talloc_show_parents: void (const void *, FILE *)
+talloc_strdup: char *(const void *, const char *)
+talloc_strdup_append: char *(char *, const char *)
+talloc_strdup_append_buffer: char *(char *, const char *)
+talloc_strndup: char *(const void *, const char *, size_t)
+talloc_strndup_append: char *(char *, const char *, size_t)
+talloc_strndup_append_buffer: char *(char *, const char *, size_t)
+talloc_test_get_magic: int (void)
+talloc_total_blocks: size_t (const void *)
+talloc_total_size: size_t (const void *)
+talloc_unlink: int (const void *, void *)
+talloc_vasprintf: char *(const void *, const char *, va_list)
+talloc_vasprintf_append: char *(char *, const char *, va_list)
+talloc_vasprintf_append_buffer: char *(char *, const char *, va_list)
+talloc_version_major: int (void)
+talloc_version_minor: int (void)
diff --git a/lib/talloc/ABI/talloc-2.1.13.sigs b/lib/talloc/ABI/talloc-2.1.13.sigs
new file mode 100644
index 0000000..9969ce3
--- /dev/null
+++ b/lib/talloc/ABI/talloc-2.1.13.sigs
@@ -0,0 +1,65 @@
+_talloc: void *(const void *, size_t)
+_talloc_array: void *(const void *, size_t, unsigned int, const char *)
+_talloc_free: int (void *, const char *)
+_talloc_get_type_abort: void *(const void *, const char *, const char *)
+_talloc_memdup: void *(const void *, const void *, size_t, const char *)
+_talloc_move: void *(const void *, const void *)
+_talloc_pooled_object: void *(const void *, size_t, const char *, unsigned int, size_t)
+_talloc_realloc: void *(const void *, void *, size_t, const char *)
+_talloc_realloc_array: void *(const void *, void *, size_t, unsigned int, const char *)
+_talloc_reference_loc: void *(const void *, const void *, const char *)
+_talloc_set_destructor: void (const void *, int (*)(void *))
+_talloc_steal_loc: void *(const void *, const void *, const char *)
+_talloc_zero: void *(const void *, size_t, const char *)
+_talloc_zero_array: void *(const void *, size_t, unsigned int, const char *)
+talloc_asprintf: char *(const void *, const char *, ...)
+talloc_asprintf_append: char *(char *, const char *, ...)
+talloc_asprintf_append_buffer: char *(char *, const char *, ...)
+talloc_autofree_context: void *(void)
+talloc_check_name: void *(const void *, const char *)
+talloc_disable_null_tracking: void (void)
+talloc_enable_leak_report: void (void)
+talloc_enable_leak_report_full: void (void)
+talloc_enable_null_tracking: void (void)
+talloc_enable_null_tracking_no_autofree: void (void)
+talloc_find_parent_byname: void *(const void *, const char *)
+talloc_free_children: void (void *)
+talloc_get_name: const char *(const void *)
+talloc_get_size: size_t (const void *)
+talloc_increase_ref_count: int (const void *)
+talloc_init: void *(const char *, ...)
+talloc_is_parent: int (const void *, const void *)
+talloc_named: void *(const void *, size_t, const char *, ...)
+talloc_named_const: void *(const void *, size_t, const char *)
+talloc_parent: void *(const void *)
+talloc_parent_name: const char *(const void *)
+talloc_pool: void *(const void *, size_t)
+talloc_realloc_fn: void *(const void *, void *, size_t)
+talloc_reference_count: size_t (const void *)
+talloc_reparent: void *(const void *, const void *, const void *)
+talloc_report: void (const void *, FILE *)
+talloc_report_depth_cb: void (const void *, int, int, void (*)(const void *, int, int, int, void *), void *)
+talloc_report_depth_file: void (const void *, int, int, FILE *)
+talloc_report_full: void (const void *, FILE *)
+talloc_set_abort_fn: void (void (*)(const char *))
+talloc_set_log_fn: void (void (*)(const char *))
+talloc_set_log_stderr: void (void)
+talloc_set_memlimit: int (const void *, size_t)
+talloc_set_name: const char *(const void *, const char *, ...)
+talloc_set_name_const: void (const void *, const char *)
+talloc_show_parents: void (const void *, FILE *)
+talloc_strdup: char *(const void *, const char *)
+talloc_strdup_append: char *(char *, const char *)
+talloc_strdup_append_buffer: char *(char *, const char *)
+talloc_strndup: char *(const void *, const char *, size_t)
+talloc_strndup_append: char *(char *, const char *, size_t)
+talloc_strndup_append_buffer: char *(char *, const char *, size_t)
+talloc_test_get_magic: int (void)
+talloc_total_blocks: size_t (const void *)
+talloc_total_size: size_t (const void *)
+talloc_unlink: int (const void *, void *)
+talloc_vasprintf: char *(const void *, const char *, va_list)
+talloc_vasprintf_append: char *(char *, const char *, va_list)
+talloc_vasprintf_append_buffer: char *(char *, const char *, va_list)
+talloc_version_major: int (void)
+talloc_version_minor: int (void)
diff --git a/lib/talloc/pytalloc.c b/lib/talloc/pytalloc.c
index 3532fdf..95dbb29 100644
--- a/lib/talloc/pytalloc.c
+++ b/lib/talloc/pytalloc.c
@@ -86,7 +86,7 @@ static PyObject *pytalloc_default_repr(PyObject *obj)
pytalloc_Object *talloc_obj = (pytalloc_Object *)obj;
PyTypeObject *type = (PyTypeObject*)PyObject_Type(obj);
- return PyStr_FromFormat("<%s talloc object at 0x%p>",
+ return PyStr_FromFormat("<%s talloc object at %p>",
type->tp_name, talloc_obj->ptr);
}
diff --git a/lib/talloc/talloc.c b/lib/talloc/talloc.c
index cd159ef..54be634 100644
--- a/lib/talloc/talloc.c
+++ b/lib/talloc/talloc.c
@@ -121,8 +121,12 @@ static unsigned int talloc_magic = TALLOC_MAGIC_NON_RANDOM;
NULL
*/
static void *null_context;
+static bool talloc_report_null;
+static bool talloc_report_null_full;
static void *autofree_context;
+static void talloc_setup_atexit(void);
+
/* used to enable fill of memory on free, which can be useful for
* catching use after free errors when valgrind is too slow
*/
@@ -426,6 +430,33 @@ void talloc_lib_init(void)
#warning "No __attribute__((constructor)) support found on this platform, additional talloc security measures not available"
#endif
+static void talloc_lib_atexit(void)
+{
+ TALLOC_FREE(autofree_context);
+
+ if (talloc_total_size(null_context) == 0) {
+ return;
+ }
+
+ if (talloc_report_null_full) {
+ talloc_report_full(null_context, stderr);
+ } else if (talloc_report_null) {
+ talloc_report(null_context, stderr);
+ }
+}
+
+static void talloc_setup_atexit(void)
+{
+ static bool done;
+
+ if (done) {
+ return;
+ }
+
+ atexit(talloc_lib_atexit);
+ done = true;
+}
+
static void talloc_log(const char *fmt, ...) PRINTF_ATTRIBUTE(1,2);
static void talloc_log(const char *fmt, ...)
{
@@ -2295,26 +2326,6 @@ _PUBLIC_ void talloc_report(const void *ptr, FILE *f)
}
/*
- report on any memory hanging off the null context
-*/
-static void talloc_report_null(void)
-{
- if (talloc_total_size(null_context) != 0) {
- talloc_report(null_context, stderr);
- }
-}
-
-/*
- report on any memory hanging off the null context
-*/
-static void talloc_report_null_full(void)
-{
- if (talloc_total_size(null_context) != 0) {
- talloc_report_full(null_context, stderr);
- }
-}
-
-/*
enable tracking of the NULL context
*/
_PUBLIC_ void talloc_enable_null_tracking(void)
@@ -2369,7 +2380,8 @@ _PUBLIC_ void talloc_disable_null_tracking(void)
_PUBLIC_ void talloc_enable_leak_report(void)
{
talloc_enable_null_tracking();
- atexit(talloc_report_null);
+ talloc_report_null = true;
+ talloc_setup_atexit();
}
/*
@@ -2378,7 +2390,8 @@ _PUBLIC_ void talloc_enable_leak_report(void)
_PUBLIC_ void talloc_enable_leak_report_full(void)
{
talloc_enable_null_tracking();
- atexit(talloc_report_null_full);
+ talloc_report_null_full = true;
+ talloc_setup_atexit();
}
/*
@@ -2554,7 +2567,8 @@ static struct talloc_chunk *_vasprintf_tc(const void *t,
const char *fmt,
va_list ap)
{
- int len;
+ int vlen;
+ size_t len;
char *ret;
va_list ap2;
struct talloc_chunk *tc;
@@ -2562,9 +2576,13 @@ static struct talloc_chunk *_vasprintf_tc(const void *t,
/* this call looks strange, but it makes it work on older solaris boxes */
va_copy(ap2, ap);
- len = vsnprintf(buf, sizeof(buf), fmt, ap2);
+ vlen = vsnprintf(buf, sizeof(buf), fmt, ap2);
va_end(ap2);
- if (unlikely(len < 0)) {
+ if (unlikely(vlen < 0)) {
+ return NULL;
+ }
+ len = vlen;
+ if (unlikely(len + 1 < len)) {
return NULL;
}
@@ -2760,11 +2778,6 @@ static int talloc_autofree_destructor(void *ptr)
return 0;
}
-static void talloc_autofree(void)
-{
- talloc_free(autofree_context);
-}
-
/*
return a context which will be auto-freed on exit
this is useful for reducing the noise in leak reports
@@ -2774,7 +2787,7 @@ _PUBLIC_ void *talloc_autofree_context(void)
if (autofree_context == NULL) {
autofree_context = _talloc_named_const(NULL, 0, "autofree_context");
talloc_set_destructor(autofree_context, talloc_autofree_destructor);
- atexit(talloc_autofree);
+ talloc_setup_atexit();
}
return autofree_context;
}
diff --git a/lib/talloc/talloc.h b/lib/talloc/talloc.h
index 618430a..7372df1 100644
--- a/lib/talloc/talloc.h
+++ b/lib/talloc/talloc.h
@@ -1226,7 +1226,7 @@ size_t talloc_array_length(const void *ctx);
*
* @code
* ptr = talloc_array(ctx, type, count);
- * if (ptr) memset(ptr, sizeof(type) * count);
+ * if (ptr) memset(ptr, 0, sizeof(type) * count);
* @endcode
*/
void *talloc_zero_array(const void *ctx, #type, unsigned count);
@@ -1898,8 +1898,8 @@ void talloc_set_log_stderr(void);
* This affects all children of this context and constrain any
* allocation in the hierarchy to never exceed the limit set.
* The limit can be removed by setting 0 (unlimited) as the
- * max_size by calling the funciton again on the sam context.
- * Memory limits can also be nested, meaning a hild can have
+ * max_size by calling the function again on the same context.
+ * Memory limits can also be nested, meaning a child can have
* a stricter memory limit than a parent.
* Memory limits are enforced only at memory allocation time.
* Stealing a context into a 'limited' hierarchy properly
diff --git a/lib/talloc/wscript b/lib/talloc/wscript
index 0afa162..1b31672 100644
--- a/lib/talloc/wscript
+++ b/lib/talloc/wscript
@@ -1,7 +1,7 @@
#!/usr/bin/env python
APPNAME = 'talloc'
-VERSION = '2.1.11'
+VERSION = '2.1.13'
blddir = 'bin'
@@ -165,7 +165,7 @@ def build(bld):
bld.SAMBA_PYTHON('test_pytalloc',
'test_pytalloc.c',
- deps='pytalloc',
+ deps=name,
enabled=bld.PYTHON_BUILD_IS_ENABLED(),
realname='_test_pytalloc.so',
install=False)
diff --git a/lib/tdb/common/check.c b/lib/tdb/common/check.c
index e632af5..3a5c8b8 100644
--- a/lib/tdb/common/check.c
+++ b/lib/tdb/common/check.c
@@ -242,12 +242,27 @@ static bool tdb_check_used_record(struct tdb_context *tdb,
void *private_data)
{
TDB_DATA key, data;
+ tdb_len_t len;
if (!tdb_check_record(tdb, off, rec))
return false;
/* key + data + tailer must fit in record */
- if (rec->key_len + rec->data_len + sizeof(tdb_off_t) > rec->rec_len) {
+ len = rec->key_len;
+ len += rec->data_len;
+ if (len < rec->data_len) {
+ /* overflow */
+ TDB_LOG((tdb, TDB_DEBUG_ERROR, "Record lengths overflow\n"));
+ return false;
+ }
+ len += sizeof(tdb_off_t);
+ if (len < sizeof(tdb_off_t)) {
+ /* overflow */
+ TDB_LOG((tdb, TDB_DEBUG_ERROR, "Record lengths overflow\n"));
+ return false;
+ }
+
+ if (len > rec->rec_len) {
TDB_LOG((tdb, TDB_DEBUG_ERROR,
"Record offset %u too short for contents\n", off));
return false;
diff --git a/lib/tdb/common/hash.c b/lib/tdb/common/hash.c
index 1eed722..4de7ba9 100644
--- a/lib/tdb/common/hash.c
+++ b/lib/tdb/common/hash.c
@@ -232,16 +232,16 @@ static uint32_t hashlittle( const void *key, size_t length )
switch(length)
{
case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
- case 11: c+=((uint32_t)k8[10])<<16; /* fall through */
- case 10: c+=((uint32_t)k8[9])<<8; /* fall through */
- case 9 : c+=k8[8]; /* fall through */
+ case 11: c+=((uint32_t)k8[10])<<16; FALL_THROUGH;
+ case 10: c+=((uint32_t)k8[9])<<8; FALL_THROUGH;
+ case 9 : c+=k8[8]; FALL_THROUGH;
case 8 : b+=k[1]; a+=k[0]; break;
- case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */
- case 6 : b+=((uint32_t)k8[5])<<8; /* fall through */
- case 5 : b+=k8[4]; /* fall through */
+ case 7 : b+=((uint32_t)k8[6])<<16; FALL_THROUGH;
+ case 6 : b+=((uint32_t)k8[5])<<8; FALL_THROUGH;
+ case 5 : b+=k8[4]; FALL_THROUGH;
case 4 : a+=k[0]; break;
- case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */
- case 2 : a+=((uint32_t)k8[1])<<8; /* fall through */
+ case 3 : a+=((uint32_t)k8[2])<<16; FALL_THROUGH;
+ case 2 : a+=((uint32_t)k8[1])<<8; FALL_THROUGH;
case 1 : a+=k8[0]; break;
case 0 : return c;
}
@@ -268,23 +268,23 @@ static uint32_t hashlittle( const void *key, size_t length )
b+=k[2]+(((uint32_t)k[3])<<16);
a+=k[0]+(((uint32_t)k[1])<<16);
break;
- case 11: c+=((uint32_t)k8[10])<<16; /* fall through */
+ case 11: c+=((uint32_t)k8[10])<<16; FALL_THROUGH;
case 10: c+=k[4];
b+=k[2]+(((uint32_t)k[3])<<16);
a+=k[0]+(((uint32_t)k[1])<<16);
break;
- case 9 : c+=k8[8]; /* fall through */
+ case 9 : c+=k8[8]; FALL_THROUGH;
case 8 : b+=k[2]+(((uint32_t)k[3])<<16);
a+=k[0]+(((uint32_t)k[1])<<16);
break;
- case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */
+ case 7 : b+=((uint32_t)k8[6])<<16; FALL_THROUGH;
case 6 : b+=k[2];
a+=k[0]+(((uint32_t)k[1])<<16);
break;
- case 5 : b+=k8[4]; /* fall through */
+ case 5 : b+=k8[4]; FALL_THROUGH;
case 4 : a+=k[0]+(((uint32_t)k[1])<<16);
break;
- case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */
+ case 3 : a+=((uint32_t)k8[2])<<16; FALL_THROUGH;
case 2 : a+=k[0];
break;
case 1 : a+=k8[0];
@@ -316,19 +316,19 @@ static uint32_t hashlittle( const void *key, size_t length )
}
/*-------------------------------- last block: affect all 32 bits of (c) */
- switch(length) /* all the case statements fall through */
+ switch(length)
{
- case 12: c+=((uint32_t)k[11])<<24;
- case 11: c+=((uint32_t)k[10])<<16;
- case 10: c+=((uint32_t)k[9])<<8;
- case 9 : c+=k[8];
- case 8 : b+=((uint32_t)k[7])<<24;
- case 7 : b+=((uint32_t)k[6])<<16;
- case 6 : b+=((uint32_t)k[5])<<8;
- case 5 : b+=k[4];
- case 4 : a+=((uint32_t)k[3])<<24;
- case 3 : a+=((uint32_t)k[2])<<16;
- case 2 : a+=((uint32_t)k[1])<<8;
+ case 12: c+=((uint32_t)k[11])<<24; FALL_THROUGH;
+ case 11: c+=((uint32_t)k[10])<<16; FALL_THROUGH;
+ case 10: c+=((uint32_t)k[9])<<8; FALL_THROUGH;
+ case 9 : c+=k[8]; FALL_THROUGH;
+ case 8 : b+=((uint32_t)k[7])<<24; FALL_THROUGH;
+ case 7 : b+=((uint32_t)k[6])<<16; FALL_THROUGH;
+ case 6 : b+=((uint32_t)k[5])<<8; FALL_THROUGH;
+ case 5 : b+=k[4]; FALL_THROUGH;
+ case 4 : a+=((uint32_t)k[3])<<24; FALL_THROUGH;
+ case 3 : a+=((uint32_t)k[2])<<16; FALL_THROUGH;
+ case 2 : a+=((uint32_t)k[1])<<8; FALL_THROUGH;
case 1 : a+=k[0];
break;
case 0 : return c;
diff --git a/lib/tdb/common/io.c b/lib/tdb/common/io.c
index 15ba5b7..94b3163 100644
--- a/lib/tdb/common/io.c
+++ b/lib/tdb/common/io.c
@@ -733,6 +733,9 @@ int tdb_parse_data(struct tdb_context *tdb, TDB_DATA key,
/* read/write a record */
int tdb_rec_read(struct tdb_context *tdb, tdb_off_t offset, struct tdb_record *rec)
{
+ int ret;
+ tdb_len_t overall_len;
+
if (tdb->methods->tdb_read(tdb, offset, rec, sizeof(*rec),DOCONV()) == -1)
return -1;
if (TDB_BAD_MAGIC(rec)) {
@@ -741,6 +744,31 @@ int tdb_rec_read(struct tdb_context *tdb, tdb_off_t offset, struct tdb_record *r
TDB_LOG((tdb, TDB_DEBUG_FATAL,"tdb_rec_read bad magic 0x%x at offset=%u\n", rec->magic, offset));
return -1;
}
+
+ overall_len = rec->key_len + rec->data_len;
+ if (overall_len < rec->data_len) {
+ /* overflow */
+ return -1;
+ }
+
+ if (overall_len > rec->rec_len) {
+ /* invalid record */
+ return -1;
+ }
+
+ ret = tdb->methods->tdb_oob(tdb, offset, rec->key_len, 1);
+ if (ret == -1) {
+ return -1;
+ }
+ ret = tdb->methods->tdb_oob(tdb, offset, rec->data_len, 1);
+ if (ret == -1) {
+ return -1;
+ }
+ ret = tdb->methods->tdb_oob(tdb, offset, rec->rec_len, 1);
+ if (ret == -1) {
+ return -1;
+ }
+
return tdb->methods->tdb_oob(tdb, rec->next, sizeof(*rec), 0);
}
diff --git a/lib/tdb/common/open.c b/lib/tdb/common/open.c
index cfb476d..8baa7e4 100644
--- a/lib/tdb/common/open.c
+++ b/lib/tdb/common/open.c
@@ -687,6 +687,21 @@ _PUBLIC_ struct tdb_context *tdb_open_ex(const char *name, int hash_size, int td
}
}
+ if (tdb->hash_size > UINT32_MAX/4) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_open_ex: "
+ "hash size %"PRIu32" too large\n", tdb->hash_size));
+ errno = EINVAL;
+ goto fail;
+ }
+
+ ret = tdb->methods->tdb_oob(tdb, FREELIST_TOP, 4*tdb->hash_size, 1);
+ if (ret == -1) {
+ TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_open_ex: "
+ "hash size %"PRIu32" does not fit\n", tdb->hash_size));
+ errno = EINVAL;
+ goto fail;
+ }
+
if (locked) {
if (tdb_nest_unlock(tdb, ACTIVE_LOCK, F_WRLCK, false) == -1) {
TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_open_ex: "
diff --git a/lib/tdb/common/summary.c b/lib/tdb/common/summary.c
index d786132..c9b5bc4 100644
--- a/lib/tdb/common/summary.c
+++ b/lib/tdb/common/summary.c
@@ -151,7 +151,8 @@ _PUBLIC_ char *tdb_summary(struct tdb_context *tdb)
rec.rec_len = tdb_dead_space(tdb, off)
- sizeof(rec);
}
- /* Fall through */
+
+ FALL_THROUGH;
case TDB_DEAD_MAGIC:
tally_add(&dead, rec.rec_len);
break;
diff --git a/lib/tdb/common/transaction.c b/lib/tdb/common/transaction.c
index 7d281fc..73d02b6 100644
--- a/lib/tdb/common/transaction.c
+++ b/lib/tdb/common/transaction.c
@@ -592,7 +592,8 @@ static int transaction_sync(struct tdb_context *tdb, tdb_off_t offset, tdb_len_t
static int _tdb_transaction_cancel(struct tdb_context *tdb)
{
- int i, ret = 0;
+ uint32_t i;
+ int ret = 0;
if (tdb->transaction == NULL) {
TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_transaction_cancel: no transaction\n"));
@@ -654,7 +655,7 @@ _PUBLIC_ int tdb_transaction_cancel(struct tdb_context *tdb)
static bool tdb_recovery_size(struct tdb_context *tdb, tdb_len_t *result)
{
tdb_len_t recovery_size = 0;
- int i;
+ uint32_t i;
recovery_size = sizeof(uint32_t);
for (i=0;i<tdb->transaction->num_blocks;i++) {
@@ -689,6 +690,8 @@ int tdb_recovery_area(struct tdb_context *tdb,
tdb_off_t *recovery_offset,
struct tdb_record *rec)
{
+ int ret;
+
if (tdb_ofs_read(tdb, TDB_RECOVERY_HEAD, recovery_offset) == -1) {
return -1;
}
@@ -709,6 +712,13 @@ int tdb_recovery_area(struct tdb_context *tdb,
*recovery_offset = 0;
rec->rec_len = 0;
}
+
+ ret = methods->tdb_oob(tdb, *recovery_offset, rec->rec_len, 1);
+ if (ret == -1) {
+ *recovery_offset = 0;
+ rec->rec_len = 0;
+ }
+
return 0;
}
@@ -834,7 +844,7 @@ static int transaction_setup_recovery(struct tdb_context *tdb,
tdb_off_t recovery_offset, recovery_max_size;
tdb_off_t old_map_size = tdb->transaction->old_map_size;
uint32_t magic, tailer;
- int i;
+ uint32_t i;
/*
check that the recovery area has enough space
@@ -844,13 +854,12 @@ static int transaction_setup_recovery(struct tdb_context *tdb,
return -1;
}
- data = (unsigned char *)malloc(recovery_size + sizeof(*rec));
- if (data == NULL) {
+ rec = malloc(recovery_size + sizeof(*rec));
+ if (rec == NULL) {
tdb->ecode = TDB_ERR_OOM;
return -1;
}
- rec = (struct tdb_record *)data;
memset(rec, 0, sizeof(*rec));
rec->magic = TDB_RECOVERY_INVALID_MAGIC;
@@ -859,6 +868,8 @@ static int transaction_setup_recovery(struct tdb_context *tdb,
rec->key_len = old_map_size;
CONVERT(*rec);
+ data = (unsigned char *)rec;
+
/* build the recovery data into a single blob to allow us to do a single
large write, which should be more efficient */
p = data + sizeof(*rec);
@@ -1096,7 +1107,7 @@ static bool repack_worthwhile(struct tdb_context *tdb)
_PUBLIC_ int tdb_transaction_commit(struct tdb_context *tdb)
{
const struct tdb_methods *methods;
- int i;
+ uint32_t i;
bool need_repack = false;
if (tdb->transaction == NULL) {
diff --git a/lib/tdb/common/traverse.c b/lib/tdb/common/traverse.c
index 9b83379..7a1d567 100644
--- a/lib/tdb/common/traverse.c
+++ b/lib/tdb/common/traverse.c
@@ -166,9 +166,16 @@ static int tdb_traverse_internal(struct tdb_context *tdb,
/* tdb_next_lock places locks on the record returned, and its chain */
while ((off = tdb_next_lock(tdb, tl, &rec)) != 0) {
- tdb_len_t full_len = rec.key_len + rec.data_len;
+ tdb_len_t full_len;
int nread;
+ if (off == TDB_NEXT_LOCK_ERR) {
+ ret = -1;
+ goto out;
+ }
+
+ full_len = rec.key_len + rec.data_len;
+
if (full_len > recbuf_len) {
recbuf_len = full_len;
@@ -195,10 +202,6 @@ static int tdb_traverse_internal(struct tdb_context *tdb,
}
}
- if (off == TDB_NEXT_LOCK_ERR) {
- ret = -1;
- goto out;
- }
count++;
/* now read the full record */
nread = tdb->methods->tdb_read(tdb, tl->off + sizeof(rec),
diff --git a/lib/tdb/python/tdbdump.py b/lib/tdb/python/tdbdump.py
index 01859eb..08769ea 100644
--- a/lib/tdb/python/tdbdump.py
+++ b/lib/tdb/python/tdbdump.py
@@ -1,12 +1,13 @@
#!/usr/bin/env python
# Trivial reimplementation of tdbdump in Python
+from __future__ import print_function
import tdb, sys
if len(sys.argv) < 2:
- print "Usage: tdbdump.py <tdb-file>"
+ print("Usage: tdbdump.py <tdb-file>")
sys.exit(1)
db = tdb.Tdb(sys.argv[1])
-for (k, v) in db.iteritems():
- print "{\nkey(%d) = %r\ndata(%d) = %r\n}" % (len(k), k, len(v), v)
+for (k, v) in db.items():
+ print("{\nkey(%d) = %r\ndata(%d) = %r\n}" % (len(k), k, len(v), v))
diff --git a/lib/tdb/tools/tdbdump.c b/lib/tdb/tools/tdbdump.c
index 8cf3146..2c68970 100644
--- a/lib/tdb/tools/tdbdump.c
+++ b/lib/tdb/tools/tdbdump.c
@@ -41,10 +41,10 @@ static void print_data(TDB_DATA d)
static int traverse_fn(TDB_CONTEXT *tdb, TDB_DATA key, TDB_DATA dbuf, void *state)
{
printf("{\n");
- printf("key(%d) = \"", (int)key.dsize);
+ printf("key(%zu) = \"", key.dsize);
print_data(key);
printf("\"\n");
- printf("data(%d) = \"", (int)dbuf.dsize);
+ printf("data(%zu) = \"", dbuf.dsize);
print_data(dbuf);
printf("\"\n");
printf("}\n");
diff --git a/lib/tdb/tools/tdbtool.c b/lib/tdb/tools/tdbtool.c
index e3535b9..3e9d154 100644
--- a/lib/tdb/tools/tdbtool.c
+++ b/lib/tdb/tools/tdbtool.c
@@ -647,12 +647,6 @@ static char *tdb_getline(const char *prompt)
return p?thisline:NULL;
}
-static int do_delete_fn(TDB_CONTEXT *the_tdb, TDB_DATA key, TDB_DATA dbuf,
- void *state)
-{
- return tdb_delete(the_tdb, key);
-}
-
static void first_record(TDB_CONTEXT *the_tdb, TDB_DATA *pkey)
{
TDB_DATA dbuf;
@@ -758,7 +752,7 @@ static int do_command(void)
return 0;
case CMD_ERASE:
bIterate = 0;
- tdb_traverse(tdb, do_delete_fn, NULL);
+ tdb_wipe_all(tdb);
return 0;
case CMD_DUMP:
bIterate = 0;
@@ -930,10 +924,13 @@ int main(int argc, char *argv[])
break;
case 5:
arg2 = tdb_convert_string(argv[4],&arg2len);
+ FALL_THROUGH;
case 4:
arg1 = tdb_convert_string(argv[3],&arg1len);
+ FALL_THROUGH;
case 3:
cmdname = argv[2];
+ FALL_THROUGH;
default:
do_command();
break;
diff --git a/lib/tevent/pytevent.c b/lib/tevent/pytevent.c
index 10d8a22..369ec6e 100644
--- a/lib/tevent/pytevent.c
+++ b/lib/tevent/pytevent.c
@@ -188,7 +188,7 @@ static PyObject *py_register_backend(PyObject *self, PyObject *args)
return NULL;
}
- if (!PyStr_Check(name)) {
+ if (!(PyStr_Check(name) || PyUnicode_Check(name))) {
PyErr_SetNone(PyExc_TypeError);
Py_DECREF(name);
return NULL;
diff --git a/lib/tevent/testsuite.c b/lib/tevent/testsuite.c
index e508452..63abbf2 100644
--- a/lib/tevent/testsuite.c
+++ b/lib/tevent/testsuite.c
@@ -375,6 +375,7 @@ static bool test_event_fd1(struct torture_context *tctx,
const void *test_data)
{
struct test_event_fd1_state state;
+ int ret;
ZERO_STRUCT(state);
state.tctx = tctx;
@@ -415,7 +416,9 @@ static bool test_event_fd1(struct torture_context *tctx,
*/
state.sock[0] = -1;
state.sock[1] = -1;
- socketpair(AF_UNIX, SOCK_STREAM, 0, state.sock);
+
+ ret = socketpair(AF_UNIX, SOCK_STREAM, 0, state.sock);
+ torture_assert(tctx, ret == 0, "socketpair() failed");
state.te = tevent_add_timer(state.ev, state.ev,
timeval_current_ofs(0,1000),
diff --git a/lib/tevent/tevent.h b/lib/tevent/tevent.h
index 7bb9c61..3ccac6a 100644
--- a/lib/tevent/tevent.h
+++ b/lib/tevent/tevent.h
@@ -1026,7 +1026,8 @@ struct tevent_req *_tevent_req_create(TALLOC_CTX *mem_ctx,
#endif
/**
- * @brief Set a timeout for an async request.
+ * @brief Set a timeout for an async request. On failure, "req" is already
+ * set to state TEVENT_REQ_NO_MEMORY.
*
* @param[in] req The request to set the timeout for.
*
diff --git a/modules/paged_results.c b/modules/paged_results.c
index de014a3..ecb2227 100644
--- a/modules/paged_results.c
+++ b/modules/paged_results.c
@@ -35,6 +35,8 @@
#include "replace.h"
#include "system/filesys.h"
#include "system/time.h"
+#include "dlinklist.h"
+#include <assert.h>
#include "ldb_module.h"
struct message_store {
@@ -48,14 +50,13 @@ struct message_store {
struct private_data;
struct results_store {
+ struct results_store *prev, *next;
struct private_data *priv;
char *cookie;
time_t timestamp;
- struct results_store *next;
-
struct message_store *first;
struct message_store *last;
int num_entries;
@@ -68,6 +69,7 @@ struct results_store {
struct private_data {
uint32_t next_free_id;
+ size_t num_stores;
struct results_store *store;
};
@@ -75,22 +77,12 @@ struct private_data {
static int store_destructor(struct results_store *del)
{
struct private_data *priv = del->priv;
- struct results_store *loop;
-
- if (priv->store == del) {
- priv->store = del->next;
- return 0;
- }
+ DLIST_REMOVE(priv->store, del);
- for (loop = priv->store; loop; loop = loop->next) {
- if (loop->next == del) {
- loop->next = del->next;
- return 0;
- }
- }
+ assert(priv->num_stores > 0);
+ priv->num_stores -= 1;
- /* is not in list ? */
- return -1;
+ return 0;
}
static struct results_store *new_store(struct private_data *priv)
@@ -120,11 +112,23 @@ static struct results_store *new_store(struct private_data *priv)
newr->first_ref = NULL;
newr->controls = NULL;
- newr->next = priv->store;
- priv->store = newr;
+ DLIST_ADD(priv->store, newr);
+
+ assert(priv->num_stores < SIZE_MAX);
+ priv->num_stores += 1;
talloc_set_destructor(newr, store_destructor);
+ if (priv->num_stores > 10) {
+ struct results_store *last;
+ /*
+ * 10 is the default for MaxResultSetsPerConn --
+ * possibly need to parameterize it.
+ */
+ last = DLIST_TAIL(priv->store);
+ TALLOC_FREE(last);
+ }
+
return newr;
}
@@ -381,6 +385,8 @@ static int paged_search(struct ldb_module *module, struct ldb_request *req)
return LDB_ERR_UNWILLING_TO_PERFORM;
}
+ DLIST_PROMOTE(private_data->store, current);
+
ac->store = current;
/* check if it is an abandon */
@@ -412,6 +418,7 @@ static int paged_request_init(struct ldb_module *module)
}
data->next_free_id = 1;
+ data->num_stores = 0;
data->store = NULL;
ldb_module_set_private(module, data);
diff --git a/pyldb.c b/pyldb.c
index 04b3f1b..110ec8e 100644
--- a/pyldb.c
+++ b/pyldb.c
@@ -1078,7 +1078,7 @@ static const char **PyList_AsStrList(TALLOC_CTX *mem_ctx, PyObject *list,
const char *str = NULL;
Py_ssize_t size;
PyObject *item = PyList_GetItem(list, i);
- if (!PyStr_Check(item)) {
+ if (!(PyStr_Check(item) || PyUnicode_Check(item))) {
PyErr_Format(PyExc_TypeError, "%s should be strings", paramname);
talloc_free(ret);
return NULL;
@@ -1154,7 +1154,7 @@ static PyObject *py_ldb_new(PyTypeObject *type, PyObject *args, PyObject *kwargs
static PyObject *py_ldb_connect(PyLdbObject *self, PyObject *args, PyObject *kwargs)
{
- char *url;
+ char *url = NULL;
unsigned int flags = 0;
PyObject *py_options = Py_None;
int ret;
@@ -2861,7 +2861,7 @@ static struct ldb_message_element *PyObject_AsMessageElement(
me->name = talloc_strdup(me, attr_name);
me->flags = flags;
- if (PyBytes_Check(set_obj) || PyStr_Check(set_obj)) {
+ if (PyBytes_Check(set_obj) || PyUnicode_Check(set_obj)) {
me->num_values = 1;
me->values = talloc_array(me, struct ldb_val, me->num_values);
if (PyBytes_Check(set_obj)) {
@@ -2897,7 +2897,7 @@ static struct ldb_message_element *PyObject_AsMessageElement(
return NULL;
}
msg = _msg;
- } else if (PyStr_Check(obj)) {
+ } else if (PyUnicode_Check(obj)) {
msg = PyStr_AsUTF8AndSize(obj, &size);
if (msg == NULL) {
talloc_free(me);
@@ -3069,7 +3069,7 @@ static PyObject *py_ldb_msg_element_new(PyTypeObject *type, PyObject *args, PyOb
if (py_elements != NULL) {
Py_ssize_t i;
- if (PyBytes_Check(py_elements) || PyStr_Check(py_elements)) {
+ if (PyBytes_Check(py_elements) || PyUnicode_Check(py_elements)) {
char *_msg = NULL;
el->num_values = 1;
el->values = talloc_array(el, struct ldb_val, 1);
@@ -3110,7 +3110,7 @@ static PyObject *py_ldb_msg_element_new(PyTypeObject *type, PyObject *args, PyOb
char *_msg = NULL;
result = PyBytes_AsStringAndSize(item, &_msg, &size);
msg = _msg;
- } else if (PyStr_Check(item)) {
+ } else if (PyUnicode_Check(item)) {
msg = PyStr_AsUTF8AndSize(item, &size);
result = (msg == NULL) ? -1 : 0;
} else {
@@ -4226,6 +4226,10 @@ static PyObject* module_init(void)
ADD_LDB_INT(FLG_NOSYNC);
ADD_LDB_INT(FLG_RECONNECT);
ADD_LDB_INT(FLG_NOMMAP);
+ ADD_LDB_INT(FLG_SHOW_BINARY);
+ ADD_LDB_INT(FLG_ENABLE_TRACING);
+ ADD_LDB_INT(FLG_DONT_CREATE_DB);
+
/* Historical misspelling */
PyModule_AddIntConstant(m, "ERR_ALIAS_DEREFERINCING_PROBLEM", LDB_ERR_ALIAS_DEREFERENCING_PROBLEM);
diff --git a/pyldb_util.c b/pyldb_util.c
index 3bda1db..46ee403 100644
--- a/pyldb_util.c
+++ b/pyldb_util.c
@@ -70,7 +70,7 @@ bool pyldb_Object_AsDn(TALLOC_CTX *mem_ctx, PyObject *object,
struct ldb_dn *odn;
PyTypeObject *PyLdb_Dn_Type;
- if (ldb_ctx != NULL && PyStr_Check(object)) {
+ if (ldb_ctx != NULL && (PyStr_Check(object) || PyUnicode_Check(object))) {
odn = ldb_dn_new(mem_ctx, ldb_ctx, PyStr_AsUTF8(object));
*dn = odn;
return true;
diff --git a/tests/ldb_kv_ops_test.c b/tests/ldb_kv_ops_test.c
new file mode 100644
index 0000000..30ce019
--- /dev/null
+++ b/tests/ldb_kv_ops_test.c
@@ -0,0 +1,1576 @@
+/*
+ * Tests exercising the ldb key value operations.
+ *
+ * Copyright (C) Andrew Bartlett <abartlet@samba.org> 2018
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/*
+ * from cmocka.c:
+ * These headers or their equivalents should be included prior to
+ * including
+ * this header file.
+ *
+ * #include <stdarg.h>
+ * #include <stddef.h>
+ * #include <setjmp.h>
+ *
+ * This allows test applications to use custom definitions of C standard
+ * library functions and types.
+ *
+ */
+
+/*
+ * A KV module is expected to have the following behaviour
+ *
+ * - A transaction must be open to perform any read, write or delete operation
+ * - Writes and Deletes should not be visible until a transaction is commited
+ * - Nested transactions are not permitted
+ * - transactions can be rolled back and commited.
+ * - supports iteration over all records in the database
+ * - supports the update_in_iterate operation allowing entries to be
+ * re-keyed.
+ */
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+#include <errno.h>
+#include <unistd.h>
+#include <talloc.h>
+#include <tevent.h>
+#include <ldb.h>
+#include <ldb_module.h>
+#include <ldb_private.h>
+#include <string.h>
+#include <ctype.h>
+
+#include <sys/wait.h>
+
+#include "ldb_tdb/ldb_tdb.h"
+
+
+#define DEFAULT_BE "tdb"
+
+#ifndef TEST_BE
+#define TEST_BE DEFAULT_BE
+#endif /* TEST_BE */
+
+#define NUM_RECS 1024
+
+
+struct test_ctx {
+ struct tevent_context *ev;
+ struct ldb_context *ldb;
+
+ const char *dbfile;
+ const char *lockfile; /* lockfile is separate */
+
+ const char *dbpath;
+};
+
+static void unlink_old_db(struct test_ctx *test_ctx)
+{
+ int ret;
+
+ errno = 0;
+ ret = unlink(test_ctx->lockfile);
+ if (ret == -1 && errno != ENOENT) {
+ fail();
+ }
+
+ errno = 0;
+ ret = unlink(test_ctx->dbfile);
+ if (ret == -1 && errno != ENOENT) {
+ fail();
+ }
+}
+
+static int noconn_setup(void **state)
+{
+ struct test_ctx *test_ctx;
+
+ test_ctx = talloc_zero(NULL, struct test_ctx);
+ assert_non_null(test_ctx);
+
+ test_ctx->ev = tevent_context_init(test_ctx);
+ assert_non_null(test_ctx->ev);
+
+ test_ctx->ldb = ldb_init(test_ctx, test_ctx->ev);
+ assert_non_null(test_ctx->ldb);
+
+ test_ctx->dbfile = talloc_strdup(test_ctx, "kvopstest.ldb");
+ assert_non_null(test_ctx->dbfile);
+
+ test_ctx->lockfile = talloc_asprintf(test_ctx, "%s-lock",
+ test_ctx->dbfile);
+ assert_non_null(test_ctx->lockfile);
+
+ test_ctx->dbpath = talloc_asprintf(test_ctx,
+ TEST_BE"://%s", test_ctx->dbfile);
+ assert_non_null(test_ctx->dbpath);
+
+ unlink_old_db(test_ctx);
+ *state = test_ctx;
+ return 0;
+}
+
+static int noconn_teardown(void **state)
+{
+ struct test_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct test_ctx);
+
+ unlink_old_db(test_ctx);
+ talloc_free(test_ctx);
+ return 0;
+}
+
+static int setup(void **state)
+{
+ struct test_ctx *test_ctx;
+ int ret;
+ struct ldb_ldif *ldif;
+ const char *index_ldif = \
+ "dn: @INDEXLIST\n"
+ "@IDXGUID: objectUUID\n"
+ "@IDX_DN_GUID: GUID\n"
+ "\n";
+
+ noconn_setup((void **) &test_ctx);
+
+ ret = ldb_connect(test_ctx->ldb, test_ctx->dbpath, 0, NULL);
+ assert_int_equal(ret, 0);
+
+ while ((ldif = ldb_ldif_read_string(test_ctx->ldb, &index_ldif))) {
+ ret = ldb_add(test_ctx->ldb, ldif->msg);
+ assert_int_equal(ret, LDB_SUCCESS);
+ }
+ *state = test_ctx;
+ return 0;
+}
+
+static int teardown(void **state)
+{
+ struct test_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct test_ctx);
+ noconn_teardown((void **) &test_ctx);
+ return 0;
+}
+
+static struct ltdb_private *get_ltdb(struct ldb_context *ldb)
+{
+ void *data = NULL;
+ struct ltdb_private *ltdb = NULL;
+
+ data = ldb_module_get_private(ldb->modules);
+ assert_non_null(data);
+
+ ltdb = talloc_get_type(data, struct ltdb_private);
+ assert_non_null(ltdb);
+
+ return ltdb;
+}
+
+static int parse(struct ldb_val key,
+ struct ldb_val data,
+ void *private_data)
+{
+ struct ldb_val* read = private_data;
+
+ /* Yes, we essentially leak this. That is OK */
+ read->data = talloc_size(talloc_autofree_context(),
+ data.length);
+ assert_non_null(read->data);
+
+ memcpy(read->data, data.data, data.length);
+ read->length = data.length;
+ return LDB_SUCCESS;
+}
+
+/*
+ * Test that data can be written to the kv store and be read back.
+ */
+static void test_add_get(void **state)
+{
+ int ret;
+ struct test_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct test_ctx);
+ struct ltdb_private *ltdb = get_ltdb(test_ctx->ldb);
+ uint8_t key_val[] = "TheKey";
+ struct ldb_val key = {
+ .data = key_val,
+ .length = sizeof(key_val)
+ };
+
+ uint8_t value[] = "The record contents";
+ struct ldb_val data = {
+ .data = value,
+ .length = sizeof(value)
+ };
+
+ struct ldb_val read;
+
+ int flags = 0;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_new(test_ctx);
+ assert_non_null(tmp_ctx);
+
+ /*
+ * Begin a transaction
+ */
+ ret = ltdb->kv_ops->begin_write(ltdb);
+ assert_int_equal(ret, 0);
+
+ /*
+ * Write the record
+ */
+ ret = ltdb->kv_ops->store(ltdb, key, data, flags);
+ assert_int_equal(ret, 0);
+
+ /*
+ * Commit the transaction
+ */
+ ret = ltdb->kv_ops->finish_write(ltdb);
+ assert_int_equal(ret, 0);
+
+ /*
+ * And now read it back
+ */
+ ret = ltdb->kv_ops->lock_read(test_ctx->ldb->modules);
+ assert_int_equal(ret, 0);
+
+ ret = ltdb->kv_ops->fetch_and_parse(ltdb, key, parse, &read);
+ assert_int_equal(ret, 0);
+
+ assert_int_equal(sizeof(value), read.length);
+ assert_memory_equal(value, read.data, sizeof(value));
+
+ ret = ltdb->kv_ops->unlock_read(test_ctx->ldb->modules);
+ assert_int_equal(ret, 0);
+ talloc_free(tmp_ctx);
+}
+
+/*
+ * Test that attempts to read data without a read transaction fail.
+ */
+static void test_read_outside_transaction(void **state)
+{
+ int ret;
+ struct test_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct test_ctx);
+ struct ltdb_private *ltdb = get_ltdb(test_ctx->ldb);
+ uint8_t key_val[] = "TheKey";
+ struct ldb_val key = {
+ .data = key_val,
+ .length = sizeof(key_val)
+ };
+
+ uint8_t value[] = "The record contents";
+ struct ldb_val data = {
+ .data = value,
+ .length = sizeof(value)
+ };
+
+ struct ldb_val read;
+
+ int flags = 0;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_new(test_ctx);
+ assert_non_null(tmp_ctx);
+
+ /*
+ * Begin a transaction
+ */
+ ret = ltdb->kv_ops->begin_write(ltdb);
+ assert_int_equal(ret, 0);
+
+ /*
+ * Write the record
+ */
+ ret = ltdb->kv_ops->store(ltdb, key, data, flags);
+ assert_int_equal(ret, 0);
+
+ /*
+ * Commit the transaction
+ */
+ ret = ltdb->kv_ops->finish_write(ltdb);
+ assert_int_equal(ret, 0);
+
+ /*
+ * And now read it back
+ * Note there is no read transaction active
+ */
+ ret = ltdb->kv_ops->fetch_and_parse(ltdb, key, parse, &read);
+ assert_int_equal(ret, LDB_ERR_PROTOCOL_ERROR);
+
+ talloc_free(tmp_ctx);
+}
+
+/*
+ * Test that data can be deleted from the kv store
+ */
+static void test_delete(void **state)
+{
+ int ret;
+ struct test_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct test_ctx);
+ struct ltdb_private *ltdb = get_ltdb(test_ctx->ldb);
+ uint8_t key_val[] = "TheKey";
+ struct ldb_val key = {
+ .data = key_val,
+ .length = sizeof(key_val)
+ };
+
+ uint8_t value[] = "The record contents";
+ struct ldb_val data = {
+ .data = value,
+ .length = sizeof(value)
+ };
+
+ struct ldb_val read;
+
+ int flags = 0;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_new(test_ctx);
+ assert_non_null(tmp_ctx);
+
+ /*
+ * Begin a transaction
+ */
+ ret = ltdb->kv_ops->begin_write(ltdb);
+ assert_int_equal(ret, 0);
+
+ /*
+ * Write the record
+ */
+ ret = ltdb->kv_ops->store(ltdb, key, data, flags);
+ assert_int_equal(ret, 0);
+
+ /*
+ * Commit the transaction
+ */
+ ret = ltdb->kv_ops->finish_write(ltdb);
+ assert_int_equal(ret, 0);
+
+ /*
+ * And now read it back
+ */
+ ret = ltdb->kv_ops->lock_read(test_ctx->ldb->modules);
+ assert_int_equal(ret, 0);
+ ret = ltdb->kv_ops->fetch_and_parse(ltdb, key, parse, &read);
+ assert_int_equal(ret, 0);
+ assert_int_equal(sizeof(value), read.length);
+ assert_memory_equal(value, read.data, sizeof(value));
+ ret = ltdb->kv_ops->unlock_read(test_ctx->ldb->modules);
+ assert_int_equal(ret, 0);
+
+ /*
+ * Begin a transaction
+ */
+ ret = ltdb->kv_ops->begin_write(ltdb);
+ assert_int_equal(ret, 0);
+
+ /*
+ * Now delete it.
+ */
+ ret = ltdb->kv_ops->delete(ltdb, key);
+ assert_int_equal(ret, 0);
+
+ /*
+ * Commit the transaction
+ */
+ ret = ltdb->kv_ops->finish_write(ltdb);
+ assert_int_equal(ret, 0);
+
+ /*
+ * And now try to read it back
+ */
+ ret = ltdb->kv_ops->lock_read(test_ctx->ldb->modules);
+ assert_int_equal(ret, 0);
+ ret = ltdb->kv_ops->fetch_and_parse(ltdb, key, parse, &read);
+ assert_int_equal(ret, LDB_ERR_NO_SUCH_OBJECT);
+ ret = ltdb->kv_ops->unlock_read(test_ctx->ldb->modules);
+ assert_int_equal(ret, 0);
+
+ talloc_free(tmp_ctx);
+}
+
+/*
+ * Check that writes are correctly rolled back when a transaction
+ * is rolled back.
+ */
+static void test_transaction_abort_write(void **state)
+{
+ int ret;
+ struct test_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct test_ctx);
+ struct ltdb_private *ltdb = get_ltdb(test_ctx->ldb);
+ uint8_t key_val[] = "TheKey";
+ struct ldb_val key = {
+ .data = key_val,
+ .length = sizeof(key_val)
+ };
+
+ uint8_t value[] = "The record contents";
+ struct ldb_val data = {
+ .data = value,
+ .length = sizeof(value)
+ };
+
+ struct ldb_val read;
+
+ int flags = 0;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_new(test_ctx);
+ assert_non_null(tmp_ctx);
+
+ /*
+ * Begin a transaction
+ */
+ ret = ltdb->kv_ops->begin_write(ltdb);
+ assert_int_equal(ret, 0);
+
+ /*
+ * Write the record
+ */
+ ret = ltdb->kv_ops->store(ltdb, key, data, flags);
+ assert_int_equal(ret, 0);
+
+ /*
+ * And now read it back
+ */
+ ret = ltdb->kv_ops->fetch_and_parse(ltdb, key, parse, &read);
+ assert_int_equal(ret, 0);
+ assert_int_equal(sizeof(value), read.length);
+ assert_memory_equal(value, read.data, sizeof(value));
+
+
+ /*
+ * Now abort the transaction
+ */
+ ret = ltdb->kv_ops->abort_write(ltdb);
+ assert_int_equal(ret, 0);
+
+ /*
+ * And now read it back, should not be there
+ */
+ ret = ltdb->kv_ops->lock_read(test_ctx->ldb->modules);
+ assert_int_equal(ret, 0);
+ ret = ltdb->kv_ops->fetch_and_parse(ltdb, key, parse, &read);
+ assert_int_equal(ret, LDB_ERR_NO_SUCH_OBJECT);
+ ret = ltdb->kv_ops->unlock_read(test_ctx->ldb->modules);
+ assert_int_equal(ret, 0);
+
+ talloc_free(tmp_ctx);
+}
+
+/*
+ * Check that deletes are correctly rolled back when a transaction is
+ * aborted.
+ */
+static void test_transaction_abort_delete(void **state)
+{
+ int ret;
+ struct test_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct test_ctx);
+ struct ltdb_private *ltdb = get_ltdb(test_ctx->ldb);
+ uint8_t key_val[] = "TheKey";
+ struct ldb_val key = {
+ .data = key_val,
+ .length = sizeof(key_val)
+ };
+
+ uint8_t value[] = "The record contents";
+ struct ldb_val data = {
+ .data = value,
+ .length = sizeof(value)
+ };
+
+ struct ldb_val read;
+
+ int flags = 0;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_new(test_ctx);
+ assert_non_null(tmp_ctx);
+
+ /*
+ * Begin a transaction
+ */
+ ret = ltdb->kv_ops->begin_write(ltdb);
+ assert_int_equal(ret, 0);
+
+ /*
+ * Write the record
+ */
+ ret = ltdb->kv_ops->store(ltdb, key, data, flags);
+ assert_int_equal(ret, 0);
+
+ /*
+ * Commit the transaction
+ */
+ ret = ltdb->kv_ops->finish_write(ltdb);
+ assert_int_equal(ret, 0);
+
+ /*
+ * And now read it back
+ */
+ ret = ltdb->kv_ops->lock_read(test_ctx->ldb->modules);
+ assert_int_equal(ret, 0);
+ ret = ltdb->kv_ops->fetch_and_parse(ltdb, key, parse, &read);
+ assert_int_equal(ret, 0);
+ assert_int_equal(sizeof(value), read.length);
+ assert_memory_equal(value, read.data, sizeof(value));
+ ret = ltdb->kv_ops->unlock_read(test_ctx->ldb->modules);
+ assert_int_equal(ret, 0);
+
+ /*
+ * Begin a transaction
+ */
+ ret = ltdb->kv_ops->begin_write(ltdb);
+ assert_int_equal(ret, 0);
+
+ /*
+ * Now delete it.
+ */
+ ret = ltdb->kv_ops->delete(ltdb, key);
+ assert_int_equal(ret, 0);
+
+ /*
+ * And now read it back
+ */
+ ret = ltdb->kv_ops->fetch_and_parse(ltdb, key, parse, &read);
+ assert_int_equal(ret, LDB_ERR_NO_SUCH_OBJECT);
+
+ /*
+ * Abort the transaction
+ */
+ ret = ltdb->kv_ops->abort_write(ltdb);
+ assert_int_equal(ret, 0);
+
+ /*
+ * And now try to read it back
+ */
+ ret = ltdb->kv_ops->lock_read(test_ctx->ldb->modules);
+ assert_int_equal(ret, 0);
+ ret = ltdb->kv_ops->fetch_and_parse(ltdb, key, parse, &read);
+ assert_int_equal(ret, 0);
+ assert_int_equal(sizeof(value), read.length);
+ assert_memory_equal(value, read.data, sizeof(value));
+ ret = ltdb->kv_ops->unlock_read(test_ctx->ldb->modules);
+ assert_int_equal(ret, 0);
+
+ talloc_free(tmp_ctx);
+}
+
+/*
+ * Test that writes outside a transaction fail
+ */
+static void test_write_outside_transaction(void **state)
+{
+ int ret;
+ struct test_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct test_ctx);
+ struct ltdb_private *ltdb = get_ltdb(test_ctx->ldb);
+ uint8_t key_val[] = "TheKey";
+ struct ldb_val key = {
+ .data = key_val,
+ .length = sizeof(key_val)
+ };
+
+ uint8_t value[] = "The record contents";
+ struct ldb_val data = {
+ .data = value,
+ .length = sizeof(value)
+ };
+
+
+ int flags = 0;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_new(test_ctx);
+ assert_non_null(tmp_ctx);
+
+ /*
+ * Attempt to write the record
+ */
+ ret = ltdb->kv_ops->store(ltdb, key, data, flags);
+ assert_int_equal(ret, LDB_ERR_PROTOCOL_ERROR);
+
+ talloc_free(tmp_ctx);
+}
+
+/*
+ * Test data can not be deleted outside a transaction
+ */
+static void test_delete_outside_transaction(void **state)
+{
+ int ret;
+ struct test_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct test_ctx);
+ struct ltdb_private *ltdb = get_ltdb(test_ctx->ldb);
+ uint8_t key_val[] = "TheKey";
+ struct ldb_val key = {
+ .data = key_val,
+ .length = sizeof(key_val)
+ };
+
+ uint8_t value[] = "The record contents";
+ struct ldb_val data = {
+ .data = value,
+ .length = sizeof(value)
+ };
+
+ struct ldb_val read;
+
+ int flags = 0;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_new(test_ctx);
+ assert_non_null(tmp_ctx);
+
+ /*
+ * Begin a transaction
+ */
+ ret = ltdb->kv_ops->begin_write(ltdb);
+ assert_int_equal(ret, 0);
+
+ /*
+ * Write the record
+ */
+ ret = ltdb->kv_ops->store(ltdb, key, data, flags);
+ assert_int_equal(ret, 0);
+
+ /*
+ * Commit the transaction
+ */
+ ret = ltdb->kv_ops->finish_write(ltdb);
+ assert_int_equal(ret, 0);
+
+ /*
+ * And now read it back
+ */
+ ret = ltdb->kv_ops->lock_read(test_ctx->ldb->modules);
+ assert_int_equal(ret, 0);
+ ret = ltdb->kv_ops->fetch_and_parse(ltdb, key, parse, &read);
+ assert_int_equal(ret, 0);
+ assert_int_equal(sizeof(value), read.length);
+ assert_memory_equal(value, read.data, sizeof(value));
+ ret = ltdb->kv_ops->unlock_read(test_ctx->ldb->modules);
+ assert_int_equal(ret, 0);
+
+ /*
+ * Now attempt to delete a record
+ */
+ ret = ltdb->kv_ops->delete(ltdb, key);
+ assert_int_equal(ret, LDB_ERR_PROTOCOL_ERROR);
+
+ /*
+ * And now read it back
+ */
+ ret = ltdb->kv_ops->lock_read(test_ctx->ldb->modules);
+ assert_int_equal(ret, 0);
+ ret = ltdb->kv_ops->fetch_and_parse(ltdb, key, parse, &read);
+ assert_int_equal(ret, 0);
+ assert_int_equal(sizeof(value), read.length);
+ assert_memory_equal(value, read.data, sizeof(value));
+ ret = ltdb->kv_ops->unlock_read(test_ctx->ldb->modules);
+ assert_int_equal(ret, 0);
+
+ talloc_free(tmp_ctx);
+}
+
+static int traverse_fn(struct ltdb_private *ltdb,
+ struct ldb_val key,
+ struct ldb_val data,
+ void *ctx) {
+
+ int *visits = ctx;
+ int i;
+
+ if (strncmp("key ", (char *) key.data, 4) == 0) {
+ i = strtol((char *) &key.data[4], NULL, 10);
+ visits[i]++;
+ }
+ return LDB_SUCCESS;
+}
+
+
+/*
+ * Test that iterate visits all the records.
+ */
+static void test_iterate(void **state)
+{
+ int ret;
+ struct test_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct test_ctx);
+ struct ltdb_private *ltdb = get_ltdb(test_ctx->ldb);
+ int i;
+ int num_recs = 1024;
+ int visits[num_recs];
+
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_new(test_ctx);
+ assert_non_null(tmp_ctx);
+
+ /*
+ * Begin a transaction
+ */
+ ret = ltdb->kv_ops->begin_write(ltdb);
+ assert_int_equal(ret, 0);
+
+ /*
+ * Write the records
+ */
+ for (i = 0; i < num_recs; i++) {
+ struct ldb_val key;
+ struct ldb_val rec;
+ int flags = 0;
+
+ visits[i] = 0;
+ key.data = (uint8_t *)talloc_asprintf(tmp_ctx, "key %04d", i);
+ key.length = strlen((char *)key.data) + 1;
+
+ rec.data = (uint8_t *) talloc_asprintf(tmp_ctx,
+ "data for record (%04d)",
+ i);
+ rec.length = strlen((char *)rec.data) + 1;
+
+ ret = ltdb->kv_ops->store(ltdb, key, rec, flags);
+ assert_int_equal(ret, 0);
+
+ TALLOC_FREE(key.data);
+ TALLOC_FREE(rec.data);
+ }
+
+ /*
+ * Commit the transaction
+ */
+ ret = ltdb->kv_ops->finish_write(ltdb);
+ assert_int_equal(ret, 0);
+
+ /*
+ * Now iterate over the kv store and ensure that all the
+ * records are visited.
+ */
+ ret = ltdb->kv_ops->lock_read(test_ctx->ldb->modules);
+ assert_int_equal(ret, 0);
+ ret = ltdb->kv_ops->iterate(ltdb, traverse_fn, visits);
+ for (i = 0; i <num_recs; i++) {
+ assert_int_equal(1, visits[i]);
+ }
+ ret = ltdb->kv_ops->unlock_read(test_ctx->ldb->modules);
+ assert_int_equal(ret, 0);
+
+ TALLOC_FREE(tmp_ctx);
+}
+
+struct update_context {
+ struct ldb_context* ldb;
+ int visits[NUM_RECS];
+};
+
+static int update_fn(struct ltdb_private *ltdb,
+ struct ldb_val key,
+ struct ldb_val data,
+ void *ctx) {
+
+ struct ldb_val new_key;
+ struct ldb_module *module = NULL;
+ struct update_context *context =NULL;
+ int ret = LDB_SUCCESS;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_new(ltdb);
+ assert_non_null(tmp_ctx);
+
+ context = talloc_get_type_abort(ctx, struct update_context);
+
+ module = talloc_zero(tmp_ctx, struct ldb_module);
+ module->ldb = context->ldb;
+
+ if (strncmp("key ", (char *) key.data, 4) == 0) {
+ int i = strtol((char *) &key.data[4], NULL, 10);
+ context->visits[i]++;
+ new_key.data = talloc_memdup(tmp_ctx, key.data, key.length);
+ new_key.length = key.length;
+ new_key.data[0] = 'K';
+
+ ret = ltdb->kv_ops->update_in_iterate(ltdb,
+ key,
+ new_key,
+ data,
+ &module);
+ }
+ TALLOC_FREE(tmp_ctx);
+ return ret;
+}
+
+/*
+ * Test that update_in_iterate behaves as expected.
+ */
+static void test_update_in_iterate(void **state)
+{
+ int ret;
+ struct test_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct test_ctx);
+ struct ltdb_private *ltdb = get_ltdb(test_ctx->ldb);
+ int i;
+ struct update_context *context = NULL;
+
+
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_new(test_ctx);
+ assert_non_null(tmp_ctx);
+
+ context = talloc_zero(tmp_ctx, struct update_context);
+ assert_non_null(context);
+ context->ldb = test_ctx->ldb;
+ /*
+ * Begin a transaction
+ */
+ ret = ltdb->kv_ops->begin_write(ltdb);
+ assert_int_equal(ret, 0);
+
+ /*
+ * Write the records
+ */
+ for (i = 0; i < NUM_RECS; i++) {
+ struct ldb_val key;
+ struct ldb_val rec;
+ int flags = 0;
+
+ key.data = (uint8_t *)talloc_asprintf(tmp_ctx, "key %04d", i);
+ key.length = strlen((char *)key.data) + 1;
+
+ rec.data = (uint8_t *) talloc_asprintf(tmp_ctx,
+ "data for record (%04d)",
+ i);
+ rec.length = strlen((char *)rec.data) + 1;
+
+ ret = ltdb->kv_ops->store(ltdb, key, rec, flags);
+ assert_int_equal(ret, 0);
+
+ TALLOC_FREE(key.data);
+ TALLOC_FREE(rec.data);
+ }
+
+ /*
+ * Commit the transaction
+ */
+ ret = ltdb->kv_ops->finish_write(ltdb);
+ assert_int_equal(ret, 0);
+
+ /*
+ * Now iterate over the kv store and ensure that all the
+ * records are visited.
+ */
+
+ /*
+ * Needs to be done inside a transaction
+ */
+ ret = ltdb->kv_ops->begin_write(ltdb);
+ assert_int_equal(ret, 0);
+
+ ret = ltdb->kv_ops->iterate(ltdb, update_fn, context);
+ for (i = 0; i < NUM_RECS; i++) {
+ assert_int_equal(1, context->visits[i]);
+ }
+
+ ret = ltdb->kv_ops->finish_write(ltdb);
+ assert_int_equal(ret, 0);
+
+ TALLOC_FREE(tmp_ctx);
+}
+
+/*
+ * Ensure that writes are not visible until the transaction has been
+ * committed.
+ */
+static void test_write_transaction_isolation(void **state)
+{
+ int ret;
+ struct test_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct test_ctx);
+ struct ltdb_private *ltdb = get_ltdb(test_ctx->ldb);
+ struct ldb_val key;
+ struct ldb_val val;
+
+ const char *KEY1 = "KEY01";
+ const char *VAL1 = "VALUE01";
+
+ const char *KEY2 = "KEY02";
+ const char *VAL2 = "VALUE02";
+
+ /*
+ * Pipes etc to co-ordinate the processes
+ */
+ int to_child[2];
+ int to_parent[2];
+ char buf[2];
+ pid_t pid, w_pid;
+ int wstatus;
+
+ TALLOC_CTX *tmp_ctx;
+ tmp_ctx = talloc_new(test_ctx);
+ assert_non_null(tmp_ctx);
+
+
+ /*
+ * Add a record to the database
+ */
+ ret = ltdb->kv_ops->begin_write(ltdb);
+ assert_int_equal(ret, 0);
+
+ key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY1);
+ key.length = strlen(KEY1) + 1;
+
+ val.data = (uint8_t *)talloc_strdup(tmp_ctx, VAL1);
+ val.length = strlen(VAL1) + 1;
+
+ ret = ltdb->kv_ops->store(ltdb, key, val, 0);
+ assert_int_equal(ret, 0);
+
+ ret = ltdb->kv_ops->finish_write(ltdb);
+ assert_int_equal(ret, 0);
+
+
+ ret = pipe(to_child);
+ assert_int_equal(ret, 0);
+ ret = pipe(to_parent);
+ assert_int_equal(ret, 0);
+ /*
+ * Now fork a new process
+ */
+
+ pid = fork();
+ if (pid == 0) {
+
+ struct ldb_context *ldb = NULL;
+ close(to_child[1]);
+ close(to_parent[0]);
+
+ /*
+ * Wait for the transaction to start
+ */
+ ret = read(to_child[0], buf, 2);
+ if (ret != 2) {
+ print_error(__location__": read returned (%d)\n",
+ ret);
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+ ldb = ldb_init(test_ctx, test_ctx->ev);
+ ret = ldb_connect(ldb, test_ctx->dbpath, 0, NULL);
+ if (ret != LDB_SUCCESS) {
+ print_error(__location__": ldb_connect returned (%d)\n",
+ ret);
+ exit(ret);
+ }
+
+ ltdb = get_ltdb(ldb);
+
+ ret = ltdb->kv_ops->lock_read(ldb->modules);
+ if (ret != LDB_SUCCESS) {
+ print_error(__location__": lock_read returned (%d)\n",
+ ret);
+ exit(ret);
+ }
+
+ /*
+ * Check that KEY1 is there
+ */
+ key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY1);
+ key.length = strlen(KEY1) + 1;
+
+ ret = ltdb->kv_ops->fetch_and_parse(ltdb, key, parse, &val);
+ if (ret != LDB_SUCCESS) {
+ print_error(__location__": fetch_and_parse returned "
+ "(%d)\n",
+ ret);
+ exit(ret);
+ }
+
+ if ((strlen(VAL1) + 1) != val.length) {
+ print_error(__location__": KEY1 value lengths different"
+ ", expected (%d) actual(%d)\n",
+ (int)(strlen(VAL1) + 1), (int)val.length);
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (memcmp(VAL1, val.data, strlen(VAL1)) != 0) {
+ print_error(__location__": KEY1 values different, "
+ "expected (%s) actual(%s)\n",
+ VAL1,
+ val.data);
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ ret = ltdb->kv_ops->unlock_read(ldb->modules);
+ if (ret != LDB_SUCCESS) {
+ print_error(__location__": unlock_read returned (%d)\n",
+ ret);
+ exit(ret);
+ }
+
+ /*
+ * Check that KEY2 is not there
+ */
+ key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY2);
+ key.length = strlen(KEY2 + 1);
+
+ ret = ltdb->kv_ops->lock_read(ldb->modules);
+ if (ret != LDB_SUCCESS) {
+ print_error(__location__": lock_read returned (%d)\n",
+ ret);
+ exit(ret);
+ }
+
+ ret = ltdb->kv_ops->fetch_and_parse(ltdb, key, parse, &val);
+ if (ret != LDB_ERR_NO_SUCH_OBJECT) {
+ print_error(__location__": fetch_and_parse returned "
+ "(%d)\n",
+ ret);
+ exit(ret);
+ }
+
+ ret = ltdb->kv_ops->unlock_read(ldb->modules);
+ if (ret != LDB_SUCCESS) {
+ print_error(__location__": unlock_read returned (%d)\n",
+ ret);
+ exit(ret);
+ }
+
+ /*
+ * Signal the other process to commit the transaction
+ */
+ ret = write(to_parent[1], "GO", 2);
+ if (ret != 2) {
+ print_error(__location__": write returned (%d)\n",
+ ret);
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ /*
+ * Wait for the transaction to be commited
+ */
+ ret = read(to_child[0], buf, 2);
+ if (ret != 2) {
+ print_error(__location__": read returned (%d)\n",
+ ret);
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ /*
+ * Check that KEY1 is there
+ */
+ ret = ltdb->kv_ops->lock_read(ldb->modules);
+ if (ret != LDB_SUCCESS) {
+ print_error(__location__": unlock_read returned (%d)\n",
+ ret);
+ exit(ret);
+ }
+ key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY1);
+ key.length = strlen(KEY1) + 1;
+
+ ret = ltdb->kv_ops->fetch_and_parse(ltdb, key, parse, &val);
+ if (ret != LDB_SUCCESS) {
+ print_error(__location__": fetch_and_parse returned "
+ "(%d)\n",
+ ret);
+ exit(ret);
+ }
+
+ if ((strlen(VAL1) + 1) != val.length) {
+ print_error(__location__": KEY1 value lengths different"
+ ", expected (%d) actual(%d)\n",
+ (int)(strlen(VAL1) + 1), (int)val.length);
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (memcmp(VAL1, val.data, strlen(VAL1)) != 0) {
+ print_error(__location__": KEY1 values different, "
+ "expected (%s) actual(%s)\n",
+ VAL1,
+ val.data);
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ ret = ltdb->kv_ops->unlock_read(ldb->modules);
+ if (ret != LDB_SUCCESS) {
+ print_error(__location__": unlock_read returned (%d)\n",
+ ret);
+ exit(ret);
+ }
+
+
+ /*
+ * Check that KEY2 is there
+ */
+ ret = ltdb->kv_ops->lock_read(ldb->modules);
+ if (ret != LDB_SUCCESS) {
+ print_error(__location__": unlock_read returned (%d)\n",
+ ret);
+ exit(ret);
+ }
+
+ key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY2);
+ key.length = strlen(KEY2) + 1;
+
+ ret = ltdb->kv_ops->fetch_and_parse(ltdb, key, parse, &val);
+ if (ret != LDB_SUCCESS) {
+ print_error(__location__": fetch_and_parse returned "
+ "(%d)\n",
+ ret);
+ exit(ret);
+ }
+
+ if ((strlen(VAL2) + 1) != val.length) {
+ print_error(__location__": KEY2 value lengths different"
+ ", expected (%d) actual(%d)\n",
+ (int)(strlen(VAL2) + 1), (int)val.length);
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (memcmp(VAL2, val.data, strlen(VAL2)) != 0) {
+ print_error(__location__": KEY2 values different, "
+ "expected (%s) actual(%s)\n",
+ VAL2,
+ val.data);
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ ret = ltdb->kv_ops->unlock_read(ldb->modules);
+ if (ret != LDB_SUCCESS) {
+ print_error(__location__": unlock_read returned (%d)\n",
+ ret);
+ exit(ret);
+ }
+
+ exit(0);
+ }
+ close(to_child[0]);
+ close(to_parent[1]);
+
+ /*
+ * Begin a transaction and add a record to the database
+ * but leave the transaction open
+ */
+ ret = ltdb->kv_ops->begin_write(ltdb);
+ assert_int_equal(ret, 0);
+
+ key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY2);
+ key.length = strlen(KEY2) + 1;
+
+ val.data = (uint8_t *)talloc_strdup(tmp_ctx, VAL2);
+ val.length = strlen(VAL2) + 1;
+
+ ret = ltdb->kv_ops->store(ltdb, key, val, 0);
+ assert_int_equal(ret, 0);
+
+ /*
+ * Signal the child process
+ */
+ ret = write(to_child[1], "GO", 2);
+ assert_int_equal(2, ret);
+
+ /*
+ * Wait for the child process to check the DB state while the
+ * transaction is active
+ */
+ ret = read(to_parent[0], buf, 2);
+ assert_int_equal(2, ret);
+
+ /*
+ * commit the transaction
+ */
+ ret = ltdb->kv_ops->finish_write(ltdb);
+ assert_int_equal(0, ret);
+
+ /*
+ * Signal the child process
+ */
+ ret = write(to_child[1], "GO", 2);
+ assert_int_equal(2, ret);
+
+ w_pid = waitpid(pid, &wstatus, 0);
+ assert_int_equal(pid, w_pid);
+
+ assert_true(WIFEXITED(wstatus));
+
+ assert_int_equal(WEXITSTATUS(wstatus), 0);
+
+
+ TALLOC_FREE(tmp_ctx);
+}
+
+/*
+ * Ensure that deletes are not visible until the transaction has been
+ * committed.
+ */
+static void test_delete_transaction_isolation(void **state)
+{
+ int ret;
+ struct test_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct test_ctx);
+ struct ltdb_private *ltdb = get_ltdb(test_ctx->ldb);
+ struct ldb_val key;
+ struct ldb_val val;
+
+ const char *KEY1 = "KEY01";
+ const char *VAL1 = "VALUE01";
+
+ const char *KEY2 = "KEY02";
+ const char *VAL2 = "VALUE02";
+
+ /*
+ * Pipes etc to co-ordinate the processes
+ */
+ int to_child[2];
+ int to_parent[2];
+ char buf[2];
+ pid_t pid, w_pid;
+ int wstatus;
+
+ TALLOC_CTX *tmp_ctx;
+ tmp_ctx = talloc_new(test_ctx);
+ assert_non_null(tmp_ctx);
+
+
+ /*
+ * Add records to the database
+ */
+ ret = ltdb->kv_ops->begin_write(ltdb);
+ assert_int_equal(ret, 0);
+
+ key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY1);
+ key.length = strlen(KEY1) + 1;
+
+ val.data = (uint8_t *)talloc_strdup(tmp_ctx, VAL1);
+ val.length = strlen(VAL1) + 1;
+
+ ret = ltdb->kv_ops->store(ltdb, key, val, 0);
+ assert_int_equal(ret, 0);
+
+ key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY2);
+ key.length = strlen(KEY2) + 1;
+
+ val.data = (uint8_t *)talloc_strdup(tmp_ctx, VAL2);
+ val.length = strlen(VAL2) + 1;
+
+ ret = ltdb->kv_ops->store(ltdb, key, val, 0);
+ assert_int_equal(ret, 0);
+
+ ret = ltdb->kv_ops->finish_write(ltdb);
+ assert_int_equal(ret, 0);
+
+
+ ret = pipe(to_child);
+ assert_int_equal(ret, 0);
+ ret = pipe(to_parent);
+ assert_int_equal(ret, 0);
+ /*
+ * Now fork a new process
+ */
+
+ pid = fork();
+ if (pid == 0) {
+
+ struct ldb_context *ldb = NULL;
+ close(to_child[1]);
+ close(to_parent[0]);
+
+ /*
+ * Wait for the transaction to be started
+ */
+ ret = read(to_child[0], buf, 2);
+ if (ret != 2) {
+ print_error(__location__": read returned (%d)\n",
+ ret);
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ ldb = ldb_init(test_ctx, test_ctx->ev);
+ ret = ldb_connect(ldb, test_ctx->dbpath, 0, NULL);
+ if (ret != LDB_SUCCESS) {
+ print_error(__location__": ldb_connect returned (%d)\n",
+ ret);
+ exit(ret);
+ }
+
+ ltdb = get_ltdb(ldb);
+
+ ret = ltdb->kv_ops->lock_read(ldb->modules);
+ if (ret != LDB_SUCCESS) {
+ print_error(__location__": lock_read returned (%d)\n",
+ ret);
+ exit(ret);
+ }
+
+ /*
+ * Check that KEY1 is there
+ */
+ key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY1);
+ key.length = strlen(KEY1) + 1;
+
+ ret = ltdb->kv_ops->fetch_and_parse(ltdb, key, parse, &val);
+ if (ret != LDB_SUCCESS) {
+ print_error(__location__": fetch_and_parse returned "
+ "(%d)\n",
+ ret);
+ exit(ret);
+ }
+
+ if ((strlen(VAL1) + 1) != val.length) {
+ print_error(__location__": KEY1 value lengths different"
+ ", expected (%d) actual(%d)\n",
+ (int)(strlen(VAL1) + 1), (int)val.length);
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (memcmp(VAL1, val.data, strlen(VAL1)) != 0) {
+ print_error(__location__": KEY1 values different, "
+ "expected (%s) actual(%s)\n",
+ VAL1,
+ val.data);
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ /*
+ * Check that KEY2 is there
+ */
+
+ key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY2);
+ key.length = strlen(KEY2) + 1;
+
+ ret = ltdb->kv_ops->fetch_and_parse(ltdb, key, parse, &val);
+ if (ret != LDB_SUCCESS) {
+ print_error(__location__": fetch_and_parse returned "
+ "(%d)\n",
+ ret);
+ exit(ret);
+ }
+
+ if ((strlen(VAL2) + 1) != val.length) {
+ print_error(__location__": KEY2 value lengths different"
+ ", expected (%d) actual(%d)\n",
+ (int)(strlen(VAL2) + 1), (int)val.length);
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (memcmp(VAL2, val.data, strlen(VAL2)) != 0) {
+ print_error(__location__": KEY2 values different, "
+ "expected (%s) actual(%s)\n",
+ VAL2,
+ val.data);
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ ret = ltdb->kv_ops->unlock_read(ldb->modules);
+ if (ret != LDB_SUCCESS) {
+ print_error(__location__": unlock_read returned (%d)\n",
+ ret);
+ exit(ret);
+ }
+
+ /*
+ * Signal the other process to commit the transaction
+ */
+ ret = write(to_parent[1], "GO", 2);
+ if (ret != 2) {
+ print_error(__location__": write returned (%d)\n",
+ ret);
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ /*
+ * Wait for the transaction to be commited
+ */
+ ret = read(to_child[0], buf, 2);
+ if (ret != 2) {
+ print_error(__location__": read returned (%d)\n",
+ ret);
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ /*
+ * Check that KEY1 is there
+ */
+ ret = ltdb->kv_ops->lock_read(ldb->modules);
+ if (ret != LDB_SUCCESS) {
+ print_error(__location__": unlock_read returned (%d)\n",
+ ret);
+ exit(ret);
+ }
+ key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY1);
+ key.length = strlen(KEY1) + 1;
+
+ ret = ltdb->kv_ops->fetch_and_parse(ltdb, key, parse, &val);
+ if (ret != LDB_SUCCESS) {
+ print_error(__location__": fetch_and_parse returned "
+ "(%d)\n",
+ ret);
+ exit(ret);
+ }
+
+ if ((strlen(VAL1) + 1) != val.length) {
+ print_error(__location__": KEY1 value lengths different"
+ ", expected (%d) actual(%d)\n",
+ (int)(strlen(VAL1) + 1), (int)val.length);
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (memcmp(VAL1, val.data, strlen(VAL1)) != 0) {
+ print_error(__location__": KEY1 values different, "
+ "expected (%s) actual(%s)\n",
+ VAL1,
+ val.data);
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ /*
+ * Check that KEY2 is not there
+ */
+ key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY2);
+ key.length = strlen(KEY2 + 1);
+
+ ret = ltdb->kv_ops->lock_read(ldb->modules);
+ if (ret != LDB_SUCCESS) {
+ print_error(__location__": lock_read returned (%d)\n",
+ ret);
+ exit(ret);
+ }
+
+ ret = ltdb->kv_ops->fetch_and_parse(ltdb, key, parse, &val);
+ if (ret != LDB_ERR_NO_SUCH_OBJECT) {
+ print_error(__location__": fetch_and_parse returned "
+ "(%d)\n",
+ ret);
+ exit(ret);
+ }
+
+ ret = ltdb->kv_ops->unlock_read(ldb->modules);
+ if (ret != LDB_SUCCESS) {
+ print_error(__location__": unlock_read returned (%d)\n",
+ ret);
+ exit(ret);
+ }
+
+ exit(0);
+ }
+ close(to_child[0]);
+ close(to_parent[1]);
+
+ /*
+ * Begin a transaction and delete a record from the database
+ * but leave the transaction open
+ */
+ ret = ltdb->kv_ops->begin_write(ltdb);
+ assert_int_equal(ret, 0);
+
+ key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY2);
+ key.length = strlen(KEY2) + 1;
+
+ ret = ltdb->kv_ops->delete(ltdb, key);
+ assert_int_equal(ret, 0);
+ /*
+ * Signal the child process
+ */
+ ret = write(to_child[1], "GO", 2);
+ assert_int_equal(2, ret);
+
+ /*
+ * Wait for the child process to check the DB state while the
+ * transaction is active
+ */
+ ret = read(to_parent[0], buf, 2);
+ assert_int_equal(2, ret);
+
+ /*
+ * commit the transaction
+ */
+ ret = ltdb->kv_ops->finish_write(ltdb);
+ assert_int_equal(0, ret);
+
+ /*
+ * Signal the child process
+ */
+ ret = write(to_child[1], "GO", 2);
+ assert_int_equal(2, ret);
+
+ w_pid = waitpid(pid, &wstatus, 0);
+ assert_int_equal(pid, w_pid);
+
+ assert_true(WIFEXITED(wstatus));
+
+ assert_int_equal(WEXITSTATUS(wstatus), 0);
+
+
+ TALLOC_FREE(tmp_ctx);
+}
+
+
+int main(int argc, const char **argv)
+{
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(
+ test_add_get,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_delete,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_transaction_abort_write,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_transaction_abort_delete,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_read_outside_transaction,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_write_outside_transaction,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_delete_outside_transaction,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_iterate,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_update_in_iterate,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_write_transaction_isolation,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_delete_transaction_isolation,
+ setup,
+ teardown),
+ };
+
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/tests/ldb_lmdb_size_test.c b/tests/ldb_lmdb_size_test.c
new file mode 100644
index 0000000..af015fa
--- /dev/null
+++ b/tests/ldb_lmdb_size_test.c
@@ -0,0 +1,210 @@
+/*
+ * lmdb backend specific tests for ldb
+ * Tests for truncated index keys
+ *
+ * Copyright (C) Andrew Bartlett <abartlet@samba.org> 2018
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/*
+ * These tests confirm that database sizes of > 4GB are supported
+ * Due to the disk space requirement they are not run as part of the normal
+ * self test runs.
+ *
+ * Setup and tear down code copied from ldb_mod_op_test.c
+ */
+
+/*
+ * from cmocka.c:
+ * These headers or their equivalents should be included prior to
+ * including
+ * this header file.
+ *
+ * #include <stdarg.h>
+ * #include <stddef.h>
+ * #include <setjmp.h>
+ *
+ * This allows test applications to use custom definitions of C standard
+ * library functions and types.
+ *
+ */
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+#include <errno.h>
+#include <unistd.h>
+#include <talloc.h>
+#include <tevent.h>
+#include <ldb.h>
+#include <ldb_module.h>
+#include <ldb_private.h>
+#include <string.h>
+#include <ctype.h>
+
+#include <sys/wait.h>
+
+#include <lmdb.h>
+
+
+#define TEST_BE "mdb"
+
+struct ldbtest_ctx {
+ struct tevent_context *ev;
+ struct ldb_context *ldb;
+
+ const char *dbfile;
+ const char *lockfile; /* lockfile is separate */
+
+ const char *dbpath;
+};
+
+static void unlink_old_db(struct ldbtest_ctx *test_ctx)
+{
+ int ret;
+
+ errno = 0;
+ ret = unlink(test_ctx->lockfile);
+ if (ret == -1 && errno != ENOENT) {
+ fail();
+ }
+
+ errno = 0;
+ ret = unlink(test_ctx->dbfile);
+ if (ret == -1 && errno != ENOENT) {
+ fail();
+ }
+}
+
+static int ldbtest_noconn_setup(void **state)
+{
+ struct ldbtest_ctx *test_ctx;
+
+ test_ctx = talloc_zero(NULL, struct ldbtest_ctx);
+ assert_non_null(test_ctx);
+
+ test_ctx->ev = tevent_context_init(test_ctx);
+ assert_non_null(test_ctx->ev);
+
+ test_ctx->ldb = ldb_init(test_ctx, test_ctx->ev);
+ assert_non_null(test_ctx->ldb);
+
+ test_ctx->dbfile = talloc_strdup(test_ctx, "apitest.ldb");
+ assert_non_null(test_ctx->dbfile);
+
+ test_ctx->lockfile = talloc_asprintf(test_ctx, "%s-lock",
+ test_ctx->dbfile);
+ assert_non_null(test_ctx->lockfile);
+
+ test_ctx->dbpath = talloc_asprintf(test_ctx,
+ TEST_BE"://%s", test_ctx->dbfile);
+ assert_non_null(test_ctx->dbpath);
+
+ unlink_old_db(test_ctx);
+ *state = test_ctx;
+ return 0;
+}
+
+static int ldbtest_noconn_teardown(void **state)
+{
+ struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct ldbtest_ctx);
+
+ unlink_old_db(test_ctx);
+ talloc_free(test_ctx);
+ return 0;
+}
+
+static int ldbtest_setup(void **state)
+{
+ struct ldbtest_ctx *test_ctx;
+ int ret;
+
+ ldbtest_noconn_setup((void **) &test_ctx);
+
+ ret = ldb_connect(test_ctx->ldb, test_ctx->dbpath, 0, NULL);
+ assert_int_equal(ret, 0);
+
+ *state = test_ctx;
+ return 0;
+}
+
+static int ldbtest_teardown(void **state)
+{
+ struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct ldbtest_ctx);
+ ldbtest_noconn_teardown((void **) &test_ctx);
+ return 0;
+}
+
+static void test_db_size_gt_4GB(void **state)
+{
+ int ret, x;
+ struct ldb_message *msg;
+ struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct ldbtest_ctx);
+ const int MB = 1024 * 1024;
+ char *blob = NULL;
+
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_new(test_ctx);
+ assert_non_null(tmp_ctx);
+
+
+ blob = talloc_zero_size(tmp_ctx, (MB + 1));
+ assert_non_null(blob);
+ memset(blob, 'x', MB);
+
+
+ for (x = 0; x < 6144; x++) {
+ msg = ldb_msg_new(tmp_ctx);
+ assert_non_null(msg);
+
+ msg->dn = ldb_dn_new_fmt(msg, test_ctx->ldb, "dc=test%d", x);
+ assert_non_null(msg->dn);
+
+ ldb_transaction_start(test_ctx->ldb);
+ ret = ldb_msg_add_string(msg, "blob", blob);
+ assert_int_equal(ret, 0);
+
+ ret = ldb_add(test_ctx->ldb, msg);
+ assert_int_equal(ret, 0);
+ ldb_transaction_commit(test_ctx->ldb);
+
+ TALLOC_FREE(msg);
+ }
+ talloc_free(tmp_ctx);
+ {
+ struct stat s;
+ ret = stat(test_ctx->dbfile, &s);
+ assert_int_equal(ret, 0);
+ assert_true(s.st_size > (6144LL * MB));
+ }
+}
+
+int main(int argc, const char **argv)
+{
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(
+ test_db_size_gt_4GB,
+ ldbtest_setup,
+ ldbtest_teardown),
+ };
+
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/tests/ldb_lmdb_test.c b/tests/ldb_lmdb_test.c
new file mode 100644
index 0000000..a254a84
--- /dev/null
+++ b/tests/ldb_lmdb_test.c
@@ -0,0 +1,588 @@
+/*
+ * lmdb backend specific tests for ldb
+ *
+ * Copyright (C) Andrew Bartlett <abartlet@samba.org> 2018
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/*
+ * lmdb backend specific tests for ldb
+ *
+ * Setup and tear down code copied from ldb_mod_op_test.c
+ */
+
+/*
+ * from cmocka.c:
+ * These headers or their equivalents should be included prior to
+ * including
+ * this header file.
+ *
+ * #include <stdarg.h>
+ * #include <stddef.h>
+ * #include <setjmp.h>
+ *
+ * This allows test applications to use custom definitions of C standard
+ * library functions and types.
+ *
+ */
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+#include <errno.h>
+#include <unistd.h>
+#include <talloc.h>
+#include <tevent.h>
+#include <ldb.h>
+#include <ldb_module.h>
+#include <ldb_private.h>
+#include <string.h>
+#include <ctype.h>
+
+#include <sys/wait.h>
+
+#include "../ldb_tdb/ldb_tdb.h"
+#include "../ldb_mdb/ldb_mdb.h"
+
+#define TEST_BE "mdb"
+
+#define LMDB_MAX_KEY_SIZE 511
+
+struct ldbtest_ctx {
+ struct tevent_context *ev;
+ struct ldb_context *ldb;
+
+ const char *dbfile;
+ const char *lockfile; /* lockfile is separate */
+
+ const char *dbpath;
+};
+
+static void unlink_old_db(struct ldbtest_ctx *test_ctx)
+{
+ int ret;
+
+ errno = 0;
+ ret = unlink(test_ctx->lockfile);
+ if (ret == -1 && errno != ENOENT) {
+ fail();
+ }
+
+ errno = 0;
+ ret = unlink(test_ctx->dbfile);
+ if (ret == -1 && errno != ENOENT) {
+ fail();
+ }
+}
+
+static int ldbtest_noconn_setup(void **state)
+{
+ struct ldbtest_ctx *test_ctx;
+
+ test_ctx = talloc_zero(NULL, struct ldbtest_ctx);
+ assert_non_null(test_ctx);
+
+ test_ctx->ev = tevent_context_init(test_ctx);
+ assert_non_null(test_ctx->ev);
+
+ test_ctx->ldb = ldb_init(test_ctx, test_ctx->ev);
+ assert_non_null(test_ctx->ldb);
+
+ test_ctx->dbfile = talloc_strdup(test_ctx, "apitest.ldb");
+ assert_non_null(test_ctx->dbfile);
+
+ test_ctx->lockfile = talloc_asprintf(test_ctx, "%s-lock",
+ test_ctx->dbfile);
+ assert_non_null(test_ctx->lockfile);
+
+ test_ctx->dbpath = talloc_asprintf(test_ctx,
+ TEST_BE"://%s", test_ctx->dbfile);
+ assert_non_null(test_ctx->dbpath);
+
+ unlink_old_db(test_ctx);
+ *state = test_ctx;
+ return 0;
+}
+
+static int ldbtest_noconn_teardown(void **state)
+{
+ struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct ldbtest_ctx);
+
+ unlink_old_db(test_ctx);
+ talloc_free(test_ctx);
+ return 0;
+}
+
+static int ldbtest_setup(void **state)
+{
+ struct ldbtest_ctx *test_ctx;
+ int ret;
+ struct ldb_ldif *ldif;
+ const char *index_ldif = \
+ "dn: @INDEXLIST\n"
+ "@IDXGUID: objectUUID\n"
+ "@IDX_DN_GUID: GUID\n"
+ "\n";
+
+ ldbtest_noconn_setup((void **) &test_ctx);
+
+ ret = ldb_connect(test_ctx->ldb, test_ctx->dbpath, 0, NULL);
+ assert_int_equal(ret, 0);
+
+ while ((ldif = ldb_ldif_read_string(test_ctx->ldb, &index_ldif))) {
+ ret = ldb_add(test_ctx->ldb, ldif->msg);
+ assert_int_equal(ret, LDB_SUCCESS);
+ }
+ *state = test_ctx;
+ return 0;
+}
+
+static int ldbtest_teardown(void **state)
+{
+ struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct ldbtest_ctx);
+ ldbtest_noconn_teardown((void **) &test_ctx);
+ return 0;
+}
+
+static void test_ldb_add_key_len_gt_max(void **state)
+{
+ int ret;
+ int xs_size = 0;
+ struct ldb_message *msg;
+ struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct ldbtest_ctx);
+ char *xs = NULL;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_new(test_ctx);
+ assert_non_null(tmp_ctx);
+
+ msg = ldb_msg_new(tmp_ctx);
+ assert_non_null(msg);
+
+ /*
+ * The zero terminator is part of the key if we were not in
+ * GUID mode
+ */
+
+ xs_size = LMDB_MAX_KEY_SIZE - 7; /* "dn=dc=" and the zero terminator */
+ xs_size += 1; /* want key on char too long */
+ xs = talloc_zero_size(tmp_ctx, (xs_size + 1));
+ memset(xs, 'x', xs_size);
+
+ msg->dn = ldb_dn_new_fmt(msg, test_ctx->ldb, "dc=%s", xs);
+ assert_non_null(msg->dn);
+
+ ret = ldb_msg_add_string(msg, "cn", "test_cn_val");
+ assert_int_equal(ret, 0);
+
+ ret = ldb_msg_add_string(msg, "objectUUID", "0123456789abcdef");
+ assert_int_equal(ret, 0);
+
+ ret = ldb_add(test_ctx->ldb, msg);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ talloc_free(tmp_ctx);
+}
+
+static void test_ldb_add_key_len_2x_gt_max(void **state)
+{
+ int ret;
+ int xs_size = 0;
+ struct ldb_message *msg;
+ struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct ldbtest_ctx);
+ char *xs = NULL;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_new(test_ctx);
+ assert_non_null(tmp_ctx);
+
+ msg = ldb_msg_new(tmp_ctx);
+ assert_non_null(msg);
+
+ /*
+ * The zero terminator is part of the key if we were not in
+ * GUID mode
+ */
+
+ xs_size = 2 * LMDB_MAX_KEY_SIZE;
+ xs = talloc_zero_size(tmp_ctx, (xs_size + 1));
+ memset(xs, 'x', xs_size);
+
+ msg->dn = ldb_dn_new_fmt(msg, test_ctx->ldb, "dc=%s", xs);
+ assert_non_null(msg->dn);
+
+ ret = ldb_msg_add_string(msg, "cn", "test_cn_val");
+ assert_int_equal(ret, 0);
+
+ ret = ldb_msg_add_string(msg, "objectUUID", "0123456789abcdef");
+ assert_int_equal(ret, 0);
+
+ ret = ldb_add(test_ctx->ldb, msg);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ talloc_free(tmp_ctx);
+}
+
+static void test_ldb_add_key_len_eq_max(void **state)
+{
+ int ret;
+ int xs_size = 0;
+ struct ldb_message *msg;
+ struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct ldbtest_ctx);
+ char *xs = NULL;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_new(test_ctx);
+ assert_non_null(tmp_ctx);
+
+ msg = ldb_msg_new(tmp_ctx);
+ assert_non_null(msg);
+
+ /*
+ * The zero terminator is part of the key if we were not in
+ * GUID mode
+ */
+
+ xs_size = LMDB_MAX_KEY_SIZE - 7; /* "dn=dc=" and the zero terminator */
+ xs = talloc_zero_size(tmp_ctx, (xs_size + 1));
+ memset(xs, 'x', xs_size);
+
+ msg->dn = ldb_dn_new_fmt(msg, test_ctx->ldb, "dc=%s", xs);
+ assert_non_null(msg->dn);
+
+ ret = ldb_msg_add_string(msg, "cn", "test_cn_val");
+ assert_int_equal(ret, 0);
+
+ ret = ldb_msg_add_string(msg, "objectUUID", "0123456789abcdef");
+ assert_int_equal(ret, 0);
+
+ ret = ldb_add(test_ctx->ldb, msg);
+ assert_int_equal(ret, 0);
+
+ talloc_free(tmp_ctx);
+}
+
+static int ldbtest_setup_noguid(void **state)
+{
+ struct ldbtest_ctx *test_ctx;
+ int ret;
+
+ ldbtest_noconn_setup((void **) &test_ctx);
+
+ ret = ldb_connect(test_ctx->ldb, test_ctx->dbpath, 0, NULL);
+ assert_int_equal(ret, 0);
+
+ *state = test_ctx;
+ return 0;
+}
+
+static void test_ldb_add_special_key_len_gt_max(void **state)
+{
+ int ret;
+ int xs_size = 0;
+ struct ldb_message *msg;
+ struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct ldbtest_ctx);
+ char *xs = NULL;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_new(test_ctx);
+ assert_non_null(tmp_ctx);
+
+ msg = ldb_msg_new(tmp_ctx);
+ assert_non_null(msg);
+
+ /*
+ * The zero terminator is part of the key if we were not in
+ * GUID mode
+ */
+
+ xs_size = LMDB_MAX_KEY_SIZE - 5; /* "dn=@" and the zero terminator */
+ xs_size += 1; /* want key on char too long */
+ xs = talloc_zero_size(tmp_ctx, (xs_size + 1));
+ memset(xs, 'x', xs_size);
+
+ msg->dn = ldb_dn_new_fmt(msg, test_ctx->ldb, "@%s", xs);
+ assert_non_null(msg->dn);
+
+ ret = ldb_msg_add_string(msg, "cn", "test_cn_val");
+ assert_int_equal(ret, 0);
+
+ ret = ldb_add(test_ctx->ldb, msg);
+ assert_int_equal(ret, LDB_ERR_PROTOCOL_ERROR);
+
+ talloc_free(tmp_ctx);
+}
+
+static void test_ldb_add_special_key_len_eq_max(void **state)
+{
+ int ret;
+ int xs_size = 0;
+ struct ldb_message *msg;
+ struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct ldbtest_ctx);
+ char *xs = NULL;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_new(test_ctx);
+ assert_non_null(tmp_ctx);
+
+ msg = ldb_msg_new(tmp_ctx);
+ assert_non_null(msg);
+
+ /*
+ * The zero terminator is part of the key if we were not in
+ * GUID mode
+ */
+
+ xs_size = LMDB_MAX_KEY_SIZE - 5; /* "dn=@" and the zero terminator */
+ xs = talloc_zero_size(tmp_ctx, (xs_size + 1));
+ memset(xs, 'x', xs_size);
+
+ msg->dn = ldb_dn_new_fmt(msg, test_ctx->ldb, "@%s", xs);
+ assert_non_null(msg->dn);
+
+ ret = ldb_msg_add_string(msg, "cn", "test_cn_val");
+ assert_int_equal(ret, 0);
+
+ ret = ldb_add(test_ctx->ldb, msg);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ talloc_free(tmp_ctx);
+}
+
+static void test_ldb_add_dn_no_guid_mode(void **state)
+{
+ int ret;
+ int xs_size = 0;
+ struct ldb_message *msg;
+ struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct ldbtest_ctx);
+ char *xs = NULL;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_new(test_ctx);
+ assert_non_null(tmp_ctx);
+
+ msg = ldb_msg_new(tmp_ctx);
+ assert_non_null(msg);
+
+ /*
+ * The zero terminator is part of the key if we were not in
+ * GUID mode
+ */
+
+ xs_size = LMDB_MAX_KEY_SIZE - 7; /* "dn=dc=" and the zero terminator */
+ xs_size += 1; /* want key on char too long */
+ xs = talloc_zero_size(tmp_ctx, (xs_size + 1));
+ memset(xs, 'x', xs_size);
+
+ msg->dn = ldb_dn_new_fmt(msg, test_ctx->ldb, "dc=%s", xs);
+ assert_non_null(msg->dn);
+
+ ret = ldb_msg_add_string(msg, "cn", "test_cn_val");
+ assert_int_equal(ret, 0);
+
+ ret = ldb_msg_add_string(msg, "objectUUID", "0123456789abcdef");
+ assert_int_equal(ret, 0);
+
+ ret = ldb_add(test_ctx->ldb, msg);
+ assert_int_equal(ret, LDB_ERR_UNWILLING_TO_PERFORM);
+
+ talloc_free(tmp_ctx);
+}
+
+static struct MDB_env *get_mdb_env(struct ldb_context *ldb)
+{
+ void *data = NULL;
+ struct ltdb_private *ltdb = NULL;
+ struct lmdb_private *lmdb = NULL;
+ struct MDB_env *env = NULL;
+
+ data = ldb_module_get_private(ldb->modules);
+ assert_non_null(data);
+
+ ltdb = talloc_get_type(data, struct ltdb_private);
+ assert_non_null(ltdb);
+
+ lmdb = ltdb->lmdb_private;
+ assert_non_null(lmdb);
+
+ env = lmdb->env;
+ assert_non_null(env);
+
+ return env;
+}
+
+static void test_multiple_opens(void **state)
+{
+ struct ldb_context *ldb1 = NULL;
+ struct ldb_context *ldb2 = NULL;
+ struct ldb_context *ldb3 = NULL;
+ struct MDB_env *env1 = NULL;
+ struct MDB_env *env2 = NULL;
+ struct MDB_env *env3 = NULL;
+ int ret;
+ struct ldbtest_ctx *test_ctx = NULL;
+
+ test_ctx = talloc_get_type_abort(*state, struct ldbtest_ctx);
+
+ /*
+ * Open the database again
+ */
+ ldb1 = ldb_init(test_ctx, test_ctx->ev);
+ ret = ldb_connect(ldb1, test_ctx->dbpath, LDB_FLG_RDONLY, NULL);
+ assert_int_equal(ret, 0);
+
+ ldb2 = ldb_init(test_ctx, test_ctx->ev);
+ ret = ldb_connect(ldb2, test_ctx->dbpath, LDB_FLG_RDONLY, NULL);
+ assert_int_equal(ret, 0);
+
+ ldb3 = ldb_init(test_ctx, test_ctx->ev);
+ ret = ldb_connect(ldb3, test_ctx->dbpath, 0, NULL);
+ assert_int_equal(ret, 0);
+ /*
+ * We now have 3 ldb's open pointing to the same on disk database
+ * they should all share the same MDB_env
+ */
+ env1 = get_mdb_env(ldb1);
+ env2 = get_mdb_env(ldb2);
+ env3 = get_mdb_env(ldb3);
+
+ assert_ptr_equal(env1, env2);
+ assert_ptr_equal(env1, env3);
+}
+
+static void test_multiple_opens_across_fork(void **state)
+{
+ struct ldb_context *ldb1 = NULL;
+ struct ldb_context *ldb2 = NULL;
+ struct MDB_env *env1 = NULL;
+ struct MDB_env *env2 = NULL;
+ int ret;
+ struct ldbtest_ctx *test_ctx = NULL;
+ int pipes[2];
+ char buf[2];
+ int wstatus;
+ pid_t pid, child_pid;
+
+ test_ctx = talloc_get_type_abort(*state, struct ldbtest_ctx);
+
+ /*
+ * Open the database again
+ */
+ ldb1 = ldb_init(test_ctx, test_ctx->ev);
+ ret = ldb_connect(ldb1, test_ctx->dbpath, LDB_FLG_RDONLY, NULL);
+ assert_int_equal(ret, 0);
+
+ ldb2 = ldb_init(test_ctx, test_ctx->ev);
+ ret = ldb_connect(ldb2, test_ctx->dbpath, LDB_FLG_RDONLY, NULL);
+ assert_int_equal(ret, 0);
+
+ env1 = get_mdb_env(ldb1);
+ env2 = get_mdb_env(ldb2);
+
+ ret = pipe(pipes);
+ assert_int_equal(ret, 0);
+
+ child_pid = fork();
+ if (child_pid == 0) {
+ struct ldb_context *ldb3 = NULL;
+ struct MDB_env *env3 = NULL;
+
+ close(pipes[0]);
+ ldb3 = ldb_init(test_ctx, test_ctx->ev);
+ ret = ldb_connect(ldb3, test_ctx->dbpath, 0, NULL);
+ if (ret != 0) {
+ print_error(__location__": ldb_connect returned (%d)\n",
+ ret);
+ exit(ret);
+ }
+ env3 = get_mdb_env(ldb3);
+ if (env1 != env2) {
+ print_error(__location__": env1 != env2\n");
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (env1 == env3) {
+ print_error(__location__": env1 == env3\n");
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+ ret = write(pipes[1], "GO", 2);
+ if (ret != 2) {
+ print_error(__location__
+ " write returned (%d)",
+ ret);
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+ exit(LDB_SUCCESS);
+ }
+ close(pipes[1]);
+ ret = read(pipes[0], buf, 2);
+ assert_int_equal(ret, 2);
+
+ pid = waitpid(child_pid, &wstatus, 0);
+ assert_int_equal(pid, child_pid);
+
+ assert_true(WIFEXITED(wstatus));
+
+ assert_int_equal(WEXITSTATUS(wstatus), 0);
+}
+
+int main(int argc, const char **argv)
+{
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(
+ test_ldb_add_key_len_eq_max,
+ ldbtest_setup,
+ ldbtest_teardown),
+ cmocka_unit_test_setup_teardown(
+ test_ldb_add_key_len_gt_max,
+ ldbtest_setup,
+ ldbtest_teardown),
+ cmocka_unit_test_setup_teardown(
+ test_ldb_add_key_len_2x_gt_max,
+ ldbtest_setup,
+ ldbtest_teardown),
+ cmocka_unit_test_setup_teardown(
+ test_ldb_add_special_key_len_eq_max,
+ ldbtest_setup_noguid,
+ ldbtest_teardown),
+ cmocka_unit_test_setup_teardown(
+ test_ldb_add_special_key_len_gt_max,
+ ldbtest_setup_noguid,
+ ldbtest_teardown),
+ cmocka_unit_test_setup_teardown(
+ test_ldb_add_dn_no_guid_mode,
+ ldbtest_setup_noguid,
+ ldbtest_teardown),
+ cmocka_unit_test_setup_teardown(
+ test_multiple_opens,
+ ldbtest_setup,
+ ldbtest_teardown),
+ cmocka_unit_test_setup_teardown(
+ test_multiple_opens_across_fork,
+ ldbtest_setup,
+ ldbtest_teardown),
+ };
+
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/tests/ldb_mod_op_test.c b/tests/ldb_mod_op_test.c
index 0f8642d..01667af 100644
--- a/tests/ldb_mod_op_test.c
+++ b/tests/ldb_mod_op_test.c
@@ -38,6 +38,12 @@
#define TEST_BE DEFAULT_BE
#endif /* TEST_BE */
+#ifdef TEST_LMDB
+#include "lmdb.h"
+#include "../ldb_tdb/ldb_tdb.h"
+#include "../ldb_mdb/ldb_mdb.h"
+#endif
+
struct ldbtest_ctx {
struct tevent_context *ev;
struct ldb_context *ldb;
@@ -202,6 +208,16 @@ static void test_ldif_message_redacted(void **state)
static int ldbtest_setup(void **state)
{
struct ldbtest_ctx *test_ctx;
+ struct ldb_ldif *ldif;
+#ifdef GUID_IDX
+ const char *index_ldif = \
+ "dn: @INDEXLIST\n"
+ "@IDXGUID: objectUUID\n"
+ "@IDX_DN_GUID: GUID\n"
+ "\n";
+#else
+ const char *index_ldif = "\n";
+#endif
int ret;
ldbtest_noconn_setup((void **) &test_ctx);
@@ -209,6 +225,10 @@ static int ldbtest_setup(void **state)
ret = ldb_connect(test_ctx->ldb, test_ctx->dbpath, 0, NULL);
assert_int_equal(ret, 0);
+ while ((ldif = ldb_ldif_read_string(test_ctx->ldb, &index_ldif))) {
+ ret = ldb_add(test_ctx->ldb, ldif->msg);
+ assert_int_equal(ret, LDB_SUCCESS);
+ }
*state = test_ctx;
return 0;
}
@@ -241,6 +261,9 @@ static void test_ldb_add(void **state)
ret = ldb_msg_add_string(msg, "cn", "test_cn_val");
assert_int_equal(ret, 0);
+ ret = ldb_msg_add_string(msg, "objectUUID", "0123456789abcdef");
+ assert_int_equal(ret, 0);
+
ret = ldb_add(test_ctx->ldb, msg);
assert_int_equal(ret, 0);
@@ -279,6 +302,9 @@ static void test_ldb_search(void **state)
ret = ldb_msg_add_string(msg, "cn", "test_cn_val1");
assert_int_equal(ret, 0);
+ ret = ldb_msg_add_string(msg, "objectUUID", "0123456789abcde1");
+ assert_int_equal(ret, 0);
+
ret = ldb_add(test_ctx->ldb, msg);
assert_int_equal(ret, 0);
@@ -294,6 +320,9 @@ static void test_ldb_search(void **state)
ret = ldb_msg_add_string(msg, "cn", "test_cn_val2");
assert_int_equal(ret, 0);
+ ret = ldb_msg_add_string(msg, "objectUUID", "0123456789abcde2");
+ assert_int_equal(ret, 0);
+
ret = ldb_add(test_ctx->ldb, msg);
assert_int_equal(ret, 0);
@@ -390,7 +419,8 @@ static void assert_dn_doesnt_exist(struct ldbtest_ctx *test_ctx,
static void add_dn_with_cn(struct ldbtest_ctx *test_ctx,
struct ldb_dn *dn,
- const char *cn_value)
+ const char *cn_value,
+ const char *uuid_value)
{
int ret;
TALLOC_CTX *tmp_ctx;
@@ -409,6 +439,9 @@ static void add_dn_with_cn(struct ldbtest_ctx *test_ctx,
ret = ldb_msg_add_string(msg, "cn", cn_value);
assert_int_equal(ret, LDB_SUCCESS);
+ ret = ldb_msg_add_string(msg, "objectUUID", uuid_value);
+ assert_int_equal(ret, 0);
+
ret = ldb_add(test_ctx->ldb, msg);
assert_int_equal(ret, LDB_SUCCESS);
@@ -428,7 +461,9 @@ static void test_ldb_del(void **state)
dn = ldb_dn_new_fmt(test_ctx, test_ctx->ldb, "%s", basedn);
assert_non_null(dn);
- add_dn_with_cn(test_ctx, dn, "test_del_cn_val");
+ add_dn_with_cn(test_ctx, dn,
+ "test_del_cn_val",
+ "0123456789abcdef");
ret = ldb_delete(test_ctx->ldb, dn);
assert_int_equal(ret, LDB_SUCCESS);
@@ -552,7 +587,8 @@ static void test_ldb_build_search_req(void **state)
static void add_keyval(struct ldbtest_ctx *test_ctx,
const char *key,
- const char *val)
+ const char *val,
+ const char *uuid)
{
int ret;
struct ldb_message *msg;
@@ -566,6 +602,9 @@ static void add_keyval(struct ldbtest_ctx *test_ctx,
ret = ldb_msg_add_string(msg, key, val);
assert_int_equal(ret, 0);
+ ret = ldb_msg_add_string(msg, "objectUUID", uuid);
+ assert_int_equal(ret, 0);
+
ret = ldb_add(test_ctx->ldb, msg);
assert_int_equal(ret, 0);
@@ -601,7 +640,8 @@ static void test_transactions(void **state)
ret = ldb_transaction_start(test_ctx->ldb);
assert_int_equal(ret, 0);
- add_keyval(test_ctx, "vegetable", "carrot");
+ add_keyval(test_ctx, "vegetable", "carrot",
+ "0123456789abcde0");
/* commit lev-0 transaction */
ret = ldb_transaction_commit(test_ctx->ldb);
@@ -611,7 +651,8 @@ static void test_transactions(void **state)
ret = ldb_transaction_start(test_ctx->ldb);
assert_int_equal(ret, 0);
- add_keyval(test_ctx, "fruit", "apple");
+ add_keyval(test_ctx, "fruit", "apple",
+ "0123456789abcde1");
/* abort lev-1 nested transaction */
ret = ldb_transaction_cancel(test_ctx->ldb);
@@ -626,6 +667,48 @@ static void test_transactions(void **state)
assert_int_equal(res->count, 0);
}
+static void test_nested_transactions(void **state)
+{
+ int ret;
+ struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct ldbtest_ctx);
+ struct ldb_result *res;
+
+ /* start lev-0 transaction */
+ ret = ldb_transaction_start(test_ctx->ldb);
+ assert_int_equal(ret, 0);
+
+ add_keyval(test_ctx, "vegetable", "carrot",
+ "0123456789abcde0");
+
+
+ /* start another lev-1 nested transaction */
+ ret = ldb_transaction_start(test_ctx->ldb);
+ assert_int_equal(ret, 0);
+
+ add_keyval(test_ctx, "fruit", "apple",
+ "0123456789abcde1");
+
+ /* abort lev-1 nested transaction */
+ ret = ldb_transaction_cancel(test_ctx->ldb);
+ assert_int_equal(ret, 0);
+
+ /* commit lev-0 transaction */
+ ret = ldb_transaction_commit(test_ctx->ldb);
+ assert_int_equal(ret, 0);
+
+ res = get_keyval(test_ctx, "vegetable", "carrot");
+ assert_non_null(res);
+ assert_int_equal(res->count, 1);
+
+ /* This documents the current ldb behaviour, i.e. nested
+ * transactions are not supported. And the cancellation of the nested
+ * transaction has no effect.
+ */
+ res = get_keyval(test_ctx, "fruit", "apple");
+ assert_non_null(res);
+ assert_int_equal(res->count, 1);
+}
struct ldb_mod_test_ctx {
struct ldbtest_ctx *ldb_test_ctx;
const char *entry_dn;
@@ -785,6 +868,7 @@ static int ldb_modify_test_setup(void **state)
struct ldb_mod_test_ctx *mod_test_ctx;
struct keyval kvs[] = {
{ "cn", "test_mod_cn" },
+ { "objectUUID", "0123456789abcdef"},
{ NULL, NULL },
};
@@ -1089,6 +1173,7 @@ static int ldb_search_test_setup(void **state)
{ "cn", "test_search_cn2" },
{ "uid", "test_search_uid" },
{ "uid", "test_search_uid2" },
+ { "objectUUID", "0123456789abcde0"},
{ NULL, NULL },
};
struct keyval kvs2[] = {
@@ -1096,6 +1181,7 @@ static int ldb_search_test_setup(void **state)
{ "cn", "test_search_2_cn2" },
{ "uid", "test_search_2_uid" },
{ "uid", "test_search_2_uid2" },
+ { "objectUUID", "0123456789abcde1"},
{ NULL, NULL },
};
@@ -1444,6 +1530,7 @@ static int test_ldb_search_against_transaction_callback1(struct ldb_request *req
struct ldb_message *msg;
TALLOC_FREE(ctx->test_ctx->ldb);
TALLOC_FREE(ctx->test_ctx->ev);
+ close(pipes[0]);
ctx->test_ctx->ev = tevent_context_init(ctx->test_ctx);
if (ctx->test_ctx->ev == NULL) {
exit(LDB_ERR_OPERATIONS_ERROR);
@@ -1492,6 +1579,12 @@ static int test_ldb_search_against_transaction_callback1(struct ldb_request *req
exit(LDB_ERR_OPERATIONS_ERROR);
}
+ ret = ldb_msg_add_string(msg, "objectUUID",
+ "0123456789abcdef");
+ if (ret != 0) {
+ exit(ret);
+ }
+
ret = ldb_add(ctx->test_ctx->ldb, msg);
if (ret != 0) {
exit(ret);
@@ -1500,7 +1593,7 @@ static int test_ldb_search_against_transaction_callback1(struct ldb_request *req
ret = ldb_transaction_commit(ctx->test_ctx->ldb);
exit(ret);
}
-
+ close(pipes[1]);
ret = read(pipes[0], buf, 2);
assert_int_equal(ret, 2);
@@ -1674,6 +1767,7 @@ static int test_ldb_modify_during_search_callback1(struct ldb_request *req,
struct ldb_dn *dn, *new_dn;
TALLOC_FREE(ctx->test_ctx->ldb);
TALLOC_FREE(ctx->test_ctx->ev);
+ close(pipes[0]);
ctx->test_ctx->ev = tevent_context_init(ctx->test_ctx);
if (ctx->test_ctx->ev == NULL) {
exit(LDB_ERR_OPERATIONS_ERROR);
@@ -1740,6 +1834,7 @@ static int test_ldb_modify_during_search_callback1(struct ldb_request *req,
struct ldb_message_element *el;
TALLOC_FREE(ctx->test_ctx->ldb);
TALLOC_FREE(ctx->test_ctx->ev);
+ close(pipes[0]);
ctx->test_ctx->ev = tevent_context_init(ctx->test_ctx);
if (ctx->test_ctx->ev == NULL) {
exit(LDB_ERR_OPERATIONS_ERROR);
@@ -1815,6 +1910,7 @@ static int test_ldb_modify_during_search_callback1(struct ldb_request *req,
* sending the "GO" as it is blocked at ldb_transaction_start().
*/
+ close(pipes[1]);
ret = read(pipes[0], buf, 2);
assert_int_equal(ret, 2);
@@ -1854,10 +1950,13 @@ static void test_ldb_modify_during_search(void **state, bool add_index,
ret = ldb_msg_add_string(msg, "@IDXATTR", "cn");
assert_int_equal(ret, LDB_SUCCESS);
-
ret = ldb_add(search_test_ctx->ldb_test_ctx->ldb,
msg);
-
+ if (ret == LDB_ERR_ENTRY_ALREADY_EXISTS) {
+ msg->elements[0].flags = LDB_FLAG_MOD_ADD;
+ ret = ldb_modify(search_test_ctx->ldb_test_ctx->ldb,
+ msg);
+ }
assert_int_equal(ret, LDB_SUCCESS);
}
@@ -1986,6 +2085,7 @@ static int test_ldb_modify_during_whole_search_callback1(struct ldb_request *req
struct ldb_message_element *el;
TALLOC_FREE(ctx->test_ctx->ldb);
TALLOC_FREE(ctx->test_ctx->ev);
+ close(pipes[0]);
ctx->test_ctx->ev = tevent_context_init(ctx->test_ctx);
if (ctx->test_ctx->ev == NULL) {
exit(LDB_ERR_OPERATIONS_ERROR);
@@ -2048,6 +2148,7 @@ static int test_ldb_modify_during_whole_search_callback1(struct ldb_request *req
exit(ret);
}
+ close(pipes[1]);
ret = read(pipes[0], buf, 2);
assert_int_equal(ret, 2);
@@ -2260,6 +2361,7 @@ static void test_ldb_modify_before_ldb_wait(void **state)
struct ldb_message_element *el;
TALLOC_FREE(search_test_ctx->ldb_test_ctx->ldb);
TALLOC_FREE(search_test_ctx->ldb_test_ctx->ev);
+ close(pipes[0]);
search_test_ctx->ldb_test_ctx->ev = tevent_context_init(search_test_ctx->ldb_test_ctx);
if (search_test_ctx->ldb_test_ctx->ev == NULL) {
exit(LDB_ERR_OPERATIONS_ERROR);
@@ -2328,6 +2430,7 @@ static void test_ldb_modify_before_ldb_wait(void **state)
ret = ldb_transaction_commit(search_test_ctx->ldb_test_ctx->ldb);
exit(ret);
}
+ close(pipes[1]);
ret = read(pipes[0], buf, 2);
assert_int_equal(ret, 2);
@@ -2384,6 +2487,269 @@ static void test_ldb_modify_before_ldb_wait(void **state)
assert_int_equal(res2->count, 1);
}
+/*
+ * This test is also complex.
+ * The purpose is to test if a modify can occur during an ldb_search()
+ * This would be a failure if if in process
+ * (1) and (2):
+ * - (1) ltdb_search() starts and calls back for one entry
+ * - (2) one of the entries to be matched is modified
+ * - (1) the indexed search tries to return the modified entry, but
+ * it is no longer found, either:
+ * - despite it still matching (dn changed)
+ * - it no longer matching (attrs changed)
+ *
+ * We also try un-indexed to show that the behaviour differs on this
+ * point, which it should not (an index should only impact search
+ * speed).
+ */
+
+/*
+ * This purpose of this callback is to trigger a write in the callback
+ * so as to change in in-memory index code while looping over the
+ * index result.
+ */
+
+static int test_ldb_callback_modify_during_search_callback1(struct ldb_request *req,
+ struct ldb_reply *ares)
+{
+ int ret;
+ struct modify_during_search_test_ctx *ctx = req->context;
+ struct ldb_dn *dn = NULL, *new_dn = NULL;
+ TALLOC_CTX *tmp_ctx = talloc_new(ctx->test_ctx);
+ struct ldb_message *msg = NULL;
+
+ assert_non_null(tmp_ctx);
+
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+ {
+ const struct ldb_val *cn_val
+ = ldb_dn_get_component_val(ares->message->dn, 0);
+ const char *cn = (char *)cn_val->data;
+ ctx->res_count++;
+ if (strcmp(cn, "test_search_cn") == 0) {
+ ctx->got_cn = true;
+ } else if (strcmp(cn, "test_search_2_cn") == 0) {
+ ctx->got_2_cn = true;
+ }
+ if (ctx->res_count == 2) {
+ return LDB_SUCCESS;
+ }
+ break;
+ }
+ case LDB_REPLY_REFERRAL:
+ return LDB_SUCCESS;
+
+ case LDB_REPLY_DONE:
+ return ldb_request_done(req, LDB_SUCCESS);
+ }
+
+ if (ctx->rename) {
+ if (ctx->got_2_cn) {
+ /* Modify this one */
+ dn = ldb_dn_new_fmt(tmp_ctx,
+ ctx->test_ctx->ldb,
+ "cn=test_search_2_cn,%s",
+ ldb_dn_get_linearized(ctx->basedn));
+ } else {
+ dn = ldb_dn_new_fmt(tmp_ctx,
+ ctx->test_ctx->ldb,
+ "cn=test_search_cn,%s",
+ ldb_dn_get_linearized(ctx->basedn));
+ }
+ assert_non_null(dn);
+
+ new_dn = ldb_dn_new_fmt(tmp_ctx,
+ ctx->test_ctx->ldb,
+ "cn=test_search_cn_renamed,"
+ "dc=not_search_test_entry");
+ assert_non_null(new_dn);
+
+ ret = ldb_rename(ctx->test_ctx->ldb, dn, new_dn);
+ assert_int_equal(ret, 0);
+
+ } else {
+ if (ctx->got_2_cn) {
+ /* Delete this one */
+ dn = ldb_dn_new_fmt(tmp_ctx,
+ ctx->test_ctx->ldb,
+ "cn=test_search_2_cn,%s",
+ ldb_dn_get_linearized(ctx->basedn));
+ } else {
+ dn = ldb_dn_new_fmt(tmp_ctx,
+ ctx->test_ctx->ldb,
+ "cn=test_search_cn,%s",
+ ldb_dn_get_linearized(ctx->basedn));
+ }
+ assert_non_null(dn);
+
+ ret = ldb_delete(ctx->test_ctx->ldb, dn);
+ assert_int_equal(ret, 0);
+ }
+
+ /*
+ * Now fill in the position we just removed from the
+ * index to ensure we fail the test (otherwise we just read
+ * past the end of the array and find the value we wanted to
+ * skip)
+ */
+ msg = ldb_msg_new(tmp_ctx);
+ assert_non_null(msg);
+
+ /* We deliberatly use ou= not cn= here */
+ msg->dn = ldb_dn_new_fmt(msg,
+ ctx->test_ctx->ldb,
+ "ou=test_search_cn_extra,%s",
+ ldb_dn_get_linearized(ctx->basedn));
+
+ ret = ldb_msg_add_string(msg,
+ "objectUUID",
+ "0123456789abcde3");
+
+ ret = ldb_add(ctx->test_ctx->ldb,
+ msg);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ TALLOC_FREE(tmp_ctx);
+ return LDB_SUCCESS;
+}
+
+static void test_ldb_callback_modify_during_search(void **state, bool add_index,
+ bool rename)
+{
+ struct search_test_ctx *search_test_ctx = talloc_get_type_abort(*state,
+ struct search_test_ctx);
+ struct modify_during_search_test_ctx
+ ctx =
+ { .res_count = 0,
+ .test_ctx = search_test_ctx->ldb_test_ctx,
+ .rename = rename
+ };
+
+ int ret;
+ struct ldb_request *req;
+
+ ret = ldb_transaction_start(search_test_ctx->ldb_test_ctx->ldb);
+ assert_int_equal(ret, 0);
+
+ if (add_index) {
+ struct ldb_message *msg;
+ struct ldb_dn *indexlist = ldb_dn_new(search_test_ctx,
+ search_test_ctx->ldb_test_ctx->ldb,
+ "@INDEXLIST");
+ assert_non_null(indexlist);
+
+ msg = ldb_msg_new(search_test_ctx);
+ assert_non_null(msg);
+
+ msg->dn = indexlist;
+
+ ret = ldb_msg_add_string(msg, "@IDXONE", "1");
+ assert_int_equal(ret, LDB_SUCCESS);
+ ret = ldb_msg_add_string(msg, "@IDXATTR", "cn");
+ assert_int_equal(ret, LDB_SUCCESS);
+ ret = ldb_add(search_test_ctx->ldb_test_ctx->ldb,
+ msg);
+ if (ret == LDB_ERR_ENTRY_ALREADY_EXISTS) {
+ msg->elements[0].flags = LDB_FLAG_MOD_ADD;
+ msg->elements[1].flags = LDB_FLAG_MOD_ADD;
+ ret = ldb_modify(search_test_ctx->ldb_test_ctx->ldb,
+ msg);
+ }
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ /*
+ * Now bring the IDXONE index into memory by modifying
+ * it. This exposes an issue in ldb_tdb
+ */
+ msg = ldb_msg_new(search_test_ctx);
+ assert_non_null(msg);
+
+ msg->dn = ldb_dn_new_fmt(search_test_ctx,
+ search_test_ctx->ldb_test_ctx->ldb,
+ "cn=test_search_cn_extra,%s",
+ search_test_ctx->base_dn);
+
+ ret = ldb_msg_add_string(msg,
+ "objectUUID",
+ "0123456789abcde2");
+
+ ret = ldb_add(search_test_ctx->ldb_test_ctx->ldb,
+ msg);
+ assert_int_equal(ret, LDB_SUCCESS);
+
+ ret = ldb_delete(search_test_ctx->ldb_test_ctx->ldb,
+ msg->dn);
+ assert_int_equal(ret, LDB_SUCCESS);
+ }
+
+ tevent_loop_allow_nesting(search_test_ctx->ldb_test_ctx->ev);
+
+ ctx.basedn
+ = ldb_dn_new_fmt(search_test_ctx,
+ search_test_ctx->ldb_test_ctx->ldb,
+ "%s",
+ search_test_ctx->base_dn);
+ assert_non_null(ctx.basedn);
+
+
+ /*
+ * This search must be over multiple items, and should include
+ * the new name after a rename, to show that it would match
+ * both before and after that modify
+ *
+ * This needs to be a search that isn't matched by an index so
+ * that we just use the one-level index.
+ */
+ ret = ldb_build_search_req(&req,
+ search_test_ctx->ldb_test_ctx->ldb,
+ search_test_ctx,
+ ctx.basedn,
+ LDB_SCOPE_ONELEVEL,
+ "(cn=*)",
+ NULL,
+ NULL,
+ &ctx,
+ test_ldb_callback_modify_during_search_callback1,
+ NULL);
+ assert_int_equal(ret, 0);
+
+ ret = ldb_request(search_test_ctx->ldb_test_ctx->ldb, req);
+
+ if (ret == LDB_SUCCESS) {
+ ret = ldb_wait(req->handle, LDB_WAIT_ALL);
+ }
+ assert_int_equal(ret, 0);
+
+ ret = ldb_transaction_commit(search_test_ctx->ldb_test_ctx->ldb);
+ assert_int_equal(ret, 0);
+
+ assert_int_equal(ctx.res_count, 2);
+ assert_int_equal(ctx.got_cn, true);
+ assert_int_equal(ctx.got_2_cn, true);
+}
+
+static void test_ldb_callback_delete_during_indexed_search(void **state)
+{
+ test_ldb_callback_modify_during_search(state, true, false);
+}
+
+static void test_ldb_callback_delete_during_unindexed_search(void **state)
+{
+ test_ldb_callback_modify_during_search(state, false, false);
+}
+
+static void test_ldb_callback_rename_during_indexed_search(void **state)
+{
+ test_ldb_callback_modify_during_search(state, true, true);
+}
+
+static void test_ldb_callback_rename_during_unindexed_search(void **state)
+{
+ test_ldb_callback_modify_during_search(state, false, true);
+}
+
static int ldb_case_test_setup(void **state)
{
int ret;
@@ -2396,6 +2762,7 @@ static int ldb_case_test_setup(void **state)
struct keyval kvs[] = {
{ "cn", "CaseInsensitiveValue" },
{ "uid", "CaseSensitiveValue" },
+ { "objectUUID", "0123456789abcdef" },
{ NULL, NULL },
};
@@ -2587,6 +2954,14 @@ static void test_ldb_attrs_index_handler(void **state)
syntax, &cn_attr_2);
assert_int_equal(ret, LDB_SUCCESS);
+ syntax = ldb_standard_syntax_by_name(ldb, LDB_SYNTAX_OCTET_STRING);
+ assert_non_null(syntax);
+
+ ret = ldb_schema_attribute_fill_with_syntax(ldb, ldb,
+ "", 0,
+ syntax, &default_attr);
+ assert_int_equal(ret, LDB_SUCCESS);
+
/*
* Set an attribute handler
*/
@@ -2601,6 +2976,11 @@ static void test_ldb_attrs_index_handler(void **state)
/* Add the index (actually any modify will do) */
while ((ldif = ldb_ldif_read_string(ldb_test_ctx->ldb, &index_ldif))) {
ret = ldb_add(ldb_test_ctx->ldb, ldif->msg);
+ if (ret == LDB_ERR_ENTRY_ALREADY_EXISTS) {
+ ldif->msg->elements[0].flags = LDB_FLAG_MOD_ADD;
+ ret = ldb_modify(ldb_test_ctx->ldb,
+ ldif->msg);
+ }
assert_int_equal(ret, LDB_SUCCESS);
}
@@ -2686,7 +3066,8 @@ static int ldb_rename_test_setup(void **state)
add_dn_with_cn(ldb_test_ctx,
rename_test_ctx->basedn,
- "test_rename_cn_val");
+ "test_rename_cn_val",
+ "0123456789abcde0");
*state = rename_test_ctx;
return 0;
@@ -2791,7 +3172,8 @@ static void test_ldb_rename_to_exists(void **state)
add_dn_with_cn(rename_test_ctx->ldb_test_ctx,
new_dn,
- "test_rename_cn_val");
+ "test_rename_cn_val",
+ "0123456789abcde1");
ret = ldb_rename(rename_test_ctx->ldb_test_ctx->ldb,
rename_test_ctx->basedn,
@@ -2958,6 +3340,10 @@ static void test_read_only(void **state)
ret = ldb_msg_add_string(msg, "cn", "test_cn_val");
assert_int_equal(ret, 0);
+ ret = ldb_msg_add_string(msg, "objectUUID",
+ "0123456789abcde1");
+ assert_int_equal(ret, LDB_SUCCESS);
+
ret = ldb_add(ro_ldb, msg);
assert_int_equal(ret, LDB_ERR_UNWILLING_TO_PERFORM);
TALLOC_FREE(msg);
@@ -2971,12 +3357,16 @@ static void test_read_only(void **state)
msg = ldb_msg_new(tmp_ctx);
assert_non_null(msg);
- msg->dn = ldb_dn_new_fmt(msg, ro_ldb, "dc=test");
+ msg->dn = ldb_dn_new_fmt(msg, rw_ldb, "dc=test");
assert_non_null(msg->dn);
ret = ldb_msg_add_string(msg, "cn", "test_cn_val");
assert_int_equal(ret, 0);
+ ret = ldb_msg_add_string(msg, "objectUUID",
+ "0123456789abcde2");
+ assert_int_equal(ret, LDB_SUCCESS);
+
ret = ldb_add(rw_ldb, msg);
assert_int_equal(ret, LDB_SUCCESS);
TALLOC_FREE(msg);
@@ -3050,8 +3440,12 @@ static int ldb_unique_index_test_setup(void **state)
const char *index_ldif = \
"dn: @INDEXLIST\n"
"@IDXATTR: cn\n"
+#ifdef GUID_IDX
+ "@IDXGUID: objectUUID\n"
+ "@IDX_DN_GUID: GUID\n"
+#endif
"\n";
- const char *options[] = {"modules:unique_index_test"};
+ const char *options[] = {"modules:unique_index_test", NULL};
ret = ldb_register_module(&ldb_unique_index_test_module_ops);
@@ -3138,6 +3532,10 @@ static void test_ldb_add_unique_value_to_unique_index(void **state)
ret = ldb_msg_add_string(msg, "cn", "test_unique_index");
assert_int_equal(ret, LDB_SUCCESS);
+ ret = ldb_msg_add_string(msg, "objectUUID",
+ "0123456789abcde1");
+ assert_int_equal(ret, LDB_SUCCESS);
+
ret = ldb_add(test_ctx->ldb, msg);
assert_int_equal(ret, LDB_SUCCESS);
@@ -3152,8 +3550,12 @@ static int ldb_non_unique_index_test_setup(void **state)
const char *index_ldif = \
"dn: @INDEXLIST\n"
"@IDXATTR: cn\n"
+#ifdef GUID_IDX
+ "@IDXGUID: objectUUID\n"
+ "@IDX_DN_GUID: GUID\n"
+#endif
"\n";
- const char *options[] = {"modules:unique_index_test"};
+ const char *options[] = {"modules:unique_index_test", NULL};
ret = ldb_register_module(&ldb_unique_index_test_module_ops);
@@ -3222,6 +3624,10 @@ static void test_ldb_add_duplicate_value_to_unique_index(void **state)
ret = ldb_msg_add_string(msg01, "cn", "test_unique_index");
assert_int_equal(ret, LDB_SUCCESS);
+ ret = ldb_msg_add_string(msg01, "objectUUID",
+ "0123456789abcde1");
+ assert_int_equal(ret, LDB_SUCCESS);
+
ret = ldb_add(test_ctx->ldb, msg01);
assert_int_equal(ret, LDB_SUCCESS);
@@ -3234,6 +3640,10 @@ static void test_ldb_add_duplicate_value_to_unique_index(void **state)
ret = ldb_msg_add_string(msg02, "cn", "test_unique_index");
assert_int_equal(ret, LDB_SUCCESS);
+ ret = ldb_msg_add_string(msg02, "objectUUID",
+ "0123456789abcde2");
+ assert_int_equal(ret, LDB_SUCCESS);
+
ret = ldb_add(test_ctx->ldb, msg02);
assert_int_equal(ret, LDB_ERR_CONSTRAINT_VIOLATION);
talloc_free(tmp_ctx);
@@ -3263,6 +3673,9 @@ static void test_ldb_add_to_index_duplicates_allowed(void **state)
ret = ldb_msg_add_string(msg01, "cn", "test_unique_index");
assert_int_equal(ret, LDB_SUCCESS);
+ ret = ldb_msg_add_string(msg01, "objectUUID",
+ "0123456789abcde1");
+
ret = ldb_add(test_ctx->ldb, msg01);
assert_int_equal(ret, LDB_SUCCESS);
@@ -3275,6 +3688,9 @@ static void test_ldb_add_to_index_duplicates_allowed(void **state)
ret = ldb_msg_add_string(msg02, "cn", "test_unique_index");
assert_int_equal(ret, LDB_SUCCESS);
+ ret = ldb_msg_add_string(msg02, "objectUUID",
+ "0123456789abcde2");
+
ret = ldb_add(test_ctx->ldb, msg02);
assert_int_equal(ret, LDB_SUCCESS);
talloc_free(tmp_ctx);
@@ -3304,6 +3720,9 @@ static void test_ldb_add_to_index_unique_values_required(void **state)
ret = ldb_msg_add_string(msg01, "cn", "test_unique_index");
assert_int_equal(ret, LDB_SUCCESS);
+ ret = ldb_msg_add_string(msg01, "objectUUID",
+ "0123456789abcde1");
+
ret = ldb_add(test_ctx->ldb, msg01);
assert_int_equal(ret, LDB_SUCCESS);
@@ -3316,6 +3735,9 @@ static void test_ldb_add_to_index_unique_values_required(void **state)
ret = ldb_msg_add_string(msg02, "cn", "test_unique_index");
assert_int_equal(ret, LDB_SUCCESS);
+ ret = ldb_msg_add_string(msg02, "objectUUID",
+ "0123456789abcde2");
+
ret = ldb_add(test_ctx->ldb, msg02);
assert_int_equal(ret, LDB_ERR_CONSTRAINT_VIOLATION);
talloc_free(tmp_ctx);
@@ -3341,6 +3763,11 @@ static void test_ldb_unique_index_duplicate_logging(void **state)
char *debug_string = NULL;
char *p = NULL;
+ /* The GUID mode is not compatible with this test */
+#ifdef GUID_IDX
+ return;
+#endif
+
ldb_set_debug(test_ctx->ldb, ldb_debug_string, &debug_string);
tmp_ctx = talloc_new(test_ctx);
assert_non_null(tmp_ctx);
@@ -3354,6 +3781,9 @@ static void test_ldb_unique_index_duplicate_logging(void **state)
ret = ldb_msg_add_string(msg01, "cn", "test_unique_index");
assert_int_equal(ret, LDB_SUCCESS);
+ ret = ldb_msg_add_string(msg01, "objectUUID",
+ "0123456789abcde1");
+
ret = ldb_add(test_ctx->ldb, msg01);
assert_int_equal(ret, LDB_SUCCESS);
@@ -3366,6 +3796,9 @@ static void test_ldb_unique_index_duplicate_logging(void **state)
ret = ldb_msg_add_string(msg02, "cn", "test_unique_index");
assert_int_equal(ret, LDB_SUCCESS);
+ ret = ldb_msg_add_string(msg02, "objectUUID",
+ "0123456789abcde2");
+
ret = ldb_add(test_ctx->ldb, msg02);
assert_int_equal(ret, LDB_ERR_CONSTRAINT_VIOLATION);
@@ -3390,6 +3823,11 @@ static void test_ldb_duplicate_dn_logging(void **state)
TALLOC_CTX *tmp_ctx;
char *debug_string = NULL;
+ /* The GUID mode is not compatible with this test */
+#ifdef GUID_IDX
+ return;
+#endif
+
ldb_set_debug(test_ctx->ldb, ldb_debug_string, &debug_string);
tmp_ctx = talloc_new(test_ctx);
assert_non_null(tmp_ctx);
@@ -3403,6 +3841,9 @@ static void test_ldb_duplicate_dn_logging(void **state)
ret = ldb_msg_add_string(msg01, "cn", "test_unique_index01");
assert_int_equal(ret, LDB_SUCCESS);
+ ret = ldb_msg_add_string(msg01, "objectUUID",
+ "0123456789abcde1");
+
ret = ldb_add(test_ctx->ldb, msg01);
assert_int_equal(ret, LDB_SUCCESS);
@@ -3415,6 +3856,9 @@ static void test_ldb_duplicate_dn_logging(void **state)
ret = ldb_msg_add_string(msg02, "cn", "test_unique_index02");
assert_int_equal(ret, LDB_SUCCESS);
+ ret = ldb_msg_add_string(msg02, "objectUUID",
+ "0123456789abcde2");
+
ret = ldb_add(test_ctx->ldb, msg02);
assert_int_equal(ret, LDB_ERR_ENTRY_ALREADY_EXISTS);
@@ -3601,6 +4045,475 @@ static void test_ldb_guid_index_duplicate_dn_logging(void **state)
talloc_free(tmp_ctx);
}
+static void test_ldb_talloc_destructor_transaction_cleanup(void **state)
+{
+ struct ldbtest_ctx *test_ctx = NULL;
+
+ test_ctx = talloc_get_type_abort(*state, struct ldbtest_ctx);
+ assert_non_null(test_ctx);
+
+ ldb_transaction_start(test_ctx->ldb);
+
+ /*
+ * Trigger the destructor
+ */
+ TALLOC_FREE(test_ctx->ldb);
+
+ /*
+ * Now ensure that a new connection can be opened
+ */
+ {
+ TALLOC_CTX *tctx = talloc_new(test_ctx);
+ struct ldbtest_ctx *ctx = talloc_zero(tctx, struct ldbtest_ctx);
+ struct ldb_dn *basedn;
+ struct ldb_result *result = NULL;
+ int ret;
+
+ ldbtest_setup((void *)&ctx);
+
+ basedn = ldb_dn_new_fmt(tctx, ctx->ldb, "dc=test");
+ assert_non_null(basedn);
+
+ ret = ldb_search(ctx->ldb,
+ tctx,
+ &result,
+ basedn,
+ LDB_SCOPE_BASE,
+ NULL,
+ NULL);
+ assert_int_equal(ret, 0);
+ assert_non_null(result);
+ assert_int_equal(result->count, 0);
+
+ ldbtest_teardown((void *)&ctx);
+ }
+}
+
+#ifdef TEST_LMDB
+static int test_ldb_multiple_connections_callback(struct ldb_request *req,
+ struct ldb_reply *ares)
+{
+ int ret;
+ int pipes[2];
+ char buf[2];
+ int pid, child_pid;
+ int wstatus;
+
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+ break;
+
+ case LDB_REPLY_REFERRAL:
+ return LDB_SUCCESS;
+
+ case LDB_REPLY_DONE:
+ return ldb_request_done(req, LDB_SUCCESS);
+ }
+
+ {
+ /*
+ * We open a new ldb on an ldb that is already open and
+ * then close it.
+ *
+ * If the multiple connection wrapping is correct the
+ * underlying MDB_env will be left open and we should see
+ * an active reader in the child we fork next
+ */
+ struct ldb_context *ldb = NULL;
+ struct tevent_context *ev = NULL;
+ TALLOC_CTX *mem_ctx = talloc_new(NULL);
+
+ ev = tevent_context_init(mem_ctx);
+ assert_non_null(ev);
+
+ ldb = ldb_init(mem_ctx, ev);
+ assert_non_null(ldb);
+
+ ret = ldb_connect(ldb, TEST_BE"://apitest.ldb" , 0, NULL);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ TALLOC_FREE(ldb);
+ TALLOC_FREE(mem_ctx);
+ }
+
+ ret = pipe(pipes);
+ assert_int_equal(ret, 0);
+
+ child_pid = fork();
+ if (child_pid == 0) {
+ struct MDB_env *env = NULL;
+ struct MDB_envinfo stat;
+ close(pipes[0]);
+
+ /*
+ * Check that there are exactly two readers on the MDB file
+ * backing the ldb.
+ *
+ */
+ ret = mdb_env_create(&env);
+ if (ret != 0) {
+ print_error(__location__
+ " mdb_env_create returned (%d)",
+ ret);
+ exit(ret);
+ }
+
+ ret = mdb_env_open(env,
+ "apitest.ldb",
+ MDB_NOSUBDIR | MDB_NOTLS,
+ 0644);
+ if (ret != 0) {
+ print_error(__location__
+ " mdb_env_open returned (%d)",
+ ret);
+ exit(ret);
+ }
+
+ ret = mdb_env_info(env, &stat);
+ if (ret != 0) {
+ print_error(__location__
+ " mdb_env_info returned (%d)",
+ ret);
+ exit(ret);
+ }
+ if (stat.me_numreaders != 2) {
+ print_error(__location__
+ " Incorrect number of readers (%d)",
+ stat.me_numreaders);
+ exit(LDB_ERR_CONSTRAINT_VIOLATION);
+ }
+
+ ret = write(pipes[1], "GO", 2);
+ if (ret != 2) {
+ print_error(__location__
+ " write returned (%d)",
+ ret);
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+ exit(LDB_SUCCESS);
+ }
+ close(pipes[1]);
+ ret = read(pipes[0], buf, 2);
+ assert_int_equal(ret, 2);
+
+ pid = waitpid(child_pid, &wstatus, 0);
+ assert_int_equal(pid, child_pid);
+
+ assert_true(WIFEXITED(wstatus));
+
+ assert_int_equal(WEXITSTATUS(wstatus), 0);
+ return LDB_SUCCESS;
+
+}
+
+static void test_ldb_close_with_multiple_connections(void **state)
+{
+ struct search_test_ctx *search_test_ctx = NULL;
+ struct ldb_dn *search_dn = NULL;
+ struct ldb_request *req = NULL;
+ int ret = 0;
+
+ search_test_ctx = talloc_get_type_abort(*state, struct search_test_ctx);
+ assert_non_null(search_test_ctx);
+
+ search_dn = ldb_dn_new_fmt(search_test_ctx,
+ search_test_ctx->ldb_test_ctx->ldb,
+ "cn=test_search_cn,"
+ "dc=search_test_entry");
+ assert_non_null(search_dn);
+
+ /*
+ * The search just needs to call DONE, we don't care about the
+ * contents of the search for this test
+ */
+ ret = ldb_build_search_req(&req,
+ search_test_ctx->ldb_test_ctx->ldb,
+ search_test_ctx,
+ search_dn,
+ LDB_SCOPE_SUBTREE,
+ "(&(!(filterAttr=*))"
+ "(cn=test_search_cn))",
+ NULL,
+ NULL,
+ NULL,
+ test_ldb_multiple_connections_callback,
+ NULL);
+ assert_int_equal(ret, 0);
+
+ ret = ldb_request(search_test_ctx->ldb_test_ctx->ldb, req);
+ assert_int_equal(ret, 0);
+
+ ret = ldb_wait(req->handle, LDB_WAIT_ALL);
+ assert_int_equal(ret, 0);
+}
+
+#endif
+
+static void test_transaction_start_across_fork(void **state)
+{
+ struct ldb_context *ldb1 = NULL;
+ int ret;
+ struct ldbtest_ctx *test_ctx = NULL;
+ int pipes[2];
+ char buf[2];
+ int wstatus;
+ pid_t pid, child_pid;
+
+ test_ctx = talloc_get_type_abort(*state, struct ldbtest_ctx);
+
+ /*
+ * Open the database
+ */
+ ldb1 = ldb_init(test_ctx, test_ctx->ev);
+ ret = ldb_connect(ldb1, test_ctx->dbpath, 0, NULL);
+ assert_int_equal(ret, 0);
+
+ ret = pipe(pipes);
+ assert_int_equal(ret, 0);
+
+ child_pid = fork();
+ if (child_pid == 0) {
+ close(pipes[0]);
+ ret = ldb_transaction_start(ldb1);
+ if (ret != LDB_ERR_PROTOCOL_ERROR) {
+ print_error(__location__": ldb_transaction_start "
+ "returned (%d) %s\n",
+ ret,
+ ldb1->err_string);
+ exit(LDB_ERR_OTHER);
+ }
+
+ ret = write(pipes[1], "GO", 2);
+ if (ret != 2) {
+ print_error(__location__
+ " write returned (%d)",
+ ret);
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+ exit(LDB_SUCCESS);
+ }
+ close(pipes[1]);
+ ret = read(pipes[0], buf, 2);
+ assert_int_equal(ret, 2);
+
+ pid = waitpid(child_pid, &wstatus, 0);
+ assert_int_equal(pid, child_pid);
+
+ assert_true(WIFEXITED(wstatus));
+
+ assert_int_equal(WEXITSTATUS(wstatus), 0);
+}
+
+static void test_transaction_commit_across_fork(void **state)
+{
+ struct ldb_context *ldb1 = NULL;
+ int ret;
+ struct ldbtest_ctx *test_ctx = NULL;
+ int pipes[2];
+ char buf[2];
+ int wstatus;
+ pid_t pid, child_pid;
+
+ test_ctx = talloc_get_type_abort(*state, struct ldbtest_ctx);
+
+ /*
+ * Open the database
+ */
+ ldb1 = ldb_init(test_ctx, test_ctx->ev);
+ ret = ldb_connect(ldb1, test_ctx->dbpath, 0, NULL);
+ assert_int_equal(ret, 0);
+
+ ret = ldb_transaction_start(ldb1);
+ assert_int_equal(ret, 0);
+
+ ret = pipe(pipes);
+ assert_int_equal(ret, 0);
+
+ child_pid = fork();
+ if (child_pid == 0) {
+ close(pipes[0]);
+ ret = ldb_transaction_commit(ldb1);
+
+ if (ret != LDB_ERR_PROTOCOL_ERROR) {
+ print_error(__location__": ldb_transaction_commit "
+ "returned (%d) %s\n",
+ ret,
+ ldb1->err_string);
+ exit(LDB_ERR_OTHER);
+ }
+
+ ret = write(pipes[1], "GO", 2);
+ if (ret != 2) {
+ print_error(__location__
+ " write returned (%d)",
+ ret);
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+ exit(LDB_SUCCESS);
+ }
+ close(pipes[1]);
+ ret = read(pipes[0], buf, 2);
+ assert_int_equal(ret, 2);
+
+ pid = waitpid(child_pid, &wstatus, 0);
+ assert_int_equal(pid, child_pid);
+
+ assert_true(WIFEXITED(wstatus));
+
+ assert_int_equal(WEXITSTATUS(wstatus), 0);
+}
+
+static void test_lock_read_across_fork(void **state)
+{
+ struct ldb_context *ldb1 = NULL;
+ int ret;
+ struct ldbtest_ctx *test_ctx = NULL;
+ int pipes[2];
+ char buf[2];
+ int wstatus;
+ pid_t pid, child_pid;
+
+ test_ctx = talloc_get_type_abort(*state, struct ldbtest_ctx);
+
+ /*
+ * Open the database
+ */
+ ldb1 = ldb_init(test_ctx, test_ctx->ev);
+ ret = ldb_connect(ldb1, test_ctx->dbpath, 0, NULL);
+ assert_int_equal(ret, 0);
+
+ ret = pipe(pipes);
+ assert_int_equal(ret, 0);
+
+ child_pid = fork();
+ if (child_pid == 0) {
+ struct ldb_dn *basedn;
+ struct ldb_result *result = NULL;
+
+ close(pipes[0]);
+
+ basedn = ldb_dn_new_fmt(test_ctx, test_ctx->ldb, "dc=test");
+ assert_non_null(basedn);
+
+ ret = ldb_search(test_ctx->ldb,
+ test_ctx,
+ &result,
+ basedn,
+ LDB_SCOPE_BASE,
+ NULL,
+ NULL);
+ if (ret != LDB_ERR_PROTOCOL_ERROR) {
+ print_error(__location__": ldb_search "
+ "returned (%d) %s\n",
+ ret,
+ ldb1->err_string);
+ exit(LDB_ERR_OTHER);
+ }
+
+ ret = write(pipes[1], "GO", 2);
+ if (ret != 2) {
+ print_error(__location__
+ " write returned (%d)",
+ ret);
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+ exit(LDB_SUCCESS);
+ }
+ close(pipes[1]);
+ ret = read(pipes[0], buf, 2);
+ assert_int_equal(ret, 2);
+
+ pid = waitpid(child_pid, &wstatus, 0);
+ assert_int_equal(pid, child_pid);
+
+ assert_true(WIFEXITED(wstatus));
+
+ assert_int_equal(WEXITSTATUS(wstatus), 0);
+
+ {
+ /*
+ * Ensure that the search actually succeeds on the opening
+ * pid
+ */
+ struct ldb_dn *basedn;
+ struct ldb_result *result = NULL;
+
+ close(pipes[0]);
+
+ basedn = ldb_dn_new_fmt(test_ctx, test_ctx->ldb, "dc=test");
+ assert_non_null(basedn);
+
+ ret = ldb_search(test_ctx->ldb,
+ test_ctx,
+ &result,
+ basedn,
+ LDB_SCOPE_BASE,
+ NULL,
+ NULL);
+ assert_int_equal(0, ret);
+ }
+}
+
+static void test_multiple_opens_across_fork(void **state)
+{
+ struct ldb_context *ldb1 = NULL;
+ struct ldb_context *ldb2 = NULL;
+ int ret;
+ struct ldbtest_ctx *test_ctx = NULL;
+ int pipes[2];
+ char buf[2];
+ int wstatus;
+ pid_t pid, child_pid;
+
+ test_ctx = talloc_get_type_abort(*state, struct ldbtest_ctx);
+
+ /*
+ * Open the database again
+ */
+ ldb1 = ldb_init(test_ctx, test_ctx->ev);
+ ret = ldb_connect(ldb1, test_ctx->dbpath, LDB_FLG_RDONLY, NULL);
+ assert_int_equal(ret, 0);
+
+ ldb2 = ldb_init(test_ctx, test_ctx->ev);
+ ret = ldb_connect(ldb2, test_ctx->dbpath, 0, NULL);
+ assert_int_equal(ret, 0);
+
+ ret = pipe(pipes);
+ assert_int_equal(ret, 0);
+
+ child_pid = fork();
+ if (child_pid == 0) {
+ struct ldb_context *ldb3 = NULL;
+
+ close(pipes[0]);
+ ldb3 = ldb_init(test_ctx, test_ctx->ev);
+ ret = ldb_connect(ldb3, test_ctx->dbpath, 0, NULL);
+ if (ret != 0) {
+ print_error(__location__": ldb_connect returned (%d)\n",
+ ret);
+ exit(ret);
+ }
+ ret = write(pipes[1], "GO", 2);
+ if (ret != 2) {
+ print_error(__location__
+ " write returned (%d)",
+ ret);
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+ exit(LDB_SUCCESS);
+ }
+ close(pipes[1]);
+ ret = read(pipes[0], buf, 2);
+ assert_int_equal(ret, 2);
+
+ pid = waitpid(child_pid, &wstatus, 0);
+ assert_int_equal(pid, child_pid);
+
+ assert_true(WIFEXITED(wstatus));
+
+ assert_int_equal(WEXITSTATUS(wstatus), 0);
+}
int main(int argc, const char **argv)
{
@@ -3635,6 +4548,9 @@ int main(int argc, const char **argv)
cmocka_unit_test_setup_teardown(test_transactions,
ldbtest_setup,
ldbtest_teardown),
+ cmocka_unit_test_setup_teardown(test_nested_transactions,
+ ldbtest_setup,
+ ldbtest_teardown),
cmocka_unit_test_setup_teardown(test_ldb_modify_add_key,
ldb_modify_test_setup,
ldb_modify_test_teardown),
@@ -3692,6 +4608,18 @@ int main(int argc, const char **argv)
cmocka_unit_test_setup_teardown(test_ldb_rename_during_indexed_search,
ldb_search_test_setup,
ldb_search_test_teardown),
+ cmocka_unit_test_setup_teardown(test_ldb_callback_rename_during_unindexed_search,
+ ldb_search_test_setup,
+ ldb_search_test_teardown),
+ cmocka_unit_test_setup_teardown(test_ldb_callback_rename_during_indexed_search,
+ ldb_search_test_setup,
+ ldb_search_test_teardown),
+ cmocka_unit_test_setup_teardown(test_ldb_callback_delete_during_unindexed_search,
+ ldb_search_test_setup,
+ ldb_search_test_teardown),
+ cmocka_unit_test_setup_teardown(test_ldb_callback_delete_during_indexed_search,
+ ldb_search_test_setup,
+ ldb_search_test_teardown),
cmocka_unit_test_setup_teardown(test_ldb_modify_during_whole_search,
ldb_search_test_setup,
ldb_search_test_teardown),
@@ -3741,6 +4669,7 @@ int main(int argc, const char **argv)
test_ldb_add_to_index_unique_values_required,
ldb_non_unique_index_test_setup,
ldb_non_unique_index_test_teardown),
+ /* These tests are not compatible with mdb */
cmocka_unit_test_setup_teardown(
test_ldb_unique_index_duplicate_logging,
ldb_unique_index_test_setup,
@@ -3757,6 +4686,32 @@ int main(int argc, const char **argv)
test_ldb_unique_index_duplicate_with_guid,
ldb_guid_index_test_setup,
ldb_guid_index_test_teardown),
+ cmocka_unit_test_setup_teardown(
+ test_ldb_talloc_destructor_transaction_cleanup,
+ ldbtest_setup,
+ ldbtest_teardown),
+#ifdef TEST_LMDB
+ cmocka_unit_test_setup_teardown(
+ test_ldb_close_with_multiple_connections,
+ ldb_search_test_setup,
+ ldb_search_test_teardown),
+#endif
+ cmocka_unit_test_setup_teardown(
+ test_transaction_start_across_fork,
+ ldbtest_setup,
+ ldbtest_teardown),
+ cmocka_unit_test_setup_teardown(
+ test_transaction_commit_across_fork,
+ ldbtest_setup,
+ ldbtest_teardown),
+ cmocka_unit_test_setup_teardown(
+ test_lock_read_across_fork,
+ ldbtest_setup,
+ ldbtest_teardown),
+ cmocka_unit_test_setup_teardown(
+ test_multiple_opens_across_fork,
+ ldbtest_setup,
+ ldbtest_teardown),
};
return cmocka_run_group_tests(tests, NULL, NULL);
diff --git a/tests/ldb_tdb_test.c b/tests/ldb_tdb_test.c
new file mode 100644
index 0000000..686a351
--- /dev/null
+++ b/tests/ldb_tdb_test.c
@@ -0,0 +1,387 @@
+/*
+ * lmdb backend specific tests for ldb
+ *
+ * Copyright (C) Andrew Bartlett <abartlet@samba.org> 2018
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/*
+ * lmdb backend specific tests for ldb
+ *
+ * Setup and tear down code copied from ldb_mod_op_test.c
+ */
+
+/*
+ * from cmocka.c:
+ * These headers or their equivalents should be included prior to
+ * including
+ * this header file.
+ *
+ * #include <stdarg.h>
+ * #include <stddef.h>
+ * #include <setjmp.h>
+ *
+ * This allows test applications to use custom definitions of C standard
+ * library functions and types.
+ *
+ */
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+#include <errno.h>
+#include <unistd.h>
+#include <talloc.h>
+#include <tevent.h>
+#include <ldb.h>
+#include <ldb_module.h>
+#include <ldb_private.h>
+#include <string.h>
+#include <ctype.h>
+
+#include <sys/wait.h>
+
+#include "../ldb_tdb/ldb_tdb.h"
+
+#define TEST_BE "tdb"
+
+struct ldbtest_ctx {
+ struct tevent_context *ev;
+ struct ldb_context *ldb;
+
+ const char *dbfile;
+
+ const char *dbpath;
+};
+
+static void unlink_old_db(struct ldbtest_ctx *test_ctx)
+{
+ int ret;
+
+ errno = 0;
+ ret = unlink(test_ctx->dbfile);
+ if (ret == -1 && errno != ENOENT) {
+ fail();
+ }
+}
+
+static int ldbtest_noconn_setup(void **state)
+{
+ struct ldbtest_ctx *test_ctx;
+
+ test_ctx = talloc_zero(NULL, struct ldbtest_ctx);
+ assert_non_null(test_ctx);
+
+ test_ctx->ev = tevent_context_init(test_ctx);
+ assert_non_null(test_ctx->ev);
+
+ test_ctx->ldb = ldb_init(test_ctx, test_ctx->ev);
+ assert_non_null(test_ctx->ldb);
+
+ test_ctx->dbfile = talloc_strdup(test_ctx, "apitest.ldb");
+ assert_non_null(test_ctx->dbfile);
+
+ test_ctx->dbpath = talloc_asprintf(test_ctx,
+ TEST_BE"://%s", test_ctx->dbfile);
+ assert_non_null(test_ctx->dbpath);
+
+ unlink_old_db(test_ctx);
+ *state = test_ctx;
+ return 0;
+}
+
+static int ldbtest_noconn_teardown(void **state)
+{
+ struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct ldbtest_ctx);
+
+ unlink_old_db(test_ctx);
+ talloc_free(test_ctx);
+ return 0;
+}
+
+static int ldbtest_setup(void **state)
+{
+ struct ldbtest_ctx *test_ctx;
+ int ret;
+ struct ldb_ldif *ldif;
+ const char *index_ldif = \
+ "dn: @INDEXLIST\n"
+ "@IDXGUID: objectUUID\n"
+ "@IDX_DN_GUID: GUID\n"
+ "\n";
+
+ ldbtest_noconn_setup((void **) &test_ctx);
+
+ ret = ldb_connect(test_ctx->ldb, test_ctx->dbpath, 0, NULL);
+ assert_int_equal(ret, 0);
+
+ while ((ldif = ldb_ldif_read_string(test_ctx->ldb, &index_ldif))) {
+ ret = ldb_add(test_ctx->ldb, ldif->msg);
+ assert_int_equal(ret, LDB_SUCCESS);
+ }
+ *state = test_ctx;
+ return 0;
+}
+
+static int ldbtest_teardown(void **state)
+{
+ struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct ldbtest_ctx);
+ ldbtest_noconn_teardown((void **) &test_ctx);
+ return 0;
+}
+
+
+static TDB_CONTEXT *get_tdb_context(struct ldb_context *ldb)
+{
+ void *data = NULL;
+ struct ltdb_private *ltdb = NULL;
+ TDB_CONTEXT *tdb = NULL;
+
+ data = ldb_module_get_private(ldb->modules);
+ assert_non_null(data);
+
+ ltdb = talloc_get_type(data, struct ltdb_private);
+ assert_non_null(ltdb);
+
+ tdb = ltdb->tdb;
+ assert_non_null(tdb);
+
+ return tdb;
+}
+
+static void test_multiple_opens(void **state)
+{
+ struct ldb_context *ldb1 = NULL;
+ struct ldb_context *ldb2 = NULL;
+ struct ldb_context *ldb3 = NULL;
+ TDB_CONTEXT *tdb1 = NULL;
+ TDB_CONTEXT *tdb2 = NULL;
+ TDB_CONTEXT *tdb3 = NULL;
+ int ret;
+ struct ldbtest_ctx *test_ctx = NULL;
+
+ test_ctx = talloc_get_type_abort(*state, struct ldbtest_ctx);
+
+ /*
+ * Open the database again
+ */
+ ldb1 = ldb_init(test_ctx, test_ctx->ev);
+ ret = ldb_connect(ldb1, test_ctx->dbpath, LDB_FLG_RDONLY, NULL);
+ assert_int_equal(ret, 0);
+
+ ldb2 = ldb_init(test_ctx, test_ctx->ev);
+ ret = ldb_connect(ldb2, test_ctx->dbpath, LDB_FLG_RDONLY, NULL);
+ assert_int_equal(ret, 0);
+
+ ldb3 = ldb_init(test_ctx, test_ctx->ev);
+ ret = ldb_connect(ldb3, test_ctx->dbpath, 0, NULL);
+ assert_int_equal(ret, 0);
+ /*
+ * We now have 3 ldb's open pointing to the same on disk database
+ * they should all share the same MDB_env
+ */
+ tdb1 = get_tdb_context(ldb1);
+ tdb2 = get_tdb_context(ldb2);
+ tdb3 = get_tdb_context(ldb3);
+
+ assert_ptr_equal(tdb1, tdb2);
+ assert_ptr_equal(tdb1, tdb3);
+}
+
+static void test_multiple_opens_across_fork(void **state)
+{
+ struct ldb_context *ldb1 = NULL;
+ struct ldb_context *ldb2 = NULL;
+ TDB_CONTEXT *tdb1 = NULL;
+ TDB_CONTEXT *tdb2 = NULL;
+ int ret;
+ struct ldbtest_ctx *test_ctx = NULL;
+ int pipes[2];
+ char buf[2];
+ int wstatus;
+ pid_t pid, child_pid;
+
+ test_ctx = talloc_get_type_abort(*state, struct ldbtest_ctx);
+
+ /*
+ * Open the database again
+ */
+ ldb1 = ldb_init(test_ctx, test_ctx->ev);
+ ret = ldb_connect(ldb1, test_ctx->dbpath, LDB_FLG_RDONLY, NULL);
+ assert_int_equal(ret, 0);
+
+ ldb2 = ldb_init(test_ctx, test_ctx->ev);
+ ret = ldb_connect(ldb2, test_ctx->dbpath, LDB_FLG_RDONLY, NULL);
+ assert_int_equal(ret, 0);
+
+ tdb1 = get_tdb_context(ldb1);
+ tdb2 = get_tdb_context(ldb2);
+
+ ret = pipe(pipes);
+ assert_int_equal(ret, 0);
+
+ child_pid = fork();
+ if (child_pid == 0) {
+ struct ldb_context *ldb3 = NULL;
+ TDB_CONTEXT *tdb3 = NULL;
+
+ close(pipes[0]);
+ ldb3 = ldb_init(test_ctx, test_ctx->ev);
+ ret = ldb_connect(ldb3, test_ctx->dbpath, 0, NULL);
+ if (ret != 0) {
+ print_error(__location__": ldb_connect returned (%d)\n",
+ ret);
+ exit(ret);
+ }
+ tdb3 = get_tdb_context(ldb3);
+ if (tdb1 != tdb2) {
+ print_error(__location__": tdb1 != tdb2\n");
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (tdb1 != tdb3) {
+ print_error(__location__": tdb1 != tdb3\n");
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+ ret = write(pipes[1], "GO", 2);
+ if (ret != 2) {
+ print_error(__location__
+ " write returned (%d)",
+ ret);
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+ exit(LDB_SUCCESS);
+ }
+ close(pipes[1]);
+ ret = read(pipes[0], buf, 2);
+ assert_int_equal(ret, 2);
+
+ pid = waitpid(child_pid, &wstatus, 0);
+ assert_int_equal(pid, child_pid);
+
+ assert_true(WIFEXITED(wstatus));
+
+ assert_int_equal(WEXITSTATUS(wstatus), 0);
+}
+
+static void test_multiple_opens_across_fork_triggers_reopen(void **state)
+{
+ struct ldb_context *ldb1 = NULL;
+ struct ldb_context *ldb2 = NULL;
+ TDB_CONTEXT *tdb1 = NULL;
+ TDB_CONTEXT *tdb2 = NULL;
+ int ret;
+ struct ldbtest_ctx *test_ctx = NULL;
+ int pipes[2];
+ char buf[2];
+ int wstatus;
+ pid_t pid, child_pid;
+
+ test_ctx = talloc_get_type_abort(*state, struct ldbtest_ctx);
+
+ /*
+ * Open the database again
+ */
+ ldb1 = ldb_init(test_ctx, test_ctx->ev);
+ ret = ldb_connect(ldb1, test_ctx->dbpath, LDB_FLG_RDONLY, NULL);
+ assert_int_equal(ret, 0);
+
+ ldb2 = ldb_init(test_ctx, test_ctx->ev);
+ ret = ldb_connect(ldb2, test_ctx->dbpath, LDB_FLG_RDONLY, NULL);
+ assert_int_equal(ret, 0);
+
+ tdb1 = get_tdb_context(ldb1);
+ tdb2 = get_tdb_context(ldb2);
+ assert_ptr_equal(tdb1, tdb2);
+
+ /*
+ * Break the internal tdb_reopen() by making a
+ * transaction
+ *
+ * This shows that the tdb_reopen() is called, which is
+ * essential if the host OS does not have pread()
+ */
+ ret = tdb_transaction_start(tdb1);
+ assert_int_equal(ret, 0);
+
+ ret = pipe(pipes);
+ assert_int_equal(ret, 0);
+
+ child_pid = fork();
+ if (child_pid == 0) {
+ struct ldb_context *ldb3 = NULL;
+
+ close(pipes[0]);
+ ldb3 = ldb_init(test_ctx, test_ctx->ev);
+
+ /*
+ * This should fail as we have taken out a lock
+ * against the raw TDB above, and tdb_reopen()
+ * will fail in that state.
+ *
+ * This check matters as tdb_reopen() is important
+ * if the host does not have pread()
+ */
+ ret = ldb_connect(ldb3, test_ctx->dbpath, 0, NULL);
+ if (ret == 0) {
+ print_error(__location__": ldb_connect expected "
+ "LDB_ERR_OPERATIONS_ERROR "
+ "returned (%d)\n",
+ ret);
+ exit(5000);
+ }
+ ret = write(pipes[1], "GO", 2);
+ if (ret != 2) {
+ print_error(__location__
+ " write returned (%d)",
+ ret);
+ exit(LDB_ERR_OPERATIONS_ERROR);
+ }
+ exit(LDB_SUCCESS);
+ }
+ close(pipes[1]);
+ ret = read(pipes[0], buf, 2);
+ assert_int_equal(ret, 2);
+
+ pid = waitpid(child_pid, &wstatus, 0);
+ assert_int_equal(pid, child_pid);
+
+ assert_true(WIFEXITED(wstatus));
+
+ assert_int_equal(WEXITSTATUS(wstatus), 0);
+}
+
+int main(int argc, const char **argv)
+{
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(
+ test_multiple_opens,
+ ldbtest_setup,
+ ldbtest_teardown),
+ cmocka_unit_test_setup_teardown(
+ test_multiple_opens_across_fork,
+ ldbtest_setup,
+ ldbtest_teardown),
+ cmocka_unit_test_setup_teardown(
+ test_multiple_opens_across_fork_triggers_reopen,
+ ldbtest_setup,
+ ldbtest_teardown),
+ };
+
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/tests/python/api.py b/tests/python/api.py
index 1167517..9d01535 100755
--- a/tests/python/api.py
+++ b/tests/python/api.py
@@ -15,6 +15,12 @@ PY3 = sys.version_info > (3, 0)
TDB_PREFIX = "tdb://"
MDB_PREFIX = "mdb://"
+MDB_INDEX_OBJ = {
+ "dn": "@INDEXLIST",
+ "@IDXONE": [b"1"],
+ "@IDXGUID": [b"objectUUID"],
+ "@IDX_DN_GUID": [b"GUID"]
+}
def tempdir():
import tempfile
@@ -77,6 +83,10 @@ class SimpleLdb(LdbBaseTest):
self.testdir = tempdir()
self.filename = os.path.join(self.testdir, "test.ldb")
self.ldb = ldb.Ldb(self.url(), flags=self.flags())
+ try:
+ self.ldb.add(self.index)
+ except AttributeError:
+ pass
def tearDown(self):
shutil.rmtree(self.testdir)
@@ -165,6 +175,7 @@ class SimpleLdb(LdbBaseTest):
m = ldb.Message()
m.dn = ldb.Dn(l, "dc=foo1")
m["b"] = [b"a"]
+ m["objectUUID"] = b"0123456789abcdef"
l.add(m)
self.assertRaises(ldb.LdbError, lambda: l.delete(m.dn, ["search_options:1:2"]))
l.delete(m.dn)
@@ -177,6 +188,7 @@ class SimpleLdb(LdbBaseTest):
m = ldb.Message()
m.dn = ldb.Dn(l, "dc=foo3")
m["b"] = ["a"]
+ m["objectUUID"] = b"0123456789abcdef"
l.add(m)
try:
self.assertTrue(ldb.Dn(l, "dc=foo3") in l)
@@ -205,6 +217,7 @@ class SimpleLdb(LdbBaseTest):
m = ldb.Message()
m.dn = ldb.Dn(l, "dc=foo4")
m["bla"] = b"bla"
+ m["objectUUID"] = b"0123456789abcdef"
self.assertEqual(len(l.search()), 0)
l.add(m)
try:
@@ -245,6 +258,7 @@ class SimpleLdb(LdbBaseTest):
m1 = ldb.Message()
m1.dn = ldb.Dn(l, "dc=foo4")
m1["bla"] = b"bla"
+ m1["objectUUID"] = b"0123456789abcdef"
l.add(m1)
try:
s = l.search_iterator()
@@ -261,6 +275,7 @@ class SimpleLdb(LdbBaseTest):
m2 = ldb.Message()
m2.dn = ldb.Dn(l, "dc=foo5")
m2["bla"] = b"bla"
+ m2["objectUUID"] = b"0123456789abcdee"
l.add(m2)
s = l.search_iterator()
@@ -317,6 +332,7 @@ class SimpleLdb(LdbBaseTest):
m = ldb.Message()
m.dn = ldb.Dn(l, "dc=foo4")
m["bla"] = "bla"
+ m["objectUUID"] = b"0123456789abcdef"
self.assertEqual(len(l.search()), 0)
l.add(m)
try:
@@ -335,7 +351,8 @@ class SimpleLdb(LdbBaseTest):
def test_add_dict(self):
l = ldb.Ldb(self.url(), flags=self.flags())
m = {"dn": ldb.Dn(l, "dc=foo5"),
- "bla": b"bla"}
+ "bla": b"bla",
+ "objectUUID": b"0123456789abcdef"}
self.assertEqual(len(l.search()), 0)
l.add(m)
try:
@@ -346,7 +363,8 @@ class SimpleLdb(LdbBaseTest):
def test_add_dict_text(self):
l = ldb.Ldb(self.url(), flags=self.flags())
m = {"dn": ldb.Dn(l, "dc=foo5"),
- "bla": "bla"}
+ "bla": "bla",
+ "objectUUID": b"0123456789abcdef"}
self.assertEqual(len(l.search()), 0)
l.add(m)
try:
@@ -356,7 +374,8 @@ class SimpleLdb(LdbBaseTest):
def test_add_dict_string_dn(self):
l = ldb.Ldb(self.url(), flags=self.flags())
- m = {"dn": "dc=foo6", "bla": b"bla"}
+ m = {"dn": "dc=foo6", "bla": b"bla",
+ "objectUUID": b"0123456789abcdef"}
self.assertEqual(len(l.search()), 0)
l.add(m)
try:
@@ -366,7 +385,8 @@ class SimpleLdb(LdbBaseTest):
def test_add_dict_bytes_dn(self):
l = ldb.Ldb(self.url(), flags=self.flags())
- m = {"dn": b"dc=foo6", "bla": b"bla"}
+ m = {"dn": b"dc=foo6", "bla": b"bla",
+ "objectUUID": b"0123456789abcdef"}
self.assertEqual(len(l.search()), 0)
l.add(m)
try:
@@ -379,6 +399,7 @@ class SimpleLdb(LdbBaseTest):
m = ldb.Message()
m.dn = ldb.Dn(l, "dc=foo7")
m["bla"] = b"bla"
+ m["objectUUID"] = b"0123456789abcdef"
self.assertEqual(len(l.search()), 0)
l.add(m)
try:
@@ -392,6 +413,7 @@ class SimpleLdb(LdbBaseTest):
m = ldb.Message()
m.dn = ldb.Dn(l, "dc=foo8")
m["bla"] = b"bla"
+ m["objectUUID"] = b"0123456789abcdef"
self.assertEqual(len(l.search()), 0)
l.add(m)
self.assertEqual(len(l.search()), 1)
@@ -406,14 +428,17 @@ class SimpleLdb(LdbBaseTest):
self.assertEqual(0, len(l.search()))
m = ldb.Message()
m.dn = ldb.Dn(l, "dc=empty")
+ m["objectUUID"] = b"0123456789abcdef"
l.add(m)
rm = l.search()
self.assertEqual(1, len(rm))
- self.assertEqual(set(["dn", "distinguishedName"]), set(rm[0].keys()))
+ self.assertEqual(set(["dn", "distinguishedName", "objectUUID"]),
+ set(rm[0].keys()))
rm = l.search(m.dn)
self.assertEqual(1, len(rm))
- self.assertEqual(set(["dn", "distinguishedName"]), set(rm[0].keys()))
+ self.assertEqual(set(["dn", "distinguishedName", "objectUUID"]),
+ set(rm[0].keys()))
rm = l.search(m.dn, attrs=["blah"])
self.assertEqual(1, len(rm))
self.assertEqual(0, len(rm[0]))
@@ -423,6 +448,7 @@ class SimpleLdb(LdbBaseTest):
m = ldb.Message()
m.dn = ldb.Dn(l, "dc=modifydelete")
m["bla"] = [b"1234"]
+ m["objectUUID"] = b"0123456789abcdef"
l.add(m)
rm = l.search(m.dn)[0]
self.assertEqual([b"1234"], list(rm["bla"]))
@@ -434,7 +460,8 @@ class SimpleLdb(LdbBaseTest):
l.modify(m)
rm = l.search(m.dn)
self.assertEqual(1, len(rm))
- self.assertEqual(set(["dn", "distinguishedName"]), set(rm[0].keys()))
+ self.assertEqual(set(["dn", "distinguishedName", "objectUUID"]),
+ set(rm[0].keys()))
rm = l.search(m.dn, attrs=["bla"])
self.assertEqual(1, len(rm))
self.assertEqual(0, len(rm[0]))
@@ -446,6 +473,7 @@ class SimpleLdb(LdbBaseTest):
m = ldb.Message()
m.dn = ldb.Dn(l, "dc=modifydelete")
m.text["bla"] = ["1234"]
+ m["objectUUID"] = b"0123456789abcdef"
l.add(m)
rm = l.search(m.dn)[0]
self.assertEqual(["1234"], list(rm.text["bla"]))
@@ -457,7 +485,8 @@ class SimpleLdb(LdbBaseTest):
l.modify(m)
rm = l.search(m.dn)
self.assertEqual(1, len(rm))
- self.assertEqual(set(["dn", "distinguishedName"]), set(rm[0].keys()))
+ self.assertEqual(set(["dn", "distinguishedName", "objectUUID"]),
+ set(rm[0].keys()))
rm = l.search(m.dn, attrs=["bla"])
self.assertEqual(1, len(rm))
self.assertEqual(0, len(rm[0]))
@@ -469,6 +498,7 @@ class SimpleLdb(LdbBaseTest):
m = ldb.Message()
m.dn = ldb.Dn(l, "dc=add")
m["bla"] = [b"1234"]
+ m["objectUUID"] = b"0123456789abcdef"
l.add(m)
try:
m = ldb.Message()
@@ -477,7 +507,7 @@ class SimpleLdb(LdbBaseTest):
self.assertEqual(ldb.FLAG_MOD_ADD, m["bla"].flags())
l.modify(m)
rm = l.search(m.dn)[0]
- self.assertEqual(2, len(rm))
+ self.assertEqual(3, len(rm))
self.assertEqual([b"1234", b"456"], list(rm["bla"]))
finally:
l.delete(ldb.Dn(l, "dc=add"))
@@ -487,6 +517,7 @@ class SimpleLdb(LdbBaseTest):
m = ldb.Message()
m.dn = ldb.Dn(l, "dc=add")
m.text["bla"] = ["1234"]
+ m["objectUUID"] = b"0123456789abcdef"
l.add(m)
try:
m = ldb.Message()
@@ -495,7 +526,7 @@ class SimpleLdb(LdbBaseTest):
self.assertEqual(ldb.FLAG_MOD_ADD, m["bla"].flags())
l.modify(m)
rm = l.search(m.dn)[0]
- self.assertEqual(2, len(rm))
+ self.assertEqual(3, len(rm))
self.assertEqual(["1234", "456"], list(rm.text["bla"]))
finally:
l.delete(ldb.Dn(l, "dc=add"))
@@ -505,6 +536,7 @@ class SimpleLdb(LdbBaseTest):
m = ldb.Message()
m.dn = ldb.Dn(l, "dc=modify2")
m["bla"] = [b"1234", b"456"]
+ m["objectUUID"] = b"0123456789abcdef"
l.add(m)
try:
m = ldb.Message()
@@ -513,7 +545,7 @@ class SimpleLdb(LdbBaseTest):
self.assertEqual(ldb.FLAG_MOD_REPLACE, m["bla"].flags())
l.modify(m)
rm = l.search(m.dn)[0]
- self.assertEqual(2, len(rm))
+ self.assertEqual(3, len(rm))
self.assertEqual([b"789"], list(rm["bla"]))
rm = l.search(m.dn, attrs=["bla"])[0]
self.assertEqual(1, len(rm))
@@ -525,6 +557,7 @@ class SimpleLdb(LdbBaseTest):
m = ldb.Message()
m.dn = ldb.Dn(l, "dc=modify2")
m.text["bla"] = ["1234", "456"]
+ m["objectUUID"] = b"0123456789abcdef"
l.add(m)
try:
m = ldb.Message()
@@ -533,7 +566,7 @@ class SimpleLdb(LdbBaseTest):
self.assertEqual(ldb.FLAG_MOD_REPLACE, m["bla"].flags())
l.modify(m)
rm = l.search(m.dn)[0]
- self.assertEqual(2, len(rm))
+ self.assertEqual(3, len(rm))
self.assertEqual(["789"], list(rm.text["bla"]))
rm = l.search(m.dn, attrs=["bla"])[0]
self.assertEqual(1, len(rm))
@@ -545,6 +578,7 @@ class SimpleLdb(LdbBaseTest):
m = ldb.Message()
m.dn = ldb.Dn(l, "dc=add")
m["bla"] = [b"1234"]
+ m["objectUUID"] = b"0123456789abcdef"
l.add(m)
try:
m = ldb.Message()
@@ -553,7 +587,7 @@ class SimpleLdb(LdbBaseTest):
self.assertEqual(ldb.FLAG_MOD_ADD, m["bla"].flags())
l.modify(m)
rm = l.search(m.dn)[0]
- self.assertEqual(2, len(rm))
+ self.assertEqual(3, len(rm))
self.assertEqual([b"1234", b"456"], list(rm["bla"]))
# Now create another modify, but switch the flags before we do it
@@ -571,6 +605,7 @@ class SimpleLdb(LdbBaseTest):
m = ldb.Message()
m.dn = ldb.Dn(l, "dc=add")
m.text["bla"] = ["1234"]
+ m["objectUUID"] = b"0123456789abcdef"
l.add(m)
try:
m = ldb.Message()
@@ -579,7 +614,7 @@ class SimpleLdb(LdbBaseTest):
self.assertEqual(ldb.FLAG_MOD_ADD, m["bla"].flags())
l.modify(m)
rm = l.search(m.dn)[0]
- self.assertEqual(2, len(rm))
+ self.assertEqual(3, len(rm))
self.assertEqual(["1234", "456"], list(rm.text["bla"]))
# Now create another modify, but switch the flags before we do it
@@ -597,6 +632,7 @@ class SimpleLdb(LdbBaseTest):
l.transaction_start()
m = ldb.Message(ldb.Dn(l, "dc=foo9"))
m["foo"] = [b"bar"]
+ m["objectUUID"] = b"0123456789abcdef"
l.add(m)
l.transaction_commit()
l.delete(m.dn)
@@ -606,6 +642,7 @@ class SimpleLdb(LdbBaseTest):
l.transaction_start()
m = ldb.Message(ldb.Dn(l, "dc=foo10"))
m["foo"] = [b"bar"]
+ m["objectUUID"] = b"0123456789abcdee"
l.add(m)
l.transaction_cancel()
self.assertEqual(0, len(l.search(ldb.Dn(l, "dc=foo10"))))
@@ -625,6 +662,7 @@ class SimpleLdb(LdbBaseTest):
"cN" : b"LDAPtestUSER",
"givenname" : b"ldap",
"displayname" : b"foo\0bar",
+ "objectUUID" : b"0123456789abcdef"
})
res = l.search(expression="(dn=dc=somedn)")
self.assertEqual(b"foo\0bar", res[0]["displayname"][0])
@@ -633,6 +671,17 @@ class SimpleLdb(LdbBaseTest):
l = ldb.Ldb(self.url(), flags=self.flags())
self.assertRaises(ldb.LdbError,lambda: l.search("", ldb.SCOPE_SUBTREE, "&(dc=*)(dn=*)", ["dc"]))
+# Run the SimpleLdb tests against an lmdb backend
+class SimpleLdbLmdb(SimpleLdb):
+
+ def setUp(self):
+ self.prefix = MDB_PREFIX
+ self.index = MDB_INDEX_OBJ
+ super(SimpleLdbLmdb, self).setUp()
+
+ def tearDown(self):
+ super(SimpleLdbLmdb, self).tearDown()
+
class SearchTests(LdbBaseTest):
def tearDown(self):
shutil.rmtree(self.testdir)
@@ -646,9 +695,16 @@ class SearchTests(LdbBaseTest):
super(SearchTests, self).setUp()
self.testdir = tempdir()
self.filename = os.path.join(self.testdir, "search_test.ldb")
+ options = ["modules:rdn_name"]
+ if hasattr(self, 'IDXCHECK'):
+ options.append("disable_full_db_scan_for_self_test:1")
self.l = ldb.Ldb(self.url(),
flags=self.flags(),
- options=["modules:rdn_name"])
+ options=options)
+ try:
+ self.l.add(self.index)
+ except AttributeError:
+ pass
self.l.add({"dn": "@ATTRIBUTES",
"DC": "CASE_INSENSITIVE"})
@@ -660,7 +716,7 @@ class SearchTests(LdbBaseTest):
self.l.add({"dn": "DC=SAMBA,DC=ORG",
"name": b"samba.org",
- "objectUUID": b"0123456789abcddf"})
+ "objectUUID": b"0123456789abcdef"})
self.l.add({"dn": "OU=ADMIN,DC=SAMBA,DC=ORG",
"name": b"Admins",
"x": "z", "y": "a",
@@ -929,6 +985,47 @@ class SearchTests(LdbBaseTest):
expression="(|(x=y)(y=b))")
self.assertEqual(len(res11), 20)
+ def test_one_unindexable(self):
+ """Testing a search"""
+
+ try:
+ res11 = self.l.search(base="DC=samba,DC=org",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression="(y=b*)")
+ if hasattr(self, 'IDX') and \
+ not hasattr(self, 'IDXONE') and \
+ hasattr(self, 'IDXCHECK'):
+ self.fail("Should have failed as un-indexed search")
+
+ self.assertEqual(len(res11), 9)
+
+ except ldb.LdbError as err:
+ enum = err.args[0]
+ estr = err.args[1]
+ self.assertEqual(enum, ldb.ERR_INAPPROPRIATE_MATCHING)
+ self.assertIn(estr, "ldb FULL SEARCH disabled")
+
+ def test_one_unindexable_presence(self):
+ """Testing a search"""
+
+ try:
+ res11 = self.l.search(base="DC=samba,DC=org",
+ scope=ldb.SCOPE_ONELEVEL,
+ expression="(y=*)")
+ if hasattr(self, 'IDX') and \
+ not hasattr(self, 'IDXONE') and \
+ hasattr(self, 'IDXCHECK'):
+ self.fail("Should have failed as un-indexed search")
+
+ self.assertEqual(len(res11), 24)
+
+ except ldb.LdbError as err:
+ enum = err.args[0]
+ estr = err.args[1]
+ self.assertEqual(enum, ldb.ERR_INAPPROPRIATE_MATCHING)
+ self.assertIn(estr, "ldb FULL SEARCH disabled")
+
+
def test_subtree_and_or(self):
"""Testing a search"""
@@ -1009,6 +1106,45 @@ class SearchTests(LdbBaseTest):
expression="(@IDXONE=DC=SAMBA,DC=ORG)")
self.assertEqual(len(res11), 0)
+ def test_subtree_unindexable(self):
+ """Testing a search"""
+
+ try:
+ res11 = self.l.search(base="DC=samba,DC=org",
+ scope=ldb.SCOPE_SUBTREE,
+ expression="(y=b*)")
+ if hasattr(self, 'IDX') and \
+ hasattr(self, 'IDXCHECK'):
+ self.fail("Should have failed as un-indexed search")
+
+ self.assertEqual(len(res11), 9)
+
+ except ldb.LdbError as err:
+ enum = err.args[0]
+ estr = err.args[1]
+ self.assertEqual(enum, ldb.ERR_INAPPROPRIATE_MATCHING)
+ self.assertIn(estr, "ldb FULL SEARCH disabled")
+
+ def test_subtree_unindexable_presence(self):
+ """Testing a search"""
+
+ try:
+ res11 = self.l.search(base="DC=samba,DC=org",
+ scope=ldb.SCOPE_SUBTREE,
+ expression="(y=*)")
+ if hasattr(self, 'IDX') and \
+ hasattr(self, 'IDXCHECK'):
+ self.fail("Should have failed as un-indexed search")
+
+ self.assertEqual(len(res11), 24)
+
+ except ldb.LdbError as err:
+ enum = err.args[0]
+ estr = err.args[1]
+ self.assertEqual(enum, ldb.ERR_INAPPROPRIATE_MATCHING)
+ self.assertIn(estr, "ldb FULL SEARCH disabled")
+
+
def test_dn_filter_one(self):
"""Testing that a dn= filter succeeds
(or fails with disallowDNFilter
@@ -1057,6 +1193,18 @@ class SearchTests(LdbBaseTest):
self.assertEqual(len(res11), 1)
+# Run the search tests against an lmdb backend
+class SearchTestsLmdb(SearchTests):
+
+ def setUp(self):
+ self.prefix = MDB_PREFIX
+ self.index = MDB_INDEX_OBJ
+ super(SearchTestsLmdb, self).setUp()
+
+ def tearDown(self):
+ super(SearchTestsLmdb, self).tearDown()
+
+
class IndexedSearchTests(SearchTests):
"""Test searches using the index, to ensure the index doesn't
break things"""
@@ -1066,6 +1214,13 @@ class IndexedSearchTests(SearchTests):
"@IDXATTR": [b"x", b"y", b"ou"]})
self.IDX = True
+class IndexedCheckSearchTests(IndexedSearchTests):
+ """Test searches using the index, to ensure the index doesn't
+ break things (full scan disabled)"""
+ def setUp(self):
+ self.IDXCHECK = True
+ super(IndexedCheckSearchTests, self).setUp()
+
class IndexedSearchDnFilterTests(SearchTests):
"""Test searches using the index, to ensure the index doesn't
break things"""
@@ -1088,6 +1243,14 @@ class IndexedAndOneLevelSearchTests(SearchTests):
"@IDXATTR": [b"x", b"y", b"ou"],
"@IDXONE": [b"1"]})
self.IDX = True
+ self.IDXONE = True
+
+class IndexedCheckedAndOneLevelSearchTests(IndexedAndOneLevelSearchTests):
+ """Test searches using the index including @IDXONE, to ensure
+ the index doesn't break things (full scan disabled)"""
+ def setUp(self):
+ self.IDXCHECK = True
+ super(IndexedCheckedAndOneLevelSearchTests, self).setUp()
class IndexedAndOneLevelDNFilterSearchTests(SearchTests):
"""Test searches using the index including @IDXONE, to ensure
@@ -1108,12 +1271,12 @@ class GUIDIndexedSearchTests(SearchTests):
"""Test searches using the index, to ensure the index doesn't
break things"""
def setUp(self):
+ self.index = {"dn": "@INDEXLIST",
+ "@IDXATTR": [b"x", b"y", b"ou"],
+ "@IDXGUID": [b"objectUUID"],
+ "@IDX_DN_GUID": [b"GUID"]}
super(GUIDIndexedSearchTests, self).setUp()
- self.l.add({"dn": "@INDEXLIST",
- "@IDXATTR": [b"x", b"y", b"ou"],
- "@IDXGUID": [b"objectUUID"],
- "@IDX_DN_GUID": [b"GUID"]})
self.IDXGUID = True
self.IDXONE = True
@@ -1122,15 +1285,14 @@ class GUIDIndexedDNFilterSearchTests(SearchTests):
"""Test searches using the index, to ensure the index doesn't
break things"""
def setUp(self):
+ self.index = {"dn": "@INDEXLIST",
+ "@IDXATTR": [b"x", b"y", b"ou"],
+ "@IDXGUID": [b"objectUUID"],
+ "@IDX_DN_GUID": [b"GUID"]}
super(GUIDIndexedDNFilterSearchTests, self).setUp()
self.l.add({"dn": "@OPTIONS",
"disallowDNFilter": "TRUE"})
self.disallowDNFilter = True
-
- self.l.add({"dn": "@INDEXLIST",
- "@IDXATTR": [b"x", b"y", b"ou"],
- "@IDXGUID": [b"objectUUID"],
- "@IDX_DN_GUID": [b"GUID"]})
self.IDX = True
self.IDXGUID = True
@@ -1138,20 +1300,47 @@ class GUIDAndOneLevelIndexedSearchTests(SearchTests):
"""Test searches using the index including @IDXONE, to ensure
the index doesn't break things"""
def setUp(self):
+ self.index = {"dn": "@INDEXLIST",
+ "@IDXATTR": [b"x", b"y", b"ou"],
+ "@IDXGUID": [b"objectUUID"],
+ "@IDX_DN_GUID": [b"GUID"]}
super(GUIDAndOneLevelIndexedSearchTests, self).setUp()
self.l.add({"dn": "@OPTIONS",
"disallowDNFilter": "TRUE"})
self.disallowDNFilter = True
-
- self.l.add({"dn": "@INDEXLIST",
- "@IDXATTR": [b"x", b"y", b"ou"],
- "@IDXONE": [b"1"],
- "@IDXGUID": [b"objectUUID"],
- "@IDX_DN_GUID": [b"GUID"]})
self.IDX = True
self.IDXGUID = True
self.IDXONE = True
+class GUIDIndexedSearchTestsLmdb(GUIDIndexedSearchTests):
+
+ def setUp(self):
+ self.prefix = MDB_PREFIX
+ super(GUIDIndexedSearchTestsLmdb, self).setUp()
+
+ def tearDown(self):
+ super(GUIDIndexedSearchTestsLmdb, self).tearDown()
+
+
+class GUIDIndexedDNFilterSearchTestsLmdb(GUIDIndexedDNFilterSearchTests):
+
+ def setUp(self):
+ self.prefix = MDB_PREFIX
+ super(GUIDIndexedDNFilterSearchTestsLmdb, self).setUp()
+
+ def tearDown(self):
+ super(GUIDIndexedDNFilterSearchTestsLmdb, self).tearDown()
+
+
+class GUIDAndOneLevelIndexedSearchTestsLmdb(GUIDAndOneLevelIndexedSearchTests):
+
+ def setUp(self):
+ self.prefix = MDB_PREFIX
+ super(GUIDAndOneLevelIndexedSearchTestsLmdb, self).setUp()
+
+ def tearDown(self):
+ super(GUIDAndOneLevelIndexedSearchTestsLmdb, self).tearDown()
+
class AddModifyTests(LdbBaseTest):
def tearDown(self):
@@ -1168,6 +1357,11 @@ class AddModifyTests(LdbBaseTest):
self.l = ldb.Ldb(self.url(),
flags=self.flags(),
options=["modules:rdn_name"])
+ try:
+ self.l.add(self.index)
+ except AttributeError:
+ pass
+
self.l.add({"dn": "DC=SAMBA,DC=ORG",
"name": b"samba.org",
"objectUUID": b"0123456789abcdef"})
@@ -1221,6 +1415,19 @@ class AddModifyTests(LdbBaseTest):
"name": b"Admins",
"x": "z", "y": "a",
"objectUUID": b"0123456789abcde2"})
+
+ res2 = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_SUBTREE,
+ expression="(objectUUID=0123456789abcde1)")
+ self.assertEqual(len(res2), 1)
+ self.assertEqual(str(res2[0].dn), "OU=DUP,DC=SAMBA,DC=ORG")
+
+ res3 = self.l.search(base="DC=SAMBA,DC=ORG",
+ scope=ldb.SCOPE_SUBTREE,
+ expression="(objectUUID=0123456789abcde2)")
+ self.assertEqual(len(res3), 1)
+ self.assertEqual(str(res3[0].dn), "OU=DUP2,DC=SAMBA,DC=ORG")
+
try:
self.l.rename("OU=DUP,DC=SAMBA,DC=ORG",
"OU=DUP2,DC=SAMBA,DC=ORG")
@@ -1296,14 +1503,25 @@ class AddModifyTests(LdbBaseTest):
"objectUUID": b"0123456789abcde3"})
+class AddModifyTestsLmdb(AddModifyTests):
+
+ def setUp(self):
+ self.prefix = MDB_PREFIX
+ self.index = MDB_INDEX_OBJ
+ super(AddModifyTestsLmdb, self).setUp()
+
+ def tearDown(self):
+ super(AddModifyTestsLmdb, self).tearDown()
+
class IndexedAddModifyTests(AddModifyTests):
"""Test searches using the index, to ensure the index doesn't
break things"""
def setUp(self):
+ if not hasattr(self, 'index'):
+ self.index = {"dn": "@INDEXLIST",
+ "@IDXATTR": [b"x", b"y", b"ou", b"objectUUID"],
+ "@IDXONE": [b"1"]}
super(IndexedAddModifyTests, self).setUp()
- self.l.add({"dn": "@INDEXLIST",
- "@IDXATTR": [b"x", b"y", b"ou", b"objectUUID"],
- "@IDXONE": [b"1"]})
def test_duplicate_GUID(self):
try:
@@ -1377,14 +1595,12 @@ class GUIDIndexedAddModifyTests(IndexedAddModifyTests):
"""Test searches using the index, to ensure the index doesn't
break things"""
def setUp(self):
+ self.index = {"dn": "@INDEXLIST",
+ "@IDXATTR": [b"x", b"y", b"ou"],
+ "@IDXONE": [b"1"],
+ "@IDXGUID": [b"objectUUID"],
+ "@IDX_DN_GUID": [b"GUID"]}
super(GUIDIndexedAddModifyTests, self).setUp()
- indexlist = {"dn": "@INDEXLIST",
- "@IDXATTR": [b"x", b"y", b"ou"],
- "@IDXONE": [b"1"],
- "@IDXGUID": [b"objectUUID"],
- "@IDX_DN_GUID": [b"GUID"]}
- m = ldb.Message.from_dict(self.l, indexlist, ldb.FLAG_MOD_REPLACE)
- self.l.modify(m)
class GUIDTransIndexedAddModifyTests(GUIDIndexedAddModifyTests):
@@ -1407,6 +1623,23 @@ class TransIndexedAddModifyTests(IndexedAddModifyTests):
self.l.transaction_commit()
super(TransIndexedAddModifyTests, self).tearDown()
+class GuidIndexedAddModifyTestsLmdb(GUIDIndexedAddModifyTests):
+
+ def setUp(self):
+ self.prefix = MDB_PREFIX
+ super(GuidIndexedAddModifyTestsLmdb, self).setUp()
+
+ def tearDown(self):
+ super(GuidIndexedAddModifyTestsLmdb, self).tearDown()
+
+class GuidTransIndexedAddModifyTestsLmdb(GUIDTransIndexedAddModifyTests):
+
+ def setUp(self):
+ self.prefix = MDB_PREFIX
+ super(GuidTransIndexedAddModifyTestsLmdb, self).setUp()
+
+ def tearDown(self):
+ super(GuidTransIndexedAddModifyTestsLmdb, self).tearDown()
class BadIndexTests(LdbBaseTest):
def setUp(self):
@@ -1567,7 +1800,6 @@ class GUIDBadIndexTests(BadIndexTests):
super(GUIDBadIndexTests, self).setUp()
-
class DnTests(TestCase):
def setUp(self):
@@ -1701,9 +1933,11 @@ class DnTests(TestCase):
dn3 = ldb.Dn(self.ldb, "cn=bar,dc=base")
dn4 = ldb.Dn(self.ldb, "cn=baz,cn=bar,dc=base")
+ self.assertTrue(dn1.is_child_of(dn1))
self.assertTrue(dn2.is_child_of(dn1))
self.assertTrue(dn4.is_child_of(dn1))
self.assertTrue(dn4.is_child_of(dn3))
+ self.assertTrue(dn4.is_child_of(dn4))
self.assertFalse(dn3.is_child_of(dn2))
self.assertFalse(dn1.is_child_of(dn4))
@@ -1719,9 +1953,11 @@ class DnTests(TestCase):
dn3 = ldb.Dn(self.ldb, dn3_str)
dn4 = ldb.Dn(self.ldb, dn4_str)
+ self.assertTrue(dn1.is_child_of(dn1_str))
self.assertTrue(dn2.is_child_of(dn1_str))
self.assertTrue(dn4.is_child_of(dn1_str))
self.assertTrue(dn4.is_child_of(dn3_str))
+ self.assertTrue(dn4.is_child_of(dn4_str))
self.assertFalse(dn3.is_child_of(dn2_str))
self.assertFalse(dn1.is_child_of(dn4_str))
@@ -1903,13 +2139,13 @@ class LdbMsgTests(TestCase):
self.msg.dn = ldb.Dn(ldb.Ldb(), "@BASEINFO")
self.msg["foo"] = [b"bla"]
self.msg["bar"] = [b"bla"]
- self.assertEqual(["dn", "foo", "bar"], self.msg.keys())
+ self.assertEqual(["dn", "foo", "bar"], list(self.msg.keys()))
def test_keys_text(self):
self.msg.dn = ldb.Dn(ldb.Ldb(), "@BASEINFO")
self.msg["foo"] = ["bla"]
self.msg["bar"] = ["bla"]
- self.assertEqual(["dn", "foo", "bar"], self.msg.text.keys())
+ self.assertEqual(["dn", "foo", "bar"], list(self.msg.text.keys()))
def test_dn(self):
self.msg.dn = ldb.Dn(ldb.Ldb(), "@BASEINFO")
@@ -2181,19 +2417,36 @@ class LdbResultTests(LdbBaseTest):
self.testdir = tempdir()
self.filename = os.path.join(self.testdir, "test.ldb")
self.l = ldb.Ldb(self.url(), flags=self.flags())
- self.l.add({"dn": "DC=SAMBA,DC=ORG", "name": b"samba.org"})
- self.l.add({"dn": "OU=ADMIN,DC=SAMBA,DC=ORG", "name": b"Admins"})
- self.l.add({"dn": "OU=USERS,DC=SAMBA,DC=ORG", "name": b"Users"})
- self.l.add({"dn": "OU=OU1,DC=SAMBA,DC=ORG", "name": b"OU #1"})
- self.l.add({"dn": "OU=OU2,DC=SAMBA,DC=ORG", "name": b"OU #2"})
- self.l.add({"dn": "OU=OU3,DC=SAMBA,DC=ORG", "name": b"OU #3"})
- self.l.add({"dn": "OU=OU4,DC=SAMBA,DC=ORG", "name": b"OU #4"})
- self.l.add({"dn": "OU=OU5,DC=SAMBA,DC=ORG", "name": b"OU #5"})
- self.l.add({"dn": "OU=OU6,DC=SAMBA,DC=ORG", "name": b"OU #6"})
- self.l.add({"dn": "OU=OU7,DC=SAMBA,DC=ORG", "name": b"OU #7"})
- self.l.add({"dn": "OU=OU8,DC=SAMBA,DC=ORG", "name": b"OU #8"})
- self.l.add({"dn": "OU=OU9,DC=SAMBA,DC=ORG", "name": b"OU #9"})
- self.l.add({"dn": "OU=OU10,DC=SAMBA,DC=ORG", "name": b"OU #10"})
+ try:
+ self.l.add(self.index)
+ except AttributeError:
+ pass
+ self.l.add({"dn": "DC=SAMBA,DC=ORG", "name": b"samba.org",
+ "objectUUID": b"0123456789abcde0"})
+ self.l.add({"dn": "OU=ADMIN,DC=SAMBA,DC=ORG", "name": b"Admins",
+ "objectUUID": b"0123456789abcde1"})
+ self.l.add({"dn": "OU=USERS,DC=SAMBA,DC=ORG", "name": b"Users",
+ "objectUUID": b"0123456789abcde2"})
+ self.l.add({"dn": "OU=OU1,DC=SAMBA,DC=ORG", "name": b"OU #1",
+ "objectUUID": b"0123456789abcde3"})
+ self.l.add({"dn": "OU=OU2,DC=SAMBA,DC=ORG", "name": b"OU #2",
+ "objectUUID": b"0123456789abcde4"})
+ self.l.add({"dn": "OU=OU3,DC=SAMBA,DC=ORG", "name": b"OU #3",
+ "objectUUID": b"0123456789abcde5"})
+ self.l.add({"dn": "OU=OU4,DC=SAMBA,DC=ORG", "name": b"OU #4",
+ "objectUUID": b"0123456789abcde6"})
+ self.l.add({"dn": "OU=OU5,DC=SAMBA,DC=ORG", "name": b"OU #5",
+ "objectUUID": b"0123456789abcde7"})
+ self.l.add({"dn": "OU=OU6,DC=SAMBA,DC=ORG", "name": b"OU #6",
+ "objectUUID": b"0123456789abcde8"})
+ self.l.add({"dn": "OU=OU7,DC=SAMBA,DC=ORG", "name": b"OU #7",
+ "objectUUID": b"0123456789abcde9"})
+ self.l.add({"dn": "OU=OU8,DC=SAMBA,DC=ORG", "name": b"OU #8",
+ "objectUUID": b"0123456789abcdea"})
+ self.l.add({"dn": "OU=OU9,DC=SAMBA,DC=ORG", "name": b"OU #9",
+ "objectUUID": b"0123456789abcdeb"})
+ self.l.add({"dn": "OU=OU10,DC=SAMBA,DC=ORG", "name": b"OU #10",
+ "objectUUID": b"0123456789abcdec"})
def tearDown(self):
shutil.rmtree(self.testdir)
@@ -2294,7 +2547,8 @@ class LdbResultTests(LdbBaseTest):
# write to it
child_ldb.add({"dn": "OU=OU11,DC=SAMBA,DC=ORG",
- "name": b"samba.org"})
+ "name": b"samba.org",
+ "objectUUID": b"o123456789acbdef"})
os.write(w1, b"added")
@@ -2365,7 +2619,8 @@ class LdbResultTests(LdbBaseTest):
# write to it
child_ldb.add({"dn": "OU=OU11,DC=SAMBA,DC=ORG",
- "name": b"samba.org"})
+ "name": b"samba.org",
+ "objectUUID": b"o123456789acbdef"})
os.write(w1, b"added")
@@ -2425,6 +2680,17 @@ class LdbResultTests(LdbBaseTest):
self.assertEqual(got_pid, pid)
+class LdbResultTestsLmdb(LdbResultTests):
+
+ def setUp(self):
+ self.prefix = MDB_PREFIX
+ self.index = MDB_INDEX_OBJ
+ super(LdbResultTestsLmdb, self).setUp()
+
+ def tearDown(self):
+ super(LdbResultTestsLmdb, self).tearDown()
+
+
class BadTypeTests(TestCase):
def test_control(self):
l = ldb.Ldb()
diff --git a/tests/python/index.py b/tests/python/index.py
index d8a84f2..2613a4d 100755
--- a/tests/python/index.py
+++ b/tests/python/index.py
@@ -17,15 +17,10 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
-"""Tests for index keys
+"""Tests for truncated index keys
-This is a modified version of the test from master for databases such
-as lmdb have a maximum key length, instead just checking that the
-GUID index code still operates correctly.
-
-Many of the test names are therefore incorrect, but are retained
-to keep the code easy to backport into if more tested are added in
-master.
+Databases such as lmdb have a maximum key length, these tests ensure that
+ldb behaves correctly in those circumstances.
"""
@@ -118,19 +113,42 @@ class MaxIndexKeyLengthTests(LdbBaseTest):
super(MaxIndexKeyLengthTests, self).setUp()
self.testdir = tempdir()
self.filename = os.path.join(self.testdir, "key_len_test.ldb")
- # Note that the maximum key length is set to 50
+ # Note that the maximum key length is set to 54
+ # This accounts for the 4 bytes added by the dn formatting
+ # a leading dn=, and a trailing zero terminator
+ #
self.l = ldb.Ldb(self.url(),
options=[
"modules:rdn_name",
- "max_key_len_for_self_test:50"])
+ "max_key_len_for_self_test:54"])
self.l.add({"dn": "@ATTRIBUTES",
"uniqueThing": "UNIQUE_INDEX"})
self.l.add({"dn": "@INDEXLIST",
- "@IDXATTR": [b"uniqueThing", b"notUnique"],
+ "@IDXATTR": [
+ b"uniqueThing",
+ b"notUnique",
+ b"base64____lt",
+ b"base64_____eq",
+ b"base64______gt"],
"@IDXONE": [b"1"],
"@IDXGUID": [b"objectUUID"],
"@IDX_DN_GUID": [b"GUID"]})
+ # Add a value to a unique index that exceeds the maximum key length
+ # This should be rejected.
+ def test_add_long_unique_add(self):
+ try:
+ self.l.add({"dn": "OU=UNIQUE_MAX_LEN,DC=SAMBA,DC=ORG",
+ "objectUUID": b"0123456789abcdef",
+ "uniqueThing": "01234567890123456789012345678901"})
+ # index key will be
+ # "@INDEX:UNIQUETHING:01234567890123456789012345678901"
+ self.fail("Should have failed on long index key")
+
+ except ldb.LdbError as err:
+ enum = err.args[0]
+ self.assertEqual(enum, ldb.ERR_CONSTRAINT_VIOLATION)
+
# Test that DN's longer the maximum key length can be added
# and that duplicate DN's are rejected correctly
def test_add_long_dn_add(self):
@@ -140,12 +158,21 @@ class MaxIndexKeyLengthTests(LdbBaseTest):
#
self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=ORG",
"objectUUID": b"0123456789abcdef"})
+ self.checkGuids(
+ "@INDEX#@IDXDN#OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
+ b"0123456789abcdef")
self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=COM",
"objectUUID": b"0123456789abcde0"})
+ self.checkGuids(
+ "@INDEX#@IDXDN#OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
+ b"0123456789abcde0" + b"0123456789abcdef")
self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=GOV",
"objectUUID": b"0123456789abcde1"})
+ self.checkGuids(
+ "@INDEX#@IDXDN#OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
+ b"0123456789abcde0" + b"0123456789abcde1" + b"0123456789abcdef")
# Key is equal to max length does not get inserted into the truncated
# key namespace
@@ -205,13 +232,23 @@ class MaxIndexKeyLengthTests(LdbBaseTest):
# @INDEX:@IDXDN:OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA
self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=ORG",
"objectUUID": b"0123456789abcdef"})
+ self.checkGuids(
+ "@INDEX#@IDXDN#OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
+ b"0123456789abcdef")
self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=COM",
"objectUUID": b"0123456789abcde0"})
+ self.checkGuids(
+ "@INDEX#@IDXDN#OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
+ b"0123456789abcde0" + b"0123456789abcdef")
# Non conflicting rename, should succeed
self.l.rename("OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=ORG",
"OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=GOV")
+ # Index should be unchanged.
+ self.checkGuids(
+ "@INDEX#@IDXDN#OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
+ b"0123456789abcde0" + b"0123456789abcdef")
# Conflicting rename should fail
try:
@@ -230,9 +267,15 @@ class MaxIndexKeyLengthTests(LdbBaseTest):
#
self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=ORG",
"objectUUID": b"0123456789abcdef"})
+ self.checkGuids(
+ "@INDEX#@IDXDN#OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
+ b"0123456789abcdef")
self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=GOV",
"objectUUID": b"0123456789abcde1"})
+ self.checkGuids(
+ "@INDEX#@IDXDN#OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
+ b"0123456789abcde1" + b"0123456789abcdef")
self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
"objectUUID": b"0123456789abcde5"})
@@ -262,6 +305,9 @@ class MaxIndexKeyLengthTests(LdbBaseTest):
# Check the indexes are correct
self.checkGuids(
+ "@INDEX#@IDXDN#OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
+ b"0123456789abcde1" + b"0123456789abcdef")
+ self.checkGuids(
"@INDEX:@IDXDN:OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
b"0123456789abcde5")
@@ -282,6 +328,9 @@ class MaxIndexKeyLengthTests(LdbBaseTest):
# Check the indexes are correct
self.checkGuids(
+ "@INDEX#@IDXDN#OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
+ b"0123456789abcde1")
+ self.checkGuids(
"@INDEX:@IDXDN:OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
b"0123456789abcde5")
@@ -297,6 +346,9 @@ class MaxIndexKeyLengthTests(LdbBaseTest):
self.assertEqual(len(res), 1)
# Check the indexes are correct
self.checkGuids(
+ "@INDEX#@IDXDN#OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
+ None)
+ self.checkGuids(
"@INDEX:@IDXDN:OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
b"0123456789abcde5")
@@ -319,9 +371,15 @@ class MaxIndexKeyLengthTests(LdbBaseTest):
#
self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=ORG",
"objectUUID": b"0123456789abcdef"})
+ self.checkGuids(
+ "@INDEX#@IDXDN#OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
+ b"0123456789abcdef")
self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=GOV",
"objectUUID": b"0123456789abcde1"})
+ self.checkGuids(
+ "@INDEX#@IDXDN#OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
+ b"0123456789abcde1" + b"0123456789abcdef")
self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
"objectUUID": b"0123456789abcde5"})
@@ -357,9 +415,15 @@ class MaxIndexKeyLengthTests(LdbBaseTest):
#
self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=ORG",
"objectUUID": b"0123456789abcdef"})
+ self.checkGuids(
+ "@INDEX#@IDXDN#OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
+ b"0123456789abcdef")
self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=GOV",
"objectUUID": b"0123456789abcde1"})
+ self.checkGuids(
+ "@INDEX#@IDXDN#OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
+ b"0123456789abcde1" + b"0123456789abcdef")
self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
"objectUUID": b"0123456789abcde5"})
@@ -406,21 +470,33 @@ class MaxIndexKeyLengthTests(LdbBaseTest):
"objectUUID": b"0123456789abcdef"})
self.l.add({"dn": "OU=A_LONG_DN_ONE_LVLX,DC=SAMBA,DC=OR2",
"objectUUID": b"0123456789abcd1f"})
+ self.checkGuids(
+ "@INDEX#@IDXDN#OU=A_LONG_DN_ONE_LVLX,DC=SAMBA,DC=OR",
+ b"0123456789abcd1f" + b"0123456789abcdef")
self.l.add({"dn": "OU=01,OU=A_LONG_DN_ONE_LVLX,DC=SAMBA,DC=OR1",
"objectUUID": b"0123456789abcde1"})
self.l.add({"dn": "OU=01,OU=A_LONG_DN_ONE_LVLX,DC=SAMBA,DC=OR2",
"objectUUID": b"0123456789abcd11"})
+ self.checkGuids(
+ "@INDEX#@IDXDN#OU=01,OU=A_LONG_DN_ONE_LVLX,DC=SAMBA",
+ b"0123456789abcd11" + b"0123456789abcde1")
self.l.add({"dn": "OU=02,OU=A_LONG_DN_ONE_LVLX,DC=SAMBA,DC=OR1",
"objectUUID": b"0123456789abcde2"})
self.l.add({"dn": "OU=02,OU=A_LONG_DN_ONE_LVLX,DC=SAMBA,DC=OR2",
"objectUUID": b"0123456789abcdf2"})
+ self.checkGuids(
+ "@INDEX#@IDXDN#OU=02,OU=A_LONG_DN_ONE_LVLX,DC=SAMBA",
+ b"0123456789abcde2" + b"0123456789abcdf2")
self.l.add({"dn": "OU=03,OU=A_LONG_DN_ONE_LVLX,DC=SAMBA,DC=OR1",
"objectUUID": b"0123456789abcde3"})
self.l.add({"dn": "OU=03,OU=A_LONG_DN_ONE_LVLX,DC=SAMBA,DC=OR2",
"objectUUID": b"0123456789abcd13"})
+ self.checkGuids(
+ "@INDEX#@IDXDN#OU=03,OU=A_LONG_DN_ONE_LVLX,DC=SAMBA",
+ b"0123456789abcd13" + b"0123456789abcde3")
# This key is not truncated as it's the max_key_len
self.l.add({"dn": "OU=01,OU=A_LONG_DN_ONE_LVLX,DC=SAMBA",
@@ -471,25 +547,40 @@ class MaxIndexKeyLengthTests(LdbBaseTest):
"objectUUID": b"0123456789abcde4"})
self.l.add({"dn": "OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR3",
"objectUUID": b"0123456789abcde8"})
+ self.checkGuids(
+ "@INDEX#@IDXDN#OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR",
+ b"0123456789abcde4" + b"0123456789abcde8" + b"0123456789abcdef")
self.l.add({"dn": "OU=01,OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR1",
"objectUUID": b"0123456789abcde1"})
self.l.add({"dn": "OU=01,OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR2",
"objectUUID": b"0123456789abcde5"})
+ self.checkGuids(
+ "@INDEX#@IDXDN#OU=01,OU=A_LONG_DN_SUB_TREE,DC=SAMBA",
+ b"0123456789abcde1" + b"0123456789abcde5")
self.l.add({"dn": "OU=02,OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR1",
"objectUUID": b"0123456789abcde2"})
self.l.add({"dn": "OU=02,OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR2",
"objectUUID": b"0123456789abcde6"})
+ self.checkGuids(
+ "@INDEX#@IDXDN#OU=02,OU=A_LONG_DN_SUB_TREE,DC=SAMBA",
+ b"0123456789abcde2" + b"0123456789abcde6")
self.l.add({"dn": "OU=03,OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR1",
"objectUUID": b"0123456789abcde3"})
self.l.add({"dn": "OU=03,OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR2",
"objectUUID": b"0123456789abcde7"})
+ self.checkGuids(
+ "@INDEX#@IDXDN#OU=03,OU=A_LONG_DN_SUB_TREE,DC=SAMBA",
+ b"0123456789abcde3" + b"0123456789abcde7")
self.l.add({"dn": "OU=04,OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR4",
"objectUUID": b"0123456789abcde9"})
+ self.checkGuids(
+ "@INDEX#@IDXDN#OU=04,OU=A_LONG_DN_SUB_TREE,DC=SAMBA",
+ b"0123456789abcde9")
res = self.l.search(base="OU=A_LONG_DN_SUB_TREE,DC=SAMBA,DC=OR1",
scope=ldb.SCOPE_SUBTREE)
@@ -536,9 +627,15 @@ class MaxIndexKeyLengthTests(LdbBaseTest):
#
self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=ORG",
"objectUUID": b"0123456789abcdef"})
+ self.checkGuids(
+ "@INDEX#@IDXDN#OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
+ b"0123456789abcdef")
self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA,DC=GOV",
"objectUUID": b"0123456789abcde1"})
+ self.checkGuids(
+ "@INDEX#@IDXDN#OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
+ b"0123456789abcde1" + b"0123456789abcdef")
self.l.add({"dn": "OU=A_LONG_DNXXXXXXXXXXXXXXX,DC=SAMBA",
"objectUUID": b"0123456789abcde5"})
@@ -622,18 +719,29 @@ class MaxIndexKeyLengthTests(LdbBaseTest):
self.l.add({"dn": "OU=03,OU=SEARCH_NON_UNIQUE,DC=SAMBA,DC=ORG",
"notUnique": gt_max,
"objectUUID": b"0123456789abcde2"})
+ # But in the truncated key space
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ b"0123456789abcde2")
# Key longer than max so should get truncated to same key as
# the previous entries but differs in the chars after max length
self.l.add({"dn": "OU=23,OU=SEARCH_NON_UNIQUE,DC=SAMBA,DC=ORG",
"notUnique": gt_max_b,
"objectUUID": b"0123456789abcd22"})
+ # But in the truncated key space
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ b"0123456789abcd22" + b"0123456789abcde2")
#
# An entry outside the tree
#
self.l.add({"dn": "OU=11,OU=SEARCH_NON_UNIQUE01,DC=SAMBA,DC=ORG",
"notUnique": gt_max,
"objectUUID": b"0123456789abcd12"})
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ b"0123456789abcd12" + b"0123456789abcd22" + b"0123456789abcde2")
# Key shorter than max
#
@@ -767,6 +875,36 @@ class MaxIndexKeyLengthTests(LdbBaseTest):
contains(res, "OU=12,OU=SEARCH_NON_UNIQUE01,DC=SAMBA,DC=ORG"))
#
+ # Test index key truncation for base64 encoded values
+ #
+ def test_index_truncated_base64_encoded_keys(self):
+ value = b"aaaaaaaaaaaaaaaaaaaa\x02"
+ # base64 encodes to "YWFhYWFhYWFhYWFhYWFhYWFhYWEC"
+
+ # One less than max key length
+ self.l.add({"dn": "OU=01,OU=BASE64,DC=SAMBA,DC=ORG",
+ "base64____lt": value,
+ "objectUUID": b"0123456789abcde0"})
+ self.checkGuids(
+ "@INDEX:BASE64____LT::YWFhYWFhYWFhYWFhYWFhYWFhYWEC",
+ b"0123456789abcde0")
+
+ # Equal max key length
+ self.l.add({"dn": "OU=02,OU=BASE64,DC=SAMBA,DC=ORG",
+ "base64_____eq": value,
+ "objectUUID": b"0123456789abcde1"})
+ self.checkGuids(
+ "@INDEX:BASE64_____EQ::YWFhYWFhYWFhYWFhYWFhYWFhYWEC",
+ b"0123456789abcde1")
+
+ # One greater than max key length
+ self.l.add({"dn": "OU=03,OU=BASE64,DC=SAMBA,DC=ORG",
+ "base64______gt": value,
+ "objectUUID": b"0123456789abcde2"})
+ self.checkGuids(
+ "@INDEX#BASE64______GT##YWFhYWFhYWFhYWFhYWFhYWFhYWE",
+ b"0123456789abcde2")
+ #
# Test adding to non unique index with identical multivalued index
# attributes
#
@@ -802,6 +940,12 @@ class MaxIndexKeyLengthTests(LdbBaseTest):
self.l.add({"dn": "OU=01,OU=SEARCH_NON_UNIQUE,DC=SAMBA,DC=ORG",
"notUnique": [aa_gt_max, ab_gt_max, bb_gt_max],
"objectUUID": b"0123456789abcde0"})
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ b"0123456789abcde0" + b"0123456789abcde0")
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ b"0123456789abcde0")
expression = "(notUnique=" + aa_gt_max.decode('ascii') + ")"
res = self.l.search(base="OU=SEARCH_NON_UNIQUE,DC=SAMBA,DC=ORG",
@@ -848,6 +992,16 @@ class MaxIndexKeyLengthTests(LdbBaseTest):
self.l.add({"dn": "OU=02,OU=DELETE_NON_UNIQUE,DC=SAMBA,DC=ORG",
"notUnique": [aa_gt_max, ab_gt_max, cc_gt_max],
"objectUUID": b"0123456789abcde1"})
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ b"0123456789abcde0" + b"0123456789abcde0" +
+ b"0123456789abcde1" + b"0123456789abcde1")
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ b"0123456789abcde0")
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#ccccccccccccccccccccccccccccccccc",
+ b"0123456789abcde1")
res = self.l.search(
base="DC=SAMBA,DC=ORG",
@@ -859,8 +1013,26 @@ class MaxIndexKeyLengthTests(LdbBaseTest):
contains(res, "OU=02,OU=DELETE_NON_UNIQUE,DC=SAMBA,DC=ORG"))
self.l.delete("OU=02,OU=DELETE_NON_UNIQUE,DC=SAMBA,DC=ORG")
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ b"0123456789abcde0" + b"0123456789abcde0")
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ b"0123456789abcde0")
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#ccccccccccccccccccccccccccccccccc",
+ None)
self.l.delete("OU=01,OU=DELETE_NON_UNIQUE,DC=SAMBA,DC=ORG")
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ None)
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ None)
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#ccccccccccccccccccccccccccccccccc",
+ None)
#
# Test modification of records with non unique index with multivalued index
@@ -882,11 +1054,21 @@ class MaxIndexKeyLengthTests(LdbBaseTest):
self.l.add({"dn": "OU=02,OU=MODIFY_NON_UNIQUE,DC=SAMBA,DC=ORG",
"notUnique": [aa_gt_max, ab_gt_max, cc_gt_max],
"objectUUID": b"0123456789abcde1"})
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ b"0123456789abcde0" + b"0123456789abcde0" +
+ b"0123456789abcde1" + b"0123456789abcde1")
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ b"0123456789abcde0")
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#ccccccccccccccccccccccccccccccccc",
+ b"0123456789abcde1")
res = self.l.search(
base="DC=SAMBA,DC=ORG",
expression="(notUnique=" + aa_gt_max.decode("ascii") + ")")
- self.assertEquals(2, len(res))
+ self.assertEqual(2, len(res))
self.assertTrue(
contains(res, "OU=01,OU=MODIFY_NON_UNIQUE,DC=SAMBA,DC=ORG"))
self.assertTrue(
@@ -906,6 +1088,16 @@ class MaxIndexKeyLengthTests(LdbBaseTest):
# As the modify is replacing the attribute with the same contents
# there should be no changes to the indexes.
#
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ b"0123456789abcde0" + b"0123456789abcde0" +
+ b"0123456789abcde1" + b"0123456789abcde1")
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ b"0123456789abcde0")
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#ccccccccccccccccccccccccccccccccc",
+ b"0123456789abcde1")
#
# Modify that removes a value from the indexed attribute
@@ -918,6 +1110,17 @@ class MaxIndexKeyLengthTests(LdbBaseTest):
"notUnique")
self.l.modify(msg)
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ b"0123456789abcde0" +
+ b"0123456789abcde1" + b"0123456789abcde1")
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ b"0123456789abcde0")
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#ccccccccccccccccccccccccccccccccc",
+ b"0123456789abcde1")
+
#
# Modify that does a constrained delete the indexed attribute
#
@@ -929,6 +1132,16 @@ class MaxIndexKeyLengthTests(LdbBaseTest):
"notUnique")
self.l.modify(msg)
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ b"0123456789abcde0" + b"0123456789abcde1")
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ b"0123456789abcde0")
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#ccccccccccccccccccccccccccccccccc",
+ b"0123456789abcde1")
+
#
# Modify that does an unconstrained delete the indexed attribute
#
@@ -940,6 +1153,16 @@ class MaxIndexKeyLengthTests(LdbBaseTest):
"notUnique")
self.l.modify(msg)
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ b"0123456789abcde0")
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ b"0123456789abcde0")
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#ccccccccccccccccccccccccccccccccc",
+ None)
+
#
# Modify that adds a value to the indexed attribute
#
@@ -951,6 +1174,16 @@ class MaxIndexKeyLengthTests(LdbBaseTest):
"notUnique")
self.l.modify(msg)
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ b"0123456789abcde0")
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ b"0123456789abcde0")
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#ccccccccccccccccccccccccccccccccc",
+ b"0123456789abcde1")
+
#
# Modify that adds a values to the indexed attribute
#
@@ -962,6 +1195,17 @@ class MaxIndexKeyLengthTests(LdbBaseTest):
"notUnique")
self.l.modify(msg)
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ b"0123456789abcde0" +
+ b"0123456789abcde1" + b"0123456789abcde1")
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ b"0123456789abcde0")
+ self.checkGuids(
+ "@INDEX#NOTUNIQUE#ccccccccccccccccccccccccccccccccc",
+ b"0123456789abcde1")
+
#
# Test Sub tree searches when checkBaseOnSearch is enabled and the
# DN indexes are truncated and collide.
@@ -984,6 +1228,9 @@ class MaxIndexKeyLengthTests(LdbBaseTest):
"objectUUID": b"0123456789abcdef"})
self.l.add({"dn": "OU=CHECK_BASE_DN_XXXX,DC=SAMBA,DC=OR2",
"objectUUID": b"0123456789abcdee"})
+ self.checkGuids(
+ "@INDEX#@IDXDN#OU=CHECK_BASE_DN_XXXX,DC=SAMBA,DC=OR",
+ b"0123456789abcdee" + b"0123456789abcdef")
self.l.add({"dn": "OU=01,OU=CHECK_BASE_DN_XXXX,DC=SAMBA,DC=OR1",
"objectUUID": b"0123456789abcdec"})
@@ -991,6 +1238,9 @@ class MaxIndexKeyLengthTests(LdbBaseTest):
"objectUUID": b"0123456789abcdeb"})
self.l.add({"dn": "OU=01,OU=CHECK_BASE_DN_XXXX,DC=SAMBA,DC=OR3",
"objectUUID": b"0123456789abcded"})
+ self.checkGuids(
+ "@INDEX#@IDXDN#OU=01,OU=CHECK_BASE_DN_XXXX,DC=SAMBA",
+ b"0123456789abcdeb" + b"0123456789abcdec" + b"0123456789abcded")
self.l.add({"dn": "OU=02,OU=CHECK_BASE_DN_XXXX,DC=SAMBA,DC=OR1",
"objectUUID": b"0123456789abcde0"})
@@ -998,6 +1248,9 @@ class MaxIndexKeyLengthTests(LdbBaseTest):
"objectUUID": b"0123456789abcde1"})
self.l.add({"dn": "OU=02,OU=CHECK_BASE_DN_XXXX,DC=SAMBA,DC=OR3",
"objectUUID": b"0123456789abcde2"})
+ self.checkGuids(
+ "@INDEX#@IDXDN#OU=02,OU=CHECK_BASE_DN_XXXX,DC=SAMBA",
+ b"0123456789abcde0" + b"0123456789abcde1" + b"0123456789abcde2")
res = self.l.search(base="OU=CHECK_BASE_DN_XXXX,DC=SAMBA,DC=OR1",
scope=ldb.SCOPE_SUBTREE)
@@ -1027,6 +1280,49 @@ class MaxIndexKeyLengthTests(LdbBaseTest):
code = e.args[0]
self.assertEqual(ldb.ERR_NO_SUCH_OBJECT, code)
+
+# Run the index truncation tests against an lmdb backend
+class MaxIndexKeyLengthTestsLmdb(MaxIndexKeyLengthTests):
+
+ def setUp(self):
+ self.prefix = MDB_PREFIX
+ super(MaxIndexKeyLengthTestsLmdb, self).setUp()
+
+ def tearDown(self):
+ super(MaxIndexKeyLengthTestsLmdb, self).tearDown()
+
+
+# Run the index truncation tests against an lmdb backend
+class RejectSubDBIndex(LdbBaseTest):
+
+ def setUp(self):
+ self.prefix = MDB_PREFIX
+ super(RejectSubDBIndex, self).setUp()
+ self.testdir = tempdir()
+ self.filename = os.path.join(self.testdir,
+ "reject_subidx_test.ldb")
+ self.l = ldb.Ldb(self.url(),
+ options=[
+ "modules:rdn_name"])
+
+ def tearDown(self):
+ super(RejectSubDBIndex, self).tearDown()
+
+ def test_try_subdb_index(self):
+ try:
+ self.l.add({"dn": "@INDEXLIST",
+ "@IDX_LMDB_SUBDB": [b"1"],
+ "@IDXONE": [b"1"],
+ "@IDXONE": [b"1"],
+ "@IDXGUID": [b"objectUUID"],
+ "@IDX_DN_GUID": [b"GUID"],
+ })
+ except ldb.LdbError as e:
+ code = e.args[0]
+ string = e.args[1]
+ self.assertEqual(ldb.ERR_OPERATIONS_ERROR, code)
+ self.assertIn("sub-database index", string)
+
if __name__ == '__main__':
import unittest
unittest.TestProgram()
diff --git a/tests/test_ldb_qsort.c b/tests/test_ldb_qsort.c
new file mode 100644
index 0000000..06e80d9
--- /dev/null
+++ b/tests/test_ldb_qsort.c
@@ -0,0 +1,64 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * Copyright (C) 2018 Andreas Schneider <asn@samba.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+#include <ldb.h>
+
+static int cmp_integer(int *a, int *b, void *opaque)
+{
+ if (a == NULL || b == NULL) {
+ return 0;
+ }
+
+ if (*a > *b) {
+ return 1;
+ }
+
+ if (*a < *b) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static void test_ldb_qsort(void **state)
+{
+ int a[6] = { 6, 3, 2, 7, 9, 4 };
+
+ ldb_qsort(a, 6, sizeof(int), NULL, (ldb_qsort_cmp_fn_t)cmp_integer);
+
+ assert_int_equal(a[0], 2);
+ assert_int_equal(a[1], 3);
+ assert_int_equal(a[2], 4);
+ assert_int_equal(a[3], 6);
+ assert_int_equal(a[4], 7);
+ assert_int_equal(a[5], 9);
+}
+
+int main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(test_ldb_qsort),
+ };
+
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/third_party/cmocka/cmocka.c b/third_party/cmocka/cmocka.c
index 14b2765..0861c2c 100644
--- a/third_party/cmocka/cmocka.c
+++ b/third_party/cmocka/cmocka.c
@@ -45,7 +45,7 @@
#include <time.h>
/*
- * This allows to add a platform specific header file. Some embedded platforms
+ * This allows one to add a platform specific header file. Some embedded platforms
* sometimes miss certain types and definitions.
*
* Example:
@@ -244,8 +244,8 @@ static void free_symbol_map_value(
static void remove_always_return_values(ListNode * const map_head,
const size_t number_of_symbol_names);
-static int check_for_leftover_values_list(const ListNode * head,
- const char * const error_message);
+static size_t check_for_leftover_values_list(const ListNode * head,
+ const char * const error_message);
static int check_for_leftover_values(
const ListNode * const map_head, const char * const error_message,
@@ -811,11 +811,11 @@ static void remove_always_return_values(ListNode * const map_head,
}
}
-static int check_for_leftover_values_list(const ListNode * head,
- const char * const error_message)
+static size_t check_for_leftover_values_list(const ListNode * head,
+ const char * const error_message)
{
ListNode *child_node;
- int leftover_count = 0;
+ size_t leftover_count = 0;
if (!list_empty(head))
{
for (child_node = head->next; child_node != head;
@@ -1952,10 +1952,10 @@ static const ListNode* check_point_allocated_blocks(void) {
/* Display the blocks allocated after the specified check point. This
* function returns the number of blocks displayed. */
-static int display_allocated_blocks(const ListNode * const check_point) {
+static size_t display_allocated_blocks(const ListNode * const check_point) {
const ListNode * const head = get_allocated_blocks_list();
const ListNode *node;
- int allocated_blocks = 0;
+ size_t allocated_blocks = 0;
assert_non_null(check_point);
assert_non_null(check_point->next);
@@ -1964,14 +1964,14 @@ static int display_allocated_blocks(const ListNode * const check_point) {
(const MallocBlockInfo*)node->value;
assert_non_null(block_info);
- if (!allocated_blocks) {
+ if (allocated_blocks == 0) {
cm_print_error("Blocks allocated...\n");
}
cm_print_error(SOURCE_LOCATION_FORMAT ": note: block %p allocated here\n",
block_info->location.file,
block_info->location.line,
block_info->block);
- allocated_blocks ++;
+ allocated_blocks++;
}
return allocated_blocks;
}
@@ -1997,10 +1997,10 @@ static void free_allocated_blocks(const ListNode * const check_point) {
/* Fail if any any blocks are allocated after the specified check point. */
static void fail_if_blocks_allocated(const ListNode * const check_point,
const char * const test_name) {
- const int allocated_blocks = display_allocated_blocks(check_point);
- if (allocated_blocks) {
+ const size_t allocated_blocks = display_allocated_blocks(check_point);
+ if (allocated_blocks > 0) {
free_allocated_blocks(check_point);
- cm_print_error("ERROR: %s leaked %d block(s)\n", test_name,
+ cm_print_error("ERROR: %s leaked %zu block(s)\n", test_name,
allocated_blocks);
exit_test(1);
}
diff --git a/third_party/cmocka/cmocka.h b/third_party/cmocka/cmocka.h
index 72d6ae2..4fd82a9 100644
--- a/third_party/cmocka/cmocka.h
+++ b/third_party/cmocka/cmocka.h
@@ -2269,7 +2269,7 @@ enum cm_message_output {
/**
* @brief Function to set the output format for a test.
*
- * The ouput format for the test can either be set globally using this
+ * The output format for the test can either be set globally using this
* function or overriden with environment variable CMOCKA_MESSAGE_OUTPUT.
*
* The environment variable can be set to either STDOUT, SUBUNIT, TAP or XML.
diff --git a/third_party/popt/README b/third_party/popt/README
index 95f8f8d..c66432d 100644
--- a/third_party/popt/README
+++ b/third_party/popt/README
@@ -1,18 +1,16 @@
-This is the popt command line option parsing library. While it is similiar
+This is the popt(3) command line option parsing library. While it is similiar
to getopt(3), it contains a number of enhancements, including:
1) popt is fully reentrant
2) popt can parse arbitrary argv[] style arrays while
- getopt(2) makes this quite difficult
+ getopt(3) makes this quite difficult
3) popt allows users to alias command line arguments
4) popt provides convience functions for parsing strings
into argv[] style arrays
-popt is used by rpm, the Red Hat install program, and many other Red Hat
-utilities, all of which provide excellent examples of how to use popt.
-Complete documentation on popt is available in popt.ps (included in this
+Complete documentation on popt(3) is available in popt.ps (included in this
tarball), which is excerpted with permission from the book "Linux
Application Development" by Michael K. Johnson and Erik Troan (available
from Addison Wesley in May, 1998).
-Comments on popt should be addressed to ewt@redhat.com.
+Comments on popt should be addressed to popt-devel@rpm5.org.
diff --git a/third_party/popt/dummy.in b/third_party/popt/dummy.in
deleted file mode 100644
index e69de29..0000000
--- a/third_party/popt/dummy.in
+++ /dev/null
diff --git a/third_party/popt/findme.c b/third_party/popt/findme.c
deleted file mode 100644
index b28981b..0000000
--- a/third_party/popt/findme.c
+++ /dev/null
@@ -1,50 +0,0 @@
-/** \ingroup popt
- * \file popt/findme.c
- */
-
-/* (C) 1998-2002 Red Hat, Inc. -- Licensing details are in the COPYING
- file accompanying popt source distributions, available from
- ftp://ftp.rpm.org/pub/rpm/dist. */
-
-#include "system.h"
-#include "findme.h"
-
-const char * findProgramPath(const char * argv0) {
- char * path = getenv("PATH");
- char * pathbuf;
- char * start, * chptr;
- char * buf;
-
- if (argv0 == NULL) return NULL; /* XXX can't happen */
- /* If there is a / in the argv[0], it has to be an absolute path */
- if (strchr(argv0, '/'))
- return xstrdup(argv0);
-
- if (path == NULL) return NULL;
-
- start = pathbuf = (char *)alloca(strlen(path) + 1);
- buf = (char *)malloc(strlen(path) + strlen(argv0) + sizeof("/"));
- if (buf == NULL) return NULL; /* XXX can't happen */
- strcpy(pathbuf, path);
-
- chptr = NULL;
- /*@-branchstate@*/
- do {
- if ((chptr = strchr(start, ':')))
- *chptr = '\0';
- sprintf(buf, "%s/%s", start, argv0);
-
- if (!access(buf, X_OK))
- return buf;
-
- if (chptr)
- start = chptr + 1;
- else
- start = NULL;
- } while (start && *start);
- /*@=branchstate@*/
-
- free(buf);
-
- return NULL;
-}
diff --git a/third_party/popt/lookup3.c b/third_party/popt/lookup3.c
new file mode 100644
index 0000000..eb4e5ce
--- /dev/null
+++ b/third_party/popt/lookup3.c
@@ -0,0 +1,969 @@
+/* -------------------------------------------------------------------- */
+/*
+ * lookup3.c, by Bob Jenkins, May 2006, Public Domain.
+ *
+ * These are functions for producing 32-bit hashes for hash table lookup.
+ * jlu32w(), jlu32l(), jlu32lpair(), jlu32b(), _JLU3_MIX(), and _JLU3_FINAL()
+ * are externally useful functions. Routines to test the hash are included
+ * if SELF_TEST is defined. You can use this free for any purpose. It's in
+ * the public domain. It has no warranty.
+ *
+ * You probably want to use jlu32l(). jlu32l() and jlu32b()
+ * hash byte arrays. jlu32l() is is faster than jlu32b() on
+ * little-endian machines. Intel and AMD are little-endian machines.
+ * On second thought, you probably want jlu32lpair(), which is identical to
+ * jlu32l() except it returns two 32-bit hashes for the price of one.
+ * You could implement jlu32bpair() if you wanted but I haven't bothered here.
+ *
+ * If you want to find a hash of, say, exactly 7 integers, do
+ * a = i1; b = i2; c = i3;
+ * _JLU3_MIX(a,b,c);
+ * a += i4; b += i5; c += i6;
+ * _JLU3_MIX(a,b,c);
+ * a += i7;
+ * _JLU3_FINAL(a,b,c);
+ * then use c as the hash value. If you have a variable size array of
+ * 4-byte integers to hash, use jlu32w(). If you have a byte array (like
+ * a character string), use jlu32l(). If you have several byte arrays, or
+ * a mix of things, see the comments above jlu32l().
+ *
+ * Why is this so big? I read 12 bytes at a time into 3 4-byte integers,
+ * then mix those integers. This is fast (you can do a lot more thorough
+ * mixing with 12*3 instructions on 3 integers than you can with 3 instructions
+ * on 1 byte), but shoehorning those bytes into integers efficiently is messy.
+*/
+/* -------------------------------------------------------------------- */
+
+#include <stdint.h>
+
+#if defined(_JLU3_SELFTEST)
+# define _JLU3_jlu32w 1
+# define _JLU3_jlu32l 1
+# define _JLU3_jlu32lpair 1
+# define _JLU3_jlu32b 1
+#endif
+
+/*@-redef@*/
+/*@unchecked@*/
+static const union _dbswap {
+ const uint32_t ui;
+ const unsigned char uc[4];
+} endian = { .ui = 0x11223344 };
+# define HASH_LITTLE_ENDIAN (endian.uc[0] == (unsigned char) 0x44)
+# define HASH_BIG_ENDIAN (endian.uc[0] == (unsigned char) 0x11)
+/*@=redef@*/
+
+#ifndef ROTL32
+# define ROTL32(x, s) (((x) << (s)) | ((x) >> (32 - (s))))
+#endif
+
+/* NOTE: The _size parameter should be in bytes. */
+#define _JLU3_INIT(_h, _size) (0xdeadbeef + ((uint32_t)(_size)) + (_h))
+
+/* -------------------------------------------------------------------- */
+/*
+ * _JLU3_MIX -- mix 3 32-bit values reversibly.
+ *
+ * This is reversible, so any information in (a,b,c) before _JLU3_MIX() is
+ * still in (a,b,c) after _JLU3_MIX().
+ *
+ * If four pairs of (a,b,c) inputs are run through _JLU3_MIX(), or through
+ * _JLU3_MIX() in reverse, there are at least 32 bits of the output that
+ * are sometimes the same for one pair and different for another pair.
+ * This was tested for:
+ * * pairs that differed by one bit, by two bits, in any combination
+ * of top bits of (a,b,c), or in any combination of bottom bits of
+ * (a,b,c).
+ * * "differ" is defined as +, -, ^, or ~^. For + and -, I transformed
+ * the output delta to a Gray code (a^(a>>1)) so a string of 1's (as
+ * is commonly produced by subtraction) look like a single 1-bit
+ * difference.
+ * * the base values were pseudorandom, all zero but one bit set, or
+ * all zero plus a counter that starts at zero.
+ *
+ * Some k values for my "a-=c; a^=ROTL32(c,k); c+=b;" arrangement that
+ * satisfy this are
+ * 4 6 8 16 19 4
+ * 9 15 3 18 27 15
+ * 14 9 3 7 17 3
+ * Well, "9 15 3 18 27 15" didn't quite get 32 bits diffing
+ * for "differ" defined as + with a one-bit base and a two-bit delta. I
+ * used http://burtleburtle.net/bob/hash/avalanche.html to choose
+ * the operations, constants, and arrangements of the variables.
+ *
+ * This does not achieve avalanche. There are input bits of (a,b,c)
+ * that fail to affect some output bits of (a,b,c), especially of a. The
+ * most thoroughly mixed value is c, but it doesn't really even achieve
+ * avalanche in c.
+ *
+ * This allows some parallelism. Read-after-writes are good at doubling
+ * the number of bits affected, so the goal of mixing pulls in the opposite
+ * direction as the goal of parallelism. I did what I could. Rotates
+ * seem to cost as much as shifts on every machine I could lay my hands
+ * on, and rotates are much kinder to the top and bottom bits, so I used
+ * rotates.
+ */
+/* -------------------------------------------------------------------- */
+#define _JLU3_MIX(a,b,c) \
+{ \
+ a -= c; a ^= ROTL32(c, 4); c += b; \
+ b -= a; b ^= ROTL32(a, 6); a += c; \
+ c -= b; c ^= ROTL32(b, 8); b += a; \
+ a -= c; a ^= ROTL32(c,16); c += b; \
+ b -= a; b ^= ROTL32(a,19); a += c; \
+ c -= b; c ^= ROTL32(b, 4); b += a; \
+}
+
+/* -------------------------------------------------------------------- */
+/**
+ * _JLU3_FINAL -- final mixing of 3 32-bit values (a,b,c) into c
+ *
+ * Pairs of (a,b,c) values differing in only a few bits will usually
+ * produce values of c that look totally different. This was tested for
+ * * pairs that differed by one bit, by two bits, in any combination
+ * of top bits of (a,b,c), or in any combination of bottom bits of
+ * (a,b,c).
+ * * "differ" is defined as +, -, ^, or ~^. For + and -, I transformed
+ * the output delta to a Gray code (a^(a>>1)) so a string of 1's (as
+ * is commonly produced by subtraction) look like a single 1-bit
+ * difference.
+ * * the base values were pseudorandom, all zero but one bit set, or
+ * all zero plus a counter that starts at zero.
+ *
+ * These constants passed:
+ * 14 11 25 16 4 14 24
+ * 12 14 25 16 4 14 24
+ * and these came close:
+ * 4 8 15 26 3 22 24
+ * 10 8 15 26 3 22 24
+ * 11 8 15 26 3 22 24
+ */
+/* -------------------------------------------------------------------- */
+#define _JLU3_FINAL(a,b,c) \
+{ \
+ c ^= b; c -= ROTL32(b,14); \
+ a ^= c; a -= ROTL32(c,11); \
+ b ^= a; b -= ROTL32(a,25); \
+ c ^= b; c -= ROTL32(b,16); \
+ a ^= c; a -= ROTL32(c,4); \
+ b ^= a; b -= ROTL32(a,14); \
+ c ^= b; c -= ROTL32(b,24); \
+}
+
+#if defined(_JLU3_jlu32w)
+uint32_t jlu32w(uint32_t h, /*@null@*/ const uint32_t *k, size_t size)
+ /*@*/;
+/* -------------------------------------------------------------------- */
+/**
+ * This works on all machines. To be useful, it requires
+ * -- that the key be an array of uint32_t's, and
+ * -- that the size be the number of uint32_t's in the key
+ *
+ * The function jlu32w() is identical to jlu32l() on little-endian
+ * machines, and identical to jlu32b() on big-endian machines,
+ * except that the size has to be measured in uint32_ts rather than in
+ * bytes. jlu32l() is more complicated than jlu32w() only because
+ * jlu32l() has to dance around fitting the key bytes into registers.
+ *
+ * @param h the previous hash, or an arbitrary value
+ * @param *k the key, an array of uint32_t values
+ * @param size the size of the key, in uint32_ts
+ * @return the lookup3 hash
+ */
+/* -------------------------------------------------------------------- */
+uint32_t jlu32w(uint32_t h, const uint32_t *k, size_t size)
+{
+ uint32_t a = _JLU3_INIT(h, (size * sizeof(*k)));
+ uint32_t b = a;
+ uint32_t c = a;
+
+ if (k == NULL)
+ goto exit;
+
+ /*----------------------------------------------- handle most of the key */
+ while (size > 3) {
+ a += k[0];
+ b += k[1];
+ c += k[2];
+ _JLU3_MIX(a,b,c);
+ size -= 3;
+ k += 3;
+ }
+
+ /*----------------------------------------- handle the last 3 uint32_t's */
+ switch (size) {
+ case 3 : c+=k[2];
+ case 2 : b+=k[1];
+ case 1 : a+=k[0];
+ _JLU3_FINAL(a,b,c);
+ /*@fallthrough@*/
+ case 0:
+ break;
+ }
+ /*---------------------------------------------------- report the result */
+exit:
+ return c;
+}
+#endif /* defined(_JLU3_jlu32w) */
+
+#if defined(_JLU3_jlu32l)
+uint32_t jlu32l(uint32_t h, const void *key, size_t size)
+ /*@*/;
+/* -------------------------------------------------------------------- */
+/*
+ * jlu32l() -- hash a variable-length key into a 32-bit value
+ * h : can be any 4-byte value
+ * k : the key (the unaligned variable-length array of bytes)
+ * size : the size of the key, counting by bytes
+ * Returns a 32-bit value. Every bit of the key affects every bit of
+ * the return value. Two keys differing by one or two bits will have
+ * totally different hash values.
+ *
+ * The best hash table sizes are powers of 2. There is no need to do
+ * mod a prime (mod is sooo slow!). If you need less than 32 bits,
+ * use a bitmask. For example, if you need only 10 bits, do
+ * h = (h & hashmask(10));
+ * In which case, the hash table should have hashsize(10) elements.
+ *
+ * If you are hashing n strings (uint8_t **)k, do it like this:
+ * for (i=0, h=0; i<n; ++i) h = jlu32l(h, k[i], len[i]);
+ *
+ * By Bob Jenkins, 2006. bob_jenkins@burtleburtle.net. You may use this
+ * code any way you wish, private, educational, or commercial. It's free.
+ *
+ * Use for hash table lookup, or anything where one collision in 2^^32 is
+ * acceptable. Do NOT use for cryptographic purposes.
+ *
+ * @param h the previous hash, or an arbitrary value
+ * @param *k the key, an array of uint8_t values
+ * @param size the size of the key
+ * @return the lookup3 hash
+ */
+/* -------------------------------------------------------------------- */
+uint32_t jlu32l(uint32_t h, const void *key, size_t size)
+{
+ union { const void *ptr; size_t i; } u;
+ uint32_t a = _JLU3_INIT(h, size);
+ uint32_t b = a;
+ uint32_t c = a;
+
+ if (key == NULL)
+ goto exit;
+
+ u.ptr = key;
+ if (HASH_LITTLE_ENDIAN && ((u.i & 0x3) == 0)) {
+ const uint32_t *k = (const uint32_t *)key; /* read 32-bit chunks */
+#ifdef VALGRIND
+ const uint8_t *k8;
+#endif
+
+ /*------ all but last block: aligned reads and affect 32 bits of (a,b,c) */
+ while (size > 12) {
+ a += k[0];
+ b += k[1];
+ c += k[2];
+ _JLU3_MIX(a,b,c);
+ size -= 12;
+ k += 3;
+ }
+
+ /*------------------------- handle the last (probably partial) block */
+ /*
+ * "k[2]&0xffffff" actually reads beyond the end of the string, but
+ * then masks off the part it's not allowed to read. Because the
+ * string is aligned, the masked-off tail is in the same word as the
+ * rest of the string. Every machine with memory protection I've seen
+ * does it on word boundaries, so is OK with this. But VALGRIND will
+ * still catch it and complain. The masking trick does make the hash
+ * noticably faster for short strings (like English words).
+ */
+#ifndef VALGRIND
+
+ switch (size) {
+ case 12: c += k[2]; b+=k[1]; a+=k[0]; break;
+ case 11: c += k[2]&0xffffff; b+=k[1]; a+=k[0]; break;
+ case 10: c += k[2]&0xffff; b+=k[1]; a+=k[0]; break;
+ case 9: c += k[2]&0xff; b+=k[1]; a+=k[0]; break;
+ case 8: b += k[1]; a+=k[0]; break;
+ case 7: b += k[1]&0xffffff; a+=k[0]; break;
+ case 6: b += k[1]&0xffff; a+=k[0]; break;
+ case 5: b += k[1]&0xff; a+=k[0]; break;
+ case 4: a += k[0]; break;
+ case 3: a += k[0]&0xffffff; break;
+ case 2: a += k[0]&0xffff; break;
+ case 1: a += k[0]&0xff; break;
+ case 0: goto exit;
+ }
+
+#else /* make valgrind happy */
+
+ k8 = (const uint8_t *)k;
+ switch (size) {
+ case 12: c += k[2]; b+=k[1]; a+=k[0] break;
+ case 11: c += ((uint32_t)k8[10])<<16; /*@fallthrough@*/
+ case 10: c += ((uint32_t)k8[9])<<8; /*@fallthrough@*/
+ case 9: c += k8[8]; /*@fallthrough@*/
+ case 8: b += k[1]; a+=k[0]; break;
+ case 7: b += ((uint32_t)k8[6])<<16; /*@fallthrough@*/
+ case 6: b += ((uint32_t)k8[5])<<8; /*@fallthrough@*/
+ case 5: b += k8[4]; /*@fallthrough@*/
+ case 4: a += k[0]; break;
+ case 3: a += ((uint32_t)k8[2])<<16; /*@fallthrough@*/
+ case 2: a += ((uint32_t)k8[1])<<8; /*@fallthrough@*/
+ case 1: a += k8[0]; break;
+ case 0: goto exit;
+ }
+
+#endif /* !valgrind */
+
+ } else if (HASH_LITTLE_ENDIAN && ((u.i & 0x1) == 0)) {
+ const uint16_t *k = (const uint16_t *)key; /* read 16-bit chunks */
+ const uint8_t *k8;
+
+ /*----------- all but last block: aligned reads and different mixing */
+ while (size > 12) {
+ a += k[0] + (((uint32_t)k[1])<<16);
+ b += k[2] + (((uint32_t)k[3])<<16);
+ c += k[4] + (((uint32_t)k[5])<<16);
+ _JLU3_MIX(a,b,c);
+ size -= 12;
+ k += 6;
+ }
+
+ /*------------------------- handle the last (probably partial) block */
+ k8 = (const uint8_t *)k;
+ switch (size) {
+ case 12:
+ c += k[4]+(((uint32_t)k[5])<<16);
+ b += k[2]+(((uint32_t)k[3])<<16);
+ a += k[0]+(((uint32_t)k[1])<<16);
+ break;
+ case 11:
+ c += ((uint32_t)k8[10])<<16;
+ /*@fallthrough@*/
+ case 10:
+ c += (uint32_t)k[4];
+ b += k[2]+(((uint32_t)k[3])<<16);
+ a += k[0]+(((uint32_t)k[1])<<16);
+ break;
+ case 9:
+ c += (uint32_t)k8[8];
+ /*@fallthrough@*/
+ case 8:
+ b += k[2]+(((uint32_t)k[3])<<16);
+ a += k[0]+(((uint32_t)k[1])<<16);
+ break;
+ case 7:
+ b += ((uint32_t)k8[6])<<16;
+ /*@fallthrough@*/
+ case 6:
+ b += (uint32_t)k[2];
+ a += k[0]+(((uint32_t)k[1])<<16);
+ break;
+ case 5:
+ b += (uint32_t)k8[4];
+ /*@fallthrough@*/
+ case 4:
+ a += k[0]+(((uint32_t)k[1])<<16);
+ break;
+ case 3:
+ a += ((uint32_t)k8[2])<<16;
+ /*@fallthrough@*/
+ case 2:
+ a += (uint32_t)k[0];
+ break;
+ case 1:
+ a += (uint32_t)k8[0];
+ break;
+ case 0:
+ goto exit;
+ }
+
+ } else { /* need to read the key one byte at a time */
+ const uint8_t *k = (const uint8_t *)key;
+
+ /*----------- all but the last block: affect some 32 bits of (a,b,c) */
+ while (size > 12) {
+ a += (uint32_t)k[0];
+ a += ((uint32_t)k[1])<<8;
+ a += ((uint32_t)k[2])<<16;
+ a += ((uint32_t)k[3])<<24;
+ b += (uint32_t)k[4];
+ b += ((uint32_t)k[5])<<8;
+ b += ((uint32_t)k[6])<<16;
+ b += ((uint32_t)k[7])<<24;
+ c += (uint32_t)k[8];
+ c += ((uint32_t)k[9])<<8;
+ c += ((uint32_t)k[10])<<16;
+ c += ((uint32_t)k[11])<<24;
+ _JLU3_MIX(a,b,c);
+ size -= 12;
+ k += 12;
+ }
+
+ /*---------------------------- last block: affect all 32 bits of (c) */
+ switch (size) {
+ case 12: c += ((uint32_t)k[11])<<24; /*@fallthrough@*/
+ case 11: c += ((uint32_t)k[10])<<16; /*@fallthrough@*/
+ case 10: c += ((uint32_t)k[9])<<8; /*@fallthrough@*/
+ case 9: c += (uint32_t)k[8]; /*@fallthrough@*/
+ case 8: b += ((uint32_t)k[7])<<24; /*@fallthrough@*/
+ case 7: b += ((uint32_t)k[6])<<16; /*@fallthrough@*/
+ case 6: b += ((uint32_t)k[5])<<8; /*@fallthrough@*/
+ case 5: b += (uint32_t)k[4]; /*@fallthrough@*/
+ case 4: a += ((uint32_t)k[3])<<24; /*@fallthrough@*/
+ case 3: a += ((uint32_t)k[2])<<16; /*@fallthrough@*/
+ case 2: a += ((uint32_t)k[1])<<8; /*@fallthrough@*/
+ case 1: a += (uint32_t)k[0];
+ break;
+ case 0:
+ goto exit;
+ }
+ }
+
+ _JLU3_FINAL(a,b,c);
+
+exit:
+ return c;
+}
+#endif /* defined(_JLU3_jlu32l) */
+
+#if defined(_JLU3_jlu32lpair)
+/**
+ * jlu32lpair: return 2 32-bit hash values.
+ *
+ * This is identical to jlu32l(), except it returns two 32-bit hash
+ * values instead of just one. This is good enough for hash table
+ * lookup with 2^^64 buckets, or if you want a second hash if you're not
+ * happy with the first, or if you want a probably-unique 64-bit ID for
+ * the key. *pc is better mixed than *pb, so use *pc first. If you want
+ * a 64-bit value do something like "*pc + (((uint64_t)*pb)<<32)".
+ *
+ * @param h the previous hash, or an arbitrary value
+ * @param *key the key, an array of uint8_t values
+ * @param size the size of the key in bytes
+ * @retval *pc, IN: primary initval, OUT: primary hash
+ * *retval *pb IN: secondary initval, OUT: secondary hash
+ */
+void jlu32lpair(const void *key, size_t size, uint32_t *pc, uint32_t *pb)
+{
+ union { const void *ptr; size_t i; } u;
+ uint32_t a = _JLU3_INIT(*pc, size);
+ uint32_t b = a;
+ uint32_t c = a;
+
+ if (key == NULL)
+ goto exit;
+
+ c += *pb; /* Add the secondary hash. */
+
+ u.ptr = key;
+ if (HASH_LITTLE_ENDIAN && ((u.i & 0x3) == 0)) {
+ const uint32_t *k = (const uint32_t *)key; /* read 32-bit chunks */
+#ifdef VALGRIND
+ const uint8_t *k8;
+#endif
+
+ /*-- all but last block: aligned reads and affect 32 bits of (a,b,c) */
+ while (size > (size_t)12) {
+ a += k[0];
+ b += k[1];
+ c += k[2];
+ _JLU3_MIX(a,b,c);
+ size -= 12;
+ k += 3;
+ }
+ /*------------------------- handle the last (probably partial) block */
+ /*
+ * "k[2]&0xffffff" actually reads beyond the end of the string, but
+ * then masks off the part it's not allowed to read. Because the
+ * string is aligned, the masked-off tail is in the same word as the
+ * rest of the string. Every machine with memory protection I've seen
+ * does it on word boundaries, so is OK with this. But VALGRIND will
+ * still catch it and complain. The masking trick does make the hash
+ * noticably faster for short strings (like English words).
+ */
+#ifndef VALGRIND
+
+ switch (size) {
+ case 12: c += k[2]; b+=k[1]; a+=k[0]; break;
+ case 11: c += k[2]&0xffffff; b+=k[1]; a+=k[0]; break;
+ case 10: c += k[2]&0xffff; b+=k[1]; a+=k[0]; break;
+ case 9: c += k[2]&0xff; b+=k[1]; a+=k[0]; break;
+ case 8: b += k[1]; a+=k[0]; break;
+ case 7: b += k[1]&0xffffff; a+=k[0]; break;
+ case 6: b += k[1]&0xffff; a+=k[0]; break;
+ case 5: b += k[1]&0xff; a+=k[0]; break;
+ case 4: a += k[0]; break;
+ case 3: a += k[0]&0xffffff; break;
+ case 2: a += k[0]&0xffff; break;
+ case 1: a += k[0]&0xff; break;
+ case 0: goto exit;
+ }
+
+#else /* make valgrind happy */
+
+ k8 = (const uint8_t *)k;
+ switch (size) {
+ case 12: c += k[2]; b+=k[1]; a+=k[0]; break;
+ case 11: c += ((uint32_t)k8[10])<<16; /*@fallthrough@*/
+ case 10: c += ((uint32_t)k8[9])<<8; /*@fallthrough@*/
+ case 9: c += k8[8]; /*@fallthrough@*/
+ case 8: b += k[1]; a+=k[0]; break;
+ case 7: b += ((uint32_t)k8[6])<<16; /*@fallthrough@*/
+ case 6: b += ((uint32_t)k8[5])<<8; /*@fallthrough@*/
+ case 5: b += k8[4]; /*@fallthrough@*/
+ case 4: a += k[0]; break;
+ case 3: a += ((uint32_t)k8[2])<<16; /*@fallthrough@*/
+ case 2: a += ((uint32_t)k8[1])<<8; /*@fallthrough@*/
+ case 1: a += k8[0]; break;
+ case 0: goto exit;
+ }
+
+#endif /* !valgrind */
+
+ } else if (HASH_LITTLE_ENDIAN && ((u.i & 0x1) == 0)) {
+ const uint16_t *k = (const uint16_t *)key; /* read 16-bit chunks */
+ const uint8_t *k8;
+
+ /*----------- all but last block: aligned reads and different mixing */
+ while (size > (size_t)12) {
+ a += k[0] + (((uint32_t)k[1])<<16);
+ b += k[2] + (((uint32_t)k[3])<<16);
+ c += k[4] + (((uint32_t)k[5])<<16);
+ _JLU3_MIX(a,b,c);
+ size -= 12;
+ k += 6;
+ }
+
+ /*------------------------- handle the last (probably partial) block */
+ k8 = (const uint8_t *)k;
+ switch (size) {
+ case 12:
+ c += k[4]+(((uint32_t)k[5])<<16);
+ b += k[2]+(((uint32_t)k[3])<<16);
+ a += k[0]+(((uint32_t)k[1])<<16);
+ break;
+ case 11:
+ c += ((uint32_t)k8[10])<<16;
+ /*@fallthrough@*/
+ case 10:
+ c += k[4];
+ b += k[2]+(((uint32_t)k[3])<<16);
+ a += k[0]+(((uint32_t)k[1])<<16);
+ break;
+ case 9:
+ c += k8[8];
+ /*@fallthrough@*/
+ case 8:
+ b += k[2]+(((uint32_t)k[3])<<16);
+ a += k[0]+(((uint32_t)k[1])<<16);
+ break;
+ case 7:
+ b += ((uint32_t)k8[6])<<16;
+ /*@fallthrough@*/
+ case 6:
+ b += k[2];
+ a += k[0]+(((uint32_t)k[1])<<16);
+ break;
+ case 5:
+ b += k8[4];
+ /*@fallthrough@*/
+ case 4:
+ a += k[0]+(((uint32_t)k[1])<<16);
+ break;
+ case 3:
+ a += ((uint32_t)k8[2])<<16;
+ /*@fallthrough@*/
+ case 2:
+ a += k[0];
+ break;
+ case 1:
+ a += k8[0];
+ break;
+ case 0:
+ goto exit;
+ }
+
+ } else { /* need to read the key one byte at a time */
+ const uint8_t *k = (const uint8_t *)key;
+
+ /*----------- all but the last block: affect some 32 bits of (a,b,c) */
+ while (size > (size_t)12) {
+ a += k[0];
+ a += ((uint32_t)k[1])<<8;
+ a += ((uint32_t)k[2])<<16;
+ a += ((uint32_t)k[3])<<24;
+ b += k[4];
+ b += ((uint32_t)k[5])<<8;
+ b += ((uint32_t)k[6])<<16;
+ b += ((uint32_t)k[7])<<24;
+ c += k[8];
+ c += ((uint32_t)k[9])<<8;
+ c += ((uint32_t)k[10])<<16;
+ c += ((uint32_t)k[11])<<24;
+ _JLU3_MIX(a,b,c);
+ size -= 12;
+ k += 12;
+ }
+
+ /*---------------------------- last block: affect all 32 bits of (c) */
+ switch (size) {
+ case 12: c += ((uint32_t)k[11])<<24; /*@fallthrough@*/
+ case 11: c += ((uint32_t)k[10])<<16; /*@fallthrough@*/
+ case 10: c += ((uint32_t)k[9])<<8; /*@fallthrough@*/
+ case 9: c += k[8]; /*@fallthrough@*/
+ case 8: b += ((uint32_t)k[7])<<24; /*@fallthrough@*/
+ case 7: b += ((uint32_t)k[6])<<16; /*@fallthrough@*/
+ case 6: b += ((uint32_t)k[5])<<8; /*@fallthrough@*/
+ case 5: b += k[4]; /*@fallthrough@*/
+ case 4: a += ((uint32_t)k[3])<<24; /*@fallthrough@*/
+ case 3: a += ((uint32_t)k[2])<<16; /*@fallthrough@*/
+ case 2: a += ((uint32_t)k[1])<<8; /*@fallthrough@*/
+ case 1: a += k[0];
+ break;
+ case 0:
+ goto exit;
+ }
+ }
+
+ _JLU3_FINAL(a,b,c);
+
+exit:
+ *pc = c;
+ *pb = b;
+ return;
+}
+#endif /* defined(_JLU3_jlu32lpair) */
+
+#if defined(_JLU3_jlu32b)
+uint32_t jlu32b(uint32_t h, /*@null@*/ const void *key, size_t size)
+ /*@*/;
+/*
+ * jlu32b():
+ * This is the same as jlu32w() on big-endian machines. It is different
+ * from jlu32l() on all machines. jlu32b() takes advantage of
+ * big-endian byte ordering.
+ *
+ * @param h the previous hash, or an arbitrary value
+ * @param *k the key, an array of uint8_t values
+ * @param size the size of the key
+ * @return the lookup3 hash
+ */
+uint32_t jlu32b(uint32_t h, const void *key, size_t size)
+{
+ union { const void *ptr; size_t i; } u;
+ uint32_t a = _JLU3_INIT(h, size);
+ uint32_t b = a;
+ uint32_t c = a;
+
+ if (key == NULL)
+ return h;
+
+ u.ptr = key;
+ if (HASH_BIG_ENDIAN && ((u.i & 0x3) == 0)) {
+ const uint32_t *k = (const uint32_t *)key; /* read 32-bit chunks */
+#ifdef VALGRIND
+ const uint8_t *k8;
+#endif
+
+ /*-- all but last block: aligned reads and affect 32 bits of (a,b,c) */
+ while (size > 12) {
+ a += k[0];
+ b += k[1];
+ c += k[2];
+ _JLU3_MIX(a,b,c);
+ size -= 12;
+ k += 3;
+ }
+
+ /*------------------------- handle the last (probably partial) block */
+ /*
+ * "k[2]<<8" actually reads beyond the end of the string, but
+ * then shifts out the part it's not allowed to read. Because the
+ * string is aligned, the illegal read is in the same word as the
+ * rest of the string. Every machine with memory protection I've seen
+ * does it on word boundaries, so is OK with this. But VALGRIND will
+ * still catch it and complain. The masking trick does make the hash
+ * noticably faster for short strings (like English words).
+ */
+#ifndef VALGRIND
+
+ switch (size) {
+ case 12: c += k[2]; b+=k[1]; a+=k[0]; break;
+ case 11: c += k[2]&0xffffff00; b+=k[1]; a+=k[0]; break;
+ case 10: c += k[2]&0xffff0000; b+=k[1]; a+=k[0]; break;
+ case 9: c += k[2]&0xff000000; b+=k[1]; a+=k[0]; break;
+ case 8: b += k[1]; a+=k[0]; break;
+ case 7: b += k[1]&0xffffff00; a+=k[0]; break;
+ case 6: b += k[1]&0xffff0000; a+=k[0]; break;
+ case 5: b += k[1]&0xff000000; a+=k[0]; break;
+ case 4: a += k[0]; break;
+ case 3: a += k[0]&0xffffff00; break;
+ case 2: a += k[0]&0xffff0000; break;
+ case 1: a += k[0]&0xff000000; break;
+ case 0: goto exit;
+ }
+
+#else /* make valgrind happy */
+
+ k8 = (const uint8_t *)k;
+ switch (size) { /* all the case statements fall through */
+ case 12: c += k[2]; b+=k[1]; a+=k[0]; break;
+ case 11: c += ((uint32_t)k8[10])<<8; /*@fallthrough@*/
+ case 10: c += ((uint32_t)k8[9])<<16; /*@fallthrough@*/
+ case 9: c += ((uint32_t)k8[8])<<24; /*@fallthrough@*/
+ case 8: b += k[1]; a+=k[0]; break;
+ case 7: b += ((uint32_t)k8[6])<<8; /*@fallthrough@*/
+ case 6: b += ((uint32_t)k8[5])<<16; /*@fallthrough@*/
+ case 5: b += ((uint32_t)k8[4])<<24; /*@fallthrough@*/
+ case 4: a += k[0]; break;
+ case 3: a += ((uint32_t)k8[2])<<8; /*@fallthrough@*/
+ case 2: a += ((uint32_t)k8[1])<<16; /*@fallthrough@*/
+ case 1: a += ((uint32_t)k8[0])<<24; break;
+ case 0: goto exit;
+ }
+
+#endif /* !VALGRIND */
+
+ } else { /* need to read the key one byte at a time */
+ const uint8_t *k = (const uint8_t *)key;
+
+ /*----------- all but the last block: affect some 32 bits of (a,b,c) */
+ while (size > 12) {
+ a += ((uint32_t)k[0])<<24;
+ a += ((uint32_t)k[1])<<16;
+ a += ((uint32_t)k[2])<<8;
+ a += ((uint32_t)k[3]);
+ b += ((uint32_t)k[4])<<24;
+ b += ((uint32_t)k[5])<<16;
+ b += ((uint32_t)k[6])<<8;
+ b += ((uint32_t)k[7]);
+ c += ((uint32_t)k[8])<<24;
+ c += ((uint32_t)k[9])<<16;
+ c += ((uint32_t)k[10])<<8;
+ c += ((uint32_t)k[11]);
+ _JLU3_MIX(a,b,c);
+ size -= 12;
+ k += 12;
+ }
+
+ /*---------------------------- last block: affect all 32 bits of (c) */
+ switch (size) { /* all the case statements fall through */
+ case 12: c += k[11]; /*@fallthrough@*/
+ case 11: c += ((uint32_t)k[10])<<8; /*@fallthrough@*/
+ case 10: c += ((uint32_t)k[9])<<16; /*@fallthrough@*/
+ case 9: c += ((uint32_t)k[8])<<24; /*@fallthrough@*/
+ case 8: b += k[7]; /*@fallthrough@*/
+ case 7: b += ((uint32_t)k[6])<<8; /*@fallthrough@*/
+ case 6: b += ((uint32_t)k[5])<<16; /*@fallthrough@*/
+ case 5: b += ((uint32_t)k[4])<<24; /*@fallthrough@*/
+ case 4: a += k[3]; /*@fallthrough@*/
+ case 3: a += ((uint32_t)k[2])<<8; /*@fallthrough@*/
+ case 2: a += ((uint32_t)k[1])<<16; /*@fallthrough@*/
+ case 1: a += ((uint32_t)k[0])<<24; /*@fallthrough@*/
+ break;
+ case 0:
+ goto exit;
+ }
+ }
+
+ _JLU3_FINAL(a,b,c);
+
+exit:
+ return c;
+}
+#endif /* defined(_JLU3_jlu32b) */
+
+#if defined(_JLU3_SELFTEST)
+
+/* used for timings */
+static void driver1(void)
+ /*@*/
+{
+ uint8_t buf[256];
+ uint32_t i;
+ uint32_t h=0;
+ time_t a,z;
+
+ time(&a);
+ for (i=0; i<256; ++i) buf[i] = 'x';
+ for (i=0; i<1; ++i) {
+ h = jlu32l(h, &buf[0], sizeof(buf[0]));
+ }
+ time(&z);
+ if (z-a > 0) printf("time %d %.8x\n", (int)(z-a), h);
+}
+
+/* check that every input bit changes every output bit half the time */
+#define HASHSTATE 1
+#define HASHLEN 1
+#define MAXPAIR 60
+#define MAXLEN 70
+static void driver2(void)
+ /*@*/
+{
+ uint8_t qa[MAXLEN+1], qb[MAXLEN+2], *a = &qa[0], *b = &qb[1];
+ uint32_t c[HASHSTATE], d[HASHSTATE], i=0, j=0, k, l, m=0, z;
+ uint32_t e[HASHSTATE],f[HASHSTATE],g[HASHSTATE],h[HASHSTATE];
+ uint32_t x[HASHSTATE],y[HASHSTATE];
+ uint32_t hlen;
+
+ printf("No more than %d trials should ever be needed \n",MAXPAIR/2);
+ for (hlen=0; hlen < MAXLEN; ++hlen) {
+ z=0;
+ for (i=0; i<hlen; ++i) { /*-------------- for each input byte, */
+ for (j=0; j<8; ++j) { /*--------------- for each input bit, */
+ for (m=1; m<8; ++m) { /*--- for serveral possible initvals, */
+ for (l=0; l<HASHSTATE; ++l)
+ e[l]=f[l]=g[l]=h[l]=x[l]=y[l]=~((uint32_t)0);
+
+ /* check that every output bit is affected by that input bit */
+ for (k=0; k<MAXPAIR; k+=2) {
+ uint32_t finished=1;
+ /* keys have one bit different */
+ for (l=0; l<hlen+1; ++l) {a[l] = b[l] = (uint8_t)0;}
+ /* have a and b be two keys differing in only one bit */
+ a[i] ^= (k<<j);
+ a[i] ^= (k>>(8-j));
+ c[0] = jlu32l(m, a, hlen);
+ b[i] ^= ((k+1)<<j);
+ b[i] ^= ((k+1)>>(8-j));
+ d[0] = jlu32l(m, b, hlen);
+ /* check every bit is 1, 0, set, and not set at least once */
+ for (l=0; l<HASHSTATE; ++l) {
+ e[l] &= (c[l]^d[l]);
+ f[l] &= ~(c[l]^d[l]);
+ g[l] &= c[l];
+ h[l] &= ~c[l];
+ x[l] &= d[l];
+ y[l] &= ~d[l];
+ if (e[l]|f[l]|g[l]|h[l]|x[l]|y[l]) finished=0;
+ }
+ if (finished) break;
+ }
+ if (k>z) z=k;
+ if (k == MAXPAIR) {
+ printf("Some bit didn't change: ");
+ printf("%.8x %.8x %.8x %.8x %.8x %.8x ",
+ e[0],f[0],g[0],h[0],x[0],y[0]);
+ printf("i %d j %d m %d len %d\n", i, j, m, hlen);
+ }
+ if (z == MAXPAIR) goto done;
+ }
+ }
+ }
+ done:
+ if (z < MAXPAIR) {
+ printf("Mix success %2d bytes %2d initvals ",i,m);
+ printf("required %d trials\n", z/2);
+ }
+ }
+ printf("\n");
+}
+
+/* Check for reading beyond the end of the buffer and alignment problems */
+static void driver3(void)
+ /*@*/
+{
+ uint8_t buf[MAXLEN+20], *b;
+ uint32_t len;
+ uint8_t q[] = "This is the time for all good men to come to the aid of their country...";
+ uint32_t h;
+ uint8_t qq[] = "xThis is the time for all good men to come to the aid of their country...";
+ uint32_t i;
+ uint8_t qqq[] = "xxThis is the time for all good men to come to the aid of their country...";
+ uint32_t j;
+ uint8_t qqqq[] = "xxxThis is the time for all good men to come to the aid of their country...";
+ uint32_t ref,x,y;
+ uint8_t *p;
+ uint32_t m = 13;
+
+ printf("Endianness. These lines should all be the same (for values filled in):\n");
+ printf("%.8x %.8x %.8x\n",
+ jlu32w(m, (const uint32_t *)q, (sizeof(q)-1)/4),
+ jlu32w(m, (const uint32_t *)q, (sizeof(q)-5)/4),
+ jlu32w(m, (const uint32_t *)q, (sizeof(q)-9)/4));
+ p = q;
+ printf("%.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x\n",
+ jlu32l(m, p, sizeof(q)-1), jlu32l(m, p, sizeof(q)-2),
+ jlu32l(m, p, sizeof(q)-3), jlu32l(m, p, sizeof(q)-4),
+ jlu32l(m, p, sizeof(q)-5), jlu32l(m, p, sizeof(q)-6),
+ jlu32l(m, p, sizeof(q)-7), jlu32l(m, p, sizeof(q)-8),
+ jlu32l(m, p, sizeof(q)-9), jlu32l(m, p, sizeof(q)-10),
+ jlu32l(m, p, sizeof(q)-11), jlu32l(m, p, sizeof(q)-12));
+ p = &qq[1];
+ printf("%.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x\n",
+ jlu32l(m, p, sizeof(q)-1), jlu32l(m, p, sizeof(q)-2),
+ jlu32l(m, p, sizeof(q)-3), jlu32l(m, p, sizeof(q)-4),
+ jlu32l(m, p, sizeof(q)-5), jlu32l(m, p, sizeof(q)-6),
+ jlu32l(m, p, sizeof(q)-7), jlu32l(m, p, sizeof(q)-8),
+ jlu32l(m, p, sizeof(q)-9), jlu32l(m, p, sizeof(q)-10),
+ jlu32l(m, p, sizeof(q)-11), jlu32l(m, p, sizeof(q)-12));
+ p = &qqq[2];
+ printf("%.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x\n",
+ jlu32l(m, p, sizeof(q)-1), jlu32l(m, p, sizeof(q)-2),
+ jlu32l(m, p, sizeof(q)-3), jlu32l(m, p, sizeof(q)-4),
+ jlu32l(m, p, sizeof(q)-5), jlu32l(m, p, sizeof(q)-6),
+ jlu32l(m, p, sizeof(q)-7), jlu32l(m, p, sizeof(q)-8),
+ jlu32l(m, p, sizeof(q)-9), jlu32l(m, p, sizeof(q)-10),
+ jlu32l(m, p, sizeof(q)-11), jlu32l(m, p, sizeof(q)-12));
+ p = &qqqq[3];
+ printf("%.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x %.8x\n",
+ jlu32l(m, p, sizeof(q)-1), jlu32l(m, p, sizeof(q)-2),
+ jlu32l(m, p, sizeof(q)-3), jlu32l(m, p, sizeof(q)-4),
+ jlu32l(m, p, sizeof(q)-5), jlu32l(m, p, sizeof(q)-6),
+ jlu32l(m, p, sizeof(q)-7), jlu32l(m, p, sizeof(q)-8),
+ jlu32l(m, p, sizeof(q)-9), jlu32l(m, p, sizeof(q)-10),
+ jlu32l(m, p, sizeof(q)-11), jlu32l(m, p, sizeof(q)-12));
+ printf("\n");
+ for (h=0, b=buf+1; h<8; ++h, ++b) {
+ for (i=0; i<MAXLEN; ++i) {
+ len = i;
+ for (j=0; j<i; ++j)
+ *(b+j)=0;
+
+ /* these should all be equal */
+ m = 1;
+ ref = jlu32l(m, b, len);
+ *(b+i)=(uint8_t)~0;
+ *(b-1)=(uint8_t)~0;
+ x = jlu32l(m, b, len);
+ y = jlu32l(m, b, len);
+ if ((ref != x) || (ref != y))
+ printf("alignment error: %.8x %.8x %.8x %d %d\n",ref,x,y, h, i);
+ }
+ }
+}
+
+/* check for problems with nulls */
+static void driver4(void)
+ /*@*/
+{
+ uint8_t buf[1];
+ uint32_t h;
+ uint32_t i;
+ uint32_t state[HASHSTATE];
+
+ buf[0] = ~0;
+ for (i=0; i<HASHSTATE; ++i)
+ state[i] = 1;
+ printf("These should all be different\n");
+ h = 0;
+ for (i=0; i<8; ++i) {
+ h = jlu32l(h, buf, 0);
+ printf("%2ld 0-byte strings, hash is %.8x\n", (long)i, h);
+ }
+}
+
+
+int main(int argc, char ** argv)
+{
+ driver1(); /* test that the key is hashed: used for timings */
+ driver2(); /* test that whole key is hashed thoroughly */
+ driver3(); /* test that nothing but the key is hashed */
+ driver4(); /* test hashing multiple buffers (all buffers are null) */
+ return 1;
+}
+
+#endif /* _JLU3_SELFTEST */
diff --git a/third_party/popt/popt.c b/third_party/popt/popt.c
index d9e8411..c6bae95 100644
--- a/third_party/popt/popt.c
+++ b/third_party/popt/popt.c
@@ -10,12 +10,19 @@
#include "system.h"
-#if HAVE_FLOAT_H
+#if defined(__LCLINT__)
+/*@-declundef -exportheader @*/
+extern long long int strtoll(const char *nptr, /*@null@*/ char **endptr,
+ int base)
+ /*@modifies *endptr@*/;
+/*@=declundef =exportheader @*/
+#endif
+
+#ifdef HAVE_FLOAT_H
#include <float.h>
#endif
#include <math.h>
-#include "findme.h"
#include "poptint.h"
#ifdef MYDEBUG
@@ -23,8 +30,14 @@
int _popt_debug = 0;
#endif
-#ifndef HAVE_STRERROR
-static char * strerror(int errno) {
+/*@unchecked@*/
+unsigned int _poptArgMask = POPT_ARG_MASK;
+/*@unchecked@*/
+unsigned int _poptGroupMask = POPT_GROUP_MASK;
+
+#if !defined(HAVE_STRERROR) && !defined(__LCLINT__)
+static char * strerror(int errno)
+{
extern int sys_nerr;
extern char * sys_errlist[];
@@ -36,7 +49,8 @@ static char * strerror(int errno) {
#endif
#ifdef MYDEBUG
-/*@unused@*/ static void prtcon(const char *msg, poptContext con)
+/*@unused@*/
+static void prtcon(const char *msg, poptContext con)
{
if (msg) fprintf(stderr, "%s", msg);
fprintf(stderr, "\tcon %p os %p nextCharArg \"%s\" nextArg \"%s\" argv[%d] \"%s\"\n",
@@ -51,12 +65,10 @@ static char * strerror(int errno) {
void poptSetExecPath(poptContext con, const char * path, int allowAbsolute)
{
- con->execPath = (const char *)_free(con->execPath);
+ con->execPath = _free(con->execPath);
con->execPath = xstrdup(path);
con->execAbsolute = allowAbsolute;
- /*@-nullstate@*/ /* LCL: con->execPath can be NULL? */
return;
- /*@=nullstate@*/
}
static void invokeCallbacksPRE(poptContext con, const struct poptOption * opt)
@@ -65,19 +77,20 @@ static void invokeCallbacksPRE(poptContext con, const struct poptOption * opt)
{
if (opt != NULL)
for (; opt->longName || opt->shortName || opt->arg; opt++) {
- if (opt->arg == NULL) continue; /* XXX program error. */
- if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_INCLUDE_TABLE) {
- /* Recurse on included sub-tables. */
- invokeCallbacksPRE(con, (const struct poptOption *)opt->arg);
- } else if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_CALLBACK &&
- (opt->argInfo & POPT_CBFLAG_PRE))
- { /*@-castfcnptr@*/
- poptCallbackType cb = (poptCallbackType)opt->arg;
- /*@=castfcnptr@*/
- /* Perform callback. */
- /*@-moduncon -noeffectuncon @*/
- cb(con, POPT_CALLBACK_REASON_PRE, NULL, NULL, opt->descrip);
- /*@=moduncon =noeffectuncon @*/
+ poptArg arg = { .ptr = opt->arg };
+ if (arg.ptr)
+ switch (poptArgType(opt)) {
+ case POPT_ARG_INCLUDE_TABLE: /* Recurse on included sub-tables. */
+ poptSubstituteHelpI18N(arg.opt); /* XXX side effects */
+ invokeCallbacksPRE(con, arg.opt);
+ /*@switchbreak@*/ break;
+ case POPT_ARG_CALLBACK: /* Perform callback. */
+ if (!CBF_ISSET(opt, PRE))
+ /*@switchbreak@*/ break;
+/*@-noeffectuncon @*/ /* XXX no known way to annotate (*vector) calls. */
+ arg.cb(con, POPT_CALLBACK_REASON_PRE, NULL, NULL, opt->descrip);
+/*@=noeffectuncon @*/
+ /*@switchbreak@*/ break;
}
}
}
@@ -88,114 +101,109 @@ static void invokeCallbacksPOST(poptContext con, const struct poptOption * opt)
{
if (opt != NULL)
for (; opt->longName || opt->shortName || opt->arg; opt++) {
- if (opt->arg == NULL) continue; /* XXX program error. */
- if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_INCLUDE_TABLE) {
- /* Recurse on included sub-tables. */
- invokeCallbacksPOST(con, (const struct poptOption *)opt->arg);
- } else if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_CALLBACK &&
- (opt->argInfo & POPT_CBFLAG_POST))
- { /*@-castfcnptr@*/
- poptCallbackType cb = (poptCallbackType)opt->arg;
- /*@=castfcnptr@*/
- /* Perform callback. */
- /*@-moduncon -noeffectuncon @*/
- cb(con, POPT_CALLBACK_REASON_POST, NULL, NULL, opt->descrip);
- /*@=moduncon =noeffectuncon @*/
+ poptArg arg = { .ptr = opt->arg };
+ if (arg.ptr)
+ switch (poptArgType(opt)) {
+ case POPT_ARG_INCLUDE_TABLE: /* Recurse on included sub-tables. */
+ poptSubstituteHelpI18N(arg.opt); /* XXX side effects */
+ invokeCallbacksPOST(con, arg.opt);
+ /*@switchbreak@*/ break;
+ case POPT_ARG_CALLBACK: /* Perform callback. */
+ if (!CBF_ISSET(opt, POST))
+ /*@switchbreak@*/ break;
+/*@-noeffectuncon @*/ /* XXX no known way to annotate (*vector) calls. */
+ arg.cb(con, POPT_CALLBACK_REASON_POST, NULL, NULL, opt->descrip);
+/*@=noeffectuncon @*/
+ /*@switchbreak@*/ break;
}
}
}
static void invokeCallbacksOPTION(poptContext con,
- const struct poptOption * opt,
- const struct poptOption * myOpt,
- /*@null@*/ const void * myData, int shorty)
+ const struct poptOption * opt,
+ const struct poptOption * myOpt,
+ /*@null@*/ const void * myData, int shorty)
/*@globals internalState@*/
/*@modifies internalState@*/
{
const struct poptOption * cbopt = NULL;
+ poptArg cbarg = { .ptr = NULL };
if (opt != NULL)
for (; opt->longName || opt->shortName || opt->arg; opt++) {
- if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_INCLUDE_TABLE) {
- /* Recurse on included sub-tables. */
- if (opt->arg != NULL) /* XXX program error */
- invokeCallbacksOPTION(con, (const struct poptOption *)opt->arg,
- myOpt, myData, shorty);
- } else if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_CALLBACK &&
- !(opt->argInfo & POPT_CBFLAG_SKIPOPTION)) {
- /* Save callback info. */
+ poptArg arg = { .ptr = opt->arg };
+ switch (poptArgType(opt)) {
+ case POPT_ARG_INCLUDE_TABLE: /* Recurse on included sub-tables. */
+ poptSubstituteHelpI18N(arg.opt); /* XXX side effects */
+ if (opt->arg != NULL)
+ invokeCallbacksOPTION(con, opt->arg, myOpt, myData, shorty);
+ /*@switchbreak@*/ break;
+ case POPT_ARG_CALLBACK: /* Save callback info. */
+ if (CBF_ISSET(opt, SKIPOPTION))
+ /*@switchbreak@*/ break;
cbopt = opt;
- } else if (cbopt != NULL &&
- ((myOpt->shortName && opt->shortName && shorty &&
- myOpt->shortName == opt->shortName) ||
- (myOpt->longName && opt->longName &&
- /*@-nullpass@*/ /* LCL: opt->longName != NULL */
+ cbarg.ptr = opt->arg;
+ /*@switchbreak@*/ break;
+ default: /* Perform callback on matching option. */
+ if (cbopt == NULL || cbarg.cb == NULL)
+ /*@switchbreak@*/ break;
+ if ((myOpt->shortName && opt->shortName && shorty &&
+ myOpt->shortName == opt->shortName)
+ || (myOpt->longName != NULL && opt->longName != NULL &&
!strcmp(myOpt->longName, opt->longName)))
- /*@=nullpass@*/
- )
- { /*@-castfcnptr@*/
- poptCallbackType cb = (poptCallbackType)cbopt->arg;
- /*@=castfcnptr@*/
- const void * cbData = (cbopt->descrip ? cbopt->descrip : myData);
- /* Perform callback. */
- if (cb != NULL) { /* XXX program error */
- /*@-moduncon -noeffectuncon @*/
- cb(con, POPT_CALLBACK_REASON_OPTION, myOpt,
- con->os->nextArg, cbData);
- /*@=moduncon =noeffectuncon @*/
+ { const void *cbData = (cbopt->descrip ? cbopt->descrip : myData);
+/*@-noeffectuncon @*/ /* XXX no known way to annotate (*vector) calls. */
+ cbarg.cb(con, POPT_CALLBACK_REASON_OPTION,
+ myOpt, con->os->nextArg, cbData);
+/*@=noeffectuncon @*/
+ /* Terminate (unless explcitly continuing). */
+ if (!CBF_ISSET(cbopt, CONTINUE))
+ return;
}
- /* Terminate (unless explcitly continuing). */
- if (!(cbopt->argInfo & POPT_CBFLAG_CONTINUE))
- return;
+ /*@switchbreak@*/ break;
}
}
}
poptContext poptGetContext(const char * name, int argc, const char ** argv,
- const struct poptOption * options, int flags)
+ const struct poptOption * options, unsigned int flags)
{
- poptContext con = (poptContext)malloc(sizeof(*con));
+ poptContext con = malloc(sizeof(*con));
if (con == NULL) return NULL; /* XXX can't happen */
memset(con, 0, sizeof(*con));
con->os = con->optionStack;
con->os->argc = argc;
- /*@-dependenttrans -assignexpose@*/ /* FIX: W2DO? */
+/*@-dependenttrans -assignexpose@*/ /* FIX: W2DO? */
con->os->argv = argv;
- /*@=dependenttrans =assignexpose@*/
+/*@=dependenttrans =assignexpose@*/
con->os->argb = NULL;
if (!(flags & POPT_CONTEXT_KEEP_FIRST))
- con->os->next = 1; /* skip argv[0] */
+ con->os->next = 1; /* skip argv[0] */
- con->leftovers = (const char **)calloc( (argc + 1),
- sizeof(*con->leftovers) );
- /*@-dependenttrans -assignexpose@*/ /* FIX: W2DO? */
+ con->leftovers = calloc( (size_t)(argc + 1), sizeof(*con->leftovers) );
+/*@-dependenttrans -assignexpose@*/ /* FIX: W2DO? */
con->options = options;
- /*@=dependenttrans =assignexpose@*/
+/*@=dependenttrans =assignexpose@*/
con->aliases = NULL;
con->numAliases = 0;
con->flags = flags;
con->execs = NULL;
con->numExecs = 0;
con->finalArgvAlloced = argc * 2;
- con->finalArgv = (const char **)calloc( con->finalArgvAlloced,
- sizeof(*con->finalArgv) );
+ con->finalArgv = calloc( (size_t)con->finalArgvAlloced, sizeof(*con->finalArgv) );
con->execAbsolute = 1;
con->arg_strip = NULL;
if (getenv("POSIXLY_CORRECT") || getenv("POSIX_ME_HARDER"))
con->flags |= POPT_CONTEXT_POSIXMEHARDER;
- if (name) {
- char * t = (char *)malloc(strlen(name) + 1);
- if (t) con->appName = strcpy(t, name);
- }
+ if (name)
+ con->appName = xstrdup(name);
- /*@-internalglobs@*/
invokeCallbacksPRE(con, con->options);
- /*@=internalglobs@*/
return con;
}
@@ -205,12 +213,11 @@ static void cleanOSE(/*@special@*/ struct optionStackEntry *os)
/*@releases os->nextArg, os->argv, os->argb @*/
/*@modifies os @*/
{
- os->nextArg = (const char *)_free(os->nextArg);
- os->argv = (const char **)_free(os->argv);
- os->argb = (pbm_set *)PBM_FREE(os->argb);
+ os->nextArg = _free(os->nextArg);
+ os->argv = _free(os->argv);
+ os->argb = PBM_FREE(os->argb);
}
-/*@-boundswrite@*/
void poptResetContext(poptContext con)
{
int i;
@@ -219,7 +226,7 @@ void poptResetContext(poptContext con)
while (con->os > con->optionStack) {
cleanOSE(con->os--);
}
- con->os->argb = (pbm_set *)PBM_FREE(con->os->argb);
+ con->os->argb = PBM_FREE(con->os->argb);
con->os->currAlias = NULL;
con->os->nextCharArg = NULL;
con->os->nextArg = NULL;
@@ -232,21 +239,19 @@ void poptResetContext(poptContext con)
if (con->finalArgv != NULL)
for (i = 0; i < con->finalArgvCount; i++) {
- /*@-unqualifiedtrans@*/ /* FIX: typedef double indirection. */
- con->finalArgv[i] = (const char *)_free(con->finalArgv[i]);
- /*@=unqualifiedtrans@*/
+/*@-unqualifiedtrans@*/ /* FIX: typedef double indirection. */
+ con->finalArgv[i] = _free(con->finalArgv[i]);
+/*@=unqualifiedtrans@*/
}
con->finalArgvCount = 0;
- con->arg_strip = ( pbm_set *)PBM_FREE(con->arg_strip);
- /*@-nullstate@*/ /* FIX: con->finalArgv != NULL */
+ con->arg_strip = PBM_FREE(con->arg_strip);
+/*@-nullstate@*/ /* FIX: con->finalArgv != NULL */
return;
- /*@=nullstate@*/
+/*@=nullstate@*/
}
-/*@=boundswrite@*/
/* Only one of longName, shortName should be set, not both. */
-/*@-boundswrite@*/
static int handleExec(/*@special@*/ poptContext con,
/*@null@*/ const char * longName, char shortName)
/*@uses con->execs, con->numExecs, con->flags, con->doExec,
@@ -283,33 +288,72 @@ static int handleExec(/*@special@*/ poptContext con,
time 'round */
if ((con->finalArgvCount + 1) >= (con->finalArgvAlloced)) {
con->finalArgvAlloced += 10;
- con->finalArgv = (const char **)realloc(con->finalArgv,
+ con->finalArgv = realloc(con->finalArgv,
sizeof(*con->finalArgv) * con->finalArgvAlloced);
}
i = con->finalArgvCount++;
if (con->finalArgv != NULL) /* XXX can't happen */
- { char *s = (char *)malloc((longName ? strlen(longName) : 0) + 3);
+ { char *s = malloc((longName ? strlen(longName) : 0) + sizeof("--"));
if (s != NULL) { /* XXX can't happen */
+ con->finalArgv[i] = s;
+ *s++ = '-';
if (longName)
- sprintf(s, "--%s", longName);
+ s = stpcpy( stpcpy(s, "-"), longName);
else
- sprintf(s, "-%c", shortName);
- con->finalArgv[i] = s;
+ *s++ = shortName;
+ *s = '\0';
} else
con->finalArgv[i] = NULL;
}
- /*@-nullstate@*/ /* FIX: con->finalArgv[] == NULL */
return 1;
- /*@=nullstate@*/
}
-/*@=boundswrite@*/
+
+/**
+ * Compare long option for equality, adjusting for POPT_ARGFLAG_TOGGLE.
+ * @param opt option
+ * @param longName arg option
+ * @param longNameLen arg option length
+ * @return does long option match?
+ */
+static int
+longOptionStrcmp(const struct poptOption * opt,
+ /*@null@*/ const char * longName, size_t longNameLen)
+ /*@*/
+{
+ const char * optLongName = opt->longName;
+ int rc;
+
+ if (optLongName == NULL || longName == NULL) /* XXX can't heppen */
+ return 0;
+
+ if (F_ISSET(opt, TOGGLE)) {
+ if (optLongName[0] == 'n' && optLongName[1] == 'o') {
+ optLongName += sizeof("no") - 1;
+ if (optLongName[0] == '-')
+ optLongName++;
+ }
+ if (longName[0] == 'n' && longName[1] == 'o') {
+ longName += sizeof("no") - 1;
+ longNameLen -= sizeof("no") - 1;
+ if (longName[0] == '-') {
+ longName++;
+ longNameLen--;
+ }
+ }
+ }
+ rc = (int)(strlen(optLongName) == longNameLen);
+ if (rc)
+ rc = (int)(strncmp(optLongName, longName, longNameLen) == 0);
+ return rc;
+}
/* Only one of longName, shortName may be set at a time */
static int handleAlias(/*@special@*/ poptContext con,
- /*@null@*/ const char * longName, char shortName,
- /*@exposed@*/ /*@null@*/ const char * nextCharArg)
+ /*@null@*/ const char * longName, size_t longNameLen,
+ char shortName,
+ /*@exposed@*/ /*@null@*/ const char * nextArg)
/*@uses con->aliases, con->numAliases, con->optionStack, con->os,
con->os->currAlias, con->os->currAlias->option.longName @*/
/*@modifies con @*/
@@ -319,9 +363,10 @@ static int handleAlias(/*@special@*/ poptContext con,
int i;
if (item) {
- if (longName && (item->option.longName &&
- !strcmp(longName, item->option.longName)))
+ if (longName && item->option.longName != NULL
+ && longOptionStrcmp(&item->option, longName, longNameLen))
return 0;
+ else
if (shortName && shortName == item->option.shortName)
return 0;
}
@@ -331,10 +376,12 @@ static int handleAlias(/*@special@*/ poptContext con,
for (i = con->numAliases - 1; i >= 0; i--) {
item = con->aliases + i;
- if (longName && !(item->option.longName &&
- !strcmp(longName, item->option.longName)))
- continue;
- else if (shortName != item->option.shortName)
+ if (longName) {
+ if (item->option.longName == NULL)
+ continue;
+ if (!longOptionStrcmp(&item->option, longName, longNameLen))
+ continue;
+ } else if (shortName != item->option.shortName)
continue;
break;
}
@@ -343,10 +390,8 @@ static int handleAlias(/*@special@*/ poptContext con,
if ((con->os - con->optionStack + 1) == POPT_OPTION_DEPTH)
return POPT_ERROR_OPTSTOODEEP;
-/*@-boundsread@*/
- if (nextCharArg && *nextCharArg)
- con->os->nextCharArg = nextCharArg;
-/*@=boundsread@*/
+ if (longName == NULL && nextArg != NULL && *nextArg != '\0')
+ con->os->nextCharArg = nextArg;
con->os++;
con->os->next = 0;
@@ -354,22 +399,89 @@ static int handleAlias(/*@special@*/ poptContext con,
con->os->nextArg = NULL;
con->os->nextCharArg = NULL;
con->os->currAlias = con->aliases + i;
- rc = poptDupArgv(con->os->currAlias->argc, con->os->currAlias->argv,
- &con->os->argc, &con->os->argv);
+ { const char ** av;
+ int ac = con->os->currAlias->argc;
+ /* Append --foo=bar arg to alias argv array (if present). */
+ if (longName && nextArg != NULL && *nextArg != '\0') {
+ av = malloc((ac + 1 + 1) * sizeof(*av));
+ if (av != NULL) { /* XXX won't happen. */
+ for (i = 0; i < ac; i++) {
+ av[i] = con->os->currAlias->argv[i];
+ }
+ av[ac++] = nextArg;
+ av[ac] = NULL;
+ } else /* XXX revert to old popt behavior if malloc fails. */
+ av = con->os->currAlias->argv;
+ } else
+ av = con->os->currAlias->argv;
+ rc = poptDupArgv(ac, av, &con->os->argc, &con->os->argv);
+ if (av != NULL && av != con->os->currAlias->argv)
+ free(av);
+ }
con->os->argb = NULL;
return (rc ? rc : 1);
}
-/*@-bounds -boundswrite @*/
+/**
+ * Return absolute path to executable by searching PATH.
+ * @param argv0 name of executable
+ * @return (malloc'd) absolute path to executable (or NULL)
+ */
+static /*@null@*/
+const char * findProgramPath(/*@null@*/ const char * argv0)
+ /*@*/
+{
+ char *path = NULL, *s = NULL, *se;
+ char *t = NULL;
+
+ if (argv0 == NULL) return NULL; /* XXX can't happen */
+
+ /* If there is a / in argv[0], it has to be an absolute path. */
+ /* XXX Hmmm, why not if (argv0[0] == '/') ... instead? */
+ if (strchr(argv0, '/'))
+ return xstrdup(argv0);
+
+ if ((path = getenv("PATH")) == NULL || (path = xstrdup(path)) == NULL)
+ return NULL;
+
+ /* The return buffer in t is big enough for any path. */
+ if ((t = malloc(strlen(path) + strlen(argv0) + sizeof("/"))) != NULL)
+ for (s = path; s && *s; s = se) {
+
+ /* Snip PATH element into [s,se). */
+ if ((se = strchr(s, ':')))
+ *se++ = '\0';
+
+ /* Append argv0 to PATH element. */
+ (void) stpcpy(stpcpy(stpcpy(t, s), "/"), argv0);
+
+ /* If file is executable, bingo! */
+ if (!access(t, X_OK))
+ break;
+ }
+
+ /* If no executable was found in PATH, return NULL. */
+/*@-compdef@*/
+ if (!(s && *s) && t != NULL)
+ t = _free(t);
+/*@=compdef@*/
+/*@-modobserver -observertrans -usedef @*/
+ path = _free(path);
+/*@=modobserver =observertrans =usedef @*/
+
+ return t;
+}
+
static int execCommand(poptContext con)
/*@globals internalState @*/
/*@modifies internalState @*/
{
poptItem item = con->doExec;
- const char ** argv;
+ poptArgv argv = NULL;
int argc = 0;
int rc;
+ int ec = POPT_ERROR_ERRNO;
if (item == NULL) /*XXX can't happen*/
return POPT_ERROR_NOARG;
@@ -378,19 +490,22 @@ static int execCommand(poptContext con)
(!con->execAbsolute && strchr(item->argv[0], '/')))
return POPT_ERROR_NOARG;
- argv = (const char **)malloc(
- sizeof(*argv) * (6 + item->argc + con->numLeftovers + con->finalArgvCount));
- if (argv == NULL) return POPT_ERROR_MALLOC; /* XXX can't happen */
+ argv = malloc(sizeof(*argv) *
+ (6 + item->argc + con->numLeftovers + con->finalArgvCount));
+ if (argv == NULL) return POPT_ERROR_MALLOC;
+
+ if (!strchr(item->argv[0], '/') && con->execPath != NULL) {
+ char *s = malloc(strlen(con->execPath) + strlen(item->argv[0]) + sizeof("/"));
+ if (s)
+ (void)stpcpy(stpcpy(stpcpy(s, con->execPath), "/"), item->argv[0]);
- if (!strchr(item->argv[0], '/') && con->execPath) {
- char *s = (char *)alloca(
- strlen(con->execPath) + strlen(item->argv[0]) + sizeof("/"));
- sprintf(s, "%s/%s", con->execPath, item->argv[0]);
argv[argc] = s;
- } else {
+ } else
argv[argc] = findProgramPath(item->argv[0]);
+ if (argv[argc++] == NULL) {
+ ec = POPT_ERROR_NOARG;
+ goto exit;
}
- if (argv[argc++] == NULL) return POPT_ERROR_NOARG;
if (item->argc > 1) {
memcpy(argv + argc, item->argv + 1, sizeof(*argv) * (item->argc - 1));
@@ -404,18 +519,17 @@ static int execCommand(poptContext con)
}
if (con->leftovers != NULL && con->numLeftovers > 0) {
-#if 0
- argv[argc++] = "--";
-#endif
memcpy(argv + argc, con->leftovers, sizeof(*argv) * con->numLeftovers);
argc += con->numLeftovers;
}
argv[argc] = NULL;
-#ifdef __hpux
+#if defined(hpux) || defined(__hpux)
+ rc = setresgid(getgid(), getgid(),-1);
+ if (rc) goto exit;
rc = setresuid(getuid(), getuid(),-1);
- if (rc) return POPT_ERROR_ERRNO;
+ if (rc) goto exit;
#else
/*
* XXX " ... on BSD systems setuid() should be preferred over setreuid()"
@@ -423,22 +537,23 @@ static int execCommand(poptContext con)
* XXX from Norbert Warmuth <nwarmuth@privat.circular.de>
*/
#if defined(HAVE_SETUID)
+ rc = setgid(getgid());
+ if (rc) goto exit;
rc = setuid(getuid());
- if (rc) return POPT_ERROR_ERRNO;
+ if (rc) goto exit;
#elif defined (HAVE_SETREUID)
- rc = setreuid(getuid(), getuid()); /*hlauer: not portable to hpux9.01 */
- if (rc) return POPT_ERROR_ERRNO;
+ rc = setregid(getgid(), getgid());
+ if (rc) goto exit;
+ rc = setreuid(getuid(), getuid());
+ if (rc) goto exit;
#else
; /* Can't drop privileges */
#endif
#endif
- if (argv[0] == NULL)
- return POPT_ERROR_NOARG;
-
#ifdef MYDEBUG
if (_popt_debug)
- { const char ** avp;
+ { poptArgv avp;
fprintf(stderr, "==> execvp(%s) argv[%d]:", argv[0], argc);
for (avp = argv; *avp; avp++)
fprintf(stderr, " '%s'", *avp);
@@ -446,56 +561,68 @@ if (_popt_debug)
}
#endif
+/*@-nullstate@*/
rc = execvp(argv[0], (char *const *)argv);
- /* notreached */
- if (rc) {
- return POPT_ERROR_ERRNO;
+/*@=nullstate@*/
+
+exit:
+ if (argv) {
+ if (argv[0])
+ free((void *)argv[0]);
+ free(argv);
}
-
- return 0;
+ return ec;
}
-/*@=bounds =boundswrite @*/
-/*@-boundswrite@*/
-/*@observer@*/ /*@null@*/ static const struct poptOption *
-findOption(const struct poptOption * opt, /*@null@*/ const char * longName,
+/*@observer@*/ /*@null@*/
+static const struct poptOption *
+findOption(const struct poptOption * opt,
+ /*@null@*/ const char * longName, size_t longNameLen,
char shortName,
/*@null@*/ /*@out@*/ poptCallbackType * callback,
/*@null@*/ /*@out@*/ const void ** callbackData,
- int singleDash)
+ unsigned int argInfo)
/*@modifies *callback, *callbackData */
{
const struct poptOption * cb = NULL;
+ poptArg cbarg = { .ptr = NULL };
/* This happens when a single - is given */
- if (singleDash && !shortName && (longName && *longName == '\0'))
+ if (LF_ISSET(ONEDASH) && !shortName && (longName && *longName == '\0'))
shortName = '-';
for (; opt->longName || opt->shortName || opt->arg; opt++) {
+ poptArg arg = { .ptr = opt->arg };
- if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_INCLUDE_TABLE) {
- const struct poptOption * opt2;
+ switch (poptArgType(opt)) {
+ case POPT_ARG_INCLUDE_TABLE: /* Recurse on included sub-tables. */
+ { const struct poptOption * opt2;
- /* Recurse on included sub-tables. */
- if (opt->arg == NULL) continue; /* XXX program error */
- opt2 = findOption((const struct poptOption *)opt->arg, longName,
- shortName, callback,
- callbackData, singleDash);
+ poptSubstituteHelpI18N(arg.opt); /* XXX side effects */
+ if (arg.ptr == NULL) continue; /* XXX program error */
+ opt2 = findOption(arg.opt, longName, longNameLen, shortName, callback,
+ callbackData, argInfo);
if (opt2 == NULL) continue;
/* Sub-table data will be inheirited if no data yet. */
- if (!(callback && *callback)) return opt2;
- if (!(callbackData && *callbackData == NULL)) return opt2;
- /*@-observertrans -dependenttrans @*/
- *callbackData = opt->descrip;
- /*@=observertrans =dependenttrans @*/
+/*@-observertrans -dependenttrans @*/
+ if (callback && *callback
+ && callbackData && *callbackData == NULL)
+ *callbackData = opt->descrip;
+/*@=observertrans =dependenttrans @*/
return opt2;
- } else if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_CALLBACK) {
+ } /*@notreached@*/ /*@switchbreak@*/ break;
+ case POPT_ARG_CALLBACK:
cb = opt;
- } else if (longName && opt->longName &&
- (!singleDash || (opt->argInfo & POPT_ARGFLAG_ONEDASH)) &&
- /*@-nullpass@*/ /* LCL: opt->longName != NULL */
- !strcmp(longName, opt->longName))
- /*@=nullpass@*/
+ cbarg.ptr = opt->arg;
+ continue;
+ /*@notreached@*/ /*@switchbreak@*/ break;
+ default:
+ /*@switchbreak@*/ break;
+ }
+
+ if (longName != NULL && opt->longName != NULL &&
+ (!LF_ISSET(ONEDASH) || F_ISSET(opt, ONEDASH)) &&
+ longOptionStrcmp(opt, longName, longNameLen))
{
break;
} else if (shortName && shortName == opt->shortName) {
@@ -503,28 +630,20 @@ findOption(const struct poptOption * opt, /*@null@*/ const char * longName,
}
}
- if (!opt->longName && !opt->shortName)
+ if (opt->longName == NULL && !opt->shortName)
return NULL;
- /*@-modobserver -mods @*/
- if (callback) *callback = NULL;
- if (callbackData) *callbackData = NULL;
- if (cb) {
- if (callback)
- /*@-castfcnptr@*/
- *callback = (poptCallbackType)cb->arg;
- /*@=castfcnptr@*/
- if (!(cb->argInfo & POPT_CBFLAG_INC_DATA)) {
- if (callbackData)
- /*@-observertrans@*/ /* FIX: typedef double indirection. */
- *callbackData = cb->descrip;
- /*@=observertrans@*/
- }
- }
- /*@=modobserver =mods @*/
+
+/*@-modobserver -mods @*/
+ if (callback)
+ *callback = (cb ? cbarg.cb : NULL);
+ if (callbackData)
+/*@-observertrans -dependenttrans @*/
+ *callbackData = (cb && !CBF_ISSET(cb, INC_DATA) ? cb->descrip : NULL);
+/*@=observertrans =dependenttrans @*/
+/*@=modobserver =mods @*/
return opt;
}
-/*@=boundswrite@*/
static const char * findNextArg(/*@special@*/ poptContext con,
unsigned argx, int delete_arg)
@@ -542,7 +661,7 @@ static const char * findNextArg(/*@special@*/ poptContext con,
if (os->next == os->argc && os == con->optionStack) break;
if (os->argv != NULL)
for (i = os->next; i < os->argc; i++) {
- /*@-sizeoftype@*/
+/*@-sizeoftype@*/
if (os->argb && PBM_ISSET(i, os->argb))
/*@innercontinue@*/ continue;
if (*os->argv[i] == '-')
@@ -551,19 +670,18 @@ static const char * findNextArg(/*@special@*/ poptContext con,
/*@innercontinue@*/ continue;
arg = os->argv[i];
if (delete_arg) {
- if (os->argb == NULL) os->argb = (pbm_set *)PBM_ALLOC(os->argc);
+ if (os->argb == NULL) os->argb = PBM_ALLOC(os->argc);
if (os->argb != NULL) /* XXX can't happen */
- PBM_SET(i, os->argb);
+ PBM_SET(i, os->argb);
}
/*@innerbreak@*/ break;
- /*@=sizeoftype@*/
+/*@=sizeoftype@*/
}
if (os > con->optionStack) os--;
} while (arg == NULL);
return arg;
}
-/*@-boundswrite@*/
static /*@only@*/ /*@null@*/ const char *
expandNextArg(/*@special@*/ poptContext con, const char * s)
/*@uses con->optionStack, con->os,
@@ -571,13 +689,13 @@ expandNextArg(/*@special@*/ poptContext con, const char * s)
/*@modifies con @*/
{
const char * a = NULL;
- size_t alen;
char *t, *te;
size_t tn = strlen(s) + 1;
char c;
- te = t = (char *)malloc(tn);;
+ te = t = malloc(tn);
if (t == NULL) return NULL; /* XXX can't happen */
+ *t = '\0';
while ((c = *s++) != '\0') {
switch (c) {
#if 0 /* XXX can't do this */
@@ -590,17 +708,17 @@ expandNextArg(/*@special@*/ poptContext con, const char * s)
/*@switchbreak@*/ break;
/* XXX Make sure that findNextArg deletes only next arg. */
if (a == NULL) {
- if ((a = findNextArg(con, 1, 1)) == NULL)
+ if ((a = findNextArg(con, 1U, 1)) == NULL)
/*@switchbreak@*/ break;
}
- s += 3;
-
- alen = strlen(a);
- tn += alen;
- *te = '\0';
- t = (char *)realloc(t, tn);
- te = t + strlen(t);
- strncpy(te, a, alen); te += alen;
+ s += sizeof("#:+") - 1;
+
+ tn += strlen(a);
+ { size_t pos = (size_t) (te - t);
+ if ((t = realloc(t, tn)) == NULL) /* XXX can't happen */
+ return NULL;
+ te = stpcpy(t + pos, a);
+ }
continue;
/*@notreached@*/ /*@switchbreak@*/ break;
default:
@@ -608,48 +726,292 @@ expandNextArg(/*@special@*/ poptContext con, const char * s)
}
*te++ = c;
}
- *te = '\0';
- t = (char *)realloc(t, strlen(t) + 1); /* XXX memory leak, hard to plug */
+ *te++ = '\0';
+ /* If the new string is longer than needed, shorten. */
+ if ((t + tn) > te) {
+/*@-usereleased@*/ /* XXX splint can't follow the pointers. */
+ if ((te = realloc(t, (size_t)(te - t))) == NULL)
+ free(t);
+ t = te;
+/*@=usereleased@*/
+ }
return t;
}
-/*@=boundswrite@*/
static void poptStripArg(/*@special@*/ poptContext con, int which)
- /*@uses con->arg_strip, con->optionStack @*/
+ /*@uses con->optionStack @*/
/*@defines con->arg_strip @*/
/*@modifies con @*/
{
- /*@-sizeoftype@*/
+/*@-compdef -sizeoftype -usedef @*/
if (con->arg_strip == NULL)
- con->arg_strip = (pbm_set *)PBM_ALLOC(con->optionStack[0].argc);
+ con->arg_strip = PBM_ALLOC(con->optionStack[0].argc);
if (con->arg_strip != NULL) /* XXX can't happen */
PBM_SET(which, con->arg_strip);
- /*@=sizeoftype@*/
- /*@-compdef@*/ /* LCL: con->arg_strip udefined? */
return;
- /*@=compdef@*/
+/*@=compdef =sizeoftype =usedef @*/
+}
+
+/*@unchecked@*/
+unsigned int _poptBitsN = _POPT_BITS_N;
+/*@unchecked@*/
+unsigned int _poptBitsM = _POPT_BITS_M;
+/*@unchecked@*/
+unsigned int _poptBitsK = _POPT_BITS_K;
+
+/*@-sizeoftype@*/
+static int _poptBitsNew(/*@null@*/ poptBits *bitsp)
+ /*@globals _poptBitsN, _poptBitsM, _poptBitsK @*/
+ /*@modifies *bitsp, _poptBitsN, _poptBitsM, _poptBitsK @*/
+{
+ if (bitsp == NULL)
+ return POPT_ERROR_NULLARG;
+
+ /* XXX handle negated initialization. */
+ if (*bitsp == NULL) {
+ if (_poptBitsN == 0) {
+ _poptBitsN = _POPT_BITS_N;
+ _poptBitsM = _POPT_BITS_M;
+ }
+ if (_poptBitsM == 0U) _poptBitsM = (3 * _poptBitsN) / 2;
+ if (_poptBitsK == 0U || _poptBitsK > 32U) _poptBitsK = _POPT_BITS_K;
+ *bitsp = PBM_ALLOC(_poptBitsM-1);
+ }
+/*@-nullstate@*/
+ return 0;
+/*@=nullstate@*/
+}
+
+int poptBitsAdd(poptBits bits, const char * s)
+{
+ size_t ns = (s ? strlen(s) : 0);
+ uint32_t h0 = 0;
+ uint32_t h1 = 0;
+
+ if (bits == NULL || ns == 0)
+ return POPT_ERROR_NULLARG;
+
+ poptJlu32lpair(s, ns, &h0, &h1);
+
+ for (ns = 0; ns < (size_t)_poptBitsK; ns++) {
+ uint32_t h = h0 + ns * h1;
+ uint32_t ix = (h % _poptBitsM);
+ PBM_SET(ix, bits);
+ }
+ return 0;
}
-int poptSaveLong(long * arg, int argInfo, long aLong)
+int poptBitsChk(poptBits bits, const char * s)
{
+ size_t ns = (s ? strlen(s) : 0);
+ uint32_t h0 = 0;
+ uint32_t h1 = 0;
+ int rc = 1;
+
+ if (bits == NULL || ns == 0)
+ return POPT_ERROR_NULLARG;
+
+ poptJlu32lpair(s, ns, &h0, &h1);
+
+ for (ns = 0; ns < (size_t)_poptBitsK; ns++) {
+ uint32_t h = h0 + ns * h1;
+ uint32_t ix = (h % _poptBitsM);
+ if (PBM_ISSET(ix, bits))
+ continue;
+ rc = 0;
+ break;
+ }
+ return rc;
+}
+
+int poptBitsClr(poptBits bits)
+{
+ static size_t nbw = (__PBM_NBITS/8);
+ size_t nw = (__PBM_IX(_poptBitsM-1) + 1);
+
+ if (bits == NULL)
+ return POPT_ERROR_NULLARG;
+ memset(bits, 0, nw * nbw);
+ return 0;
+}
+
+int poptBitsDel(poptBits bits, const char * s)
+{
+ size_t ns = (s ? strlen(s) : 0);
+ uint32_t h0 = 0;
+ uint32_t h1 = 0;
+
+ if (bits == NULL || ns == 0)
+ return POPT_ERROR_NULLARG;
+
+ poptJlu32lpair(s, ns, &h0, &h1);
+
+ for (ns = 0; ns < (size_t)_poptBitsK; ns++) {
+ uint32_t h = h0 + ns * h1;
+ uint32_t ix = (h % _poptBitsM);
+ PBM_CLR(ix, bits);
+ }
+ return 0;
+}
+
+int poptBitsIntersect(poptBits *ap, const poptBits b)
+{
+ __pbm_bits *abits;
+ __pbm_bits *bbits;
+ __pbm_bits rc = 0;
+ size_t nw = (__PBM_IX(_poptBitsM-1) + 1);
+ size_t i;
+
+ if (ap == NULL || b == NULL || _poptBitsNew(ap))
+ return POPT_ERROR_NULLARG;
+ abits = __PBM_BITS(*ap);
+ bbits = __PBM_BITS(b);
+
+ for (i = 0; i < nw; i++) {
+ abits[i] &= bbits[i];
+ rc |= abits[i];
+ }
+ return (rc ? 1 : 0);
+}
+
+int poptBitsUnion(poptBits *ap, const poptBits b)
+{
+ __pbm_bits *abits;
+ __pbm_bits *bbits;
+ __pbm_bits rc = 0;
+ size_t nw = (__PBM_IX(_poptBitsM-1) + 1);
+ size_t i;
+
+ if (ap == NULL || b == NULL || _poptBitsNew(ap))
+ return POPT_ERROR_NULLARG;
+ abits = __PBM_BITS(*ap);
+ bbits = __PBM_BITS(b);
+
+ for (i = 0; i < nw; i++) {
+ abits[i] |= bbits[i];
+ rc |= abits[i];
+ }
+ return (rc ? 1 : 0);
+}
+
+int poptBitsArgs(poptContext con, poptBits *ap)
+{
+ const char ** av;
+ int rc = 0;
+
+ if (con == NULL || ap == NULL || _poptBitsNew(ap) ||
+ con->leftovers == NULL || con->numLeftovers == con->nextLeftover)
+ return POPT_ERROR_NULLARG;
+
+ /* some apps like [like RPM ;-) ] need this NULL terminated */
+ con->leftovers[con->numLeftovers] = NULL;
+
+ for (av = con->leftovers + con->nextLeftover; *av != NULL; av++) {
+ if ((rc = poptBitsAdd(*ap, *av)) != 0)
+ break;
+ }
+/*@-nullstate@*/
+ return rc;
+/*@=nullstate@*/
+}
+
+int poptSaveBits(poptBits * bitsp,
+ /*@unused@*/ UNUSED(unsigned int argInfo), const char * s)
+{
+ char *tbuf = NULL;
+ char *t, *te;
+ int rc = 0;
+
+ if (bitsp == NULL || s == NULL || *s == '\0' || _poptBitsNew(bitsp))
+ return POPT_ERROR_NULLARG;
+
+ /* Parse comma separated attributes. */
+ te = tbuf = xstrdup(s);
+ while ((t = te) != NULL && *t) {
+ while (*te != '\0' && *te != ',')
+ te++;
+ if (*te != '\0')
+ *te++ = '\0';
+ /* XXX Ignore empty strings. */
+ if (*t == '\0')
+ continue;
+ /* XXX Permit negated attributes. caveat emptor: false negatives. */
+ if (*t == '!') {
+ t++;
+ if ((rc = poptBitsChk(*bitsp, t)) > 0)
+ rc = poptBitsDel(*bitsp, t);
+ } else
+ rc = poptBitsAdd(*bitsp, t);
+ if (rc)
+ break;
+ }
+ tbuf = _free(tbuf);
+ return rc;
+}
+/*@=sizeoftype@*/
+
+int poptSaveString(const char *** argvp,
+ /*@unused@*/ UNUSED(unsigned int argInfo), const char * val)
+{
+ int argc = 0;
+
+ if (argvp == NULL || val == NULL)
+ return POPT_ERROR_NULLARG;
+
+ /* XXX likely needs an upper bound on argc. */
+ if (*argvp != NULL)
+ while ((*argvp)[argc] != NULL)
+ argc++;
+
+/*@-unqualifiedtrans -nullstate@*/ /* XXX no annotation for (*argvp) */
+ if ((*argvp = xrealloc(*argvp, (argc + 1 + 1) * sizeof(**argvp))) != NULL) {
+ (*argvp)[argc++] = xstrdup(val);
+ (*argvp)[argc ] = NULL;
+ }
+ return 0;
+/*@=unqualifiedtrans =nullstate@*/
+}
+
+/*@unchecked@*/
+static unsigned int seed = 0;
+
+int poptSaveLongLong(long long * arg, unsigned int argInfo, long long aLongLong)
+{
+ if (arg == NULL
+#ifdef NOTYET
/* XXX Check alignment, may fail on funky platforms. */
- if (arg == NULL || (((unsigned long)arg) & (sizeof(*arg)-1)))
+ || (((unsigned long long)arg) & (sizeof(*arg)-1))
+#endif
+ )
return POPT_ERROR_NULLARG;
- if (argInfo & POPT_ARGFLAG_NOT)
- aLong = ~aLong;
- switch (argInfo & POPT_ARGFLAG_LOGICALOPS) {
+ if (aLongLong != 0 && LF_ISSET(RANDOM)) {
+#if defined(HAVE_SRANDOM)
+ if (!seed) {
+ srandom((unsigned)getpid());
+ srandom((unsigned)random());
+ }
+ aLongLong = (long long)(random() % (aLongLong > 0 ? aLongLong : -aLongLong));
+ aLongLong++;
+#else
+ /* XXX avoid adding POPT_ERROR_UNIMPLEMENTED to minimize i18n churn. */
+ return POPT_ERROR_BADOPERATION;
+#endif
+ }
+ if (LF_ISSET(NOT))
+ aLongLong = ~aLongLong;
+ switch (LF_ISSET(LOGICALOPS)) {
case 0:
- *arg = aLong;
+ *arg = aLongLong;
break;
case POPT_ARGFLAG_OR:
- *arg |= aLong;
+ *(unsigned long long *)arg |= (unsigned long long)aLongLong;
break;
case POPT_ARGFLAG_AND:
- *arg &= aLong;
+ *(unsigned long long *)arg &= (unsigned long long)aLongLong;
break;
case POPT_ARGFLAG_XOR:
- *arg ^= aLong;
+ *(unsigned long long *)arg ^= (unsigned long long)aLongLong;
break;
default:
return POPT_ERROR_BADOPERATION;
@@ -658,35 +1020,281 @@ int poptSaveLong(long * arg, int argInfo, long aLong)
return 0;
}
-int poptSaveInt(/*@null@*/ int * arg, int argInfo, long aLong)
+int poptSaveLong(long * arg, unsigned int argInfo, long aLong)
{
/* XXX Check alignment, may fail on funky platforms. */
if (arg == NULL || (((unsigned long)arg) & (sizeof(*arg)-1)))
return POPT_ERROR_NULLARG;
- if (argInfo & POPT_ARGFLAG_NOT)
+ if (aLong != 0 && LF_ISSET(RANDOM)) {
+#if defined(HAVE_SRANDOM)
+ if (!seed) {
+ srandom((unsigned)getpid());
+ srandom((unsigned)random());
+ }
+ aLong = random() % (aLong > 0 ? aLong : -aLong);
+ aLong++;
+#else
+ /* XXX avoid adding POPT_ERROR_UNIMPLEMENTED to minimize i18n churn. */
+ return POPT_ERROR_BADOPERATION;
+#endif
+ }
+ if (LF_ISSET(NOT))
aLong = ~aLong;
- switch (argInfo & POPT_ARGFLAG_LOGICALOPS) {
- case 0:
- *arg = aLong;
+ switch (LF_ISSET(LOGICALOPS)) {
+ case 0: *arg = aLong; break;
+ case POPT_ARGFLAG_OR: *(unsigned long *)arg |= (unsigned long)aLong; break;
+ case POPT_ARGFLAG_AND: *(unsigned long *)arg &= (unsigned long)aLong; break;
+ case POPT_ARGFLAG_XOR: *(unsigned long *)arg ^= (unsigned long)aLong; break;
+ default:
+ return POPT_ERROR_BADOPERATION;
+ /*@notreached@*/ break;
+ }
+ return 0;
+}
+
+int poptSaveInt(/*@null@*/ int * arg, unsigned int argInfo, long aLong)
+{
+ /* XXX Check alignment, may fail on funky platforms. */
+ if (arg == NULL || (((unsigned long)arg) & (sizeof(*arg)-1)))
+ return POPT_ERROR_NULLARG;
+
+ if (aLong != 0 && LF_ISSET(RANDOM)) {
+#if defined(HAVE_SRANDOM)
+ if (!seed) {
+ srandom((unsigned)getpid());
+ srandom((unsigned)random());
+ }
+ aLong = random() % (aLong > 0 ? aLong : -aLong);
+ aLong++;
+#else
+ /* XXX avoid adding POPT_ERROR_UNIMPLEMENTED to minimize i18n churn. */
+ return POPT_ERROR_BADOPERATION;
+#endif
+ }
+ if (LF_ISSET(NOT))
+ aLong = ~aLong;
+ switch (LF_ISSET(LOGICALOPS)) {
+ case 0: *arg = (int) aLong; break;
+ case POPT_ARGFLAG_OR: *(unsigned int *)arg |= (unsigned int) aLong; break;
+ case POPT_ARGFLAG_AND: *(unsigned int *)arg &= (unsigned int) aLong; break;
+ case POPT_ARGFLAG_XOR: *(unsigned int *)arg ^= (unsigned int) aLong; break;
+ default:
+ return POPT_ERROR_BADOPERATION;
+ /*@notreached@*/ break;
+ }
+ return 0;
+}
+
+int poptSaveShort(/*@null@*/ short * arg, unsigned int argInfo, long aLong)
+{
+ /* XXX Check alignment, may fail on funky platforms. */
+ if (arg == NULL || (((unsigned long)arg) & (sizeof(*arg)-1)))
+ return POPT_ERROR_NULLARG;
+
+ if (aLong != 0 && LF_ISSET(RANDOM)) {
+#if defined(HAVE_SRANDOM)
+ if (!seed) {
+ srandom((unsigned)getpid());
+ srandom((unsigned)random());
+ }
+ aLong = random() % (aLong > 0 ? aLong : -aLong);
+ aLong++;
+#else
+ /* XXX avoid adding POPT_ERROR_UNIMPLEMENTED to minimize i18n churn. */
+ return POPT_ERROR_BADOPERATION;
+#endif
+ }
+ if (LF_ISSET(NOT))
+ aLong = ~aLong;
+ switch (LF_ISSET(LOGICALOPS)) {
+ case 0: *arg = (short) aLong;
break;
- case POPT_ARGFLAG_OR:
- *arg |= aLong;
+ case POPT_ARGFLAG_OR: *(unsigned short *)arg |= (unsigned short) aLong;
break;
- case POPT_ARGFLAG_AND:
- *arg &= aLong;
+ case POPT_ARGFLAG_AND: *(unsigned short *)arg &= (unsigned short) aLong;
break;
- case POPT_ARGFLAG_XOR:
- *arg ^= aLong;
+ case POPT_ARGFLAG_XOR: *(unsigned short *)arg ^= (unsigned short) aLong;
break;
- default:
- return POPT_ERROR_BADOPERATION;
+ default: return POPT_ERROR_BADOPERATION;
/*@notreached@*/ break;
}
return 0;
}
-/*@-boundswrite@*/
+/**
+ * Return argInfo field, handling POPT_ARGFLAG_TOGGLE overrides.
+ * @param con context
+ * @param opt option
+ * @return argInfo
+ */
+static unsigned int poptArgInfo(poptContext con, const struct poptOption * opt)
+ /*@*/
+{
+ unsigned int argInfo = opt->argInfo;
+
+ if (con->os->argv != NULL && con->os->next > 0 && opt->longName != NULL)
+ if (LF_ISSET(TOGGLE)) {
+ const char * longName = con->os->argv[con->os->next-1];
+ while (*longName == '-') longName++;
+ /* XXX almost good enough but consider --[no]nofoo corner cases. */
+ if (longName[0] != opt->longName[0] || longName[1] != opt->longName[1])
+ {
+ if (!LF_ISSET(XOR)) { /* XXX dont toggle with XOR */
+ /* Toggle POPT_BIT_SET <=> POPT_BIT_CLR. */
+ if (LF_ISSET(LOGICALOPS))
+ argInfo ^= (POPT_ARGFLAG_OR|POPT_ARGFLAG_AND);
+ argInfo ^= POPT_ARGFLAG_NOT;
+ }
+ }
+ }
+ return argInfo;
+}
+
+/**
+ * Parse an integer expression.
+ * @retval *llp integer expression value
+ * @param argInfo integer expression type
+ * @param val integer expression string
+ * @return 0 on success, otherwise POPT_* error.
+ */
+static int poptParseInteger(long long * llp,
+ /*@unused@*/ UNUSED(unsigned int argInfo),
+ /*@null@*/ const char * val)
+ /*@modifies *llp @*/
+{
+ if (val) {
+ char *end = NULL;
+ *llp = strtoll(val, &end, 0);
+
+ /* XXX parse scaling suffixes here. */
+
+ if (!(end && *end == '\0'))
+ return POPT_ERROR_BADNUMBER;
+ } else
+ *llp = 0;
+ return 0;
+}
+
+/**
+ * Save the option argument through the (*opt->arg) pointer.
+ * @param con context
+ * @param opt option
+ * @return 0 on success, otherwise POPT_* error.
+ */
+static int poptSaveArg(poptContext con, const struct poptOption * opt)
+ /*@globals fileSystem, internalState @*/
+ /*@modifies con, fileSystem, internalState @*/
+{
+ poptArg arg = { .ptr = opt->arg };
+ int rc = 0; /* assume success */
+
+ switch (poptArgType(opt)) {
+ case POPT_ARG_BITSET:
+ /* XXX memory leak, application is responsible for free. */
+ rc = poptSaveBits(arg.ptr, opt->argInfo, con->os->nextArg);
+ /*@switchbreak@*/ break;
+ case POPT_ARG_ARGV:
+ /* XXX memory leak, application is responsible for free. */
+ rc = poptSaveString(arg.ptr, opt->argInfo, con->os->nextArg);
+ /*@switchbreak@*/ break;
+ case POPT_ARG_STRING:
+ /* XXX memory leak, application is responsible for free. */
+ arg.argv[0] = (con->os->nextArg) ? xstrdup(con->os->nextArg) : NULL;
+ /*@switchbreak@*/ break;
+
+ case POPT_ARG_INT:
+ case POPT_ARG_SHORT:
+ case POPT_ARG_LONG:
+ case POPT_ARG_LONGLONG:
+ { unsigned int argInfo = poptArgInfo(con, opt);
+ long long aNUM = 0;
+
+ if ((rc = poptParseInteger(&aNUM, argInfo, con->os->nextArg)) != 0)
+ break;
+
+ switch (poptArgType(opt)) {
+ case POPT_ARG_LONGLONG:
+/* XXX let's not demand C99 compiler flags for <limits.h> quite yet. */
+#if !defined(LLONG_MAX)
+# define LLONG_MAX 9223372036854775807LL
+# define LLONG_MIN (-LLONG_MAX - 1LL)
+#endif
+ rc = !(aNUM == LLONG_MIN || aNUM == LLONG_MAX)
+ ? poptSaveLongLong(arg.longlongp, argInfo, aNUM)
+ : POPT_ERROR_OVERFLOW;
+ /*@innerbreak@*/ break;
+ case POPT_ARG_LONG:
+ rc = !(aNUM < (long long)LONG_MIN || aNUM > (long long)LONG_MAX)
+ ? poptSaveLong(arg.longp, argInfo, (long)aNUM)
+ : POPT_ERROR_OVERFLOW;
+ /*@innerbreak@*/ break;
+ case POPT_ARG_INT:
+ rc = !(aNUM < (long long)INT_MIN || aNUM > (long long)INT_MAX)
+ ? poptSaveInt(arg.intp, argInfo, (long)aNUM)
+ : POPT_ERROR_OVERFLOW;
+ /*@innerbreak@*/ break;
+ case POPT_ARG_SHORT:
+ rc = !(aNUM < (long long)SHRT_MIN || aNUM > (long long)SHRT_MAX)
+ ? poptSaveShort(arg.shortp, argInfo, (long)aNUM)
+ : POPT_ERROR_OVERFLOW;
+ /*@innerbreak@*/ break;
+ }
+ } /*@switchbreak@*/ break;
+
+ case POPT_ARG_FLOAT:
+ case POPT_ARG_DOUBLE:
+ { char *end = NULL;
+ double aDouble = 0.0;
+
+ if (con->os->nextArg) {
+/*@-mods@*/
+ int saveerrno = errno;
+ errno = 0;
+ aDouble = strtod(con->os->nextArg, &end);
+ if (errno == ERANGE) {
+ rc = POPT_ERROR_OVERFLOW;
+ break;
+ }
+ errno = saveerrno;
+/*@=mods@*/
+ if (*end != '\0') {
+ rc = POPT_ERROR_BADNUMBER;
+ break;
+ }
+ }
+
+ switch (poptArgType(opt)) {
+ case POPT_ARG_DOUBLE:
+ arg.doublep[0] = aDouble;
+ /*@innerbreak@*/ break;
+ case POPT_ARG_FLOAT:
+#if !defined(DBL_EPSILON) && !defined(__LCLINT__)
+#define DBL_EPSILON 2.2204460492503131e-16
+#endif
+#define POPT_ABS(a) ((((a) - 0.0) < DBL_EPSILON) ? -(a) : (a))
+ if ((FLT_MIN - POPT_ABS(aDouble)) > DBL_EPSILON
+ || (POPT_ABS(aDouble) - FLT_MAX) > DBL_EPSILON)
+ rc = POPT_ERROR_OVERFLOW;
+ else
+ arg.floatp[0] = (float) aDouble;
+ /*@innerbreak@*/ break;
+ }
+ } /*@switchbreak@*/ break;
+ case POPT_ARG_MAINCALL:
+/*@-assignexpose -type@*/
+ con->maincall = opt->arg;
+/*@=assignexpose =type@*/
+ /*@switchbreak@*/ break;
+ default:
+ fprintf(stdout, POPT_("option type (%u) not implemented in popt\n"),
+ poptArgType(opt));
+ exit(EXIT_FAILURE);
+ /*@notreached@*/ /*@switchbreak@*/ break;
+ }
+ return rc;
+}
+
/* returns 'val' element, -1 on last item, POPT_ERROR_* on error */
int poptGetNextOpt(poptContext con)
{
@@ -708,24 +1316,31 @@ int poptGetNextOpt(poptContext con)
cleanOSE(con->os--);
}
if (!con->os->nextCharArg && con->os->next == con->os->argc) {
- /*@-internalglobs@*/
invokeCallbacksPOST(con, con->options);
- /*@=internalglobs@*/
+
+ if (con->maincall) {
+ /*@-noeffectuncon @*/
+ (void) (*con->maincall) (con->finalArgvCount, con->finalArgv);
+ /*@=noeffectuncon @*/
+ return -1;
+ }
+
if (con->doExec) return execCommand(con);
return -1;
}
/* Process next long option */
if (!con->os->nextCharArg) {
- char * localOptString, * optString;
+ const char * optString;
+ size_t optStringLen;
int thisopt;
- /*@-sizeoftype@*/
+/*@-sizeoftype@*/
if (con->os->argb && PBM_ISSET(con->os->next, con->os->argb)) {
con->os->next++;
continue;
}
- /*@=sizeoftype@*/
+/*@=sizeoftype@*/
thisopt = con->os->next;
if (con->os->argv != NULL) /* XXX can't happen */
origOptString = con->os->argv[con->os->next++];
@@ -733,7 +1348,9 @@ int poptGetNextOpt(poptContext con)
if (origOptString == NULL) /* XXX can't happen */
return POPT_ERROR_BADOPT;
- if (con->restLeftover || *origOptString != '-') {
+ if (con->restLeftover || *origOptString != '-' ||
+ (*origOptString == '-' && origOptString[1] == '\0'))
+ {
if (con->flags & POPT_CONTEXT_POSIXMEHARDER)
con->restLeftover = 1;
if (con->flags & POPT_CONTEXT_ARG_OPTS) {
@@ -746,9 +1363,7 @@ int poptGetNextOpt(poptContext con)
}
/* Make a copy we can hack at */
- localOptString = optString =
- strcpy((char *)alloca(strlen(origOptString) + 1),
- origOptString);
+ optString = origOptString;
if (optString[0] == '\0')
return POPT_ERROR_BADOPT;
@@ -757,42 +1372,42 @@ int poptGetNextOpt(poptContext con)
con->restLeftover = 1;
continue;
} else {
- char *oe;
- int singleDash;
+ const char *oe;
+ unsigned int argInfo = 0;
optString++;
if (*optString == '-')
- singleDash = 0, optString++;
+ optString++;
else
- singleDash = 1;
+ argInfo |= POPT_ARGFLAG_ONEDASH;
+
+ /* Check for "--long=arg" option. */
+ for (oe = optString; *oe && *oe != '='; oe++)
+ {};
+ optStringLen = (size_t)(oe - optString);
+ if (*oe == '=')
+ longArg = oe + 1;
/* XXX aliases with arg substitution need "--alias=arg" */
- if (handleAlias(con, optString, '\0', NULL))
+ if (handleAlias(con, optString, optStringLen, '\0', longArg)) {
+ longArg = NULL;
continue;
+ }
if (handleExec(con, optString, '\0'))
continue;
- /* Check for "--long=arg" option. */
- for (oe = optString; *oe && *oe != '='; oe++)
- {};
- if (*oe == '=') {
- *oe++ = '\0';
- /* XXX longArg is mapped back to persistent storage. */
- longArg = origOptString + (oe - localOptString);
- }
-
- opt = findOption(con->options, optString, '\0', &cb, &cbData,
- singleDash);
- if (!opt && !singleDash)
+ opt = findOption(con->options, optString, optStringLen, '\0', &cb, &cbData,
+ argInfo);
+ if (!opt && !LF_ISSET(ONEDASH))
return POPT_ERROR_BADOPT;
}
if (!opt) {
con->os->nextCharArg = origOptString + 1;
+ longArg = NULL;
} else {
- if (con->os == con->optionStack &&
- opt->argInfo & POPT_ARGFLAG_STRIP)
+ if (con->os == con->optionStack && F_ISSET(opt, STRIP))
{
canstrip = 1;
poptStripArg(con, thisopt);
@@ -802,65 +1417,64 @@ int poptGetNextOpt(poptContext con)
}
/* Process next short option */
- /*@-branchstate@*/ /* FIX: W2DO? */
if (con->os->nextCharArg) {
- origOptString = con->os->nextCharArg;
+ const char * nextCharArg = con->os->nextCharArg;
con->os->nextCharArg = NULL;
- if (handleAlias(con, NULL, *origOptString, origOptString + 1))
+ if (handleAlias(con, NULL, 0, *nextCharArg, nextCharArg + 1))
continue;
- if (handleExec(con, NULL, *origOptString)) {
+ if (handleExec(con, NULL, *nextCharArg)) {
/* Restore rest of short options for further processing */
- origOptString++;
- if (*origOptString != '\0')
- con->os->nextCharArg = origOptString;
+ nextCharArg++;
+ if (*nextCharArg != '\0')
+ con->os->nextCharArg = nextCharArg;
continue;
}
- opt = findOption(con->options, NULL, *origOptString, &cb,
+ opt = findOption(con->options, NULL, 0, *nextCharArg, &cb,
&cbData, 0);
if (!opt)
return POPT_ERROR_BADOPT;
shorty = 1;
- origOptString++;
- if (*origOptString != '\0')
- con->os->nextCharArg = origOptString;
+ nextCharArg++;
+ if (*nextCharArg != '\0')
+ con->os->nextCharArg = nextCharArg + (int)(*nextCharArg == '=');
}
- /*@=branchstate@*/
if (opt == NULL) return POPT_ERROR_BADOPT; /* XXX can't happen */
- if (opt->arg && (opt->argInfo & POPT_ARG_MASK) == POPT_ARG_NONE) {
- if (poptSaveInt((int *)opt->arg, opt->argInfo, 1L))
+ if (opt->arg && poptArgType(opt) == POPT_ARG_NONE) {
+ unsigned int argInfo = poptArgInfo(con, opt);
+ if (poptSaveInt((int *)opt->arg, argInfo, 1L))
return POPT_ERROR_BADOPERATION;
- } else if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_VAL) {
+ } else if (poptArgType(opt) == POPT_ARG_VAL) {
if (opt->arg) {
- if (poptSaveInt((int *)opt->arg, opt->argInfo, (long)opt->val))
+ unsigned int argInfo = poptArgInfo(con, opt);
+ if (poptSaveInt((int *)opt->arg, argInfo, (long)opt->val))
return POPT_ERROR_BADOPERATION;
}
- } else if ((opt->argInfo & POPT_ARG_MASK) != POPT_ARG_NONE) {
- con->os->nextArg = (const char *)_free(con->os->nextArg);
- /*@-usedef@*/ /* FIX: W2DO? */
+ } else if (poptArgType(opt) != POPT_ARG_NONE) {
+ int rc;
+
+ con->os->nextArg = _free(con->os->nextArg);
if (longArg) {
- /*@=usedef@*/
longArg = expandNextArg(con, longArg);
- con->os->nextArg = longArg;
+ con->os->nextArg = (char *) longArg;
} else if (con->os->nextCharArg) {
longArg = expandNextArg(con, con->os->nextCharArg);
- con->os->nextArg = longArg;
+ con->os->nextArg = (char *) longArg;
con->os->nextCharArg = NULL;
} else {
while (con->os->next == con->os->argc &&
- con->os > con->optionStack) {
+ con->os > con->optionStack)
+ {
cleanOSE(con->os--);
}
if (con->os->next == con->os->argc) {
- if (!(opt->argInfo & POPT_ARGFLAG_OPTIONAL))
- /*@-compdef@*/ /* FIX: con->os->argv not defined */
+ if (!F_ISSET(opt, OPTIONAL))
return POPT_ERROR_NOARG;
- /*@=compdef@*/
con->os->nextArg = NULL;
} else {
@@ -868,149 +1482,81 @@ int poptGetNextOpt(poptContext con)
* Make sure this isn't part of a short arg or the
* result of an alias expansion.
*/
- if (con->os == con->optionStack &&
- (opt->argInfo & POPT_ARGFLAG_STRIP) &&
- canstrip) {
+ if (con->os == con->optionStack
+ && F_ISSET(opt, STRIP) && canstrip)
+ {
poptStripArg(con, con->os->next);
}
if (con->os->argv != NULL) { /* XXX can't happen */
- /* XXX watchout: subtle side-effects live here. */
- longArg = con->os->argv[con->os->next++];
- longArg = expandNextArg(con, longArg);
- con->os->nextArg = longArg;
+ if (F_ISSET(opt, OPTIONAL) &&
+ con->os->argv[con->os->next][0] == '-') {
+ con->os->nextArg = NULL;
+ } else {
+ /* XXX watchout: subtle side-effects live here. */
+ longArg = con->os->argv[con->os->next++];
+ longArg = expandNextArg(con, longArg);
+ con->os->nextArg = (char *) longArg;
+ }
}
}
}
longArg = NULL;
- if (opt->arg) {
- switch (opt->argInfo & POPT_ARG_MASK) {
- case POPT_ARG_STRING:
- /* XXX memory leak, hard to plug */
- *((const char **) opt->arg) = (con->os->nextArg)
- ? xstrdup(con->os->nextArg) : NULL;
- /*@switchbreak@*/ break;
-
- case POPT_ARG_INT:
- case POPT_ARG_LONG:
- { long aLong = 0;
- char *end;
-
- if (con->os->nextArg) {
- aLong = strtol(con->os->nextArg, &end, 0);
- if (!(end && *end == '\0'))
- return POPT_ERROR_BADNUMBER;
- }
-
- if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_LONG) {
- if (aLong == LONG_MIN || aLong == LONG_MAX)
- return POPT_ERROR_OVERFLOW;
- if (poptSaveLong((long *)opt->arg, opt->argInfo, aLong))
- return POPT_ERROR_BADOPERATION;
- } else {
- if (aLong > INT_MAX || aLong < INT_MIN)
- return POPT_ERROR_OVERFLOW;
- if (poptSaveInt((int *)opt->arg, opt->argInfo, aLong))
- return POPT_ERROR_BADOPERATION;
- }
- } /*@switchbreak@*/ break;
-
- case POPT_ARG_FLOAT:
- case POPT_ARG_DOUBLE:
- { double aDouble = 0.0;
- char *end;
-
- if (con->os->nextArg) {
- /*@-mods@*/
- int saveerrno = errno;
- errno = 0;
- aDouble = strtod(con->os->nextArg, &end);
- if (errno == ERANGE)
- return POPT_ERROR_OVERFLOW;
- errno = saveerrno;
- /*@=mods@*/
- if (*end != '\0')
- return POPT_ERROR_BADNUMBER;
- }
-
- if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_DOUBLE) {
- *((double *) opt->arg) = aDouble;
- } else {
-#ifndef _ABS
-#define _ABS(a) ((((a) - 0.0) < DBL_EPSILON) ? -(a) : (a))
-#endif
- if ((_ABS(aDouble) - FLT_MAX) > DBL_EPSILON)
- return POPT_ERROR_OVERFLOW;
- if ((FLT_MIN - _ABS(aDouble)) > DBL_EPSILON)
- return POPT_ERROR_OVERFLOW;
- *((float *) opt->arg) = aDouble;
- }
- } /*@switchbreak@*/ break;
- default:
- fprintf(stdout,
- POPT_("option type (%d) not implemented in popt\n"),
- (opt->argInfo & POPT_ARG_MASK));
- exit(EXIT_FAILURE);
- /*@notreached@*/ /*@switchbreak@*/ break;
- }
- }
+ /* Save the option argument through a (*opt->arg) pointer. */
+ if (opt->arg != NULL && (rc = poptSaveArg(con, opt)) != 0)
+ return rc;
}
- if (cb) {
- /*@-internalglobs@*/
+ if (cb)
invokeCallbacksOPTION(con, con->options, opt, cbData, shorty);
- /*@=internalglobs@*/
- } else if (opt->val && ((opt->argInfo & POPT_ARG_MASK) != POPT_ARG_VAL))
+ else if (opt->val && (poptArgType(opt) != POPT_ARG_VAL))
done = 1;
if ((con->finalArgvCount + 2) >= (con->finalArgvAlloced)) {
con->finalArgvAlloced += 10;
- con->finalArgv = (const char **)realloc(con->finalArgv,
+ con->finalArgv = realloc(con->finalArgv,
sizeof(*con->finalArgv) * con->finalArgvAlloced);
}
if (con->finalArgv != NULL)
- { char *s = (char *)malloc(
- (opt->longName ? strlen(opt->longName) : 0) + 3);
+ { char *s = malloc((opt->longName ? strlen(opt->longName) : 0) + sizeof("--"));
if (s != NULL) { /* XXX can't happen */
- if (opt->longName)
- sprintf(s, "%s%s",
- ((opt->argInfo & POPT_ARGFLAG_ONEDASH) ? "-" : "--"),
- opt->longName);
- else
- sprintf(s, "-%c", opt->shortName);
con->finalArgv[con->finalArgvCount++] = s;
+ *s++ = '-';
+ if (opt->longName) {
+ if (!F_ISSET(opt, ONEDASH))
+ *s++ = '-';
+ s = stpcpy(s, opt->longName);
+ } else {
+ *s++ = opt->shortName;
+ *s = '\0';
+ }
} else
con->finalArgv[con->finalArgvCount++] = NULL;
}
- if (opt->arg && (opt->argInfo & POPT_ARG_MASK) == POPT_ARG_NONE)
+ if (opt->arg && poptArgType(opt) == POPT_ARG_NONE)
/*@-ifempty@*/ ; /*@=ifempty@*/
- else if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_VAL)
+ else if (poptArgType(opt) == POPT_ARG_VAL)
/*@-ifempty@*/ ; /*@=ifempty@*/
- else if ((opt->argInfo & POPT_ARG_MASK) != POPT_ARG_NONE) {
- if (con->finalArgv != NULL && con->os->nextArg)
+ else if (poptArgType(opt) != POPT_ARG_NONE) {
+ if (con->finalArgv != NULL && con->os->nextArg != NULL)
con->finalArgv[con->finalArgvCount++] =
- /*@-nullpass@*/ /* LCL: con->os->nextArg != NULL */
xstrdup(con->os->nextArg);
- /*@=nullpass@*/
}
}
return (opt ? opt->val : -1); /* XXX can't happen */
}
-/*@=boundswrite@*/
-const char * poptGetOptArg(poptContext con)
+char * poptGetOptArg(poptContext con)
{
- const char * ret = NULL;
- /*@-branchstate@*/
+ char * ret = NULL;
if (con) {
ret = con->os->nextArg;
con->os->nextArg = NULL;
}
- /*@=branchstate@*/
return ret;
}
@@ -1030,7 +1576,6 @@ const char * poptPeekArg(poptContext con)
return ret;
}
-/*@-boundswrite@*/
const char ** poptGetArgs(poptContext con)
{
if (con == NULL ||
@@ -1040,60 +1585,59 @@ const char ** poptGetArgs(poptContext con)
/* some apps like [like RPM ;-) ] need this NULL terminated */
con->leftovers[con->numLeftovers] = NULL;
- /*@-nullret -nullstate @*/ /* FIX: typedef double indirection. */
+/*@-nullret -nullstate @*/ /* FIX: typedef double indirection. */
return (con->leftovers + con->nextLeftover);
- /*@=nullret =nullstate @*/
+/*@=nullret =nullstate @*/
}
-/*@=boundswrite@*/
-poptContext poptFreeContext(poptContext con)
+static /*@null@*/
+poptItem poptFreeItems(/*@only@*/ /*@null@*/ poptItem items, int nitems)
+ /*@modifies items @*/
{
- poptItem item;
- int i;
+ if (items != NULL) {
+ poptItem item = items;
+ while (--nitems >= 0) {
+/*@-modobserver -observertrans -dependenttrans@*/
+ item->option.longName = _free(item->option.longName);
+ item->option.descrip = _free(item->option.descrip);
+ item->option.argDescrip = _free(item->option.argDescrip);
+/*@=modobserver =observertrans =dependenttrans@*/
+ item->argv = _free(item->argv);
+ item++;
+ }
+ items = _free(items);
+ }
+ return NULL;
+}
+poptContext poptFreeContext(poptContext con)
+{
if (con == NULL) return con;
poptResetContext(con);
- con->os->argb = (pbm_set *)_free(con->os->argb);
+ con->os->argb = _free(con->os->argb);
- if (con->aliases != NULL)
- for (i = 0; i < con->numAliases; i++) {
- item = con->aliases + i;
- /*@-modobserver -observertrans -dependenttrans@*/
- item->option.longName = (const char *)_free(item->option.longName);
- item->option.descrip = (const char *)_free(item->option.descrip);
- item->option.argDescrip = (const char *)_free(item->option.argDescrip);
- /*@=modobserver =observertrans =dependenttrans@*/
- item->argv = (const char **)_free(item->argv);
- }
- con->aliases = (poptItem)_free(con->aliases);
+ con->aliases = poptFreeItems(con->aliases, con->numAliases);
+ con->numAliases = 0;
- if (con->execs != NULL)
- for (i = 0; i < con->numExecs; i++) {
- item = con->execs + i;
- /*@-modobserver -observertrans -dependenttrans@*/
- item->option.longName = (const char *)_free(item->option.longName);
- item->option.descrip = (const char *)_free(item->option.descrip);
- item->option.argDescrip = (const char *)_free(item->option.argDescrip);
- /*@=modobserver =observertrans =dependenttrans@*/
- item->argv = (const char **)_free(item->argv);
- }
- con->execs = (poptItem)_free(con->execs);
-
- con->leftovers = (const char **)_free(con->leftovers);
- con->finalArgv = (const char **)_free(con->finalArgv);
- con->appName = (const char *)_free(con->appName);
- con->otherHelp = (const char *)_free(con->otherHelp);
- con->execPath = (const char *)_free(con->execPath);
- con->arg_strip = (pbm_set *)PBM_FREE(con->arg_strip);
+ con->execs = poptFreeItems(con->execs, con->numExecs);
+ con->numExecs = 0;
+
+ con->leftovers = _free(con->leftovers);
+ con->finalArgv = _free(con->finalArgv);
+ con->appName = _free(con->appName);
+ con->otherHelp = _free(con->otherHelp);
+ con->execPath = _free(con->execPath);
+ con->arg_strip = PBM_FREE(con->arg_strip);
- con = (poptContext)_free(con);
+ con = _free(con);
return con;
}
int poptAddAlias(poptContext con, struct poptAlias alias,
- /*@unused@*/ int flags)
+ /*@unused@*/ UNUSED(int flags))
{
- poptItem item = (poptItem)alloca(sizeof(*item));
+ struct poptItem_s item_buf;
+ poptItem item = &item_buf;
memset(item, 0, sizeof(*item));
item->option.longName = alias.longName;
item->option.shortName = alias.shortName;
@@ -1107,8 +1651,6 @@ int poptAddAlias(poptContext con, struct poptAlias alias,
return poptAddItem(con, item, 0);
}
-/*@-boundswrite@*/
-/*@-mustmod@*/ /* LCL: con not modified? */
int poptAddItem(poptContext con, poptItem newItem, int flags)
{
poptItem * items, item;
@@ -1128,7 +1670,7 @@ int poptAddItem(poptContext con, poptItem newItem, int flags)
/*@notreached@*/ break;
}
- *items = (poptItem)realloc((*items), ((*nitems) + 1) * sizeof(**items));
+ *items = realloc((*items), ((*nitems) + 1) * sizeof(**items));
if ((*items) == NULL)
return 1;
@@ -1151,22 +1693,18 @@ int poptAddItem(poptContext con, poptItem newItem, int flags)
return 0;
}
-/*@=mustmod@*/
-/*@=boundswrite@*/
-const char * poptBadOption(poptContext con, int flags)
+const char * poptBadOption(poptContext con, unsigned int flags)
{
struct optionStackEntry * os = NULL;
if (con != NULL)
os = (flags & POPT_BADOPTION_NOALIAS) ? con->optionStack : con->os;
- /*@-nullderef@*/ /* LCL: os->argv != NULL */
- return (os && os->argv ? os->argv[os->next - 1] : NULL);
- /*@=nullderef@*/
+ return (os != NULL && os->argv != NULL ? os->argv[os->next - 1] : NULL);
}
-const char *poptStrerror(const int error)
+const char * poptStrerror(const int error)
{
switch (error) {
case POPT_ERROR_NOARG:
@@ -1187,6 +1725,8 @@ const char *poptStrerror(const int error)
return POPT_("number too large or too small");
case POPT_ERROR_MALLOC:
return POPT_("memory allocation failed");
+ case POPT_ERROR_BADCONFIG:
+ return POPT_("config file failed sanity test");
case POPT_ERROR_ERRNO:
return strerror(errno);
default:
@@ -1222,14 +1762,13 @@ const char * poptGetInvocationName(poptContext con)
return (con->os->argv ? con->os->argv[0] : "");
}
-/*@-boundswrite@*/
int poptStrippedArgv(poptContext con, int argc, char ** argv)
{
int numargs = argc;
int j = 1;
int i;
- /*@-sizeoftype@*/
+/*@-sizeoftype@*/
if (con->arg_strip)
for (i = 1; i < argc; i++) {
if (PBM_ISSET(i, con->arg_strip))
@@ -1242,8 +1781,7 @@ int poptStrippedArgv(poptContext con, int argc, char ** argv)
argv[j] = (j < numargs) ? argv[i] : NULL;
j++;
}
- /*@=sizeoftype@*/
+/*@=sizeoftype@*/
return numargs;
}
-/*@=boundswrite@*/
diff --git a/third_party/popt/popt.h b/third_party/popt/popt.h
index c60ae29..a12d143 100644
--- a/third_party/popt/popt.h
+++ b/third_party/popt/popt.h
@@ -17,41 +17,49 @@
* \name Arg type identifiers
*/
/*@{*/
-#define POPT_ARG_NONE 0 /*!< no arg */
-#define POPT_ARG_STRING 1 /*!< arg will be saved as string */
-#define POPT_ARG_INT 2 /*!< arg will be converted to int */
-#define POPT_ARG_LONG 3 /*!< arg will be converted to long */
-#define POPT_ARG_INCLUDE_TABLE 4 /*!< arg points to table */
-#define POPT_ARG_CALLBACK 5 /*!< table-wide callback... must be
+#define POPT_ARG_NONE 0U /*!< no arg */
+#define POPT_ARG_STRING 1U /*!< arg will be saved as string */
+#define POPT_ARG_INT 2U /*!< arg ==> int */
+#define POPT_ARG_LONG 3U /*!< arg ==> long */
+#define POPT_ARG_INCLUDE_TABLE 4U /*!< arg points to table */
+#define POPT_ARG_CALLBACK 5U /*!< table-wide callback... must be
set first in table; arg points
to callback, descrip points to
callback data to pass */
-#define POPT_ARG_INTL_DOMAIN 6 /*!< set the translation domain
+#define POPT_ARG_INTL_DOMAIN 6U /*!< set the translation domain
for this table and any
included tables; arg points
to the domain string */
-#define POPT_ARG_VAL 7 /*!< arg should take value val */
-#define POPT_ARG_FLOAT 8 /*!< arg will be converted to float */
-#define POPT_ARG_DOUBLE 9 /*!< arg will be converted to double */
+#define POPT_ARG_VAL 7U /*!< arg should take value val */
+#define POPT_ARG_FLOAT 8U /*!< arg ==> float */
+#define POPT_ARG_DOUBLE 9U /*!< arg ==> double */
+#define POPT_ARG_LONGLONG 10U /*!< arg ==> long long */
+
+#define POPT_ARG_MAINCALL 16U+11U /*!< EXPERIMENTAL: return (*arg) (argc, argv) */
+#define POPT_ARG_ARGV 12U /*!< dupe'd arg appended to realloc'd argv array. */
+#define POPT_ARG_SHORT 13U /*!< arg ==> short */
+#define POPT_ARG_BITSET 16U+14U /*!< arg ==> bit set */
+
+#define POPT_ARG_MASK 0x000000FFU
+#define POPT_GROUP_MASK 0x0000FF00U
-#define POPT_ARG_MASK 0x0000FFFF
/*@}*/
/** \ingroup popt
* \name Arg modifiers
*/
/*@{*/
-#define POPT_ARGFLAG_ONEDASH 0x80000000 /*!< allow -longoption */
-#define POPT_ARGFLAG_DOC_HIDDEN 0x40000000 /*!< don't show in help/usage */
-#define POPT_ARGFLAG_STRIP 0x20000000 /*!< strip this arg from argv(only applies to long args) */
-#define POPT_ARGFLAG_OPTIONAL 0x10000000 /*!< arg may be missing */
-
-#define POPT_ARGFLAG_OR 0x08000000 /*!< arg will be or'ed */
-#define POPT_ARGFLAG_NOR 0x09000000 /*!< arg will be nor'ed */
-#define POPT_ARGFLAG_AND 0x04000000 /*!< arg will be and'ed */
-#define POPT_ARGFLAG_NAND 0x05000000 /*!< arg will be nand'ed */
-#define POPT_ARGFLAG_XOR 0x02000000 /*!< arg will be xor'ed */
-#define POPT_ARGFLAG_NOT 0x01000000 /*!< arg will be negated */
+#define POPT_ARGFLAG_ONEDASH 0x80000000U /*!< allow -longoption */
+#define POPT_ARGFLAG_DOC_HIDDEN 0x40000000U /*!< don't show in help/usage */
+#define POPT_ARGFLAG_STRIP 0x20000000U /*!< strip this arg from argv(only applies to long args) */
+#define POPT_ARGFLAG_OPTIONAL 0x10000000U /*!< arg may be missing */
+
+#define POPT_ARGFLAG_OR 0x08000000U /*!< arg will be or'ed */
+#define POPT_ARGFLAG_NOR 0x09000000U /*!< arg will be nor'ed */
+#define POPT_ARGFLAG_AND 0x04000000U /*!< arg will be and'ed */
+#define POPT_ARGFLAG_NAND 0x05000000U /*!< arg will be nand'ed */
+#define POPT_ARGFLAG_XOR 0x02000000U /*!< arg will be xor'ed */
+#define POPT_ARGFLAG_NOT 0x01000000U /*!< arg will be negated */
#define POPT_ARGFLAG_LOGICALOPS \
(POPT_ARGFLAG_OR|POPT_ARGFLAG_AND|POPT_ARGFLAG_XOR)
@@ -60,7 +68,9 @@
#define POPT_BIT_CLR (POPT_ARG_VAL|POPT_ARGFLAG_NAND)
/*!< clear arg bit(s) */
-#define POPT_ARGFLAG_SHOW_DEFAULT 0x00800000 /*!< show default value in --help */
+#define POPT_ARGFLAG_SHOW_DEFAULT 0x00800000U /*!< show default value in --help */
+#define POPT_ARGFLAG_RANDOM 0x00400000U /*!< random value in [1,arg] */
+#define POPT_ARGFLAG_TOGGLE 0x00200000U /*!< permit --[no]opt prefix toggle */
/*@}*/
@@ -68,12 +78,12 @@
* \name Callback modifiers
*/
/*@{*/
-#define POPT_CBFLAG_PRE 0x80000000 /*!< call the callback before parse */
-#define POPT_CBFLAG_POST 0x40000000 /*!< call the callback after parse */
-#define POPT_CBFLAG_INC_DATA 0x20000000 /*!< use data from the include line,
+#define POPT_CBFLAG_PRE 0x80000000U /*!< call the callback before parse */
+#define POPT_CBFLAG_POST 0x40000000U /*!< call the callback after parse */
+#define POPT_CBFLAG_INC_DATA 0x20000000U /*!< use data from the include line,
not the subtable */
-#define POPT_CBFLAG_SKIPOPTION 0x10000000 /*!< don't callback with option */
-#define POPT_CBFLAG_CONTINUE 0x08000000 /*!< continue callbacks with option */
+#define POPT_CBFLAG_SKIPOPTION 0x10000000U /*!< don't callback with option */
+#define POPT_CBFLAG_CONTINUE 0x08000000U /*!< continue callbacks with option */
/*@}*/
/** \ingroup popt
@@ -83,30 +93,31 @@
#define POPT_ERROR_NOARG -10 /*!< missing argument */
#define POPT_ERROR_BADOPT -11 /*!< unknown option */
#define POPT_ERROR_OPTSTOODEEP -13 /*!< aliases nested too deeply */
-#define POPT_ERROR_BADQUOTE -15 /*!< error in parameter quoting */
+#define POPT_ERROR_BADQUOTE -15 /*!< error in paramter quoting */
#define POPT_ERROR_ERRNO -16 /*!< errno set, use strerror(errno) */
#define POPT_ERROR_BADNUMBER -17 /*!< invalid numeric value */
#define POPT_ERROR_OVERFLOW -18 /*!< number too large or too small */
#define POPT_ERROR_BADOPERATION -19 /*!< mutually exclusive logical operations requested */
#define POPT_ERROR_NULLARG -20 /*!< opt->arg should not be NULL */
#define POPT_ERROR_MALLOC -21 /*!< memory allocation failed */
+#define POPT_ERROR_BADCONFIG -22 /*!< config file failed sanity test */
/*@}*/
/** \ingroup popt
* \name poptBadOption() flags
*/
/*@{*/
-#define POPT_BADOPTION_NOALIAS (1 << 0) /*!< don't go into an alias */
+#define POPT_BADOPTION_NOALIAS (1U << 0) /*!< don't go into an alias */
/*@}*/
/** \ingroup popt
* \name poptGetContext() flags
*/
/*@{*/
-#define POPT_CONTEXT_NO_EXEC (1 << 0) /*!< ignore exec expansions */
-#define POPT_CONTEXT_KEEP_FIRST (1 << 1) /*!< pay attention to argv[0] */
-#define POPT_CONTEXT_POSIXMEHARDER (1 << 2) /*!< options can't follow args */
-#define POPT_CONTEXT_ARG_OPTS (1 << 4) /*!< return args as options with value 0 */
+#define POPT_CONTEXT_NO_EXEC (1U << 0) /*!< ignore exec expansions */
+#define POPT_CONTEXT_KEEP_FIRST (1U << 1) /*!< pay attention to argv[0] */
+#define POPT_CONTEXT_POSIXMEHARDER (1U << 2) /*!< options can't follow args */
+#define POPT_CONTEXT_ARG_OPTS (1U << 4) /*!< return args as options with value 0 */
/*@}*/
/** \ingroup popt
@@ -114,8 +125,8 @@
struct poptOption {
/*@observer@*/ /*@null@*/
const char * longName; /*!< may be NULL */
- char shortName; /*!< may be '\0' */
- int argInfo;
+ char shortName; /*!< may be NUL */
+ unsigned int argInfo;
/*@shared@*/ /*@null@*/
void * arg; /*!< depends on argInfo */
int val; /*!< 0 means don't return, just update flag */
@@ -131,7 +142,7 @@ struct poptOption {
struct poptAlias {
/*@owned@*/ /*@null@*/
const char * longName; /*!< may be NULL */
- char shortName; /*!< may be '\0' */
+ char shortName; /*!< may be NUL */
int argc;
/*@owned@*/
const char ** argv; /*!< must be free()able */
@@ -171,10 +182,16 @@ extern struct poptOption poptAliasOptions[];
/*@unchecked@*/ /*@observer@*/
extern struct poptOption poptHelpOptions[];
/*@=exportvar@*/
+
+/*@-exportvar@*/
+/*@unchecked@*/ /*@observer@*/
+extern struct poptOption * poptHelpOptionsI18N;
+/*@=exportvar@*/
+
#define POPT_AUTOHELP { NULL, '\0', POPT_ARG_INCLUDE_TABLE, poptHelpOptions, \
0, "Help options:", NULL },
-#define POPT_TABLEEND { NULL, '\0', 0, 0, 0, NULL, NULL }
+#define POPT_TABLEEND { NULL, '\0', 0, NULL, 0, NULL, NULL }
/*@}*/
/** \ingroup popt
@@ -191,6 +208,8 @@ typedef struct poptOption * poptOption;
/*@=exporttype =typeuse@*/
#endif
+/** \ingroup popt
+ */
/*@-exportconst@*/
enum poptCallbackReason {
POPT_CALLBACK_REASON_PRE = 0,
@@ -217,7 +236,17 @@ typedef void (*poptCallbackType) (poptContext con,
/*@null@*/ const struct poptOption * opt,
/*@null@*/ const char * arg,
/*@null@*/ const void * data)
- /*@*/;
+ /*@globals internalState @*/
+ /*@modifies internalState @*/;
+
+/** \ingroup popt
+ * Destroy context.
+ * @param con context
+ * @return NULL always
+ */
+/*@null@*/
+poptContext poptFreeContext( /*@only@*/ /*@null@*/ poptContext con)
+ /*@modifies con @*/;
/** \ingroup popt
* Initialize popt context.
@@ -228,12 +257,39 @@ typedef void (*poptCallbackType) (poptContext con,
* @param flags or'd POPT_CONTEXT_* bits
* @return initialized popt context
*/
-/*@only@*/ /*@null@*/ poptContext poptGetContext(
+/*@only@*/ /*@null@*/
+poptContext poptGetContext(
/*@dependent@*/ /*@keep@*/ const char * name,
int argc, /*@dependent@*/ /*@keep@*/ const char ** argv,
/*@dependent@*/ /*@keep@*/ const struct poptOption * options,
- int flags)
- /*@*/;
+ unsigned int flags)
+ /*@globals internalState @*/
+ /*@modifies internalState @*/;
+
+/** \ingroup popt
+ * Destroy context (alternative implementation).
+ * @param con context
+ * @return NULL always
+ */
+/*@null@*/
+poptContext poptFini( /*@only@*/ /*@null@*/ poptContext con)
+ /*@modifies con @*/;
+
+/** \ingroup popt
+ * Initialize popt context (alternative implementation).
+ * This routine does poptGetContext() and then poptReadConfigFiles().
+ * @param argc no. of arguments
+ * @param argv argument array
+ * @param options address of popt option table
+ * @param configPaths colon separated file path(s) to read.
+ * @return initialized popt context (NULL on error).
+ */
+/*@only@*/ /*@null@*/ /*@unused@*/
+poptContext poptInit(int argc, /*@dependent@*/ /*@keep@*/ const char ** argv,
+ /*@dependent@*/ /*@keep@*/ const struct poptOption * options,
+ /*@null@*/ const char * configPaths)
+ /*@globals fileSystem, internalState @*/
+ /*@modifies fileSystem, internalState @*/;
/** \ingroup popt
* Reinitialize popt context.
@@ -257,7 +313,8 @@ int poptGetNextOpt(/*@null@*/poptContext con)
* @param con context
* @return option argument, NULL if no argument is available
*/
-/*@observer@*/ /*@null@*/ const char * poptGetOptArg(/*@null@*/poptContext con)
+/*@observer@*/ /*@null@*/ /*@unused@*/
+char * poptGetOptArg(/*@null@*/poptContext con)
/*@modifies con @*/;
/** \ingroup popt
@@ -265,7 +322,8 @@ int poptGetNextOpt(/*@null@*/poptContext con)
* @param con context
* @return next argument, NULL if no argument is available
*/
-/*@observer@*/ /*@null@*/ const char * poptGetArg(/*@null@*/poptContext con)
+/*@observer@*/ /*@null@*/ /*@unused@*/
+const char * poptGetArg(/*@null@*/poptContext con)
/*@modifies con @*/;
/** \ingroup popt
@@ -273,7 +331,8 @@ int poptGetNextOpt(/*@null@*/poptContext con)
* @param con context
* @return current argument, NULL if no argument is available
*/
-/*@observer@*/ /*@null@*/ const char * poptPeekArg(/*@null@*/poptContext con)
+/*@observer@*/ /*@null@*/ /*@unused@*/
+const char * poptPeekArg(/*@null@*/poptContext con)
/*@*/;
/** \ingroup popt
@@ -281,7 +340,8 @@ int poptGetNextOpt(/*@null@*/poptContext con)
* @param con context
* @return argument array, NULL terminated
*/
-/*@observer@*/ /*@null@*/ const char ** poptGetArgs(/*@null@*/poptContext con)
+/*@observer@*/ /*@null@*/
+const char ** poptGetArgs(/*@null@*/poptContext con)
/*@modifies con @*/;
/** \ingroup popt
@@ -290,23 +350,17 @@ int poptGetNextOpt(/*@null@*/poptContext con)
* @param flags
* @return offending option
*/
-/*@observer@*/ const char * poptBadOption(/*@null@*/poptContext con, int flags)
+/*@observer@*/
+const char * poptBadOption(/*@null@*/poptContext con, unsigned int flags)
/*@*/;
/** \ingroup popt
- * Destroy context.
- * @param con context
- * @return NULL always
- */
-/*@null@*/ poptContext poptFreeContext( /*@only@*/ /*@null@*/ poptContext con)
- /*@modifies con @*/;
-
-/** \ingroup popt
* Add arguments to context.
* @param con context
* @param argv argument array, NULL terminated
* @return 0 on success, POPT_ERROR_OPTSTOODEEP on failure
*/
+/*@unused@*/
int poptStuffArgs(poptContext con, /*@keep@*/ const char ** argv)
/*@modifies con @*/;
@@ -334,15 +388,52 @@ int poptAddItem(poptContext con, poptItem newItem, int flags)
/*@modifies con @*/;
/** \ingroup popt
+ * Perform sanity checks on a file path.
+ * @param fn file name
+ * @return 0 on OK, 1 on NOTOK.
+ */
+int poptSaneFile(const char * fn)
+ /*@globals errno, internalState @*/
+ /*@modifies errno, internalState @*/;
+
+/**
+ * Read a file into a buffer.
+ * @param fn file name
+ * @retval *bp buffer (malloc'd) (or NULL)
+ * @retval *nbp no. of bytes in buffer (including final NUL) (or NULL)
+ * @param flags 1 to trim escaped newlines
+ * return 0 on success
+ */
+int poptReadFile(const char * fn, /*@null@*/ /*@out@*/ char ** bp,
+ /*@null@*/ /*@out@*/ size_t * nbp, int flags)
+ /*@globals errno, fileSystem, internalState @*/
+ /*@modifies *bp, *nbp, errno, fileSystem, internalState @*/;
+#define POPT_READFILE_TRIMNEWLINES 1
+
+/** \ingroup popt
* Read configuration file.
* @param con context
* @param fn file name to read
* @return 0 on success, POPT_ERROR_ERRNO on failure
*/
int poptReadConfigFile(poptContext con, const char * fn)
- /*@globals fileSystem, internalState @*/
+ /*@globals errno, fileSystem, internalState @*/
/*@modifies con->execs, con->numExecs,
- fileSystem, internalState @*/;
+ errno, fileSystem, internalState @*/;
+
+/** \ingroup popt
+ * Read configuration file(s).
+ * Colon separated files to read, looping over poptReadConfigFile().
+ * Note that an '@' character preceeding a path in the list will
+ * also perform additional sanity checks on the file before reading.
+ * @param con context
+ * @param paths colon separated file name(s) to read
+ * @return 0 on success, POPT_ERROR_BADCONFIG on failure
+ */
+int poptReadConfigFiles(poptContext con, /*@null@*/ const char * paths)
+ /*@globals errno, fileSystem, internalState @*/
+ /*@modifies con->execs, con->numExecs,
+ errno, fileSystem, internalState @*/;
/** \ingroup popt
* Read default configuration from /etc/popt and $HOME/.popt.
@@ -350,6 +441,7 @@ int poptReadConfigFile(poptContext con, const char * fn)
* @param useEnv (unused)
* @return 0 on success, POPT_ERROR_ERRNO on failure
*/
+/*@unused@*/
int poptReadDefaultConfig(poptContext con, /*@unused@*/ int useEnv)
/*@globals fileSystem, internalState @*/
/*@modifies con->execs, con->numExecs,
@@ -443,7 +535,8 @@ int poptConfigFileToString(FILE *fp, /*@out@*/ char ** argstrp, int flags)
* @param error popt error
* @return error string
*/
-/*@observer@*/ const char* poptStrerror(const int error)
+/*@observer@*/
+const char * poptStrerror(const int error)
/*@*/;
/** \ingroup popt
@@ -452,28 +545,29 @@ int poptConfigFileToString(FILE *fp, /*@out@*/ char ** argstrp, int flags)
* @param path single path to search for executables
* @param allowAbsolute absolute paths only?
*/
+/*@unused@*/
void poptSetExecPath(poptContext con, const char * path, int allowAbsolute)
/*@modifies con @*/;
/** \ingroup popt
* Print detailed description of options.
* @param con context
- * @param fp output file handle
+ * @param fp ouput file handle
* @param flags (unused)
*/
void poptPrintHelp(poptContext con, FILE * fp, /*@unused@*/ int flags)
/*@globals fileSystem @*/
- /*@modifies *fp, fileSystem @*/;
+ /*@modifies fp, fileSystem @*/;
/** \ingroup popt
* Print terse description of options.
* @param con context
- * @param fp output file handle
+ * @param fp ouput file handle
* @param flags (unused)
*/
void poptPrintUsage(poptContext con, FILE * fp, /*@unused@*/ int flags)
/*@globals fileSystem @*/
- /*@modifies *fp, fileSystem @*/;
+ /*@modifies fp, fileSystem @*/;
/** \ingroup popt
* Provide text to replace default "[OPTION...]" in help/usage output.
@@ -491,7 +585,8 @@ void poptSetOtherOptionHelp(poptContext con, const char * text)
* @return argv[0]
*/
/*@-fcnuse@*/
-/*@observer@*/ const char * poptGetInvocationName(poptContext con)
+/*@observer@*/
+const char * poptGetInvocationName(poptContext con)
/*@*/;
/*@=fcnuse@*/
@@ -508,6 +603,35 @@ int poptStrippedArgv(poptContext con, int argc, char ** argv)
/*@=fcnuse@*/
/**
+ * Add a string to an argv array.
+ * @retval *argvp argv array
+ * @param argInfo (unused)
+ * @param val string arg to add (using strdup)
+ * @return 0 on success, POPT_ERROR_NULLARG/POPT_ERROR_BADOPERATION
+ */
+/*@unused@*/
+int poptSaveString(/*@null@*/ const char *** argvp, unsigned int argInfo,
+ /*@null@*/const char * val)
+ /*@modifies *argvp @*/;
+
+/**
+ * Save a long long, performing logical operation with value.
+ * @warning Alignment check may be too strict on certain platorms.
+ * @param arg integer pointer, aligned on int boundary.
+ * @param argInfo logical operation (see POPT_ARGFLAG_*)
+ * @param aLongLong value to use
+ * @return 0 on success, POPT_ERROR_NULLARG/POPT_ERROR_BADOPERATION
+ */
+/*@-incondefs@*/
+/*@unused@*/
+int poptSaveLongLong(/*@null@*/ long long * arg, unsigned int argInfo,
+ long long aLongLong)
+ /*@globals internalState @*/
+ /*@modifies *arg, internalState @*/
+ /*@requires maxSet(arg) >= 0 /\ maxRead(arg) == 0 @*/;
+/*@=incondefs@*/
+
+/**
* Save a long, performing logical operation with value.
* @warning Alignment check may be too strict on certain platorms.
* @param arg integer pointer, aligned on int boundary.
@@ -517,8 +641,25 @@ int poptStrippedArgv(poptContext con, int argc, char ** argv)
*/
/*@-incondefs@*/
/*@unused@*/
-int poptSaveLong(/*@null@*/ long * arg, int argInfo, long aLong)
- /*@modifies *arg @*/
+int poptSaveLong(/*@null@*/ long * arg, unsigned int argInfo, long aLong)
+ /*@globals internalState @*/
+ /*@modifies *arg, internalState @*/
+ /*@requires maxSet(arg) >= 0 /\ maxRead(arg) == 0 @*/;
+/*@=incondefs@*/
+
+/**
+ * Save a short integer, performing logical operation with value.
+ * @warning Alignment check may be too strict on certain platorms.
+ * @param arg short pointer, aligned on short boundary.
+ * @param argInfo logical operation (see POPT_ARGFLAG_*)
+ * @param aLong value to use
+ * @return 0 on success, POPT_ERROR_NULLARG/POPT_ERROR_BADOPERATION
+ */
+/*@-incondefs@*/
+/*@unused@*/
+int poptSaveShort(/*@null@*/ short * arg, unsigned int argInfo, long aLong)
+ /*@globals internalState @*/
+ /*@modifies *arg, internalState @*/
/*@requires maxSet(arg) >= 0 /\ maxRead(arg) == 0 @*/;
/*@=incondefs@*/
@@ -532,12 +673,70 @@ int poptSaveLong(/*@null@*/ long * arg, int argInfo, long aLong)
*/
/*@-incondefs@*/
/*@unused@*/
-int poptSaveInt(/*@null@*/ int * arg, int argInfo, long aLong)
- /*@modifies *arg @*/
+int poptSaveInt(/*@null@*/ int * arg, unsigned int argInfo, long aLong)
+ /*@globals internalState @*/
+ /*@modifies *arg, internalState @*/
/*@requires maxSet(arg) >= 0 /\ maxRead(arg) == 0 @*/;
/*@=incondefs@*/
+/* The bit set typedef. */
+/*@-exporttype@*/
+typedef struct poptBits_s {
+ unsigned int bits[1];
+} * poptBits;
+/*@=exporttype@*/
+
+#define _POPT_BITS_N 1024U /* estimated population */
+#define _POPT_BITS_M ((3U * _POPT_BITS_N) / 2U)
+#define _POPT_BITS_K 16U /* no. of linear hash combinations */
+
+/*@-exportlocal -exportvar -globuse @*/
+/*@unchecked@*/
+extern unsigned int _poptBitsN;
+/*@unchecked@*/
+extern unsigned int _poptBitsM;
+/*@unchecked@*/
+extern unsigned int _poptBitsK;
+/*@=exportlocal =exportvar =globuse @*/
+
+/*@-exportlocal@*/
+int poptBitsAdd(/*@null@*/poptBits bits, /*@null@*/const char * s)
+ /*@modifies bits @*/;
+/*@=exportlocal@*/
+int poptBitsChk(/*@null@*/poptBits bits, /*@null@*/const char * s)
+ /*@*/;
+int poptBitsClr(/*@null@*/poptBits bits)
+ /*@modifies bits @*/;
+/*@-exportlocal@*/
+int poptBitsDel(/*@null@*/poptBits bits, /*@null@*/const char * s)
+ /*@modifies bits @*/;
+/*@-fcnuse@*/
+int poptBitsIntersect(/*@null@*/ poptBits * ap, /*@null@*/ const poptBits b)
+ /*@modifies *ap @*/;
+int poptBitsUnion(/*@null@*/ poptBits * ap, /*@null@*/ const poptBits b)
+ /*@modifies *ap @*/;
+int poptBitsArgs(/*@null@*/ poptContext con, /*@null@*/ poptBits * ap)
+ /*@modifies con, *ap @*/;
+/*@=fcnuse@*/
+/*@=exportlocal@*/
+
+/**
+ * Save a string into a bit set (experimental).
+ * @retval *bits bit set (lazily malloc'd if NULL)
+ * @param argInfo logical operation (see POPT_ARGFLAG_*)
+ * @param s string to add to bit set
+ * @return 0 on success, POPT_ERROR_NULLARG/POPT_ERROR_BADOPERATION
+ */
+/*@-incondefs@*/
+/*@unused@*/
+int poptSaveBits(/*@null@*/ poptBits * bitsp, unsigned int argInfo,
+ /*@null@*/ const char * s)
+ /*@globals _poptBitsN, _poptBitsM, _poptBitsK, internalState @*/
+ /*@modifies *bitsp, _poptBitsN, _poptBitsM, _poptBitsK, internalState @*/;
+/*@=incondefs@*/
+
/*@=type@*/
+
#ifdef __cplusplus
}
#endif
diff --git a/third_party/popt/poptconfig.c b/third_party/popt/poptconfig.c
index 837828c..f0a92e0 100644
--- a/third_party/popt/poptconfig.c
+++ b/third_party/popt/poptconfig.c
@@ -8,53 +8,346 @@
#include "system.h"
#include "poptint.h"
+#include <sys/stat.h>
+
+#if defined(HAVE_FNMATCH_H)
+#include <fnmatch.h>
+
+#if defined(__LCLINT__)
+/*@-declundef -exportheader -incondefs -protoparammatch -redecl -type @*/
+extern int fnmatch (const char *__pattern, const char *__name, int __flags)
+ /*@*/;
+/*@=declundef =exportheader =incondefs =protoparammatch =redecl =type @*/
+#endif /* __LCLINT__ */
+#endif
+
+#if defined(HAVE_GLOB_H)
+#include <glob.h>
+
+#if defined(__LCLINT__)
+/*@-declundef -exportheader -incondefs -protoparammatch -redecl -type @*/
+extern int glob (const char *__pattern, int __flags,
+ /*@null@*/ int (*__errfunc) (const char *, int),
+ /*@out@*/ glob_t *__pglob)
+ /*@globals errno, fileSystem @*/
+ /*@modifies *__pglob, errno, fileSystem @*/;
+
+/* XXX only annotation is a white lie */
+extern void globfree (/*@only@*/ glob_t *__pglob)
+ /*@modifies *__pglob @*/;
+
+/* XXX _GNU_SOURCE ifdef and/or retrofit is needed for portability. */
+extern int glob_pattern_p (const char *__pattern, int __quote)
+ /*@*/;
+/*@=declundef =exportheader =incondefs =protoparammatch =redecl =type @*/
+#endif /* __LCLINT__ */
+
+#if !defined(__GLIBC__)
+/* Return nonzero if PATTERN contains any metacharacters.
+ Metacharacters can be quoted with backslashes if QUOTE is nonzero. */
+static int
+glob_pattern_p (const char * pattern, int quote)
+ /*@*/
+{
+ const char * p;
+ int open = 0;
+
+ for (p = pattern; *p != '\0'; ++p)
+ switch (*p) {
+ case '?':
+ case '*':
+ return 1;
+ /*@notreached@*/ /*@switchbreak@*/ break;
+ case '\\':
+ if (quote && p[1] != '\0')
+ ++p;
+ /*@switchbreak@*/ break;
+ case '[':
+ open = 1;
+ /*@switchbreak@*/ break;
+ case ']':
+ if (open)
+ return 1;
+ /*@switchbreak@*/ break;
+ }
+ return 0;
+}
+#endif /* !defined(__GLIBC__) */
+
+/*@unchecked@*/
+static int poptGlobFlags = 0;
+
+static int poptGlob_error(/*@unused@*/ UNUSED(const char * epath),
+ /*@unused@*/ UNUSED(int eerrno))
+ /*@*/
+{
+ return 1;
+}
+#endif /* HAVE_GLOB_H */
+
+/**
+ * Return path(s) from a glob pattern.
+ * @param con context
+ * @param pattern glob pattern
+ * @retval *acp no. of paths
+ * @retval *avp array of paths
+ * @return 0 on success
+ */
+static int poptGlob(/*@unused@*/ UNUSED(poptContext con), const char * pattern,
+ /*@out@*/ int * acp, /*@out@*/ const char *** avp)
+ /*@modifies *acp, *avp @*/
+{
+ const char * pat = pattern;
+ int rc = 0; /* assume success */
+
+ /* XXX skip the attention marker. */
+ if (pat[0] == '@' && pat[1] != '(')
+ pat++;
+
+#if defined(HAVE_GLOB_H)
+ if (glob_pattern_p(pat, 0)) {
+ glob_t _g, *pglob = &_g;
+
+ if (!glob(pat, poptGlobFlags, poptGlob_error, pglob)) {
+ if (acp) {
+ *acp = (int) pglob->gl_pathc;
+ pglob->gl_pathc = 0;
+ }
+ if (avp) {
+/*@-onlytrans@*/
+ *avp = (const char **) pglob->gl_pathv;
+/*@=onlytrans@*/
+ pglob->gl_pathv = NULL;
+ }
+/*@-nullstate@*/
+ globfree(pglob);
+/*@=nullstate@*/
+ } else
+ rc = POPT_ERROR_ERRNO;
+ } else
+#endif /* HAVE_GLOB_H */
+ {
+ if (acp)
+ *acp = 1;
+ if (avp && (*avp = calloc((size_t)(1 + 1), sizeof (**avp))) != NULL)
+ (*avp)[0] = xstrdup(pat);
+ }
+
+ return rc;
+}
+
+/*@access poptContext @*/
+
+int poptSaneFile(const char * fn)
+{
+ struct stat sb;
+ uid_t uid = getuid();
+
+ if (stat(fn, &sb) == -1)
+ return 1;
+ if ((uid_t)sb.st_uid != uid)
+ return 0;
+ if (!S_ISREG(sb.st_mode))
+ return 0;
+/*@-bitwisesigned@*/
+ if (sb.st_mode & (S_IWGRP|S_IWOTH))
+ return 0;
+/*@=bitwisesigned@*/
+ return 1;
+}
+
+int poptReadFile(const char * fn, char ** bp, size_t * nbp, int flags)
+{
+ int fdno;
+ char * b = NULL;
+ off_t nb = 0;
+ char * s, * t, * se;
+ int rc = POPT_ERROR_ERRNO; /* assume failure */
+
+ fdno = open(fn, O_RDONLY);
+ if (fdno < 0)
+ goto exit;
+
+ if ((nb = lseek(fdno, 0, SEEK_END)) == (off_t)-1
+ || lseek(fdno, 0, SEEK_SET) == (off_t)-1
+ || (b = calloc(sizeof(*b), (size_t)nb + 1)) == NULL
+ || read(fdno, (char *)b, (size_t)nb) != (ssize_t)nb)
+ {
+ int oerrno = errno;
+ (void) close(fdno);
+ errno = oerrno;
+ goto exit;
+ }
+ if (close(fdno) == -1)
+ goto exit;
+ if (b == NULL) {
+ rc = POPT_ERROR_MALLOC;
+ goto exit;
+ }
+ rc = 0;
+
+ /* Trim out escaped newlines. */
+/*@-bitwisesigned@*/
+ if (flags & POPT_READFILE_TRIMNEWLINES)
+/*@=bitwisesigned@*/
+ {
+ for (t = b, s = b, se = b + nb; *s && s < se; s++) {
+ switch (*s) {
+ case '\\':
+ if (s[1] == '\n') {
+ s++;
+ continue;
+ }
+ /*@fallthrough@*/
+ default:
+ *t++ = *s;
+ /*@switchbreak@*/ break;
+ }
+ }
+ *t++ = '\0';
+ nb = (off_t)(t - b);
+ }
+
+exit:
+ if (rc != 0) {
+/*@-usedef@*/
+ if (b)
+ free(b);
+/*@=usedef@*/
+ b = NULL;
+ nb = 0;
+ }
+ if (bp)
+ *bp = b;
+/*@-usereleased@*/
+ else if (b)
+ free(b);
+/*@=usereleased@*/
+ if (nbp)
+ *nbp = (size_t)nb;
+/*@-compdef -nullstate @*/ /* XXX cannot annotate char ** correctly */
+ return rc;
+/*@=compdef =nullstate @*/
+}
+
+/**
+ * Check for application match.
+ * @param con context
+ * @param s config application name
+ * return 0 if config application matches
+ */
+static int configAppMatch(poptContext con, const char * s)
+ /*@*/
+{
+ int rc = 1;
+
+ if (con->appName == NULL) /* XXX can't happen. */
+ return rc;
+
+#if defined(HAVE_GLOB_H) && defined(HAVE_FNMATCH_H)
+ if (glob_pattern_p(s, 1)) {
+/*@-bitwisesigned@*/
+ static int flags = FNM_PATHNAME | FNM_PERIOD;
+#ifdef FNM_EXTMATCH
+ flags |= FNM_EXTMATCH;
+#endif
+/*@=bitwisesigned@*/
+ rc = fnmatch(s, con->appName, flags);
+ } else
+#endif
+ rc = strcmp(s, con->appName);
+ return rc;
+}
/*@-compmempass@*/ /* FIX: item->option.longName kept, not dependent. */
-static void configLine(poptContext con, char * line)
- /*@modifies con @*/
+static int poptConfigLine(poptContext con, char * line)
+ /*@globals fileSystem, internalState @*/
+ /*@modifies con, fileSystem, internalState @*/
{
- /*@-type@*/
- int nameLength = strlen(con->appName);
- /*@=type@*/
+ char *b = NULL;
+ size_t nb = 0;
+ char * se = line;
+ const char * appName;
const char * entryType;
const char * opt;
- poptItem item = (poptItem)alloca(sizeof(*item));
+ struct poptItem_s item_buf;
+ poptItem item = &item_buf;
int i, j;
+ int rc = POPT_ERROR_BADCONFIG;
+
+ if (con->appName == NULL)
+ goto exit;
-/*@-boundswrite@*/
memset(item, 0, sizeof(*item));
- /*@-type@*/
- if (strncmp(line, con->appName, nameLength)) return;
- /*@=type@*/
+ appName = se;
+ while (*se != '\0' && !_isspaceptr(se)) se++;
+ if (*se == '\0')
+ goto exit;
+ else
+ *se++ = '\0';
- line += nameLength;
- if (*line == '\0' || !isspace(*line)) return;
+ if (configAppMatch(con, appName)) goto exit;
- while (*line != '\0' && isspace(*line)) line++;
- entryType = line;
- while (*line == '\0' || !isspace(*line)) line++;
- *line++ = '\0';
+ while (*se != '\0' && _isspaceptr(se)) se++;
+ entryType = se;
+ while (*se != '\0' && !_isspaceptr(se)) se++;
+ if (*se != '\0') *se++ = '\0';
- while (*line != '\0' && isspace(*line)) line++;
- if (*line == '\0') return;
- opt = line;
- while (*line == '\0' || !isspace(*line)) line++;
- *line++ = '\0';
+ while (*se != '\0' && _isspaceptr(se)) se++;
+ if (*se == '\0') goto exit;
+ opt = se;
+ while (*se != '\0' && !_isspaceptr(se)) se++;
+ if (opt[0] == '-' && *se == '\0') goto exit;
+ if (*se != '\0') *se++ = '\0';
- while (*line != '\0' && isspace(*line)) line++;
- if (*line == '\0') return;
+ while (*se != '\0' && _isspaceptr(se)) se++;
+ if (opt[0] == '-' && *se == '\0') goto exit;
- /*@-temptrans@*/ /* FIX: line alias is saved */
+/*@-temptrans@*/ /* FIX: line alias is saved */
if (opt[0] == '-' && opt[1] == '-')
item->option.longName = opt + 2;
else if (opt[0] == '-' && opt[2] == '\0')
item->option.shortName = opt[1];
- /*@=temptrans@*/
+ else {
+ const char * fn = opt;
- if (poptParseArgvString(line, &item->argc, &item->argv)) return;
+ /* XXX handle globs and directories in fn? */
+ if ((rc = poptReadFile(fn, &b, &nb, POPT_READFILE_TRIMNEWLINES)) != 0)
+ goto exit;
+ if (b == NULL || nb == 0)
+ goto exit;
- /*@-modobserver@*/
+ /* Append remaining text to the interpolated file option text. */
+ if (*se != '\0') {
+ size_t nse = strlen(se) + 1;
+ if ((b = realloc(b, (nb + nse))) == NULL) /* XXX can't happen */
+ goto exit;
+ (void) stpcpy( stpcpy(&b[nb-1], " "), se);
+ nb += nse;
+ }
+ se = b;
+
+ /* Use the basename of the path as the long option name. */
+ { const char * longName = strrchr(fn, '/');
+ if (longName != NULL)
+ longName++;
+ else
+ longName = fn;
+ if (longName == NULL) /* XXX can't happen. */
+ goto exit;
+ /* Single character basenames are treated as short options. */
+ if (longName[1] != '\0')
+ item->option.longName = longName;
+ else
+ item->option.shortName = longName[0];
+ }
+ }
+/*@=temptrans@*/
+
+ if (poptParseArgvString(se, &item->argc, &item->argv)) goto exit;
+
+/*@-modobserver@*/
item->option.argInfo = POPT_ARGFLAG_DOC_HIDDEN;
for (i = 0, j = 0; i < item->argc; i++, j++) {
const char * f;
@@ -80,111 +373,210 @@ static void configLine(poptContext con, char * line)
item->argv[j] = NULL;
item->argc = j;
}
- /*@=modobserver@*/
-/*@=boundswrite@*/
+/*@=modobserver@*/
- /*@-nullstate@*/ /* FIX: item->argv[] may be NULL */
+/*@-nullstate@*/ /* FIX: item->argv[] may be NULL */
if (!strcmp(entryType, "alias"))
- (void) poptAddItem(con, item, 0);
+ rc = poptAddItem(con, item, 0);
else if (!strcmp(entryType, "exec"))
- (void) poptAddItem(con, item, 1);
- /*@=nullstate@*/
+ rc = poptAddItem(con, item, 1);
+/*@=nullstate@*/
+exit:
+ rc = 0; /* XXX for now, always return success */
+ if (b)
+ free(b);
+ return rc;
}
/*@=compmempass@*/
int poptReadConfigFile(poptContext con, const char * fn)
{
- const char * file, * chptr, * end;
- char * buf;
-/*@dependent@*/ char * dst;
- int fd, rc;
- off_t fileLength;
-
- fd = open(fn, O_RDONLY);
- if (fd < 0)
- return (errno == ENOENT ? 0 : POPT_ERROR_ERRNO);
-
- fileLength = lseek(fd, 0, SEEK_END);
- if (fileLength == -1 || lseek(fd, 0, 0) == -1) {
- rc = errno;
- (void) close(fd);
- /*@-mods@*/
- errno = rc;
- /*@=mods@*/
- return POPT_ERROR_ERRNO;
- }
+ char * b = NULL, *be;
+ size_t nb = 0;
+ const char *se;
+ char *t, *te;
+ int rc;
+ int xx;
- file = (const char *)alloca(fileLength + 1);
- if (read(fd, (char *)file, fileLength) != fileLength) {
- rc = errno;
- (void) close(fd);
- /*@-mods@*/
- errno = rc;
- /*@=mods@*/
- return POPT_ERROR_ERRNO;
- }
- if (close(fd) == -1)
- return POPT_ERROR_ERRNO;
+ if ((rc = poptReadFile(fn, &b, &nb, POPT_READFILE_TRIMNEWLINES)) != 0)
+ return (errno == ENOENT ? 0 : rc);
+ if (b == NULL || nb == 0)
+ return POPT_ERROR_BADCONFIG;
-/*@-boundswrite@*/
- dst = buf = (char *)alloca(fileLength + 1);
+ if ((t = malloc(nb + 1)) == NULL)
+ goto exit;
+ te = t;
- chptr = file;
- end = (file + fileLength);
- /*@-infloops@*/ /* LCL: can't detect chptr++ */
- while (chptr < end) {
- switch (*chptr) {
+ be = (b + nb);
+ for (se = b; se < be; se++) {
+ switch (*se) {
case '\n':
- *dst = '\0';
- dst = buf;
- while (*dst && isspace(*dst)) dst++;
- if (*dst && *dst != '#')
- configLine(con, dst);
- chptr++;
+ *te = '\0';
+ te = t;
+ while (*te && _isspaceptr(te)) te++;
+ if (*te && *te != '#')
+ xx = poptConfigLine(con, te);
/*@switchbreak@*/ break;
+/*@-usedef@*/ /* XXX *se may be uninitialized */
case '\\':
- *dst++ = *chptr++;
- if (chptr < end) {
- if (*chptr == '\n')
- dst--, chptr++;
- /* \ at the end of a line does not insert a \n */
- else
- *dst++ = *chptr++;
+ *te = *se++;
+ /* \ at the end of a line does not insert a \n */
+ if (se < be && *se != '\n') {
+ te++;
+ *te++ = *se;
}
/*@switchbreak@*/ break;
default:
- *dst++ = *chptr++;
+ *te++ = *se;
/*@switchbreak@*/ break;
+/*@=usedef@*/
}
}
- /*@=infloops@*/
-/*@=boundswrite@*/
- return 0;
+ free(t);
+ rc = 0;
+
+exit:
+ if (b)
+ free(b);
+ return rc;
}
-int poptReadDefaultConfig(poptContext con, /*@unused@*/ int useEnv)
+int poptReadConfigFiles(poptContext con, const char * paths)
{
- char * fn, * home;
- int rc;
+ char * buf = (paths ? xstrdup(paths) : NULL);
+ const char * p;
+ char * pe;
+ int rc = 0; /* assume success */
+
+ for (p = buf; p != NULL && *p != '\0'; p = pe) {
+ const char ** av = NULL;
+ int ac = 0;
+ int i;
+ int xx;
+
+ /* locate start of next path element */
+ pe = strchr(p, ':');
+ if (pe != NULL && *pe == ':')
+ *pe++ = '\0';
+ else
+ pe = (char *) (p + strlen(p));
+
+ xx = poptGlob(con, p, &ac, &av);
+
+ /* work-off each resulting file from the path element */
+ for (i = 0; i < ac; i++) {
+ const char * fn = av[i];
+ if (av[i] == NULL) /* XXX can't happen */
+ /*@innercontinue@*/ continue;
+ /* XXX should '@' attention be pushed into poptReadConfigFile? */
+ if (p[0] == '@' && p[1] != '(') {
+ if (fn[0] == '@' && fn[1] != '(')
+ fn++;
+ xx = poptSaneFile(fn);
+ if (!xx && rc == 0)
+ rc = POPT_ERROR_BADCONFIG;
+ /*@innercontinue@*/ continue;
+ }
+ xx = poptReadConfigFile(con, fn);
+ if (xx && rc == 0)
+ rc = xx;
+ free((void *)av[i]);
+ av[i] = NULL;
+ }
+ free(av);
+ av = NULL;
+ }
- /*@-type@*/
- if (!con->appName) return 0;
- /*@=type@*/
+/*@-usedef@*/
+ if (buf)
+ free(buf);
+/*@=usedef@*/
- rc = poptReadConfigFile(con, "/etc/popt");
- if (rc) return rc;
-#if defined(HAVE_GETUID) && defined(HAVE_GETEUID)
- if (getuid() != geteuid()) return 0;
+ return rc;
+}
+
+int poptReadDefaultConfig(poptContext con, /*@unused@*/ UNUSED(int useEnv))
+{
+ static const char _popt_sysconfdir[] = POPT_SYSCONFDIR "/popt";
+ static const char _popt_etc[] = "/etc/popt";
+ char * home;
+ struct stat sb;
+ int rc = 0; /* assume success */
+
+ if (con->appName == NULL) goto exit;
+
+ if (strcmp(_popt_sysconfdir, _popt_etc)) {
+ rc = poptReadConfigFile(con, _popt_sysconfdir);
+ if (rc) goto exit;
+ }
+
+ rc = poptReadConfigFile(con, _popt_etc);
+ if (rc) goto exit;
+
+#if defined(HAVE_GLOB_H)
+ if (!stat("/etc/popt.d", &sb) && S_ISDIR(sb.st_mode)) {
+ const char ** av = NULL;
+ int ac = 0;
+ int i;
+
+ if ((rc = poptGlob(con, "/etc/popt.d/*", &ac, &av)) == 0) {
+ for (i = 0; rc == 0 && i < ac; i++) {
+ const char * fn = av[i];
+ if (fn == NULL || strstr(fn, ".rpmnew") || strstr(fn, ".rpmsave"))
+ continue;
+ if (!stat(fn, &sb)) {
+ if (!S_ISREG(sb.st_mode) && !S_ISLNK(sb.st_mode))
+ continue;
+ }
+ rc = poptReadConfigFile(con, fn);
+ free((void *)av[i]);
+ av[i] = NULL;
+ }
+ free(av);
+ av = NULL;
+ }
+ }
+ if (rc) goto exit;
#endif
if ((home = getenv("HOME"))) {
- fn = (char *)alloca(strlen(home) + 20);
- strcpy(fn, home);
- strcat(fn, "/.popt");
- rc = poptReadConfigFile(con, fn);
- if (rc) return rc;
+ char * fn = malloc(strlen(home) + 20);
+ if (fn != NULL) {
+ (void) stpcpy(stpcpy(fn, home), "/.popt");
+ rc = poptReadConfigFile(con, fn);
+ free(fn);
+ } else
+ rc = POPT_ERROR_ERRNO;
+ if (rc) goto exit;
}
- return 0;
+exit:
+ return rc;
+}
+
+poptContext
+poptFini(poptContext con)
+{
+ return poptFreeContext(con);
+}
+
+poptContext
+poptInit(int argc, const char ** argv,
+ const struct poptOption * options, const char * configPaths)
+{
+ poptContext con = NULL;
+ const char * argv0;
+
+ if (argv == NULL || argv[0] == NULL || options == NULL)
+ return con;
+
+ if ((argv0 = strrchr(argv[0], '/')) != NULL) argv0++;
+ else argv0 = argv[0];
+
+ con = poptGetContext(argv0, argc, (const char **)argv, options, 0);
+ if (con != NULL&& poptReadConfigFiles(con, configPaths))
+ con = poptFini(con);
+
+ return con;
}
diff --git a/third_party/popt/popthelp.c b/third_party/popt/popthelp.c
index 4e2a1a5..0d8548b 100644
--- a/third_party/popt/popthelp.c
+++ b/third_party/popt/popthelp.c
@@ -1,6 +1,5 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/*@-type@*/
/** \ingroup popt
* \file popt/popthelp.c
*/
@@ -10,8 +9,21 @@
ftp://ftp.rpm.org/pub/rpm/dist. */
#include "system.h"
+
+#define POPT_USE_TIOCGWINSZ
+#ifdef POPT_USE_TIOCGWINSZ
+#include <sys/ioctl.h>
+#endif
+
+#define POPT_WCHAR_HACK
+#ifdef POPT_WCHAR_HACK
+#include <wchar.h> /* for mbsrtowcs */
+/*@access mbstate_t @*/
+#endif
#include "poptint.h"
+/*@access poptContext@*/
+
/**
* Display arguments.
* @param con context
@@ -20,10 +32,12 @@
* @param arg (unused)
* @param data (unused)
*/
+/*@exits@*/
static void displayArgs(poptContext con,
- /*@unused@*/ enum poptCallbackReason foo,
+ /*@unused@*/ UNUSED(enum poptCallbackReason foo),
struct poptOption * key,
- /*@unused@*/ const char * arg, /*@unused@*/ void * data)
+ /*@unused@*/ UNUSED(const char * arg),
+ /*@unused@*/ UNUSED(void * data))
/*@globals fileSystem@*/
/*@modifies fileSystem@*/
{
@@ -31,6 +45,10 @@ static void displayArgs(poptContext con,
poptPrintHelp(con, stdout, 0);
else
poptPrintUsage(con, stdout, 0);
+
+#if !defined(__LCLINT__) /* XXX keep both splint & valgrind happy */
+ con = poptFreeContext(con);
+#endif
exit(0);
}
@@ -53,30 +71,97 @@ struct poptOption poptAliasOptions[] = {
/*@-castfcnptr@*/
/*@observer@*/ /*@unchecked@*/
struct poptOption poptHelpOptions[] = {
- { NULL, '\0', POPT_ARG_CALLBACK, (void *)&displayArgs, '\0', NULL, NULL },
- { "help", '?', 0, NULL, '?', N_("Show this help message"), NULL },
- { "usage", '\0', 0, NULL, 'u', N_("Display brief usage message"), NULL },
+ { NULL, '\0', POPT_ARG_CALLBACK, (void *)displayArgs, 0, NULL, NULL },
+ { "help", '?', 0, NULL, (int)'?', N_("Show this help message"), NULL },
+ { "usage", '\0', 0, NULL, (int)'u', N_("Display brief usage message"), NULL },
+ POPT_TABLEEND
+} ;
+
+/*@observer@*/ /*@unchecked@*/
+static struct poptOption poptHelpOptions2[] = {
+/*@-readonlytrans@*/
+ { NULL, '\0', POPT_ARG_INTL_DOMAIN, PACKAGE, 0, NULL, NULL},
+/*@=readonlytrans@*/
+ { NULL, '\0', POPT_ARG_CALLBACK, (void *)displayArgs, 0, NULL, NULL },
+ { "help", '?', 0, NULL, (int)'?', N_("Show this help message"), NULL },
+ { "usage", '\0', 0, NULL, (int)'u', N_("Display brief usage message"), NULL },
#ifdef NOTYET
{ "defaults", '\0', POPT_ARG_NONE, &show_option_defaults, 0,
N_("Display option defaults in message"), NULL },
#endif
+ { "", '\0', 0, NULL, 0, N_("Terminate options"), NULL },
POPT_TABLEEND
} ;
+
+/*@observer@*/ /*@unchecked@*/
+struct poptOption * poptHelpOptionsI18N = poptHelpOptions2;
/*@=castfcnptr@*/
+#define _POPTHELP_MAXLINE ((size_t)79)
+
+typedef struct columns_s {
+ size_t cur;
+ size_t max;
+} * columns_t;
+
/**
- * @param table option(s)
+ * Return no. of columns in output window.
+ * @param fp FILE
+ * @return no. of columns
*/
-/*@observer@*/ /*@null@*/ static const char *
-getTableTranslationDomain(/*@null@*/ const struct poptOption *table)
+static size_t maxColumnWidth(FILE *fp)
+ /*@*/
+{
+ size_t maxcols = _POPTHELP_MAXLINE;
+#if defined(TIOCGWINSZ)
+ struct winsize ws;
+ int fdno = fileno(fp ? fp : stdout);
+
+ memset(&ws, 0, sizeof(ws));
+ if (fdno >= 0 && !ioctl(fdno, (unsigned long)TIOCGWINSZ, &ws)) {
+ size_t ws_col = (size_t)ws.ws_col;
+ if (ws_col > maxcols && ws_col < (size_t)256)
+ maxcols = ws_col - 1;
+ }
+#endif
+ return maxcols;
+}
+
+/**
+ * Determine number of display characters in a string.
+ * @param s string
+ * @return no. of display characters.
+ */
+static inline size_t stringDisplayWidth(const char *s)
/*@*/
{
- const struct poptOption *opt;
+ size_t n = strlen(s);
+#ifdef POPT_WCHAR_HACK
+ mbstate_t t;
- if (table != NULL)
- for (opt = table; opt->longName || opt->shortName || opt->arg; opt++) {
+ memset ((void *)&t, 0, sizeof (t)); /* In initial state. */
+ /* Determine number of display characters. */
+ n = mbsrtowcs (NULL, &s, n, &t);
+#else
+ n = 0;
+ for (; *s; s = POPT_next_char(s))
+ n++;
+#endif
+
+ return n;
+}
+
+/**
+ * @param opt option(s)
+ */
+/*@observer@*/ /*@null@*/ static const char *
+getTableTranslationDomain(/*@null@*/ const struct poptOption *opt)
+ /*@*/
+{
+ if (opt != NULL)
+ for (; opt->longName || opt->shortName || opt->arg; opt++) {
if (opt->argInfo == POPT_ARG_INTL_DOMAIN)
- return (char *)opt->arg;
+ return opt->arg;
}
return NULL;
}
@@ -92,14 +177,26 @@ getArgDescrip(const struct poptOption * opt,
/*@=paramuse@*/
/*@*/
{
- if (!(opt->argInfo & POPT_ARG_MASK)) return NULL;
-
- if (opt == (poptHelpOptions + 1) || opt == (poptHelpOptions + 2))
- if (opt->argDescrip) return POPT_(opt->argDescrip);
-
- if (opt->argDescrip) return D_(translation_domain, opt->argDescrip);
+ if (!poptArgType(opt)) return NULL;
+
+ if (poptArgType(opt) == POPT_ARG_MAINCALL)
+ return opt->argDescrip;
+ if (poptArgType(opt) == POPT_ARG_ARGV)
+ return opt->argDescrip;
+
+ if (opt->argDescrip) {
+ /* Some strings need popt library, not application, i18n domain. */
+ if (opt == (poptHelpOptions + 1)
+ || opt == (poptHelpOptions + 2)
+ || !strcmp(opt->argDescrip, N_("Help options:"))
+ || !strcmp(opt->argDescrip, N_("Options implemented via popt alias/exec:")))
+ return POPT_(opt->argDescrip);
+
+ /* Use the application i18n domain. */
+ return D_(translation_domain, opt->argDescrip);
+ }
- switch (opt->argInfo & POPT_ARG_MASK) {
+ switch (poptArgType(opt)) {
case POPT_ARG_NONE: return POPT_("NONE");
#ifdef DYING
case POPT_ARG_VAL: return POPT_("VAL");
@@ -107,23 +204,27 @@ getArgDescrip(const struct poptOption * opt,
case POPT_ARG_VAL: return NULL;
#endif
case POPT_ARG_INT: return POPT_("INT");
+ case POPT_ARG_SHORT: return POPT_("SHORT");
case POPT_ARG_LONG: return POPT_("LONG");
+ case POPT_ARG_LONGLONG: return POPT_("LONGLONG");
case POPT_ARG_STRING: return POPT_("STRING");
case POPT_ARG_FLOAT: return POPT_("FLOAT");
case POPT_ARG_DOUBLE: return POPT_("DOUBLE");
+ case POPT_ARG_MAINCALL: return NULL;
+ case POPT_ARG_ARGV: return NULL;
default: return POPT_("ARG");
}
}
/**
* Display default value for an option.
- * @param lineLength
+ * @param lineLength display positions remaining
* @param opt option(s)
* @param translation_domain translation domain
* @return
*/
static /*@only@*/ /*@null@*/ char *
-singleOptionDefaultValue(int lineLength,
+singleOptionDefaultValue(size_t lineLength,
const struct poptOption * opt,
/*@-paramuse@*/ /* FIX: i18n macros disabled with lclint */
/*@null@*/ const char * translation_domain)
@@ -131,58 +232,67 @@ singleOptionDefaultValue(int lineLength,
/*@*/
{
const char * defstr = D_(translation_domain, "default");
- char * le = (char *)malloc(4*lineLength + 1);
+ char * le = malloc(4*lineLength + 1);
char * l = le;
if (le == NULL) return NULL; /* XXX can't happen */
-/*@-boundswrite@*/
*le = '\0';
*le++ = '(';
- strcpy(le, defstr); le += strlen(le);
+ le = stpcpy(le, defstr);
*le++ = ':';
*le++ = ' ';
- if (opt->arg) /* XXX programmer error */
- switch (opt->argInfo & POPT_ARG_MASK) {
+ if (opt->arg) { /* XXX programmer error */
+ poptArg arg = { .ptr = opt->arg };
+ switch (poptArgType(opt)) {
case POPT_ARG_VAL:
case POPT_ARG_INT:
- { long aLong = *((int *)opt->arg);
- le += sprintf(le, "%ld", aLong);
- } break;
+ le += sprintf(le, "%d", arg.intp[0]);
+ break;
+ case POPT_ARG_SHORT:
+ le += sprintf(le, "%hd", arg.shortp[0]);
+ break;
case POPT_ARG_LONG:
- { long aLong = *((long *)opt->arg);
- le += sprintf(le, "%ld", aLong);
- } break;
+ le += sprintf(le, "%ld", arg.longp[0]);
+ break;
+ case POPT_ARG_LONGLONG:
+ le += sprintf(le, "%lld", arg.longlongp[0]);
+ break;
case POPT_ARG_FLOAT:
- { double aDouble = *((float *)opt->arg);
+ { double aDouble = (double) arg.floatp[0];
le += sprintf(le, "%g", aDouble);
} break;
case POPT_ARG_DOUBLE:
- { double aDouble = *((double *)opt->arg);
- le += sprintf(le, "%g", aDouble);
- } break;
+ le += sprintf(le, "%g", arg.doublep[0]);
+ break;
+ case POPT_ARG_MAINCALL:
+ le += sprintf(le, "%p", opt->arg);
+ break;
+ case POPT_ARG_ARGV:
+ le += sprintf(le, "%p", opt->arg);
+ break;
case POPT_ARG_STRING:
- { const char * s = *(const char **)opt->arg;
- if (s == NULL) {
- strcpy(le, "null"); le += strlen(le);
- } else {
- size_t slen = 4*lineLength - (le - l) - sizeof("\"...\")");
+ { const char * s = arg.argv[0];
+ if (s == NULL)
+ le = stpcpy(le, "null");
+ else {
+ size_t limit = 4*lineLength - (le - l) - sizeof("\"\")");
+ size_t slen;
*le++ = '"';
- strncpy(le, s, slen); le[slen] = '\0'; le += strlen(le);
- if (slen < strlen(s)) {
- strcpy(le, "..."); le += strlen(le);
- }
+ strncpy(le, s, limit); le[limit] = '\0'; le += (slen = strlen(le));
+ if (slen == limit && s[limit])
+ le[-1] = le[-2] = le[-3] = '.';
*le++ = '"';
}
} break;
case POPT_ARG_NONE:
default:
- l = (char *)_free(l);
+ l = _free(l);
return NULL;
/*@notreached@*/ break;
}
+ }
*le++ = ')';
*le = '\0';
-/*@=boundswrite@*/
return l;
}
@@ -190,85 +300,104 @@ singleOptionDefaultValue(int lineLength,
/**
* Display help text for an option.
* @param fp output file handle
- * @param maxLeftCol
+ * @param columns output display width control
* @param opt option(s)
* @param translation_domain translation domain
*/
-static void singleOptionHelp(FILE * fp, int maxLeftCol,
+static void singleOptionHelp(FILE * fp, columns_t columns,
const struct poptOption * opt,
/*@null@*/ const char * translation_domain)
/*@globals fileSystem @*/
- /*@modifies *fp, fileSystem @*/
+ /*@modifies fp, fileSystem @*/
{
- int indentLength = maxLeftCol + 5;
- int lineLength = 79 - indentLength;
+ size_t maxLeftCol = columns->cur;
+ size_t indentLength = maxLeftCol + 5;
+ size_t lineLength = columns->max - indentLength;
const char * help = D_(translation_domain, opt->descrip);
const char * argDescrip = getArgDescrip(opt, translation_domain);
- int helpLength;
+ /* Display shortName iff printable non-space. */
+ int prtshort = (int)(isprint((int)opt->shortName) && opt->shortName != ' ');
+ size_t helpLength;
char * defs = NULL;
char * left;
- int nb = maxLeftCol + 1;
+ size_t nb = maxLeftCol + 1;
+ int displaypad = 0;
+ int xx;
/* Make sure there's more than enough room in target buffer. */
if (opt->longName) nb += strlen(opt->longName);
+ if (F_ISSET(opt, TOGGLE)) nb += sizeof("[no]") - 1;
if (argDescrip) nb += strlen(argDescrip);
-/*@-boundswrite@*/
- left = (char *)malloc(nb);
+ left = malloc(nb);
if (left == NULL) return; /* XXX can't happen */
left[0] = '\0';
left[maxLeftCol] = '\0';
- if (opt->longName && opt->shortName)
- sprintf(left, "-%c, %s%s", opt->shortName,
- ((opt->argInfo & POPT_ARGFLAG_ONEDASH) ? "-" : "--"),
- opt->longName);
- else if (opt->shortName != '\0')
- sprintf(left, "-%c", opt->shortName);
- else if (opt->longName)
- sprintf(left, "%s%s",
- ((opt->argInfo & POPT_ARGFLAG_ONEDASH) ? "-" : "--"),
- opt->longName);
- if (!*left) goto out;
+#define prtlong (opt->longName != NULL) /* XXX splint needs a clue */
+ if (!(prtshort || prtlong))
+ goto out;
+ if (prtshort && prtlong) {
+ char *dash = F_ISSET(opt, ONEDASH) ? "-" : "--";
+ left[0] = '-';
+ left[1] = opt->shortName;
+ (void) stpcpy(stpcpy(stpcpy(left+2, ", "), dash), opt->longName);
+ } else if (prtshort) {
+ left[0] = '-';
+ left[1] = opt->shortName;
+ left[2] = '\0';
+ } else if (prtlong) {
+ /* XXX --long always padded for alignment with/without "-X, ". */
+ char *dash = poptArgType(opt) == POPT_ARG_MAINCALL ? ""
+ : (F_ISSET(opt, ONEDASH) ? "-" : "--");
+ const char *longName = opt->longName;
+ const char *toggle;
+ if (F_ISSET(opt, TOGGLE)) {
+ toggle = "[no]";
+ if (longName[0] == 'n' && longName[1] == 'o') {
+ longName += sizeof("no") - 1;
+ if (longName[0] == '-')
+ longName++;
+ }
+ } else
+ toggle = "";
+ (void) stpcpy(stpcpy(stpcpy(stpcpy(left, " "), dash), toggle), longName);
+ }
+#undef prtlong
if (argDescrip) {
char * le = left + strlen(left);
- if (opt->argInfo & POPT_ARGFLAG_OPTIONAL)
+ if (F_ISSET(opt, OPTIONAL))
*le++ = '[';
/* Choose type of output */
- /*@-branchstate@*/
- if (opt->argInfo & POPT_ARGFLAG_SHOW_DEFAULT) {
- defs = singleOptionDefaultValue(lineLength, opt, translation_domain);
- if (defs) {
- char * t = (char *)malloc((help ? strlen(help) : 0) +
+ if (F_ISSET(opt, SHOW_DEFAULT)) {
+ defs = singleOptionDefaultValue(lineLength, opt, translation_domain);
+ if (defs) {
+ char * t = malloc((help ? strlen(help) : 0) +
strlen(defs) + sizeof(" "));
- if (t) {
- char * te = t;
- *te = '\0';
- if (help) {
- strcpy(te, help);
- te += strlen(te);
- }
- *te++ = ' ';
- strcpy(te, defs);
- }
- defs = (char *)_free(defs);
- defs = t;
+ if (t) {
+ char * te = t;
+ if (help)
+ te = stpcpy(te, help);
+ *te++ = ' ';
+ strcpy(te, defs);
+ defs = _free(defs);
+ defs = t;
}
+ }
}
- /*@=branchstate@*/
if (opt->argDescrip == NULL) {
- switch (opt->argInfo & POPT_ARG_MASK) {
+ switch (poptArgType(opt)) {
case POPT_ARG_NONE:
break;
case POPT_ARG_VAL:
#ifdef NOTNOW /* XXX pug ugly nerdy output */
{ long aLong = opt->val;
- int ops = (opt->argInfo & POPT_ARGFLAG_LOGICALOPS);
- int negate = (opt->argInfo & POPT_ARGFLAG_NOT);
+ int ops = F_ISSET(opt, LOGICALOPS);
+ int negate = F_ISSET(opt, NOT);
/* Don't bother displaying typical values */
if (!ops && (aLong == 0L || aLong == 1L || aLong == -1L))
@@ -287,7 +416,7 @@ static void singleOptionHelp(FILE * fp, int maxLeftCol,
default:
/*@innerbreak@*/ break;
}
- *le++ = '=';
+ *le++ = (opt->longName != NULL ? '=' : ' ');
if (negate) *le++ = '~';
/*@-formatconst@*/
le += sprintf(le, (ops ? "0x%lx" : "%ld"), aLong);
@@ -297,104 +426,133 @@ static void singleOptionHelp(FILE * fp, int maxLeftCol,
#endif
break;
case POPT_ARG_INT:
+ case POPT_ARG_SHORT:
case POPT_ARG_LONG:
+ case POPT_ARG_LONGLONG:
case POPT_ARG_FLOAT:
case POPT_ARG_DOUBLE:
case POPT_ARG_STRING:
- *le++ = '=';
- strcpy(le, argDescrip); le += strlen(le);
+ *le++ = (opt->longName != NULL ? '=' : ' ');
+ le = stpcpy(le, argDescrip);
break;
default:
break;
}
} else {
- *le++ = '=';
- strcpy(le, argDescrip); le += strlen(le);
+ char *leo;
+
+ /* XXX argDescrip[0] determines "--foo=bar" or "--foo bar". */
+ if (!strchr(" =(", argDescrip[0]))
+ *le++ = ((poptArgType(opt) == POPT_ARG_MAINCALL) ? ' ' :
+ (poptArgType(opt) == POPT_ARG_ARGV) ? ' ' : '=');
+ le = stpcpy(leo = le, argDescrip);
+
+ /* Adjust for (possible) wide characters. */
+ displaypad = (int)((le - leo) - stringDisplayWidth(argDescrip));
}
- if (opt->argInfo & POPT_ARGFLAG_OPTIONAL)
+ if (F_ISSET(opt, OPTIONAL))
*le++ = ']';
*le = '\0';
}
-/*@=boundswrite@*/
if (help)
- fprintf(fp," %-*s ", maxLeftCol, left);
+ xx = POPT_fprintf(fp," %-*s ", (int)(maxLeftCol+displaypad), left);
else {
- fprintf(fp," %s\n", left);
+ xx = POPT_fprintf(fp," %s\n", left);
goto out;
}
- left = (char *)_free(left);
- if (defs) {
+ left = _free(left);
+ if (defs)
help = defs;
- }
helpLength = strlen(help);
-/*@-boundsread@*/
while (helpLength > lineLength) {
const char * ch;
char format[16];
ch = help + lineLength - 1;
- while (ch > help && !isspace(*ch)) ch--;
+ while (ch > help && !_isspaceptr(ch))
+ ch = POPT_prev_char(ch);
if (ch == help) break; /* give up */
- while (ch > (help + 1) && isspace(*ch)) ch--;
- ch++;
+ while (ch > (help + 1) && _isspaceptr(ch))
+ ch = POPT_prev_char (ch);
+ ch = POPT_next_char(ch);
+
+ /*
+ * XXX strdup is necessary to add NUL terminator so that an unknown
+ * no. of (possible) multi-byte characters can be displayed.
+ */
+ { char * fmthelp = xstrdup(help);
+ if (fmthelp) {
+ fmthelp[ch - help] = '\0';
+ sprintf(format, "%%s\n%%%ds", (int) indentLength);
+ /*@-formatconst@*/
+ xx = POPT_fprintf(fp, format, fmthelp, " ");
+ /*@=formatconst@*/
+ free(fmthelp);
+ }
+ }
- sprintf(format, "%%.%ds\n%%%ds", (int) (ch - help), indentLength);
- /*@-formatconst@*/
- fprintf(fp, format, help, " ");
- /*@=formatconst@*/
help = ch;
- while (isspace(*help) && *help) help++;
+ while (_isspaceptr(help) && *help)
+ help = POPT_next_char(help);
helpLength = strlen(help);
}
-/*@=boundsread@*/
if (helpLength) fprintf(fp, "%s\n", help);
+ help = NULL;
out:
/*@-dependenttrans@*/
- defs = (char *)_free(defs);
+ defs = _free(defs);
/*@=dependenttrans@*/
- left = (char *)_free(left);
+ left = _free(left);
}
/**
+ * Find display width for longest argument string.
* @param opt option(s)
* @param translation_domain translation domain
+ * @return display width
*/
-static int maxArgWidth(const struct poptOption * opt,
+static size_t maxArgWidth(const struct poptOption * opt,
/*@null@*/ const char * translation_domain)
/*@*/
{
- int max = 0;
- int len = 0;
- const char * s;
+ size_t max = 0;
+ size_t len = 0;
+ const char * argDescrip;
if (opt != NULL)
while (opt->longName || opt->shortName || opt->arg) {
- if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_INCLUDE_TABLE) {
+ if (poptArgType(opt) == POPT_ARG_INCLUDE_TABLE) {
if (opt->arg) /* XXX program error */
- len = maxArgWidth((const struct poptOption *)opt->arg, translation_domain);
+ len = maxArgWidth(opt->arg, translation_domain);
if (len > max) max = len;
- } else if (!(opt->argInfo & POPT_ARGFLAG_DOC_HIDDEN)) {
+ } else if (!F_ISSET(opt, DOC_HIDDEN)) {
len = sizeof(" ")-1;
- if (opt->shortName != '\0') len += sizeof("-X")-1;
- if (opt->shortName != '\0' && opt->longName) len += sizeof(", ")-1;
+ /* XXX --long always padded for alignment with/without "-X, ". */
+ len += sizeof("-X, ")-1;
if (opt->longName) {
- len += ((opt->argInfo & POPT_ARGFLAG_ONEDASH)
- ? sizeof("-")-1 : sizeof("--")-1);
+ len += (F_ISSET(opt, ONEDASH) ? sizeof("-") : sizeof("--")) - 1;
len += strlen(opt->longName);
}
- s = getArgDescrip(opt, translation_domain);
- if (s)
- len += sizeof("=")-1 + strlen(s);
- if (opt->argInfo & POPT_ARGFLAG_OPTIONAL) len += sizeof("[]")-1;
+ argDescrip = getArgDescrip(opt, translation_domain);
+
+ if (argDescrip) {
+
+ /* XXX argDescrip[0] determines "--foo=bar" or "--foo bar". */
+ if (!strchr(" =(", argDescrip[0])) len += sizeof("=")-1;
+
+ /* Adjust for (possible) wide characters. */
+ len += stringDisplayWidth(argDescrip);
+ }
+
+ if (F_ISSET(opt, OPTIONAL)) len += sizeof("[]")-1;
if (len > max) max = len;
}
-
opt++;
}
@@ -406,14 +564,15 @@ static int maxArgWidth(const struct poptOption * opt,
* @param fp output file handle
* @param items alias/exec array
* @param nitems no. of alias/exec entries
- * @param left
+ * @param columns output display width control
* @param translation_domain translation domain
*/
static void itemHelp(FILE * fp,
- /*@null@*/ poptItem items, int nitems, int left,
+ /*@null@*/ poptItem items, int nitems,
+ columns_t columns,
/*@null@*/ const char * translation_domain)
/*@globals fileSystem @*/
- /*@modifies *fp, fileSystem @*/
+ /*@modifies fp, fileSystem @*/
{
poptItem item;
int i;
@@ -422,9 +581,8 @@ static void itemHelp(FILE * fp,
for (i = 0, item = items; i < nitems; i++, item++) {
const struct poptOption * opt;
opt = &item->option;
- if ((opt->longName || opt->shortName) &&
- !(opt->argInfo & POPT_ARGFLAG_DOC_HIDDEN))
- singleOptionHelp(fp, left, opt, translation_domain);
+ if ((opt->longName || opt->shortName) && !F_ISSET(opt, DOC_HIDDEN))
+ singleOptionHelp(fp, columns, opt, translation_domain);
}
}
@@ -433,44 +591,47 @@ static void itemHelp(FILE * fp,
* @param con context
* @param fp output file handle
* @param table option(s)
- * @param left
+ * @param columns output display width control
* @param translation_domain translation domain
*/
static void singleTableHelp(poptContext con, FILE * fp,
- /*@null@*/ const struct poptOption * table, int left,
+ /*@null@*/ const struct poptOption * table,
+ columns_t columns,
/*@null@*/ const char * translation_domain)
/*@globals fileSystem @*/
- /*@modifies *fp, fileSystem @*/
+ /*@modifies fp, columns->cur, fileSystem @*/
{
const struct poptOption * opt;
const char *sub_transdom;
+ int xx;
if (table == poptAliasOptions) {
- itemHelp(fp, con->aliases, con->numAliases, left, NULL);
- itemHelp(fp, con->execs, con->numExecs, left, NULL);
+ itemHelp(fp, con->aliases, con->numAliases, columns, NULL);
+ itemHelp(fp, con->execs, con->numExecs, columns, NULL);
return;
}
if (table != NULL)
- for (opt = table; (opt->longName || opt->shortName || opt->arg); opt++) {
- if ((opt->longName || opt->shortName) &&
- !(opt->argInfo & POPT_ARGFLAG_DOC_HIDDEN))
- singleOptionHelp(fp, left, opt, translation_domain);
+ for (opt = table; opt->longName || opt->shortName || opt->arg; opt++) {
+ if ((opt->longName || opt->shortName) && !F_ISSET(opt, DOC_HIDDEN))
+ singleOptionHelp(fp, columns, opt, translation_domain);
}
if (table != NULL)
- for (opt = table; (opt->longName || opt->shortName || opt->arg); opt++) {
- if ((opt->argInfo & POPT_ARG_MASK) != POPT_ARG_INCLUDE_TABLE)
+ for (opt = table; opt->longName || opt->shortName || opt->arg; opt++) {
+ if (poptArgType(opt) != POPT_ARG_INCLUDE_TABLE)
continue;
- sub_transdom = getTableTranslationDomain(
- (const struct poptOption *)opt->arg);
+ sub_transdom = getTableTranslationDomain(opt->arg);
if (sub_transdom == NULL)
sub_transdom = translation_domain;
+ /* If no popt aliases/execs, skip poptAliasOption processing. */
+ if (opt->arg == poptAliasOptions && !(con->numAliases || con->numExecs))
+ continue;
if (opt->descrip)
- fprintf(fp, "\n%s\n", D_(sub_transdom, opt->descrip));
+ xx = POPT_fprintf(fp, "\n%s\n", D_(sub_transdom, opt->descrip));
- singleTableHelp(con, fp, (const struct poptOption *)opt->arg, left, sub_transdom);
+ singleTableHelp(con, fp, opt->arg, columns, sub_transdom);
}
}
@@ -478,22 +639,20 @@ static void singleTableHelp(poptContext con, FILE * fp,
* @param con context
* @param fp output file handle
*/
-static int showHelpIntro(poptContext con, FILE * fp)
+static size_t showHelpIntro(poptContext con, FILE * fp)
/*@globals fileSystem @*/
- /*@modifies *fp, fileSystem @*/
+ /*@modifies fp, fileSystem @*/
{
- int len = 6;
- const char * fn;
+ size_t len = (size_t)6;
+ int xx;
- fprintf(fp, POPT_("Usage:"));
+ xx = POPT_fprintf(fp, POPT_("Usage:"));
if (!(con->flags & POPT_CONTEXT_KEEP_FIRST)) {
-/*@-boundsread@*/
- /*@-nullderef@*/ /* LCL: wazzup? */
- fn = con->optionStack->argv[0];
- /*@=nullderef@*/
-/*@=boundsread@*/
+ struct optionStackEntry * os = con->optionStack;
+ const char * fn = (os->argv ? os->argv[0] : NULL);
if (fn == NULL) return len;
if (strchr(fn, '/')) fn = strrchr(fn, '/') + 1;
+ /* XXX POPT_fprintf not needed for argv[0] display. */
fprintf(fp, " %s", fn);
len += strlen(fn) + 1;
}
@@ -501,108 +660,119 @@ static int showHelpIntro(poptContext con, FILE * fp)
return len;
}
-void poptPrintHelp(poptContext con, FILE * fp, /*@unused@*/ int flags)
+void poptPrintHelp(poptContext con, FILE * fp, /*@unused@*/ UNUSED(int flags))
{
- int leftColWidth;
+ columns_t columns = calloc((size_t)1, sizeof(*columns));
+ int xx;
(void) showHelpIntro(con, fp);
if (con->otherHelp)
- fprintf(fp, " %s\n", con->otherHelp);
+ xx = POPT_fprintf(fp, " %s\n", con->otherHelp);
else
- fprintf(fp, " %s\n", POPT_("[OPTION...]"));
+ xx = POPT_fprintf(fp, " %s\n", POPT_("[OPTION...]"));
- leftColWidth = maxArgWidth(con->options, NULL);
- singleTableHelp(con, fp, con->options, leftColWidth, NULL);
+ if (columns) {
+ columns->cur = maxArgWidth(con->options, NULL);
+ columns->max = maxColumnWidth(fp);
+ singleTableHelp(con, fp, con->options, columns, NULL);
+ free(columns);
+ }
}
/**
+ * Display usage text for an option.
* @param fp output file handle
- * @param cursor
+ * @param columns output display width control
* @param opt option(s)
* @param translation_domain translation domain
*/
-static int singleOptionUsage(FILE * fp, int cursor,
+static size_t singleOptionUsage(FILE * fp, columns_t columns,
const struct poptOption * opt,
/*@null@*/ const char *translation_domain)
/*@globals fileSystem @*/
- /*@modifies *fp, fileSystem @*/
+ /*@modifies fp, columns->cur, fileSystem @*/
{
- int len = 4;
- char shortStr[2] = { '\0', '\0' };
- const char * item = shortStr;
+ size_t len = sizeof(" []")-1;
const char * argDescrip = getArgDescrip(opt, translation_domain);
-
- if (opt->shortName != '\0' && opt->longName != NULL) {
- len += 2;
- if (!(opt->argInfo & POPT_ARGFLAG_ONEDASH)) len++;
- len += strlen(opt->longName);
- } else if (opt->shortName != '\0') {
- len++;
- shortStr[0] = opt->shortName;
- shortStr[1] = '\0';
- } else if (opt->longName) {
+ /* Display shortName iff printable non-space. */
+ int prtshort = (int)(isprint((int)opt->shortName) && opt->shortName != ' ');
+
+#define prtlong (opt->longName != NULL) /* XXX splint needs a clue */
+ if (!(prtshort || prtlong))
+ return columns->cur;
+
+ len = sizeof(" []")-1;
+ if (prtshort)
+ len += sizeof("-c")-1;
+ if (prtlong) {
+ if (prtshort) len += sizeof("|")-1;
+ len += (F_ISSET(opt, ONEDASH) ? sizeof("-") : sizeof("--")) - 1;
len += strlen(opt->longName);
- if (!(opt->argInfo & POPT_ARGFLAG_ONEDASH)) len++;
- item = opt->longName;
}
- if (len == 4) return cursor;
+ if (argDescrip) {
+
+ /* XXX argDescrip[0] determines "--foo=bar" or "--foo bar". */
+ if (!strchr(" =(", argDescrip[0])) len += sizeof("=")-1;
- if (argDescrip)
- len += strlen(argDescrip) + 1;
+ /* Adjust for (possible) wide characters. */
+ len += stringDisplayWidth(argDescrip);
+ }
- if ((cursor + len) > 79) {
+ if ((columns->cur + len) > columns->max) {
fprintf(fp, "\n ");
- cursor = 7;
+ columns->cur = (size_t)7;
}
- if (opt->longName && opt->shortName) {
- fprintf(fp, " [-%c|-%s%s%s%s]",
- opt->shortName, ((opt->argInfo & POPT_ARGFLAG_ONEDASH) ? "" : "-"),
- opt->longName,
- (argDescrip ? " " : ""),
- (argDescrip ? argDescrip : ""));
- } else {
- fprintf(fp, " [-%s%s%s%s]",
- ((opt->shortName || (opt->argInfo & POPT_ARGFLAG_ONEDASH)) ? "" : "-"),
- item,
- (argDescrip ? (opt->shortName != '\0' ? " " : "=") : ""),
- (argDescrip ? argDescrip : ""));
+ fprintf(fp, " [");
+ if (prtshort)
+ fprintf(fp, "-%c", opt->shortName);
+ if (prtlong)
+ fprintf(fp, "%s%s%s",
+ (prtshort ? "|" : ""),
+ (F_ISSET(opt, ONEDASH) ? "-" : "--"),
+ opt->longName);
+#undef prtlong
+
+ if (argDescrip) {
+ /* XXX argDescrip[0] determines "--foo=bar" or "--foo bar". */
+ if (!strchr(" =(", argDescrip[0])) fprintf(fp, "=");
+ fprintf(fp, "%s", argDescrip);
}
+ fprintf(fp, "]");
- return cursor + len + 1;
+ return columns->cur + len + 1;
}
/**
* Display popt alias and exec usage.
* @param fp output file handle
- * @param cursor
+ * @param columns output display width control
* @param item alias/exec array
* @param nitems no. of ara/exec entries
* @param translation_domain translation domain
*/
-static int itemUsage(FILE * fp, int cursor, poptItem item, int nitems,
+static size_t itemUsage(FILE * fp, columns_t columns,
+ /*@null@*/ poptItem item, int nitems,
/*@null@*/ const char * translation_domain)
/*@globals fileSystem @*/
- /*@modifies *fp, fileSystem @*/
+ /*@modifies fp, columns->cur, fileSystem @*/
{
int i;
- /*@-branchstate@*/ /* FIX: W2DO? */
if (item != NULL)
for (i = 0; i < nitems; i++, item++) {
const struct poptOption * opt;
opt = &item->option;
- if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_INTL_DOMAIN) {
+ if (poptArgType(opt) == POPT_ARG_INTL_DOMAIN) {
translation_domain = (const char *)opt->arg;
- } else if ((opt->longName || opt->shortName) &&
- !(opt->argInfo & POPT_ARGFLAG_DOC_HIDDEN)) {
- cursor = singleOptionUsage(fp, cursor, opt, translation_domain);
+ } else
+ if ((opt->longName || opt->shortName) && !F_ISSET(opt, DOC_HIDDEN)) {
+ columns->cur = singleOptionUsage(fp, columns, opt, translation_domain);
}
}
- /*@=branchstate@*/
- return cursor;
+ return columns->cur;
}
/**
@@ -611,6 +781,7 @@ static int itemUsage(FILE * fp, int cursor, poptItem item, int nitems,
typedef struct poptDone_s {
int nopts;
int maxopts;
+/*@null@*/
const void ** opts;
} * poptDone;
@@ -618,31 +789,30 @@ typedef struct poptDone_s {
* Display usage text for a table of options.
* @param con context
* @param fp output file handle
- * @param cursor
+ * @param columns output display width control
* @param opt option(s)
* @param translation_domain translation domain
* @param done tables already processed
* @return
*/
-static int singleTableUsage(poptContext con, FILE * fp, int cursor,
+static size_t singleTableUsage(poptContext con, FILE * fp, columns_t columns,
/*@null@*/ const struct poptOption * opt,
/*@null@*/ const char * translation_domain,
/*@null@*/ poptDone done)
/*@globals fileSystem @*/
- /*@modifies *fp, done, fileSystem @*/
+ /*@modifies fp, columns->cur, done, fileSystem @*/
{
- /*@-branchstate@*/ /* FIX: W2DO? */
if (opt != NULL)
for (; (opt->longName || opt->shortName || opt->arg) ; opt++) {
- if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_INTL_DOMAIN) {
+ if (poptArgType(opt) == POPT_ARG_INTL_DOMAIN) {
translation_domain = (const char *)opt->arg;
- } else if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_INCLUDE_TABLE) {
+ } else
+ if (poptArgType(opt) == POPT_ARG_INCLUDE_TABLE) {
if (done) {
int i = 0;
+ if (done->opts != NULL)
for (i = 0; i < done->nopts; i++) {
-/*@-boundsread@*/
const void * that = done->opts[i];
-/*@=boundsread@*/
if (that == NULL || that != opt->arg)
/*@innercontinue@*/ continue;
/*@innerbreak@*/ break;
@@ -650,21 +820,18 @@ static int singleTableUsage(poptContext con, FILE * fp, int cursor,
/* Skip if this table has already been processed. */
if (opt->arg == NULL || i < done->nopts)
continue;
-/*@-boundswrite@*/
- if (done->nopts < done->maxopts)
+ if (done->opts != NULL && done->nopts < done->maxopts)
done->opts[done->nopts++] = (const void *) opt->arg;
-/*@=boundswrite@*/
}
- cursor = singleTableUsage(con, fp, cursor, (const struct poptOption *)opt->arg,
+ columns->cur = singleTableUsage(con, fp, columns, opt->arg,
translation_domain, done);
- } else if ((opt->longName || opt->shortName) &&
- !(opt->argInfo & POPT_ARGFLAG_DOC_HIDDEN)) {
- cursor = singleOptionUsage(fp, cursor, opt, translation_domain);
+ } else
+ if ((opt->longName || opt->shortName) && !F_ISSET(opt, DOC_HIDDEN)) {
+ columns->cur = singleOptionUsage(fp, columns, opt, translation_domain);
}
}
- /*@=branchstate@*/
- return cursor;
+ return columns->cur;
}
/**
@@ -675,67 +842,84 @@ static int singleTableUsage(poptContext con, FILE * fp, int cursor,
* @retval str concatenation of short options
* @return length of display string
*/
-static int showShortOptions(const struct poptOption * opt, FILE * fp,
+static size_t showShortOptions(const struct poptOption * opt, FILE * fp,
/*@null@*/ char * str)
/*@globals fileSystem @*/
- /*@modifies *str, *fp, fileSystem @*/
+ /*@modifies str, *fp, fileSystem @*/
/*@requires maxRead(str) >= 0 @*/
{
- /* bufsize larger then the ascii set, lazy alloca on top level call. */
- char * s = (str != NULL ? str : (char *)memset(alloca(300), 0, 300));
- int len = 0;
+ /* bufsize larger then the ascii set, lazy allocation on top level call. */
+ size_t nb = (size_t)300;
+ char * s = (str != NULL ? str : calloc((size_t)1, nb));
+ size_t len = (size_t)0;
+
+ if (s == NULL)
+ return 0;
-/*@-boundswrite@*/
if (opt != NULL)
for (; (opt->longName || opt->shortName || opt->arg); opt++) {
- if (opt->shortName && !(opt->argInfo & POPT_ARG_MASK))
- s[strlen(s)] = opt->shortName;
- else if ((opt->argInfo & POPT_ARG_MASK) == POPT_ARG_INCLUDE_TABLE)
+ if (!F_ISSET(opt, DOC_HIDDEN) && opt->shortName && !poptArgType(opt))
+ {
+ /* Display shortName iff unique printable non-space. */
+ if (!strchr(s, opt->shortName) && isprint((int)opt->shortName)
+ && opt->shortName != ' ')
+ s[strlen(s)] = opt->shortName;
+ } else if (poptArgType(opt) == POPT_ARG_INCLUDE_TABLE)
if (opt->arg) /* XXX program error */
- len = showShortOptions(
- (const struct poptOption *)opt->arg, fp, s);
+ len = showShortOptions(opt->arg, fp, s);
}
-/*@=boundswrite@*/
/* On return to top level, print the short options, return print length. */
- if (s == str && *s != '\0') {
+ if (s != str && *s != '\0') {
fprintf(fp, " [-%s]", s);
len = strlen(s) + sizeof(" [-]")-1;
}
+/*@-temptrans@*/ /* LCL: local s, not str arg, is being freed. */
+ if (s != str)
+ free(s);
+/*@=temptrans@*/
return len;
}
-void poptPrintUsage(poptContext con, FILE * fp, /*@unused@*/ int flags)
+void poptPrintUsage(poptContext con, FILE * fp, /*@unused@*/ UNUSED(int flags))
{
- poptDone done = (poptDone)memset(alloca(sizeof(*done)), 0, sizeof(*done));
- int cursor;
+ columns_t columns = calloc((size_t)1, sizeof(*columns));
+ struct poptDone_s done_buf;
+ poptDone done = &done_buf;
+ memset(done, 0, sizeof(*done));
done->nopts = 0;
done->maxopts = 64;
- cursor = done->maxopts * sizeof(*done->opts);
-/*@-boundswrite@*/
- done->opts = (const void **)memset(alloca(cursor), 0, cursor);
- done->opts[done->nopts++] = (const void *) con->options;
-/*@=boundswrite@*/
-
- cursor = showHelpIntro(con, fp);
- cursor += showShortOptions(con->options, fp, NULL);
- cursor = singleTableUsage(con, fp, cursor, con->options, NULL, done);
- cursor = itemUsage(fp, cursor, con->aliases, con->numAliases, NULL);
- cursor = itemUsage(fp, cursor, con->execs, con->numExecs, NULL);
+ if (columns) {
+ columns->cur = done->maxopts * sizeof(*done->opts);
+ columns->max = maxColumnWidth(fp);
+ done->opts = calloc((size_t)1, columns->cur);
+ /*@-keeptrans@*/
+ if (done->opts != NULL)
+ done->opts[done->nopts++] = (const void *) con->options;
+ /*@=keeptrans@*/
+
+ columns->cur = showHelpIntro(con, fp);
+ columns->cur += showShortOptions(con->options, fp, NULL);
+ columns->cur = singleTableUsage(con, fp, columns, con->options, NULL, done);
+ columns->cur = itemUsage(fp, columns, con->aliases, con->numAliases, NULL);
+ columns->cur = itemUsage(fp, columns, con->execs, con->numExecs, NULL);
if (con->otherHelp) {
- cursor += strlen(con->otherHelp) + 1;
- if (cursor > 79) fprintf(fp, "\n ");
+ columns->cur += strlen(con->otherHelp) + 1;
+ if (columns->cur > columns->max) fprintf(fp, "\n ");
fprintf(fp, " %s", con->otherHelp);
}
fprintf(fp, "\n");
+ if (done->opts != NULL)
+ free(done->opts);
+ free(columns);
+ }
}
void poptSetOtherOptionHelp(poptContext con, const char * text)
{
- con->otherHelp = (const char *)_free(con->otherHelp);
+ con->otherHelp = _free(con->otherHelp);
con->otherHelp = xstrdup(text);
}
-/*@=type@*/
diff --git a/third_party/popt/poptint.c b/third_party/popt/poptint.c
new file mode 100644
index 0000000..1af46ff
--- /dev/null
+++ b/third_party/popt/poptint.c
@@ -0,0 +1,199 @@
+#include "system.h"
+#include <stdarg.h>
+#include "poptint.h"
+
+/* Any pair of 32 bit hashes can be used. lookup3.c generates pairs, will do. */
+#define _JLU3_jlu32lpair 1
+#define jlu32lpair poptJlu32lpair
+#include "lookup3.c"
+
+/*@-varuse +charint +ignoresigns @*/
+/*@unchecked@*/ /*@observer@*/
+static const unsigned char utf8_skip_data[256] = {
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+ 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+ 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,6,6,1,1
+};
+/*@=varuse =charint =ignoresigns @*/
+
+const char *
+POPT_prev_char (const char *str)
+{
+ const char *p = str;
+
+ while (1) {
+ p--;
+ if (((unsigned)*p & 0xc0) != (unsigned)0x80)
+ return p;
+ }
+}
+
+const char *
+POPT_next_char (const char *str)
+{
+ const char *p = str;
+
+ while (*p != '\0') {
+ p++;
+ if (((unsigned)*p & 0xc0) != (unsigned)0x80)
+ break;
+ }
+ return p;
+}
+
+#if !defined(POPT_fprintf) /* XXX lose all the goop ... */
+
+#if defined(HAVE_DCGETTEXT) && !defined(__LCLINT__)
+/*
+ * Rebind a "UTF-8" codeset for popt's internal use.
+ */
+char *
+POPT_dgettext(const char * dom, const char * str)
+{
+ char * codeset = NULL;
+ char * retval = NULL;
+
+ if (!dom)
+ dom = textdomain(NULL);
+ codeset = bind_textdomain_codeset(dom, NULL);
+ bind_textdomain_codeset(dom, "UTF-8");
+ retval = dgettext(dom, str);
+ bind_textdomain_codeset(dom, codeset);
+
+ return retval;
+}
+#endif
+
+#ifdef HAVE_ICONV
+/**
+ * Return malloc'd string converted from UTF-8 to current locale.
+ * @param istr input string (UTF-8 encoding assumed)
+ * @return localized string
+ */
+static /*@only@*/ /*@null@*/ char *
+strdup_locale_from_utf8 (/*@null@*/ char * istr)
+ /*@*/
+{
+ char * codeset = NULL;
+ char * ostr = NULL;
+ iconv_t cd;
+
+ if (istr == NULL)
+ return NULL;
+
+#ifdef HAVE_LANGINFO_H
+ codeset = nl_langinfo ((nl_item)CODESET);
+#endif
+
+ if (codeset != NULL && strcmp(codeset, "UTF-8") != 0
+ && (cd = iconv_open(codeset, "UTF-8")) != (iconv_t)-1)
+ {
+ char * shift_pin = NULL;
+ size_t db = strlen(istr);
+/*@owned@*/
+ char * dstr = malloc((db + 1) * sizeof(*dstr));
+ char * pin = istr;
+ char * pout = dstr;
+ size_t ib = db;
+ size_t ob = db;
+ size_t err;
+
+ if (dstr == NULL)
+ return NULL;
+ err = iconv(cd, NULL, NULL, NULL, NULL);
+ while (1) {
+ *pout = '\0';
+ err = iconv(cd, &pin, &ib, &pout, &ob);
+ if (err != (size_t)-1) {
+ if (shift_pin == NULL) {
+ shift_pin = pin;
+ pin = NULL;
+ ib = 0;
+ continue;
+ }
+ } else
+ switch (errno) {
+ case E2BIG:
+ { size_t used = (size_t)(pout - dstr);
+ db *= 2;
+ dstr = realloc(dstr, (db + 1) * sizeof(*dstr));
+ if (dstr != NULL) {
+ pout = dstr + used;
+ ob = db - used;
+ continue;
+ }
+ } /*@switchbreak@*/ break;
+ case EINVAL:
+ case EILSEQ:
+ default:
+ /*@switchbreak@*/ break;
+ }
+ break;
+ }
+ (void) iconv_close(cd);
+ *pout = '\0';
+ ostr = xstrdup(dstr);
+ free(dstr);
+ } else
+ ostr = xstrdup(istr);
+
+ return ostr;
+}
+#endif
+
+int
+POPT_fprintf (FILE * stream, const char * format, ...)
+{
+ char * b = NULL, * ob = NULL;
+ int rc;
+ va_list ap;
+
+#if defined(HAVE_VASPRINTF) && !defined(__LCLINT__)
+ va_start(ap, format);
+ if ((rc = vasprintf(&b, format, ap)) < 0)
+ b = NULL;
+ va_end(ap);
+#else
+ size_t nb = (size_t)1;
+
+ /* HACK: add +1 to the realloc no. of bytes "just in case". */
+ /* XXX Likely unneeded, the issues wrto vsnprintf(3) return b0rkage have
+ * to do with whether the final '\0' is counted (or not). The code
+ * below already adds +1 for the (possibly already counted) trailing NUL.
+ */
+ while ((b = realloc(b, nb+1)) != NULL) {
+ va_start(ap, format);
+ rc = vsnprintf(b, nb, format, ap);
+ va_end(ap);
+ if (rc > -1) { /* glibc 2.1 */
+ if ((size_t)rc < nb)
+ break;
+ nb = (size_t)(rc + 1); /* precise buffer length known */
+ } else /* glibc 2.0 */
+ nb += (nb < (size_t)100 ? (size_t)100 : nb);
+ ob = b;
+ }
+#endif
+
+ rc = 0;
+ if (b != NULL) {
+#ifdef HAVE_ICONV
+ ob = strdup_locale_from_utf8(b);
+ if (ob != NULL) {
+ rc = fprintf(stream, "%s", ob);
+ free(ob);
+ } else
+#endif
+ rc = fprintf(stream, "%s", b);
+ free (b);
+ }
+
+ return rc;
+}
+
+#endif /* !defined(POPT_fprintf) */
diff --git a/third_party/popt/poptint.h b/third_party/popt/poptint.h
index 5d308ef..80cbaca 100644
--- a/third_party/popt/poptint.h
+++ b/third_party/popt/poptint.h
@@ -9,6 +9,8 @@
#ifndef H_POPTINT
#define H_POPTINT
+#include <stdint.h>
+
/**
* Wrapper to free(3), hides const compilation noise, permit NULL, return NULL.
* @param p memory to free
@@ -42,15 +44,67 @@ typedef struct {
#define PBM_CLR(d, s) (__PBM_BITS (s)[__PBM_IX (d)] &= ~__PBM_MASK (d))
#define PBM_ISSET(d, s) ((__PBM_BITS (s)[__PBM_IX (d)] & __PBM_MASK (d)) != 0)
+extern void poptJlu32lpair(/*@null@*/ const void *key, size_t size,
+ uint32_t *pc, uint32_t *pb)
+ /*@modifies *pc, *pb@*/;
+
+/** \ingroup popt
+ * Typedef's for string and array of strings.
+ */
+/*@-exporttype@*/
+typedef const char * poptString;
+typedef poptString * poptArgv;
+/*@=exporttype@*/
+
+/** \ingroup popt
+ * A union to simplify opt->arg access without casting.
+ */
+/*@-exporttype -fielduse@*/
+typedef union poptArg_u {
+/*@shared@*/
+ void * ptr;
+ int * intp;
+ short * shortp;
+ long * longp;
+ long long * longlongp;
+ float * floatp;
+ double * doublep;
+ const char ** argv;
+ poptCallbackType cb;
+/*@shared@*/
+ poptOption opt;
+} poptArg;
+/*@=exporttype =fielduse@*/
+
+/*@-exportvar@*/
+/*@unchecked@*/
+extern unsigned int _poptArgMask;
+/*@unchecked@*/
+extern unsigned int _poptGroupMask;
+/*@=exportvar@*/
+
+#define poptArgType(_opt) ((_opt)->argInfo & _poptArgMask)
+#define poptGroup(_opt) ((_opt)->argInfo & _poptGroupMask)
+
+#define F_ISSET(_opt, _FLAG) ((_opt)->argInfo & POPT_ARGFLAG_##_FLAG)
+#define LF_ISSET(_FLAG) (argInfo & POPT_ARGFLAG_##_FLAG)
+#define CBF_ISSET(_opt, _FLAG) ((_opt)->argInfo & POPT_CBFLAG_##_FLAG)
+
+/* XXX sick hack to preserve pretense of a popt-1.x ABI. */
+#define poptSubstituteHelpI18N(opt) \
+ { /*@-observertrans@*/ \
+ if ((opt) == poptHelpOptions) (opt) = poptHelpOptionsI18N; \
+ /*@=observertrans@*/ }
+
struct optionStackEntry {
int argc;
/*@only@*/ /*@null@*/
- const char ** argv;
+ poptArgv argv;
/*@only@*/ /*@null@*/
pbm_set * argb;
int next;
/*@only@*/ /*@null@*/
- const char * nextArg;
+ char * nextArg;
/*@observer@*/ /*@null@*/
const char * nextCharArg;
/*@dependent@*/ /*@null@*/
@@ -63,7 +117,7 @@ struct poptContext_s {
/*@dependent@*/
struct optionStackEntry * os;
/*@owned@*/ /*@null@*/
- const char ** leftovers;
+ poptArgv leftovers;
int numLeftovers;
int nextLeftover;
/*@keep@*/
@@ -74,37 +128,90 @@ struct poptContext_s {
/*@only@*/ /*@null@*/
poptItem aliases;
int numAliases;
- int flags;
+ unsigned int flags;
/*@owned@*/ /*@null@*/
poptItem execs;
int numExecs;
/*@only@*/ /*@null@*/
- const char ** finalArgv;
+ poptArgv finalArgv;
int finalArgvCount;
int finalArgvAlloced;
+/*@null@*/
+ int (*maincall) (int argc, const char **argv);
/*@dependent@*/ /*@null@*/
poptItem doExec;
-/*@only@*/
+/*@only@*/ /*@null@*/
const char * execPath;
int execAbsolute;
-/*@only@*/
+/*@only@*/ /*@relnull@*/
const char * otherHelp;
/*@null@*/
pbm_set * arg_strip;
};
-#ifdef HAVE_LIBINTL_H
+#if defined(POPT_fprintf)
+#define POPT_dgettext dgettext
+#else
+#ifdef HAVE_ICONV
+#include <iconv.h>
+#if defined(__LCLINT__)
+/*@-declundef -incondefs @*/
+extern /*@only@*/ iconv_t iconv_open(const char *__tocode, const char *__fromcode)
+ /*@*/;
+
+extern size_t iconv(iconv_t __cd, /*@null@*/ char ** __inbuf,
+ /*@null@*/ /*@out@*/ size_t * __inbytesleft,
+ /*@null@*/ /*@out@*/ char ** __outbuf,
+ /*@null@*/ /*@out@*/ size_t * __outbytesleft)
+ /*@modifies __cd,
+ *__inbuf, *__inbytesleft, *__outbuf, *__outbytesleft @*/;
+
+extern int iconv_close(/*@only@*/ iconv_t __cd)
+ /*@modifies __cd @*/;
+/*@=declundef =incondefs @*/
+#endif
+#endif
+
+#ifdef HAVE_LANGINFO_H
+#include <langinfo.h>
+#if defined(__LCLINT__)
+/*@-declundef -incondefs @*/
+extern char *nl_langinfo (nl_item __item)
+ /*@*/;
+/*@=declundef =incondefs @*/
+#endif
+#endif
+
+#if defined(HAVE_DCGETTEXT) && !defined(__LCLINT__)
+char *POPT_dgettext(const char * dom, const char * str)
+ /*@*/;
+#endif
+
+int POPT_fprintf (FILE* stream, const char *format, ...)
+ /*@globals fileSystem @*/
+ /*@modifies stream, fileSystem @*/;
+#endif /* !defined(POPT_fprintf) */
+
+const char *POPT_prev_char (/*@returned@*/ const char *str)
+ /*@*/;
+
+const char *POPT_next_char (/*@returned@*/ const char *str)
+ /*@*/;
+
+#endif
+
+#if defined(ENABLE_NLS) && defined(HAVE_LIBINTL_H)
#include <libintl.h>
#endif
-#if defined(HAVE_GETTEXT) && !defined(__LCLINT__)
+#if defined(ENABLE_NLS) && defined(HAVE_GETTEXT) && !defined(__LCLINT__)
#define _(foo) gettext(foo)
#else
#define _(foo) foo
#endif
-#if defined(HAVE_DCGETTEXT) && !defined(__LCLINT__)
-#define D_(dom, str) dgettext(dom, str)
+#if defined(ENABLE_NLS) && defined(HAVE_DCGETTEXT) && !defined(__LCLINT__)
+#define D_(dom, str) POPT_dgettext(dom, str)
#define POPT_(foo) D_("popt", foo)
#else
#define D_(dom, str) str
@@ -113,4 +220,3 @@ struct poptContext_s {
#define N_(foo) foo
-#endif
diff --git a/third_party/popt/poptparse.c b/third_party/popt/poptparse.c
index b03deef..39bf1aa 100644
--- a/third_party/popt/poptparse.c
+++ b/third_party/popt/poptparse.c
@@ -10,7 +10,6 @@
#define POPT_ARGV_ARRAY_GROW_DELTA 5
-/*@-boundswrite@*/
int poptDupArgv(int argc, const char **argv,
int * argcPtr, const char *** argvPtr)
{
@@ -27,18 +26,18 @@ int poptDupArgv(int argc, const char **argv,
nb += strlen(argv[i]) + 1;
}
- dst = (char *)malloc(nb);
+ dst = malloc(nb);
if (dst == NULL) /* XXX can't happen */
return POPT_ERROR_MALLOC;
- argv2 = (const char **) dst;
+ argv2 = (void *) dst;
dst += (argc + 1) * sizeof(*argv);
+ *dst = '\0';
- /*@-branchstate@*/
for (i = 0; i < argc; i++) {
argv2[i] = dst;
- dst += strlen(strcpy(dst, argv[i])) + 1;
+ dst = stpcpy(dst, argv[i]);
+ dst++; /* trailing NUL */
}
- /*@=branchstate@*/
argv2[argc] = NULL;
if (argvPtr) {
@@ -51,21 +50,24 @@ int poptDupArgv(int argc, const char **argv,
*argcPtr = argc;
return 0;
}
-/*@=boundswrite@*/
-/*@-bounds@*/
int poptParseArgvString(const char * s, int * argcPtr, const char *** argvPtr)
{
const char * src;
char quote = '\0';
int argvAlloced = POPT_ARGV_ARRAY_GROW_DELTA;
- const char ** argv = (const char **)malloc(sizeof(*argv) * argvAlloced);
+ const char ** argv = malloc(sizeof(*argv) * argvAlloced);
int argc = 0;
- int buflen = strlen(s) + 1;
- char * buf = (char*)memset(alloca(buflen), 0, buflen);
+ size_t buflen = strlen(s) + 1;
+ char * buf, * bufOrig = NULL;
int rc = POPT_ERROR_MALLOC;
if (argv == NULL) return rc;
+ buf = bufOrig = calloc((size_t)1, buflen);
+ if (buf == NULL) {
+ free(argv);
+ return rc;
+ }
argv[argc] = buf;
for (src = s; *src != '\0'; src++) {
@@ -81,12 +83,12 @@ int poptParseArgvString(const char * s, int * argcPtr, const char *** argvPtr)
if (*src != quote) *buf++ = '\\';
}
*buf++ = *src;
- } else if (isspace(*src)) {
+ } else if (_isspaceptr(src)) {
if (*argv[argc] != '\0') {
buf++, argc++;
if (argc == argvAlloced) {
argvAlloced += POPT_ARGV_ARRAY_GROW_DELTA;
- argv = (const char **)realloc(argv, sizeof(*argv) * argvAlloced);
+ argv = realloc(argv, sizeof(*argv) * argvAlloced);
if (argv == NULL) goto exit;
}
argv[argc] = buf;
@@ -116,29 +118,29 @@ int poptParseArgvString(const char * s, int * argcPtr, const char *** argvPtr)
rc = poptDupArgv(argc, argv, argcPtr, argvPtr);
exit:
+ if (bufOrig) free(bufOrig);
if (argv) free(argv);
return rc;
}
-/*@=bounds@*/
/* still in the dev stage.
* return values, perhaps 1== file erro
* 2== line to long
* 3== umm.... more?
*/
-int poptConfigFileToString(FILE *fp, char ** argstrp, /*@unused@*/ int flags)
+int poptConfigFileToString(FILE *fp, char ** argstrp,
+ /*@unused@*/ UNUSED(int flags))
{
char line[999];
char * argstr;
char * p;
char * q;
char * x;
- int t;
- int argvlen = 0;
+ size_t t;
+ size_t argvlen = 0;
size_t maxlinelen = sizeof(line);
size_t linelen;
- int maxargvlen = 480;
- int linenum = 0;
+ size_t maxargvlen = (size_t)480;
*argstrp = NULL;
@@ -149,41 +151,42 @@ int poptConfigFileToString(FILE *fp, char ** argstrp, /*@unused@*/ int flags)
if (fp == NULL)
return POPT_ERROR_NULLARG;
- argstr = (char *)calloc(maxargvlen, sizeof(*argstr));
+ argstr = calloc(maxargvlen, sizeof(*argstr));
if (argstr == NULL) return POPT_ERROR_MALLOC;
while (fgets(line, (int)maxlinelen, fp) != NULL) {
- linenum++;
p = line;
/* loop until first non-space char or EOL */
- while( *p != '\0' && isspace(*p) )
+ while( *p != '\0' && _isspaceptr(p) )
p++;
linelen = strlen(p);
- if (linelen >= maxlinelen-1)
+ if (linelen >= maxlinelen-1) {
+ free(argstr);
return POPT_ERROR_OVERFLOW; /* XXX line too long */
+ }
if (*p == '\0' || *p == '\n') continue; /* line is empty */
if (*p == '#') continue; /* comment line */
q = p;
- while (*q != '\0' && (!isspace(*q)) && *q != '=')
+ while (*q != '\0' && (!_isspaceptr(q)) && *q != '=')
q++;
- if (isspace(*q)) {
+ if (_isspaceptr(q)) {
/* a space after the name, find next non space */
*q++='\0';
- while( *q != '\0' && isspace((int)*q) ) q++;
+ while( *q != '\0' && _isspaceptr(q) ) q++;
}
if (*q == '\0') {
/* single command line option (ie, no name=val, just name) */
q[-1] = '\0'; /* kill off newline from fgets() call */
- argvlen += (t = q - p) + (sizeof(" --")-1);
+ argvlen += (t = (size_t)(q - p)) + (sizeof(" --")-1);
if (argvlen >= maxargvlen) {
maxargvlen = (t > maxargvlen) ? t*2 : maxargvlen*2;
- argstr = (char *)realloc(argstr, maxargvlen);
+ argstr = realloc(argstr, maxargvlen);
if (argstr == NULL) return POPT_ERROR_MALLOC;
}
strcat(argstr, " --");
@@ -197,22 +200,22 @@ int poptConfigFileToString(FILE *fp, char ** argstrp, /*@unused@*/ int flags)
*q++ = '\0';
/* find next non-space letter of value */
- while (*q != '\0' && isspace(*q))
+ while (*q != '\0' && _isspaceptr(q))
q++;
if (*q == '\0')
continue; /* XXX silently ignore missing value */
/* now, loop and strip all ending whitespace */
x = p + linelen;
- while (isspace(*--x))
- *x = 0; /* null out last char if space (including fgets() NL) */
+ while (_isspaceptr(--x))
+ *x = '\0'; /* null out last char if space (including fgets() NL) */
/* rest of line accept */
- t = x - p;
+ t = (size_t)(x - p);
argvlen += t + (sizeof("' --='")-1);
if (argvlen >= maxargvlen) {
maxargvlen = (t > maxargvlen) ? t*2 : maxargvlen*2;
- argstr = (char *)realloc(argstr, maxargvlen);
+ argstr = realloc(argstr, maxargvlen);
if (argstr == NULL) return POPT_ERROR_MALLOC;
}
strcat(argstr, " --");
diff --git a/third_party/popt/system.h b/third_party/popt/system.h
index bbb4f6c..452bf40 100644
--- a/third_party/popt/system.h
+++ b/third_party/popt/system.h
@@ -1,3 +1,7 @@
+/**
+ * \file popt/system.h
+ */
+
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
@@ -13,11 +17,14 @@ extern __const __int32_t *__ctype_toupper;
#include <ctype.h>
+/* XXX isspace(3) has i18n encoding signednesss issues on Solaris. */
+#define _isspaceptr(_chp) isspace((int)(*(unsigned char *)(_chp)))
+
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
-#if HAVE_MCHECK_H
+#ifdef HAVE_MCHECK_H
#include <mcheck.h>
#endif
@@ -25,7 +32,7 @@ extern __const __int32_t *__ctype_toupper;
#include <stdlib.h>
#include <string.h>
-#if HAVE_UNISTD_H
+#if defined(HAVE_UNISTD_H) && !defined(__LCLINT__)
#include <unistd.h>
#endif
@@ -35,44 +42,62 @@ extern __const __int32_t *__ctype_toupper;
#include <libc.h>
#endif
-#if defined(__LCLINT__)
-/*@-declundef -incondefs -redecl@*/ /* LCL: missing annotation */
-/*@only@*/ void * alloca (size_t __size)
- /*@ensures MaxSet(result) == (__size - 1) @*/
+/*@-incondefs@*/
+/*@mayexit@*/ /*@only@*/ /*@out@*/ /*@unused@*/
+void * xmalloc (size_t size)
+ /*@globals errno @*/
+ /*@ensures maxSet(result) == (size - 1) @*/
+ /*@modifies errno @*/;
+
+/*@mayexit@*/ /*@only@*/ /*@unused@*/
+void * xcalloc (size_t nmemb, size_t size)
+ /*@ensures maxSet(result) == (nmemb - 1) @*/
/*@*/;
-/*@=declundef =incondefs =redecl@*/
-#endif
-/* AIX requires this to be the first thing in the file. */
-#ifndef __GNUC__
-# if HAVE_ALLOCA_H
-# include <alloca.h>
-# else
-# ifdef _AIX
-#pragma alloca
-# else
-# ifndef alloca /* predefined by HP cc +Olibcalls */
-char *alloca ();
-# endif
-# endif
-# endif
-#elif defined(__GNUC__) && defined(__STRICT_ANSI__)
-#define alloca __builtin_alloca
-#elif defined(__GNUC__) && defined(HAVE_ALLOCA_H)
-# include <alloca.h>
-#endif
+/*@mayexit@*/ /*@only@*/ /*@unused@*/
+void * xrealloc (/*@null@*/ /*@only@*/ void * ptr, size_t size)
+ /*@ensures maxSet(result) == (size - 1) @*/
+ /*@modifies *ptr @*/;
-/*@-redecl -redef@*/
-/*@mayexit@*/ /*@only@*/ char * xstrdup (const char *str)
+/*@mayexit@*/ /*@only@*/ /*@unused@*/
+char * xstrdup (const char *str)
/*@*/;
-/*@=redecl =redef@*/
+/*@=incondefs@*/
+
+#if !defined(HAVE_STPCPY)
+/* Copy SRC to DEST, returning the address of the terminating '\0' in DEST. */
+static inline char * stpcpy (char *dest, const char * src) {
+ register char *d = dest;
+ register const char *s = src;
-#if HAVE_MCHECK_H && defined(__GNUC__)
+ do
+ *d++ = *s;
+ while (*s++ != '\0');
+ return d - 1;
+}
+#endif
+
+/* Memory allocation via macro defs to get meaningful locations from mtrace() */
+#if defined(HAVE_MCHECK_H) && defined(__GNUC__)
#define vmefail() (fprintf(stderr, "virtual memory exhausted.\n"), exit(EXIT_FAILURE), NULL)
+#define xmalloc(_size) (malloc(_size) ? : vmefail())
+#define xcalloc(_nmemb, _size) (calloc((_nmemb), (_size)) ? : vmefail())
+#define xrealloc(_ptr, _size) (realloc((_ptr), (_size)) ? : vmefail())
#define xstrdup(_str) (strcpy((malloc(strlen(_str)+1) ? : vmefail()), (_str)))
#else
+#define xmalloc(_size) malloc(_size)
+#define xcalloc(_nmemb, _size) calloc((_nmemb), (_size))
+#define xrealloc(_ptr, _size) realloc((_ptr), (_size))
#define xstrdup(_str) strdup(_str)
-#endif /* HAVE_MCHECK_H && defined(__GNUC__) */
+#endif /* defined(HAVE_MCHECK_H) && defined(__GNUC__) */
+#if defined(HAVE___SECURE_GETENV) && !defined(__LCLINT__)
+#define getenv(_s) __secure_getenv(_s)
+#endif
+
+#if !defined(__GNUC__) && !defined(__attribute__)
+#define __attribute__(x)
+#endif
+#define UNUSED(x) x __attribute__((__unused__))
#include "popt.h"
diff --git a/third_party/popt/wscript b/third_party/popt/wscript
index e81572c..eae8602 100644
--- a/third_party/popt/wscript
+++ b/third_party/popt/wscript
@@ -4,6 +4,7 @@ import Options
def configure(conf):
conf.CHECK_HEADERS('float.h')
+ conf.CHECK_FUNCS('stpcpy')
if conf.CHECK_POPT():
conf.define('USING_SYSTEM_POPT', 1)
@@ -12,8 +13,9 @@ def build(bld):
if bld.CONFIG_SET('USING_SYSTEM_POPT'):
return
+ cflags = '-DPACKAGE="popt" -DPOPT_SYSCONFDIR="%s"' % bld.env.SYSCONFDIR
bld.SAMBA_LIBRARY('popt',
- source='findme.c popt.c poptconfig.c popthelp.c poptparse.c',
- cflags='-DDBL_EPSILON=__DBL_EPSILON__',
+ source='popt.c poptconfig.c popthelp.c poptint.c poptparse.c',
+ cflags=cflags,
allow_warnings=True,
private_library=True)
diff --git a/tools/ldbdump.c b/tools/ldbdump.c
index c399b59..4697661 100644
--- a/tools/ldbdump.c
+++ b/tools/ldbdump.c
@@ -27,6 +27,11 @@
#include <ldb.h>
#include <ldb_private.h>
+#ifdef HAVE_LMDB
+#include <lmdb.h>
+#endif /* ifdef HAVE_LMDB */
+
+
static struct ldb_context *ldb;
bool show_index = false;
bool validate_contents = false;
@@ -166,6 +171,116 @@ static int dump_tdb(const char *fname, struct ldb_dn *dn, bool emergency)
return tdb_traverse(tdb, traverse_fn, dn) == -1 ? 1 : 0;
}
+#ifdef HAVE_LMDB
+static int dump_lmdb(const char *fname, struct ldb_dn *dn, bool emergency)
+{
+ int ret;
+ struct MDB_env *env = NULL;
+ struct MDB_txn *txn = NULL;
+ MDB_dbi dbi;
+ struct MDB_cursor *cursor = NULL;
+ struct MDB_val key;
+ struct MDB_val data;
+
+ ret = mdb_env_create(&env);
+ if (ret != 0) {
+ fprintf(stderr,
+ "Could not create MDB environment: (%d) %s\n",
+ ret,
+ mdb_strerror(ret));
+ goto close_env;
+ }
+
+ ret = mdb_env_open(env,
+ fname,
+ MDB_NOSUBDIR|MDB_NOTLS|MDB_RDONLY,
+ 0600);
+ if (ret != 0) {
+ fprintf(stderr,
+ "Could not open environment for %s: (%d) %s\n",
+ fname,
+ ret,
+ mdb_strerror(ret));
+ goto close_env;
+ }
+
+ ret = mdb_txn_begin(env, NULL, MDB_RDONLY, &txn);
+ if (ret != 0) {
+ fprintf(stderr,
+ "Could not start transaction: (%d) %s\n",
+ ret,
+ mdb_strerror(ret));
+ goto close_env;
+ }
+
+ ret = mdb_dbi_open(txn, NULL, 0, &dbi);
+ if (ret != 0) {
+ fprintf(stderr,
+ "Could not open database: (%d) %s\n",
+ ret,
+ mdb_strerror(ret));
+ goto close_txn;
+ }
+
+ ret = mdb_cursor_open(txn, dbi, &cursor);
+ if (ret != 0) {
+ fprintf(stderr,
+ "Could not open cursor: (%d) %s\n",
+ ret,
+ mdb_strerror(ret));
+ goto close_txn;
+ }
+
+ ret = mdb_cursor_get(cursor, &key, &data, MDB_FIRST);
+ if (ret != 0 && ret != MDB_NOTFOUND) {
+ fprintf(stderr,
+ "Could not find first record: (%d) %s\n",
+ ret,
+ mdb_strerror(ret));
+ goto close_cursor;
+ }
+ while (ret != MDB_NOTFOUND) {
+ struct TDB_DATA tkey = {
+ .dptr = key.mv_data,
+ .dsize = key.mv_size
+ };
+ struct TDB_DATA tdata = {
+ .dptr = data.mv_data,
+ .dsize = data.mv_size
+ };
+ traverse_fn(NULL, tkey, tdata, dn);
+ ret = mdb_cursor_get(cursor, &key, &data, MDB_NEXT);
+ if (ret != 0 && ret != MDB_NOTFOUND) {
+ fprintf(stderr,
+ "Could not read next record: (%d) %s\n",
+ ret,
+ mdb_strerror(ret));
+ goto close_cursor;
+ }
+ }
+ ret = 0;
+
+close_cursor:
+ mdb_cursor_close(cursor);
+close_txn:
+ mdb_txn_commit(txn);
+close_env:
+ mdb_env_close(env);
+
+ if (ret != 0) {
+ return 1;
+ }
+ return 0;
+
+}
+#else
+static int dump_lmdb(const char *fname, struct ldb_dn *dn, bool emergency)
+{
+ /* not built with lmdb support */
+ return 1;
+}
+#endif /* #ifdef HAVE_LMDB */
+
static void usage( void)
{
printf( "Usage: ldbdump [options] <filename>\n\n");
@@ -229,5 +344,14 @@ static void usage( void)
fname = argv[optind];
- return dump_tdb(fname, dn, emergency);
+ rc = dump_lmdb(fname, dn, emergency);
+ if (rc != 0) {
+ rc = dump_tdb(fname, dn, emergency);
+ if (rc != 0) {
+ fprintf(stderr, "Failed to open %s\n", fname);
+ return 1;
+ }
+ }
+ return 0;
+
}
diff --git a/wscript b/wscript
index 75ccff5..f5cb1e0 100644
--- a/wscript
+++ b/wscript
@@ -1,11 +1,12 @@
#!/usr/bin/env python
APPNAME = 'ldb'
-VERSION = '1.3.3'
+VERSION = '1.4.0'
blddir = 'bin'
import sys, os
+import Logs
# find the buildtools directory
srcdir = '.'
@@ -13,7 +14,7 @@ while not os.path.exists(srcdir+'/buildtools') and len(srcdir.split('/')) < 5:
srcdir = srcdir + '/..'
sys.path.insert(0, srcdir + '/buildtools/wafsamba')
-import wafsamba, samba_dist, Utils
+import wafsamba, samba_dist, Utils, Options
samba_dist.DIST_DIRS('''lib/ldb:. lib/replace:lib/replace lib/talloc:lib/talloc
lib/tdb:lib/tdb lib/tdb:lib/tdb lib/tevent:lib/tevent
@@ -31,6 +32,11 @@ def set_options(opt):
opt.RECURSE('lib/replace')
opt.tool_options('python') # options for disabling pyc or pyo compilation
+ opt.add_option('--without-ldb-lmdb',
+ help='disable new LMDB backend for LDB',
+ action='store_true', dest='without_ldb_lmdb', default=False)
+
+
def configure(conf):
conf.RECURSE('lib/tdb')
conf.RECURSE('lib/tevent')
@@ -92,6 +98,41 @@ def configure(conf):
implied_deps='replace talloc tdb tevent'):
conf.define('USING_SYSTEM_LDB', 1)
+ if not conf.CHECK_CODE('return !(sizeof(size_t) >= 8)',
+ "HAVE_64_BIT_SIZE_T_FOR_LMDB",
+ execute=True,
+ msg='Checking for a 64-bit host to '
+ 'support lmdb'):
+ Logs.warn("--without-ldb-lmdb implied as this "
+ "host is not 64-bit")
+
+ if not conf.env.standalone_ldb and \
+ not Options.options.without_ad_dc and \
+ conf.CONFIG_GET('ENABLE_SELFTEST'):
+ Logs.warn("NOTE: Some AD DC parts of selftest will fail")
+
+ conf.env.REQUIRE_LMDB = False
+ else:
+ if conf.env.standalone_ldb:
+ if Options.options.without_ldb_lmdb:
+ conf.env.REQUIRE_LMDB = False
+ else:
+ conf.env.REQUIRE_LMDB = True
+ elif Options.options.without_ad_dc:
+ conf.env.REQUIRE_LMDB = False
+ else:
+ if Options.options.without_ldb_lmdb:
+ if not Options.options.without_ad_dc and \
+ conf.CONFIG_GET('ENABLE_SELFTEST'):
+ raise Utils.WafError('--without-ldb-lmdb conflicts '
+ 'with --enable-selftest while '
+ 'building the AD DC')
+
+ conf.env.REQUIRE_LMDB = False
+ else:
+ conf.env.REQUIRE_LMDB = True
+
+
if conf.CONFIG_SET('USING_SYSTEM_LDB'):
v = VERSION.split('.')
conf.DEFINE('EXPECTED_SYSTEM_LDB_VERSION_MAJOR', int(v[0]))
@@ -110,6 +151,39 @@ def configure(conf):
if not sys.platform.startswith("openbsd"):
conf.ADD_LDFLAGS('-Wl,-no-undefined', testflags=True)
+ # if lmdb support is enabled then we require lmdb
+ # is present, build the mdb back end and enable lmdb support in
+ # the tools.
+ if conf.env.REQUIRE_LMDB and \
+ not conf.CONFIG_SET('USING_SYSTEM_LDB'):
+ if not conf.CHECK_CFG(package='lmdb',
+ args='"lmdb >= 0.9.16" --cflags --libs',
+ msg='Checking for lmdb >= 0.9.16',
+ mandatory=False):
+ if not conf.CHECK_CODE('''
+ #if MDB_VERSION_MAJOR == 0 \
+ && MDB_VERSION_MINOR <= 9 \
+ && MDB_VERSION_PATCH < 16
+ #error LMDB too old
+ #endif
+ ''',
+ 'HAVE_GOOD_LMDB_VERSION',
+ headers='lmdb.h',
+ msg='Checking for lmdb >= 0.9.16 via header check'):
+
+ if conf.env.standalone_ldb:
+ raise Utils.WafError('ldb build (unless --without-ldb-lmdb) '
+ 'requires '
+ 'lmdb 0.9.16 or later')
+ elif not Options.options.without_ad_dc:
+ raise Utils.WafError('Samba AD DC and --enable-selftest '
+ 'requires '
+ 'lmdb 0.9.16 or later')
+
+ if conf.CHECK_FUNCS_IN('mdb_env_create', 'lmdb', headers='lmdb.h'):
+ conf.DEFINE('HAVE_LMDB', '1')
+
+
conf.DEFINE('HAVE_CONFIG_H', 1, add_to_cflags=True)
conf.SAMBA_CONFIG_H()
@@ -127,9 +201,15 @@ def build(bld):
bld.RECURSE('lib/tdb')
if bld.env.standalone_ldb:
+ if not 'PACKAGE_VERSION' in bld.env:
+ bld.env.PACKAGE_VERSION = VERSION
+ bld.env.PKGCONFIGDIR = '${LIBDIR}/pkgconfig'
private_library = False
else:
private_library = True
+ # we're not currently linking against the ldap libs, but ldb.pc.in
+ # has @LDAP_LIBS@
+ bld.env.LDAP_LIBS = ''
LDB_MAP_SRC = bld.SUBDIR('ldb_map',
'ldb_map.c ldb_map_inbound.c ldb_map_outbound.c')
@@ -150,13 +230,6 @@ def build(bld):
if bld.PYTHON_BUILD_IS_ENABLED():
if not bld.CONFIG_SET('USING_SYSTEM_PYLDB_UTIL'):
for env in bld.gen_python_environments(['PKGCONFIGDIR']):
- # we're not currently linking against the ldap libs, but ldb.pc.in
- # has @LDAP_LIBS@
- bld.env.LDAP_LIBS = ''
-
- if not 'PACKAGE_VERSION' in bld.env:
- bld.env.PACKAGE_VERSION = VERSION
- bld.env.PKGCONFIGDIR = '${LIBDIR}/pkgconfig'
name = bld.pyembed_libname('pyldb-util')
bld.SAMBA_LIBRARY(name,
@@ -307,12 +380,47 @@ def build(bld):
bld.SAMBA_MODULE('ldb_tdb',
bld.SUBDIR('ldb_tdb',
+ '''ldb_tdb_init.c'''),
+ init_function='ldb_tdb_init',
+ module_init_name='ldb_init_module',
+ internal_module=False,
+ deps='tdb ldb ldb_key_value',
+ subsystem='ldb')
+
+ bld.SAMBA_LIBRARY('ldb_key_value',
+ bld.SUBDIR('ldb_tdb',
'''ldb_tdb.c ldb_search.c ldb_index.c
ldb_cache.c ldb_tdb_wrap.c'''),
- init_function='ldb_tdb_init',
+ private_library=True,
+ deps='tdb ldb')
+
+ if bld.CONFIG_SET('HAVE_LMDB'):
+ bld.SAMBA_MODULE('ldb_mdb',
+ bld.SUBDIR('ldb_mdb',
+ '''ldb_mdb_init.c'''),
+ init_function='ldb_mdb_init',
+ module_init_name='ldb_init_module',
+ internal_module=False,
+ deps='ldb ldb_key_value ldb_mdb_int',
+ subsystem='ldb')
+
+ bld.SAMBA_LIBRARY('ldb_mdb_int',
+ bld.SUBDIR('ldb_mdb',
+ '''ldb_mdb.c '''),
+ private_library=True,
+ deps='ldb lmdb ldb_key_value')
+ lmdb_deps = ' ldb_mdb_int'
+ else:
+ lmdb_deps = ''
+
+
+ bld.SAMBA_MODULE('ldb_ldb',
+ bld.SUBDIR('ldb_ldb',
+ '''ldb_ldb.c'''),
+ init_function='ldb_ldb_init',
module_init_name='ldb_init_module',
internal_module=False,
- deps='tdb ldb',
+ deps='ldb ldb_key_value' + lmdb_deps,
subsystem='ldb')
# have a separate subsystem for common/ldb.c, so it can rebuild
@@ -332,8 +440,14 @@ def build(bld):
bld.SAMBA_BINARY('ldbtest', 'tools/ldbtest.c', deps='ldb-cmdline ldb',
install=False)
+ if bld.CONFIG_SET('HAVE_LMDB'):
+ lmdb_deps = ' lmdb'
+ else:
+ lmdb_deps = ''
# ldbdump doesn't get installed
- bld.SAMBA_BINARY('ldbdump', 'tools/ldbdump.c', deps='ldb-cmdline ldb',
+ bld.SAMBA_BINARY('ldbdump',
+ 'tools/ldbdump.c',
+ deps='ldb-cmdline ldb' + lmdb_deps,
install=False)
bld.SAMBA_LIBRARY('ldb-cmdline',
@@ -347,17 +461,67 @@ def build(bld):
deps='cmocka ldb',
install=False)
+ bld.SAMBA_BINARY('ldb_tdb_guid_mod_op_test',
+ source='tests/ldb_mod_op_test.c',
+ cflags='-DTEST_BE=\"tdb\" -DGUID_IDX=1',
+ deps='cmocka ldb',
+ install=False)
+
+ bld.SAMBA_BINARY('ldb_tdb_kv_ops_test',
+ source='tests/ldb_kv_ops_test.c',
+ cflags='-DTEST_BE=\"tdb\"',
+ deps='cmocka ldb',
+ install=False)
+
+ bld.SAMBA_BINARY('ldb_tdb_test',
+ source='tests/ldb_tdb_test.c',
+ deps='cmocka ldb',
+ install=False)
+
bld.SAMBA_BINARY('ldb_msg_test',
source='tests/ldb_msg.c',
deps='cmocka ldb',
install=False)
+ bld.SAMBA_BINARY('test_ldb_qsort',
+ source='tests/test_ldb_qsort.c',
+ deps='cmocka ldb',
+ install=False)
+
+ if bld.CONFIG_SET('HAVE_LMDB'):
+ bld.SAMBA_BINARY('ldb_mdb_mod_op_test',
+ source='tests/ldb_mod_op_test.c',
+ cflags='-DTEST_BE=\"mdb\" -DGUID_IDX=1 '
+ + '-DTEST_LMDB=1',
+ deps='cmocka ldb lmdb',
+ install=False)
+
+ bld.SAMBA_BINARY('ldb_lmdb_test',
+ source='tests/ldb_lmdb_test.c',
+ deps='cmocka ldb',
+ install=False)
+
+ bld.SAMBA_BINARY('ldb_lmdb_size_test',
+ source='tests/ldb_lmdb_size_test.c',
+ deps='cmocka ldb',
+ install=False)
+
+ bld.SAMBA_BINARY('ldb_mdb_kv_ops_test',
+ source='tests/ldb_kv_ops_test.c',
+ cflags='-DTEST_BE=\"mdb\"',
+ deps='cmocka ldb',
+ install=False)
+
def test(ctx):
'''run ldb testsuite'''
import Utils, samba_utils, shutil
env = samba_utils.LOAD_ENVIRONMENT()
ctx.env = env
+ if not env.HAVE_LMDB:
+ raise Utils.WafError('make test called, but ldb was built '
+ '--without-ldb-lmdb')
+
test_prefix = "%s/st" % (Utils.g_module.blddir)
shutil.rmtree(test_prefix, ignore_errors=True)
os.makedirs(test_prefix)
@@ -379,8 +543,21 @@ def test(ctx):
print("Python testsuite returned %d" % pyret)
cmocka_ret = 0
- for test_exe in ['ldb_tdb_mod_op_test',
- 'ldb_msg_test']:
+ test_exes = ['test_ldb_qsort',
+ 'ldb_msg_test',
+ 'ldb_tdb_mod_op_test',
+ 'ldb_tdb_guid_mod_op_test',
+ 'ldb_msg_test',
+ 'ldb_tdb_kv_ops_test',
+ 'ldb_tdb_test',
+ 'ldb_mdb_mod_op_test',
+ 'ldb_lmdb_test',
+ # we don't want to run ldb_lmdb_size_test (which proves we can
+ # fit > 4G of data into the DB), it would fill up the disk on
+ # many of our test instances
+ 'ldb_mdb_kv_ops_test']
+
+ for test_exe in test_exes:
cmd = os.path.join(Utils.g_module.blddir, test_exe)
cmocka_ret = cmocka_ret or samba_utils.RUN_COMMAND(cmd)