/*
 * Copyright (c) 2004, 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: servid.c,v 1.4 2005/06/02 19:00:36 ca Exp $")
#include "sm/error.h"
#include "sm/assert.h"
#include "sm/memops.h"
#include "sm/limits.h"
#include "sm/heap.h"
#include "sm/queue.h"
#include "sm/servid.h"

/*
**  ID context
**  Note: this module doesn't give out 0 as id because 0 is used as
**  "free" marker in id_used[].
**
**  id_min is currently "fixed" here in the code, it could be made an
**  option or slightly modified if necessary.
**
**  Optimization: by default id_max is large and the application (mcp?)
**  is expected not to use too many ids, hence it should be possible
**  to sequentially return id_min ... id_max without checking whether
**  the id has already been issued; this is recorded in id_wrapped.
*/

struct id_ctx_S
{
	uint	 id_size;	/* currently used size */
	uint	 id_alloc;	/* allocated size */
	uint	 id_next;	/* next id to use */
	uint	 id_min;	/* min id to return */
	uint	 id_max;	/* max id to return */
	bool	 id_wrapped;	/* exceeded max once? */
	uint	 id_free_idx;	/* a free index in array */
	uint	*id_used;	/* array of ids (0: free) */
};

/*
**  SM_NEW_ID_CTX -- Create ID context
**
**	Parameters:
**		size -- number of ids to maintain (> 1)
**		maxid -- maximum value of id to issue (0: use default)
**		pid_ctx -- (pointer to) ID context (output)
**
**	Returns:
**		usual sm_error code
*/

sm_ret_T
sm_new_id_ctx(uint size, uint maxid, id_ctx_P *pid_ctx)
{
	sm_ret_T ret;
	size_t len;
	id_ctx_P id_ctx;

	SM_REQUIRE(pid_ctx != NULL);
	/* SM_REQUIRE(size > 1); */
	SM_REQUIRE(maxid == 0 || maxid > size);

	id_ctx = (id_ctx_P) sm_zalloc(sizeof(*id_ctx));
	if (id_ctx == NULL)
		goto enomem;

	len = sizeof(*id_ctx->id_used) * size;
	SM_ASSERT(len >= size);
	SM_ASSERT(len >= sizeof(*id_ctx->id_used));
	id_ctx->id_used = (uint *) sm_zalloc(len);
	if (id_ctx->id_used == NULL)
		goto enomem;

	id_ctx->id_next = 1;
	id_ctx->id_min = 1;
	id_ctx->id_max = maxid == 0 ? INT_MAX : maxid;
	id_ctx->id_wrapped = false;
	id_ctx->id_free_idx = 0;
	id_ctx->id_size = size;
	id_ctx->id_alloc = size;
	*pid_ctx = id_ctx;
	return SM_SUCCESS;

  enomem:
	ret = sm_err_temp(ENOMEM);
	if (id_ctx != NULL)
		sm_free_size(id_ctx, sizeof(*id_ctx));
	return ret;
}

/*
**  SM_CHG_ID_CTX -- Change size for ID context
**
**	Parameters:
**		id_ctx -- ID context
**		size -- new number of ids to maintain
**
**	Returns:
**		usual sm_error code
*/

sm_ret_T
sm_chg_id_ctx(id_ctx_P id_ctx, uint size)
{
	sm_ret_T ret;
	size_t len;
	uint *used;

	SM_REQUIRE(id_ctx != NULL);
	if (size <= id_ctx->id_size || size <= id_ctx->id_alloc)
	{
		id_ctx->id_size = size;
		return SM_SUCCESS;
	}
	len = sizeof(*used) * size;
	SM_ASSERT(len >= size);
	SM_ASSERT(len >= sizeof(*id_ctx->id_used));
	used = (uint *) sm_zalloc(len);
	if (used == NULL)
		goto enomem;
	len = sizeof(*id_ctx->id_used) * id_ctx->id_size;
	sm_memcpy(used, id_ctx->id_used, len);
	len = sizeof(*id_ctx->id_used) * id_ctx->id_alloc;
	sm_free_size(id_ctx->id_used, len);
	id_ctx->id_used = used;
	id_ctx->id_size = size;
	id_ctx->id_alloc = size;
	if (id_ctx->id_max != INT_MAX || id_ctx->id_max <= size)
		id_ctx->id_max = size + 1;
	return SM_SUCCESS;

  enomem:
	ret = sm_err_temp(ENOMEM);
	return ret;
}

