Blob Blame History Raw
/*
 * Copyright (c) 2004, 2005 Christophe Varoqui
 * Copyright (c) 2005 Benjamin Marzinski, Redhat
 * Copyright (c) 2005 Kiyoshi Ueda, NEC
 */
#include <stdio.h>

#include "nvme-lib.h"
#include "checkers.h"
#include "memory.h"
#include "vector.h"
#include "structs.h"
#include "config.h"
#include "debug.h"
#include "pgpolicies.h"
#include "alias.h"
#include "defaults.h"
#include "devmapper.h"
#include "prio.h"
#include "discovery.h"
#include "dict.h"
#include "util.h"
#include "sysfs.h"
#include "prioritizers/alua_rtpg.h"
#include "prkey.h"
#include "propsel.h"
#include <inttypes.h>
#include <libudev.h>

pgpolicyfn *pgpolicies[] = {
	NULL,
	one_path_per_group,
	one_group,
	group_by_serial,
	group_by_prio,
	group_by_node_name
};

#define do_set(var, src, dest, msg)					\
do {									\
	if (src && src->var) {						\
		dest = src->var;					\
		origin = msg;						\
		goto out;						\
	}								\
} while(0)

#define __do_set_from_vec(type, var, src, dest)				\
({									\
	type *_p;							\
	bool _found = false;						\
	int i;								\
									\
	vector_foreach_slot(src, _p, i) {				\
		if (_p->var) {						\
			dest = _p->var;					\
			_found = true;					\
			break;						\
		}							\
	}								\
	_found;								\
})

#define __do_set_from_hwe(var, src, dest) \
	__do_set_from_vec(struct hwentry, var, (src)->hwe, dest)

#define do_set_from_hwe(var, src, dest, msg)				\
	if (__do_set_from_hwe(var, src, dest)) {			\
		origin = msg;						\
		goto out;						\
	}

static const char default_origin[] = "(setting: multipath internal)";
static const char hwe_origin[] =
	"(setting: storage device configuration)";
static const char multipaths_origin[] =
	"(setting: multipath.conf multipaths section)";
static const char conf_origin[] =
	"(setting: multipath.conf defaults/devices section)";
static const char overrides_origin[] =
	"(setting: multipath.conf overrides section)";
static const char cmdline_origin[] =
	"(setting: multipath command line [-p] flag)";
static const char autodetect_origin[] =
	"(setting: storage device autodetected)";
static const char marginal_path_origin[] =
	"(setting: implied by marginal_path check)";
static const char delay_watch_origin[] =
	"(setting: implied by delay_watch_checks)";
static const char delay_wait_origin[] =
	"(setting: implied by delay_wait_checks)";

#define do_default(dest, value)						\
do {									\
	dest = value;							\
	origin = default_origin;					\
} while(0)

#define mp_set_mpe(var)							\
do_set(var, mp->mpe, mp->var, multipaths_origin)
#define mp_set_hwe(var)							\
do_set_from_hwe(var, mp, mp->var, hwe_origin)
#define mp_set_ovr(var)							\
do_set(var, conf->overrides, mp->var, overrides_origin)
#define mp_set_conf(var)						\
do_set(var, conf, mp->var, conf_origin)
#define mp_set_default(var, value)					\
do_default(mp->var, value)

#define pp_set_mpe(var)							\
do_set(var, mpe, pp->var, multipaths_origin)
#define pp_set_hwe(var)							\
do_set_from_hwe(var, pp, pp->var, hwe_origin)
#define pp_set_conf(var)						\
do_set(var, conf, pp->var, conf_origin)
#define pp_set_ovr(var)							\
do_set(var, conf->overrides, pp->var, overrides_origin)
#define pp_set_default(var, value)					\
do_default(pp->var, value)

#define do_attr_set(var, src, shift, msg)				\
do {									\
	if (src && (src->attribute_flags & (1 << shift))) {		\
		mp->attribute_flags |= (1 << shift);			\
		mp->var = src->var;					\
		origin = msg;						\
		goto out;						\
	}								\
} while(0)

#define set_attr_mpe(var, shift)					\
do_attr_set(var, mp->mpe, shift, "(setting: multipath.conf multipaths section)")
#define set_attr_conf(var, shift)					\
do_attr_set(var, conf, shift, "(setting: multipath.conf defaults/devices section)")

#define do_prkey_set(src, msg)						\
do {									\
	if (src && src->prkey_source != PRKEY_SOURCE_NONE) {		\
		mp->prkey_source = src->prkey_source;			\
		mp->reservation_key = src->reservation_key;		\
		mp->sa_flags = src->sa_flags;				\
		origin = msg;						\
		goto out;						\
	}								\
} while (0)

int select_mode(struct config *conf, struct multipath *mp)
{
	const char *origin;

	set_attr_mpe(mode, ATTR_MODE);
	set_attr_conf(mode, ATTR_MODE);
	mp->attribute_flags &= ~(1 << ATTR_MODE);
	return 0;
out:
	condlog(3, "%s: mode = 0%o %s", mp->alias, mp->mode, origin);
	return 0;
}

int select_uid(struct config *conf, struct multipath *mp)
{
	const char *origin;

	set_attr_mpe(uid, ATTR_UID);
	set_attr_conf(uid, ATTR_UID);
	mp->attribute_flags &= ~(1 << ATTR_UID);
	return 0;
out:
	condlog(3, "%s: uid = 0%o %s", mp->alias, mp->uid, origin);
	return 0;
}

