/*
 * Copyright (c) 2002-2006 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: ibdb.c,v 1.127 2007/06/18 04:42:31 ca Exp $")
#include "sm/error.h"
#include "sm/fcntl.h"
#include "sm/memops.h"
#include "sm/stat.h"
#include "sm/time.h"
#include "sm/heap.h"
#include "sm/assert.h"
#include "sm/str.h"
#include "sm/cstr.h"
#include "sm/io.h"
#include "sm/fdset.h"
#include "sm/reccom.h"
#include "sm/ibdb.h"
#include "sm/rcb.h"
#include "sm/pthread.h"
#include "sm/fs.h"
#include "ibdb.h"
#include "sm/bhtable.h"
#include "ibdbc.h"

/* CONF: Make this configurable */
#define IBC_HT_SIZE	5013
#define IBC_HT_LIMIT	65536
#define SEQ_MIN_PURGE	10	/* minimum difference between seq and first */
#define SEQ_MIN_CLEAN	2	/* min diff. between cleanup runs */

#ifndef SM_IBDB_STATS
# define SM_IBDB_STATS	1
#endif

/*
**  ToDo:
**	Deal with entries that are too long for a single record.
**
**	What happens if the various directories are symbolic links?
**	Does renaming work? mkdir/rmdir will break things?
**	A startup script could create another IBDB directory
**	(ibdbn) which will be renamed to the default directory
**	on startup if necessary (i.e., W -> R, N -> W, use W).
**
**	Check disk space (at least) when creating a new file.
*/

/* specify an INCEDB backup on disk */
struct ibdb_ctx_S
{
	/* sm_magic? */
	uint		 ibdb_version;	/* version of this ibdb */
	sm_file_T	*ibdb_fp;	/* current file */
	sm_str_P	 ibdb_path;	/* pathname of file */
	sm_str_P	 ibdb_name_rm;	/* pathname of file to remove */
	char		*ibdb_dir;	/* directory of file */
	char		*ibdb_name;	/* basename of file */
	char		*ibdb_base_dir;	/* name of base directory */
	int			 ibdb_mode;	/* file mode */
	uint32_t	 ibdb_first;	/* first sequence number */
	uint32_t	 ibdb_sequence;	/* current sequence number */
	size_t		 ibdb_maxsize;	/* maximum size of one file */
	size_t		 ibdb_cursize;	/* current size of file */
	time_t		 ibdb_created;	/* creation time */
#if 0
	time_t		 ibdb_touched;	/* last commit; not (yet) used */
#endif
	uint		 ibdb_changes;	/* number of changes since last commit */
#if SM_IBDB_STATS

	/*
	**  This could be used to remove previous files if both counters
	**  are zero when a new file is created in ibdb_next().
	**  It could also be used to decide when to "rollover", i.e., when
	**  these counters are zero and cursize is "close" to maxsize.
	*/

	uint		 ibdb_opentas;	/* outstanding transactions */
	uint		 ibdb_openrcpts;	/* outstanding recipients */
	ulong		 ibdb_commits;
#endif /* SM_IBDB_STATS */

	/* locks entire ibdb_ctx... */
	pthread_mutex_t	 ibdb_mutex;

	/* Pool for reuse. See include/sm/edb.h */
	ibdb_req_hd_T	 ibdb_reql_pool;
	uint32_t	 ibdb_cleanrun;	/* seq. # of last cleanup run */
	bht_P		 ibdb_ct;

	fs_ctx_P	 ibdb_fs_ctx;
	int		 ibdb_fs_idx;

	uchar		*ibdb_fp_buf;	/* buffer for fp if necessary */
};

/*
**  IBDB_REQ_NEW -- return new ibdb request (allocated or from pool)
**
**	Parameters:
**		ibdb_ctx -- IBDB context
**		pedb_req -- ibdb req (output)
**
**	Returns:
**		usual sm_error code; ENOMEM
**
**	Locking:
**		ibdb_ctx (reql_pool) must be locked by caller
**
**	Last code review: 2005-03-31 18:29:33
**	Last code change:
*/

static sm_ret_T
ibdb_req_new(ibdb_ctx_P ibdb_ctx, ibdb_req_P *pedb_req)
{
	ibdb_req_P ibdb_req;

	/* Add a parameter for locking?? It can be done locally... */
	SM_REQUIRE(pedb_req != NULL);
	SM_REQUIRE(ibdb_ctx != NULL);
	if (!IBDBREQL_EMPTY(&ibdb_ctx->ibdb_reql_pool)) {
		ibdb_req = IBDBREQL_FIRST(&ibdb_ctx->ibdb_reql_pool);
		IBDBREQL_REMOVE(&ibdb_ctx->ibdb_reql_pool);
	}
	else {
		ibdb_req = (ibdb_req_P) sm_zalloc(sizeof(*ibdb_req));
		if (NULL == ibdb_req)
			return sm_error_temp(SM_EM_IBDB, ENOMEM);
	}
	*pedb_req = ibdb_req;
	return SM_SUCCESS;
}

/*
**  IBDB_REQ_REL -- release ibdb request: put it into pool, free allocated
**		substructures/fields
**
**	Parameters:
**		ibdb_ctx -- IBDB context
**		ibdb_req -- ibdb req
**
**	Returns:
**		SM_SUCCESS
**
**	Locking:
**		ibdb_ctx (reql_pool) must be locked by caller
**
**	Last code review: 2005-03-31 18:36:28
**	Last code change:
*/

static sm_ret_T
ibdb_req_rel(ibdb_ctx_P ibdb_ctx, ibdb_req_P ibdb_req)
{
	if (NULL == ibdb_req)
		return SM_SUCCESS;
	SM_REQUIRE(ibdb_ctx != NULL);

	/* Add a parameter for locking?? It can be done locally... */
	IBDBREQL_PRE(&ibdb_ctx->ibdb_reql_pool, ibdb_req);
	SM_STR_FREE(ibdb_req->ibdb_req_addr_pa);
	SM_CSTR_FREE(ibdb_req->ibdb_req_cdb_id);

	/*
	**  Clean out?
	**  sm_memzero(ibdb_req, sizeof(*ibdb_req));
	**  can't be used since it would destroy the linked list.
	*/

	return SM_SUCCESS;
}

/*
**  IBDB_REQ_FREE -- free ibdb request
**
**	Parameters:
**		ibdb_req -- ibdb req
**
**	Returns:
**		SM_SUCCESS
**
**	Last code review: 2005-03-31 18:30:29
**	Last code change:
*/

static sm_ret_T
ibdb_req_free(ibdb_req_P ibdb_req)
{
	if (NULL == ibdb_req)
		return SM_SUCCESS;
	SM_STR_FREE(ibdb_req->ibdb_req_addr_pa);
	SM_CSTR_FREE(ibdb_req->ibdb_req_cdb_id);
	sm_free_size(ibdb_req, sizeof(*ibdb_req));
	return SM_SUCCESS;
}

/*
**  IBDB_REQL_FREE -- free ibdb request list
**
**	Parameters:
**		ibdb_ctx -- IBDB context
**
**	Returns:
**		SM_SUCCESS
**
**	Locking:
**		ibdb_ctx (reql_pool) must be locked by caller
**
**	Last code review: 2005-03-31 22:35:30
**	Last code change:
*/

static sm_ret_T
ibdb_reql_free(ibdb_ctx_P ibdb_ctx)
{
	ibdb_req_P ibdb_req;

	SM_REQUIRE(ibdb_ctx != NULL);
	while (!IBDBREQL_EMPTY(&ibdb_ctx->ibdb_reql_pool)) {
		ibdb_req = IBDBREQL_FIRST(&ibdb_ctx->ibdb_reql_pool);
		IBDBREQL_REMOVE(&ibdb_ctx->ibdb_reql_pool);
		(void) ibdb_req_free(ibdb_req);
	}
	return SM_SUCCESS;
}

/*
**  IBDB_REQ_CANCEL -- cancel ibdb requests
**
**	Parameters:
**		ibdb_ctx -- IBDB context
**		ibdb_req_hd -- head of request list for IBDB to cancel
**
**	Returns:
**		SM_SUCCESS except for (un)lock errors
**
**	Locking:
**		ibdb_ctx (reql_pool) is locked during operation.
**
**	Last code review: 2005-03-31 22:37:35
**	Last code change:
*/

