/*
 * 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: map.c,v 1.52 2007/01/23 17:54:57 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/stat.h"

#if MTA_USE_PTHREADS
#define SMMAP_WR_LOCK(map) do {			\
		if (SM_IS_FLAG(mapc->sm_mapc_flags,			\
				SMMAPC_FL_LCK_WR|SMMAPC_FL_LCK_FULL))	\
		{							\
			r = pthread_rwlock_wrlock(&map->sm_map_rwlock); \
			if (r != 0)					\
				return sm_error_perm(SM_EM_MAP, r);	\
		}							\
	} while (0)

#define SMMAP_RD_LOCK(map) do {		\
		if (SM_IS_FLAG(mapc->sm_mapc_flags, SMMAPC_FL_LCK_FULL)) {	\
			r = pthread_rwlock_wrlock(&map->sm_map_rwlock); \
			if (r != 0)					\
				return sm_error_perm(SM_EM_MAP, r);	\
		}							\
		else if (SM_IS_FLAG(mapc->sm_mapc_flags, SMMAPC_FL_LCK_WR)) {	\
			r = pthread_rwlock_rdlock(&map->sm_map_rwlock); \
			if (r != 0)					\
				return sm_error_perm(SM_EM_MAP, r);	\
		}							\
	} while (0)

#define SMMAP_WR_UNLOCK(map) do {			\
		if (SM_IS_FLAG(mapc->sm_mapc_flags,			\
				SMMAPC_FL_LCK_WR|SMMAPC_FL_LCK_FULL))	\
		{							\
			r = pthread_rwlock_unlock(&map->sm_map_rwlock); \
			if (r != 0)					\
				return sm_error_perm(SM_EM_MAP, r);	\
		}							\
	} while (0)

#define SMMAP_RD_UNLOCK(map)	SMMAP_WR_UNLOCK(map)

#else /* MTA_USE_PTHREADS */

#define SMMAP_WR_LOCK(map)
#define SMMAP_WR_UNLOCK(map)
#define SMMAP_RD_LOCK(map)
#define SMMAP_RD_UNLOCK(map)

#endif /* MTA_USE_PTHREADS */

/*
**  SM_MAP_DESTROY -- destroy a map
**
**	Parameters:
**		map -- map context
**
**	Returns:
**		usual sm_error code
*/

sm_ret_T
sm_map_destroy(sm_map_P map, uint32_t flags)
{
	sm_ret_T ret;
	sm_mapc_P mapc;

	if (NULL == map)
		return SM_SUCCESS;
	SM_IS_MAP(map);
	mapc = map->sm_map_class;
	SM_IS_MAPC(mapc);
	if (mapc->sm_mapc_destroyf != NULL)
		ret = mapc->sm_mapc_destroyf(map, flags);
	else
		ret = SM_SUCCESS;
#if MTA_USE_PTHREADS
	(void) pthread_rwlock_destroy(&map->sm_map_rwlock);
#endif
	sm_free_size(map, sizeof(*map));
	return SM_SUCCESS;
}

/*
**  SM_MAP_CREATE -- create map
**
**	Parameters:
**		maps -- map system context
**		type -- type of map
**		flags -- flags for map
**		pmap -- pointer to map (output)
**	Note: The following assertions must hold:
**		pmap != NULL && *pmap == NULL
**
**	Returns:
**		usual sm_error code
*/

