/*-
 * Copyright (c) 2014-2018 MongoDB, Inc.
 * Copyright (c) 2008-2014 WiredTiger, Inc.
 *	All rights reserved.
 *
 * See the file LICENSE for redistribution information.
 */

#include "wt_internal.h"

/*
 * __conn_compat_parse --
 *	Parse a compatibility release string into its parts.
 */
static int
__conn_compat_parse(WT_SESSION_IMPL *session,
    WT_CONFIG_ITEM *cvalp, uint16_t *majorp, uint16_t *minorp)
{
	uint16_t unused_patch;

	/*
	 * Accept either a major.minor.patch release string or a major.minor
	 * release string.  We ignore the patch value, but allow it in
	 * the string.
	 */
	if (sscanf(cvalp->str,
	    "%" SCNu16 ".%" SCNu16, majorp, minorp) != 2 &&
	    sscanf(cvalp->str, "%" SCNu16 ".%" SCNu16 ".%" SCNu16,
	    majorp, minorp, &unused_patch) != 3)
		WT_RET_MSG(session, EINVAL,
		    "illegal compatibility release");
	if (*majorp > WIREDTIGER_VERSION_MAJOR)
		WT_RET_MSG(session, ENOTSUP,
		    WT_COMPAT_MSG_PREFIX
		    "unsupported major version");
	if (*majorp == WIREDTIGER_VERSION_MAJOR &&
	    *minorp > WIREDTIGER_VERSION_MINOR)
		WT_RET_MSG(session, ENOTSUP,
		    WT_COMPAT_MSG_PREFIX
		    "unsupported minor version");
	return (0);
}

/*
 * __wt_conn_compat_config --
 *	Configure compatibility version.
 */
