/*
 * 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: ibdbr.c,v 1.68 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/io.h"
#include "sm/fdset.h"
#include "sm/reccom.h"
#include "sm/cstr.h"
#include "sm/ibdb.h"
#include "ibdb.h"

/* specify an INCEDB backup on disk */
struct ibdbr_ctx_S
{
	uint		 ibdbr_version;	/* version of this ibdb */
	sm_file_T	*ibdbr_fp;	/* current file */
	sm_str_P	 ibdbr_path;	/* pathname of file */
	sm_str_P	 ibdbr_name_rm;	/* pathname of file to remove */
	char		*ibdbr_dir;	/* directory of file */
	char		*ibdbr_base_dir;	/* name of base directory */
	char		*ibdbr_name;	/* basename of file */
	int		 ibdbr_mode;	/* file mode */
	uint32_t	 ibdbr_sequence;	/* current sequence number */
	bool		 ibdbr_first;	/* first record? */
	uchar		*ibdbr_fp_buf;	/* buffer for fp if necessary */
};

/*
**  IBDBR_CTX_FREE -- free an IBDBR context
**
**	Parameters:
**		ibdbr_ctx -- INCEDB context
**
**	Returns:
**		none.
*/

static void
ibdbr_ctx_free(ibdbr_ctx_P ibdbr_ctx)
{
	if (ibdbr_ctx == NULL)
		return;
	SM_FREE(ibdbr_ctx->ibdbr_name);
	SM_FREE(ibdbr_ctx->ibdbr_fp_buf);
	SM_STR_FREE(ibdbr_ctx->ibdbr_path);
	SM_STR_FREE(ibdbr_ctx->ibdbr_name_rm);
	sm_free_size(ibdbr_ctx, sizeof(*ibdbr_ctx));
	return;
}

/*
**  IBDBR_CTX_NEW -- allocate new ibdbr context
**
**	Parameters:
**		base_dir -- name of base directory (can be NULL)
**		name -- name of IBDB file
**		seq -- initial sequence number
**		flags -- flags
**
**	Returns:
**		INCEDB context (NULL on error)
*/

static ibdbr_ctx_P
ibdbr_ctx_new(const char *base_dir, const char *name, uint32_t seq, uint flags)
{
	size_t l;
	ibdbr_ctx_P ibdbr_ctx;

	SM_ASSERT(name != NULL);

	ibdbr_ctx = (ibdbr_ctx_P) sm_zalloc(sizeof(*ibdbr_ctx));
	if (ibdbr_ctx == NULL)
		return NULL;

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

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

	IBDB_DIR(ibdbr_ctx->ibdbr_dir, ibdbr_ctx->ibdbr_base_dir, flags);

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

	l += strlen(ibdbr_ctx->ibdbr_dir) + 1 + UINT32_LEN;
	ibdbr_ctx->ibdbr_path = sm_str_new(NULL, l, l);
	if (ibdbr_ctx->ibdbr_path == NULL)
		goto error;
	crt_ibdb_path(ibdbr_ctx->ibdbr_path, ibdbr_ctx->ibdbr_dir, name, seq);

	++l;
	ibdbr_ctx->ibdbr_name_rm = sm_str_new(NULL, l, l);
	if (ibdbr_ctx->ibdbr_name_rm == NULL)
		goto error;

	ibdbr_ctx->ibdbr_first = true;
	ibdbr_ctx->ibdbr_sequence = seq;
	return ibdbr_ctx;

  error:
	ibdbr_ctx_free(ibdbr_ctx);
	return NULL;
}

/*
**  IBDBR_NEXT -- open next ibdb backup on disk
**
**	Parameters:
**		ibdbr_ctx -- INCEDB context
**
**	Returns:
**		usual sm_error code (sm_io_{open,close}())
*/