sm_ret_T
ibdb_req_cancel(ibdb_ctx_P ibdb_ctx, ibdb_req_hd_P ibdb_req_hd)
{
	sm_ret_T ret;
	int r;
	ibdb_req_P ibdb_req;

	if (IBDBREQL_EMPTY(ibdb_req_hd))
		return SM_SUCCESS;
	SM_REQUIRE(ibdb_ctx != NULL);
	r = pthread_mutex_lock(&ibdb_ctx->ibdb_mutex);
	SM_LOCK_OK(r);
	if (r != 0) {
		/* LOG? */
		return sm_error_perm(SM_EM_IBDB, r);
	}
	ret = SM_SUCCESS;

	/* Remove request list */
	while (!IBDBREQL_EMPTY(ibdb_req_hd)) {
		ibdb_req = IBDBREQL_FIRST(ibdb_req_hd);
		IBDBREQL_REMOVE(ibdb_req_hd);
		(void) ibdb_req_rel(ibdb_ctx, ibdb_req); /* always ok... */
	}
	r = pthread_mutex_unlock(&ibdb_ctx->ibdb_mutex);
	SM_ASSERT(0 == r);
	if (r != 0 && sm_is_success(ret))
		ret = sm_error_perm(SM_EM_IBDB, r);
	return ret;
}

/*
**  IBDB_CTX_FREE -- free IBDB context
**
**	Parameters:
**		ibdb_ctx -- ibdb context
**
**	Returns:
**		none.
**
**	Last code review: 2005-03-31 22:51:10
**	Last code change:
*/

static void
ibdb_ctx_free(ibdb_ctx_P ibdb_ctx)
{
	if (NULL == ibdb_ctx)
		return;
	SM_FREE(ibdb_ctx->ibdb_name);
	SM_FREE(ibdb_ctx->ibdb_fp_buf);
	SM_FREE(ibdb_ctx->ibdb_base_dir);
	SM_STR_FREE(ibdb_ctx->ibdb_path);
	SM_STR_FREE(ibdb_ctx->ibdb_name_rm);
	bht_destroy(ibdb_ctx->ibdb_ct, NULL, NULL);
	sm_free_size(ibdb_ctx, sizeof(*ibdb_ctx));
	return;
}

/*
**  IBDB_DIR_CRT -- create path for IBDB directory
**
**	Parameters:
**		base_dir -- name of base directory (can be NULL)
**		flags -- flags
**		pdir -- (pointer to) directory
**
**	Returns:
**
**	Last code review:
**	Last code change:
*/

sm_ret_T
ibdb_dir_crt(const char *base_dir, uint flags, char **pdir)
{
	size_t len;
	char *dir;

	SM_REQUIRE(pdir != NULL);
	len = IBDB_DIR_LEN;
	if (base_dir != NULL && *base_dir != '\0')
		len += strlen(base_dir) + 1;
	dir = (char *) sm_zalloc(len);
	if (dir != NULL && base_dir != NULL && *base_dir != '\0')
		strlcpy(dir, base_dir, len);
	if (dir != NULL) {
		if (IBDB_IS_OPEN_FLAG(flags, IBDB_OFL_WRITE))
			strlcat(dir, IBDB_WR_DIR, len);
		else if (IBDB_IS_OPEN_FLAG(flags, IBDB_OFL_RCVR))
			strlcat(dir, IBDB_REC_DIR, len);
		else if (IBDB_IS_OPEN_FLAG(flags, IBDB_OFL_CLEAN))
			strlcat(dir, IBDB_CL_DIR, len);
		else
			dir = ".";	/* Really?? abort instead? */
	}
	*pdir = dir;
	return SM_SUCCESS;
}

/*
**  IBDB_CTX_NEW -- allocate new IBDB context
**
**	Parameters:
**		base_dir -- name of base directory (can be NULL)
**		name -- base name, will be extended by seq
**		seq -- sequence number
**		flags -- flags
**
**	Returns:
**		NULL on error (out of memory)
**		otherwise pointer to new IBDB context
**
**	Last code review: 2005-03-31 22:50:37
**	Last code change: 2005-03-31 22:40:47
*/

static ibdb_ctx_P
ibdb_ctx_new(const char *base_dir, const char *name, uint32_t seq, uint flags)
{
	size_t l;
	ibdb_ctx_P ibdb_ctx;

	SM_ASSERT(name != NULL);

	ibdb_ctx = (ibdb_ctx_P) sm_zalloc(sizeof(*ibdb_ctx));
	if (NULL == ibdb_ctx)
		return NULL;
	ibdb_ctx->ibdb_ct = bht_new(IBC_HT_SIZE, IBC_HT_LIMIT);
	if (NULL == ibdb_ctx->ibdb_ct)
		goto error;
	l = 0;

	if (base_dir != NULL && *base_dir != '\0') {
		size_t lb;

		lb = strlen(base_dir) + 1;
		ibdb_ctx->ibdb_base_dir = (char *) sm_malloc(lb);
		if (NULL == ibdb_ctx->ibdb_base_dir)
			goto error;
		strlcpy(ibdb_ctx->ibdb_base_dir, base_dir, lb);
	}

	IBDB_DIR(ibdb_ctx->ibdb_dir, ibdb_ctx->ibdb_base_dir, flags);
	if (NULL == ibdb_ctx->ibdb_dir)
		goto error;

	l += strlen(name) + 1;
	ibdb_ctx->ibdb_name = (char *) sm_malloc(l);
	if (NULL == ibdb_ctx->ibdb_name)
		goto error;
	strlcpy(ibdb_ctx->ibdb_name, name, l);

	l += strlen(ibdb_ctx->ibdb_dir) + 1 + UINT32_LEN;
	ibdb_ctx->ibdb_path = sm_str_new(NULL, l, l);
	if (NULL == ibdb_ctx->ibdb_path)
		goto error;
	crt_ibdb_path(ibdb_ctx->ibdb_path, ibdb_ctx->ibdb_dir, name, seq);

	++l;
	ibdb_ctx->ibdb_name_rm = sm_str_new(NULL, l, l);
	if (NULL == ibdb_ctx->ibdb_name_rm)
		goto error;

	ibdb_ctx->ibdb_sequence = seq;
	ibdb_ctx->ibdb_first = seq;
	return ibdb_ctx;

  error:
	ibdb_ctx_free(ibdb_ctx);
	return NULL;
}

/*
**  IBDB_NEXT -- switch to next IBDB file
**
**	Parameters:
**		ibdb_ctx -- ibdb context
**
**	Returns:
**		usual sm_error code; I/O errors (flush, fsync, ...) et.al.
**
**	Side Effects: close fp, open next file, change free space, write
**		version number to file
**		does not undo any of these on error (that could occur
**		(almost) anywhere in the sequence)
**
**	Last code review: 2005-03-31 23:13:10
**	Last code change: 2005-11-20 17:27:27
*/

static sm_ret_T
ibdb_next(ibdb_ctx_P ibdb_ctx)
{
	sm_ret_T ret;
	sm_file_T *fp;

	SM_ASSERT(ibdb_ctx != NULL);
	ret = sm_io_flush(ibdb_ctx->ibdb_fp);
	if (sm_is_err(ret))
		return ret;
	ret = fsync(sm_io_fileno(ibdb_ctx->ibdb_fp));
	if (sm_is_err(ret))
		return ret;
	ret = sm_io_close(ibdb_ctx->ibdb_fp, SM_IO_CF_NONE);
	ibdb_ctx->ibdb_fp = NULL;
	if (sm_is_err(ret))
		return ret;

	if (ibdb_ctx->ibdb_fs_ctx != NULL) {
		ulong kbfree;

		ret = fs_chgfree(ibdb_ctx->ibdb_fs_ctx, ibdb_ctx->ibdb_fs_idx,
			(long) ((0L - (long) ibdb_ctx->ibdb_cursize) / ONEKB),
			&kbfree);
	}

	ibdb_ctx->ibdb_sequence++;
	sm_str_clr(ibdb_ctx->ibdb_path);
	crt_ibdb_path(ibdb_ctx->ibdb_path, ibdb_ctx->ibdb_dir,
		ibdb_ctx->ibdb_name, ibdb_ctx->ibdb_sequence);
	ret = sm_io_open(SmStStdio, sm_str_getdata(ibdb_ctx->ibdb_path),
			ibdb_ctx->ibdb_mode, &fp, SM_IO_WHAT_END);
	if (sm_is_err(ret))
		return ret;
	if (NULL != ibdb_ctx->ibdb_fp_buf) {
		ret = sm_io_setvbuf(fp, ibdb_ctx->ibdb_fp_buf, SM_IO_FBF,
				IBDB_BUF_SIZE);
		if (sm_is_err(ret))
			goto error;
	}

	/* only if writing? */
	ibdb_ctx->ibdb_version = IBDB_VERSION;
	ibdb_ctx->ibdb_fp = fp;
	ibdb_ctx->ibdb_cursize = 0;
#if 0
	ibdb_ctx->ibdb_maxsize = ?;
	ibdb_ctx->ibdb_created = now;
	ibdb_ctx->ibdb_touched = now;
#endif /* 0 */

	/* first: record type */
	ret = sm_io_fputuint32(fp, RT_IBDB_VERS_R);
	if (sm_is_err(ret))
		goto error;
	ret = sm_io_fputv(fp,
		SM_RCBV_INT, RT_IBDB_VERS_N, ibdb_ctx->ibdb_version,
		SM_RCBV_END);
	if (sm_is_err(ret))
		goto error;
	ret = sm_io_falign(fp, IBDB_REC_SIZE);
	if (sm_is_err(ret))
		goto error;

  error:
	/* no cleanup... many side effects possible! */
	return ret;
}