int
__wt_conn_compat_config(
    WT_SESSION_IMPL *session, const char **cfg, bool reconfig)
{
	WT_CONFIG_ITEM cval;
	WT_CONNECTION_IMPL *conn;
	WT_DECL_RET;
	uint16_t max_major, max_minor, min_major, min_minor;
	uint16_t rel_major, rel_minor;
	char *value;
	bool txn_active;

	conn = S2C(session);
	value = NULL;
	max_major = WT_CONN_COMPAT_NONE;
	max_minor = WT_CONN_COMPAT_NONE;
	min_major = WT_CONN_COMPAT_NONE;
	min_minor = WT_CONN_COMPAT_NONE;

	WT_RET(__wt_config_gets(session, cfg, "compatibility.release", &cval));
	if (cval.len == 0) {
		rel_major = WIREDTIGER_VERSION_MAJOR;
		rel_minor = WIREDTIGER_VERSION_MINOR;
		F_CLR(conn, WT_CONN_COMPATIBILITY);
	} else {
		WT_RET(__conn_compat_parse(
		    session, &cval, &rel_major, &rel_minor));

		/*
		 * We're doing an upgrade or downgrade, check whether
		 * transactions are active.
		 */
		WT_RET(__wt_txn_activity_check(session, &txn_active));
		if (txn_active)
			WT_RET_MSG(session, ENOTSUP,
			    "system must be quiescent"
			    " for upgrade or downgrade");
		F_SET(conn, WT_CONN_COMPATIBILITY);
	}
	/*
	 * If we're a reconfigure and the user did not set any compatibility,
	 * we're done.
	 */
	if (reconfig && !F_ISSET(conn, WT_CONN_COMPATIBILITY))
		goto done;

	/*
	 * The maximum and minimum required version for existing files
	 * is only available on opening the connection, not reconfigure.
	 */
	WT_RET(__wt_config_gets(session,
	    cfg, "compatibility.require_min", &cval));
	if (cval.len != 0)
		WT_RET(__conn_compat_parse(
		    session, &cval, &min_major, &min_minor));

	WT_RET(__wt_config_gets(session,
	    cfg, "compatibility.require_max", &cval));
	if (cval.len != 0)
		WT_RET(__conn_compat_parse(
		    session, &cval, &max_major, &max_minor));

	/*
	 * The maximum required must be greater than or equal to the
	 * compatibility release we're using now. This is on an open and we're
	 * checking the two against each other. We'll check against what was
	 * saved on a restart later.
	 */
	if (!reconfig && max_major != WT_CONN_COMPAT_NONE &&
	    (max_major < rel_major ||
	    (max_major == rel_major && max_minor < rel_minor)))
		WT_RET_MSG(session, ENOTSUP,
		    WT_COMPAT_MSG_PREFIX
		    "required max of %" PRIu16 ".%" PRIu16
		    "cannot be smaller than compatibility release %"
		    PRIu16 ".%" PRIu16,
		    max_major, max_minor, rel_major, rel_minor);

	/*
	 * The minimum required must be less than or equal to the compatibility
	 * release we're using now. This is on an open and we're checking the
	 * two against each other. We'll check against what was saved on a
	 * restart later.
	 */
	if (!reconfig && min_major != WT_CONN_COMPAT_NONE &&
	    (min_major > rel_major ||
	    (min_major == rel_major && min_minor > rel_minor)))
		WT_RET_MSG(session, ENOTSUP,
		    WT_COMPAT_MSG_PREFIX
		    "required min of %" PRIu16 ".%" PRIu16
		    "cannot be larger than compatibility release %"
		    PRIu16 ".%" PRIu16,
		    min_major, min_minor, rel_major, rel_minor);

	/*
	 * On a reconfigure, check the new release version against any
	 * required maximum version set on open.
	 */
	if (reconfig && conn->req_max_major != WT_CONN_COMPAT_NONE &&
	    (conn->req_max_major < rel_major ||
	    (conn->req_max_major == rel_major &&
	    conn->req_max_minor < rel_minor)))
		WT_RET_MSG(session, ENOTSUP,
		    WT_COMPAT_MSG_PREFIX
		    "required max of %" PRIu16 ".%" PRIu16
		    "cannot be smaller than requested compatibility release %"
		    PRIu16 ".%" PRIu16,
		    conn->req_max_major, conn->req_max_minor,
		    rel_major, rel_minor);

	/*
	 * On a reconfigure, check the new release version against any
	 * required minimum version set on open.
	 */
	if (reconfig && conn->req_min_major != WT_CONN_COMPAT_NONE &&
	    (conn->req_min_major > rel_major ||
	    (conn->req_min_major == rel_major &&
	    conn->req_min_minor > rel_minor)))
		WT_RET_MSG(session, ENOTSUP,
		    WT_COMPAT_MSG_PREFIX
		    "required min of %" PRIu16 ".%" PRIu16
		    "cannot be larger than requested compatibility release %"
		    PRIu16 ".%" PRIu16,
		    conn->req_min_major, conn->req_min_minor,
		    rel_major, rel_minor);

	conn->compat_major = rel_major;
	conn->compat_minor = rel_minor;

	/*
	 * Only rewrite the turtle file if this is a reconfig. On startup
	 * it will get written as part of creating the connection. We do this
	 * after checking the required minimum version so that we don't rewrite
	 * the turtle file if there is an error.
	 */
	if (reconfig)
		WT_RET(__wt_metadata_turtle_rewrite(session));

	/*
	 * The required maximum and minimum cannot be set via reconfigure and
	 * they are meaningless on a newly created database. We're done in
	 * those cases.
	 */
	if (reconfig || conn->is_new ||
	    (min_major == WT_CONN_COMPAT_NONE &&
	    max_major == WT_CONN_COMPAT_NONE))
		goto done;

	/*
	 * Check the minimum required against any saved compatibility version
	 * in the turtle file saved from an earlier run.
	 */
	rel_major = rel_minor = WT_CONN_COMPAT_NONE;
	if ((ret =
	    __wt_metadata_search(session, WT_METADATA_COMPAT, &value)) == 0) {
		WT_ERR(__wt_config_getones(session, value, "major", &cval));
		rel_major = (uint16_t)cval.val;
		WT_ERR(__wt_config_getones(session, value, "minor", &cval));
		rel_minor = (uint16_t)cval.val;
		if (max_major != WT_CONN_COMPAT_NONE &&
		    (max_major < rel_major ||
		    (max_major == rel_major && max_minor < rel_minor)))
			WT_ERR_MSG(session, ENOTSUP,
			    WT_COMPAT_MSG_PREFIX
			    "required max of %" PRIu16 ".%" PRIu16
			    "cannot be larger than saved release %"
			    PRIu16 ".%" PRIu16,
			    max_major, max_minor, rel_major, rel_minor);
		if (min_major != WT_CONN_COMPAT_NONE &&
		    (min_major > rel_major ||
		    (min_major == rel_major && min_minor > rel_minor)))
			WT_ERR_MSG(session, ENOTSUP,
			    WT_COMPAT_MSG_PREFIX
			    "required min of %" PRIu16 ".%" PRIu16
			    "cannot be larger than saved release %"
			    PRIu16 ".%" PRIu16,
			    min_major, min_minor, rel_major, rel_minor);
	} else if (ret == WT_NOTFOUND)
		ret = 0;
	else
		WT_ERR(ret);

done:	conn->req_max_major = max_major;
	conn->req_max_minor = max_minor;
	conn->req_min_major = min_major;
	conn->req_min_minor = min_minor;

err:	if (value != NULL)
		__wt_free(session, value);

	return (ret);
}

