libmulitpath: cleanup uid_fallback code
[multipath-tools/.git] / libmultipath / discovery.c
index 1fb4db4..3ec60d6 100644 (file)
 #include "discovery.h"
 #include "prio.h"
 #include "defaults.h"
+#include "unaligned.h"
+#include "prioritizers/alua_rtpg.h"
+#include "foreign.h"
 
 int
 alloc_path_with_pathinfo (struct config *conf, struct udev_device *udevice,
-                         int flag, struct path **pp_ptr)
+                         const char *wwid, int flag, struct path **pp_ptr)
 {
        int err = PATHINFO_FAILED;
        struct path * pp;
@@ -51,6 +54,9 @@ alloc_path_with_pathinfo (struct config *conf, struct udev_device *udevice,
        if (!pp)
                return PATHINFO_FAILED;
 
+       if (wwid)
+               strlcpy(pp->wwid, wwid, sizeof(pp->wwid));
+
        if (safe_sprintf(pp->dev, "%s", devname)) {
                condlog(0, "pp->dev too small");
        } else {
@@ -58,7 +64,7 @@ alloc_path_with_pathinfo (struct config *conf, struct udev_device *udevice,
                err = pathinfo(pp, conf, flag | DI_BLACKLIST);
        }
 
-       if (err)
+       if (err || !pp_ptr)
                free_path(pp);
        else if (pp_ptr)
                *pp_ptr = pp;
@@ -97,6 +103,7 @@ store_pathinfo (vector pathvec, struct config *conf,
        err = store_path(pathvec, pp);
        if (err)
                goto out;
+       pp->checkint = conf->checkint;
 
 out:
        if (err)
@@ -117,17 +124,17 @@ path_discover (vector pathvec, struct config * conf,
        if (!devname)
                return PATHINFO_FAILED;
 
-       if (filter_property(conf, udevice) > 0)
-               return PATHINFO_SKIPPED;
-
-       if (filter_devnode(conf->blist_devnode, conf->elist_devnode,
-                          (char *)devname) > 0)
-               return PATHINFO_SKIPPED;
-
-       pp = find_path_by_dev(pathvec, (char *)devname);
+       pp = find_path_by_dev(pathvec, devname);
        if (!pp) {
-               return store_pathinfo(pathvec, conf,
-                                     udevice, flag, NULL);
+               char devt[BLK_DEV_SIZE];
+               dev_t devnum = udev_device_get_devnum(udevice);
+
+               snprintf(devt, BLK_DEV_SIZE, "%d:%d",
+                        major(devnum), minor(devnum));
+               pp = find_path_by_devt(pathvec, devt);
+               if (!pp)
+                       return store_pathinfo(pathvec, conf,
+                                             udevice, flag, NULL);
        }
        return pathinfo(pp, conf, flag);
 }
@@ -164,10 +171,11 @@ path_discovery (vector pathvec, int flag)
                if(devtype && !strncmp(devtype, "disk", 4)) {
                        total_paths++;
                        conf = get_multipath_config();
+                       pthread_cleanup_push(put_multipath_config, conf);
                        if (path_discover(pathvec, conf,
                                          udevice, flag) == PATHINFO_OK)
                                num_paths++;
-                       put_multipath_config(conf);
+                       pthread_cleanup_pop(1);
                }
                udev_device_unref(udevice);
        }
@@ -177,7 +185,7 @@ path_discovery (vector pathvec, int flag)
 }
 
 #define declare_sysfs_get_str(fname)                                   \
-extern ssize_t                                                         \
+ssize_t                                                                        \
 sysfs_get_##fname (struct udev_device * udev, char * buff, size_t len) \
 {                                                                      \
        int l;                                                  \
@@ -209,8 +217,6 @@ declare_sysfs_get_str(devtype);
 declare_sysfs_get_str(vendor);
 declare_sysfs_get_str(model);
 declare_sysfs_get_str(rev);
-declare_sysfs_get_str(access_state);
-declare_sysfs_get_str(preferred_path);
 
 ssize_t
 sysfs_get_vpd (struct udev_device * udev, int pg,
@@ -274,7 +280,7 @@ sysfs_get_timeout(struct path *pp, unsigned int *timeout)
        }
        *timeout = t;
 
-       return 0;
+       return 1;
 }
 
 int
@@ -399,7 +405,7 @@ sysfs_get_tgt_nodename (struct path *pp, char * node)
        return 0;
 }
 
-int sysfs_get_host_adapter_name(struct path *pp, char *adapter_name)
+int sysfs_get_host_adapter_name(const struct path *pp, char *adapter_name)
 {
        int proto_id;
 
@@ -425,7 +431,7 @@ int sysfs_get_host_adapter_name(struct path *pp, char *adapter_name)
        return sysfs_get_host_pci_name(pp, adapter_name);
 }
 
-int sysfs_get_host_pci_name(struct path *pp, char *pci_name)
+int sysfs_get_host_pci_name(const struct path *pp, char *pci_name)
 {
        struct udev_device *hostdev, *parent;
        char host_name[HOST_NAME_LEN];
@@ -464,7 +470,7 @@ int sysfs_get_host_pci_name(struct path *pp, char *pci_name)
        return 1;
 }
 
-int sysfs_get_iscsi_ip_address(struct path *pp, char *ip_address)
+int sysfs_get_iscsi_ip_address(const struct path *pp, char *ip_address)
 {
        struct udev_device *hostdev;
        char host_name[HOST_NAME_LEN];
@@ -491,7 +497,7 @@ sysfs_get_asymmetric_access_state(struct path *pp, char *buff, int buflen)
 {
        struct udev_device *parent = pp->udev;
        char value[16], *eptr;
-       unsigned int preferred;
+       unsigned long preferred;
 
        while (parent) {
                const char *subsys = udev_device_get_subsystem(parent);
@@ -503,10 +509,10 @@ sysfs_get_asymmetric_access_state(struct path *pp, char *buff, int buflen)
        if (!parent)
                return -1;
 
-       if (sysfs_get_access_state(parent, buff, buflen) <= 0)
+       if (sysfs_attr_get_value(parent, "access_state", buff, buflen) <= 0)
                return -1;
 
-       if (sysfs_get_preferred_path(parent, value, 16) <= 0)
+       if (sysfs_attr_get_value(parent, "preferred_path", value, 16) <= 0)
                return 0;
 
        preferred = strtoul(value, &eptr, 0);
@@ -514,7 +520,7 @@ sysfs_get_asymmetric_access_state(struct path *pp, char *buff, int buflen)
                /* Parse error, ignore */
                return 0;
        }
-       return  preferred;
+       return !!preferred;
 }
 
 static void
@@ -585,7 +591,8 @@ sysfs_set_rport_tmo(struct multipath *mpp, struct path *pp)
                                goto out;
                        }
                }