/*
**  IBDB_COMMIT -- commit ibdb backup to disk
**
**	Parameters:
**		ibdb_ctx -- ibdb context
**
**	Returns:
**		usual sm_error code; I/O errors (flush, fsync), (un)lock
**
**	Side Effects: flush/fsync file
**
**	Last code review: 2005-04-12 15:56:25
**	Last code change: 2005-04-12 15:56:14
*/

sm_ret_T
ibdb_commit(ibdb_ctx_P ibdb_ctx)
{
	sm_ret_T ret;
	int r;

	SM_ASSERT(ibdb_ctx != NULL);
	r = pthread_mutex_lock(&ibdb_ctx->ibdb_mutex);
	SM_LOCK_OK(r);
	if (r != 0) {
		/* LOG? */
		return sm_error_perm(SM_EM_IBDB, r);
	}

	if (ibdb_ctx->ibdb_changes > 0) {
		ret = sm_io_flush(ibdb_ctx->ibdb_fp);
		if (sm_is_err(ret))
			goto error;
#if HAVE_FDATASYNC
		r = fdatasync(sm_io_fileno(ibdb_ctx->ibdb_fp));
#else
		r = fsync(sm_io_fileno(ibdb_ctx->ibdb_fp));
#endif
		if (r != 0) {
			ret = sm_error_perm(SM_EM_IBDB, r);
			goto error;
		}
#if SM_IBDB_STATS
		++ibdb_ctx->ibdb_commits;
#endif
#if 0
		ibdb_ctx->ibdb_touched = now;
#endif
		ibdb_ctx->ibdb_changes = 0;
	}
	else
		ret = SM_SUCCESS;

  error:
	r = pthread_mutex_unlock(&ibdb_ctx->ibdb_mutex);
	SM_ASSERT(0 == r);
	if (r != 0 && sm_is_success(ret))
		ret = sm_error_perm(SM_EM_IBDB, r);
	return ret;
}

/*
**  IBDB_OPEN -- open ibdb backup on disk
**
**	Parameters:
**		base_dir -- name of base directory (can be NULL)
**		name -- base name, will be extended by seq
**		mode -- open mode
**		seq -- initial sequence number (> 0)
**		size -- maximum size (> 0)
**		flags -- flags
**		fs_ctx -- file system context (can be NULL)
**		pibdb_ctx -- (pointer to) ibdb context (output)
**
**	Returns:
**		usual sm_error code; ENOMEM, I/O errors
**
**	Last code review: 2005-03-31 23:58:30
**	Last code change: 2005-11-20 17:27:37
*/

sm_ret_T
ibdb_open(const char *base_dir, const char *name, int mode, uint32_t seq, size_t size, uint flags, fs_ctx_P fs_ctx, ibdb_ctx_P *pibdb_ctx)
{
	int r;
	sm_ret_T ret;
	sm_file_T *fp;
	size_t bufsize;
	uint fct_state;
	ibdb_ctx_P ibdb_ctx;
	struct stat sb;

#define FST_MUTEX_INIT	0x01	/* mutex is initialized */

	SM_REQUIRE(pibdb_ctx != NULL);
	SM_REQUIRE(seq > 0);
	SM_REQUIRE(size > 0);	/* larger minimum? */
	fp = NULL;
	fct_state = 0;
	ibdb_ctx = ibdb_ctx_new(base_dir, name, seq, flags);
	if (NULL == ibdb_ctx)
		return sm_error_temp(SM_EM_IBDB, ENOMEM);

	/* Check whether ibdb_ctx->ibdb_dir exists: if no: try to create it */
	r = stat(ibdb_ctx->ibdb_dir, &sb);
	if (r < 0) {
		r = sm_mkdir(ibdb_ctx->ibdb_dir, 0700);
		if (r < 0) {
			ret = sm_error_temp(SM_EM_IBDB, errno);
			goto error;
		}
	}

	ret = sm_io_open(SmStStdio, sm_str_getdata(ibdb_ctx->ibdb_path), mode,
			&fp, SM_IO_WHAT_END);
	if (sm_is_err(ret))
		goto error;
	(void) sm_whatbuf(fp, &bufsize);
	if (0 == bufsize || (bufsize % IBDB_REC_SIZE) != 0) {
		bufsize = (0 == bufsize)
			? IBDB_BUF_SIZE
			: (((bufsize / IBDB_REC_SIZE) + 1) * IBDB_REC_SIZE);

		ibdb_ctx->ibdb_fp_buf = sm_malloc(bufsize);
		if (NULL == ibdb_ctx->ibdb_fp_buf)
			goto error;
		ret = sm_io_setvbuf(fp, ibdb_ctx->ibdb_fp_buf, SM_IO_FBF,
				bufsize);
		if (sm_is_err(ret))
			goto error;
	}
	r = pthread_mutex_init(&ibdb_ctx->ibdb_mutex, SM_PTHREAD_MUTEXATTR);
	if (r != 0) {
		ret = sm_error_perm(SM_EM_IBDB, r);
		goto error;
	}
	SM_SET_FLAG(fct_state, FST_MUTEX_INIT);

	IBDBREQL_INIT(&ibdb_ctx->ibdb_reql_pool);
	/* preallocate some entries?? */

	if (fs_ctx != NULL) {
		ibdb_ctx->ibdb_fs_ctx = fs_ctx;
		ret = fs_new(fs_ctx, ibdb_ctx->ibdb_dir, &ibdb_ctx->ibdb_fs_idx);
		if (sm_is_err(ret))
			goto error;
	}

	/* only if writing? */
	ibdb_ctx->ibdb_version = IBDB_VERSION;
	ibdb_ctx->ibdb_fp = fp;
	ibdb_ctx->ibdb_maxsize = size;
	ibdb_ctx->ibdb_mode = mode;
/*
//	ibdb_ctx->ibdb_created;
//	ibdb_ctx->ibdb_touched;
*/

	/* first: record type */
	ret = sm_io_fputuint32(fp, RT_IBDB_VERS_R);
	if (sm_is_err(ret))
		goto error;
	ret = sm_io_fputv(fp,
		SM_RCBV_INT, RT_IBDB_VERS_N, ibdb_ctx->ibdb_version,
		SM_RCBV_END);
	if (sm_is_err(ret))
		goto error;
	ret = sm_io_falign(fp, IBDB_REC_SIZE);
	if (sm_is_err(ret))
		goto error;

	*pibdb_ctx = ibdb_ctx;
	return ret;

  error:
	/* note: fs_new() not undone on error */
	if (fp != NULL) {
		(void) sm_io_close(fp, SM_IO_CF_NONE);
		fp = NULL;
	}
	if (SM_IS_FLAG(fct_state, FST_MUTEX_INIT)) {
		(void) pthread_mutex_destroy(&ibdb_ctx->ibdb_mutex);
		SM_CLR_FLAG(fct_state, FST_MUTEX_INIT);
	}
	ibdb_ctx_free(ibdb_ctx);
	return ret;
#undef FST_MUTEX_INIT
}

/*
**  We could put the stuff ourselves in uio and call fvwrite() just once.
**  That would require that we store the constants in an int array.
**  We could also optimize and write putrecordTYPE() functions that put
**  and int, str, char * into the buffer.
*/

/*
**  IBDB_HDRMOD_WR -- write header modification record
**
**	Parameters:
**		ibdb_ctx -- INCEDB context
**		ibdb_hdrmod -- header modification data
**		flags -- flags
**		locktype -- kind of locking
**
**	Returns:
**		usual sm_error code; ENOMEM (for new ta), I/O errors
**
**	Last code review:
**	Last code change:
*/

