/*
 * Copyright (c) 2003-2005 Sendmail, Inc. and its suppliers.
 *	All rights reserved.
 *
 * By using this file, you agree to the terms and conditions set
 * forth in the LICENSE file which can be found at the top level of
 * the sendmail distribution.
 */

#include "sm/generic.h"
SM_RCSID("@(#)$Id: bdb.c,v 1.43 2006/10/05 04:27:38 ca Exp $")

#include "sm/assert.h"
#include "sm/error.h"
#include "sm/memops.h"
#include "sm/heap.h"
#include "map.h"
#include "sm/map.h"
#include "sm/maps.h"
#include "sm/mapc.h"
#include "sm/mapclasses.h"
#include "sm/bdb.h"

/*
ToDo: implementation of map for Berkeley DB.

translate BDB error numbers into sm?
or can we simply reserve the BDB error range?

locking?

*/

#ifndef DBMMODE
# define DBMMODE 0640
#endif


/* static sm_map_open_F	 sm_bdb_open; */
/* static sm_map_close_F	 sm_bdb_close; */
static sm_map_alloc_F	 sm_bdb_alloc;
static sm_map_free_F	 sm_bdb_free;
static sm_map_locate_F	 sm_bdb_locate;
static sm_map_first_F	 sm_bdb_first;
static sm_map_next_F	 sm_bdb_next;

/*
**  These are types of databases.
*/

#define SMDB_TYPE_DEFAULT	NULL
#define SMDB_TYPE_HASH		"hash"
#define SMDB_TYPE_BTREE		"btree"
#define SMDB_TYPE_NDBM		"dbm"

/*
**  BDB_TYPE2BDB -- Translates database type to BDB type.
**
**	Parameters:
**		type -- The type to translate.
**
**	Returns:
**		The BDB type that corresponds to type.
*/

static DBTYPE
bdb_type2bdb2(const sm_cstr_P type)
{
	if (type == SMDB_TYPE_DEFAULT)
		return DB_HASH;

	if (strncmp((const char*)sm_cstr_data(type),
		    SMDB_TYPE_HASH, sm_cstr_getlen(type)) == 0)
		return DB_HASH;

	if (strncmp((const char*)sm_cstr_data(type), SMDB_TYPE_BTREE,
		    sm_cstr_getlen(type)) == 0)
		return DB_BTREE;
	return DB_UNKNOWN;
}

/*
**  SM_BDB_SETOPT -- set options for map
**
**	Parameters:
**		map -- map
**		ap -- options
**
**	Returns:
**		usual sm_error code
*/

static sm_ret_T
sm_bdb_setopt(sm_map_P map, va_list ap)
{
	sm_ret_T ret;
	int k, v;
	DB *db;

	SM_IS_MAP(map);
	db = (DB *) map->sm_map_db;
	if (db == NULL)
		return sm_error_perm(SM_EM_MAP, ENOENT);
	ret = SM_SUCCESS;

	for (;;)
	{
		k = va_arg(ap, int);
		if (k == SMPO_END)
			break;

		switch (k)
		{
		  case SMPO_BDB_DB_CACHE_SIZE:
			v = va_arg(ap, int);
			ret = db->set_cachesize(db, 0, v, 1);
			if (ret != 0)
			{
				ret = BDB_ERR2RET(ret);
				goto error;
			}
			break;
		  case SMPO_BDB_DB_HASH_NELEM:
			v = va_arg(ap, int);
			ret = db->set_h_nelem(db, v);
			if (ret != 0)
			{
				ret = BDB_ERR2RET(ret);
				goto error;
			}
		  default:
			/* silently ignore bogus options? */
			break;
		}
	}

  error:
	return ret;
}

/*
**  SM_BDB_GETOPT -- get options for map
**
**	Parameters:
**		map -- map
**		which -- which option?
**		valp -- pointer to place where result should be stored
**
**	Returns:
**		usual sm_error code
*/