static sm_ret_T
ibdbr_next(ibdbr_ctx_P ibdbr_ctx)
{
	sm_ret_T ret;
	sm_file_T *fp;

	SM_ASSERT(ibdbr_ctx != NULL);
	ret = sm_io_close(ibdbr_ctx->ibdbr_fp, SM_IO_CF_NONE);
	ibdbr_ctx->ibdbr_fp = NULL;
	if (sm_is_err(ret))
		return ret;
	ibdbr_ctx->ibdbr_sequence++;
	sm_str_clr(ibdbr_ctx->ibdbr_path);
	crt_ibdb_path(ibdbr_ctx->ibdbr_path, ibdbr_ctx->ibdbr_dir,
		ibdbr_ctx->ibdbr_name, ibdbr_ctx->ibdbr_sequence);
	ret = sm_io_open(SmStStdio, sm_str_getdata(ibdbr_ctx->ibdbr_path),
			ibdbr_ctx->ibdbr_mode, &fp, SM_IO_WHAT_END);
	if (sm_is_err(ret))
		return ret;

	if (NULL != ibdbr_ctx->ibdbr_fp_buf)
	{
		ret = sm_io_setvbuf(fp, ibdbr_ctx->ibdbr_fp_buf, SM_IO_FBF,
				IBDB_BUF_SIZE);
		if (sm_is_err(ret))
			return ret;
	}

	ibdbr_ctx->ibdbr_fp = fp;
	ibdbr_ctx->ibdbr_first = true;
	return ret;
}

/*
**  IBDBR_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 (ignored)
**		flags -- flags
**		pibdbr_ctx -- (pointer to) INCEDB context (output)
**
**	Returns:
**		usual sm_error code; ENOMEM,
*/

sm_ret_T
ibdbr_open(const char *base_dir, const char *name, int mode, uint32_t seq, size_t size, uint flags, ibdbr_ctx_P *pibdbr_ctx)
{
	sm_ret_T ret;
	sm_file_T *fp;
	ibdbr_ctx_P ibdbr_ctx;

	SM_REQUIRE(pibdbr_ctx != NULL);
	SM_REQUIRE(seq > 0);
	ibdbr_ctx = ibdbr_ctx_new(base_dir, name, seq, flags);
	if (ibdbr_ctx == NULL)
		return sm_error_temp(SM_EM_IBDB, ENOMEM);
	ret = sm_io_open(SmStStdio, sm_str_getdata(ibdbr_ctx->ibdbr_path),
			mode, &fp, SM_IO_WHAT_END);
	if (sm_is_err(ret))
		goto error;

	if (f_blksize(*fp) < IBDB_MINBUF_SIZE ||
	    (f_blksize(*fp) % IBDB_MINBUF_SIZE) != 0)
	{
		ibdbr_ctx->ibdbr_fp_buf = sm_malloc(IBDB_BUF_SIZE);
		if (NULL == ibdbr_ctx->ibdbr_fp_buf)
			goto error;
		ret = sm_io_setvbuf(fp, ibdbr_ctx->ibdbr_fp_buf, SM_IO_FBF,
				IBDB_BUF_SIZE);
		if (sm_is_err(ret))
			goto error;
	}

	ibdbr_ctx->ibdbr_fp = fp;
	ibdbr_ctx->ibdbr_first = true;

	ibdbr_ctx->ibdbr_mode = mode;
	*pibdbr_ctx = ibdbr_ctx;
	return ret;

  error:
	ibdbr_ctx_free(ibdbr_ctx);
	return sm_is_err(ret) ? ret : sm_error_perm(SM_EM_IBDB, ENOMEM);
}

/*
**  IBDBR_HDR_MOD -- read header modification record
**
**	Parameters:
**		ibdbr_ctx -- INCEDB context
**		ibdb_hdrmod -- header modification record
**
**	Returns:
**		usual sm_error code
**
**	Note: ibdb_hdrmod structure must be allocated
*/

