/* * Copyright (c) 2004, 2005 Christophe Varoqui * Copyright (c) 2005 Benjamin Marzinski, Redhat * Copyright (c) 2005 Edward Goggin, EMC */ #include #include #include #include #include #include #include "checkers.h" #include "memory.h" #include "util.h" #include "debug.h" #include "parser.h" #include "dict.h" #include "hwtable.h" #include "vector.h" #include "structs.h" #include "config.h" #include "blacklist.h" #include "defaults.h" #include "prio.h" #include "devmapper.h" #include "mpath_cmd.h" #include "propsel.h" #include "version.h" static int hwe_strmatch (const struct hwentry *hwe1, const struct hwentry *hwe2) { if ((hwe2->vendor && !hwe1->vendor) || (hwe1->vendor && (!hwe2->vendor || strcmp(hwe1->vendor, hwe2->vendor)))) return 1; if ((hwe2->product && !hwe1->product) || (hwe1->product && (!hwe2->product || strcmp(hwe1->product, hwe2->product)))) return 1; if ((hwe2->revision && !hwe1->revision) || (hwe1->revision && (!hwe2->revision || strcmp(hwe1->revision, hwe2->revision)))) return 1; return 0; } static struct hwentry * find_hwe_strmatch (const struct _vector *hwtable, const struct hwentry *hwe) { int i; struct hwentry *tmp, *ret = NULL; vector_foreach_slot (hwtable, tmp, i) { if (hwe_strmatch(tmp, hwe)) continue; ret = tmp; break; } return ret; } static int hwe_regmatch (const struct hwentry *hwe1, const char *vendor, const char *product, const char *revision) { regex_t vre, pre, rre; int retval = 1; if (hwe1->vendor && regcomp(&vre, hwe1->vendor, REG_EXTENDED|REG_NOSUB)) goto out; if (hwe1->product && regcomp(&pre, hwe1->product, REG_EXTENDED|REG_NOSUB)) goto out_vre; if (hwe1->revision && regcomp(&rre, hwe1->revision, REG_EXTENDED|REG_NOSUB)) goto out_pre; if ((vendor || product || revision) && (!hwe1->vendor || !vendor || !regexec(&vre, vendor, 0, NULL, 0)) && (!hwe1->product || !product || !regexec(&pre, product, 0, NULL, 0)) && (!hwe1->revision || !revision || !regexec(&rre, revision, 0, NULL, 0))) retval = 0; if (hwe1->revision) regfree(&rre); out_pre: if (hwe1->product) regfree(&pre); out_vre: if (hwe1->vendor) regfree(&vre); out: return retval; } static void _log_match(const char *fn, const struct hwentry *h, const char *vendor, const char *product, const char *revision) { condlog(4, "%s: found match /%s:%s:%s/ for '%s:%s:%s'", fn, h->vendor, h->product, h->revision, vendor, product, revision); } #define log_match(h, v, p, r) _log_match(__func__, (h), (v), (p), (r)) int find_hwe (const struct _vector *hwtable, const char * vendor, const char * product, const char * revision, vector result) { int i, n = 0; struct hwentry *tmp; /* * Search backwards here, and add forward. * User modified entries are attached at the end of * the list, so we have to check them first before * continuing to the generic entries */ vector_reset(result); vector_foreach_slot_backwards (hwtable, tmp, i) { if (hwe_regmatch(tmp, vendor, product, revision)) continue; if (vector_alloc_slot(result) != NULL) { vector_set_slot(result, tmp); n++; } log_match(tmp, vendor, product, revision); } condlog(n > 1 ? 3 : 4, "%s: found %d hwtable matches for %s:%s:%s", __func__, n, vendor, product, revision); return n; } struct mpentry *find_mpe(vector mptable, char *wwid) { int i; struct mpentry * mpe; if (!wwid) return NULL; vector_foreach_slot (mptable, mpe, i) if (mpe->wwid && !strcmp(mpe->wwid, wwid)) return mpe; return NULL; } char *get_mpe_wwid(vector mptable, char *alias) { int i; struct mpentry * mpe; if (!alias) return NULL; vector_foreach_slot (mptable, mpe, i) if (mpe->alias && strcmp(mpe->alias, alias) == 0) return mpe->wwid; return NULL; } void free_hwe (struct hwentry * hwe) { if (!hwe) return; if (hwe->vendor) FREE(hwe->vendor); if (hwe->product) FREE(hwe->product); if (hwe->revision) FREE(hwe->revision); if (hwe->getuid) FREE(hwe->getuid); if (hwe->uid_attribute) FREE(hwe->uid_attribute); if (hwe->features) FREE(hwe->features); if (hwe->hwhandler) FREE(hwe->hwhandler); if (hwe->selector) FREE(hwe->selector); if (hwe->checker_name) FREE(hwe->checker_name); if (hwe->prio_name) FREE(hwe->prio_name); if (hwe->prio_args) FREE(hwe->prio_args); if (hwe->alias_prefix) FREE(hwe->alias_prefix); if (hwe->bl_product) FREE(hwe->bl_product); FREE(hwe); } void free_hwtable (vector hwtable) { int i; struct hwentry * hwe; if (!hwtable) return; vector_foreach_slot (hwtable, hwe, i) free_hwe(hwe); vector_free(hwtable); } void free_mpe (struct mpentry * mpe) { if (!mpe) return; if (mpe->wwid) FREE(mpe->wwid); if (mpe->selector) FREE(mpe->selector); if (mpe->getuid) FREE(mpe->getuid); if (mpe->uid_attribute) FREE(mpe->uid_attribute); if (mpe->alias) FREE(mpe->alias); if (mpe->prio_name) FREE(mpe->prio_name); if (mpe->prio_args) FREE(mpe->prio_args); FREE(mpe); } void free_mptable (vector mptable) { int i; struct mpentry * mpe; if (!mptable) return; vector_foreach_slot (mptable, mpe, i) free_mpe(mpe); vector_free(mptable); } struct mpentry * alloc_mpe (void) { struct mpentry * mpe = (struct mpentry *) MALLOC(sizeof(struct mpentry)); return mpe; } struct hwentry * alloc_hwe (void) { struct hwentry * hwe = (struct hwentry *) MALLOC(sizeof(struct hwentry)); return hwe; } static char * set_param_str(const char * str) { char * dst; int len; if (!str) return NULL; len = strlen(str); if (!len) return NULL; dst = (char *)MALLOC(len + 1); if (!dst) return NULL; strcpy(dst, str); return dst; } #define merge_str(s) \ if (!dst->s && src->s) { \ if (!(dst->s = set_param_str(src->s))) \ return 1; \ } #define merge_num(s) \ if (!dst->s && src->s) \ dst->s = src->s static int merge_hwe (struct hwentry * dst, struct hwentry * src) { char id[SCSI_VENDOR_SIZE+PATH_PRODUCT_SIZE]; merge_str(vendor); merge_str(product); merge_str(revision); merge_str(getuid); merge_str(uid_attribute); merge_str(features); merge_str(hwhandler); merge_str(selector); merge_str(checker_name); merge_str(prio_name); merge_str(prio_args); merge_str(alias_prefix); merge_str(bl_product); merge_num(pgpolicy); merge_num(pgfailback); merge_num(rr_weight); merge_num(no_path_retry); merge_num(minio); merge_num(minio_rq); merge_num(flush_on_last_del); merge_num(fast_io_fail); merge_num(dev_loss); merge_num(user_friendly_names); merge_num(retain_hwhandler); merge_num(detect_prio); merge_num(detect_checker); merge_num(deferred_remove); merge_num(delay_watch_checks); merge_num(delay_wait_checks); merge_num(skip_kpartx); merge_num(max_sectors_kb); merge_num(ghost_delay); merge_num(all_tg_pt); merge_num(vpd_vendor_id); merge_num(san_path_err_threshold); merge_num(san_path_err_forget_rate); merge_num(san_path_err_recovery_time); merge_num(marginal_path_err_sample_time); merge_num(marginal_path_err_rate_threshold); merge_num(marginal_path_err_recheck_gap_time); merge_num(marginal_path_double_failed_time); snprintf(id, sizeof(id), "%s/%s", dst->vendor, dst->product); reconcile_features_with_options(id, &dst->features, &dst->no_path_retry, &dst->retain_hwhandler); return 0; } static int merge_mpe(struct mpentry *dst, struct mpentry *src) { if (!dst || !src) return 1; merge_str(alias); merge_str(uid_attribute); merge_str(getuid); merge_str(selector); merge_str(features); merge_str(prio_name); merge_str(prio_args); if (dst->prkey_source == PRKEY_SOURCE_NONE && src->prkey_source != PRKEY_SOURCE_NONE) { dst->prkey_source = src->prkey_source; dst->sa_flags = src->sa_flags; memcpy(&dst->reservation_key, &src->reservation_key, sizeof(dst->reservation_key)); } merge_num(pgpolicy); merge_num(pgfailback); merge_num(rr_weight); merge_num(no_path_retry); merge_num(minio); merge_num(minio_rq); merge_num(flush_on_last_del); merge_num(attribute_flags); merge_num(user_friendly_names); merge_num(deferred_remove); merge_num(delay_watch_checks); merge_num(delay_wait_checks); merge_num(san_path_err_threshold); merge_num(san_path_err_forget_rate); merge_num(san_path_err_recovery_time); merge_num(marginal_path_err_sample_time); merge_num(marginal_path_err_rate_threshold); merge_num(marginal_path_err_recheck_gap_time); merge_num(marginal_path_double_failed_time); merge_num(skip_kpartx); merge_num(max_sectors_kb); merge_num(ghost_delay); merge_num(uid); merge_num(gid); merge_num(mode); return 0; } void merge_mptable(vector mptable) { struct mpentry *mp1, *mp2; int i, j; vector_foreach_slot(mptable, mp1, i) { j = i + 1; vector_foreach_slot_after(mptable, mp2, j) { if (strcmp(mp1->wwid, mp2->wwid)) continue; condlog(1, "%s: duplicate multipath config section for %s", __func__, mp1->wwid); merge_mpe(mp2, mp1); free_mpe(mp1); vector_del_slot(mptable, i); i--; break; } } } int store_hwe (vector hwtable, struct hwentry * dhwe) { struct hwentry * hwe; if (find_hwe_strmatch(hwtable, dhwe)) return 0; if (!(hwe = alloc_hwe())) return 1; if (!dhwe->vendor || !(hwe->vendor = set_param_str(dhwe->vendor))) goto out; if (!dhwe->product || !(hwe->product = set_param_str(dhwe->product))) goto out; if (dhwe->revision && !(hwe->revision = set_param_str(dhwe->revision))) goto out; if (dhwe->uid_attribute && !(hwe->uid_attribute = set_param_str(dhwe->uid_attribute))) goto out; if (dhwe->getuid && !(hwe->getuid = set_param_str(dhwe->getuid))) goto out; if (dhwe->features && !(hwe->features = set_param_str(dhwe->features))) goto out; if (dhwe->hwhandler && !(hwe->hwhandler = set_param_str(dhwe->hwhandler))) goto out; if (dhwe->selector && !(hwe->selector = set_param_str(dhwe->selector))) goto out; if (dhwe->checker_name && !(hwe->checker_name = set_param_str(dhwe->checker_name))) goto out; if (dhwe->prio_name && !(hwe->prio_name = set_param_str(dhwe->prio_name))) goto out; if (dhwe->prio_args && !(hwe->prio_args = set_param_str(dhwe->prio_args))) goto out; if (dhwe->alias_prefix && !(hwe->alias_prefix = set_param_str(dhwe->alias_prefix))) goto out; hwe->pgpolicy = dhwe->pgpolicy; hwe->pgfailback = dhwe->pgfailback; hwe->rr_weight = dhwe->rr_weight; hwe->no_path_retry = dhwe->no_path_retry; hwe->minio = dhwe->minio; hwe->minio_rq = dhwe->minio_rq; hwe->flush_on_last_del = dhwe->flush_on_last_del; hwe->fast_io_fail = dhwe->fast_io_fail; hwe->dev_loss = dhwe->dev_loss; hwe->user_friendly_names = dhwe->user_friendly_names; hwe->retain_hwhandler = dhwe->retain_hwhandler; hwe->detect_prio = dhwe->detect_prio; hwe->detect_checker = dhwe->detect_checker; hwe->ghost_delay = dhwe->ghost_delay; hwe->vpd_vendor_id = dhwe->vpd_vendor_id; if (dhwe->bl_product && !(hwe->bl_product = set_param_str(dhwe->bl_product))) goto out; if (!vector_alloc_slot(hwtable)) goto out; vector_set_slot(hwtable, hwe); return 0; out: free_hwe(hwe); return 1; } static void factorize_hwtable (vector hw, int n, const char *table_desc) { struct hwentry *hwe1, *hwe2; int i, j; restart: vector_foreach_slot(hw, hwe1, i) { /* drop invalid device configs */ if (i >= n && (!hwe1->vendor || !hwe1->product)) { condlog(0, "device config in %s missing vendor or product parameter", table_desc); vector_del_slot(hw, i--); free_hwe(hwe1); continue; } j = n > i + 1 ? n : i + 1; vector_foreach_slot_after(hw, hwe2, j) { if (hwe_strmatch(hwe2, hwe1) == 0) { condlog(i >= n ? 1 : 3, "%s: duplicate device section for %s:%s:%s in %s", __func__, hwe1->vendor, hwe1->product, hwe1->revision, table_desc); vector_del_slot(hw, i); merge_hwe(hwe2, hwe1); free_hwe(hwe1); if (i < n) n -= 1; /* * Play safe here; we have modified * the original vector so the outer * vector_foreach_slot() might * become confused. */ goto restart; } } } return; } struct config * alloc_config (void) { return (struct config *)MALLOC(sizeof(struct config)); } void free_config (struct config * conf) { if (!conf) return; if (conf->multipath_dir) FREE(conf->multipath_dir); if (conf->selector) FREE(conf->selector); if (conf->uid_attribute) FREE(conf->uid_attribute); vector_reset(&conf->uid_attrs); if (conf->getuid) FREE(conf->getuid); if (conf->features) FREE(conf->features); if (conf->hwhandler) FREE(conf->hwhandler); if (conf->bindings_file) FREE(conf->bindings_file); if (conf->wwids_file) FREE(conf->wwids_file); if (conf->prkeys_file) FREE(conf->prkeys_file); if (conf->prio_name) FREE(conf->prio_name); if (conf->alias_prefix) FREE(conf->alias_prefix); if (conf->partition_delim) FREE(conf->partition_delim); if (conf->prio_args) FREE(conf->prio_args); if (conf->checker_name) FREE(conf->checker_name); if (conf->config_dir) FREE(conf->config_dir); free_blacklist(conf->blist_devnode); free_blacklist(conf->blist_wwid); free_blacklist(conf->blist_property); free_blacklist(conf->blist_protocol); free_blacklist_device(conf->blist_device); free_blacklist(conf->elist_devnode); free_blacklist(conf->elist_wwid); free_blacklist(conf->elist_property); free_blacklist(conf->elist_protocol); free_blacklist_device(conf->elist_device); free_mptable(conf->mptable); free_hwtable(conf->hwtable); free_hwe(conf->overrides); free_keywords(conf->keywords); FREE(conf); } /* if multipath fails to process the config directory, it should continue, * with just a warning message */ static void process_config_dir(struct config *conf, char *dir) { struct dirent **namelist; struct scandir_result sr; int i, n; char path[LINE_MAX]; int old_hwtable_size; if (dir[0] != '/') { condlog(1, "config_dir '%s' must be a fully qualified path", dir); return; } n = scandir(dir, &namelist, NULL, alphasort); if (n < 0) { if (errno == ENOENT) condlog(3, "No configuration dir '%s'", dir); else condlog(0, "couldn't open configuration dir '%s': %s", dir, strerror(errno)); return; } else if (n == 0) return; sr.di = namelist; sr.n = n; pthread_cleanup_push_cast(free_scandir_result, &sr); for (i = 0; i < n; i++) { char *ext = strrchr(namelist[i]->d_name, '.'); if (!ext || strcmp(ext, ".conf")) continue; old_hwtable_size = VECTOR_SIZE(conf->hwtable); snprintf(path, LINE_MAX, "%s/%s", dir, namelist[i]->d_name); path[LINE_MAX-1] = '\0'; process_file(conf, path); factorize_hwtable(conf->hwtable, old_hwtable_size, namelist[i]->d_name); } pthread_cleanup_pop(1); } static void set_max_checkint_from_watchdog(struct config *conf) { #ifdef USE_SYSTEMD char *envp = getenv("WATCHDOG_USEC"); unsigned long checkint; if (envp && sscanf(envp, "%lu", &checkint) == 1) { /* Value is in microseconds */ checkint /= 1000000; if (checkint < 1 || checkint > UINT_MAX) { condlog(1, "invalid value for WatchdogSec: \"%s\"", envp); return; } if (conf->max_checkint == 0 || conf->max_checkint > checkint) conf->max_checkint = checkint; condlog(3, "enabling watchdog, interval %ld", checkint); conf->use_watchdog = true; } #endif } struct config * load_config (char * file) { struct config *conf = alloc_config(); if (!conf) return NULL; /* * internal defaults */ conf->verbosity = DEFAULT_VERBOSITY; get_sys_max_fds(&conf->max_fds); conf->bindings_file = set_default(DEFAULT_BINDINGS_FILE); conf->wwids_file = set_default(DEFAULT_WWIDS_FILE); conf->prkeys_file = set_default(DEFAULT_PRKEYS_FILE); conf->multipath_dir = set_default(DEFAULT_MULTIPATHDIR); conf->attribute_flags = 0; conf->reassign_maps = DEFAULT_REASSIGN_MAPS; conf->checkint = CHECKINT_UNDEF; conf->use_watchdog = false; conf->max_checkint = 0; conf->force_sync = DEFAULT_FORCE_SYNC; conf->partition_delim = (default_partition_delim != NULL ? strdup(default_partition_delim) : NULL); conf->processed_main_config = 0; conf->find_multipaths = DEFAULT_FIND_MULTIPATHS; conf->uxsock_timeout = DEFAULT_REPLY_TIMEOUT; conf->retrigger_tries = DEFAULT_RETRIGGER_TRIES; conf->retrigger_delay = DEFAULT_RETRIGGER_DELAY; conf->uev_wait_timeout = DEFAULT_UEV_WAIT_TIMEOUT; conf->remove_retries = 0; conf->ghost_delay = DEFAULT_GHOST_DELAY; conf->all_tg_pt = DEFAULT_ALL_TG_PT; /* * preload default hwtable */ conf->hwtable = vector_alloc(); if (!conf->hwtable) goto out; if (setup_default_hwtable(conf->hwtable)) goto out; #ifdef CHECK_BUILTIN_HWTABLE factorize_hwtable(conf->hwtable, 0, "builtin"); #endif /* * read the config file */ conf->keywords = vector_alloc(); init_keywords(conf->keywords); if (filepresent(file)) { int builtin_hwtable_size; builtin_hwtable_size = VECTOR_SIZE(conf->hwtable); if (process_file(conf, file)) { condlog(0, "error parsing config file"); goto out; } factorize_hwtable(conf->hwtable, builtin_hwtable_size, file); } else { condlog(0, "/etc/multipath.conf does not exist, blacklisting all devices."); condlog(0, "You can run \"/sbin/mpathconf --enable\" to create"); condlog(0, "/etc/multipath.conf. See man mpathconf(8) for more details"); if (conf->blist_devnode == NULL) { conf->blist_devnode = vector_alloc(); if (!conf->blist_devnode) { condlog(0, "cannot allocate blacklist\n"); goto out; } } if (store_ble(conf->blist_devnode, strdup(".*"), ORIGIN_NO_CONFIG)) { condlog(0, "cannot store default no-config blacklist\n"); goto out; } } conf->processed_main_config = 1; if (conf->config_dir == NULL) conf->config_dir = set_default(DEFAULT_CONFIG_DIR); if (conf->config_dir && conf->config_dir[0] != '\0') process_config_dir(conf, conf->config_dir); /* * fill the voids left in the config file */ set_max_checkint_from_watchdog(conf); if (conf->max_checkint == 0) { if (conf->checkint == CHECKINT_UNDEF) conf->checkint = DEFAULT_CHECKINT; conf->max_checkint = (conf->checkint < UINT_MAX / 4 ? conf->checkint * 4 : UINT_MAX); } else if (conf->checkint == CHECKINT_UNDEF) conf->checkint = (conf->max_checkint >= 4 ? conf->max_checkint / 4 : 1); else if (conf->checkint > conf->max_checkint) conf->checkint = conf->max_checkint; condlog(3, "polling interval: %d, max: %d", conf->checkint, conf->max_checkint); if (conf->blist_devnode == NULL) { conf->blist_devnode = vector_alloc(); if (!conf->blist_devnode) goto out; } if (conf->blist_wwid == NULL) { conf->blist_wwid = vector_alloc(); if (!conf->blist_wwid) goto out; } if (conf->blist_device == NULL) { conf->blist_device = vector_alloc(); if (!conf->blist_device) goto out; } if (conf->blist_property == NULL) { conf->blist_property = vector_alloc(); if (!conf->blist_property) goto out; } if (conf->blist_protocol == NULL) { conf->blist_protocol = vector_alloc(); if (!conf->blist_protocol) goto out; } if (conf->elist_devnode == NULL) { conf->elist_devnode = vector_alloc(); if (!conf->elist_devnode) goto out; } if (conf->elist_wwid == NULL) { conf->elist_wwid = vector_alloc(); if (!conf->elist_wwid) goto out; } if (conf->elist_device == NULL) { conf->elist_device = vector_alloc(); if (!conf->elist_device) goto out; } if (conf->elist_property == NULL) { conf->elist_property = vector_alloc(); if (!conf->elist_property) goto out; } if (conf->elist_protocol == NULL) { conf->elist_protocol = vector_alloc(); if (!conf->elist_protocol) goto out; } if (setup_default_blist(conf)) goto out; if (conf->mptable == NULL) { conf->mptable = vector_alloc(); if (!conf->mptable) goto out; } merge_mptable(conf->mptable); merge_blacklist(conf->blist_devnode); merge_blacklist(conf->blist_property); merge_blacklist(conf->blist_wwid); merge_blacklist_device(conf->blist_device); merge_blacklist(conf->elist_devnode); merge_blacklist(conf->elist_property); merge_blacklist(conf->elist_wwid); merge_blacklist_device(conf->elist_device); if (conf->bindings_file == NULL) conf->bindings_file = set_default(DEFAULT_BINDINGS_FILE); if (!conf->multipath_dir || !conf->bindings_file || !conf->wwids_file || !conf->prkeys_file) goto out; return conf; out: free_config(conf); return NULL; } char *get_uid_attribute_by_attrs(struct config *conf, const char *path_dev) { vector uid_attrs = &conf->uid_attrs; int j; char *att, *col; vector_foreach_slot(uid_attrs, att, j) { col = strrchr(att, ':'); if (!col) continue; if (!strncmp(path_dev, att, col - att)) return col + 1; } return NULL; } int parse_uid_attrs(char *uid_attrs, struct config *conf) { vector attrs = &conf->uid_attrs; char *uid_attr_record, *tmp; int ret = 0, count; if (!uid_attrs) return 1; count = get_word(uid_attrs, &uid_attr_record); while (uid_attr_record) { tmp = strchr(uid_attr_record, ':'); if (!tmp) { condlog(2, "invalid record in uid_attrs: %s", uid_attr_record); free(uid_attr_record); ret = 1; } else if (!vector_alloc_slot(attrs)) { free(uid_attr_record); ret = 1; } else vector_set_slot(attrs, uid_attr_record); if (!count) break; uid_attrs += count; count = get_word(uid_attrs, &uid_attr_record); } return ret; }