int select_gid(struct config *conf, struct multipath *mp)
{
	const char *origin;

	set_attr_mpe(gid, ATTR_GID);
	set_attr_conf(gid, ATTR_GID);
	mp->attribute_flags &= ~(1 << ATTR_GID);
	return 0;
out:
	condlog(3, "%s: gid = 0%o %s", mp->alias, mp->gid, origin);
	return 0;
}

/*
 * selectors :
 * traverse the configuration layers from most specific to most generic
 * stop at first explicit setting found
 */
int select_rr_weight(struct config *conf, struct multipath * mp)
{
	const char *origin;
	char buff[13];

	mp_set_mpe(rr_weight);
	mp_set_ovr(rr_weight);
	mp_set_hwe(rr_weight);
	mp_set_conf(rr_weight);
	mp_set_default(rr_weight, DEFAULT_RR_WEIGHT);
out:
	print_rr_weight(buff, 13, mp->rr_weight);
	condlog(3, "%s: rr_weight = %s %s", mp->alias, buff, origin);
	return 0;
}

int select_pgfailback(struct config *conf, struct multipath * mp)
{
	const char *origin;
	char buff[13];

	mp_set_mpe(pgfailback);
	mp_set_ovr(pgfailback);
	mp_set_hwe(pgfailback);
	mp_set_conf(pgfailback);
	mp_set_default(pgfailback, DEFAULT_FAILBACK);
out:
	print_pgfailback(buff, 13, mp->pgfailback);
	condlog(3, "%s: failback = %s %s", mp->alias, buff, origin);
	return 0;
}

int select_pgpolicy(struct config *conf, struct multipath * mp)
{
	const char *origin;
	char buff[POLICY_NAME_SIZE];

	if (conf->pgpolicy_flag > 0) {
		mp->pgpolicy = conf->pgpolicy_flag;
		origin = cmdline_origin;
		goto out;
	}
	mp_set_mpe(pgpolicy);
	mp_set_ovr(pgpolicy);
	mp_set_hwe(pgpolicy);
	mp_set_conf(pgpolicy);
	mp_set_default(pgpolicy, DEFAULT_PGPOLICY);
out:
	mp->pgpolicyfn = pgpolicies[mp->pgpolicy];
	get_pgpolicy_name(buff, POLICY_NAME_SIZE, mp->pgpolicy);
	condlog(3, "%s: path_grouping_policy = %s %s", mp->alias, buff, origin);
	return 0;
}

int select_selector(struct config *conf, struct multipath * mp)
{
	const char *origin;

	mp_set_mpe(selector);
	mp_set_ovr(selector);
	mp_set_hwe(selector);
	mp_set_conf(selector);
	mp_set_default(selector, DEFAULT_SELECTOR);
out:
	mp->selector = STRDUP(mp->selector);
	condlog(3, "%s: path_selector = \"%s\" %s", mp->alias, mp->selector,
		origin);
	return 0;
}

static void
select_alias_prefix (struct config *conf, struct multipath * mp)
{
	const char *origin;

	mp_set_ovr(alias_prefix);
	mp_set_hwe(alias_prefix);
	mp_set_conf(alias_prefix);
	mp_set_default(alias_prefix, DEFAULT_ALIAS_PREFIX);
out:
	condlog(3, "%s: alias_prefix = %s %s", mp->wwid, mp->alias_prefix,
		origin);
}

static int
want_user_friendly_names(struct config *conf, struct multipath * mp)
{

	const char *origin;
	int user_friendly_names;

	do_set(user_friendly_names, mp->mpe, user_friendly_names,
	       multipaths_origin);
	do_set(user_friendly_names, conf->overrides, user_friendly_names,
	       overrides_origin);
	do_set_from_hwe(user_friendly_names, mp, user_friendly_names,
			hwe_origin);
	do_set(user_friendly_names, conf, user_friendly_names,
	       conf_origin);
	do_default(user_friendly_names, DEFAULT_USER_FRIENDLY_NAMES);
out:
	condlog(3, "%s: user_friendly_names = %s %s", mp->wwid,
		(user_friendly_names == USER_FRIENDLY_NAMES_ON)? "yes" : "no",
		origin);
	return (user_friendly_names == USER_FRIENDLY_NAMES_ON);
}

int select_alias(struct config *conf, struct multipath * mp)
{
	const char *origin = NULL;

	if (mp->mpe && mp->mpe->alias) {
		mp->alias = STRDUP(mp->mpe->alias);
		origin = multipaths_origin;
		goto out;
	}

	mp->alias = NULL;
	if (!want_user_friendly_names(conf, mp))
		goto out;

	select_alias_prefix(conf, mp);

	if (strlen(mp->alias_old) > 0) {
		mp->alias = use_existing_alias(mp->wwid, conf->bindings_file,
				mp->alias_old, mp->alias_prefix,
				conf->bindings_read_only);
		memset (mp->alias_old, 0, WWID_SIZE);
		origin = "(setting: using existing alias)";
	}

	if (mp->alias == NULL) {
		mp->alias = get_user_friendly_alias(mp->wwid,
				conf->bindings_file, mp->alias_prefix, conf->bindings_read_only);
		origin = "(setting: user_friendly_name)";
	}
out:
	if (mp->alias == NULL) {
		mp->alias = STRDUP(mp->wwid);
		origin = "(setting: default to WWID)";
	}
	if (mp->alias)
		condlog(3, "%s: alias = %s %s", mp->wwid, mp->alias, origin);
	return mp->alias ? 0 : 1;
}