static sm_ret_T
ibdbr_hdr_mod(ibdbr_ctx_P ibdbr_ctx, ibdb_hdrmod_P ibdb_hdrmod)
{
	uint32_t n;
	sm_file_T *fp;
	sm_ret_T ret;

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

	fp = ibdbr_ctx->ibdbr_fp;

	/* transaction ID */
	ret = sm_io_fgetuint32(fp, &n);
	if (sm_is_err(ret) || n != RT_IBDB_HM_TAID)
		goto error;
	ret = sm_io_fgetuint32(fp, &n);
	if (sm_is_err(ret) || n > SMTP_STID_SIZE)
		goto error;
	ret = sm_io_fgetn(fp, (uchar *) ibdb_hdrmod->ibh_ta_id, n);
	if (sm_is_err(ret))
		goto error;
	ibdb_hdrmod->ibh_ta_id[SMTP_STID_SIZE - 1] = '\0';

	/* type */
	ret = sm_io_fgetuint32(fp, &n);
	if (sm_is_err(ret) || n != RT_IBDB_HM_TYPE)
		goto error;
	ret = sm_io_fgetuint32(fp, &n);
	if (sm_is_err(ret) || n != 4)
		goto error;
	ret = sm_io_fgetuint32(fp, &n);
	if (sm_is_err(ret))
		goto error;
	ibdb_hdrmod->ibh_type = n;

	/* position */
	ret = sm_io_fgetuint32(fp, &n);
	if (sm_is_err(ret) || n != RT_IBDB_HM_POS)
		goto error;
	ret = sm_io_fgetuint32(fp, &n);
	if (sm_is_err(ret) || n != 4)
		goto error;
	ret = sm_io_fgetuint32(fp, &n);
	if (sm_is_err(ret))
		goto error;
	ibdb_hdrmod->ibh_pos = n;

	/* header XXX this may exceed the record size??? */
	ret = sm_io_fgetuint32(fp, &n);
	if (sm_is_err(ret) || n != RT_IBDB_HM_HDR)
		goto error;
	ret = sm_io_fgetuint32(fp, &n);
	if (sm_is_err(ret) || n > SM_MAXHDRLEN)
		goto error;
	if (n > 0)
	{
		ret = sm_io_fgetncstr(fp, &ibdb_hdrmod->ibh_hdr, n);
		if (sm_is_err(ret))
			goto error;
	}

	ret = sm_io_falign(fp, IBDB_REC_SIZE);
	if (sm_is_err(ret))
		goto error;
	return SM_SUCCESS;

  error:
	/* cleanup? */
	if (!sm_is_err(ret))
		ret = sm_error_perm(SM_EM_IBDB, SM_E_PR_ERR);
	return ret;
}

/*
**  IBDBR_TA_STATUS -- read transaction status
**
**	Parameters:
**		ibdbr_ctx -- INCEDB context
**		ta -- transaction (sender) data
**		pstatus -- (pointer to) transaction status (output)
**
**	Returns:
**		usual sm_error code
**
**	Note: ta structure must be allocated by caller except for cdb_id.
*/