/*
 * __wt_conn_optrack_setup --
 *     Set up operation logging.
 */
int
__wt_conn_optrack_setup(WT_SESSION_IMPL *session,
    const char *cfg[], bool reconfig)
{
	WT_CONFIG_ITEM cval;
	WT_CONNECTION_IMPL *conn;
	WT_DECL_ITEM(buf);
	WT_DECL_RET;

	conn = S2C(session);

	/* Once an operation tracking path has been set it can't be changed. */
	if (!reconfig) {
		WT_RET(__wt_config_gets(session,
		    cfg, "operation_tracking.path", &cval));
		WT_RET(__wt_strndup(session,
		    cval.str, cval.len, &conn->optrack_path));
	}

	WT_RET(__wt_config_gets(session,
	    cfg, "operation_tracking.enabled", &cval));
	if (cval.val == 0) {
		if (F_ISSET(conn, WT_CONN_OPTRACK)) {
			WT_RET(__wt_conn_optrack_teardown(session, reconfig));
			F_CLR(conn, WT_CONN_OPTRACK);
		}
		return (0);
	}
	if (F_ISSET(conn, WT_CONN_READONLY))
		/* Operation tracking isn't supported in read-only mode */
		WT_RET_MSG(session, EINVAL,
		    "Operation tracking is incompatible with read only "
		    "configuration.");
	if (F_ISSET(conn, WT_CONN_OPTRACK))
		/* Already enabled, nothing else to do */
		return (0);

	/*
	 * Operation tracking files will include the ID of the creating process
	 * in their name, so we can distinguish between log files created by
	 * different WiredTiger processes in the same directory. We cache the
	 * process id for future use.
	 */
	conn->optrack_pid = __wt_process_id();

	/*
	 * Open the file in the same directory that will hold a map of
	 * translations between function names and function IDs. If the file
	 * exists, remove it.
	 */
	WT_RET(__wt_scr_alloc(session, 0, &buf));
	WT_ERR(__wt_filename_construct(session, conn->optrack_path,
	    "optrack-map", conn->optrack_pid, UINT32_MAX, buf));
	WT_ERR(__wt_open(session,
	    (const char *)buf->data, WT_FS_OPEN_FILE_TYPE_REGULAR,
	    WT_FS_OPEN_CREATE, &conn->optrack_map_fh));

	WT_ERR(__wt_spin_init(session,
	    &conn->optrack_map_spinlock, "optrack map spinlock"));

	WT_ERR(__wt_malloc(session, WT_OPTRACK_BUFSIZE,
	    &conn->dummy_session.optrack_buf));

	/* Set operation tracking on */
	F_SET(conn, WT_CONN_OPTRACK);

err:	__wt_scr_free(session, &buf);
	return (ret);
}

/*
 * __wt_conn_optrack_teardown --
 *      Clean up connection-wide resources used for operation logging.
 */
int
__wt_conn_optrack_teardown(WT_SESSION_IMPL *session, bool reconfig)
{
	WT_CONNECTION_IMPL *conn;
	WT_DECL_RET;

	conn = S2C(session);

	if (!reconfig)
		/* Looks like we are shutting down */
		__wt_free(session, conn->optrack_path);

	if (!F_ISSET(conn, WT_CONN_OPTRACK))
		return (0);

	__wt_spin_destroy(session, &conn->optrack_map_spinlock);

	WT_TRET(__wt_close(session, &conn->optrack_map_fh));
	__wt_free(session, conn->dummy_session.optrack_buf);

	return (ret);
}

/*
 * __wt_conn_statistics_config --
 *	Set statistics configuration.
 */