-       } else if (mpp->dev_loss > DEFAULT_DEV_LOSS_TMO) {
+       } else if (mpp->dev_loss > DEFAULT_DEV_LOSS_TMO &&
+               mpp->no_path_retry != NO_PATH_RETRY_QUEUE) {
                condlog(3, "%s: limiting dev_loss_tmo to %d, since "
                        "fast_io_fail is not set",
                        rport_id, DEFAULT_DEV_LOSS_TMO);
@@ -655,7 +662,7 @@ sysfs_set_session_tmo(struct multipath *mpp, struct path *pp)
                } else {
                        snprintf(value, 11, "%u", mpp->fast_io_fail);
                        if (sysfs_attr_set_value(session_dev, "recovery_tmo",
-                                                value, 11) <= 0) {
+                                                value, strlen(value)) <= 0) {
                                condlog(3, "%s: Failed to set recovery_tmo, "
                                        " error %d", pp->dev, errno);
                        }
@@ -687,7 +694,7 @@ sysfs_set_nexus_loss_tmo(struct multipath *mpp, struct path *pp)
        if (mpp->dev_loss) {
                snprintf(value, 11, "%u", mpp->dev_loss);
                if (sysfs_attr_set_value(sas_dev, "I_T_nexus_loss_timeout",
-                                        value, 11) <= 0)
+                                        value, strlen(value)) <= 0)
                        condlog(3, "%s: failed to update "
                                "I_T Nexus loss timeout, error %d",
                                pp->dev, errno);