static sm_ret_T
sm_bdb_getopt(sm_map_P map, int which, void *valp)
{
	sm_ret_T ret;
	DB *db;

	SM_IS_MAP(map);
	db = (DB *) map->sm_map_db;
	if (db == NULL)
		return sm_error_perm(SM_EM_MAP, ENOENT);

	/* ... */

	ret = SM_SUCCESS;
	return ret;
}

/*
**  SM_BDB_CLOSE -- close map
**	more parameters???
**
**	Parameters:
**		map -- map
**		flags -- currently ignored
**
**	Returns:
**		usual sm_error code
*/

static sm_ret_T
sm_bdb_close(sm_map_P map, uint32_t flags)
{
	sm_ret_T ret;
	sm_mapc_P mapc;
	DB *db;

	SM_IS_MAP(map);
	mapc = map->sm_map_class;
	SM_IS_MAPC(mapc);
	ret = SM_SUCCESS;

	db = (DB *) map->sm_map_db;
	if (db == NULL)
		return sm_error_perm(SM_EM_MAP, ENOENT);

	/* close BDB map */
	ret = db->close(db, SM_IS_FLAG(map->sm_map_openflags, SMAP_MODE_RDONLY)
				? DB_NOSYNC : 0);
	map->sm_map_db = NULL;

	return ret;
}

/*
**  SM_BDB_DESTROY -- destroy map
**	XXX more parameters...
**
**	Parameters:
**		map -- map
**		flags -- flags for map
**
**	Returns:
**		usual sm_error code
*/

static sm_ret_T
sm_bdb_destroy(sm_map_P map, uint32_t flags)
{
	sm_ret_T ret;
	sm_mapc_P mapc;
	DB *db;

	SM_IS_MAP(map);
	mapc = map->sm_map_class;
	SM_IS_MAPC(mapc);
	ret = SM_SUCCESS;

	db = (DB *) map->sm_map_db;
	if (db == NULL)
		return sm_error_perm(SM_EM_MAP, ENOENT);

	/* call db destroy? */

	map->sm_map_db = NULL;
	return ret;
}

/*
**  SM_BDB_CREATE -- create map
**
**	Parameters:
**		mapc -- map context
**		type -- type of map
**		flags -- flags for map
**		map -- map
**
**	Returns:
**		usual sm_error code
*/

static sm_ret_T
sm_bdb_create(sm_mapc_P mapc, const sm_cstr_P type, uint32_t flags, sm_map_P map)
{
	sm_ret_T ret;
	DB *db;

	SM_IS_MAPC(mapc);
	SM_REQUIRE(map != NULL);
	db = NULL;

	/* create BDB map ... */
	ret = db_create(&db, NULL, 0);
	if (ret == 0)
	{
		map->sm_map_db = db;
		map->sm_map_caps = SMMAP_CAPS_LTALL;
	}
	/* XXX map error code? */
	return ret;
}

/*
**  BDB_OPEN_FLAGS -- translate external (map) flags into internal flags
**
**	Paramters:
**		flags -- map flags
**
**	Returns:
**		Internal flag value matching user selected flags
*/

static uint32_t
bdb_open_flags(uint32_t flags)
{
	uint32_t ret;

#if MTA_USE_PTHREADS
	ret = DB_THREAD;
#else
	ret = 0;
#endif
	if (SM_IS_FLAG(flags, SMAP_MODE_RDONLY))
		ret |= DB_RDONLY;
	if (SM_IS_FLAG(flags, SMAP_MODE_CREATE))
		ret |= DB_CREATE;
	return ret;
}

/*
**  SM_BDB_OPEN -- open map
**
**	Parameters:
**		mapc -- map context
**		type -- type of map
**		flags -- flags for opening map
**		path -- path of map
**		mode -- open mode (currently ignored, DBMMODE is used)
**		map -- map
**		ap -- additional argument
**
**	Returns:
**		usual sm_error code
*/