int
__wt_conn_statistics_config(WT_SESSION_IMPL *session, const char *cfg[])
{
	WT_CONFIG_ITEM cval, sval;
	WT_CONNECTION_IMPL *conn;
	WT_DECL_RET;
	uint32_t flags;
	int set;

	conn = S2C(session);

	WT_RET(__wt_config_gets(session, cfg, "statistics", &cval));

	flags = 0;
	set = 0;
	if ((ret = __wt_config_subgets(
	    session, &cval, "none", &sval)) == 0 && sval.val != 0) {
		flags = 0;
		++set;
	}
	WT_RET_NOTFOUND_OK(ret);

	if ((ret = __wt_config_subgets(
	    session, &cval, "fast", &sval)) == 0 && sval.val != 0) {
		LF_SET(WT_STAT_TYPE_FAST);
		++set;
	}
	WT_RET_NOTFOUND_OK(ret);

	if ((ret = __wt_config_subgets(
	    session, &cval, "all", &sval)) == 0 && sval.val != 0) {
		LF_SET(
		    WT_STAT_TYPE_ALL | WT_STAT_TYPE_CACHE_WALK |
		    WT_STAT_TYPE_FAST | WT_STAT_TYPE_TREE_WALK);
		++set;
	}
	WT_RET_NOTFOUND_OK(ret);

	if (set > 1)
		WT_RET_MSG(session, EINVAL,
		    "Only one of all, fast, none configuration values should "
		    "be specified");

	/*
	 * Now that we've parsed general statistics categories, process
	 * sub-categories.
	 */
	if ((ret = __wt_config_subgets(
	    session, &cval, "cache_walk", &sval)) == 0 && sval.val != 0)
		/*
		 * Configuring cache walk statistics implies fast statistics.
		 * Keep that knowledge internal for now - it may change in the
		 * future.
		 */
		LF_SET(WT_STAT_TYPE_FAST | WT_STAT_TYPE_CACHE_WALK);
	WT_RET_NOTFOUND_OK(ret);

	if ((ret = __wt_config_subgets(
	    session, &cval, "tree_walk", &sval)) == 0 && sval.val != 0)
		/*
		 * Configuring tree walk statistics implies fast statistics.
		 * Keep that knowledge internal for now - it may change in the
		 * future.
		 */
		LF_SET(WT_STAT_TYPE_FAST | WT_STAT_TYPE_TREE_WALK);
	WT_RET_NOTFOUND_OK(ret);

	if ((ret = __wt_config_subgets(
	    session, &cval, "clear", &sval)) == 0 && sval.val != 0) {
		if (!LF_ISSET(WT_STAT_TYPE_ALL | WT_STAT_TYPE_CACHE_WALK |
		    WT_STAT_TYPE_FAST | WT_STAT_TYPE_TREE_WALK))
			WT_RET_MSG(session, EINVAL,
			    "the value \"clear\" can only be specified if "
			    "statistics are enabled");
		LF_SET(WT_STAT_CLEAR);
	}
	WT_RET_NOTFOUND_OK(ret);

	/* Configuring statistics clears any existing values. */
	conn->stat_flags = flags;

	return (0);
}

/*
 * __wt_conn_reconfig --
 *	Reconfigure a connection (internal version).
 */
int
__wt_conn_reconfig(WT_SESSION_IMPL *session, const char **cfg)
{
	WT_CONNECTION_IMPL *conn;
	WT_DECL_RET;
	const char *p;

	conn = S2C(session);

	/* Serialize reconfiguration. */
	__wt_spin_lock(session, &conn->reconfig_lock);

	/*
	 * The configuration argument has been checked for validity, update the
	 * previous connection configuration.
	 *
	 * DO NOT merge the configuration before the reconfigure calls.  Some
	 * of the underlying reconfiguration functions do explicit checks with
	 * the second element of the configuration array, knowing the defaults
	 * are in slot #1 and the application's modifications are in slot #2.
	 *
	 * Replace the base configuration set up by CONNECTION_API_CALL with
	 * the current connection configuration, otherwise reconfiguration
	 * functions will find the base value instead of previously configured
	 * value.
	 */
	cfg[0] = conn->cfg;

	/*
	 * Reconfigure the system.
	 *
	 * The compatibility version check is special: upgrade / downgrade
	 * cannot be done with transactions active, and checkpoints must not
	 * span a version change.  Hold the checkpoint lock to avoid conflicts
	 * with WiredTiger's checkpoint thread, and rely on the documentation
	 * specifying that no new operations can start until the upgrade /
	 * downgrade completes.
	 */
	WT_WITH_CHECKPOINT_LOCK(session,
	    ret = __wt_conn_compat_config(session, cfg, true));
	WT_ERR(ret);
	WT_ERR(__wt_conn_optrack_setup(session, cfg, true));
	WT_ERR(__wt_conn_statistics_config(session, cfg));
	WT_ERR(__wt_async_reconfig(session, cfg));
	WT_ERR(__wt_cache_config(session, true, cfg));
	WT_ERR(__wt_checkpoint_server_create(session, cfg));
	WT_ERR(__wt_logmgr_reconfig(session, cfg));
	WT_ERR(__wt_lsm_manager_reconfig(session, cfg));
	WT_ERR(__wt_statlog_create(session, cfg));
	WT_ERR(__wt_sweep_config(session, cfg));
	WT_ERR(__wt_verbose_config(session, cfg));
	WT_ERR(__wt_timing_stress_config(session, cfg));

	/* Third, merge everything together, creating a new connection state. */
	WT_ERR(__wt_config_merge(session, cfg, NULL, &p));
	__wt_free(session, conn->cfg);
	conn->cfg = p;

err:	__wt_spin_unlock(session, &conn->reconfig_lock);

	return (ret);
}