void reconcile_features_with_options(const char *id, char **features, int* no_path_retry,
		  int *retain_hwhandler)
{
	static const char q_i_n_p[] = "queue_if_no_path";
	static const char r_a_h_h[] = "retain_attached_hw_handler";
	char buff[12];

	if (*features == NULL)
		return;
	if (id == NULL)
		id = "UNKNOWN";

	/*
	 * We only use no_path_retry internally. The "queue_if_no_path"
	 * device-mapper feature is derived from it when the map is loaded.
	 * For consistency, "queue_if_no_path" is removed from the
	 * internal libmultipath features string.
	 * For backward compatibility we allow 'features "1 queue_if_no_path"';
	 * it's translated into "no_path_retry queue" here.
	 */
	if (strstr(*features, q_i_n_p)) {
		condlog(0, "%s: option 'features \"1 %s\"' is deprecated, "
			"please use 'no_path_retry queue' instead",
			id, q_i_n_p);
		if (*no_path_retry == NO_PATH_RETRY_UNDEF) {
			*no_path_retry = NO_PATH_RETRY_QUEUE;
			print_no_path_retry(buff, sizeof(buff),
					    *no_path_retry);
			condlog(3, "%s: no_path_retry = %s (inherited setting from feature '%s')",
				id, buff, q_i_n_p);
		};
		/* Warn only if features string is overridden */
		if (*no_path_retry != NO_PATH_RETRY_QUEUE) {
			print_no_path_retry(buff, sizeof(buff),
					    *no_path_retry);
			condlog(2, "%s: ignoring feature '%s' because no_path_retry is set to '%s'",
				id, q_i_n_p, buff);
		}
		remove_feature(features, q_i_n_p);
	}
	if (strstr(*features, r_a_h_h)) {
		condlog(0, "%s: option 'features \"1 %s\"' is deprecated",
			id, r_a_h_h);
		if (*retain_hwhandler == RETAIN_HWHANDLER_UNDEF) {
			condlog(3, "%s: %s = on (inherited setting from feature '%s')",
				id, r_a_h_h, r_a_h_h);
			*retain_hwhandler = RETAIN_HWHANDLER_ON;
		} else if (*retain_hwhandler == RETAIN_HWHANDLER_OFF)
			condlog(2, "%s: ignoring feature '%s' because %s is set to 'off'",
				id, r_a_h_h, r_a_h_h);
		remove_feature(features, r_a_h_h);
	}
}

int select_features(struct config *conf, struct multipath *mp)
{
	const char *origin;

	mp_set_mpe(features);
	mp_set_ovr(features);
	mp_set_hwe(features);
	mp_set_conf(features);
	mp_set_default(features, DEFAULT_FEATURES);
out:
	mp->features = STRDUP(mp->features);

	reconcile_features_with_options(mp->alias, &mp->features,
					&mp->no_path_retry,
					&mp->retain_hwhandler);
	condlog(3, "%s: features = \"%s\" %s", mp->alias, mp->features, origin);
	return 0;
}

static int get_dh_state(struct path *pp, char *value, size_t value_len)
{
	struct udev_device *ud;

	if (pp->udev == NULL)
		return -1;

	ud = udev_device_get_parent_with_subsystem_devtype(
		pp->udev, "scsi", "scsi_device");
	if (ud == NULL)
		return -1;

	return sysfs_attr_get_value(ud, "dh_state", value, value_len);
}

int select_hwhandler(struct config *conf, struct multipath *mp)
{
	const char *origin;
	struct path *pp;
	/* dh_state is no longer than "detached" */
	char handler[12];
	static char alua_name[] = "1 alua";
	static const char tpgs_origin[]= "(setting: autodetected from TPGS)";
	char *dh_state;
	int i;
	bool all_tpgs = true, one_tpgs = false;

	dh_state = &handler[2];

	/*
	 * TPGS_UNDEF means that ALUA support couldn't determined either way
	 * yet, probably because the path was always down.
	 * If at least one path does have TPGS support, and no path has
	 * TPGS_NONE, assume that TPGS would be supported by all paths if
	 * all were up.
	 */
	vector_foreach_slot(mp->paths, pp, i) {
		int tpgs = path_get_tpgs(pp);

		all_tpgs = all_tpgs && tpgs != TPGS_NONE;
		one_tpgs = one_tpgs ||
			(tpgs != TPGS_NONE && tpgs != TPGS_UNDEF);
	}
	all_tpgs = all_tpgs && one_tpgs;

	if (mp->retain_hwhandler != RETAIN_HWHANDLER_OFF) {
		vector_foreach_slot(mp->paths, pp, i) {
			if (get_dh_state(pp, dh_state, sizeof(handler) - 2) > 0
			    && strcmp(dh_state, "detached")) {
				memcpy(handler, "1 ", 2);
				mp->hwhandler = handler;
				origin = "(setting: retained by kernel driver)";
				goto out;
			}
		}
	}

	mp_set_hwe(hwhandler);
	mp_set_conf(hwhandler);
	mp_set_default(hwhandler, DEFAULT_HWHANDLER);
out:
	if (all_tpgs && !strcmp(mp->hwhandler, DEFAULT_HWHANDLER) &&
		origin == default_origin) {
		mp->hwhandler = alua_name;
		origin = tpgs_origin;
	} else if (!all_tpgs && !strcmp(mp->hwhandler, alua_name)) {
		mp->hwhandler = DEFAULT_HWHANDLER;
		origin = tpgs_origin;
	}
	mp->hwhandler = STRDUP(mp->hwhandler);
	condlog(3, "%s: hardware_handler = \"%s\" %s", mp->alias, mp->hwhandler,
		origin);
	return 0;
}

