/*
 * Copyright (c) 2002-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_IDSTR(id, "@(#)$Id: t-idbr-0.c,v 1.49 2007/06/18 04:42:31 ca Exp $")

#include "sm/io.h"
#include "sm/mta.h"
#include "sm/ibdb.h"
#include "sm/cdb.h"
#include "sm/qmgr.h"
#include "sm/memops.h"
#include "sm/bhtable.h"
#include "sm/test.h"
#include "sm/io.h"

/*
**  Test program to read content of IBDB.
**  Can display the entire content or just the open transactions.
**  Performs several consistency checks.
*/

#define FNAME	"ibd"
#define IBDBSIZE	8192
#define MAX_STR_SZ	256
#define INIT_SEQ	1
#define BHTSIZE		(32 * 1024)

#define IDB_DONE	'D'
#define IDB_CANCEL	'C'
#define IDB_OOPS	'X'

static int Verbose;
static int init_seq = INIT_SEQ;
static int bhtsize = BHTSIZE;
static int bhtmax = (BHTSIZE * 2);

/*
**  Note: this program doesn't free() data since it may use it later on
**	for various Verbose output.
**	A real "recovery" program would free transaction/recipient
**	structures as soon as it figured out that the transaction/recipient
**	have been completed (delivered/cancelled/transferred to DEFEDB).
*/

/*
**  TA_CANCEL -- Cancel all recipients of a transaction (callback function)
**
**	Parameters:
**		info -- bht entry
**		ctx -- TA ID
**
**	Returns:
**		usual sm_error code
*/

static sm_ret_T
ta_cancel(bht_entry_P info, void *ctx)
{
	ibdb_rcpt_P rcpt;
	sessta_id_P ta_id;

	SM_TEST(info != NULL);
	SM_TEST(ctx != NULL);
	rcpt = (ibdb_rcpt_P) info->bhe_value;
	SM_TEST(rcpt != NULL);
	SM_TEST(rcpt->ibr_ta_id != NULL);
	ta_id = (sessta_id_P) ctx;
	if (SESSTA_EQ(rcpt->ibr_ta_id, ta_id))
	{
		if (Verbose > 2)
		{
			sm_io_fprintf(smioout, "cancel TA=%s ", ta_id);
			sm_io_fprintf(smioout, "rcpt_pa=%N, ", rcpt->ibr_pa);
			sm_io_fprintf(smioout, "rcpt_idx=%d, ", rcpt->ibr_idx);
			sm_io_fprintf(smioout, "\n");
		}
		rcpt->ibr_ta_id[0] = IDB_CANCEL;
	}
	return SM_SUCCESS;
}

/*
**  CANCEL_TA -- Cancel a transaction
**
**	Parameters:
**		bhr -- bhtable (with recipients)
**		ta_id -- TA ID
**
**	Returns:
**		usual sm_error code
*/

static sm_ret_T
cancel_ta(bht_P bhr, sessta_id_P ta_id)
{
	if (Verbose > 2)
		sm_io_fprintf(smioout, "cancel TA=%s\n", ta_id);
	bht_walk(bhr, ta_cancel, (void *) ta_id);
	return SM_SUCCESS;
}

/*
**  TA_ACTION -- Show status of transaction (callback function)
**
**	Parameters:
**		info -- bht entry
**		ctx -- unused
**
**	Returns:
**		usual sm_error code
*/

/* ARGSUSED1 */
static sm_ret_T
ta_action(bht_entry_P info, void *ctx)
{
	ibdb_ta_P ta;
	bool printit;
	char c;

	(void) ctx;
	SM_TEST(info != NULL);
	ta = (ibdb_ta_P) info->bhe_value;
	SM_TEST(ta != NULL);
	printit = false;
	SM_TEST(ta->ibt_ta_id != NULL);
	if (ta->ibt_ta_id == NULL)
		return -1;
	c = ta->ibt_ta_id[0];
	if (c != IDB_DONE && c != IDB_CANCEL)
	{
		sm_io_fprintf(smioout, "MAIL: OPEN: ");
		printit = true;
	}
	else if (Verbose > 1)
	{
		sm_io_fprintf(smioout, "MAIL: ");
		if (c == IDB_DONE)
			sm_io_fprintf(smioout, "DONE: ");
		else if (c == IDB_CANCEL)
			sm_io_fprintf(smioout, "CANCEL: ");
		else
			sm_io_fprintf(smioout, "OOPS: ");
		printit = true;
	}
	if (printit)
	{
		sm_io_fprintf(smioout, "ta=%s, ", ta->ibt_ta_id);
		sm_io_fprintf(smioout, "cdb=%C, ", ta->ibt_cdb_id);
		sm_io_fprintf(smioout, "mail_pa=%N, ", ta->ibt_mail_pa);
		if (Verbose > 3)
			sm_io_fprintf(smioout, "nrcpts=%d, ", ta->ibt_nrcpts);
		sm_io_fprintf(smioout, "\n");
	}
	return SM_SUCCESS;
}