@@ -704,7 +711,7 @@ sysfs_set_scsi_tmo (struct multipath *mpp, int checkint)
        int dev_loss_tmo = mpp->dev_loss;
 
        if (mpp->no_path_retry > 0) {
-               uint64_t no_path_retry_tmo = mpp->no_path_retry * checkint;
+               uint64_t no_path_retry_tmo = (uint64_t)mpp->no_path_retry * checkint;
 
                if (no_path_retry_tmo > MAX_DEV_LOSS_TMO)
                        no_path_retry_tmo = MAX_DEV_LOSS_TMO;
@@ -763,7 +770,7 @@ do_inq(int sg_fd, int cmddt, int evpd, unsigned int pg_op,
        io_hdr.dxferp = resp;
        io_hdr.cmdp = inqCmdBlk;
        io_hdr.sbp = sense_b;
-       io_hdr.timeout = DEF_TIMEOUT;
+       io_hdr.timeout = DEF_TIMEOUT * 1000;
 
        if (ioctl(sg_fd, SG_IO, &io_hdr) < 0)
                return -1;
@@ -812,10 +819,31 @@ get_serial (char * str, int maxlen, int fd)
        return 1;
 }
 
+static void
+detect_alua(struct path * pp, struct config *conf)
+{
+       int ret;
+       int tpgs;
+       unsigned int timeout = conf->checker_timeout;
+
+       if ((tpgs = get_target_port_group_support(pp->fd, timeout)) <= 0) {
+               pp->tpgs = TPGS_NONE;
+               return;
+       }
+       ret = get_target_port_group(pp, timeout);
+       if (ret < 0 || get_asymmetric_access_state(pp->fd, ret, timeout) < 0) {
+               pp->tpgs = TPGS_NONE;
+               return;
+       }
+       pp->tpgs = tpgs;
+}
+
 #define DEFAULT_SGIO_LEN 254
 
+/* Query VPD page @pg. Returns number of INQUIRY bytes
+   upon success and -1 upon failure. */
 static int
-sgio_get_vpd (unsigned char * buff, int maxlen, int fd)
+sgio_get_vpd (unsigned char * buff, int maxlen, int fd, int pg)
 {
        int len = DEFAULT_SGIO_LEN;
 
@@ -824,13 +852,13 @@ sgio_get_vpd (unsigned char * buff, int maxlen, int fd)
                return -1;
        }
 retry:
-       if (0 == do_inq(fd, 0, 1, 0x83, buff, len)) {
-               len = buff[3] + (buff[2] << 8);
+       if (0 == do_inq(fd, 0, 1, pg, buff, len)) {
+               len = get_unaligned_be16(&buff[2]) + 4;
                if (len >= maxlen)
                        return len;
                if (len > DEFAULT_SGIO_LEN)
                        goto retry;
-               return 0;
+               return len;
        }
        return -1;
 }
@@ -856,7 +884,7 @@ static int
 parse_vpd_pg80(const unsigned char *in, char *out, size_t out_len)
 {
        char *p = NULL;
-       int len = in[3] + (in[2] << 8);
+       int len = get_unaligned_be16(&in[2]);
 
        if (len >= out_len) {
                condlog(2, "vpd pg80 overflow, %d/%d bytes required",
@@ -883,12 +911,12 @@ static int
 parse_vpd_pg83(const unsigned char *in, size_t in_len,
               char *out, size_t out_len)
 {
-       unsigned char *d;
-       unsigned char *vpd = NULL;
+       const unsigned char *d;
+       const unsigned char *vpd = NULL;
        int len = -ENODATA, vpd_type, vpd_len, prio = -1, i, naa_prio;
 
-       d = (unsigned char *)in + 4;
-       while (d < (unsigned char *)in + in_len) {
+       d = in + 4;
+       while (d < in + in_len) {
                /* Select 'association: LUN' */
                if ((d[1] & 0x30) != 0) {
                        d += d[3] + 4;
@@ -1005,7 +1033,7 @@ parse_vpd_pg83(const unsigned char *in, size_t in_len,
                                out[len] = '\0';
                        }
                } else if (vpd_type == 0x1) {
-                       unsigned char *p;
+                       const unsigned char *p;
                        int p_len;
 
                        out[0] = '1';
@@ -1056,7 +1084,7 @@ get_vpd_sysfs (struct udev_device *parent, int pg, char * str, int maxlen)
                        pg, buff[1]);
                return -ENODATA;
        }
-       buff_len = (buff[2] << 8) + buff[3] + 4;
+       buff_len = get_unaligned_be16(&buff[2]) + 4;
        if (buff_len > 4096)
                condlog(3, "vpd pg%02x page truncated", pg);
 
@@ -1070,15 +1098,17 @@ get_vpd_sysfs (struct udev_device *parent, int pg, char * str, int maxlen)
        return len;
 }
 
-static int
+int
 get_vpd_sgio (int fd, int pg, char * str, int maxlen)
 {
        int len, buff_len;
        unsigned char buff[4096];
 
        memset(buff, 0x0, 4096);
-       if (sgio_get_vpd(buff, 4096, fd) <= 0) {
-               condlog(3, "failed to issue vpd inquiry for pg%02x",
+       if (sgio_get_vpd(buff, 4096, fd, pg) < 0) {
+               int lvl = pg == 0x80 || pg == 0x83 ? 3 : 4;
+
+               condlog(lvl, "failed to issue vpd inquiry for pg%02x",
                        pg);
                return -errno;
        }
@@ -1088,15 +1118,23 @@ get_vpd_sgio (int fd, int pg, char * str, int maxlen)
                        pg, buff[1]);
                return -ENODATA;
        }
-       buff_len = (buff[2] << 8) + buff[3] + 4;
-       if (buff_len > 4096)
+       buff_len = get_unaligned_be16(&buff[2]) + 4;
+       if (buff_len > 4096) {
                condlog(3, "vpd pg%02x page truncated", pg);
-
+               buff_len = 4096;
+       }
        if (pg == 0x80)
                len = parse_vpd_pg80(buff, str, maxlen);
        else if (pg == 0x83)
                len = parse_vpd_pg83(buff, buff_len, str, maxlen);
-       else
+       else if (pg == 0xc9 && maxlen >= 8) {
+               if (buff_len < 8)
+                       len = -ENODATA;
+               else {
+                       len = (buff_len <= maxlen)? buff_len : maxlen;
+                       memcpy (str, buff, len);
+               }
+       } else
                len = -ENOSYS;
 
        return len;
@@ -1125,27 +1163,27 @@ scsi_sysfs_pathinfo (struct path * pp, vector hwtable)
                parent = udev_device_get_parent(parent);
        }
        if (!attr_path || pp->sg_id.host_no == -1)
-               return 1;
+               return PATHINFO_FAILED;
 
        if (sysfs_get_vendor(parent, pp->vendor_id, SCSI_VENDOR_SIZE) <= 0)
-               return 1;
+               return PATHINFO_FAILED;;
 
        condlog(3, "%s: vendor = %s", pp->dev, pp->vendor_id);
 
-       if (sysfs_get_model(parent, pp->product_id, SCSI_PRODUCT_SIZE) <= 0)
-               return 1;
+       if (sysfs_get_model(parent, pp->product_id, PATH_PRODUCT_SIZE) <= 0)
+               return PATHINFO_FAILED;;
 
        condlog(3, "%s: product = %s", pp->dev, pp->product_id);
 
-       if (sysfs_get_rev(parent, pp->rev, SCSI_REV_SIZE) < 0)
-               return 1;
+       if (sysfs_get_rev(parent, pp->rev, PATH_REV_SIZE) < 0)
+               return PATHINFO_FAILED;;
 
        condlog(3, "%s: rev = %s", pp->dev, pp->rev);
 
        /*
         * set the hwe configlet pointer
         */
-       pp->hwe = find_hwe(hwtable, pp->vendor_id, pp->product_id, pp->rev);
+       find_hwe(hwtable, pp->vendor_id, pp->product_id, pp->rev, pp->hwe);
 
        /*
         * host / bus / target / lun
@@ -1161,12 +1199,57 @@ scsi_sysfs_pathinfo (struct path * pp, vector hwtable)
         * target node name
         */
        if(sysfs_get_tgt_nodename(pp, pp->tgt_node_name))
-               return 1;
+               return PATHINFO_FAILED;
 
        condlog(3, "%s: tgt_node_name = %s",
                pp->dev, pp->tgt_node_name);
 
-       return 0;
+       return PATHINFO_OK;
+}
+
+static int
+nvme_sysfs_pathinfo (struct path * pp, vector hwtable)
+{
+       struct udev_device *parent;
+       const char *attr_path = NULL;
+       const char *attr;
+
+       attr_path = udev_device_get_sysname(pp->udev);
+       if (!attr_path)
+               return PATHINFO_FAILED;
+
+       if (sscanf(attr_path, "nvme%dn%d",
+                  &pp->sg_id.host_no,
+                  &pp->sg_id.scsi_id) != 2)
+               return PATHINFO_FAILED;
+
+       parent = udev_device_get_parent_with_subsystem_devtype(pp->udev,
+                                                              "nvme", NULL);
+       if (!parent)
+               return PATHINFO_SKIPPED;
+
+       attr = udev_device_get_sysattr_value(pp->udev, "nsid");
+       pp->sg_id.lun = attr ? atoi(attr) : 0;
+
+       attr = udev_device_get_sysattr_value(parent, "cntlid");
+       pp->sg_id.channel = attr ? atoi(attr) : 0;
+
+       snprintf(pp->vendor_id, SCSI_VENDOR_SIZE, "NVME");
+       snprintf(pp->product_id, PATH_PRODUCT_SIZE, "%s",
+                udev_device_get_sysattr_value(parent, "model"));
+       snprintf(pp->serial, SERIAL_SIZE, "%s",
+                udev_device_get_sysattr_value(parent, "serial"));
+       snprintf(pp->rev, PATH_REV_SIZE, "%s",
+                udev_device_get_sysattr_value(parent, "firmware_rev"));
+
+       condlog(3, "%s: vendor = %s", pp->dev, pp->vendor_id);
+       condlog(3, "%s: product = %s", pp->dev, pp->product_id);
+       condlog(3, "%s: serial = %s", pp->dev, pp->serial);
+       condlog(3, "%s: rev = %s", pp->dev, pp->rev);
+
+       find_hwe(hwtable, pp->vendor_id, pp->product_id, NULL, pp->hwe);
+
+       return PATHINFO_OK;
 }
 
 static int
@@ -1184,14 +1267,14 @@ ccw_sysfs_pathinfo (struct path * pp, vector hwtable)
                parent = udev_device_get_parent(parent);
        }
        if (!parent)
-               return 1;
+               return PATHINFO_FAILED;
 
        sprintf(pp->vendor_id, "IBM");
 
        condlog(3, "%s: vendor = %s", pp->dev, pp->vendor_id);
 
        if (sysfs_get_devtype(parent, attr_buff, FILE_NAME_SIZE) <= 0)
-               return 1;
+               return PATHINFO_FAILED;
 
        if (!strncmp(attr_buff, "3370", 4)) {
                sprintf(pp->product_id,"S/390 DASD FBA");
@@ -1206,7 +1289,7 @@ ccw_sysfs_pathinfo (struct path * pp, vector hwtable)
        /*
         * set the hwe configlet pointer
         */
-       pp->hwe = find_hwe(hwtable, pp->vendor_id, pp->product_id, NULL);
+       find_hwe(hwtable, pp->vendor_id, pp->product_id, NULL, pp->hwe);
 
        /*
         * host / bus / target / lun
@@ -1225,7 +1308,7 @@ ccw_sysfs_pathinfo (struct path * pp, vector hwtable)
                        pp->sg_id.lun);
        }
 
-       return 0;
+       return PATHINFO_OK;
 }
 
 static int
@@ -1249,27 +1332,27 @@ cciss_sysfs_pathinfo (struct path * pp, vector hwtable)
                parent = udev_device_get_parent(parent);
        }
        if (!attr_path || pp->sg_id.host_no == -1)
-               return 1;
+               return PATHINFO_FAILED;
 
        if (sysfs_get_vendor(parent, pp->vendor_id, SCSI_VENDOR_SIZE) <= 0)
-               return 1;
+               return PATHINFO_FAILED;
 
        condlog(3, "%s: vendor = %s", pp->dev, pp->vendor_id);
 
-       if (sysfs_get_model(parent, pp->product_id, SCSI_PRODUCT_SIZE) <= 0)
-               return 1;
+       if (sysfs_get_model(parent, pp->product_id, PATH_PRODUCT_SIZE) <= 0)
+               return PATHINFO_FAILED;
 
        condlog(3, "%s: product = %s", pp->dev, pp->product_id);
 
-       if (sysfs_get_rev(parent, pp->rev, SCSI_REV_SIZE) <= 0)
-               return 1;
+       if (sysfs_get_rev(parent, pp->rev, PATH_REV_SIZE) <= 0)
+               return PATHINFO_FAILED;
 
        condlog(3, "%s: rev = %s", pp->dev, pp->rev);
 
        /*
         * set the hwe configlet pointer
         */
-       pp->hwe = find_hwe(hwtable, pp->vendor_id, pp->product_id, pp->rev);
+       find_hwe(hwtable, pp->vendor_id, pp->product_id, pp->rev, pp->hwe);
 
        /*
         * host / bus / target / lun
@@ -1282,7 +1365,8 @@ cciss_sysfs_pathinfo (struct path * pp, vector hwtable)
                pp->sg_id.channel,
                pp->sg_id.scsi_id,
                pp->sg_id.lun);
-       return 0;
+
+       return PATHINFO_OK;
 }
 
 static int
@@ -1291,23 +1375,23 @@ common_sysfs_pathinfo (struct path * pp)
        dev_t devt;
 
        if (!pp)
-               return 1;
+               return PATHINFO_FAILED;
 
        if (!pp->udev) {
                condlog(4, "%s: udev not initialised", pp->dev);
-               return 1;
+               return PATHINFO_FAILED;
        }
        devt = udev_device_get_devnum(pp->udev);
        snprintf(pp->dev_t, BLK_DEV_SIZE, "%d:%d", major(devt), minor(devt));
 
-       condlog(3, "%s: dev_t = %s", pp->dev, pp->dev_t);
+       condlog(4, "%s: dev_t = %s", pp->dev, pp->dev_t);
 
        if (sysfs_get_size(pp, &pp->size))
-               return 1;
+               return PATHINFO_FAILED;
 
        condlog(3, "%s: size = %llu", pp->dev, pp->size);
 
-       return 0;
+       return PATHINFO_OK;
 }
 
 int
@@ -1316,14 +1400,22 @@ path_offline (struct path * pp)
        struct udev_device * parent;
        char buff[SCSI_STATE_SIZE];
        int err;
+       const char *subsys_type;
 
-       if (pp->bus != SYSFS_BUS_SCSI)
+       if (pp->bus == SYSFS_BUS_SCSI) {
+               subsys_type = "scsi";
+       }
+       else if (pp->bus == SYSFS_BUS_NVME) {
+               subsys_type = "nvme";
+       }
+       else {
                return PATH_UP;
+       }
 
        parent = pp->udev;
        while (parent) {
                const char *subsys = udev_device_get_subsystem(parent);
-               if (subsys && !strncmp(subsys, "scsi", 4))
+               if (subsys && !strncmp(subsys, subsys_type, 4))
                        break;
                parent = udev_device_get_parent(parent);
        }
@@ -1343,17 +1435,33 @@ path_offline (struct path * pp)
        }
 
 
-       condlog(3, "%s: path state = %s", pp->dev, buff);
+       condlog(4, "%s: path state = %s", pp->dev, buff);
+
+       if (pp->bus == SYSFS_BUS_SCSI) {
+               if (!strncmp(buff, "offline", 7)) {
+                       pp->offline = 1;
+                       return PATH_DOWN;
+               }
+               pp->offline = 0;
+               if (!strncmp(buff, "blocked", 7) ||
+                   !strncmp(buff, "quiesce", 7))
+                       return PATH_PENDING;
+               else if (!strncmp(buff, "running", 7))
+                       return PATH_UP;
 
-       if (!strncmp(buff, "offline", 7)) {
-               pp->offline = 1;
-               return PATH_DOWN;
        }
-       pp->offline = 0;
-       if (!strncmp(buff, "blocked", 7) || !strncmp(buff, "quiesce", 7))
-               return PATH_PENDING;
-       else if (!strncmp(buff, "running", 7))
-               return PATH_UP;
+       else if (pp->bus == SYSFS_BUS_NVME) {
+               if (!strncmp(buff, "dead", 4)) {
+                       pp->offline = 1;
+                       return PATH_DOWN;
+               }
+               pp->offline = 0;
+               if (!strncmp(buff, "new", 3) ||
+                   !strncmp(buff, "deleting", 8))
+                       return PATH_PENDING;
+               else if (!strncmp(buff, "live", 4))
+                       return PATH_UP;
+       }
 
        return PATH_DOWN;
 }
@@ -1361,8 +1469,10 @@ path_offline (struct path * pp)
 int
 sysfs_pathinfo(struct path * pp, vector hwtable)
 {
-       if (common_sysfs_pathinfo(pp))
-               return 1;
+       int r = common_sysfs_pathinfo(pp);
+
+       if (r != PATHINFO_OK)
+               return r;
 
        pp->bus = SYSFS_BUS_UNDEF;
        if (!strncmp(pp->dev,"cciss",5))
@@ -1371,30 +1481,35 @@ sysfs_pathinfo(struct path * pp, vector hwtable)
                pp->bus = SYSFS_BUS_CCW;
        if (!strncmp(pp->dev,"sd", 2))
                pp->bus = SYSFS_BUS_SCSI;
-
-       if (pp->bus == SYSFS_BUS_UNDEF)
-               return 0;
-       else if (pp->bus == SYSFS_BUS_SCSI) {
-               if (scsi_sysfs_pathinfo(pp, hwtable))
-                       return 1;
-       } else if (pp->bus == SYSFS_BUS_CCW) {
-               if (ccw_sysfs_pathinfo(pp, hwtable))
-                       return 1;
-       } else if (pp->bus == SYSFS_BUS_CCISS) {
-               if (cciss_sysfs_pathinfo(pp, hwtable))
-                       return 1;
+       if (!strncmp(pp->dev,"nvme", 4))
+               pp->bus = SYSFS_BUS_NVME;
+
+       switch (pp->bus) {
+       case SYSFS_BUS_SCSI:
+               return scsi_sysfs_pathinfo(pp, hwtable);
+       case SYSFS_BUS_CCW:
+               return ccw_sysfs_pathinfo(pp, hwtable);
+       case SYSFS_BUS_CCISS:
+               return cciss_sysfs_pathinfo(pp, hwtable);
+       case SYSFS_BUS_NVME:
+               return nvme_sysfs_pathinfo(pp, hwtable);
+       case SYSFS_BUS_UNDEF:
+       default:
+               return PATHINFO_OK;
        }
-       return 0;
 }
 
-static int
-scsi_ioctl_pathinfo (struct path * pp, int mask)
+static void
+scsi_ioctl_pathinfo (struct path * pp, struct config *conf, int mask)
 {
        struct udev_device *parent;
        const char *attr_path = NULL;
 
+       if (pp->tpgs == TPGS_UNDEF)
+               detect_alua(pp, conf);
+
        if (!(mask & DI_SERIAL))
-               return 0;
+               return;
 
        parent = pp->udev;
        while (parent) {
@@ -1413,33 +1528,32 @@ scsi_ioctl_pathinfo (struct path * pp, int mask)
                parent = udev_device_get_parent(parent);
        }
        if (!attr_path || pp->sg_id.host_no == -1)
-               return 0;
+               return;
 
-       if (get_vpd_sysfs(parent, 0x80, pp->serial, SERIAL_SIZE) > 0)
-               condlog(3, "%s: serial = %s",
-                       pp->dev, pp->serial);
+       if (get_vpd_sysfs(parent, 0x80, pp->serial, SERIAL_SIZE) <= 0) {
+               if (get_serial(pp->serial, SERIAL_SIZE, pp->fd)) {
+                       condlog(3, "%s: fail to get serial", pp->dev);
+                       return;
+               }
+       }
 
-       return 0;
+       condlog(3, "%s: serial = %s", pp->dev, pp->serial);
+       return;
 }
 
-static int
-cciss_ioctl_pathinfo (struct path * pp, int mask)
+static void
+cciss_ioctl_pathinfo(struct path *pp)
 {
-       if (mask & DI_SERIAL) {
-               get_serial(pp->serial, SERIAL_SIZE, pp->fd);
-               condlog(3, "%s: serial = %s", pp->dev, pp->serial);
-       }
-       return 0;
+       get_serial(pp->serial, SERIAL_SIZE, pp->fd);
+       condlog(3, "%s: serial = %s", pp->dev, pp->serial);
 }
 
 int
-get_state (struct path * pp, struct config *conf, int daemon)
+get_state (struct path * pp, struct config *conf, int daemon, int oldstate)
 {
        struct checker * c = &pp->checker;
        int state;
 
-       condlog(3, "%s: get_state", pp->dev);
-
        if (!checker_selected(c)) {
                if (daemon) {
                        if (pathinfo(pp, conf, DI_SYSFS) != PATHINFO_OK) {
@@ -1448,6 +1562,7 @@ get_state (struct path * pp, struct config *conf, int daemon)
                                return PATH_UNCHECKED;
                        }
                }
+               select_detect_checker(conf, pp);
                select_checker(conf, pp);
                if (!checker_selected(c)) {
                        condlog(3, "%s: No checker selected", pp->dev);
@@ -1455,7 +1570,7 @@ get_state (struct path * pp, struct config *conf, int daemon)
                }
                checker_set_fd(c, pp->fd);
                if (checker_init(c, pp->mpp?&pp->mpp->mpcontext:NULL)) {
-                       memset(c, 0x0, sizeof(struct checker));
+                       checker_clear(c);
                        condlog(3, "%s: checker init failed", pp->dev);
                        return PATH_UNCHECKED;
                }
@@ -1470,12 +1585,13 @@ get_state (struct path * pp, struct config *conf, int daemon)
        if (!conf->checker_timeout &&
            sysfs_get_timeout(pp, &(c->timeout)) <= 0)
                c->timeout = DEF_TIMEOUT;
-       state = checker_check(c);
-       condlog(3, "%s: state = %s", pp->dev, checker_state_name(state));
+       state = checker_check(c, oldstate);
+       condlog(3, "%s: %s state = %s", pp->dev,
+               checker_name(c), checker_state_name(state));
        if (state != PATH_UP && state != PATH_GHOST &&
            strlen(checker_message(c)))
-               condlog(3, "%s: checker msg is \"%s\"",
-                       pp->dev, checker_message(c));
+               condlog(3, "%s: %s checker%s",
+                       pp->dev, checker_name(c), checker_message(c));
        return state;
 }
 
@@ -1484,6 +1600,8 @@ get_prio (struct path * pp)
 {
        struct prio * p;
        struct config *conf;
+       int checker_timeout;
+       int old_prio;
 
        if (!pp)
                return 0;
@@ -1491,9 +1609,10 @@ get_prio (struct path * pp)
        p = &pp->prio;
        if (!prio_selected(p)) {
                conf = get_multipath_config();
+               pthread_cleanup_push(put_multipath_config, conf);
                select_detect_prio(conf, pp);
                select_prio(conf, pp);
-               put_multipath_config(conf);
+               pthread_cleanup_pop(1);
                if (!prio_selected(p)) {
                        condlog(3, "%s: no prio selected", pp->dev);
                        pp->priority = PRIO_UNDEF;
@@ -1501,36 +1620,114 @@ get_prio (struct path * pp)
                }
        }
        conf = get_multipath_config();
-       pp->priority = prio_getprio(p, pp, conf->checker_timeout);
+       checker_timeout = conf->checker_timeout;
        put_multipath_config(conf);
+       old_prio = pp->priority;
+       pp->priority = prio_getprio(p, pp, checker_timeout);
        if (pp->priority < 0) {
                condlog(3, "%s: %s prio error", pp->dev, prio_name(p));
                pp->priority = PRIO_UNDEF;
                return 1;
        }
-       condlog(3, "%s: %s prio = %u",
+       condlog((old_prio == pp->priority ? 4 : 3), "%s: %s prio = %u",
                pp->dev, prio_name(p), pp->priority);
        return 0;
 }
 
+/*
+ * Mangle string of length *len starting at start
+ * by removing character sequence "00" (hex for a 0 byte),
+ * starting at end, backwards.
+ * Changes the value of *len if characters were removed.
+ * Returns a pointer to the position where "end" was moved to.
+ */
+static char
+*skip_zeroes_backward(char* start, int *len, char *end)
+{
+       char *p = end;
+
+       while (p >= start + 2 && *(p - 1) == '0' && *(p - 2) == '0')
+               p -= 2;
+
+       if (p == end)
+               return p;
+
+       memmove(p, end, start + *len + 1 - end);
+       *len -= end - p;
+
+       return p;
+}
+
+/*
+ * Fix for NVME wwids looking like this:
+ * nvme.0000-3163653363666438366239656630386200-4c696e75780000000000000000000000000000000000000000000000000000000000000000000000-00000002
+ * which are encountered in some combinations of Linux NVME host and target.
+ * The '00' are hex-encoded 0-bytes which are forbidden in the serial (SN)
+ * and model (MN) fields. Discard them.
+ * If a WWID of the above type is found, sets pp->wwid and returns a value > 0.
+ * Otherwise, returns 0.
+ */
+static int
+fix_broken_nvme_wwid(struct path *pp, const char *value, int size)
+{
+       static const char _nvme[] = "nvme.";
+       int len, i;
+       char mangled[256];
+       char *p;
+
+       len = strlen(value);
+       if (len >= sizeof(mangled))
+               return 0;
+
+       /* Check that value starts with "nvme.%04x-" */
+       if (memcmp(value, _nvme, sizeof(_nvme) - 1) || value[9] != '-')
+               return 0;
+       for (i = 5; i < 9; i++)
+               if (!isxdigit(value[i]))
+                       return 0;
+
+       memcpy(mangled, value, len + 1);
+
+       /* search end of "model" part and strip trailing '00' */
+       p = memrchr(mangled, '-', len);
+       if (p == NULL)
+               return 0;
+
+       p = skip_zeroes_backward(mangled, &len, p);
+
+       /* search end of "serial" part */
+       p = memrchr(mangled, '-', p - mangled);
+       if (p == NULL || memrchr(mangled, '-', p - mangled) != mangled + 9)
+           /* We expect exactly 3 '-' in the value */
+               return 0;
+
+       p = skip_zeroes_backward(mangled, &len, p);
+       if (len >= size)
+               return 0;
+
+       memcpy(pp->wwid, mangled, len + 1);
+       condlog(2, "%s: over-long WWID shortened to %s", pp->dev, pp->wwid);
+       return len;
+}
+
 static int
-get_udev_uid(struct path * pp, char *uid_attribute)
+get_udev_uid(struct path * pp, char *uid_attribute, struct udev_device *udev)
 {
        ssize_t len;
        const char *value;
 
-       value = udev_device_get_property_value(pp->udev,
-                                              uid_attribute);
+       value = udev_device_get_property_value(udev, uid_attribute);
        if (!value || strlen(value) == 0)
                value = getenv(uid_attribute);
        if (value && strlen(value)) {
-               if (strlen(value) + 1 > WWID_SIZE) {
+               len = strlcpy(pp->wwid, value, WWID_SIZE);
+               if (len >= WWID_SIZE) {
+                       len = fix_broken_nvme_wwid(pp, value, WWID_SIZE);
+                       if (len > 0)
+                               return len;
                        condlog(0, "%s: wwid overflow", pp->dev);
                        len = WWID_SIZE;
-               } else {
-                       len = strlen(value);
                }
-               strncpy(pp->wwid, value, len);
        } else {
                condlog(3, "%s: no %s attribute", pp->dev,
                        uid_attribute);
@@ -1551,11 +1748,59 @@ get_vpd_uid(struct path * pp)
                parent = udev_device_get_parent(parent);
        }
 
+       if (!parent)
+               return -EINVAL;
+
        return get_vpd_sysfs(parent, 0x83, pp->wwid, WWID_SIZE);
 }
 
-static int
-get_uid (struct path * pp, int path_state)
+static ssize_t uid_fallback(struct path *pp, int path_state,
+                           const char **origin)
+{
+       ssize_t len = -1;
+
+       if (pp->bus == SYSFS_BUS_SCSI &&
+           !strcmp(pp->uid_attribute, DEFAULT_UID_ATTRIBUTE)) {
+               len = get_vpd_uid(pp);
+               *origin = "sysfs";
+               pp->uid_attribute = NULL;
+               if (len < 0 && path_state == PATH_UP) {
+                       condlog(1, "%s: failed to get sysfs uid: %s",
+                               pp->dev, strerror(-len));
+                       len = get_vpd_sgio(pp->fd, 0x83, pp->wwid,
+                                          WWID_SIZE);
+                       *origin = "sgio";
+               }
+       } else if (pp->bus == SYSFS_BUS_NVME) {
+               char value[256];
+               len = sysfs_attr_get_value(pp->udev, "wwid", value,
+                                          sizeof(value));
+               if (len <= 0)
+                       return -1;
+               len = strlcpy(pp->wwid, value, WWID_SIZE);
+               if (len >= WWID_SIZE) {
+                       len = fix_broken_nvme_wwid(pp, value,
+                                                  WWID_SIZE);
+                       if (len > 0)
+                               return len;
+                       condlog(0, "%s: wwid overflow", pp->dev);
+                       len = WWID_SIZE;
+               }
+               *origin = "sysfs";
+               pp->uid_attribute = NULL;
+       }
+       return len;
+}
+
+static int has_uid_fallback(struct path *pp)
+{
+       return ((pp->bus == SYSFS_BUS_SCSI &&
+                !strcmp(pp->uid_attribute, DEFAULT_UID_ATTRIBUTE)) ||
+               pp->bus == SYSFS_BUS_NVME);
+}
+
+int
+get_uid (struct path * pp, int path_state, struct udev_device *udev)
 {
        char *c;
        const char *origin = "unknown";
@@ -1564,13 +1809,9 @@ get_uid (struct path * pp, int path_state)
 
        if (!pp->uid_attribute && !pp->getuid) {
                conf = get_multipath_config();
+               pthread_cleanup_push(put_multipath_config, conf);
                select_getuid(conf, pp);
-               put_multipath_config(conf);
-       }
-
-       if (!pp->udev) {
-               condlog(1, "%s: no udev information", pp->dev);
-               return 1;
+               pthread_cleanup_pop(1);
        }
 
        memset(pp->wwid, 0, WWID_SIZE);
@@ -1592,41 +1833,34 @@ get_uid (struct path * pp, int path_state)
                        len = strlen(pp->wwid);
                origin = "callout";
        } else {
-               int retrigger;
 
-               if (pp->uid_attribute) {
-                       len = get_udev_uid(pp, pp->uid_attribute);
+               if (udev && pp->uid_attribute) {
+                       len = get_udev_uid(pp, pp->uid_attribute, udev);
                        origin = "udev";
                        if (len <= 0)
                                condlog(1,
                                        "%s: failed to get udev uid: %s",
                                        pp->dev, strerror(-len));
 
-               } else {
+               } else if (pp->bus == SYSFS_BUS_SCSI) {
                        len = get_vpd_uid(pp);
                        origin = "sysfs";
                }
-               conf = get_multipath_config();
-               retrigger = conf->retrigger_tries;
-               put_multipath_config(conf);
-               if (len <= 0 && pp->retriggers >= retrigger &&
-                   !strcmp(pp->uid_attribute, DEFAULT_UID_ATTRIBUTE)) {
-                       len = get_vpd_uid(pp);
-                       origin = "sysfs";
-                       pp->uid_attribute = NULL;
-                       if (len < 0 && path_state == PATH_UP) {
-                               condlog(1, "%s: failed to get sysfs uid: %s",
-                                       pp->dev, strerror(-len));
-                               len = get_vpd_sgio(pp->fd, 0x83, pp->wwid,
-                                                  WWID_SIZE);
-                               origin = "sgio";
-                       }
+               if (len <= 0 && has_uid_fallback(pp)) {
+                       int retrigger_tries;
+
+                       conf = get_multipath_config();
+                       retrigger_tries = conf->retrigger_tries;
+                       put_multipath_config(conf);
+                       if (pp->retriggers >= retrigger_tries)
+                               len = uid_fallback(pp, path_state, &origin);
                }
        }
        if ( len < 0 ) {
                condlog(1, "%s: failed to get %s uid: %s",
                        pp->dev, origin, strerror(-len));
                memset(pp->wwid, 0x0, WWID_SIZE);
+               return 1;
        } else {
                /* Strip any trailing blanks */
                c = strchr(pp->wwid, '\0');
@@ -1641,15 +1875,38 @@ get_uid (struct path * pp, int path_state)
        return 0;
 }
 
-extern int
-pathinfo (struct path *pp, struct config *conf, int mask)
+int pathinfo(struct path *pp, struct config *conf, int mask)
 {
        int path_state;
 
-       if (!pp)
+       if (!pp || !conf)
                return PATHINFO_FAILED;
 
-       condlog(3, "%s: mask = 0x%x", pp->dev, mask);
+       /*
+        * For behavior backward-compatibility with multipathd,
+        * the blacklisting by filter_property|devnode() is not
+        * limited by DI_BLACKLIST and occurs before this debug
+        * message with the mask value.
+        */
+       if (pp->udev) {
+               const char *hidden =
+                       udev_device_get_sysattr_value(pp->udev, "hidden");
+
+               if (hidden && !strcmp(hidden, "1")) {
+                       condlog(4, "%s: hidden", pp->dev);
+                       return PATHINFO_SKIPPED;
+               }
+               if (is_claimed_by_foreign(pp->udev) ||
+                   filter_property(conf, pp->udev, 4) > 0)
+                       return PATHINFO_SKIPPED;
+       }
+
+       if (filter_devnode(conf->blist_devnode,
+                          conf->elist_devnode,
+                          pp->dev) > 0)
+               return PATHINFO_SKIPPED;
+
+       condlog(4, "%s: mask = 0x%x", pp->dev, mask);
 
        /*
         * Sanity check: we need the device number to
@@ -1664,19 +1921,33 @@ pathinfo (struct path *pp, struct config *conf, int mask)
        /*
         * fetch info available in sysfs
         */
-       if (mask & DI_SYSFS && sysfs_pathinfo(pp, conf->hwtable))
-               return PATHINFO_FAILED;
+       if (mask & DI_SYSFS) {
+               int rc = sysfs_pathinfo(pp, conf->hwtable);
+
+               if (rc != PATHINFO_OK)
+                       return rc;
+       }
 
        if (mask & DI_BLACKLIST && mask & DI_SYSFS) {
                if (filter_device(conf->blist_device, conf->elist_device,
-                                 pp->vendor_id, pp->product_id) > 0) {
+                                 pp->vendor_id, pp->product_id, pp->dev) > 0 ||
+                   filter_protocol(conf->blist_protocol, conf->elist_protocol,
+                                   pp) > 0)
                        return PATHINFO_SKIPPED;
-               }
        }
 
        path_state = path_offline(pp);
        if (path_state == PATH_REMOVED)
                goto blank;
+       else if (mask & DI_NOIO) {
+               if (mask & DI_CHECKER)
+                       /*
+                        * Avoid any IO on the device itself.
+                        * simply use the path_offline() return as its state
+                        */
+                       pp->chkrstate = pp->state = path_state;
+               return PATHINFO_OK;
+       }
 
        /*
         * fetch info not available through sysfs
@@ -1693,25 +1964,24 @@ pathinfo (struct path *pp, struct config *conf, int mask)
        if (mask & DI_SERIAL)
                get_geometry(pp);
 
-       if (path_state == PATH_UP && pp->bus == SYSFS_BUS_SCSI &&
-           scsi_ioctl_pathinfo(pp, mask))
-               goto blank;
+       if (path_state == PATH_UP && pp->bus == SYSFS_BUS_SCSI)
+               scsi_ioctl_pathinfo(pp, conf, mask);
 
-       if (pp->bus == SYSFS_BUS_CCISS &&
-           cciss_ioctl_pathinfo(pp, mask))
-               goto blank;
+       if (pp->bus == SYSFS_BUS_CCISS && mask & DI_SERIAL)
+               cciss_ioctl_pathinfo(pp);
 
        if (mask & DI_CHECKER) {
                if (path_state == PATH_UP) {
-                       pp->chkrstate = pp->state = get_state(pp, conf, 0);
-                       if (pp->state == PATH_UNCHECKED ||
+                       int newstate = get_state(pp, conf, 0, path_state);
+                       if (newstate != PATH_PENDING ||
+                           pp->state == PATH_UNCHECKED ||
                            pp->state == PATH_WILD)
-                               goto blank;
+                               pp->chkrstate = pp->state = newstate;
                        if (pp->state == PATH_TIMEOUT)
                                pp->state = PATH_DOWN;
                        if (pp->state == PATH_UP && !pp->size) {
                                condlog(3, "%s: device size is 0, "
-                                       "path unuseable", pp->dev);
+                                       "path unusable", pp->dev);
                                pp->state = PATH_GHOST;
                        }
                } else {
@@ -1724,10 +1994,14 @@ pathinfo (struct path *pp, struct config *conf, int mask)
        }
 
        if ((mask & DI_WWID) && !strlen(pp->wwid)) {
-               get_uid(pp, path_state);
+               get_uid(pp, path_state, pp->udev);
                if (!strlen(pp->wwid)) {
-                       pp->initialized = INIT_MISSING_UDEV;
-                       pp->tick = conf->retrigger_delay;
+                       if (pp->bus == SYSFS_BUS_UNDEF)
+                               return PATHINFO_SKIPPED;
+                       if (pp->initialized != INIT_FAILED) {
+                               pp->initialized = INIT_MISSING_UDEV;
+                               pp->tick = conf->retrigger_delay;
+                       }
                        return PATHINFO_OK;
                }
                else
@@ -1759,9 +2033,9 @@ blank:
        /*
         * Recoverable error, for example faulty or offline path
         */
-       memset(pp->wwid, 0, WWID_SIZE);
        pp->chkrstate = pp->state = PATH_DOWN;
-       pp->initialized = INIT_FAILED;
+       if (pp->initialized == INIT_NEW || pp->initialized == INIT_FAILED)
+               memset(pp->wwid, 0, WWID_SIZE);
 
        return PATHINFO_OK;
 }