sm_ret_T
sm_map_create(sm_maps_P maps, sm_cstr_P type, uint32_t flags, sm_map_P *pmap)
{
	sm_ret_T ret;
	sm_map_P map;
	sm_mapc_P mapc;
#if MTA_USE_PTHREADS
	int r;
#endif

	SM_REQUIRE(pmap != NULL);
	SM_REQUIRE(*pmap == NULL);
	ret = sm_maps_find(maps, type, &mapc);
	if (sm_is_err(ret))
		return ret;
	map = (sm_map_P) sm_zalloc(sizeof(*map));
	if (NULL == map)
		return sm_error_temp(SM_EM_MAP, ENOMEM);

	/* can be overridden in map specific create function */
	map->sm_map_caps = SMMAP_CAPS_LTALL;
	if (mapc->sm_mapc_createf != NULL) {
		ret = mapc->sm_mapc_createf(mapc, type, flags, map);
		if (sm_is_err(ret))
			goto error;
	}
	map->sm_map_type = SM_CSTR_DUP(type);
	map->sm_map_class = mapc;
#if MTA_USE_PTHREADS
	r = pthread_rwlock_init(&map->sm_map_rwlock, NULL);
	if (r != 0) {
		sm_free_size(map, sizeof(*map));
		return sm_error_perm(SM_EM_MAP, r);
	}
#endif /* MTA_USE_PTHREADS */

	map->sm_map_flags = SMMAP_FL_CREATED;
#if SM_MAP_CHECK
	map->sm_magic = SM_MAP_MAGIC;
#endif

	*pmap = map;
	return SM_SUCCESS;

  error:
	sm_free_size(map, sizeof(*map));	/* XXX more data to free? */
	*pmap = NULL;
	return ret;
}

/*
**  SM_MAP_OPEN -- open map
**	XXX more parameters...
**
**	Parameters:
**		maps -- map system context
**		name -- name of map
**		type -- type of map
**		flags -- flags for map
**	XXX define what these flags do... don't just pass them through
**	maybe compare sm_io_open() or sm8 map functions; in the latter
**	parseargs() defines the flags, not open().
**	maybe this should be "mode": open for read/write/create/excl.
**	add another function that sets flags for the map behavior, e.g.,
**	map to lower case, include trailing '\0', ...
**		path -- path for map (NOT copied, must be persistent in caller)
**		pmap -- pointer to map (output)
**	Note: The following assertions must hold:
**		pmap != NULL
**		*pmap == NULL iff map_create() has not been called.
**
**	Returns:
**		usual sm_error code
*/

sm_ret_T
sm_map_open(sm_maps_P maps, const sm_cstr_P name, const sm_cstr_P type, uint32_t flags, const char *path, int mode, sm_map_P *pmap, ...)
{
	sm_ret_T ret;
	sm_map_P map;
	sm_mapc_P mapc;
	struct stat sb;
	va_list ap;

	SM_IS_MAPS(maps);
	SM_REQUIRE(pmap != NULL);

	ret = sm_maps_find(maps, type, &mapc);
	if (sm_is_err(ret))
		return ret;

	if (*pmap == NULL) {
		ret = sm_map_create(maps, type, flags, pmap);
		if (sm_is_err(ret))
			return ret;
	}
	map = *pmap;
	SM_IS_MAP(map);
	SM_REQUIRE(SMMAP_IS_FL(map, SMMAP_FL_CREATED));

	/* call open function ... */
	if (mapc->sm_mapc_openf != NULL) {
		va_start(ap, pmap);
		ret = mapc->sm_mapc_openf(mapc, type, flags, path, mode, map, ap);
		va_end(ap);
		if (sm_is_err(ret))
			goto error;
	}
#if 0
	else
		/* XXX error? */;
#endif

	map->sm_map_openflags = flags;
	map->sm_map_mode = mode;
	map->sm_map_name = SM_CSTR_DUP(name);
	map->sm_map_path = path;	/* XXX NOT COPIED!!! */
	if (path != NULL && stat((const char *)path, &sb) == 0) {
		map->sm_map_mtime = sb.st_mtime;
		map->sm_map_ino = sb.st_ino;
	}
	else {
		map->sm_map_mtime = 0;
		map->sm_map_ino = 0;
	}

	ret = sm_mapname_add(maps, map);
	if (sm_is_err(ret))
		goto error;

#if 0
	map->sm_map_app_ctx = NULL;
	map->sm_map_key_allocf = sm_map_dbt_alloc;
	map->sm_map_key_freef = sm_map_dbt_free;
	map->sm_map_data_allocf = sm_map_dbt_alloc;
	map->sm_map_data_freef = sm_map_dbt_free;
#endif /* 0 */

	SMMAP_SET_FL(map, SMMAP_FL_OPEN);

	return SM_SUCCESS;

  error:
	sm_free_size(map, sizeof(*map));	/* XXX more data to free? */
	*pmap = NULL;
	return ret;
}