sm_ret_T
ibdb_hdrmod_wr(ibdb_ctx_P ibdb_ctx, ibdb_hdrmod_P ibdb_hdrmod, uint flags, thr_lock_T locktype)
{
	sm_file_T *fp;
	sm_ret_T ret;
	int r;

	SM_ASSERT(ibdb_ctx != NULL);
	SM_ASSERT(ibdb_hdrmod != NULL);

	if (thr_lock_it(locktype)) {
		r = pthread_mutex_lock(&ibdb_ctx->ibdb_mutex);
		SM_LOCK_OK(r);
		if (r != 0) {
			/* LOG? */
			return sm_error_perm(SM_EM_IBDB, r);
		}
	}
	if (!ibdb_is_noroll(flags) &&
	    ibdb_ctx->ibdb_cursize >= ibdb_ctx->ibdb_maxsize)
	{
		/* roll-over */
		ret = ibdb_next(ibdb_ctx);
		if (sm_is_err(ret))
			goto error;
	}
	fp = ibdb_ctx->ibdb_fp;

	/* first: record type */
	ret = sm_io_fputuint32(fp, RT_IBDB_HDRMOD);
	if (sm_is_err(ret))
		goto error;

	/* header modification */
	ret = sm_io_fputv(fp,
		SM_RCBV_BUF, RT_IBDB_HM_TAID,
			(uchar *) ibdb_hdrmod->ibh_ta_id,
			strlen(ibdb_hdrmod->ibh_ta_id), /* use constant?? */
		SM_RCBV_INT, RT_IBDB_HM_TYPE,
			(uint32_t) ibdb_hdrmod->ibh_type,
		SM_RCBV_INT, RT_IBDB_HM_POS, ibdb_hdrmod->ibh_pos,
		SM_RCBV_CSTR, RT_IBDB_HM_HDR, ibdb_hdrmod->ibh_hdr,
		SM_RCBV_END);
	if (sm_is_err(ret))
		goto error;
	ret = sm_io_falign(fp, IBDB_REC_SIZE);
	if (sm_is_err(ret))
		goto error;
#if 0
	ibdb_ctx->ibdb_touched = now;
#endif
	ibdb_ctx->ibdb_cursize += IBDB_REC_SIZE;
	ibdb_ctx->ibdb_changes++;

	ret = SM_SUCCESS;
	/* fall through for unlocking */

  error:
	if ((!sm_is_err(ret) && thr_unl_no_err(locktype))
	    || (sm_is_err(ret) && thr_unl_if_err(locktype)))
	{
		r = pthread_mutex_unlock(&ibdb_ctx->ibdb_mutex);
		SM_ASSERT(0 == r);
		if (r != 0 && sm_is_success(ret))
			ret = sm_error_perm(SM_EM_IBDB, r);
	}

	/* cleanup? */
	return ret;
}

/*
**  IBDB_HDRMODL_WR -- write header modification list
**
**	Parameters:
**		ibdb_ctx -- INCEDB context
**		hdrmodhd -- header of header modification list
**		ta_id -- ta_id
**		flags -- flags
**		locktype -- kind of locking
**
**	Returns:
**		usual sm_error code; ENOMEM (for new ta), I/O errors
**
**	Last code review:
**	Last code change:
*/

sm_ret_T
ibdb_hdrmodl_wr(ibdb_ctx_P ibdb_ctx, sm_hdrmodhd_P sm_hdrmodhd, sessta_id_T ta_id, uint flags, thr_lock_T locktype)
{
	sm_file_T *fp;
	sm_ret_T ret;
	int r;
	sm_hdrmod_P sm_hdrmod;

	if (NULL == sm_hdrmodhd)
		return SM_SUCCESS;
	SM_ASSERT(ibdb_ctx != NULL);

	if (thr_lock_it(locktype)) {
		r = pthread_mutex_lock(&ibdb_ctx->ibdb_mutex);
		SM_LOCK_OK(r);
		if (r != 0) {
			/* LOG? */
			return sm_error_perm(SM_EM_IBDB, r);
		}
	}
	if (!ibdb_is_noroll(flags) &&
	    ibdb_ctx->ibdb_cursize >= ibdb_ctx->ibdb_maxsize)
	{
		/* roll-over */
		ret = ibdb_next(ibdb_ctx);
		if (sm_is_err(ret))
			goto error;
	}

	fp = ibdb_ctx->ibdb_fp;
	for (sm_hdrmod = HDRMODL_FIRST(sm_hdrmodhd);
	     sm_hdrmod != HDRMODL_END(sm_hdrmodhd);
	     sm_hdrmod = HDRMODL_NEXT(sm_hdrmod))
	{
		/* first: record type */
		ret = sm_io_fputuint32(fp, RT_IBDB_HDRMOD);
		if (sm_is_err(ret))
			goto error;

		/* header modification */
		ret = sm_io_fputv(fp,
			SM_RCBV_BUF, RT_IBDB_HM_TAID, (uchar *) ta_id,
				strlen(ta_id), /* use constant?? */
			SM_RCBV_INT, RT_IBDB_HM_TYPE,
				(uint32_t) sm_hdrmod->sm_hm_type,
			SM_RCBV_INT, RT_IBDB_HM_POS, sm_hdrmod->sm_hm_pos,
			/* this may exceed the record size??? */
				SM_RCBV_CSTR, RT_IBDB_HM_HDR,
					sm_hdrmod->sm_hm_hdr,
			SM_RCBV_END);
		if (sm_is_err(ret))
			goto error;
		ret = sm_io_falign(fp, IBDB_REC_SIZE);
		if (sm_is_err(ret))
			goto error;
		ibdb_ctx->ibdb_cursize += IBDB_REC_SIZE;
		ibdb_ctx->ibdb_changes++;
	}

	ret = SM_SUCCESS;
	/* fall through for unlocking */

  error:
	if ((!sm_is_err(ret) && thr_unl_no_err(locktype))
	    || (sm_is_err(ret) && thr_unl_if_err(locktype)))
	{
		r = pthread_mutex_unlock(&ibdb_ctx->ibdb_mutex);
		SM_ASSERT(0 == r);
		if (r != 0 && sm_is_success(ret))
			ret = sm_error_perm(SM_EM_IBDB, r);
	}

	/* cleanup? */
	return ret;
}

/*
**  IBDB_TA_STATUS -- write transaction status
**
**	Parameters:
**		ibdb_ctx -- INCEDB context
**		ibdb_ta -- transaction (sender) data
**		status -- transaction status
**		flags -- flags
**		rcpt_idx_high -- highest rcpt idx (only for cancel)
**		locktype -- kind of locking
**
**	Returns:
**		usual sm_error code; ENOMEM (for new ta), I/O errors
**
**	Last code review: 2005-04-01 00:01:25
**	Last code change: 2006-08-05 04:30:39
*/

sm_ret_T
ibdb_ta_status(ibdb_ctx_P ibdb_ctx, ibdb_ta_P ibdb_ta, int status, uint flags, rcpt_idx_T rcpt_idx_high, thr_lock_T locktype)
{
	sm_file_T *fp;
	sm_ret_T ret;
	int r;

	SM_ASSERT(ibdb_ctx != NULL);
	SM_ASSERT(ibdb_ta != NULL);

	if (thr_lock_it(locktype)) {
		r = pthread_mutex_lock(&ibdb_ctx->ibdb_mutex);
		SM_LOCK_OK(r);
		if (r != 0) {
			/* LOG? */
			return sm_error_perm(SM_EM_IBDB, r);
		}
	}
	if (!ibdb_is_noroll(flags) &&
	    ibdb_ctx->ibdb_cursize >= ibdb_ctx->ibdb_maxsize)
	{
		/* roll-over */
		ret = ibdb_next(ibdb_ctx);
		if (sm_is_err(ret))
			goto error;
	}
	fp = ibdb_ctx->ibdb_fp;

	/* first: record type */
	ret = sm_io_fputuint32(fp, RT_IBDB_TA);
	if (sm_is_err(ret))
		goto error;

	/* transaction status */
	ret = sm_io_fputv(fp,
		SM_RCBV_INT, RT_IBDB_TA_ST, status,
		SM_RCBV_BUF, RT_IBDB_TAID, (uchar *) ibdb_ta->ibt_ta_id,
			strlen(ibdb_ta->ibt_ta_id), /* use constant?? */
		SM_RCBV_CSTR, RT_IBDB_CDBID, ibdb_ta->ibt_cdb_id,
		SM_RCBV_INT, RT_IBDB_NRCPTS, ibdb_ta->ibt_nrcpts,
	/* this may exceed the record size??? */
		SM_RCBV_STR, RT_IBDB_MAIL, ibdb_ta->ibt_mail_pa,
		SM_RCBV_END);
	if (sm_is_err(ret))
		goto error;
	ret = sm_io_falign(fp, IBDB_REC_SIZE);
	if (sm_is_err(ret))
		goto error;
#if 0
	ibdb_ctx->ibdb_touched = now;
#endif
	ibdb_ctx->ibdb_cursize += IBDB_REC_SIZE;
	ibdb_ctx->ibdb_changes++;

	if (IBDB_TA_NEW == status)
		ret = sm_ibc_ta_add(ibdb_ctx->ibdb_ct, ibdb_ta->ibt_ta_id,
			ibdb_ctx->ibdb_sequence);
	else if (IBDB_TA_CANCEL == status) {
		rcpt_idx_T u, lim;

		/* remove all rcpts from cache */
		lim = SM_MAX(rcpt_idx_high + 1, ibdb_ta->ibt_nrcpts);
		for (u = 0; u < lim; u++)
			(void) sm_ibc_rcpt_rm(ibdb_ctx->ibdb_ct, ibdb_ta->ibt_ta_id, u);
	}
	else if (status != IBDB_TA_CANCEL)
		ret = sm_ibc_ta_rm(ibdb_ctx->ibdb_ct, ibdb_ta->ibt_ta_id);
	if (sm_is_err(ret))
		goto error;

#if SM_IBDB_STATS
	if (IBDB_TA_NEW == status)
		ibdb_ctx->ibdb_opentas++;
	else if (status != IBDB_TA_CANCEL) {
		SM_ASSERT(ibdb_ctx->ibdb_opentas > 0);
		ibdb_ctx->ibdb_opentas--;
	}
#endif /* SM_IBDB_STATS */

	ret = SM_SUCCESS;
	if (IBDB_IS_FLAG(flags, IBDB_FL_CLEAN))
		ret = ibdb_clean(ibdb_ctx, THR_NO_LOCK);
	/* fall through for unlocking */

  error:
	if ((!sm_is_err(ret) && thr_unl_no_err(locktype))
	    || (sm_is_err(ret) && thr_unl_if_err(locktype)))
	{
		r = pthread_mutex_unlock(&ibdb_ctx->ibdb_mutex);
		SM_ASSERT(0 == r);
		if (r != 0 && sm_is_success(ret))
			ret = sm_error_perm(SM_EM_IBDB, r);
	}

	/* cleanup? */
	return ret;
}

