/*-
 * See the file LICENSE for redistribution information.
 *
 * Copyright (c) 1996, 2011 Oracle and/or its affiliates.  All rights reserved.
 *
 * $Id: db_log_verify.c,v 0f73af5ae3da 2010/05/10 05:38:40 alexander $
 */
#include "db_config.h"

#include "db_int.h"

#define	MB 1024 * 1024

int main __P((int, char *[]));
int lsn_arg __P((char *, DB_LSN *));
int usage __P((void));
int version_check __P((void));
int db_log_verify_app_record __P((DB_ENV *, DBT *, DB_LSN *, db_recops));
const char *progname;

int
main(argc, argv)
	int argc;
	char *argv[];
{
	extern char *optarg;
	extern int optind;
	DB_ENV	*dbenv;
	DB_LSN start, stop;
	int ch, cmb, exitval, nflag, rflag, ret, vsn_mismtch;
	time_t starttime, endtime;
	char *dbfile, *dbname, *home, *lvhome, *passwd;
	DB_LOG_VERIFY_CONFIG lvconfig;

	vsn_mismtch = 0;
	if ((progname = __db_rpath(argv[0])) == NULL)
		progname = argv[0];
	else
		++progname;

	if ((ret = version_check()) != 0)
		return (ret);

	dbenv = NULL;
	dbfile = dbname = home = lvhome = passwd = NULL;
	exitval = nflag = rflag = 0;
	starttime = endtime = 0;
	ZERO_LSN(start);
	ZERO_LSN(stop);

	memset(&lvconfig, 0, sizeof(lvconfig));

	while ((ch = getopt(argc, argv, "b:cC:d:D:e:h:H:NP:Vvs:z:")) != EOF)
		switch (ch) {
		case 'b':
			/* Don't use getsubopt(3), not all systems have it. */
			if (lsn_arg(optarg, &start))
				return (usage());
			break;
		case 'c':
			lvconfig.continue_after_fail = 1;
			break;
		case 'C':
			cmb = atoi(optarg);
			if (cmb <= 0)
				return (usage());
			lvconfig.cachesize = cmb * MB;
			break;
		case 'd':
			dbfile = optarg;
			break;
		case 'D':
			dbname = optarg;
			break;
		case 'e':
			/* Don't use getsubopt(3), not all systems have it. */
			if (lsn_arg(optarg, &stop))
				return (usage());
			break;
		case 'h':
			home = optarg;
			break;
		case 'H':
			lvhome = optarg;
			break;
		case 'N':
			nflag = 1;
			break;
		case 'P':
			if ((ret = __os_strdup(NULL, optarg, &passwd)) != 0) {
				__db_err(NULL, ret, "__os_strdup: ");
				return (EXIT_FAILURE);
			}
			memset(optarg, 0, strlen(optarg));
			break;
		case 'V':
			printf("%s\n", db_version(NULL, NULL, NULL));
			return (EXIT_SUCCESS);
		case 'v':
			lvconfig.verbose = 1;
			break;
		case 's':
			starttime = atoi(optarg);
			break;
		case 'z':
			endtime = atoi(optarg);
			break;
		case '?':
		default:
			return (usage());
		}
	argc -= optind;
	argv += optind;

	if (argc > 0)
		return (usage());

	lvconfig.temp_envhome = lvhome;
	lvconfig.dbfile = dbfile;
	lvconfig.dbname = dbname;
	lvconfig.start_lsn = start;
	lvconfig.end_lsn = stop;
	lvconfig.start_time = starttime;
	lvconfig.end_time = endtime;

create_again:
	/*
	 * Create an environment object and initialize it for error
	 * reporting.
	 */
	if ((ret = db_env_create(&dbenv, 0)) != 0) {
		fprintf(stderr,
		    "%s: db_env_create: %s\n", progname, db_strerror(ret));
		goto err;
	}

	dbenv->set_errfile(dbenv, stderr);
	dbenv->set_errpfx(dbenv, progname);

	if (nflag) {
		if ((ret = dbenv->set_flags(dbenv, DB_NOLOCKING, 1)) != 0) {
			dbenv->err(dbenv, ret, "set_flags: DB_NOLOCKING");
			goto err;
		}
		if ((ret = dbenv->set_flags(dbenv, DB_NOPANIC, 1)) != 0) {
			dbenv->err(dbenv, ret, "set_flags: DB_NOPANIC");
			goto err;
		}
	}

	if (passwd != NULL && (ret = dbenv->set_encrypt(dbenv,
	    passwd, DB_ENCRYPT_AES)) != 0) {
		dbenv->err(dbenv, ret, "set_passwd");
		goto err;
	}

	/*
	 * Set up an app-specific dispatch function so that we can gracefully
	 * handle app-specific log records.
	 */
	if ((ret = dbenv->set_app_dispatch(
	    dbenv, db_log_verify_app_record)) != 0) {
		dbenv->err(dbenv, ret, "app_dispatch");
		goto err;
	}

	/*
	 * An environment is required, but as all we're doing is reading log
	 * files, we create one if it doesn't already exist.  If we create
	 * it, create it private so it automatically goes away when we're done.
	 * If we are reading the replication database, do not open the env
	 * with logging, because we don't want to log the opens.
	 */
	if (!vsn_mismtch && (ret = dbenv->open(dbenv, home,
	    DB_USE_ENVIRON, 0)) != 0) {
		if (dbenv->close(dbenv, 0) != 0) {
			dbenv = NULL;
			goto err;
		}
		vsn_mismtch = 1;
		goto create_again;
	}
	if (vsn_mismtch && (ret = dbenv->open(dbenv, home, DB_CREATE |
	    DB_INIT_LOG | DB_PRIVATE | DB_USE_ENVIRON, 0)) != 0) {
		dbenv->err(dbenv, ret, "DB_ENV->open");
		goto err;
	}

	/* Handle possible interruptions. */
	__db_util_siginit();

	if ((ret = dbenv->log_verify(dbenv, &lvconfig)) != 0)
		goto err;

	if (0) {
err:		exitval = 1;
	}

	if (dbenv != NULL && (ret = dbenv->close(dbenv, 0)) != 0) {
		exitval = 1;
		fprintf(stderr,
		    "%s: dbenv->close: %s\n", progname, db_strerror(ret));
	}

	/* Resend any caught signal. */
	__db_util_sigresend();

	if (passwd != NULL)
		__os_free(NULL, passwd);

	return (exitval == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
}

int
usage()
{
	fprintf(stderr, "\nusage: %s %s\n%s\n%s\n%s\n%s\n", progname,
	    "[-NcvV] [-h home] "
	    "[-H temporary environment home for internal use]",
	    "[-P password] [-C cache size in megabytes]",
	    "[-d db file name] [-D db name]",
	    "[-b file/offset] [-e file/offset]",
	    "[-s start time] [-z end time]");

	return (EXIT_FAILURE);
}

int
version_check()
{
	int v_major, v_minor, v_patch;

	/* Make sure we're loaded with the right version of the DB library. */
	(void)db_version(&v_major, &v_minor, &v_patch);
	if (v_major != DB_VERSION_MAJOR || v_minor != DB_VERSION_MINOR) {
		fprintf(stderr,
		    "%s: version %d.%d doesn't match library version %d.%d\n",
		    progname, DB_VERSION_MAJOR, DB_VERSION_MINOR,
		    v_major, v_minor);
		return (EXIT_FAILURE);
	}
	return (0);
}

/*
 * Print an unknown, application-specific log record as best we can, this is
 * all we can do to such a log record during the verification. The counting
 * is done in __db_dispatch because we can't pass the log verify handle into
 * this function.
 */
int
db_log_verify_app_record(dbenv, dbt, lsnp, op)
	DB_ENV *dbenv;
	DBT *dbt;
	DB_LSN *lsnp;
	db_recops op;
{
	u_int32_t i, len, len2, rectype;
	int ret;
	u_int8_t ch;
	char *buf, *p;

	DB_ASSERT(dbenv->env, op == DB_TXN_LOG_VERIFY);
	COMPQUIET(op, DB_TXN_LOG_VERIFY);
	ch = 0;
	ret = 0;
	i = len = len2 = rectype = 0;
	buf = p = NULL;

	/*
	 * Fetch the rectype, which always must be at the beginning of the
	 * record (if dispatching is to work at all).
	 */
	memcpy(&rectype, dbt->data, sizeof(rectype));

	/*
	 * Applications may wish to customize the output here based on the
	 * rectype.  We just print the entire log record in the generic
	 * mixed-hex-and-printable format we use for binary data.
	 */
	if ((ret = __os_malloc(dbenv->env,
	    len = 256 + 2 * dbt->size, &buf)) != 0)
		goto err;
	memset(buf, 0, len);
	snprintf(buf, len, "[%lu][%lu] App-specific log record: %lu\n\tdata: ",
	    (u_long)lsnp->file, (u_long)lsnp->offset, (u_long)rectype);

	/*
	 * Each unprintable character takes up several bytes, so be aware of
	 * memory violation.
	 */
	for (i = 0; i < dbt->size && len2 < len; i++) {
		ch = ((u_int8_t *)dbt->data)[i];
		len2 = (u_int32_t)strlen(buf);
		p = buf + len2;
		snprintf(p, len - len2 - 1,
		    isprint(ch) || ch == 0x0a ? "%c" : "%#x ", ch);
	}
	len2 = (u_int32_t)strlen(buf);
	p = buf + len2;
	snprintf(p, len - len2 - 1, "\n\n");
	__db_msg(dbenv->env, "%s", buf);

err:	if (buf != NULL)
		__os_free(dbenv->env, buf);
	return (ret);
}

/*
 * lsn_arg --
 *	Parse a LSN argument.
 */
int
lsn_arg(arg, lsnp)
	char *arg;
	DB_LSN *lsnp;
{
	u_long uval;
	char *p;

	/*
	 * Expected format is: lsn.file/lsn.offset.
	 */
	if ((p = strchr(arg, '/')) == NULL)
		return (1);
	*p = '\0';

	if (__db_getulong(NULL, progname, arg, 0, UINT32_MAX, &uval))
		return (1);
	lsnp->file = uval;
	if (__db_getulong(NULL, progname, p + 1, 0, UINT32_MAX, &uval))
		return (1);
	lsnp->offset = uval;
	return (0);
}