/*
**  SM_MAP_CLOSE -- close map
**	XXX more parameters...
**
**	Parameters:
**		map -- map
**		flags -- flags
**
**	Returns:
**		usual sm_error code
*/

sm_ret_T
sm_map_close(sm_map_P map, uint32_t flags)
{
	sm_ret_T ret;
	sm_mapc_P mapc;

	if (NULL == map)
		return SM_SUCCESS;

	SM_IS_MAP(map);
	mapc = map->sm_map_class;
	SM_IS_MAPC(mapc);
	SMMAP_SET_FL(map, SMMAP_FL_CLOSING);

	ret = sm_mapname_rm(mapc->sm_mapc_maps, map);
	/* ignore result? */

	if (mapc->sm_mapc_closef != NULL && SMMAP_IS_FL(map, SMMAP_FL_OPEN))
		ret = mapc->sm_mapc_closef(map, flags);
	else
		ret = SM_SUCCESS;
	SMMAP_CLR_FL(map, SMMAP_FL_OPEN);
#if SM_MAP_CHECK
	map->sm_magic = SM_MAGIC_NULL;
#endif

	SMMAP_SET_FL(map, SMMAP_FL_CLOSED);
	SMMAP_CLR_FL(map, SMMAP_FL_CLOSING);
	SM_CSTR_FREE(map->sm_map_name);
	SM_CSTR_FREE(map->sm_map_type);

	/* really? */
	sm_free_size(map, sizeof(*map));

	return ret;
}

/*
**  SM_MAP_REOPEN -- reopen map
**
**	Parameters:
**		map -- map to reopen
**		flags -- flags (unused right now...)
**
**	Returns:
**		usual sm_error code
**
**	How does this deal with errors? In which state will the map be?
*/

sm_ret_T
sm_map_reopen(sm_map_P map, uint32_t flags, ...)
{
	sm_ret_T ret;
	sm_mapc_P mapc;
#if MTA_USE_PTHREADS
	int r;
#endif
	struct stat sb;
	va_list ap;

	SM_IS_MAP(map);

	ret = SM_SUCCESS;
	mapc = map->sm_map_class;
	SMMAP_WR_LOCK(map);

	if (SM_IS_FLAG(mapc->sm_mapc_flags, SMMAPC_FL_GEN_REOPEN)
	    &&
		/* did the file change? */
		(map->sm_map_path == NULL
		|| stat(map->sm_map_path, &sb) != 0
		|| map->sm_map_mtime != sb.st_mtime
		|| map->sm_map_ino != sb.st_ino))
	{
		if (mapc->sm_mapc_closef != NULL && SMMAP_IS_FL(map, SMMAP_FL_OPEN)) {
			SMMAP_SET_FL(map, SMMAP_FL_CLOSING);
			ret = mapc->sm_mapc_closef(map, 0);
			SMMAP_CLR_FL(map, SMMAP_FL_OPEN);
			SMMAP_CLR_FL(map, SMMAP_FL_CLOSING);
			SMMAP_SET_FL(map, SMMAP_FL_CLOSED);
		}

		/* call create function ... */
		if (!sm_is_err(ret) && mapc->sm_mapc_createf != NULL) {
			ret = mapc->sm_mapc_createf(mapc, map->sm_map_type
					, map->sm_map_openflags, map);
		}
		if (!sm_is_err(ret))
			ret = sm_map_setopts(map);

		/* call open function ... */
		if (!sm_is_err(ret) && mapc->sm_mapc_openf != NULL) {
			va_start(ap, flags);
			ret = mapc->sm_mapc_openf(mapc, map->sm_map_type
					, map->sm_map_openflags, map->sm_map_path, map->sm_map_mode
					, map, ap);
			va_end(ap);
			if (sm_is_success(ret)) {
				SMMAP_CLR_FL(map, SMMAP_FL_CLOSED);
				SMMAP_SET_FL(map, SMMAP_FL_OPEN);
			}
	/* XXX ap? How to "remember" these and how to create a va_list? */
		}
#if 0
		else
			/* XXX error? */;
#endif
	}
	else if (mapc->sm_mapc_reopenf != NULL) {
		SMMAP_SET_FL(map, SMMAP_FL_CLOSING);
		ret = mapc->sm_mapc_reopenf(map, 0);
		SMMAP_CLR_FL(map, SMMAP_FL_CLOSING);
		if (sm_is_success(ret))
			SMMAP_SET_FL(map, SMMAP_FL_OPEN);
		else
			SMMAP_SET_FL(map, SMMAP_FL_CLOSED);
	}
	SMMAP_WR_UNLOCK(map);
	return ret;
}