/*
**  IBDB_RCPT_STATUS -- write recipient status
**
**	Parameters:
**		ibdb_ctx -- INCEDB context
**		ibdb_rcpt -- recipient data
**		status -- recipient status
**		flags -- flags
**		locktype -- kind of locking
**
**	Side Effects:
**		writes directly to IBDB (not undone on error; theoretically
**		this could be tried with fgetpos(), fsetpos())
**
**	Returns:
**		usual sm_error code; ENOMEM (for new rcpt), I/O errors
**
**	Last code review: 2005-03-31 05:38:59
**	Last code change: 2005-03-30 23:08:35
*/

sm_ret_T
ibdb_rcpt_status(ibdb_ctx_P ibdb_ctx, ibdb_rcpt_P ibdb_rcpt, int status, uint flags, thr_lock_T locktype)
{
	sm_file_T *fp;
	sm_ret_T ret;
	int r;

	SM_ASSERT(ibdb_ctx != NULL);
	SM_ASSERT(ibdb_rcpt != NULL);

	if (thr_lock_it(locktype)) {
		r = pthread_mutex_lock(&ibdb_ctx->ibdb_mutex);
		SM_LOCK_OK(r);
		if (r != 0) {
			/* LOG? */
			return sm_error_perm(SM_EM_IBDB, r);
		}
	}
	if (!ibdb_is_noroll(flags) &&
	    ibdb_ctx->ibdb_cursize >= ibdb_ctx->ibdb_maxsize)
	{
		/* roll-over */
		ret = ibdb_next(ibdb_ctx);
		if (sm_is_err(ret))
			goto error;
	}
	fp = ibdb_ctx->ibdb_fp;

	/* first: record type */
	ret = sm_io_fputuint32(fp, RT_IBDB_RCPT);
	if (sm_is_err(ret))
		goto error;

	/* recipient status */
	ret = sm_io_fputv(fp,
		SM_RCBV_INT, RT_IBDB_RCPT_ST, status,
		SM_RCBV_INT, RT_IBDB_RCPT_IDX, ibdb_rcpt->ibr_idx,
		SM_RCBV_BUF, RT_IBDB_TAID, (uchar *) ibdb_rcpt->ibr_ta_id,
		strlen(ibdb_rcpt->ibr_ta_id), /* use constant?? */
	/* this may exceed the record size??? */
		SM_RCBV_STR, RT_IBDB_RCPT_PA, ibdb_rcpt->ibr_pa,
		SM_RCBV_END);
	if (sm_is_err(ret))
		goto error;
	ret = sm_io_falign(fp, IBDB_REC_SIZE);
	if (sm_is_err(ret))
		goto error;

#if 0
	ibdb_ctx->ibdb_touched = now;
#endif
	ibdb_ctx->ibdb_cursize += IBDB_REC_SIZE;
	ibdb_ctx->ibdb_changes++;

	if (IBDB_RCPT_NEW == status)
		ret = sm_ibc_rcpt_add(ibdb_ctx->ibdb_ct, ibdb_rcpt->ibr_ta_id,
			ibdb_rcpt->ibr_idx, ibdb_ctx->ibdb_sequence);
	else
		ret = sm_ibc_rcpt_rm(ibdb_ctx->ibdb_ct, ibdb_rcpt->ibr_ta_id,
			ibdb_rcpt->ibr_idx);
	if (sm_is_err(ret))
		goto error;

#if SM_IBDB_STATS
	if (IBDB_RCPT_NEW == status)
		ibdb_ctx->ibdb_openrcpts++;
	else {
		SM_ASSERT(ibdb_ctx->ibdb_openrcpts > 0);
		ibdb_ctx->ibdb_openrcpts--;
	}
#endif /* SM_IBDB_STATS */

	ret = SM_SUCCESS;
	if (IBDB_IS_FLAG(flags, IBDB_FL_CLEAN))
		ret = ibdb_clean(ibdb_ctx, THR_NO_LOCK);
	/* fall through for unlocking */

  error:
	if ((!sm_is_err(ret) && thr_unl_no_err(locktype))
	    || (sm_is_err(ret) && thr_unl_if_err(locktype)))
	{
		r = pthread_mutex_unlock(&ibdb_ctx->ibdb_mutex);
		SM_ASSERT(0 == r);
		if (r != 0 && sm_is_success(ret))
			ret = sm_error_perm(SM_EM_IBDB, r);
	}
	return ret;
}

/*
**  IBDB_UNLINK -- unlink ibdb files
**
**	Parameters:
**		ibdb_ctx -- ibdb context
**		first -- first sequence number to unlink
**		last -- last sequence number to unlink
**
**	Returns:
**		SM_SUCCESS (unlink() errors are ignored)
**
**	Locking: ibdb_ctx must be locked by caller.
**
**	Last code review: 2005-03-31 05:00:22
**	Last code change:
*/

static sm_ret_T
ibdb_unlink(ibdb_ctx_P ibdb_ctx, size_t first, size_t last)
{
	size_t i;

	SM_ASSERT(ibdb_ctx != NULL);
	for (i = first; i <= last; i++) {
		sm_str_clr(ibdb_ctx->ibdb_name_rm);
		crt_ibdb_path(ibdb_ctx->ibdb_name_rm, ibdb_ctx->ibdb_dir,
			ibdb_ctx->ibdb_name, i);
		(void) unlink((const char *) sm_str_getdata(ibdb_ctx->ibdb_name_rm));
		sm_str_clr(ibdb_ctx->ibdb_name_rm);
	}

	if (ibdb_ctx->ibdb_fs_ctx != NULL && last >= first) {
		ulong kbfree;

		/* This is just an estimate! The actual size may vary. */
		(void) fs_chgfree(ibdb_ctx->ibdb_fs_ctx, ibdb_ctx->ibdb_fs_idx,
			(ibdb_ctx->ibdb_maxsize * (last - first + 1)) / ONEKB,
			&kbfree);
	}
	return SM_SUCCESS;
}

/*
**  IBDB_CLOSE -- close ibdb
**
**	Parameters:
**		ibdb_ctx -- ibdb context
**
**	Returns:
**		usual sm_error code, only from sm_io_close()
**
**	Locking:
**		Does NOT lock ibdb_ctx because it shuts down IBDB.
**		Should it try to get the lock at least?
**
**	Last code review: 2005-04-01 00:03:17
**	Last code change:
*/