/*
 * Current RDAC (NetApp E-Series) firmware relies
 * on periodic REPORT TARGET PORT GROUPS for
 * internal load balancing.
 * Using the sysfs priority checker defeats this purpose.
 *
 * Moreover, NetApp would also prefer the RDAC checker over ALUA.
 * (https://www.redhat.com/archives/dm-devel/2017-September/msg00326.html)
 */
static int
check_rdac(struct path * pp)
{
	int len;
	char buff[44];
	const char *checker_name;

	if (pp->bus != SYSFS_BUS_SCSI)
		return 0;
	/* Avoid ioctl if this is likely not an RDAC array */
	if (__do_set_from_hwe(checker_name, pp, checker_name) &&
	    strcmp(checker_name, RDAC))
		return 0;
	len = get_vpd_sgio(pp->fd, 0xC9, 0, buff, 44);
	if (len <= 0)
		return 0;
	return !(memcmp(buff + 4, "vac1", 4));
}

int select_checker(struct config *conf, struct path *pp)
{
	const char *origin;
	char *ckr_name;
	struct checker * c = &pp->checker;

	if (pp->detect_checker == DETECT_CHECKER_ON) {
		origin = autodetect_origin;
		if (check_rdac(pp)) {
			ckr_name = RDAC;
			goto out;
		} else if (path_get_tpgs(pp) != TPGS_NONE) {
			ckr_name = TUR;
			goto out;
		}
	}
	do_set(checker_name, conf->overrides, ckr_name, overrides_origin);
	do_set_from_hwe(checker_name, pp, ckr_name, hwe_origin);
	do_set(checker_name, conf, ckr_name, conf_origin);
	do_default(ckr_name, DEFAULT_CHECKER);
out:
	checker_get(conf->multipath_dir, c, ckr_name);
	condlog(3, "%s: path_checker = %s %s", pp->dev,
		checker_name(c), origin);
	if (conf->checker_timeout) {
		c->timeout = conf->checker_timeout;
		condlog(3, "%s: checker timeout = %u s %s",
			pp->dev, c->timeout, conf_origin);
	}
	else if (sysfs_get_timeout(pp, &c->timeout) > 0)
		condlog(3, "%s: checker timeout = %u s (setting: kernel sysfs)",
			pp->dev, c->timeout);
	else {
		c->timeout = DEF_TIMEOUT;
		condlog(3, "%s: checker timeout = %u s %s",
			pp->dev, c->timeout, default_origin);
	}
	return 0;
}

int select_getuid(struct config *conf, struct path *pp)
{
	const char *origin;

	pp->uid_attribute = get_uid_attribute_by_attrs(conf, pp->dev);
	if (pp->uid_attribute) {
		origin = "(setting: multipath.conf defaults section / uid_attrs)";
		goto out;
	}

	pp_set_ovr(getuid);
	pp_set_ovr(uid_attribute);
	pp_set_hwe(getuid);
	pp_set_hwe(uid_attribute);
	pp_set_conf(getuid);
	pp_set_conf(uid_attribute);
	pp_set_default(uid_attribute, DEFAULT_UID_ATTRIBUTE);
out:
	if (pp->uid_attribute)
		condlog(3, "%s: uid_attribute = %s %s", pp->dev,
			pp->uid_attribute, origin);
	else if (pp->getuid)
		condlog(3, "%s: getuid = \"%s\" %s", pp->dev, pp->getuid,
			origin);
	return 0;
}

void
detect_prio(struct config *conf, struct path * pp)
{
	struct prio *p = &pp->prio;
	char buff[512];
	char *default_prio;
	int tpgs;

	switch(pp->bus) {
	case SYSFS_BUS_NVME:
		if (nvme_id_ctrl_ana(pp->fd, NULL) == 0)
			return;
		default_prio = PRIO_ANA;
		break;
	case SYSFS_BUS_SCSI:
		tpgs = path_get_tpgs(pp);
		if (tpgs == TPGS_NONE)
			return;
		if ((tpgs == TPGS_EXPLICIT || !check_rdac(pp)) &&
		    sysfs_get_asymmetric_access_state(pp, buff, 512) >= 0)
			default_prio = PRIO_SYSFS;
		else
			default_prio = PRIO_ALUA;
		break;
	default:
		return;
	}
	prio_get(conf->multipath_dir, p, default_prio, DEFAULT_PRIO_ARGS);
}

#define set_prio(dir, src, msg)					\
do {									\
	if (src && src->prio_name) {					\
		prio_get(dir, p, src->prio_name, src->prio_args);	\
		origin = msg;						\
		goto out;						\
	}								\
} while(0)

#define set_prio_from_vec(type, dir, src, msg, p)			\
do {									\
	type *_p;							\
	int i;								\
	char *prio_name = NULL, *prio_args = NULL;			\
									\
	vector_foreach_slot(src, _p, i) {				\
		if (prio_name == NULL && _p->prio_name)		\
			prio_name = _p->prio_name;			\
		if (prio_args == NULL && _p->prio_args)		\
			prio_args = _p->prio_args;			\
	}								\
	if (prio_name != NULL) {					\
		prio_get(dir, p, prio_name, prio_args);			\
		origin = msg;						\
		goto out;						\
	}								\
} while (0)

