mirror of
https://github.com/samba-team/samba.git
synced 2025-02-01 05:47:28 +03:00
ctdb-common: Add db_hash abstraction
A hash table implemented using in-memory tdb backend. Signed-off-by: Amitay Isaacs <amitay@gmail.com> Reviewed-by: Martin Schwenke <martin@meltin.net>
This commit is contained in:
parent
acf5ebfa90
commit
e5592f9fc0
268
ctdb/common/db_hash.c
Normal file
268
ctdb/common/db_hash.c
Normal file
@ -0,0 +1,268 @@
|
||||
/*
|
||||
Using tdb as a hash table
|
||||
|
||||
Copyright (C) Amitay Isaacs 2015
|
||||
|
||||
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 "replace.h"
|
||||
#include "system/filesys.h"
|
||||
|
||||
#include <talloc.h>
|
||||
#include <tdb.h>
|
||||
|
||||
#include "common/db_hash.h"
|
||||
|
||||
struct db_hash_context {
|
||||
struct tdb_context *db;
|
||||
};
|
||||
|
||||
|
||||
static int db_hash_destructor(struct db_hash_context *dh)
|
||||
{
|
||||
if (dh->db != NULL) {
|
||||
tdb_close(dh->db);
|
||||
dh->db = NULL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int db_hash_init(TALLOC_CTX *mem_ctx, const char *name, int hash_size,
|
||||
enum db_hash_type type, struct db_hash_context **result)
|
||||
{
|
||||
struct db_hash_context *dh;
|
||||
int tdb_flags = TDB_INTERNAL | TDB_DISALLOW_NESTING;
|
||||
|
||||
dh = talloc_zero(mem_ctx, struct db_hash_context);
|
||||
if (dh == NULL) {
|
||||
return ENOMEM;
|
||||
}
|
||||
|
||||
if (type == DB_HASH_COMPLEX) {
|
||||
tdb_flags |= TDB_INCOMPATIBLE_HASH;
|
||||
}
|
||||
|
||||
dh->db = tdb_open(name, hash_size, tdb_flags, O_RDWR|O_CREAT, 0);
|
||||
if (dh->db == NULL) {
|
||||
talloc_free(dh);
|
||||
return ENOMEM;
|
||||
}
|
||||
|
||||
talloc_set_destructor(dh, db_hash_destructor);
|
||||
*result = dh;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int db_hash_map_tdb_error(struct db_hash_context *dh)
|
||||
{
|
||||
enum TDB_ERROR tdb_err;
|
||||
int ret;
|
||||
|
||||
tdb_err = tdb_error(dh->db);
|
||||
switch (tdb_err) {
|
||||
case TDB_SUCCESS:
|
||||
ret = 0; break;
|
||||
case TDB_ERR_OOM:
|
||||
ret = ENOMEM; break;
|
||||
case TDB_ERR_EXISTS:
|
||||
ret = EEXIST; break;
|
||||
case TDB_ERR_NOEXIST:
|
||||
ret = ENOENT; break;
|
||||
case TDB_ERR_EINVAL:
|
||||
ret = EINVAL; break;
|
||||
default:
|
||||
ret = EIO; break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int db_hash_insert(struct db_hash_context *dh, uint8_t *keybuf, size_t keylen,
|
||||
uint8_t *databuf, size_t datalen)
|
||||
{
|
||||
TDB_DATA key, data;
|
||||
int ret;
|
||||
|
||||
if (dh == NULL) {
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
key.dptr = keybuf;
|
||||
key.dsize = keylen;
|
||||
|
||||
data.dptr = databuf;
|
||||
data.dsize = datalen;
|
||||
|
||||
ret = tdb_store(dh->db, key, data, TDB_INSERT);
|
||||
if (ret != 0) {
|
||||
ret = db_hash_map_tdb_error(dh);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int db_hash_add(struct db_hash_context *dh, uint8_t *keybuf, size_t keylen,
|
||||
uint8_t *databuf, size_t datalen)
|
||||
{
|
||||
TDB_DATA key, data;
|
||||
int ret;
|
||||
|
||||
if (dh == NULL) {
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
key.dptr = keybuf;
|
||||
key.dsize = keylen;
|
||||
|
||||
data.dptr = databuf;
|
||||
data.dsize = datalen;
|
||||
|
||||
ret = tdb_store(dh->db, key, data, TDB_REPLACE);
|
||||
if (ret != 0) {
|
||||
ret = db_hash_map_tdb_error(dh);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int db_hash_delete(struct db_hash_context *dh, uint8_t *keybuf, size_t keylen)
|
||||
{
|
||||
TDB_DATA key;
|
||||
int ret;
|
||||
|
||||
key.dptr = keybuf;
|
||||
key.dsize = keylen;
|
||||
|
||||
if (dh == NULL) {
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
ret = tdb_delete(dh->db, key);
|
||||
if (ret != 0) {
|
||||
ret = db_hash_map_tdb_error(dh);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct db_hash_fetch_state {
|
||||
db_hash_record_parser_fn parser;
|
||||
void *private_data;
|
||||
};
|
||||
|
||||
static int db_hash_fetch_parser(TDB_DATA key, TDB_DATA data, void *private_data)
|
||||
{
|
||||
struct db_hash_fetch_state *state =
|
||||
(struct db_hash_fetch_state *)private_data;
|
||||
int ret;
|
||||
|
||||
ret = state->parser(key.dptr, key.dsize, data.dptr, data.dsize,
|
||||
state->private_data);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int db_hash_fetch(struct db_hash_context *dh, uint8_t *keybuf, size_t keylen,
|
||||
db_hash_record_parser_fn parser, void *private_data)
|
||||
{
|
||||
struct db_hash_fetch_state state;
|
||||
TDB_DATA key;
|
||||
int ret;
|
||||
|
||||
if (dh == NULL || parser == NULL) {
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
state.parser = parser;
|
||||
state.private_data = private_data;
|
||||
|
||||
key.dptr = keybuf;
|
||||
key.dsize = keylen;
|
||||
|
||||
ret = tdb_parse_record(dh->db, key, db_hash_fetch_parser, &state);
|
||||
if (ret == -1) {
|
||||
return ENOENT;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int db_hash_exists(struct db_hash_context *dh, uint8_t *keybuf, size_t keylen)
|
||||
{
|
||||
TDB_DATA key;
|
||||
int ret;
|
||||
|
||||
if (dh == NULL) {
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
key.dptr = keybuf;
|
||||
key.dsize = keylen;
|
||||
|
||||
ret = tdb_exists(dh->db, key);
|
||||
if (ret == 1) {
|
||||
/* Key found */
|
||||
ret = 0;
|
||||
} else {
|
||||
ret = db_hash_map_tdb_error(dh);
|
||||
if (ret == 0) {
|
||||
ret = ENOENT;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct db_hash_traverse_state {
|
||||
db_hash_record_parser_fn parser;
|
||||
void *private_data;
|
||||
};
|
||||
|
||||
static int db_hash_traverse_parser(struct tdb_context *tdb,
|
||||
TDB_DATA key, TDB_DATA data,
|
||||
void *private_data)
|
||||
{
|
||||
struct db_hash_traverse_state *state =
|
||||
(struct db_hash_traverse_state *)private_data;
|
||||
|
||||
return state->parser(key.dptr, key.dsize, data.dptr, data.dsize,
|
||||
state->private_data);
|
||||
}
|
||||
|
||||
int db_hash_traverse(struct db_hash_context *dh,
|
||||
db_hash_record_parser_fn parser, void *private_data,
|
||||
int *count)
|
||||
{
|
||||
struct db_hash_traverse_state state;
|
||||
int ret;
|
||||
|
||||
if (dh == NULL) {
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
/* Special case, for counting records */
|
||||
if (parser == NULL) {
|
||||
ret = tdb_traverse_read(dh->db, NULL, NULL);
|
||||
} else {
|
||||
state.parser = parser;
|
||||
state.private_data = private_data;
|
||||
|
||||
ret = tdb_traverse_read(dh->db, db_hash_traverse_parser, &state);
|
||||
}
|
||||
|
||||
if (ret == -1) {
|
||||
ret = db_hash_map_tdb_error(dh);
|
||||
} else {
|
||||
if (count != NULL) {
|
||||
*count = ret;
|
||||
}
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
159
ctdb/common/db_hash.h
Normal file
159
ctdb/common/db_hash.h
Normal file
@ -0,0 +1,159 @@
|
||||
/*
|
||||
Using tdb as a hash table
|
||||
|
||||
Copyright (C) Amitay Isaacs 2015
|
||||
|
||||
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/>.
|
||||
*/
|
||||
|
||||
#ifndef __CTDB_DB_HASH_H__
|
||||
#define __CTDB_DB_HASH_H__
|
||||
|
||||
#include <talloc.h>
|
||||
#include <tdb.h>
|
||||
|
||||
/**
|
||||
* @file db_hash.h
|
||||
*
|
||||
* @brief Use tdb database as a hash table
|
||||
*
|
||||
* This uses in-memory tdb databases to create a fixed sized hash table.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Hash type to indicate the hashing function to use.
|
||||
*
|
||||
* DB_HASH_SIMPLE uses default hashing function
|
||||
* DB_HASH_COMPLEX uses jenkins hashing function
|
||||
*/
|
||||
enum db_hash_type {
|
||||
DB_HASH_SIMPLE,
|
||||
DB_HASH_COMPLEX,
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Parser callback function called when fetching a record
|
||||
*
|
||||
* This function is called when fetching a record. This function should
|
||||
* not modify key and data arguments.
|
||||
*
|
||||
* The function should return 0 on success and errno on error.
|
||||
*/
|
||||
typedef int (*db_hash_record_parser_fn)(uint8_t *keybuf, size_t keylen,
|
||||
uint8_t *databuf, size_t datalen,
|
||||
void *private_data);
|
||||
|
||||
/**
|
||||
* @brief Abstract structure representing tdb hash table
|
||||
*/
|
||||
struct db_hash_context;
|
||||
|
||||
/**
|
||||
* @brief Initialize tdb hash table
|
||||
*
|
||||
* This returns a new tdb hash table context which is a talloc context. Freeing
|
||||
* this context will free all the memory associated with the hash table.
|
||||
*
|
||||
* @param[in] mem_ctx Talloc memory context
|
||||
* @param[in] name The name for the hash table
|
||||
* @param[in] hash_size The size of the hash table
|
||||
* @param[in] type The type of hashing function to use
|
||||
* @param[out] result The new db_hash_context structure
|
||||
* @return 0 on success, errno on failure
|
||||
*/
|
||||
int db_hash_init(TALLOC_CTX *mem_ctx, const char *name, int hash_size,
|
||||
enum db_hash_type type, struct db_hash_context **result);
|
||||
|
||||
/**
|
||||
* @brief Insert a record into the hash table
|
||||
*
|
||||
* The key and data can be any binary data. Insert only if the record does not
|
||||
* exist. If the record already exists, return error.
|
||||
*
|
||||
* @param[in] dh The tdb hash table context
|
||||
* @param[in] keybuf The key buffer
|
||||
* @param[in] keylen The key length
|
||||
* @param[in] databuf The data buffer
|
||||
* @param[in] datalen The data length
|
||||
* @return 0 on success, errno on failure
|
||||
*/
|
||||
int db_hash_insert(struct db_hash_context *dh, uint8_t *keybuf, size_t keylen,
|
||||
uint8_t *databuf, size_t datalen);
|
||||
|
||||
/**
|
||||
* @brief Add a record into the hash table
|
||||
*
|
||||
* The key and data can be any binary data. If the record does not exist,
|
||||
* insert the record. If the record already exists, replace the record.
|
||||
*
|
||||
* @param[in] dh The tdb hash table context
|
||||
* @param[in] keybuf The key buffer
|
||||
* @param[in] keylen The key length
|
||||
* @param[in] databuf The data buffer
|
||||
* @param[in] datalen The data length
|
||||
* @return 0 on success, errno on failure
|
||||
*/
|
||||
int db_hash_add(struct db_hash_context *dh, uint8_t *keybuf, size_t keylen,
|
||||
uint8_t *databuf, size_t datalen);
|
||||
/**
|
||||
* @brief Delete a record from the hash table
|
||||
*
|
||||
* @param[in] dh The tdb hash table context
|
||||
* @param[in] keybuf The key buffer
|
||||
* @param[in] keylen The key length
|
||||
* @return 0 on success, errno on failure
|
||||
*/
|
||||
int db_hash_delete(struct db_hash_context *dh, uint8_t *keybuf, size_t keylen);
|
||||
|
||||
/**
|
||||
* @brief Fetch a record from the hash table
|
||||
*
|
||||
* The key and data can be any binary data.
|
||||
*
|
||||
* @param[in] dh The tdb hash table context
|
||||
* @param[in] keybuf The key buffer
|
||||
* @param[in] keylen The key length
|
||||
* @param[in] parser Function called when the matching record is found
|
||||
* @param[in] private_data Private data to parser function
|
||||
* @return 0 on success, errno on failure
|
||||
*/
|
||||
int db_hash_fetch(struct db_hash_context *dh, uint8_t *keybuf, size_t keylen,
|
||||
db_hash_record_parser_fn parser, void *private_data);
|
||||
|
||||
/**
|
||||
* @brief Check if a record exists in the hash table
|
||||
*
|
||||
* @param[in] dh The tdb hash table context
|
||||
* @param[in] keybuf The key buffer
|
||||
* @param[in] keylen The key length
|
||||
* @return 0 if the record exists, errno on failure
|
||||
*/
|
||||
int db_hash_exists(struct db_hash_context *dh, uint8_t *keybuf, size_t keylen);
|
||||
|
||||
/**
|
||||
* @brief Traverse the database
|
||||
*
|
||||
* The parser function should non-zero value to stop traverse.
|
||||
*
|
||||
* @param[in] dh The tdb hash table context
|
||||
* @param[in] parser Function called for each record
|
||||
* @param[in] private_data Private data to parser function
|
||||
* @param[out] count Number of records traversed
|
||||
* @return 0 on success, errno on failure
|
||||
*/
|
||||
int db_hash_traverse(struct db_hash_context *dh,
|
||||
db_hash_record_parser_fn parser, void *private_data,
|
||||
int *count);
|
||||
|
||||
#endif /* __CTDB_DB_HASH_H__ */
|
7
ctdb/tests/cunit/db_hash_test_001.sh
Executable file
7
ctdb/tests/cunit/db_hash_test_001.sh
Executable file
@ -0,0 +1,7 @@
|
||||
#!/bin/sh
|
||||
|
||||
. "${TEST_SCRIPTS_DIR}/unit.sh"
|
||||
|
||||
ok_null
|
||||
|
||||
unit_test db_hash_test
|
@ -255,7 +255,7 @@ export TEST_SCRIPTS_DIR="${test_dir}/scripts"
|
||||
# If no tests specified then run some defaults
|
||||
if [ -z "$1" ] ; then
|
||||
if [ -n "$TEST_LOCAL_DAEMONS" ] ; then
|
||||
set -- onnode takeover tool eventscripts simple
|
||||
set -- onnode takeover tool eventscripts cunit simple
|
||||
else
|
||||
set -- simple complex
|
||||
fi
|
||||
|
102
ctdb/tests/src/db_hash_test.c
Normal file
102
ctdb/tests/src/db_hash_test.c
Normal file
@ -0,0 +1,102 @@
|
||||
/*
|
||||
db_hash tests
|
||||
|
||||
Copyright (C) Amitay Isaacs 2015
|
||||
|
||||
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 "replace.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include "common/db_hash.c"
|
||||
|
||||
static void do_test(enum db_hash_type type)
|
||||
{
|
||||
struct db_hash_context *dh;
|
||||
TALLOC_CTX *mem_ctx = talloc_new(NULL);
|
||||
TALLOC_CTX *tmp_ctx = talloc_new(NULL);
|
||||
uint8_t *key = (uint8_t *)talloc_strdup(tmp_ctx, "This is a long key");
|
||||
uint8_t *value = (uint8_t *)talloc_strdup(tmp_ctx, "This is a long value");
|
||||
int ret;
|
||||
|
||||
ret = db_hash_init(mem_ctx, "foobar", 1024, type, &dh);
|
||||
assert(ret == 0);
|
||||
|
||||
ret = db_hash_insert(dh, key, sizeof(key), value, sizeof(value));
|
||||
assert(ret == 0);
|
||||
|
||||
ret = db_hash_exists(dh, key, sizeof(key));
|
||||
assert(ret == 0);
|
||||
|
||||
ret = db_hash_insert(dh, key, sizeof(key), value, sizeof(value));
|
||||
assert(ret == EEXIST);
|
||||
|
||||
ret = db_hash_delete(dh, key, sizeof(key));
|
||||
assert(ret == 0);
|
||||
|
||||
ret = db_hash_exists(dh, key, sizeof(key));
|
||||
assert(ret == ENOENT);
|
||||
|
||||
ret = db_hash_delete(dh, key, sizeof(key));
|
||||
assert(ret == ENOENT);
|
||||
|
||||
ret = db_hash_add(dh, key, sizeof(key), key, sizeof(key));
|
||||
assert(ret == 0);
|
||||
|
||||
ret = db_hash_add(dh, key, sizeof(key), value, sizeof(value));
|
||||
assert(ret == 0);
|
||||
|
||||
talloc_free(dh);
|
||||
ret = talloc_get_size(mem_ctx);
|
||||
assert(ret == 0);
|
||||
|
||||
talloc_free(mem_ctx);
|
||||
}
|
||||
|
||||
static void do_traverse_test(enum db_hash_type type)
|
||||
{
|
||||
struct db_hash_context *dh;
|
||||
TALLOC_CTX *mem_ctx = talloc_new(NULL);
|
||||
char key[] = "keyXXXX";
|
||||
char value[] = "This is some test value";
|
||||
int count, ret, i;
|
||||
|
||||
ret = db_hash_init(mem_ctx, "foobar", 1024, type, &dh);
|
||||
assert(ret == 0);
|
||||
|
||||
for (i=0; i<2000; i++) {
|
||||
sprintf(key, "key%04d", i);
|
||||
ret = db_hash_insert(dh, (uint8_t *)key, sizeof(key),
|
||||
(uint8_t *)value, sizeof(value));
|
||||
assert(ret == 0);
|
||||
}
|
||||
|
||||
ret = db_hash_traverse(dh, NULL, NULL, &count);
|
||||
assert(ret == 0);
|
||||
assert(count == 2000);
|
||||
|
||||
talloc_free(dh);
|
||||
talloc_free(mem_ctx);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
do_test(DB_HASH_SIMPLE);
|
||||
do_test(DB_HASH_COMPLEX);
|
||||
do_traverse_test(DB_HASH_SIMPLE);
|
||||
do_traverse_test(DB_HASH_COMPLEX);
|
||||
return 0;
|
||||
}
|
19
ctdb/wscript
19
ctdb/wscript
@ -331,6 +331,11 @@ def build(bld):
|
||||
includes='include include/internal',
|
||||
deps='replace tevent tdb')
|
||||
|
||||
bld.SAMBA_SUBSYSTEM('ctdb-util',
|
||||
source=bld.SUBDIR('common',
|
||||
'''db_hash.c'''),
|
||||
deps='replace talloc tevent tdb')
|
||||
|
||||
bld.SAMBA_SUBSYSTEM('ctdb-client',
|
||||
source=bld.SUBDIR('client', 'ctdb_client.c'),
|
||||
includes='include include/internal',
|
||||
@ -565,6 +570,19 @@ def build(bld):
|
||||
t.env.VERSION = VERSION
|
||||
bld.INSTALL_FILES('${LIBDIR}/pkgconfig', 'ctdb.pc')
|
||||
|
||||
# Unit tests
|
||||
ctdb_unit_tests = [
|
||||
'db_hash_test',
|
||||
]
|
||||
|
||||
for target in ctdb_unit_tests:
|
||||
src = 'tests/src/' + target + '.c'
|
||||
|
||||
bld.SAMBA_BINARY(target,
|
||||
source=src,
|
||||
deps='talloc tevent tdb',
|
||||
install_path='${CTDB_TEST_LIBDIR}')
|
||||
|
||||
# Test binaries
|
||||
ctdb_tests = [
|
||||
'rb_test',
|
||||
@ -626,6 +644,7 @@ def build(bld):
|
||||
|
||||
test_subdirs = [
|
||||
'complex',
|
||||
'cunit',
|
||||
'events.d',
|
||||
'eventscripts',
|
||||
'onnode',
|
||||
|
Loading…
x
Reference in New Issue
Block a user