/*
**  RCPT_ACTION -- Show status of recipient (callback function)
**
**	Parameters:
**		info -- bht entry
**		ctx -- unused
**
**	Returns:
**		usual sm_error code
*/

/* ARGSUSED1 */
static sm_ret_T
rcpt_action(bht_entry_P info, void *ctx)
{
	ibdb_rcpt_P rcpt;
	bool printit;
	char c;

	(void) ctx;
	SM_TEST(info != NULL);
	rcpt = (ibdb_rcpt_P) info->bhe_value;
	SM_TEST(rcpt != NULL);
	printit = false;
	SM_TEST(rcpt->ibr_ta_id != NULL);
	if (rcpt->ibr_ta_id == NULL)
		return -1;
	c = rcpt->ibr_ta_id[0];
	if (c != IDB_DONE && c != IDB_CANCEL)
	{
		sm_io_fprintf(smioout, "RCPT: OPEN: ");
		printit = true;
	}
	else if (Verbose > 1)
	{
		sm_io_fprintf(smioout, "RCPT: ");
		if (c == IDB_DONE)
			sm_io_fprintf(smioout, "DONE: ");
		else if (c == IDB_CANCEL)
			sm_io_fprintf(smioout, "CANCEL: ");
		else
			sm_io_fprintf(smioout, "OOPS: ");
		printit = true;
	}
	if (printit)
	{
		sm_io_fprintf(smioout, "ta=%s, ", rcpt->ibr_ta_id);
		sm_io_fprintf(smioout, "rcpt_pa=%N, ", rcpt->ibr_pa);
		sm_io_fprintf(smioout, "rcpt_idx=%d, ", rcpt->ibr_idx);
		sm_io_fprintf(smioout, "\n");
	}
	return SM_SUCCESS;
}

/*
**  TESTIDBR -- read INCEDB records
**
**	Parameters:
**		maxs -- maximum size of an INCEDB file
**
**	Returns:
**		usual sm_error code.
*/