int select_prio(struct config *conf, struct path *pp)
{
	const char *origin;
	struct mpentry * mpe;
	struct prio * p = &pp->prio;
	int log_prio = 3;

	if (pp->detect_prio == DETECT_PRIO_ON) {
		detect_prio(conf, pp);
		if (prio_selected(p)) {
			origin = autodetect_origin;
			goto out;
		}
	}
	mpe = find_mpe(conf->mptable, pp->wwid);
	set_prio(conf->multipath_dir, mpe, multipaths_origin);
	set_prio(conf->multipath_dir, conf->overrides, overrides_origin);
	set_prio_from_vec(struct hwentry, conf->multipath_dir,
			  pp->hwe, hwe_origin, p);
	set_prio(conf->multipath_dir, conf, conf_origin);
	prio_get(conf->multipath_dir, p, DEFAULT_PRIO, DEFAULT_PRIO_ARGS);
	origin = default_origin;
out:
	/*
	 * fetch tpgs mode for alua, if its not already obtained
	 */
	if (!strncmp(prio_name(p), PRIO_ALUA, PRIO_NAME_LEN)) {
		int tpgs = path_get_tpgs(pp);

		if (tpgs == TPGS_NONE) {
			prio_get(conf->multipath_dir,
				 p, DEFAULT_PRIO, DEFAULT_PRIO_ARGS);
			origin = "(setting: emergency fallback - alua failed)";
			log_prio = 1;
		}
	}
	condlog(log_prio, "%s: prio = %s %s", pp->dev, prio_name(p), origin);
	condlog(3, "%s: prio args = \"%s\" %s", pp->dev, prio_args(p), origin);
	return 0;
}

int select_no_path_retry(struct config *conf, struct multipath *mp)
{
	const char *origin = NULL;
	char buff[12];

	if (mp->disable_queueing) {
		condlog(0, "%s: queueing disabled", mp->alias);
		mp->no_path_retry = NO_PATH_RETRY_FAIL;
		return 0;
	}
	mp_set_mpe(no_path_retry);
	mp_set_ovr(no_path_retry);
	mp_set_hwe(no_path_retry);
	mp_set_conf(no_path_retry);
out:
	print_no_path_retry(buff, 12, mp->no_path_retry);
	if (origin)
		condlog(3, "%s: no_path_retry = %s %s", mp->alias, buff,
			origin);
	else
		condlog(3, "%s: no_path_retry = undef %s",
			mp->alias, default_origin);
	return 0;
}

int
select_minio_rq (struct config *conf, struct multipath * mp)
{
	const char *origin;

	do_set(minio_rq, mp->mpe, mp->minio, multipaths_origin);
	do_set(minio_rq, conf->overrides, mp->minio, overrides_origin);
	do_set_from_hwe(minio_rq, mp, mp->minio, hwe_origin);
	do_set(minio_rq, conf, mp->minio, conf_origin);
	do_default(mp->minio, DEFAULT_MINIO_RQ);
out:
	condlog(3, "%s: minio = %i %s", mp->alias, mp->minio, origin);
	return 0;
}

int
select_minio_bio (struct config *conf, struct multipath * mp)
{
	const char *origin;

	mp_set_mpe(minio);
	mp_set_ovr(minio);
	mp_set_hwe(minio);
	mp_set_conf(minio);
	mp_set_default(minio, DEFAULT_MINIO);
out:
	condlog(3, "%s: minio = %i %s", mp->alias, mp->minio, origin);
	return 0;
}

int select_minio(struct config *conf, struct multipath *mp)
{
	unsigned int minv_dmrq[3] = {1, 1, 0};

	if (VERSION_GE(conf->version, minv_dmrq))
		return select_minio_rq(conf, mp);
	else
		return select_minio_bio(conf, mp);
}

int select_fast_io_fail(struct config *conf, struct multipath *mp)
{
	const char *origin;
	char buff[12];

	mp_set_ovr(fast_io_fail);
	mp_set_hwe(fast_io_fail);
	mp_set_conf(fast_io_fail);
	mp_set_default(fast_io_fail, DEFAULT_FAST_IO_FAIL);
out:
	print_fast_io_fail(buff, 12, mp->fast_io_fail);
	condlog(3, "%s: fast_io_fail_tmo = %s %s", mp->alias, buff, origin);
	return 0;
}

int select_dev_loss(struct config *conf, struct multipath *mp)
{
	const char *origin;
	char buff[12];

	mp_set_ovr(dev_loss);
	mp_set_hwe(dev_loss);
	mp_set_conf(dev_loss);
	mp->dev_loss = 0;
	return 0;
out:
	print_dev_loss(buff, 12, mp->dev_loss);
	condlog(3, "%s: dev_loss_tmo = %s %s", mp->alias, buff, origin);
	return 0;
}

int select_flush_on_last_del(struct config *conf, struct multipath *mp)
{
	const char *origin;

	mp_set_mpe(flush_on_last_del);
	mp_set_ovr(flush_on_last_del);
	mp_set_hwe(flush_on_last_del);
	mp_set_conf(flush_on_last_del);
	mp_set_default(flush_on_last_del, DEFAULT_FLUSH);
out:
	condlog(3, "%s: flush_on_last_del = %s %s", mp->alias,
		(mp->flush_on_last_del == FLUSH_ENABLED)? "yes" : "no", origin);
	return 0;
}

int select_reservation_key(struct config *conf, struct multipath *mp)
{
	const char *origin;
	char buff[PRKEY_SIZE];
	char *from_file = "";
	uint64_t prkey = 0;

	do_prkey_set(mp->mpe, multipaths_origin);
	do_prkey_set(conf, conf_origin);
	put_be64(mp->reservation_key, 0);
	mp->sa_flags = 0;
	mp->prkey_source = PRKEY_SOURCE_NONE;
	return 0;
out:
	if (mp->prkey_source == PRKEY_SOURCE_FILE) {
		from_file = " (from prkeys file)";
		if (get_prkey(conf, mp, &prkey, &mp->sa_flags) != 0)
			put_be64(mp->reservation_key, 0);
		else
			put_be64(mp->reservation_key, prkey);
	}
	print_reservation_key(buff, PRKEY_SIZE, mp->reservation_key,
			      mp->sa_flags, mp->prkey_source);
	condlog(3, "%s: reservation_key = %s %s%s", mp->alias, buff, origin,
		from_file);
	return 0;
}