static sm_ret_T
ibdbr_ta_status(ibdbr_ctx_P ibdbr_ctx, ibdb_ta_P ibdb_ta, int *pstatus)
{
	uint32_t n;
	sm_file_T *fp;
	sm_ret_T ret;

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

	fp = ibdbr_ctx->ibdbr_fp;

	/* transaction status */
	ret = sm_io_fgetuint32(fp, &n);
	if (sm_is_err(ret) || n != RT_IBDB_TA_ST)
		goto error;
	ret = sm_io_fgetuint32(fp, &n);
	if (sm_is_err(ret) || n != 4)
		goto error;
	ret = sm_io_fgetuint32(fp, &n);
	if (sm_is_err(ret))
		goto error;
	*pstatus = n;

	/* transaction ID */
	ret = sm_io_fgetuint32(fp, &n);
	if (sm_is_err(ret) || n != RT_IBDB_TAID)
		goto error;
	ret = sm_io_fgetuint32(fp, &n);
	if (sm_is_err(ret) || n > SMTP_STID_SIZE)
		goto error;
	ret = sm_io_fgetn(fp, (uchar *) ibdb_ta->ibt_ta_id, n);
	if (sm_is_err(ret))
		goto error;
	ibdb_ta->ibt_ta_id[SMTP_STID_SIZE - 1] = '\0';

	/* CDB ID */
	ret = sm_io_fgetuint32(fp, &n);
	if (sm_is_err(ret) || n != RT_IBDB_CDBID)
		goto error;
	ret = sm_io_fgetuint32(fp, &n);
	if (sm_is_err(ret) || n > CDB_ID_SIZE)
		goto error;

	/* todo: don't read CDB if n==1? can callers deal with that?? */
	SM_ASSERT(ibdb_ta->ibt_cdb_id == NULL);
	ret = sm_io_fgetncstr(fp, &ibdb_ta->ibt_cdb_id, n);
	if (sm_is_err(ret))
		goto error;
#if 0
	/* ???  */
	if (n == CDB_ID_SIZE)
		sm_cstr_wr_elem(ibdb_ta->ibt_cdb_id, CDB_ID_SIZE - 1) = '\0';
#endif /* 0 */

	/* nrcpts */
	ret = sm_io_fgetuint32(fp, &n);
	if (sm_is_err(ret) || n != RT_IBDB_NRCPTS)
		goto error;
	ret = sm_io_fgetuint32(fp, &n);
	if (sm_is_err(ret) || n != 4)
		goto error;
	ret = sm_io_fgetuint32(fp, &n);
	if (sm_is_err(ret))
		goto error;
	ibdb_ta->ibt_nrcpts = n;

	/* mail XXX this may exceed the record size??? */
	ret = sm_io_fgetuint32(fp, &n);
	if (sm_is_err(ret) || n != RT_IBDB_MAIL)
		goto error;
	ret = sm_io_fgetuint32(fp, &n);
	if (sm_is_err(ret) || n > sm_str_getsize(ibdb_ta->ibt_mail_pa))
		goto error;
	ret = sm_io_fgetn(fp, sm_str_getdata(ibdb_ta->ibt_mail_pa), n);
	if (sm_is_err(ret))
		goto error;
	SM_STR_SETLEN(ibdb_ta->ibt_mail_pa, n);

	ret = sm_io_falign(fp, IBDB_REC_SIZE);
	if (sm_is_err(ret))
		goto error;
	return SM_SUCCESS;

  error:
	/* cleanup? */
	if (!sm_is_err(ret))
		ret = sm_error_perm(SM_EM_IBDB, SM_E_PR_ERR);
	return ret;
}

/*
**  IBDBR_RCPT_STATUS -- read recipient status
**
**	Parameters:
**		ibdbr_ctx -- INCEDB context
**		ibdb_rcpt -- recipient data
**		pstatus -- (pointer to) recipient status (output)
**
**	Returns:
**		usual sm_error code
**
**	Note: ibdb_rcpt structure must be allocated by caller.
*/