static sm_ret_T
testidbr(int maxs)
{
	int nrcpts, ntas, status;
	ibdbr_ctx_P ibdbrc;
	ibdb_ta_P ta, lta;
	ibdb_rcpt_P rcpt, lrcpt;
	ibdb_hdrmod_P ibdb_hdrmod;
	bht_P bht, bhr;
	bht_entry_P bhte;
	id_count_T id_count;
	sm_str_P str;
	sm_ret_T ret;
	bool more;
	rcpt_id_P rcpt_id;

	bhr = NULL;
	ibdbrc = NULL;
	ret = sm_error_temp(SM_EM_IBDB, ENOMEM);
	bht = bht_new(bhtsize, bhtmax);
	SM_TEST(bht != NULL);
	if (bht == NULL)
		return sm_error_temp(SM_EM_IBDB, ENOMEM);
	bhr = bht_new(bhtsize, bhtmax);
	SM_TEST(bhr != NULL);
	if (bhr == NULL)
		goto end;
	ret = ibdbr_open(NULL, FNAME, SM_IO_RDONLY, init_seq, maxs, IBDB_OFL_WRITE,
			&ibdbrc);
	if (sm_is_err(ret) && sm_error_value(ret) == ENOENT)
		goto end;
	SM_TEST(ret == SM_SUCCESS);
	if (ret != SM_SUCCESS)
		return ret;
	SM_TEST(ibdbrc != NULL);
	if (ibdbrc == NULL)
		goto end;

	ret = ibdbr_ta_new(&ta);
	SM_TEST(ret == SM_SUCCESS);
	if (ret != SM_SUCCESS)
		return ret;
	SM_TEST(ta != NULL);
	if (ta == NULL)
		goto end;

	ret = ibdbr_rcpt_new(&rcpt);
	SM_TEST(ret == SM_SUCCESS);
	if (ret != SM_SUCCESS)
		return ret;
	SM_TEST(rcpt != NULL);
	if (rcpt == NULL)
		goto end;
	ret = ibdbr_hdrmod_new(&ibdb_hdrmod);
	SM_TEST(ret == SM_SUCCESS);
	if (ret != SM_SUCCESS)
		return ret;
	SM_TEST(ibdb_hdrmod != NULL);
	if (ibdb_hdrmod == NULL)
		goto end;

	str = sm_str_new(NULL, MAX_STR_SZ, MAX_STR_SZ);
	SM_TEST(str != NULL);
	if (str == NULL)
		goto end;

	more = true;
	nrcpts = ntas = 0;
	id_count = 0;
	do
	{
		id_count++;
		sm_str_clr(str);
		ret = ibdbr_get(ibdbrc, rcpt, ta, ibdb_hdrmod, &status);
		switch (ret)
		{
		  case RT_IBDB_TA:
			if (Verbose > 2)
			{
				sm_io_fprintf(smioout, "MAIL: ");
				sm_io_fprintf(smioout, "ta=%s, ", ta->ibt_ta_id);
				sm_io_fprintf(smioout, "cdb=%C, ", ta->ibt_cdb_id);
				sm_io_fprintf(smioout, "mail_pa=%N, ", ta->ibt_mail_pa);
				sm_io_fprintf(smioout, "status=%d, ", status);
				if (Verbose > 3)
					sm_io_fprintf(smioout, "nrcpts=%d, ",
							ta->ibt_nrcpts);
				sm_io_fprintf(smioout, "\n");
			}
			++ntas;
			if (status == 0 || status == IBDB_TA_CANCEL)
			{
				if (status == IBDB_TA_CANCEL)
				{
					cancel_ta(bhr, ta->ibt_ta_id);
					ta->ibt_ta_id[0] = IDB_CANCEL;
				}
				ret = bht_add(bht,
					ta->ibt_ta_id, SMTP_STID_SIZE,
					ta, &bhte);
				SM_TEST(ret == SM_SUCCESS);
				SM_TEST(bhte != NULL);

				ret = ibdbr_ta_new(&ta);
				SM_TEST(ret == SM_SUCCESS);
				if (ret != SM_SUCCESS)
					return ret;
				SM_TEST(ta != NULL);
				if (ta == NULL)
					goto end;
			}
			else
			{
				lta = bht_find(bht,
					ta->ibt_ta_id, SMTP_STID_SIZE);
				SM_TEST(lta != NULL || idbd_ta_done(status));
				if (lta != NULL)
				{
					lta->ibt_ta_id[0] =
						idbd_ta_done(status) ?
						  IDB_DONE :
						  (idbd_ta_cancelled(status) ?
						    IDB_CANCEL : IDB_OOPS);
				}
				else if (idbd_ta_done(status))
				{
					if (Verbose > 4)
						sm_io_fprintf(smioout, "INFO: can't find ta %s, status=%x\n", ta->ibt_ta_id, status);
				}
				else if (Verbose > 0)
					sm_io_fprintf(smioout, "ERROR: can't find ta %s\n", ta->ibt_ta_id);
				SM_CSTR_FREE(ta->ibt_cdb_id);
			}
			break;

		  case RT_IBDB_RCPT:
			if (Verbose > 2)
			{
				sm_io_fprintf(smioout, "RCPT: ");
				sm_io_fprintf(smioout, "ta=%s, ", rcpt->ibr_ta_id);
				sm_io_fprintf(smioout, "rcpt_pa=%N, ", rcpt->ibr_pa);
				sm_io_fprintf(smioout, "rcpt_idx=%d, ", rcpt->ibr_idx);
				sm_io_fprintf(smioout, "status=%d, ", status);
				sm_io_fprintf(smioout, "\n");
			}
			++nrcpts;
			if (status == 0)
			{
				rcpt_id = (char *) sm_malloc(SMTP_RCPTID_SIZE + 1);
				SM_TEST(rcpt_id != NULL);
				if (rcpt_id == NULL)
					goto end;
				sm_snprintf(rcpt_id, SMTP_RCPTID_SIZE,
					SMTP_RCPTID_FORMAT,
					rcpt->ibr_ta_id,
					rcpt->ibr_idx);
				ret = bht_add(bhr,
					rcpt_id, SMTP_RCPTID_SIZE,
					rcpt, &bhte);
				SM_TEST(ret == SM_SUCCESS);
				SM_TEST(bhte != NULL);

				ret = ibdbr_rcpt_new(&rcpt);
				SM_TEST(ret == SM_SUCCESS);
				if (ret != SM_SUCCESS)
					return ret;
				SM_TEST(rcpt != NULL);
				if (rcpt == NULL)
					goto end;
			}
			else
			{
				rcpt_id_T lrcpt_id;

				sm_snprintf(lrcpt_id, SMTP_RCPTID_SIZE,
					SMTP_RCPTID_FORMAT,
					rcpt->ibr_ta_id,
					rcpt->ibr_idx);
				lrcpt = bht_find(bhr,
					lrcpt_id, SMTP_RCPTID_SIZE);
				SM_TEST(lrcpt != NULL || idbd_rcpt_done(status));
				if (lrcpt != NULL)
				{
					lrcpt->ibr_ta_id[0] =
						idbd_rcpt_done(status) ?
						  IDB_DONE : IDB_OOPS;
				}
				else if (idbd_rcpt_done(status))
				{
					if (Verbose > 4)
						sm_io_fprintf(smioout, "INFO: can't find rcpt %s, status=%x\n", lrcpt_id, status);
				}
				else if (Verbose > 0)
					sm_io_fprintf(smioout, "ERROR: can't find rcpt %s\n", lrcpt_id);
			}
			break;

		  case RT_IBDB_HDRMOD:
			if (Verbose > 2)
			{
				sm_io_fprintf(smioout, "HDRMOD: ");
				sm_io_fprintf(smioout, "ta=%s, ",
					ibdb_hdrmod->ibh_ta_id);
				sm_io_fprintf(smioout, "hdr=%T, ", 
					ibdb_hdrmod->ibh_hdr);
				sm_io_fprintf(smioout, "type=%u, ", 
					ibdb_hdrmod->ibh_type);
				sm_io_fprintf(smioout, "pos=%u", 
					ibdb_hdrmod->ibh_pos);
				sm_io_fprintf(smioout, "\n");
			}
			break;

		  default:
			if (sm_is_err(ret) && sm_error_value(ret) == ENOENT)
				sm_io_fprintf(smioout, "end of ibd\n");
			else
				sm_io_fprintf(smioout, "ERROR: ibdbr_get()=%x\n", ret);
			more = false;
			break;
		}
	} while (more);

	bht_walk(bht, ta_action, NULL);
	bht_walk(bhr, rcpt_action, NULL);

  end:
	if (ibdbrc != NULL)
	{
		ret = ibdbr_close(ibdbrc);
		SM_TEST(ret == SM_SUCCESS);
	}
	if (bht != NULL)
		bht_destroy(bht, NULL, NULL);
	if (bhr != NULL)
		bht_destroy(bhr, NULL, NULL);
	return ret;
}