int select_retain_hwhandler(struct config *conf, struct multipath *mp)
{
	const char *origin;
	unsigned int minv_dm_retain[3] = {1, 5, 0};

	if (!VERSION_GE(conf->version, minv_dm_retain)) {
		mp->retain_hwhandler = RETAIN_HWHANDLER_OFF;
		origin = "(setting: WARNING, requires kernel dm-mpath version >= 1.5.0)";
		goto out;
	}
	if (get_linux_version_code() >= KERNEL_VERSION(4, 3, 0)) {
		mp->retain_hwhandler = RETAIN_HWHANDLER_ON;
		origin = "(setting: implied in kernel >= 4.3.0)";
		goto out;
	}
	mp_set_ovr(retain_hwhandler);
	mp_set_hwe(retain_hwhandler);
	mp_set_conf(retain_hwhandler);
	mp_set_default(retain_hwhandler, DEFAULT_RETAIN_HWHANDLER);
out:
	condlog(3, "%s: retain_attached_hw_handler = %s %s", mp->alias,
		(mp->retain_hwhandler == RETAIN_HWHANDLER_ON)? "yes" : "no",
		origin);
	return 0;
}

int select_detect_prio(struct config *conf, struct path *pp)
{
	const char *origin;

	pp_set_ovr(detect_prio);
	pp_set_hwe(detect_prio);
	pp_set_conf(detect_prio);
	pp_set_default(detect_prio, DEFAULT_DETECT_PRIO);
out:
	condlog(3, "%s: detect_prio = %s %s", pp->dev,
		(pp->detect_prio == DETECT_PRIO_ON)? "yes" : "no", origin);
	return 0;
}

int select_detect_checker(struct config *conf, struct path *pp)
{
	const char *origin;

	pp_set_ovr(detect_checker);
	pp_set_hwe(detect_checker);
	pp_set_conf(detect_checker);
	pp_set_default(detect_checker, DEFAULT_DETECT_CHECKER);
out:
	condlog(3, "%s: detect_checker = %s %s", pp->dev,
		(pp->detect_checker == DETECT_CHECKER_ON)? "yes" : "no",
		origin);
	return 0;
}

int select_deferred_remove(struct config *conf, struct multipath *mp)
{
	const char *origin;

#ifndef LIBDM_API_DEFERRED
	mp->deferred_remove = DEFERRED_REMOVE_OFF;
	origin = "(setting: WARNING, not compiled with support)";
	goto out;
#endif
	if (mp->deferred_remove == DEFERRED_REMOVE_IN_PROGRESS) {
		condlog(3, "%s: deferred remove in progress", mp->alias);
		return 0;
	}
	mp_set_mpe(deferred_remove);
	mp_set_ovr(deferred_remove);
	mp_set_hwe(deferred_remove);
	mp_set_conf(deferred_remove);
	mp_set_default(deferred_remove, DEFAULT_DEFERRED_REMOVE);
out:
	condlog(3, "%s: deferred_remove = %s %s", mp->alias,
		(mp->deferred_remove == DEFERRED_REMOVE_ON)? "yes" : "no",
		origin);
	return 0;
}

static inline int san_path_check_options_set(const struct multipath *mp)
{
	return mp->san_path_err_threshold > 0 ||
	       mp->san_path_err_forget_rate > 0 ||
	       mp->san_path_err_recovery_time > 0;
}

static int
use_delay_watch_checks(struct config *conf, struct multipath *mp)
{
	int value = NU_UNDEF;
	const char *origin = default_origin;
	char buff[12];

	do_set(delay_watch_checks, mp->mpe, value, multipaths_origin);
	do_set(delay_watch_checks, conf->overrides, value, overrides_origin);
	do_set_from_hwe(delay_watch_checks, mp, value, hwe_origin);
	do_set(delay_watch_checks, conf, value, conf_origin);
out:
	if (print_off_int_undef(buff, 12, value) != 0)
		condlog(3, "%s: delay_watch_checks = %s %s", mp->alias, buff,
			origin);
	return value;
}

static int
use_delay_wait_checks(struct config *conf, struct multipath *mp)
{
	int value = NU_UNDEF;
	const char *origin = default_origin;
	char buff[12];

	do_set(delay_wait_checks, mp->mpe, value, multipaths_origin);
	do_set(delay_wait_checks, conf->overrides, value, overrides_origin);
	do_set_from_hwe(delay_wait_checks, mp, value, hwe_origin);
	do_set(delay_wait_checks, conf, value, conf_origin);
out:
	if (print_off_int_undef(buff, 12, value) != 0)
		condlog(3, "%s: delay_wait_checks = %s %s", mp->alias, buff,
			origin);
	return value;
}