static sm_ret_T
sm_bdb_open(sm_mapc_P mapc, const sm_cstr_P type, uint32_t flags, const char *path, int mode, sm_map_P map, va_list ap)
{
	sm_ret_T ret;
	DB *db;
	DBTYPE dbtype;

	SM_IS_MAPC(mapc);
	SM_REQUIRE(map != NULL);
	db = NULL;
	ret = SM_SUCCESS;
	dbtype = bdb_type2bdb2(type);

	db = map->sm_map_db;

#ifdef DB_CACHE_SIZE
	ret = db->set_cachesize(db, 0, DB_CACHE_SIZE, 0);
	if (ret != 0)
	{
		ret = BDB_ERR2RET(ret);
		goto error;
	}
#endif /* DB_CACHE_SIZE */
#ifdef DB_HASH_NELEM
	if (dbtype == DB_HASH)
	{
		ret = db->set_h_nelem(db, DB_HASH_NELEM);
		if (ret != 0)
		{
			ret = BDB_ERR2RET(ret);
			goto error;
		}
	}
#endif /* DB_HASH_NELEM */

	/* open BDB map ... */
	ret = db->open(db, NULL, path, NULL, dbtype, bdb_open_flags(flags),
			DBMMODE);
	if (ret != 0)
	{
		ret = BDB_ERR2RET(ret);
		goto error;
	}
	return ret;

  error:
	if (db != NULL)
	{
		(void) db->close(db, 0);
		db = NULL;
	}
	return ret;
}

/*
**  SM_BDB_LOOKUP -- lookup a key in BDB, return data if found
**
**	Parameters:
**		map -- map context
**		flags -- flags
**		key -- key
**		data -- data (output)
**
**	Returns:
**		usual sm_error code
*/

static sm_ret_T
sm_bdb_lookup(sm_map_P map, uint32_t flags, sm_map_key_P key, sm_map_data_P data)
{
	sm_ret_T ret;
#if MTA_USE_PTHREADS
	size_t l;
#endif
	sm_mapc_P mapc;
	DB *db;
	DBT db_key, db_data;

	SM_IS_MAP(map);
	SM_IS_KEY(key);
	SM_IS_DATA(data);
	mapc = map->sm_map_class;
	SM_IS_MAPC(mapc);
	ret = SM_SUCCESS;

	db = (DB *) map->sm_map_db;
	if (db == NULL)
		return sm_error_perm(SM_EM_MAP, ENOENT);	/* XXX */
	if (!SMMAP_IS_FL(map, SMMAP_FL_OPEN))
	{
		/* map closed but can be reopened? */
		if (mapc->sm_mapc_reopenf != NULL)
			ret = mapc->sm_mapc_reopenf(map, 0);
		else
			ret = sm_error_perm(SM_EM_MAP, SM_E_CLOSEDMAP);
		if (sm_is_err(ret))
			return ret;
	}

	sm_memzero(&db_key, sizeof(db_key));
	sm_memzero(&db_data, sizeof(db_data));

	/* XXX WARNING: changes key inplace! */
	if (SM_IS_FLAG(flags, SMMAP_FL_LWR_KEY))
		sm_str2lower(key);
	db_key.size = sm_str_getlen(key);
	db_key.data = sm_str_data(key);

#if MTA_USE_PTHREADS
	db_data.flags = DB_DBT_USERMEM;
	db_data.data = sm_str_data(data);
	db_data.ulen = sm_str_getsize(data);

	ret = db->get(db, NULL, &db_key, &db_data, 0);
	l = db_data.size;
	if (ret == DB_BUFFER_SMALL && l < sm_str_getmax(data))
	{
		if (!sm_is_err(sm_str_resize_data(data, l)))
		{
			db_data.data = sm_str_data(data);
			db_data.ulen = sm_str_getsize(data);
			ret = db->get(db, NULL, &db_key, &db_data, 0);
			l = db_data.size;
		}
	}
	if (ret == 0 && l < sm_str_getmax(data))
		SM_STR_SETLEN(data, db_data.size);

#else /* MTA_USE_PTHREADS */
	ret = db->get(db, NULL, &db_key, &db_data, 0);
	if (ret == 0 && data != NULL)
	{
		ret = sm_str_scatn(data, (const char *) db_data.data,
				db_data.size);
		if (sm_is_err(ret))
			return ret;
	}
#endif /* MTA_USE_PTHREADS */

	return BDB_ERR2RET(ret);
}

