/*
Authors:
Pavel Březina <pbrezina@redhat.com>
Copyright (C) 2016 Red Hat
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 <talloc.h>
#include <dhash.h>
#include "util/util.h"
#include "util/sss_ptr_hash.h"
static bool sss_ptr_hash_check_type(void *ptr, const char *type)
{
void *type_ptr;
type_ptr = talloc_check_name(ptr, type);
if (type_ptr == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE,
"Invalid data type detected. Expected [%s], got [%s].\n",
type, talloc_get_name(ptr));
return false;
}
return true;
}
struct sss_ptr_hash_delete_data {
hash_delete_callback *callback;
void *pvt;
};
struct sss_ptr_hash_value {
struct sss_ptr_hash_spy *spy;
void *ptr;
};
struct sss_ptr_hash_spy {
struct sss_ptr_hash_value *value;
hash_table_t *table;
const char *key;
};
static int
sss_ptr_hash_spy_destructor(struct sss_ptr_hash_spy *spy)
{
spy->value->spy = NULL;
spy->value->ptr = NULL;
/* This results in removing entry from hash table and freeing the value. */
sss_ptr_hash_delete(spy->table, spy->key, false);
return 0;
}
static struct sss_ptr_hash_spy *
sss_ptr_hash_spy_create(TALLOC_CTX *mem_ctx,
hash_table_t *table,
const char *key,
struct sss_ptr_hash_value *value)
{
struct sss_ptr_hash_spy *spy;
spy = talloc_zero(mem_ctx, struct sss_ptr_hash_spy);
if (spy == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory!\n");
return NULL;
}
spy->key = talloc_strdup(spy, key);
if (spy->key == NULL) {
talloc_free(spy);
return NULL;
}
spy->table = table;
spy->value = value;
talloc_set_destructor(spy, sss_ptr_hash_spy_destructor);
return spy;
}
static int
sss_ptr_hash_value_destructor(struct sss_ptr_hash_value *value)
{
if (value->spy != NULL) {
/* Disable spy destructor and free it. */
talloc_set_destructor(value->spy, NULL);
talloc_zfree(value->spy);
}
return 0;
}
static struct sss_ptr_hash_value *
sss_ptr_hash_value_create(hash_table_t *table,
const char *key,
void *talloc_ptr)
{
struct sss_ptr_hash_value *value;
value = talloc_zero(table, struct sss_ptr_hash_value);
if (value == NULL) {
return NULL;
}
value->spy = sss_ptr_hash_spy_create(talloc_ptr, table, key, value);
if (value->spy == NULL) {
talloc_free(value);
return NULL;
}
value->ptr = talloc_ptr;
talloc_set_destructor(value, sss_ptr_hash_value_destructor);
return value;
}
static void
sss_ptr_hash_delete_cb(hash_entry_t *item,
hash_destroy_enum deltype,
void *pvt)
{
struct sss_ptr_hash_delete_data *data;
struct sss_ptr_hash_value *value;
void *ptr;
data = talloc_get_type(pvt, struct sss_ptr_hash_delete_data);
if (data == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE, "Invalid data!\n");
return;
}
value = talloc_get_type(item->value.ptr, struct sss_ptr_hash_value);
if (value == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE, "Invalid value!\n");
return;
}
ptr = value->ptr;
/* Free value. */
talloc_free(value);
/* Switch to the input value and call custom callback. */
if (data->callback != NULL) {
item->value.ptr = ptr;
data->callback(item, deltype, data->pvt);
}
}
hash_table_t *sss_ptr_hash_create(TALLOC_CTX *mem_ctx,
hash_delete_callback *del_cb,
void *del_cb_pvt)
{
struct sss_ptr_hash_delete_data *data;
hash_table_t *table;
errno_t ret;
data = talloc_zero(NULL, struct sss_ptr_hash_delete_data);
if (data == NULL) {
return NULL;
}
data->callback = del_cb;
data->pvt = del_cb_pvt;
ret = sss_hash_create_ex(mem_ctx, 10, &table, 0, 0, 0, 0,
sss_ptr_hash_delete_cb, data);
if (ret != EOK) {
DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create hash table [%d]: %s\n",
ret, sss_strerror(ret));
talloc_free(data);
return NULL;
}
talloc_steal(table, data);
return table;
}
errno_t _sss_ptr_hash_add(hash_table_t *table,
const char *key,
void *talloc_ptr,
const char *type,
bool override)
{
struct sss_ptr_hash_value *value;
hash_value_t table_value;
hash_key_t table_key;
int hret;
if (table == NULL || key == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE, "Invalid input!\n");
return EINVAL;
}
if (!sss_ptr_hash_check_type(talloc_ptr, type)) {
return ERR_INVALID_DATA_TYPE;
}
value = sss_ptr_hash_value_create(table, key, talloc_ptr);
if (value == NULL) {
return ENOMEM;
}
table_key.type = HASH_KEY_STRING;
table_key.str = discard_const_p(char, key);
table_value.type = HASH_VALUE_PTR;
table_value.ptr = value;
if (override == false && hash_has_key(table, &table_key)) {
return EEXIST;
}
hret = hash_enter(table, &table_key, &table_value);
if (hret != HASH_SUCCESS) {
DEBUG(SSSDBG_CRIT_FAILURE, "Unable to add key %s!\n", key);
talloc_free(value);
return EIO;
}
return EOK;
}
static struct sss_ptr_hash_value *
sss_ptr_hash_lookup_internal(hash_table_t *table,
const char *key)
{
hash_value_t table_value;
hash_key_t table_key;
int hret;
table_key.type = HASH_KEY_STRING;
table_key.str = discard_const_p(char, key);
hret = hash_lookup(table, &table_key, &table_value);
if (hret == HASH_ERROR_KEY_NOT_FOUND) {
return NULL;
} else if (hret != HASH_SUCCESS) {
DEBUG(SSSDBG_CRIT_FAILURE, "Unable to search hash table [%d]\n", hret);
return NULL;
}
/* Check value type. */
if (table_value.type != HASH_VALUE_PTR) {
DEBUG(SSSDBG_CRIT_FAILURE, "Invalid value type found: %d\n",
table_value.type);
return NULL;
}
if (!sss_ptr_hash_check_type(table_value.ptr, "struct sss_ptr_hash_value")) {
return NULL;
}
return table_value.ptr;
}
void *_sss_ptr_hash_lookup(hash_table_t *table,
const char *key,
const char *type)
{
struct sss_ptr_hash_value *value;
value = sss_ptr_hash_lookup_internal(table, key);
if (value == NULL || value->ptr == NULL) {
return NULL;
}
if (!sss_ptr_hash_check_type(value->ptr, type)) {
return NULL;
}
return value->ptr;
}
void sss_ptr_hash_delete(hash_table_t *table,
const char *key,
bool free_value)
{
struct sss_ptr_hash_value *value;
hash_key_t table_key;
int hret;
void *ptr;
if (table == NULL || key == NULL) {
return;
}
value = sss_ptr_hash_lookup_internal(table, key);
if (value == NULL) {
/* Value not found. */
return;
}
ptr = value->ptr;
table_key.type = HASH_KEY_STRING;
table_key.str = discard_const_p(char, key);
/* Delete table entry. This will free value and spy in delete callback. */
hret = hash_delete(table, &table_key);
if (hret != HASH_SUCCESS && hret != HASH_ERROR_KEY_NOT_FOUND) {
DEBUG(SSSDBG_CRIT_FAILURE, "Unable to remove key from table [%d]\n",
hret);
}
/* Also free the original value if requested. */
if (free_value) {
talloc_free(ptr);
}
return;
}
void sss_ptr_hash_delete_all(hash_table_t *table,
bool free_values)
{
struct sss_ptr_hash_value *value;
hash_value_t *values;
unsigned long count;
unsigned long i;
int hret;
void *ptr;
if (table == NULL) {
return;
}
hret = hash_values(table, &count, &values);
if (hret != HASH_SUCCESS) {
DEBUG(SSSDBG_CRIT_FAILURE, "Unable to get values [%d]\n", hret);
return;
}
for (i = 0; i < count; i++) {
value = values[i].ptr;
ptr = value->ptr;
/* This will remove the entry from hash table and free value. */
talloc_free(value->spy);
if (free_values) {
/* Also free the original value. */
talloc_free(ptr);
}
}
return;
}
bool sss_ptr_hash_has_key(hash_table_t *table,
const char *key)
{
hash_key_t table_key;
table_key.type = HASH_KEY_STRING;
table_key.str = discard_const_p(char, key);
return hash_has_key(table, &table_key);
}