int select_delay_checks(struct config *conf, struct multipath *mp)
{
	int watch_checks, wait_checks;
	char buff[12];

	watch_checks = use_delay_watch_checks(conf, mp);
	wait_checks = use_delay_wait_checks(conf, mp);
	if (watch_checks <= 0 && wait_checks <= 0)
		return 0;
	if (san_path_check_options_set(mp)) {
		condlog(3, "%s: both marginal_path and delay_checks error detection options selected", mp->alias);
		condlog(3, "%s: ignoring delay_checks options", mp->alias);
		return 0;
	}
	mp->san_path_err_threshold = 1;
	condlog(3, "%s: san_path_err_threshold = 1 %s", mp->alias,
		(watch_checks > 0)? delay_watch_origin : delay_wait_origin);
	if (watch_checks > 0) {
		mp->san_path_err_forget_rate = watch_checks;
		print_off_int_undef(buff, 12, mp->san_path_err_forget_rate);
		condlog(3, "%s: san_path_err_forget_rate = %s %s", mp->alias,
			buff, delay_watch_origin);
	}
	if (wait_checks > 0) {
		mp->san_path_err_recovery_time = wait_checks *
						 conf->max_checkint;
		print_off_int_undef(buff, 12, mp->san_path_err_recovery_time);
		condlog(3, "%s: san_path_err_recovery_time = %s %s", mp->alias,
			buff, delay_wait_origin);
	}
	return 0;
}