/*
**  SM_BDB_ADD -- add key/data to BDB
**
**	Parameters:
**		map -- map context
**		key -- key
**		data -- data
**		flags -- flags
**
**	Returns:
**		usual sm_error code
*/

static sm_ret_T
sm_bdb_add(sm_map_P map, sm_map_key_P key, sm_map_data_P data, uint flags)
{
	sm_ret_T ret;
	sm_mapc_P mapc;
	DB *db;
	DBT db_key, db_data;

	SM_IS_MAP(map);
	SM_REQUIRE(key != NULL);
	SM_REQUIRE(data != NULL);
	mapc = map->sm_map_class;
	SM_IS_MAPC(mapc);
	ret = SM_SUCCESS;

	/* this needs to be more sophisticated if more flag values are used! */
	if (flags == SMMAP_AFL_UNIQUE)
		flags = DB_NOOVERWRITE;

	db = (DB *) map->sm_map_db;
	if (db == NULL)
		return sm_error_perm(SM_EM_MAP, ENOENT);	/* XXX */

	sm_memzero(&db_key, sizeof(db_key));
	sm_memzero(&db_data, sizeof(db_data));
	db_key.size = sm_str_getlen(key);
	db_key.data = sm_str_data(key);
	db_data.size = sm_str_getlen(data);
	db_data.data = sm_str_data(data);

	ret = db->put(db, NULL, &db_key, &db_data, flags);
	return BDB_ERR2RET(ret);
}

/*
**  SM_BDB_RM -- remove key/data from BDB
**
**	Parameters:
**		map -- map context
**		key -- key
**
**	Returns:
**		usual sm_error code
*/

static sm_ret_T
sm_bdb_rm(sm_map_P map, sm_map_key_P key)
{
	sm_ret_T ret;
	sm_mapc_P mapc;
	DB *db;
	DBT db_key;

	SM_IS_MAP(map);
	SM_REQUIRE(key != NULL);
	mapc = map->sm_map_class;
	SM_IS_MAPC(mapc);
	ret = SM_SUCCESS;

	db = (DB *) map->sm_map_db;
	if (db == NULL)
		return sm_error_perm(SM_EM_MAP, ENOENT);	/* XXX */

	sm_memzero(&db_key, sizeof(db_key));
	db_key.size = sm_str_getlen(key);
	db_key.data = sm_str_data(key);
	ret = db->del(db, NULL, &db_key, 0);
	return BDB_ERR2RET(ret);
}

/*
**  SM_BDB_CLASS_CREATE -- create BDB map class
**
**	Parameters:
**		maps -- map system context
**
**	Returns:
**		usual sm_error code
*/

sm_ret_T
sm_bdb_class_create(sm_maps_P maps)
{
	sm_ret_T ret;
	sm_mapc_P mapc;
	sm_cstr_P htype;

#define BDB_HASH	"hash"
/* add btree? */

	ret = SM_SUCCESS;
	mapc = NULL;
	htype = sm_cstr_scpyn0((const uchar *)BDB_HASH, strlen(BDB_HASH));
	if (htype == NULL)
		goto error;

	ret = sm_mapc_create(maps, htype,
			SMMAPC_FL_ALLOC_K|SMMAPC_FL_ALLOC_D|
			SMMAPC_FL_FREE_K| SMMAPC_FL_FREE_D|
			SMMAPC_FL_GEN_REOPEN|SMMAPC_FL_FILE,
			sm_bdb_create,
			sm_bdb_open,
			sm_bdb_close,
			NULL,
			sm_bdb_destroy,
			sm_bdb_add,
			sm_bdb_rm,
			sm_bdb_alloc,
			sm_bdb_free,
			sm_bdb_lookup,
			sm_bdb_locate,
			sm_bdb_first,
			sm_bdb_next,
			sm_bdb_setopt,
			sm_bdb_getopt,
			&mapc);

	SM_CSTR_FREE(htype);
	return ret;

  error:
	if (ret == SM_SUCCESS)
		ret = sm_error_temp(SM_EM_MAP, ENOMEM);
	/* cleanup mapc? */
	return ret;
}


syntax highlighted by Code2HTML, v. 0.9.1