static sm_ret_T
ibdbr_rcpt_status(ibdbr_ctx_P ibdbr_ctx, ibdb_rcpt_P ibdb_rcpt, int *pstatus)
{
	uint32_t n;
	sm_file_T *fp;
	sm_ret_T ret;

	fp = ibdbr_ctx->ibdbr_fp;

	/* recipient status */
	ret = sm_io_fgetuint32(fp, &n);
	if (sm_is_err(ret) || n != RT_IBDB_RCPT_ST)
		goto error;
	ret = sm_io_fgetuint32(fp, &n);
	if (sm_is_err(ret) || n != 4)
		goto error;
	ret = sm_io_fgetuint32(fp, &n);
	if (sm_is_err(ret))
		goto error;
	*pstatus = n;

	/* recipient index */
	ret = sm_io_fgetuint32(fp, &n);
	if (sm_is_err(ret) || n != RT_IBDB_RCPT_IDX)
		goto error;
	ret = sm_io_fgetuint32(fp, &n);
	if (sm_is_err(ret) || n != 4)
		goto error;
	ret = sm_io_fgetuint32(fp, &n);
	if (sm_is_err(ret))
		goto error;
	ibdb_rcpt->ibr_idx = n;

	/* transaction ID */
	ret = sm_io_fgetuint32(fp, &n);
	if (sm_is_err(ret) || n != RT_IBDB_TAID)
		goto error;
	ret = sm_io_fgetuint32(fp, &n);
	if (sm_is_err(ret) || n > SMTP_STID_SIZE)
		goto error;
	ret = sm_io_fgetn(fp, (uchar *) ibdb_rcpt->ibr_ta_id, n);
	if (sm_is_err(ret))
		goto error;
	ibdb_rcpt->ibr_ta_id[SMTP_STID_SIZE - 1] = '\0';

	/* rcpt XXX this may exceed the record size??? */
	ret = sm_io_fgetuint32(fp, &n);
	if (sm_is_err(ret) || n != RT_IBDB_RCPT_PA)
		goto error;
	ret = sm_io_fgetuint32(fp, &n);
	if (sm_is_err(ret) || n > sm_str_getsize(ibdb_rcpt->ibr_pa))
		goto error;
	ret = sm_io_fgetn(fp, sm_str_getdata(ibdb_rcpt->ibr_pa), n);
	if (sm_is_err(ret))
		goto error;
	SM_STR_SETLEN(ibdb_rcpt->ibr_pa, n);

	ret = sm_io_falign(fp, IBDB_REC_SIZE);
	if (sm_is_err(ret))
		goto error;
	return SM_SUCCESS;

  error:
	/* cleanup? */
	if (!sm_is_err(ret))
		ret = sm_error_perm(SM_EM_IBDB, SM_E_PR_ERR);
	return ret;
}

/*
**  IDBR_GET -- read record from INCEDB
**
**	Parameters:
**		ibdbr_ctx -- INCEDB context
**		ibdb_rcpt -- recipient data
**		ibdb_ta -- transaction (sender) data
**		ibdb_hdrmod -- header modification data
**		pstatus -- (pointer to) transaction status (output)
**
**	Returns:
**		>0: RT_IBDB_RCPT, RT_IBDB_TA, etc
**		<0: usual sm_error code
**
**	Locking: must be done by caller.
*/