sm_ret_T
ibdb_close(ibdb_ctx_P ibdb_ctx)
{
	sm_ret_T ret;
	sm_file_T *fp;

	SM_ASSERT(ibdb_ctx != NULL);
	(void) ibdb_reql_free(ibdb_ctx);
	fp = ibdb_ctx->ibdb_fp;
	if (fp != NULL) {
		ret = sm_io_close(fp, SM_IO_CF_NONE);
		ibdb_ctx->ibdb_fp = NULL;
		if (sm_is_err(ret))
			return ret;
	}
#if SM_IBDB_STATS
	/*
	**  If there are no open transactions and no open recipients
	**  then unlink all ibdb files except for the last one
	**  because one file is needed to keep track of the last used id
	**  (see qm_rdibdb()).
	*/

	if (ibdb_ctx->ibdb_opentas == 0 && ibdb_ctx->ibdb_openrcpts == 0
	    && ibdb_ctx->ibdb_first + 1 <= ibdb_ctx->ibdb_sequence)
	{
		ibdb_unlink(ibdb_ctx, ibdb_ctx->ibdb_first,
			ibdb_ctx->ibdb_sequence - 1);
	}
#endif /* SM_IBDB_STATS */
	(void) pthread_mutex_destroy(&ibdb_ctx->ibdb_mutex);

	ibdb_ctx_free(ibdb_ctx);
	return SM_SUCCESS;
}

/*
**  IBDB_HDRMOD_APP -- append header modification data to request list
**
**	Parameters:
**		ibdb_ctx -- IBDB context
**		ibdb_hdrmod -- header modification data
**		ibdb_req_hd -- request list
**
**	Side Effects: none on error (except if unlock fails)
**
**	Returns:
**		usual sm_error code; ENOMEM, (un)lock
**
**	Locking:
**		locks ibdb_ctx during operation
**
**	Last code review:
**	Last code change:
*/

sm_ret_T
ibdb_hdrmod_app(ibdb_ctx_P ibdb_ctx, ibdb_hdrmod_P ibdb_hdrmod, ibdb_req_hd_P ibdb_req_hd)
{
	sm_ret_T ret;
	int r;
	ibdb_req_P ibdb_req;

	SM_ASSERT(ibdb_ctx != NULL);
	SM_ASSERT(ibdb_hdrmod != NULL);

	r = pthread_mutex_lock(&ibdb_ctx->ibdb_mutex);
	SM_LOCK_OK(r);
	if (r != 0) {
		/* LOG? */
		return sm_error_perm(SM_EM_IBDB, r);
	}

	ret = ibdb_req_new(ibdb_ctx, &ibdb_req);
	if (sm_is_err(ret))
		goto error;
	/* note: We could unlock here, except for releasing req in error case */

	SESSTA_COPY(ibdb_req->ibdb_req_ss_ta_id, ibdb_hdrmod->ibh_ta_id);
	ibdb_req->ibh_req_hdr = SM_CSTR_DUP(ibdb_hdrmod->ibh_hdr);
	if (NULL == ibdb_req->ibh_req_hdr) {
		(void) ibdb_req_free(ibdb_req);	/* always ok */
		ret = sm_error_temp(SM_EM_IBDB, ENOMEM);
		goto error;
	}
	ibdb_req->ibh_req_pos = ibdb_hdrmod->ibh_pos;
	ibdb_req->ibh_req_type = ibdb_hdrmod->ibh_type;
	ibdb_req->ibdb_req_type = IBDB_REQ_HDRMOD;
	IBDBREQL_APP(ibdb_req_hd, ibdb_req);

	/* fall through for unlocking */
  error:
	r = pthread_mutex_unlock(&ibdb_ctx->ibdb_mutex);
	SM_ASSERT(0 == r);
	if (r != 0 && sm_is_success(ret))
		ret = sm_error_perm(SM_EM_IBDB, r);
	return ret;
}

/*
**  IBDB_TA_APP -- append transaction (status) to request list
**
**	Parameters:
**		ibdb_ctx -- IBDB context
**		ibdb_ta -- transaction (sender) data
**		ibdb_req_hd -- request list
**		status -- transaction status
**
**	Side Effects: none on error (except if unlock fails)
**
**	Returns:
**		usual sm_error code; ENOMEM, (un)lock
**
**	Locking:
**		locks ibdb_ctx during operation
**
**	Last code review: 2005-03-31 18:33:26
**	Last code change:
*/

sm_ret_T
ibdb_ta_app(ibdb_ctx_P ibdb_ctx, ibdb_ta_P ibdb_ta, ibdb_req_hd_P ibdb_req_hd, int status)
{
	sm_ret_T ret;
	int r;
	ibdb_req_P ibdb_req;

	SM_ASSERT(ibdb_ctx != NULL);
	SM_ASSERT(ibdb_ta != NULL);

	r = pthread_mutex_lock(&ibdb_ctx->ibdb_mutex);
	SM_LOCK_OK(r);
	if (r != 0) {
		/* LOG? */
		return sm_error_perm(SM_EM_IBDB, r);
	}

	ret = ibdb_req_new(ibdb_ctx, &ibdb_req);
	if (sm_is_err(ret))
		goto error;
	/* note: We could unlock here, except for releasing req in error case */

	SESSTA_COPY(ibdb_req->ibdb_req_ss_ta_id, ibdb_ta->ibt_ta_id);
	ibdb_req->ibdb_req_addr_pa = sm_str_dup(NULL, ibdb_ta->ibt_mail_pa);
	if (NULL == ibdb_req->ibdb_req_addr_pa) {
		(void) ibdb_req_free(ibdb_req);	/* always ok */
		ret = sm_error_temp(SM_EM_IBDB, ENOMEM);
		goto error;
	}
	ibdb_req->ibdb_req_cdb_id = SM_CSTR_DUP(ibdb_ta->ibt_cdb_id);
	ibdb_req->ibdb_req_nrcpts = ibdb_ta->ibt_nrcpts;
	ibdb_req->ibdb_req_status = status;
	ibdb_req->ibdb_req_type = IBDB_REQ_TA;
	IBDBREQL_APP(ibdb_req_hd, ibdb_req);

	/* fall through for unlocking */
  error:
	r = pthread_mutex_unlock(&ibdb_ctx->ibdb_mutex);
	SM_ASSERT(0 == r);
	if (r != 0 && sm_is_success(ret))
		ret = sm_error_perm(SM_EM_IBDB, r);
	return ret;
}

/*
**  IBDB_RCPT_APP -- append recipient (status) to request list
**
**	Parameters:
**		ibdb_ctx -- IBDB context
**		ibdb_rcpt -- recipient data
**		ibdb_req_hd -- request list
**		status -- recipient status
**
**	Side Effects: none on error (except if unlock fails)
**
**	Returns:
**		usual sm_error code; ENOMEM
**
**	Locking:
**		locks ibdb_ctx during operation
**
**	Last code review: 2005-04-01 00:05:27
**	Last code change:
*/

sm_ret_T
ibdb_rcpt_app(ibdb_ctx_P ibdb_ctx, ibdb_rcpt_P ibdb_rcpt, ibdb_req_hd_P ibdb_req_hd, int status)
{
	ibdb_req_P ibdb_req;
	sm_ret_T ret;
	int r;

	SM_ASSERT(ibdb_ctx != NULL);
	SM_ASSERT(ibdb_rcpt != NULL);

	r = pthread_mutex_lock(&ibdb_ctx->ibdb_mutex);
	SM_LOCK_OK(r);
	if (r != 0) {
		/* LOG? */
		return sm_error_perm(SM_EM_IBDB, r);
	}

	ret = ibdb_req_new(ibdb_ctx, &ibdb_req);
	if (sm_is_err(ret))
		goto error;
	/* note: We could unlock here, except for releasing req in error case */

	SESSTA_COPY(ibdb_req->ibdb_req_ss_ta_id, ibdb_rcpt->ibr_ta_id);
	ibdb_req->ibdb_req_addr_pa = sm_str_dup(NULL, ibdb_rcpt->ibr_pa);
	if (NULL == ibdb_req->ibdb_req_addr_pa) {
		ibdb_req_free(ibdb_req);
		ret = sm_error_temp(SM_EM_IBDB, ENOMEM);
		goto error;
	}
	ibdb_req->ibdb_req_rcpt_idx = ibdb_rcpt->ibr_idx;
	ibdb_req->ibdb_req_status = status;
	ibdb_req->ibdb_req_type = IBDB_REQ_RCPT;
	IBDBREQL_APP(ibdb_req_hd, ibdb_req);

  error:
	r = pthread_mutex_unlock(&ibdb_ctx->ibdb_mutex);
	SM_ASSERT(0 == r);
	if (r != 0 && sm_is_success(ret))
		ret = sm_error_perm(SM_EM_IBDB, r);

	/* cleanup? */
	return ret;
}