static int san_path_deprecated_warned;
#define warn_san_path_deprecated(v, x)					\
	do {								\
		if (v->x > 0 && !san_path_deprecated_warned) {		\
		san_path_deprecated_warned = 1;				\
		condlog(1, "WARNING: option %s is deprecated, "		\
			"please use marginal_path options instead",	\
			#x);						\
		}							\
	} while(0)

int select_san_path_err_threshold(struct config *conf, struct multipath *mp)
{
	const char *origin;
	char buff[12];

	if (marginal_path_check_enabled(mp)) {
		mp->san_path_err_threshold = NU_NO;
		origin = marginal_path_origin;
		goto out;
	}
	mp_set_mpe(san_path_err_threshold);
	mp_set_ovr(san_path_err_threshold);
	mp_set_hwe(san_path_err_threshold);
	mp_set_conf(san_path_err_threshold);
	mp_set_default(san_path_err_threshold, DEFAULT_ERR_CHECKS);
out:
	if (print_off_int_undef(buff, 12, mp->san_path_err_threshold) != 0)
		condlog(3, "%s: san_path_err_threshold = %s %s",
			mp->alias, buff, origin);
	warn_san_path_deprecated(mp, san_path_err_threshold);
	return 0;
}

int select_san_path_err_forget_rate(struct config *conf, struct multipath *mp)
{
	const char *origin;
	char buff[12];

	if (marginal_path_check_enabled(mp)) {
		mp->san_path_err_forget_rate = NU_NO;
		origin = marginal_path_origin;
		goto out;
	}
	mp_set_mpe(san_path_err_forget_rate);
	mp_set_ovr(san_path_err_forget_rate);
	mp_set_hwe(san_path_err_forget_rate);
	mp_set_conf(san_path_err_forget_rate);
	mp_set_default(san_path_err_forget_rate, DEFAULT_ERR_CHECKS);
out:
	if (print_off_int_undef(buff, 12, mp->san_path_err_forget_rate) != 0)
		condlog(3, "%s: san_path_err_forget_rate = %s %s", mp->alias,
			buff, origin);
	warn_san_path_deprecated(mp, san_path_err_forget_rate);
	return 0;

}

int select_san_path_err_recovery_time(struct config *conf, struct multipath *mp)
{
	const char *origin;
	char buff[12];

	if (marginal_path_check_enabled(mp)) {
		mp->san_path_err_recovery_time = NU_NO;
		origin = marginal_path_origin;
		goto out;
	}
	mp_set_mpe(san_path_err_recovery_time);
	mp_set_ovr(san_path_err_recovery_time);
	mp_set_hwe(san_path_err_recovery_time);
	mp_set_conf(san_path_err_recovery_time);
	mp_set_default(san_path_err_recovery_time, DEFAULT_ERR_CHECKS);
out:
	if (print_off_int_undef(buff, 12, mp->san_path_err_recovery_time) != 0)
		condlog(3, "%s: san_path_err_recovery_time = %s %s", mp->alias,
			buff, origin);
	warn_san_path_deprecated(mp, san_path_err_recovery_time);
	return 0;

}

int select_marginal_path_err_sample_time(struct config *conf, struct multipath *mp)
{
	const char *origin;
	char buff[12];

	mp_set_mpe(marginal_path_err_sample_time);
	mp_set_ovr(marginal_path_err_sample_time);
	mp_set_hwe(marginal_path_err_sample_time);
	mp_set_conf(marginal_path_err_sample_time);
	mp_set_default(marginal_path_err_sample_time, DEFAULT_ERR_CHECKS);
out:
	if (print_off_int_undef(buff, 12, mp->marginal_path_err_sample_time)
	    != 0)
		condlog(3, "%s: marginal_path_err_sample_time = %s %s",
			mp->alias, buff, origin);
	return 0;
}

int select_marginal_path_err_rate_threshold(struct config *conf, struct multipath *mp)
{
	const char *origin;
	char buff[12];

	mp_set_mpe(marginal_path_err_rate_threshold);
	mp_set_ovr(marginal_path_err_rate_threshold);
	mp_set_hwe(marginal_path_err_rate_threshold);
	mp_set_conf(marginal_path_err_rate_threshold);
	mp_set_default(marginal_path_err_rate_threshold, DEFAULT_ERR_CHECKS);
out:
	if (print_off_int_undef(buff, 12, mp->marginal_path_err_rate_threshold)
	    != 0)
		condlog(3, "%s: marginal_path_err_rate_threshold = %s %s",
			mp->alias, buff, origin);
	return 0;
}

int select_marginal_path_err_recheck_gap_time(struct config *conf, struct multipath *mp)
{
	const char *origin;
	char buff[12];

	mp_set_mpe(marginal_path_err_recheck_gap_time);
	mp_set_ovr(marginal_path_err_recheck_gap_time);
	mp_set_hwe(marginal_path_err_recheck_gap_time);
	mp_set_conf(marginal_path_err_recheck_gap_time);
	mp_set_default(marginal_path_err_recheck_gap_time, DEFAULT_ERR_CHECKS);
out:
	if (print_off_int_undef(buff, 12,
				mp->marginal_path_err_recheck_gap_time) != 0)
		condlog(3, "%s: marginal_path_err_recheck_gap_time = %s %s",
			mp->alias, buff, origin);
	return 0;
}

int select_marginal_path_double_failed_time(struct config *conf, struct multipath *mp)
{
	const char *origin;
	char buff[12];

	mp_set_mpe(marginal_path_double_failed_time);
	mp_set_ovr(marginal_path_double_failed_time);
	mp_set_hwe(marginal_path_double_failed_time);
	mp_set_conf(marginal_path_double_failed_time);
	mp_set_default(marginal_path_double_failed_time, DEFAULT_ERR_CHECKS);
out:
	if (print_off_int_undef(buff, 12, mp->marginal_path_double_failed_time)
	    != 0)
		condlog(3, "%s: marginal_path_double_failed_time = %s %s",
			mp->alias, buff, origin);
	return 0;
}

int select_skip_kpartx (struct config *conf, struct multipath * mp)
{
	const char *origin;

	mp_set_mpe(skip_kpartx);
	mp_set_ovr(skip_kpartx);
	mp_set_hwe(skip_kpartx);
	mp_set_conf(skip_kpartx);
	mp_set_default(skip_kpartx, DEFAULT_SKIP_KPARTX);
out:
	condlog(3, "%s: skip_kpartx = %s %s", mp->alias,
		(mp->skip_kpartx == SKIP_KPARTX_ON)? "yes" : "no",
		origin);
	return 0;
}

int select_max_sectors_kb(struct config *conf, struct multipath * mp)
{
	const char *origin;

	mp_set_mpe(max_sectors_kb);
	mp_set_ovr(max_sectors_kb);
	mp_set_hwe(max_sectors_kb);
	mp_set_conf(max_sectors_kb);
	mp_set_default(max_sectors_kb, DEFAULT_MAX_SECTORS_KB);
	/*
	 * In the default case, we will not modify max_sectors_kb in sysfs
	 * (see sysfs_set_max_sectors_kb()).
	 * Don't print a log message here to avoid user confusion.
	 */
	return 0;
out:
	condlog(3, "%s: max_sectors_kb = %i %s", mp->alias, mp->max_sectors_kb,
		origin);
	return 0;
}

int select_ghost_delay (struct config *conf, struct multipath * mp)
{
	const char *origin;
	char buff[12];

	mp_set_mpe(ghost_delay);
	mp_set_ovr(ghost_delay);
	mp_set_hwe(ghost_delay);
	mp_set_conf(ghost_delay);
	mp_set_default(ghost_delay, DEFAULT_GHOST_DELAY);
out:
	if (print_off_int_undef(buff, 12, mp->ghost_delay) != 0)
		condlog(3, "%s: ghost_delay = %s %s", mp->alias, buff, origin);
	return 0;
}

int select_find_multipaths_timeout(struct config *conf, struct path *pp)
{
	const char *origin;

	pp_set_conf(find_multipaths_timeout);
	pp_set_default(find_multipaths_timeout,
		       DEFAULT_FIND_MULTIPATHS_TIMEOUT);
out:
	/*
	 * If configured value is negative, and this "unknown" hardware
	 * (no hwentry), use very small timeout to avoid delays.
	 */
	if (pp->find_multipaths_timeout < 0) {
		pp->find_multipaths_timeout = -pp->find_multipaths_timeout;
		if (!pp->hwe) {
			pp->find_multipaths_timeout =
				DEFAULT_UNKNOWN_FIND_MULTIPATHS_TIMEOUT;
			origin = "(default for unknown hardware)";
		}
	}
	condlog(3, "%s: timeout for find_multipaths \"smart\" = %ds %s",
		pp->dev, pp->find_multipaths_timeout, origin);
	return 0;
}

int select_all_tg_pt (struct config *conf, struct multipath * mp)
{
	const char *origin;

	mp_set_ovr(all_tg_pt);
	mp_set_hwe(all_tg_pt);
	mp_set_conf(all_tg_pt);
	mp_set_default(all_tg_pt, DEFAULT_ALL_TG_PT);
out:
	condlog(3, "%s: all_tg_pt = %s %s", mp->alias,
		(mp->all_tg_pt == ALL_TG_PT_ON)? "yes" : "no",
		origin);
	return 0;
}

int select_vpd_vendor_id (struct path *pp)
{
	const char *origin;

	pp_set_hwe(vpd_vendor_id);
	pp_set_default(vpd_vendor_id, 0);
out:
	if (pp->vpd_vendor_id < 0 || pp->vpd_vendor_id >= VPD_VP_ARRAY_SIZE) {
		condlog(3, "%s: vpd_vendor_id = %d (invalid, setting to 0)",
			pp->dev, pp->vpd_vendor_id);
		pp->vpd_vendor_id = 0;
	}
	condlog(3, "%s: vpd_vendor_id = %d \"%s\" %s", pp->dev,
		pp->vpd_vendor_id, vpd_vendor_pages[pp->vpd_vendor_id].name,
		origin);
	return 0;
}