static void
usage(const char *prg)
{
	sm_io_fprintf(smioerr, "usage: %s [options]\n", prg);
	sm_io_fprintf(smioerr, "-d n	set verbose level\n");
	sm_io_fprintf(smioerr, "-I n	initialize IBDB file number\n");
	sm_io_fprintf(smioerr, "-m n	set IBDB file size\n");
	sm_io_fprintf(smioerr, "-M n	maximum hash table size [%d]\n",
			bhtmax);
	sm_io_fprintf(smioerr, "-T n	hash table size [%d]\n",
			bhtsize);
	sm_io_fprintf(smioerr, "-V	increase verbose level\n");
	exit(1);
}

int
main(int argc, char *argv[])
{
	int c, maxs;
	sm_ret_T ret;

	Verbose = 0;
	maxs = IBDBSIZE;
	while ((c = getopt(argc, argv, "d:HI:m:M:T:V")) != -1)
	{
		switch (c)
		{
		  case 'd':
			Verbose = strtol(optarg, NULL, 0);
			break;
#if SM_HEAP_CHECK
		  case 'H':
			SmHeapCheck = atoi(optarg);
			break;
#endif
		  case 'I':
			init_seq = atoi(optarg);
			break;
		  case 'm':
			maxs = strtol(optarg, NULL, 0);
			break;
		  case 'M':
			bhtmax = strtol(optarg, NULL, 0);
			break;
		  case 'T':
			bhtsize = strtol(optarg, NULL, 0);
			break;
		  case 'V':
			++Verbose;
			break;
		  default:
			usage(argv[0]);
			return(1);
		}
	}

	sm_test_begin(argc, argv, "test sm_idbr_0");
	ret = testidbr(maxs);
	sm_io_flush(smioout);
	return sm_test_end();
}


syntax highlighted by Code2HTML, v. 0.9.1