/*
**  IBDB_WR_STATUS -- write status (request list)
**
**	Parameters:
**		ibdb_ctx -- IBDB context
**		ibdb_req_hd -- request list
**
**	Returns:
**		usual sm_error code; ENOMEM (for new rcpt/ta), I/O errors
**
**	Side Effects:
**		writes to IBDB, does not undo changes when an error occurs,
**		see ibdb_{ta,rcpt}_status()
**
**	Locking:
**		locks ibdb_ctx during operation
**
**	Last code review: 2005-04-01 00:54:12
**	Last code change:
*/

sm_ret_T
ibdb_wr_status(ibdb_ctx_P ibdb_ctx, ibdb_req_hd_P ibdb_req_hd)
{
	sm_ret_T ret;
	int r;
	ibdb_req_P ibdb_req;
	ibdb_rcpt_T ibdb_rcpt;
	ibdb_ta_T ibdb_ta;
	ibdb_hdrmod_T ibdb_hdrmod;

	SM_ASSERT(ibdb_ctx != NULL);
	SM_ASSERT(ibdb_req_hd != NULL);
	if  (IBDBREQL_EMPTY(ibdb_req_hd))
		return SM_SUCCESS;

	r = pthread_mutex_lock(&ibdb_ctx->ibdb_mutex);
	SM_LOCK_OK(r);
	if (r != 0) {
		/* LOG? */
		return sm_error_perm(SM_EM_IBDB, r);
	}

	ret = SM_SUCCESS;
	if  (IBDBREQL_EMPTY(ibdb_req_hd))
		goto done;

	/* walk through request list and write them to IBDB */
	for (ibdb_req = IBDBREQL_FIRST(ibdb_req_hd);
	     ibdb_req != IBDBREQL_END(ibdb_req_hd);
	     ibdb_req = IBDBREQL_NEXT(ibdb_req))
	{
		/* Check type */
		switch (ibdb_req->ibdb_req_type) {
		   case IBDB_REQ_RCPT:
			ibdb_rcpt.ibr_ta_id = ibdb_req->ibdb_req_ss_ta_id;
			ibdb_rcpt.ibr_pa = ibdb_req->ibdb_req_addr_pa;
			ibdb_rcpt.ibr_idx = ibdb_req->ibdb_req_rcpt_idx;
			ret = ibdb_rcpt_status(ibdb_ctx, &ibdb_rcpt,
				ibdb_req->ibdb_req_status, IBDB_FL_NOROLL,
				THR_NO_LOCK);
			break;

		   case IBDB_REQ_TA:
			ibdb_ta.ibt_ta_id = ibdb_req->ibdb_req_ss_ta_id;
			ibdb_ta.ibt_mail_pa = ibdb_req->ibdb_req_addr_pa;
			ibdb_ta.ibt_cdb_id = ibdb_req->ibdb_req_cdb_id;
			ibdb_ta.ibt_nrcpts = ibdb_req->ibdb_req_nrcpts;
			ret = ibdb_ta_status(ibdb_ctx, &ibdb_ta,
				ibdb_req->ibdb_req_status, IBDB_FL_NOROLL,
				0, THR_NO_LOCK);
			break;

		   case IBDB_REQ_HDRMOD:
			ibdb_hdrmod.ibh_ta_id = ibdb_req->ibdb_req_ss_ta_id;
			ibdb_hdrmod.ibh_hdr = ibdb_req->ibh_req_hdr;
			ibdb_hdrmod.ibh_pos = ibdb_req->ibh_req_pos;
			ibdb_hdrmod.ibh_type = ibdb_req->ibh_req_type;
			ret = ibdb_hdrmod_wr(ibdb_ctx, &ibdb_hdrmod,
				IBDB_FL_NOROLL, THR_NO_LOCK);
			break;

		   default:
			/*
			**  XXX Really abort? May screw up DB...
			**  How about logging a fatal error and returning
			**  that to the caller?
			*/

			sm_abort("wrong ibdb_req_type %d",
				ibdb_req->ibdb_req_type);
			ret = sm_error_perm(SM_EM_IBDB, SM_E_UNEXPECTED);
			break;
		}
		if (sm_is_err(ret))
			goto error;
	}
	/* Commit it? Not really necessary... */

	/* Everything is ok: remove requests from list */
	while (!IBDBREQL_EMPTY(ibdb_req_hd)) {
		ibdb_req = IBDBREQL_FIRST(ibdb_req_hd);
		IBDBREQL_REMOVE(ibdb_req_hd);
		(void) ibdb_req_rel(ibdb_ctx, ibdb_req); /* always ok... */
	}

	/* Always try to clean? */
	ret = ibdb_clean(ibdb_ctx, THR_NO_LOCK);

	/* Fall through for unlocking */
  done:
  error:
	r = pthread_mutex_unlock(&ibdb_ctx->ibdb_mutex);
	SM_ASSERT(0 == r);
	if (r != 0 && sm_is_success(ret))
		ret = sm_error_perm(SM_EM_IBDB, r);

	/* cleanup? */
	return ret;
}

/*
**  IBDB_CLEAN -- cleanup IBDB
**	Determine lowest sequence number that is still in use and remove
**	all "older" files.
**
**	Parameters:
**		ibdb_ctx -- ibdb context
**		locktype -- kind of locking
**
**	Returns:
**		usual sm_error code; only (un)lock
**
**	Locking: locks ibdb_ctx if requested.
**
**	Side Effects:
**		unlinks ibdb files that are no longer needed.
**
**	Last code review: 2005-03-31 05:05:02
**	Last code change:
*/

sm_ret_T
ibdb_clean(ibdb_ctx_P ibdb_ctx, thr_lock_T locktype)
{
	sm_ret_T ret;
	int r;
	uint32_t seq;

	SM_ASSERT(ibdb_ctx != NULL);
	if (thr_lock_it(locktype)) {
		r = pthread_mutex_lock(&ibdb_ctx->ibdb_mutex);
		SM_LOCK_OK(r);
		if (r != 0) {
			/* LOG? */
			return sm_error_perm(SM_EM_IBDB, r);
		}
	}
	ret = SM_SUCCESS;

	/* Not worth to do anything? At least SEQ_MIN_PURGE files? */
	if (ibdb_ctx->ibdb_sequence - ibdb_ctx->ibdb_first <= SEQ_MIN_PURGE ||
	    ibdb_ctx->ibdb_sequence - ibdb_ctx->ibdb_cleanrun <= SEQ_MIN_CLEAN)
		goto unlock;
	ibdb_ctx->ibdb_cleanrun = ibdb_ctx->ibdb_sequence;
	ret = sm_ibc_low_seq(ibdb_ctx->ibdb_ct, &seq); /* always OK */
	if (sm_is_err(ret))
		goto unlock;
#if 0
	sm_io_fprintf(smioerr, "ibdb_clean: first=%u, seq=%u, cur=%u\n",
		ibdb_ctx->ibdb_first, seq, ibdb_ctx->ibdb_sequence);
#endif

	/* no reference at all? remove everything but current file */
	if (0 == seq) {
		/* make sure seq is > 0 */
		if (ibdb_ctx->ibdb_sequence <= 1)
			goto unlock;
		seq = ibdb_ctx->ibdb_sequence - 1;
	}
	if (seq + 1 <= ibdb_ctx->ibdb_first || seq >= ibdb_ctx->ibdb_sequence
	    || seq == 0)
		goto unlock;
	ret = ibdb_unlink(ibdb_ctx, ibdb_ctx->ibdb_first, seq - 1);
	if (sm_is_err(ret))			/* always OK */
		goto unlock;
#if 0
	sm_io_fprintf(smioerr, "ibdb_clean ok: first=%u, seq=%u, cur=%u\n",
		ibdb_ctx->ibdb_first, seq, ibdb_ctx->ibdb_sequence);
#endif
	ibdb_ctx->ibdb_first = seq;

  unlock:
	if ((!sm_is_err(ret) && thr_unl_no_err(locktype))
	    || (sm_is_err(ret) && thr_unl_if_err(locktype)))
	{
		r = pthread_mutex_unlock(&ibdb_ctx->ibdb_mutex);
		SM_ASSERT(0 == r);
		if (r != 0 && sm_is_success(ret))
			ret = sm_error_perm(SM_EM_IBDB, r);
	}
	return ret;
}

/*
**  IBDB_FS_GETFREE -- get free disk space in IBDB
**
**	Parameters:
**		ibdb_ctx -- ibdb context
**		pkbfree -- (pointer to) free space (KB) (output)
**
**	Returns:
**		usual sm_error code; fs_getfree(): errno from stat(); ENXIO
**
**	Last code review: 2005-04-21 23:50:08
**	Last code change:
*/