sm_ret_T
ibdbr_get(ibdbr_ctx_P ibdbr_ctx, ibdb_rcpt_P ibdb_rcpt, ibdb_ta_P ibdb_ta, ibdb_hdrmod_P ibdb_hdrmod, int *pstatus)
{
	uint32_t val;
	sm_ret_T ret;

	SM_ASSERT(ibdbr_ctx != NULL);
	SM_ASSERT(ibdb_rcpt != NULL);
	SM_ASSERT(ibdb_ta != NULL);

  again:
	if (sm_io_eof(ibdbr_ctx->ibdbr_fp))
	{
		/* roll-over */
		ret = ibdbr_next(ibdbr_ctx);
		if (sm_is_err(ret))
			goto error;
	}

	/* first: make sure one record is in file buffer... */
	ret = sm_io_ffill(ibdbr_ctx->ibdbr_fp, IBDB_REC_SIZE);
	if (sm_is_err(ret))
	{
		if (sm_io_eof(ibdbr_ctx->ibdbr_fp))
		{
			/* roll-over */
			ret = ibdbr_next(ibdbr_ctx);
			if (sm_is_err(ret))
				goto error;
			ret = sm_io_ffill(ibdbr_ctx->ibdbr_fp, IBDB_REC_SIZE);
		}
		if (sm_is_err(ret))
		{
			/*
			**  This may happen if an entry wasn't written
			**  completely. Just ignore that entry.
			*/

			if (SM_IO_EOF == ret ||
			    sm_error_perm(SM_EM_IO, EIO) == ret)
				ret = sm_error_perm(SM_EM_IBDB, ENOENT);
			goto error;
		}
	}

	/* next: get record type */
	ret = sm_io_fgetuint32(ibdbr_ctx->ibdbr_fp, &val);
	if (sm_is_err(ret))
		goto error;

	/* first record in file? check version number */
	if (ibdbr_ctx->ibdbr_first)
	{
		if (val != RT_IBDB_VERS_R)
			goto error;

		/* version number */
		ret = sm_io_fgetuint32(ibdbr_ctx->ibdbr_fp, &val);
		if (sm_is_err(ret) || val != RT_IBDB_VERS_N)
			goto error;
		ret = sm_io_fgetuint32(ibdbr_ctx->ibdbr_fp, &val);
		if (sm_is_err(ret) || val != 4)
			goto error;
		ret = sm_io_fgetuint32(ibdbr_ctx->ibdbr_fp, &val);
		if (sm_is_err(ret))
			goto error;
		if (!IBDB_VRS_COMPAT(IBDB_VERSION, val))
			goto error;
		ret = sm_io_falign(ibdbr_ctx->ibdbr_fp, IBDB_REC_SIZE);
		if (sm_is_err(ret))
			goto error;
		ibdbr_ctx->ibdbr_version = val;
		ibdbr_ctx->ibdbr_first = false;
		goto again;
	}

	if (val == RT_IBDB_TA)
		ret = ibdbr_ta_status(ibdbr_ctx, ibdb_ta, pstatus);
	else if (val == RT_IBDB_RCPT)
		ret = ibdbr_rcpt_status(ibdbr_ctx, ibdb_rcpt, pstatus);
	else if (val == RT_IBDB_HDRMOD)
	{
		*pstatus = 0; /* ??? */
		ret = ibdbr_hdr_mod(ibdbr_ctx, ibdb_hdrmod);
	}
	else
		ret = sm_error_perm(SM_EM_IBDB, SM_E_PR_ERR);
	if (sm_is_err(ret))
		goto error;

	return val;

 error:
	if (sm_is_success(ret))
		ret = sm_error_perm(SM_EM_IBDB, SM_E_VER_MIX);
	return ret;
}

/*
**  IDBR_CLOSE -- close the file
**
**	Parameters:
**		ibdbr_ctx -- INCEDB context
**
**	Returns:
**		usual sm_error code (from sm_io_close())
*/

sm_ret_T
ibdbr_close(ibdbr_ctx_P ibdbr_ctx)
{
	sm_ret_T ret;
	sm_file_T *fp;

	SM_ASSERT(ibdbr_ctx != NULL);
	ret = SM_SUCCESS;
	fp = ibdbr_ctx->ibdbr_fp;
	if (fp != NULL)
	{
		ret = sm_io_close(fp, SM_IO_CF_NONE);
		if (sm_is_err(ret))
			return ret;
	}

	ibdbr_ctx_free(ibdbr_ctx);
	return ret;
}

/*
**  IBDBR_UNLINK -- unlink IBDB files
**
**	Parameters:
**		ibdbr_ctx -- ibdbr context
**		first -- first sequence number to unlink
**		last -- last sequence number to unlink
**
**	Returns:
**		SM_SUCCESS
*/

sm_ret_T
ibdbr_unlink(ibdbr_ctx_P ibdbr_ctx, uint32_t first, uint32_t last)
{
	size_t i;

	SM_ASSERT(ibdbr_ctx != NULL);
	for (i = first; i <= last; i++)
	{
		sm_str_clr(ibdbr_ctx->ibdbr_name_rm);
		crt_ibdb_path(ibdbr_ctx->ibdbr_name_rm, ibdbr_ctx->ibdbr_dir,
			ibdbr_ctx->ibdbr_name, i);
		(void) unlink((const char *)
				sm_str_getdata(ibdbr_ctx->ibdbr_name_rm));
		sm_str_clr(ibdbr_ctx->ibdbr_name_rm);
	}
	return SM_SUCCESS;
}


syntax highlighted by Code2HTML, v. 0.9.1