/* * Copyright (c) 2004, 2005 Christophe Varoqui * Copyright (c) 2005 Benjamin Marzinski, Redhat * Copyright (c) 2005 Kiyoshi Ueda, NEC */ #include #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 #include 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; } path_get_tpgs(pp); if (pp->tpgs != TPGS_NONE && pp->tpgs != TPGS_UNDEF) { 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; }