sm_ret_T
ibdb_fs_getfree(ibdb_ctx_P ibdb_ctx, ulong *pkbfree)
{
	sm_ret_T ret;

	SM_REQUIRE(pkbfree != NULLPTR);
	if (ibdb_ctx->ibdb_fs_ctx != NULL) {
		ret = fs_getfree(ibdb_ctx->ibdb_fs_ctx, ibdb_ctx->ibdb_fs_idx, pkbfree);
	}
	else {
		ret = sm_error_perm(SM_EM_IBDB, ENXIO);
		*pkbfree = 0;
	}
	return ret;
}

/*
**  IBDB_STATS -- print IBDB stats
**
**	Parameters:
**		ibdb_ctx -- ibdb context
**		fp -- output file
**
**	Returns:
**		SM_SUCCESS
**
**	Last code review: 2005-04-01 00:55:05
**	Last code change:
*/

sm_ret_T
ibdb_stats(ibdb_ctx_P ibdb_ctx, sm_file_T *fp)
{
#if SM_IBDB_STATS
	SM_REQUIRE(ibdb_ctx != NULL);
	sm_io_fprintf(fp,
		"IBDB open TAs   =%u\n"
		"IBDB open RCPTs =%u\n"
		"IBDB commits    =%lu\n"
		"IBDB seq nr     =%u\n"
		"IBDB changes    =%u\n"
		"IBDB first      =%u\n"
		"IBDB cleanrun   =%u\n"
		, ibdb_ctx->ibdb_opentas
		, ibdb_ctx->ibdb_openrcpts
		, ibdb_ctx->ibdb_commits
		, ibdb_ctx->ibdb_sequence
		, ibdb_ctx->ibdb_changes
		, ibdb_ctx->ibdb_first
		, ibdb_ctx->ibdb_cleanrun
		);
#endif /* SM_IBDB_STATS */
	return SM_SUCCESS;
}

typedef struct ibdbs_ctx_S	ibdbs_ctx_T, *ibdbs_ctx_P;

struct ibdbs_ctx_S
{
	sm_file_T	*ibdbs_fp;	/* file for output */
	uint32_t	 ibdbs_entries;	/* number of entries */
	uint32_t	 ibdbs_sequence;	/* current sequence number */
};

/*
**  SM_IBC_SHOW -- Callback function: print bht entries
**
**	Parameters:
**		ibdb_ctx -- ibdb context
**		ctx -- ibdbs_context
**
**	Returns:
**		SM_SUCCESS
*/

static sm_ret_T
sm_ibc_show(bht_entry_P bhte, void *ctx)
{
	ibc_e_P ibc_e;
	ibdbs_ctx_P ibdbs_ctx;

	SM_REQUIRE(bhte != NULL);
	SM_REQUIRE(ctx != NULL);

	ibc_e = (ibc_e_P) bhte->bhe_value;
	ibdbs_ctx = (ibdbs_ctx_P) ctx;
	++ibdbs_ctx->ibdbs_entries;
	if (ibdbs_ctx->ibdbs_sequence > ibc_e->ibc_sequence) {
		sm_io_fprintf(ibdbs_ctx->ibdbs_fp,
			"ibc: id=%" SM_XSTR(SMTP_RCPTID_LEN) "s, type=%d, seq=%u\n"
			, ibc_e->ibc_id
			, ibc_e->ibc_type
			, ibc_e->ibc_sequence
			);
		ibdbs_ctx->ibdbs_sequence = ibc_e->ibc_sequence;
	}
	return SM_SUCCESS;
}

/*
**  IBDBC_SHOW_SEQ -- print IBDB Cache content
**
**	Parameters:
**		bht -- Hash table
**		fp -- file pointer
**
**	Returns:
**		usual sm_error code; only (un)lock
**
**	Locking:
**		must be done by caller (ibdb_ctx->ibdb_mutex)
*/

sm_ret_T
ibdbc_show_seq(ibdb_ctx_P ibdb_ctx, thr_lock_T locktype, sm_file_T *fp)
{
	sm_ret_T ret;
	int r;
	ibdbs_ctx_T ibdbs_ctx;

	SM_ASSERT(ibdb_ctx != NULL);
	if (thr_lock_it(locktype)) {
		r = pthread_mutex_lock(&ibdb_ctx->ibdb_mutex);
		SM_LOCK_OK(r);
		if (r != 0) {
			/* LOG? */
			return sm_error_perm(SM_EM_IBDB, r);
		}
	}
	ret = SM_SUCCESS;

	ibdbs_ctx.ibdbs_fp = fp;
	ibdbs_ctx.ibdbs_sequence = UINT32_MAX;
	ibdbs_ctx.ibdbs_entries = 0;
	ret = bht_walk(ibdb_ctx->ibdb_ct, sm_ibc_show, &ibdbs_ctx);
	sm_io_fprintf(fp, "ibdbc: lowest_seq=%u\n", ibdbs_ctx.ibdbs_sequence);
	sm_io_fprintf(fp, "ibdbc: #entries  =%u\n", ibdbs_ctx.ibdbs_entries);

	if ((!sm_is_err(ret) && thr_unl_no_err(locktype))
	    || (sm_is_err(ret) && thr_unl_if_err(locktype)))
	{
		r = pthread_mutex_unlock(&ibdb_ctx->ibdb_mutex);
		if (r != 0 && sm_is_success(ret))
			ret = sm_error_perm(SM_EM_IBDB, r);
	}
	return ret;
}


#if 0
///*
//**  SM_IDBSETINFO -- set/modify information for a file
//**
//**	Parameters:
//**		fp -- file to set info for
//**		what -- type of info to set
//**		valp -- location of data used for setting
//**
//**	Returns:
//**		usual sm_error code.
//*/
//
//sm_ret_T
//sm_idbsetinfo(sm_file_T *fp, int what, void *valp)
//{
//	int r;
//
//	switch (what)
//	{
//	  case SM_IO_WHAT_COMMIT:
//		SM_ASSERT(is_valid_fd(f_fd(*fp)));
//		r = fsync(f_fd(*fp));
//		if (r == -1)
//			return sm_error_perm(SM_EM_IBDB, errno);
//		return SM_SUCCESS;
//
//	  default:
//		return sm_error_perm(SM_EM_IBDB, EINVAL);
//	}
//}
//
///*
//**  SM_IDBGETINFO -- get information about the open file
//**
//**	Parameters:
//**		fp -- file to get info for
//**		what -- type of info to get
//**		valp -- location to place found info
//**
//**	Returns:
//**		Success: may or may not place info in 'valp' depending
//**			on 'what' value, and returns values >=0. Return
//**			value may be the obtained info
//**		Failure: usual sm_error code.
//*/
//
//sm_ret_T
//sm_idbgetinfo(sm_file_T *fp, int what, void *valp)
//{
//	switch (what)
//	{
//	  case SM_IO_WHAT_FD:
//		return f_fd(*fp);
//
//	  case SM_IO_WHAT_SIZE:
//	  {
//		struct stat st;
//
//		if (fstat(f_fd(*fp), &st) < 0)
//			return sm_error_perm(SM_EM_IBDB, errno);
//		return st.st_size;
//	  }
//
//	  case SM_IO_IS_READABLE:
//	  {
//		fd_set readfds;
//		struct timeval timeout;
//
//		FD_ZERO(&readfds);
//		SM_FD_SET(f_fd(*fp), &readfds);
//		timeout.tv_sec = 0;
//		timeout.tv_usec = 0;
//		if (select(f_fd(*fp) + 1, FDSET_CAST &readfds, NULL, NULL,
//			   &timeout) > 0 &&
//		    SM_FD_ISSET(f_fd(*fp), &readfds))
//			return 1;
//		return 0;
//	  }
//
//	  default:
//		return sm_error_perm(SM_EM_IBDB, EINVAL);
//	}
//}
//
///*
//**  SM_IDBREAD -- read from the file
//**
//**	Parameters:
//**
//**	Returns:
//**		usual sm_error code
//*/
//
//sm_ret_T
//sm_idbread(sm_file_T *fp, uchar *buf, size_t n, ssize_t *bytesread)
//{
//	ssize_t ret;
//
//	*bytesread = 0;
//	do
//	{
//		errno = 0;
//		ret = read(f_fd(*fp), buf, n);
//	} while (ret == -1 && EINTR == errno);
//	if (ret == -1)
//		return sm_error_perm(SM_EM_IBDB, errno);
//	*bytesread = ret;
//
//	/* if the read succeeded, update the current offset */
//	if (ret > 0)
//		fp->f_lseekoff += ret;
//	return SM_SUCCESS;
//}
#endif /* 0 */


syntax highlighted by Code2HTML, v. 0.9.1