/*
**  MAP_LOOKUP -- lookup value
**
**	Parameters:
**		map -- map
**		flags -- flags
**		key -- key
**		data -- data
**
**	Returns:
**		usual sm_error code
*/

sm_ret_T
sm_map_lookup(sm_map_P map, uint32_t flags, sm_map_key_P key, sm_map_data_P data)
{
	sm_ret_T ret;
	sm_mapc_P mapc;
#if MTA_USE_PTHREADS
	int r;
#endif

	if (NULL == map)
		return sm_error_perm(SM_EM_MAP, SM_E_NOMAP);	/* XXX */
	SM_IS_MAP(map);
	mapc = map->sm_map_class;
	SM_IS_MAPC(mapc);
	if (!SMMAP_IS_FL(map, SMMAP_FL_OPEN)) {
		/* map closed but can be reopened? */
		if (!SMMAP_IS_CAPS(map, SMMAP_CAPS_DYNAMIC) &&
		    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;
	}

	/*
	**  Only call lookup function if no capabilities are specified
	**  or the capabilities are available.
	*/

	if (NULL == mapc->sm_mapc_lookupf)
		ret = sm_error_perm(SM_EM_MAP, SM_E_NOTIMPL);
	else if ((flags & SMMAP_CAPS_LTMASK) == SMMAP_FL_NONE
		 || SMMAP_LT_M_CAPS(map, flags))
	{
		SMMAP_RD_LOCK(map);
		ret = mapc->sm_mapc_lookupf(map, flags, key, data);
		SMMAP_RD_UNLOCK(map);
	}
	else
		ret = sm_error_perm(SM_EM_MAP, SM_E_UNAVAIL);
	return ret;
}

/*
**  SM_MAP_ADD -- add data for key
**
**	Parameters:
**		map -- map
**		key -- key
**		data -- data
**		flags -- flags
**
**	Returns:
**		usual sm_error code
*/

sm_ret_T
sm_map_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;
#if MTA_USE_PTHREADS
	int r;
#endif

	if (NULL == map)
		return sm_error_perm(SM_EM_MAP, SM_E_NOMAP);
	SM_IS_MAP(map);
	mapc = map->sm_map_class;
	SM_IS_MAPC(mapc);

	if (mapc->sm_mapc_addf != NULL) {
		SMMAP_WR_LOCK(map);
		ret = mapc->sm_mapc_addf(map, key, data, flags);
		SMMAP_WR_UNLOCK(map);
	}
	else
		ret = sm_error_perm(SM_EM_MAP, SM_E_NOTIMPL);
	return ret;
}

/*
**  SM_MAP_RM -- remove data for key
**
**	Parameters:
**		map -- map
**		key -- key
**
**	Returns:
**		usual sm_error code
*/

sm_ret_T
sm_map_rm(sm_map_P map, sm_map_key_P key)
{
	sm_ret_T ret;
	sm_mapc_P mapc;
#if MTA_USE_PTHREADS
	int r;
#endif

	if (NULL == map)
		return sm_error_perm(SM_EM_MAP, SM_E_NOMAP);
	SM_IS_MAP(map);
	mapc = map->sm_map_class;
	SM_IS_MAPC(mapc);

	if (mapc->sm_mapc_rmf != NULL) {
		SMMAP_WR_LOCK(map);
		ret = mapc->sm_mapc_rmf(map, key);
		SMMAP_WR_UNLOCK(map);
	}
	else
		ret = sm_error_perm(SM_EM_MAP, SM_E_NOTIMPL);
	return ret;
}


syntax highlighted by Code2HTML, v. 0.9.1