/*
**  SM_DESTROY_ID_CTX -- Destroy ID context
**
**	Parameters:
**		id_ctx -- ID context
**
**	Returns:
**		usual sm_error code
*/

sm_ret_T
sm_destroy_id_ctx(id_ctx_P id_ctx)
{
	if (id_ctx == NULL)
		return SM_SUCCESS;
	if (id_ctx->id_used != NULL)
	{
		size_t len;

		len = sizeof(*id_ctx->id_used) * id_ctx->id_alloc;
		sm_free_size(id_ctx->id_used, len);
	}
	sm_free_size(id_ctx, sizeof(*id_ctx));
	return SM_SUCCESS;
}

/*
**  SM_FIND_ID -- Find ID in array
**
**	Parameters:
**		id_ctx -- ID context
**		id -- ID to check
**
**	Returns:
**		<0: ID not found
**		>=0: index where ID was found
*/

static int
sm_find_id(id_ctx_P id_ctx, uint id)
{
	uint i;

	SM_REQUIRE(id_ctx != NULL);
	for (i = 0; i < id_ctx->id_size; i++)
	{
		if (id == id_ctx->id_used[i])
			return i;
	}
	return -1;
}

/*
**  SM_NEW_ID -- Get new ID
**
**	Parameters:
**		id_ctx -- ID context
**		pid -- (pointer to) ID (output)
**
**	Returns:
**		usual sm_error code
*/

sm_ret_T
sm_new_id(id_ctx_P id_ctx, uint *pid)
{
	uint id;
	int idx;

	SM_REQUIRE(id_ctx != NULL);
	SM_REQUIRE(pid != NULL);

	id = id_ctx->id_next;
	if (!id_ctx->id_wrapped)
		goto found;
	do
	{
		idx = sm_find_id(id_ctx, id);
		if (idx < 0)
			goto found;
		++id;
	} while (id <= id_ctx->id_max);
	id_ctx->id_wrapped = true;
	id = id_ctx->id_min;
	do
	{
		idx = sm_find_id(id_ctx, id);
		if (idx < 0)
			goto found;
		++id;
	} while (id < id_ctx->id_next);

	return sm_err_perm(EINVAL);

  found:
	*pid = id;
	id_ctx->id_next = id + 1;
	if (id_ctx->id_next > id_ctx->id_max)
	{
		id_ctx->id_next = id_ctx->id_min;
		id_ctx->id_wrapped = true;
	}
	if (id_ctx->id_free_idx >= id_ctx->id_size
	    /* || id_ctx->id_free_idx < 0 ||*/)
	{
		idx = sm_find_id(id_ctx, 0);
		if (idx < 0 || (uint)idx >= id_ctx->id_size)
			return sm_err_perm(EINVAL);
		id_ctx->id_free_idx = idx;
	}
	SM_ASSERT(/*id_ctx->id_free_idx >= 0 &&*/
		id_ctx->id_free_idx < id_ctx->id_size);
	id_ctx->id_used[id_ctx->id_free_idx] = id;
	id_ctx->id_free_idx = sm_find_id(id_ctx, 0);
	return SM_SUCCESS;
}

/*
**  SM_FREE_ID -- Release ID
**
**	Parameters:
**		id_ctx -- ID context
**		id -- ID to release
**
**	Returns:
**		usual sm_error code
*/

sm_ret_T
sm_free_id(id_ctx_P id_ctx, uint id)
{
	int idx;

	SM_REQUIRE(id_ctx != NULL);
	idx = sm_find_id(id_ctx, id);
	if (idx < 0)
		return sm_err_perm(EINVAL);
	id_ctx->id_used[idx] = 0;
	id_ctx->id_free_idx = idx;
	return SM_SUCCESS;
}


syntax highlighted by Code2HTML, v. 0.9.1